// Update updates the given node-session. func (a *NodeSessionAPI) Update(ctx context.Context, req *pb.UpdateNodeSessionRequest) (*pb.UpdateNodeSessionResponse, error) { var appEUI, devEUI lorawan.EUI64 var appSKey, nwkSKey lorawan.AES128Key var devAddr lorawan.DevAddr if err := appEUI.UnmarshalText([]byte(req.AppEUI)); err != nil { return nil, err } if err := devEUI.UnmarshalText([]byte(req.DevEUI)); err != nil { return nil, err } if err := appSKey.UnmarshalText([]byte(req.AppSKey)); err != nil { return nil, err } if err := nwkSKey.UnmarshalText([]byte(req.NwkSKey)); err != nil { return nil, err } if err := devAddr.UnmarshalText([]byte(req.DevAddr)); err != nil { return nil, err } var cFList *lorawan.CFList if len(req.CFList) > len(cFList) { return nil, fmt.Errorf("max CFList channels is %d", len(cFList)) } if len(req.CFList) > 0 { cFList = &lorawan.CFList{} for i, f := range req.CFList { cFList[i] = f } } ns := models.NodeSession{ DevAddr: devAddr, AppEUI: appEUI, DevEUI: devEUI, AppSKey: appSKey, NwkSKey: nwkSKey, FCntUp: req.FCntUp, FCntDown: req.FCntDown, RXDelay: uint8(req.RxDelay), RX1DROffset: uint8(req.Rx1DROffset), CFList: cFList, } if err := a.validateNodeSession(ns); err != nil { return nil, err } if err := storage.SaveNodeSession(a.ctx.RedisPool, ns); err != nil { return nil, err } return &pb.UpdateNodeSessionResponse{}, nil }
func handleUplinkACK(ctx Context, ns models.NodeSession) error { txPayload, err := storage.ClearInProcessTXPayload(ctx.RedisPool, ns.DevEUI) if err != nil { return err } ns.FCntDown++ if err = storage.SaveNodeSession(ctx.RedisPool, ns); err != nil { return err } if txPayload != nil { err = ctx.Application.SendNotification(ns.AppEUI, ns.DevEUI, models.ACKNotificationType, models.ACKNotification{ Reference: txPayload.Reference, DevEUI: ns.DevEUI, }) if err != nil { return err } } return nil }
func handleDataDownReply(ctx Context, rxPacket models.RXPacket, ns models.NodeSession) error { macPL, ok := rxPacket.PHYPayload.MACPayload.(*lorawan.MACPayload) if !ok { return fmt.Errorf("expected *lorawan.MACPayload, got: %T", rxPacket.PHYPayload.MACPayload) } // get data down properies properties, err := getDataDownProperties(rxPacket.RXInfo, ns) if err != nil { return fmt.Errorf("get data down properties error: %s", err) } var frmMACCommands bool var macPayloads []models.MACPayload allMACPayloads, err := storage.ReadMACPayloadTXQueue(ctx.RedisPool, ns.DevAddr) if err != nil { return fmt.Errorf("read mac-payload tx queue error: %s", err) } if len(allMACPayloads) > 0 { if allMACPayloads[0].FRMPayload { // the first mac-commands must be sent as FRMPayload, filter the rest // of the MACPayload items with the same property, respecting the // max FRMPayload size for the data-rate. frmMACCommands = true macPayloads = storage.FilterMACPayloads(allMACPayloads, true, common.Band.MaxPayloadSize[properties.rx1DR].N) } else { // the first mac-command must be sent as FOpts, filter the rest of // the MACPayload items with the same property, respecting the // max FOpts size of 15. macPayloads = storage.FilterMACPayloads(allMACPayloads, false, 15) } } // if the MACCommands (if any) are not sent as FRMPayload, check if there // is a tx-payload in the queue and validate if the FOpts + FRMPayload // does not exceed the max payload size. var txPayload *models.TXPayload if !frmMACCommands { // check if there are payloads pending in the queue txPayload, err = getNextValidTXPayloadForDRFromQueue(ctx, ns, properties.rx1DR) if err != nil { return fmt.Errorf("get next valid tx-payload error: %s", err) } var macByteCount int for _, mac := range macPayloads { macByteCount += len(mac.MACCommand) } if txPayload != nil && len(txPayload.Data)+macByteCount > common.Band.MaxPayloadSize[properties.rx1DR].N { log.WithFields(log.Fields{ "data_rate": properties.rx1DR, "dev_eui": ns.DevEUI, "reference": txPayload.Reference, }).Info("scheduling tx-payload for next downlink, mac-commands + payload exceeds max size") txPayload = nil } } // convert the MACPayload items into MACCommand items var macCommmands []lorawan.MACCommand for _, pl := range macPayloads { var mac lorawan.MACCommand if err := mac.UnmarshalBinary(false, pl.MACCommand); err != nil { // in case the mac commands can't be unmarshaled, the payload // is ignored and an error sent to the network-controller errStr := fmt.Sprintf("unmarshal mac command error: %s", err) log.WithFields(log.Fields{ "dev_eui": ns.DevEUI, "reference": pl.Reference, }).Warning(errStr) err = ctx.Controller.SendErrorPayload(ns.AppEUI, ns.DevEUI, models.ErrorPayload{ Reference: pl.Reference, DevEUI: ns.DevEUI, Message: errStr, }) if err != nil { return fmt.Errorf("send error payload to network-controller error: %s", err) } continue } macCommmands = append(macCommmands, mac) } // uplink was unconfirmed and no downlink data in queue and no mac commands to send if txPayload == nil && rxPacket.PHYPayload.MHDR.MType == lorawan.UnconfirmedDataUp && len(macCommmands) == 0 { return nil } // get the queue size (the size includes the current payload) queueSize, err := storage.GetTXPayloadQueueSize(ctx.RedisPool, ns.DevEUI) if err != nil { return err } if txPayload != nil { queueSize-- // substract the current tx-payload from the queue-size } phy := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataDown, Major: lorawan.LoRaWANR1, }, } macPL = &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCtrl: lorawan.FCtrl{ ACK: rxPacket.PHYPayload.MHDR.MType == lorawan.ConfirmedDataUp, // set ACK when uplink packet was of type ConfirmedDataUp FPending: queueSize > 0 || len(allMACPayloads) != len(macPayloads), // items in the queue or not all mac commands being sent }, FCnt: ns.FCntDown, }, } phy.MACPayload = macPL if len(macCommmands) > 0 { if frmMACCommands { var fPort uint8 // 0 var frmPayload []lorawan.Payload for i := range macCommmands { frmPayload = append(frmPayload, &macCommmands[i]) } macPL.FPort = &fPort macPL.FRMPayload = frmPayload } else { macPL.FHDR.FOpts = macCommmands } } // add the payload to FRMPayload field // note that txPayload is by definition nil when there are mac commands // to send in the FRMPayload field. if txPayload != nil { if txPayload.Confirmed { phy.MHDR.MType = lorawan.ConfirmedDataDown } macPL.FPort = &txPayload.FPort macPL.FRMPayload = []lorawan.Payload{ &lorawan.DataPayload{Bytes: txPayload.Data}, } } // if there is no payload set, encrypt will just do nothing if len(macCommmands) > 0 && frmMACCommands { if err := phy.EncryptFRMPayload(ns.NwkSKey); err != nil { return fmt.Errorf("encrypt FRMPayload error: %s", err) } } else { if err := phy.EncryptFRMPayload(ns.AppSKey); err != nil { return fmt.Errorf("encrypt FRMPayload error: %s", err) } } if err := phy.SetMIC(ns.NwkSKey); err != nil { return fmt.Errorf("set MIC error: %s", err) } txPacket := models.TXPacket{ TXInfo: models.TXInfo{ MAC: rxPacket.RXInfo.MAC, Timestamp: rxPacket.RXInfo.Timestamp + uint32(properties.rxDelay/time.Microsecond), Frequency: properties.rx1Frequency, Power: common.Band.DefaultTXPower, DataRate: common.Band.DataRates[properties.rx1DR], CodeRate: rxPacket.RXInfo.CodeRate, }, PHYPayload: phy, } // window 1 if err := ctx.Gateway.SendTXPacket(txPacket); err != nil { return fmt.Errorf("send tx packet (rx window 1) to gateway error: %s", err) } // increment the FCntDown when MType != ConfirmedDataDown and clear // in-process queue. In case of ConfirmedDataDown we increment on ACK. if phy.MHDR.MType != lorawan.ConfirmedDataDown { ns.FCntDown++ if err = storage.SaveNodeSession(ctx.RedisPool, ns); err != nil { return err } if txPayload != nil { if _, err = storage.ClearInProcessTXPayload(ctx.RedisPool, ns.DevEUI); err != nil { return err } } } // remove the mac commands from the queue for _, pl := range macPayloads { if err = storage.DeleteMACPayloadFromTXQueue(ctx.RedisPool, ns.DevAddr, pl); err != nil { return fmt.Errorf("delete mac-payload from tx queue error: %s", err) } } return nil }
// handleCollectedJoinRequestPackets handles the received join-request. func handleCollectedJoinRequestPackets(ctx Context, rxPackets RXPackets) error { if len(rxPackets) == 0 { return errors.New("packet collector returned 0 packets") } rxPacket := rxPackets[0] var macs []string for _, p := range rxPackets { macs = append(macs, p.RXInfo.MAC.String()) } // MACPayload must be of type *lorawan.JoinRequestPayload jrPL, ok := rxPacket.PHYPayload.MACPayload.(*lorawan.JoinRequestPayload) if !ok { return fmt.Errorf("expected *lorawan.JoinRequestPayload, got: %T", rxPacket.PHYPayload.MACPayload) } log.WithFields(log.Fields{ "dev_eui": jrPL.DevEUI, "gw_count": len(rxPackets), "gw_macs": strings.Join(macs, ", "), "mtype": rxPackets[0].PHYPayload.MHDR.MType, }).Info("packet(s) collected") // get node information for this DevEUI node, err := storage.GetNode(ctx.DB, jrPL.DevEUI) if err != nil { return err } // validate the given nonce if !node.ValidateDevNonce(jrPL.DevNonce) { return fmt.Errorf("given dev-nonce %x has already been used before for node %s", jrPL.DevNonce, jrPL.DevEUI) } // get random (free) DevAddr devAddr, err := storage.GetRandomDevAddr(ctx.RedisPool, ctx.NetID) if err != nil { return fmt.Errorf("get random DevAddr error: %s", err) } // get app nonce appNonce, err := getAppNonce() if err != nil { return fmt.Errorf("get AppNonce error: %s", err) } // get the (optional) CFList cFList, err := storage.GetCFListForNode(ctx.DB, node) if err != nil { return fmt.Errorf("get CFList for node error: %s", err) } // get keys nwkSKey, err := getNwkSKey(node.AppKey, ctx.NetID, appNonce, jrPL.DevNonce) if err != nil { return fmt.Errorf("get NwkSKey error: %s", err) } appSKey, err := getAppSKey(node.AppKey, ctx.NetID, appNonce, jrPL.DevNonce) if err != nil { return fmt.Errorf("get AppSKey error: %s", err) } ns := models.NodeSession{ DevAddr: devAddr, DevEUI: jrPL.DevEUI, AppSKey: appSKey, NwkSKey: nwkSKey, FCntUp: 0, FCntDown: 0, AppEUI: node.AppEUI, RXDelay: node.RXDelay, RX1DROffset: node.RX1DROffset, CFList: cFList, } if err = storage.SaveNodeSession(ctx.RedisPool, ns); err != nil { return fmt.Errorf("save node-session error: %s", err) } // update the node (with updated used dev-nonces) if err = storage.UpdateNode(ctx.DB, node); err != nil { return fmt.Errorf("update node error: %s", err) } // construct the lorawan packet phy := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.JoinAccept, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.JoinAcceptPayload{ AppNonce: appNonce, NetID: ctx.NetID, DevAddr: ns.DevAddr, RXDelay: ns.RXDelay, DLSettings: lorawan.DLSettings{ RX2DataRate: uint8(common.Band.RX2DataRate), RX1DROffset: ns.RX1DROffset, }, CFList: cFList, }, } if err = phy.SetMIC(node.AppKey); err != nil { return fmt.Errorf("set MIC error: %s", err) } if err = phy.EncryptJoinAcceptPayload(node.AppKey); err != nil { return fmt.Errorf("encrypt join-accept error: %s", err) } // get data-rate uplinkDR, err := common.Band.GetDataRate(rxPacket.RXInfo.DataRate) if err != nil { return err } rx1DR := common.Band.RX1DataRate[uplinkDR][0] // get frequency rx1Freq, err := common.Band.GetRX1Frequency(rxPacket.RXInfo.Frequency) if err != nil { return err } txPacket := models.TXPacket{ TXInfo: models.TXInfo{ MAC: rxPacket.RXInfo.MAC, Timestamp: rxPacket.RXInfo.Timestamp + uint32(common.Band.JoinAcceptDelay1/time.Microsecond), Frequency: rx1Freq, Power: common.Band.DefaultTXPower, DataRate: common.Band.DataRates[rx1DR], CodeRate: rxPacket.RXInfo.CodeRate, }, PHYPayload: phy, } // window 1 if err = ctx.Gateway.SendTXPacket(txPacket); err != nil { return fmt.Errorf("send tx packet (rx window 1) to gateway error: %s", err) } // send a notification to the application that a node joined the network return ctx.Application.SendNotification(ns.AppEUI, ns.DevEUI, models.JoinNotificationType, models.JoinNotification{ DevAddr: ns.DevAddr, DevEUI: ns.DevEUI, }) }
func handleCollectedDataUpPackets(ctx Context, rxPackets RXPackets) error { if len(rxPackets) == 0 { return errors.New("packet collector returned 0 packets") } rxPacket := rxPackets[0] var macs []string for _, p := range rxPackets { macs = append(macs, p.RXInfo.MAC.String()) } macPL, ok := rxPacket.PHYPayload.MACPayload.(*lorawan.MACPayload) if !ok { return fmt.Errorf("expected *lorawan.MACPayload, got: %T", rxPacket.PHYPayload.MACPayload) } ns, err := storage.GetNodeSession(ctx.RedisPool, macPL.FHDR.DevAddr) if err != nil { return err } log.WithFields(log.Fields{ "dev_eui": ns.DevEUI, "gw_count": len(rxPackets), "gw_macs": strings.Join(macs, ", "), "mtype": rxPackets[0].PHYPayload.MHDR.MType, }).Info("packet(s) collected") // send rx info notification to be used by the network-controller if err = sendRXInfoPayload(ctx, ns.AppEUI, ns.DevEUI, rxPackets); err != nil { return fmt.Errorf("send rx info notification error: %s", err) } // handle FOpts mac commands (if any) if len(macPL.FHDR.FOpts) > 0 { if err := handleUplinkMACCommands(ctx, ns.AppEUI, ns.DevEUI, false, macPL.FHDR.FOpts); err != nil { return fmt.Errorf("handle FOpts mac commands error: %s", err) } } if macPL.FPort != nil { if *macPL.FPort == 0 { if len(macPL.FRMPayload) == 0 { return errors.New("expected mac commands, but FRMPayload is empty (FPort=0)") } // since the PHYPayload has been marshaled / unmarshaled when // storing it into and retrieving it from the database, we need // to decode the MAC commands from the FRMPayload. if err = rxPacket.PHYPayload.DecodeFRMPayloadToMACCommands(); err != nil { return fmt.Errorf("decode FRMPayload field to MACCommand items error: %s", err) } var commands []lorawan.MACCommand for _, pl := range macPL.FRMPayload { cmd, ok := pl.(*lorawan.MACCommand) if !ok { return fmt.Errorf("expected MACPayload, but got %T", macPL.FRMPayload) } commands = append(commands, *cmd) } if err := handleUplinkMACCommands(ctx, ns.AppEUI, ns.DevEUI, true, commands); err != nil { return fmt.Errorf("handle FRMPayload mac commands error: %s", err) } } else { var data []byte // it is possible that the FRMPayload is empty, in this case only // the FPort will be send if len(macPL.FRMPayload) == 1 { dataPL, ok := macPL.FRMPayload[0].(*lorawan.DataPayload) if !ok { return errors.New("FRMPayload must be of type *lorawan.DataPayload") } data = dataPL.Bytes } err = ctx.Application.SendRXPayload(ns.AppEUI, ns.DevEUI, models.RXPayload{ DevEUI: ns.DevEUI, GatewayCount: len(rxPackets), FPort: *macPL.FPort, RSSI: rxPacket.RXInfo.RSSI, Data: data, }) if err != nil { return fmt.Errorf("send rx payload to application error: %s", err) } } } // sync counter with that of the device ns.FCntUp = macPL.FHDR.FCnt if err := storage.SaveNodeSession(ctx.RedisPool, ns); err != nil { return err } // handle uplink ACK if macPL.FHDR.FCtrl.ACK { if err := handleUplinkACK(ctx, ns); err != nil { return fmt.Errorf("handle uplink ack error: %s", err) } } // handle downlink (ACK) time.Sleep(CollectDataDownWait) if err := handleDataDownReply(ctx, rxPacket, ns); err != nil { return fmt.Errorf("handling downlink data for node %s failed: %s", ns.DevEUI, err) } return nil }