// newReferenceUplink returns a default uplink message func newReferenceUplink() *pb.UplinkMessage { gtwID := "eui-0102030405060708" phy := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), }, }, } bytes, _ := phy.MarshalBinary() up := &pb.UplinkMessage{ Payload: bytes, ProtocolMetadata: &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{ CodingRate: "4/5", DataRate: "SF7BW125", Modulation: pb_lorawan.Modulation_LORA, }}}, GatewayMetadata: &pb_gateway.RxMetadata{ GatewayId: gtwID, Timestamp: 100, Frequency: 868100000, Rssi: -25.0, Snr: 5.0, }, } return up }
// newRXPacketFromRXPK transforms a Semtech packet into a models.RXPacket. func newRXPacketFromRXPK(mac lorawan.EUI64, rxpk RXPK) (models.RXPacket, error) { var phy lorawan.PHYPayload if err := phy.UnmarshalText([]byte(rxpk.Data)); err != nil { return models.RXPacket{}, fmt.Errorf("gateway: could not unmarshal PHYPayload: %s", err) } dataRate, err := newDataRateFromDatR(rxpk.DatR) if err != nil { return models.RXPacket{}, fmt.Errorf("gateway: could not get DataRate from DatR: %s", err) } rxPacket := models.RXPacket{ PHYPayload: phy, RXInfo: models.RXInfo{ MAC: mac, Time: time.Time(rxpk.Time), Timestamp: rxpk.Tmst, Frequency: int(rxpk.Freq * 1000000), Channel: int(rxpk.Chan), RFChain: int(rxpk.RFCh), CRCStatus: int(rxpk.Stat), DataRate: dataRate, CodeRate: rxpk.CodR, RSSI: int(rxpk.RSSI), LoRaSNR: rxpk.LSNR, Size: int(rxpk.Size), }, } return rxPacket, nil }
func msgFromPayload(payload []byte) (*pb_protocol.Message, error) { var phy lorawan.PHYPayload if err := phy.UnmarshalBinary(payload); err != nil { return nil, err } msg := pb_lorawan.MessageFromPHYPayload(phy) return &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &msg}}, nil }
// UnmarshalPayload unmarshals the Payload into Message if Message is nil func (m *DeviceActivationResponse) UnmarshalPayload() error { if m.GetMessage() == nil && m.GetDownlinkOption() != nil && m.DownlinkOption.GetProtocolConfig() != nil && m.DownlinkOption.ProtocolConfig.GetLorawan() != nil { var phy lorawan.PHYPayload if err := phy.UnmarshalBinary(m.Payload); err != nil { return err } msg := pb_lorawan.MessageFromPHYPayload(phy) m.Message = &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &msg}} } return nil }
func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_broker.DownlinkMessage, error) { // Get Device dev, err := n.devices.Get(*message.AppEui, *message.DevEui) if err != nil { return nil, err } n.status.downlink.Mark(1) dev.StartUpdate() if dev.AppID != message.AppId || dev.DevID != message.DevId { return nil, errors.NewErrInvalidArgument("Downlink", "AppID and DevID do not match AppEUI and DevEUI") } // Unmarshal LoRaWAN Payload var phyPayload lorawan.PHYPayload err = phyPayload.UnmarshalBinary(message.Payload) if err != nil { return nil, err } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { return nil, errors.NewErrInvalidArgument("Downlink", "does not contain a MAC payload") } // Set DevAddr macPayload.FHDR.DevAddr = lorawan.DevAddr(dev.DevAddr) // FIRST set and THEN increment FCntDown // TODO: For confirmed downlink, FCntDown should be incremented AFTER ACK macPayload.FHDR.FCnt = dev.FCntDown dev.FCntDown++ err = n.devices.Set(dev) if err != nil { return nil, err } // Sign MIC phyPayload.SetMIC(lorawan.AES128Key(dev.NwkSKey)) // Update message bytes, err := phyPayload.MarshalBinary() if err != nil { return nil, err } message.Payload = bytes return message, nil }
func doTestHandleActivation(h *handler, appEUI types.AppEUI, devEUI types.DevEUI, devNonce [2]byte, appKey types.AppKey) (*pb.DeviceActivationResponse, error) { devAddr := types.DevAddr{1, 2, 3, 4} requestPHY := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.JoinRequestPayload{ AppEUI: lorawan.EUI64(appEUI), DevEUI: lorawan.EUI64(devEUI), DevNonce: devNonce, }, } requestPHY.SetMIC(lorawan.AES128Key(appKey)) requestBytes, _ := requestPHY.MarshalBinary() responsePHY := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.JoinAccept, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.JoinAcceptPayload{}, } templateBytes, _ := responsePHY.MarshalBinary() return h.HandleActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ Payload: requestBytes, AppEui: &appEUI, AppId: appEUI.String(), DevEui: &devEUI, DevId: devEUI.String(), ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ Lorawan: &pb_lorawan.ActivationMetadata{ DevAddr: &devAddr, }, }}, ResponseTemplate: &pb_broker.DeviceActivationResponse{ Payload: templateBytes, }, }) }
func TestHandleUplink(t *testing.T) { a := New(t) ns := &networkServer{ devices: device.NewRedisDeviceStore(GetRedisClient(), "ns-test-handle-uplink"), } ns.InitStatus() appEUI := types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) devEUI := types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) devAddr := getDevAddr(1, 2, 3, 4) // Device Not Found message := &pb_broker.DeduplicatedUplinkMessage{ AppEui: &appEUI, DevEui: &devEUI, Payload: []byte{}, } _, err := ns.HandleUplink(message) a.So(err, ShouldNotBeNil) ns.devices.Set(&device.Device{ DevAddr: devAddr, AppEUI: appEUI, DevEUI: devEUI, }) defer func() { ns.devices.Delete(appEUI, devEUI) }() // Invalid Payload message = &pb_broker.DeduplicatedUplinkMessage{ AppEui: &appEUI, DevEui: &devEUI, Payload: []byte{}, } _, err = ns.HandleUplink(message) a.So(err, ShouldNotBeNil) phy := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), FCnt: 1, FCtrl: lorawan.FCtrl{ ADR: true, ADRACKReq: true, }, FOpts: []lorawan.MACCommand{ lorawan.MACCommand{CID: lorawan.LinkCheckReq}, }, }, }, } bytes, _ := phy.MarshalBinary() // Valid Uplink message = &pb_broker.DeduplicatedUplinkMessage{ AppEui: &appEUI, DevEui: &devEUI, Payload: bytes, ResponseTemplate: &pb_broker.DownlinkMessage{}, GatewayMetadata: []*pb_gateway.RxMetadata{ &pb_gateway.RxMetadata{}, }, ProtocolMetadata: &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{ Lorawan: &pb_lorawan.Metadata{ DataRate: "SF7BW125", }, }}, } res, err := ns.HandleUplink(message) a.So(err, ShouldBeNil) a.So(res.ResponseTemplate, ShouldNotBeNil) // LoRaWAN: Unmarshal var phyPayload lorawan.PHYPayload phyPayload.UnmarshalBinary(res.ResponseTemplate.Payload) macPayload, _ := phyPayload.MACPayload.(*lorawan.MACPayload) // ResponseTemplate DevAddr should match a.So([4]byte(macPayload.FHDR.DevAddr), ShouldEqual, [4]byte(devAddr)) // ResponseTemplate should ACK the ADRACKReq a.So(macPayload.FHDR.FCtrl.ACK, ShouldBeTrue) a.So(macPayload.FHDR.FOpts, ShouldHaveLength, 1) a.So(macPayload.FHDR.FOpts[0].Payload, ShouldResemble, &lorawan.LinkCheckAnsPayload{GwCnt: 1, Margin: 7}) // Frame Counter should have been updated dev, _ := ns.devices.Get(appEUI, devEUI) a.So(dev.FCntUp, ShouldEqual, 1) a.So(time.Now().Sub(dev.LastSeen), ShouldBeLessThan, 1*time.Second) }
func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (res *pb.DeviceActivationResponse, err error) { ctx := b.Ctx.WithFields(log.Fields{ "GatewayID": activation.GatewayMetadata.GatewayId, "AppEUI": *activation.AppEui, "DevEUI": *activation.DevEui, }) start := time.Now() defer func() { if err != nil { ctx.WithError(err).Warn("Could not handle activation") } else { ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled activation") } }() time := time.Now() b.status.activations.Mark(1) // De-duplicate uplink messages duplicates := b.deduplicateActivation(activation) if len(duplicates) == 0 { return nil, errDuplicateActivation } b.status.activationsUnique.Mark(1) base := duplicates[0] // Collect GatewayMetadata and DownlinkOptions var gatewayMetadata []*gateway.RxMetadata var downlinkOptions []*pb.DownlinkOption var deviceActivationResponse *pb.DeviceActivationResponse for _, duplicate := range duplicates { gatewayMetadata = append(gatewayMetadata, duplicate.GatewayMetadata) downlinkOptions = append(downlinkOptions, duplicate.DownlinkOptions...) } // Select best DownlinkOption if len(downlinkOptions) > 0 { deviceActivationResponse = &pb.DeviceActivationResponse{ DownlinkOption: selectBestDownlink(downlinkOptions), } } // Build Uplink deduplicatedActivationRequest := &pb.DeduplicatedDeviceActivationRequest{ Payload: base.Payload, DevEui: base.DevEui, AppEui: base.AppEui, ProtocolMetadata: base.ProtocolMetadata, GatewayMetadata: gatewayMetadata, ActivationMetadata: base.ActivationMetadata, ServerTime: time.UnixNano(), ResponseTemplate: deviceActivationResponse, } // Send Activate to NS deduplicatedActivationRequest, err = b.ns.PrepareActivation(b.Component.GetContext(b.nsToken), deduplicatedActivationRequest) if err != nil { return nil, errors.Wrap(errors.FromGRPCError(err), "NetworkServer refused to prepare activation") } ctx = ctx.WithFields(log.Fields{ "AppID": deduplicatedActivationRequest.AppId, "DevID": deduplicatedActivationRequest.DevId, }) // Find Handler (based on AppEUI) var announcements []*pb_discovery.Announcement announcements, err = b.Discovery.GetAllHandlersForAppID(deduplicatedActivationRequest.AppId) if err != nil { return nil, err } if len(announcements) == 0 { return nil, errors.NewErrNotFound(fmt.Sprintf("Handler for AppID %s", deduplicatedActivationRequest.AppId)) } ctx = ctx.WithField("NumHandlers", len(announcements)) // LoRaWAN: Unmarshal and prepare version without MIC var phyPayload lorawan.PHYPayload err = phyPayload.UnmarshalBinary(deduplicatedActivationRequest.Payload) if err != nil { return nil, err } correctMIC := phyPayload.MIC phyPayload.MIC = [4]byte{0, 0, 0, 0} phyPayloadWithoutMIC, err := phyPayload.MarshalBinary() if err != nil { return nil, err } // Build Challenge challenge := &pb.ActivationChallengeRequest{ Payload: phyPayloadWithoutMIC, AppId: deduplicatedActivationRequest.AppId, DevId: deduplicatedActivationRequest.DevId, AppEui: deduplicatedActivationRequest.AppEui, DevEui: deduplicatedActivationRequest.DevEui, } // Send Challenge to all handlers and collect responses var wg sync.WaitGroup responses := make(chan *challengeResponseWithHandler, len(announcements)) for _, announcement := range announcements { conn, err := b.getHandlerConn(announcement.Id) if err != nil { ctx.WithError(err).Warn("Could not dial handler for Activation") continue } client := pb_handler.NewHandlerClient(conn) // Do async request wg.Add(1) go func(announcement *pb_discovery.Announcement) { res, err := client.ActivationChallenge(b.Component.GetContext(""), challenge) if err == nil && res != nil { responses <- &challengeResponseWithHandler{ handler: announcement, client: client, response: res, } } wg.Done() }(announcement) } // Make sure to close channel when all requests are done go func() { wg.Wait() close(responses) }() var gotFirst bool var joinHandler *pb_discovery.Announcement var joinHandlerClient pb_handler.HandlerClient for res := range responses { var phyPayload lorawan.PHYPayload err = phyPayload.UnmarshalBinary(res.response.Payload) if err != nil { continue } if phyPayload.MIC != correctMIC { continue } if gotFirst { ctx.Warn("Duplicate Activation Response") } else { gotFirst = true joinHandler = res.handler joinHandlerClient = res.client } } // Activation not accepted by any broker if !gotFirst { ctx.Debug("Activation not accepted by any Handler") return nil, errors.New("Activation not accepted by any Handler") } ctx.WithField("HandlerID", joinHandler.Id).Debug("Forward Activation") handlerResponse, err := joinHandlerClient.Activate(b.Component.GetContext(""), deduplicatedActivationRequest) if err != nil { return nil, errors.Wrap(errors.FromGRPCError(err), "Handler refused activation") } handlerResponse, err = b.ns.Activate(b.Component.GetContext(b.nsToken), handlerResponse) if err != nil { return nil, errors.Wrap(errors.FromGRPCError(err), "NetworkServer refused activation") } res = &pb.DeviceActivationResponse{ Payload: handlerResponse.Payload, Message: handlerResponse.Message, DownlinkOption: handlerResponse.DownlinkOption, } return res, nil }
func (b *broker) HandleUplink(uplink *pb.UplinkMessage) (err error) { ctx := b.Ctx.WithField("GatewayID", uplink.GatewayMetadata.GatewayId) start := time.Now() defer func() { if err != nil { ctx.WithError(err).Warn("Could not handle uplink") } else { ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled uplink") } }() time := time.Now() b.status.uplink.Mark(1) // De-duplicate uplink messages duplicates := b.deduplicateUplink(uplink) if len(duplicates) == 0 { return nil } b.status.uplinkUnique.Mark(1) ctx = ctx.WithField("Duplicates", len(duplicates)) base := duplicates[0] if base.ProtocolMetadata.GetLorawan() == nil { return errors.NewErrInvalidArgument("Uplink", "does not contain LoRaWAN metadata") } // LoRaWAN: Unmarshal var phyPayload lorawan.PHYPayload err = phyPayload.UnmarshalBinary(base.Payload) if err != nil { return err } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { return errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") } // Request devices from NS devAddr := types.DevAddr(macPayload.FHDR.DevAddr) ctx = ctx.WithFields(log.Fields{ "DevAddr": devAddr, "FCnt": macPayload.FHDR.FCnt, }) var getDevicesResp *networkserver.DevicesResponse getDevicesResp, err = b.ns.GetDevices(b.Component.GetContext(b.nsToken), &networkserver.DevicesRequest{ DevAddr: &devAddr, FCnt: macPayload.FHDR.FCnt, }) if err != nil { return errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return devices") } b.status.deduplication.Update(int64(len(getDevicesResp.Results))) if len(getDevicesResp.Results) == 0 { return errors.NewErrNotFound(fmt.Sprintf("Device with DevAddr %s and FCnt <= %d", devAddr, macPayload.FHDR.FCnt)) } ctx = ctx.WithField("DevAddrResults", len(getDevicesResp.Results)) // Sort by FCntUp to optimize the number of MIC checks sort.Sort(ByFCntUp(getDevicesResp.Results)) // Find AppEUI/DevEUI through MIC check var device *pb_lorawan.Device var micChecks int var originalFCnt uint32 for _, candidate := range getDevicesResp.Results { nwkSKey := lorawan.AES128Key(*candidate.NwkSKey) // First check with the 16 bit counter micChecks++ ok, err = phyPayload.ValidateMIC(nwkSKey) if err != nil { return err } if ok { device = candidate break } originalFCnt = macPayload.FHDR.FCnt if candidate.Uses32BitFCnt { macPayload.FHDR.FCnt = fcnt.GetFull(candidate.FCntUp, uint16(originalFCnt)) // If 32 bit counter has different value, perform another MIC check if macPayload.FHDR.FCnt != originalFCnt { micChecks++ ok, err = phyPayload.ValidateMIC(nwkSKey) if err != nil { return err } if ok { device = candidate break } } } return errors.NewErrNotFound("device that validates MIC") } ctx = ctx.WithFields(log.Fields{ "MICChecks": micChecks, "DevEUI": device.DevEui, "AppEUI": device.AppEui, "AppID": device.AppId, "DevID": device.DevId, "FCnt": originalFCnt, }) if macPayload.FHDR.FCnt != originalFCnt { ctx = ctx.WithField("RealFCnt", macPayload.FHDR.FCnt) } if device.DisableFCntCheck { // TODO: Add warning to message? } else if device.FCntUp == 0 { } else if macPayload.FHDR.FCnt <= device.FCntUp || macPayload.FHDR.FCnt-device.FCntUp > maxFCntGap { // Replay attack or FCnt gap too big return errors.NewErrNotFound("device with matching FCnt") } // Add FCnt to Metadata (because it's not marshaled in lorawan payload) base.ProtocolMetadata.GetLorawan().FCnt = macPayload.FHDR.FCnt // Collect GatewayMetadata and DownlinkOptions var gatewayMetadata []*gateway.RxMetadata var downlinkOptions []*pb.DownlinkOption var downlinkMessage *pb.DownlinkMessage for _, duplicate := range duplicates { gatewayMetadata = append(gatewayMetadata, duplicate.GatewayMetadata) downlinkOptions = append(downlinkOptions, duplicate.DownlinkOptions...) } // Select best DownlinkOption if len(downlinkOptions) > 0 { downlinkMessage = &pb.DownlinkMessage{ DevEui: device.DevEui, AppEui: device.AppEui, AppId: device.AppId, DevId: device.DevId, DownlinkOption: selectBestDownlink(downlinkOptions), } } // Build Uplink deduplicatedUplink := &pb.DeduplicatedUplinkMessage{ Payload: base.Payload, DevEui: device.DevEui, DevId: device.DevId, AppEui: device.AppEui, AppId: device.AppId, ProtocolMetadata: base.ProtocolMetadata, GatewayMetadata: gatewayMetadata, ServerTime: time.UnixNano(), ResponseTemplate: downlinkMessage, } // Pass Uplink through NS deduplicatedUplink, err = b.ns.Uplink(b.Component.GetContext(b.nsToken), deduplicatedUplink) if err != nil { return errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not handle uplink") } var announcements []*pb_discovery.Announcement announcements, err = b.Discovery.GetAllHandlersForAppID(device.AppId) if err != nil { return err } if len(announcements) == 0 { return errors.NewErrNotFound(fmt.Sprintf("Handler for AppID %s", device.AppId)) } if len(announcements) > 1 { return errors.NewErrInternal(fmt.Sprintf("Multiple Handlers for AppID %s", device.AppId)) } var handler chan<- *pb.DeduplicatedUplinkMessage handler, err = b.getHandlerUplink(announcements[0].Id) if err != nil { return err } handler <- deduplicatedUplink 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) } // the last payload was received by the node if macPL.FHDR.FCtrl.ACK { txPayload, err := clearInProcessTXPayload(ctx.RedisPool, ns.DevEUI) if err != nil { return err } ns.FCntDown++ if err = saveNodeSession(ctx.RedisPool, ns); err != nil { return err } if txPayload != nil { err = ctx.Application.SendNotification(ns.DevEUI, ns.AppEUI, models.ACKNotificationType, models.ACKNotification{ Reference: txPayload.Reference, DevEUI: ns.DevEUI, }) if err != nil { return err } } } // check if there are payloads pending in the queue txPayload, remaining, err := getTXPayloadAndRemainingFromQueue(ctx.RedisPool, ns.DevEUI) if err != nil { return err } // nothing pending in the queue and no need to ACK RXPacket if rxPacket.PHYPayload.MHDR.MType != lorawan.ConfirmedDataUp && txPayload == nil { return nil } // get TX DR uplinkDR, err := Band.GetDataRate(rxPacket.RXInfo.DataRate) if err != nil { return err } // get TX channel uplinkChannel, err := Band.GetChannel(rxPacket.RXInfo.Frequency, uplinkDR) if err != nil { return err } // get RX1 channel rx1Channel := Band.GetRX1Channel(uplinkChannel) // get RX1 DR rx1DR, err := Band.GetRX1DataRateForOffset(uplinkDR, int(ns.RX1DROffset)) if err != nil { return err } // get rx delay rxDelay := Band.ReceiveDelay1 if ns.RXDelay > 0 { rxDelay = time.Duration(ns.RXDelay) * time.Second } 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 to true when received packet needs an ACK }, FCnt: ns.FCntDown, }, } phy.MACPayload = macPL // add the payload from the queue if txPayload != nil { // validate the max payload size if len(txPayload.Data) > Band.MaxPayloadSize[rx1DR].N { // remove the payload from the queue regarding confirmed or not if _, err := clearInProcessTXPayload(ctx.RedisPool, ns.DevEUI); err != nil { return err } log.WithFields(log.Fields{ "dev_eui": ns.DevEUI, "data_rate": rx1DR, "frmpayload_size": len(txPayload.Data), "max_frmpayload_size": Band.MaxPayloadSize[rx1DR].N, }).Warning("downlink payload max size exceeded") err = ctx.Application.SendNotification(ns.DevEUI, ns.AppEUI, models.ErrorNotificationType, models.ErrorNotification{ Reference: txPayload.Reference, DevEUI: ns.DevEUI, Message: fmt.Sprintf("downlink payload max size exceeded (dr: %d, allowed: %d, got: %d)", rx1DR, Band.MaxPayloadSize[rx1DR].N, len(txPayload.Data)), }) if err != nil { return err } } else { // remove the payload from the queue when not confirmed if !txPayload.Confirmed { if _, err := clearInProcessTXPayload(ctx.RedisPool, ns.DevEUI); err != nil { return err } } macPL.FHDR.FCtrl.FPending = remaining if txPayload.Confirmed { phy.MHDR.MType = lorawan.ConfirmedDataDown } macPL.FPort = &txPayload.FPort macPL.FRMPayload = []lorawan.Payload{ &lorawan.DataPayload{Bytes: txPayload.Data}, } } } // when the payload did not pass the validation and there is no ACK set, // there is nothing to send if !macPL.FHDR.FCtrl.ACK && len(macPL.FRMPayload) == 0 { return nil } // if there is no payload set, encrypt will just do nothing 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(rxDelay/time.Microsecond), Frequency: Band.DownlinkChannels[rx1Channel].Frequency, Power: Band.DefaultTXPower, DataRate: Band.DataRates[rx1DR], CodeRate: rxPacket.RXInfo.CodeRate, }, PHYPayload: phy, } // window 1 if err := ctx.Gateway.Send(txPacket); err != nil { return fmt.Errorf("send tx packet (rx window 1) to gateway error: %s", err) } // increment the FCntDown when MType != ConfirmedDataDown. In case of // ConfirmedDataDown we increment on ACK. if phy.MHDR.MType != lorawan.ConfirmedDataDown { ns.FCntDown++ if err := saveNodeSession(ctx.RedisPool, ns); err != nil { return 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 := 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 := 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 := 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, } if err = 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 = 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(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 TX DR uplinkDR, err := Band.GetDataRate(rxPacket.RXInfo.DataRate) if err != nil { return err } // get TX channel uplinkChannel, err := Band.GetChannel(rxPacket.RXInfo.Frequency, uplinkDR) if err != nil { return err } // get RX1 channel rx1Channel := Band.GetRX1Channel(uplinkChannel) // get RX1 DR rx1DR := Band.RX1DataRate[uplinkDR][0] txPacket := models.TXPacket{ TXInfo: models.TXInfo{ MAC: rxPacket.RXInfo.MAC, Timestamp: rxPacket.RXInfo.Timestamp + uint32(Band.JoinAcceptDelay1/time.Microsecond), Frequency: Band.DownlinkChannels[rx1Channel].Frequency, Power: Band.DefaultTXPower, DataRate: 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 (n *networkServer) HandlePrepareActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) { if activation.AppEui == nil || activation.DevEui == nil { return nil, errors.NewErrInvalidArgument("Activation", "missing AppEUI or DevEUI") } dev, err := n.devices.Get(*activation.AppEui, *activation.DevEui) if err != nil { return nil, err } activation.AppId = dev.AppID activation.DevId = dev.DevID // Get activation constraints (for DevAddr prefix selection) activationConstraints := strings.Split(dev.Options.ActivationConstraints, ",") if len(activationConstraints) == 1 && activationConstraints[0] == "" { activationConstraints = []string{} } activationConstraints = append(activationConstraints, "otaa") // Build activation metadata if not present if meta := activation.GetActivationMetadata(); meta == nil { activation.ActivationMetadata = &pb_protocol.ActivationMetadata{} } // Build lorawan metadata if not present if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil { return nil, errors.NewErrInvalidArgument("Activation", "missing LoRaWAN metadata") } // Build response template if not present if pld := activation.GetResponseTemplate(); pld == nil { return nil, errors.NewErrInvalidArgument("Activation", "missing response template") } lorawanMeta := activation.ActivationMetadata.GetLorawan() // Get a random device address devAddr, err := n.getDevAddr(activationConstraints...) if err != nil { return nil, err } // Set the DevAddr in the Activation Metadata lorawanMeta.DevAddr = &devAddr // Build JoinAccept Payload phy := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.JoinAccept, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.JoinAcceptPayload{ NetID: n.netID, DLSettings: lorawan.DLSettings{RX2DataRate: uint8(lorawanMeta.Rx2Dr), RX1DROffset: uint8(lorawanMeta.Rx1DrOffset)}, RXDelay: uint8(lorawanMeta.RxDelay), DevAddr: lorawan.DevAddr(devAddr), }, } if lorawanMeta.CfList != nil { var cfList lorawan.CFList for i, cfListItem := range lorawanMeta.CfList.Freq { cfList[i] = cfListItem } phy.MACPayload.(*lorawan.JoinAcceptPayload).CFList = &cfList } // Set the Payload phyBytes, err := phy.MarshalBinary() if err != nil { return nil, err } activation.ResponseTemplate.Payload = phyBytes return activation, nil }
func TestHandleUplink(t *testing.T) { a := New(t) b := getTestBroker(t) gtwID := "eui-0102030405060708" // Invalid Payload err := b.HandleUplink(&pb.UplinkMessage{ Payload: []byte{0x01, 0x02, 0x03}, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, ProtocolMetadata: &protocol.RxMetadata{}, }) a.So(err, ShouldNotBeNil) // Valid Payload phy := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), FCnt: 1, }, }, } bytes, _ := phy.MarshalBinary() // Device not found b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(&pb_networkserver.DevicesResponse{ Results: []*pb_lorawan.Device{}, }, nil) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldHaveSameTypeAs, &errors.ErrNotFound{}) devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} wrongDevEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 9} appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} appID := "appid-1" nwkSKey := types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8} // Add devices b = getTestBroker(t) nsResponse := &pb_networkserver.DevicesResponse{ Results: []*pb_lorawan.Device{ &pb_lorawan.Device{ DevEui: &wrongDevEUI, AppEui: &appEUI, AppId: appID, NwkSKey: &nwkSKey, FCntUp: 4, }, &pb_lorawan.Device{ DevEui: &devEUI, AppEui: &appEUI, AppId: appID, NwkSKey: &nwkSKey, FCntUp: 3, }, }, } b.handlers["handlerID"] = &handler{uplink: make(chan *pb.DeduplicatedUplinkMessage, 10)} // Device doesn't match b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldHaveSameTypeAs, &errors.ErrNotFound{}) phy.SetMIC(lorawan.AES128Key{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}) bytes, _ = phy.MarshalBinary() // Wrong FCnt b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldHaveSameTypeAs, &errors.ErrNotFound{}) // Disable FCnt Check b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) nsResponse.Results[0].DisableFCntCheck = true b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil) b.ns.EXPECT().Uplink(gomock.Any(), gomock.Any()) b.discovery.EXPECT().GetAllHandlersForAppID("appid-1").Return([]*pb_discovery.Announcement{ &pb_discovery.Announcement{ Id: "handlerID", }, }, nil) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldBeNil) // OK FCnt b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) nsResponse.Results[0].FCntUp = 0 nsResponse.Results[0].DisableFCntCheck = false b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil) b.ns.EXPECT().Uplink(gomock.Any(), gomock.Any()) b.discovery.EXPECT().GetAllHandlersForAppID("appid-1").Return([]*pb_discovery.Announcement{ &pb_discovery.Announcement{ Id: "handlerID", }, }, nil) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldBeNil) }
func (h *handler) ConvertToLoRaWAN(ctx log.Interface, appDown *types.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error { // Find Device dev, err := h.devices.Get(appDown.AppID, appDown.DevID) if err != nil { return err } // LoRaWAN: Unmarshal Downlink var phyPayload lorawan.PHYPayload err = phyPayload.UnmarshalBinary(ttnDown.Payload) if err != nil { return err } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { return errors.NewErrInvalidArgument("Downlink", "does not contain a MAC payload") } if ttnDown.DownlinkOption != nil && ttnDown.DownlinkOption.ProtocolConfig.GetLorawan() != nil { macPayload.FHDR.FCnt = ttnDown.DownlinkOption.ProtocolConfig.GetLorawan().FCnt } // Abort when downlink not needed if len(appDown.PayloadRaw) == 0 && !macPayload.FHDR.FCtrl.ACK && len(macPayload.FHDR.FOpts) == 0 { return ErrNotNeeded } // Set FPort if appDown.FPort != 0 { macPayload.FPort = &appDown.FPort } // Set Payload if len(appDown.PayloadRaw) > 0 { macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: appDown.PayloadRaw}} if macPayload.FPort == nil || *macPayload.FPort == 0 { macPayload.FPort = pointer.Uint8(1) } } else { macPayload.FRMPayload = []lorawan.Payload{} } // Encrypt err = phyPayload.EncryptFRMPayload(lorawan.AES128Key(dev.AppSKey)) if err != nil { return err } // Set MIC err = phyPayload.SetMIC(lorawan.AES128Key(dev.NwkSKey)) if err != nil { return err } // Marshal phyPayloadBytes, err := phyPayload.MarshalBinary() if err != nil { return err } ttnDown.Payload = phyPayloadBytes return nil }
func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *types.UplinkMessage) error { // Find Device dev, err := h.devices.Get(ttnUp.AppId, ttnUp.DevId) if err != nil { return err } // Check for LoRaWAN if lorawan := ttnUp.ProtocolMetadata.GetLorawan(); lorawan == nil { return errors.NewErrInvalidArgument("Activation", "does not contain LoRaWAN metadata") } // LoRaWAN: Unmarshal Uplink var phyPayload lorawan.PHYPayload err = phyPayload.UnmarshalBinary(ttnUp.Payload) if err != nil { return err } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { return errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") } macPayload.FHDR.FCnt = ttnUp.ProtocolMetadata.GetLorawan().FCnt appUp.FCnt = macPayload.FHDR.FCnt ctx = ctx.WithField("FCnt", appUp.FCnt) // LoRaWAN: Validate MIC ok, err = phyPayload.ValidateMIC(lorawan.AES128Key(dev.NwkSKey)) if err != nil { return err } if !ok { return errors.NewErrNotFound("device that validates MIC") } // LoRaWAN: Decrypt if macPayload.FPort != nil && *macPayload.FPort != 0 && len(macPayload.FRMPayload) == 1 { appUp.FPort = *macPayload.FPort ctx = ctx.WithField("FCnt", appUp.FPort) if err := phyPayload.DecryptFRMPayload(lorawan.AES128Key(dev.AppSKey)); err != nil { return errors.NewErrInternal("Could not decrypt payload") } if len(macPayload.FRMPayload) == 1 { payload, ok := macPayload.FRMPayload[0].(*lorawan.DataPayload) if !ok { return errors.NewErrInvalidArgument("Uplink FRMPayload", "must be of type *lorawan.DataPayload") } appUp.PayloadRaw = payload.Bytes } } // LoRaWAN: Publish ACKs as events if macPayload.FHDR.FCtrl.ACK { h.mqttEvent <- &types.DeviceEvent{ AppID: appUp.AppID, DevID: appUp.DevID, Event: types.DownlinkAckEvent, } } 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 }
func TestHandlePrepareActivation(t *testing.T) { a := New(t) ns := &networkServer{ netID: [3]byte{0x00, 0x00, 0x13}, prefixes: map[types.DevAddrPrefix][]string{ types.DevAddrPrefix{DevAddr: [4]byte{0x26, 0x00, 0x00, 0x00}, Length: 7}: []string{ "otaa", "local", }, }, devices: device.NewRedisDeviceStore(GetRedisClient(), "test-handle-prepare-activation"), } appEUI := types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)) devEUI := types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)) // Device not registered resp, err := ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ Lorawan: &pb_lorawan.ActivationMetadata{ CfList: &pb_lorawan.CFList{Freq: []uint32{867100000, 867300000, 867500000, 867700000, 867900000}}, }, }}, ResponseTemplate: &pb_broker.DeviceActivationResponse{}, }) a.So(err, ShouldNotBeNil) dev := &device.Device{AppEUI: appEUI, DevEUI: devEUI, Options: device.Options{ ActivationConstraints: "private", }} a.So(ns.devices.Set(dev), ShouldBeNil) defer func() { ns.devices.Delete(appEUI, devEUI) }() // Constrained Device resp, err = ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ DevEui: &devEUI, AppEui: &appEUI, ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ Lorawan: &pb_lorawan.ActivationMetadata{ CfList: &pb_lorawan.CFList{Freq: []uint32{867100000, 867300000, 867500000, 867700000, 867900000}}, }, }}, ResponseTemplate: &pb_broker.DeviceActivationResponse{}, }) a.So(err, ShouldNotBeNil) dev.StartUpdate() dev.Options = device.Options{} a.So(ns.devices.Set(dev), ShouldBeNil) // Device registered resp, err = ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ DevEui: &devEUI, AppEui: &appEUI, ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ Lorawan: &pb_lorawan.ActivationMetadata{ CfList: &pb_lorawan.CFList{Freq: []uint32{867100000, 867300000, 867500000, 867700000, 867900000}}, }, }}, ResponseTemplate: &pb_broker.DeviceActivationResponse{}, }) a.So(err, ShouldBeNil) devAddr := resp.ActivationMetadata.GetLorawan().DevAddr a.So(devAddr.IsEmpty(), ShouldBeFalse) a.So(devAddr[0]&254, ShouldEqual, 19<<1) // 7 MSB should be NetID var resPHY lorawan.PHYPayload resPHY.UnmarshalBinary(resp.ResponseTemplate.Payload) resMAC, _ := resPHY.MACPayload.(*lorawan.DataPayload) joinAccept := &lorawan.JoinAcceptPayload{} joinAccept.UnmarshalBinary(false, resMAC.Bytes) a.So(joinAccept.DevAddr[0]&254, ShouldEqual, 19<<1) a.So(*joinAccept.CFList, ShouldEqual, lorawan.CFList{867100000, 867300000, 867500000, 867700000, 867900000}) }
func TestHandleJoinRequestPackets(t *testing.T) { conf := common.GetTestConfig() Convey("Given a dummy gateway and application backend and a clean Postgres and Redis database", t, func() { a := &testApplicationBackend{ rxPayloadChan: make(chan models.RXPayload, 1), notificationPayloadChan: make(chan interface{}, 10), } g := &testGatewayBackend{ rxPacketChan: make(chan models.RXPacket, 1), txPacketChan: make(chan models.TXPacket, 1), } p := storage.NewRedisPool(conf.RedisURL) common.MustFlushRedis(p) db, err := storage.OpenDatabase(conf.PostgresDSN) So(err, ShouldBeNil) common.MustResetDB(db) ctx := Context{ RedisPool: p, Gateway: g, Application: a, DB: db, } Convey("Given a node and application in the database", func() { app := models.Application{ AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, Name: "test app", } So(storage.CreateApplication(ctx.DB, app), ShouldBeNil) node := models.Node{ DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, AppEUI: app.AppEUI, AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, RXDelay: 3, RX1DROffset: 2, } So(storage.CreateNode(ctx.DB, node), ShouldBeNil) Convey("Given a JoinRequest with correct DevEUI but incorrect AppEUI", func() { phy := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.JoinRequestPayload{ AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 9}, DevEUI: node.DevEUI, DevNonce: [2]byte{1, 2}, }, } So(phy.SetMIC(node.AppKey), ShouldBeNil) rxPacket := models.RXPacket{ PHYPayload: phy, RXInfo: models.RXInfo{ Frequency: common.Band.UplinkChannels[0].Frequency, DataRate: common.Band.DataRates[common.Band.UplinkChannels[0].DataRates[0]], }, } Convey("then handleRXPacket returns an error", func() { So(handleRXPacket(ctx, rxPacket), ShouldResemble, errors.New("node 0807060504030201 belongs to application 0102030405060708, 0102030405060709 was given")) }) }) Convey("Given a JoinRequest packet", func() { phy := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.JoinRequestPayload{ AppEUI: app.AppEUI, DevEUI: node.DevEUI, DevNonce: [2]byte{1, 2}, }, } So(phy.SetMIC(node.AppKey), ShouldBeNil) rxPacket := models.RXPacket{ PHYPayload: phy, RXInfo: models.RXInfo{ Frequency: common.Band.UplinkChannels[0].Frequency, DataRate: common.Band.DataRates[common.Band.UplinkChannels[0].DataRates[0]], }, } Convey("When calling handleRXPacket", func() { So(handleRXPacket(ctx, rxPacket), ShouldBeNil) Convey("Then a JoinAccept was sent to the node", func() { txPacket := <-g.txPacketChan phy := txPacket.PHYPayload So(phy.DecryptJoinAcceptPayload(node.AppKey), ShouldBeNil) So(phy.MHDR.MType, ShouldEqual, lorawan.JoinAccept) Convey("Then it was sent after 5s", func() { So(txPacket.TXInfo.Timestamp, ShouldEqual, rxPacket.RXInfo.Timestamp+uint32(5*time.Second/time.Microsecond)) }) Convey("Then the RXDelay is set to 3s", func() { jaPL := phy.MACPayload.(*lorawan.JoinAcceptPayload) So(jaPL.RXDelay, ShouldEqual, 3) }) Convey("Then the DLSettings are set correctly", func() { jaPL := phy.MACPayload.(*lorawan.JoinAcceptPayload) So(jaPL.DLSettings.RX2DataRate, ShouldEqual, uint8(common.Band.RX2DataRate)) So(jaPL.DLSettings.RX1DROffset, ShouldEqual, node.RX1DROffset) }) Convey("Then a node-session was created", func() { jaPL := phy.MACPayload.(*lorawan.JoinAcceptPayload) _, err := storage.GetNodeSession(ctx.RedisPool, jaPL.DevAddr) So(err, ShouldBeNil) }) Convey("Then the dev-nonce was added to the used dev-nonces", func() { node, err := storage.GetNode(ctx.DB, node.DevEUI) So(err, ShouldBeNil) So([2]byte{1, 2}, ShouldBeIn, node.UsedDevNonces) }) Convey("Then a join notification was sent to the application", func() { notification := <-a.notificationPayloadChan join, ok := notification.(models.JoinNotification) So(ok, ShouldBeTrue) So(join.DevEUI, ShouldResemble, node.DevEUI) }) }) }) }) }) }) }
func TestHandleDownlink(t *testing.T) { a := New(t) ns := &networkServer{ devices: device.NewRedisDeviceStore(GetRedisClient(), "test-handle-downlink"), } ns.InitStatus() appEUI := types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) devEUI := types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) devAddr := getDevAddr(1, 2, 3, 4) // Device Not Found message := &pb_broker.DownlinkMessage{ AppEui: &appEUI, DevEui: &devEUI, Payload: []byte{}, } _, err := ns.HandleDownlink(message) a.So(err, ShouldNotBeNil) ns.devices.Set(&device.Device{ DevAddr: devAddr, AppEUI: appEUI, DevEUI: devEUI, }) defer func() { ns.devices.Delete(appEUI, devEUI) }() // Invalid Payload message = &pb_broker.DownlinkMessage{ AppEui: &appEUI, DevEui: &devEUI, Payload: []byte{}, } _, err = ns.HandleDownlink(message) a.So(err, ShouldNotBeNil) fPort := uint8(3) phy := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataDown, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FPort: &fPort, FHDR: lorawan.FHDR{ FCtrl: lorawan.FCtrl{ ACK: true, }, }, }, } bytes, _ := phy.MarshalBinary() message = &pb_broker.DownlinkMessage{ AppEui: &appEUI, DevEui: &devEUI, Payload: bytes, } res, err := ns.HandleDownlink(message) a.So(err, ShouldBeNil) var phyPayload lorawan.PHYPayload phyPayload.UnmarshalBinary(res.Payload) macPayload, _ := phyPayload.MACPayload.(*lorawan.MACPayload) a.So(*macPayload.FPort, ShouldEqual, 3) a.So(macPayload.FHDR.DevAddr, ShouldEqual, lorawan.DevAddr{1, 2, 3, 4}) a.So(macPayload.FHDR.FCnt, ShouldEqual, 0) // The first Frame counter is zero a.So(phyPayload.MIC, ShouldNotEqual, [4]byte{0, 0, 0, 0}) // MIC should be set, we'll check it with actual examples in the integration test dev, _ := ns.devices.Get(appEUI, devEUI) a.So(dev.FCntDown, ShouldEqual, 1) }
func (r *router) HandleUplink(gatewayID string, uplink *pb.UplinkMessage) (err error) { ctx := r.Ctx.WithField("GatewayID", gatewayID) start := time.Now() defer func() { if err != nil { ctx.WithError(err).Warn("Could not handle uplink") } }() r.status.uplink.Mark(1) // LoRaWAN: Unmarshal var phyPayload lorawan.PHYPayload err = phyPayload.UnmarshalBinary(uplink.Payload) if err != nil { return err } if phyPayload.MHDR.MType == lorawan.JoinRequest { joinRequestPayload, ok := phyPayload.MACPayload.(*lorawan.JoinRequestPayload) if !ok { return errors.NewErrInvalidArgument("Join Request", "does not contain a JoinRequest payload") } devEUI := types.DevEUI(joinRequestPayload.DevEUI) appEUI := types.AppEUI(joinRequestPayload.AppEUI) ctx.WithFields(log.Fields{ "DevEUI": devEUI, "AppEUI": appEUI, }).Debug("Handle Uplink as Activation") r.HandleActivation(gatewayID, &pb.DeviceActivationRequest{ Payload: uplink.Payload, DevEui: &devEUI, AppEui: &appEUI, ProtocolMetadata: uplink.ProtocolMetadata, GatewayMetadata: uplink.GatewayMetadata, }) return nil } if lorawan := uplink.ProtocolMetadata.GetLorawan(); lorawan != nil { ctx = ctx.WithField("Modulation", lorawan.Modulation.String()) if lorawan.Modulation == pb_lorawan.Modulation_LORA { ctx = ctx.WithField("DataRate", lorawan.DataRate) } else { ctx = ctx.WithField("BitRate", lorawan.BitRate) } } if gateway := uplink.GatewayMetadata; gateway != nil { ctx = ctx.WithFields(log.Fields{ "Frequency": gateway.Frequency, "RSSI": gateway.Rssi, "SNR": gateway.Snr, }) } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { return errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") } devAddr := types.DevAddr(macPayload.FHDR.DevAddr) ctx = ctx.WithFields(log.Fields{ "DevAddr": devAddr, "FCnt": macPayload.FHDR.FCnt, }) gateway := r.getGateway(gatewayID) if err = gateway.HandleUplink(uplink); err != nil { return err } var downlinkOptions []*pb_broker.DownlinkOption if gateway.Schedule.IsActive() { downlinkOptions = r.buildDownlinkOptions(uplink, false, gateway) } ctx = ctx.WithField("DownlinkOptions", len(downlinkOptions)) // Find Broker brokers, err := r.Discovery.GetAllBrokersForDevAddr(devAddr) if err != nil { return err } if len(brokers) == 0 { ctx.Debug("No brokers to forward message to") return nil } ctx = ctx.WithField("NumBrokers", len(brokers)) // Forward to all brokers for _, broker := range brokers { broker, err := r.getBroker(broker) if err != nil { continue } broker.uplink <- &pb_broker.UplinkMessage{ Payload: uplink.Payload, ProtocolMetadata: uplink.ProtocolMetadata, GatewayMetadata: uplink.GatewayMetadata, DownlinkOptions: downlinkOptions, } } ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled uplink") return nil }