Example #1
0
func TestLogs(t *testing.T) {
	a := New(t)

	store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-downlink"))
	h := &handler{
		applications: store,
	}
	m := &handlerManager{handler: h}

	msg := &pb.DryDownlinkMessage{
		Fields: `{ "foo": [ 1, 2, 3 ] }`,
		App: &pb.Application{
			Encoder: `
				function Encoder (fields) {
					console.log("foo", 1, "bar", new Date(0))
					console.log(1, { baz: 10, baa: "foo", bal: { "bar": 10 }})
					return fields.foo
				}`,
		},
	}

	res, err := m.DryDownlink(context.TODO(), msg)
	a.So(err, ShouldBeNil)
	a.So(res.Logs, ShouldResemble, []*pb.LogEntry{
		&pb.LogEntry{
			Function: "encoder",
			Fields:   []string{`"foo"`, "1", `"bar"`, `"1970-01-01T00:00:00.000Z"`},
		},
		&pb.LogEntry{
			Function: "encoder",
			Fields:   []string{"1", `{"baa":"foo","bal":{"bar":10},"baz":10}`},
		},
	})
}
func TestConvertFieldsDownNoPort(t *testing.T) {
	a := New(t)
	appID := "AppID-1"

	h := &handler{
		applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-convert-fields-down"),
	}

	// Case1: No Encoder
	ttnDown, appDown := buildConversionDownlink()
	err := h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown)
	a.So(err, ShouldBeNil)
	a.So(appDown.PayloadRaw, ShouldBeEmpty)

	// Case2: Normal flow with Encoder
	h.applications.Set(&application.Application{
		AppID: appID,
		// Encoder takes JSON fields as argument and return the payload as []byte
		Encoder: `function Encoder (payload){
  		return [ 1, 2, 3, 4, 5, 6, 7 ]
		}`,
	})
	defer func() {
		h.applications.Delete(appID)
	}()

	ttnDown, appDown = buildConversionDownlink()
	err = h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown)
	a.So(err, ShouldBeNil)
	a.So(appDown.PayloadRaw, ShouldResemble, []byte{1, 2, 3, 4, 5, 6, 7})
}
Example #3
0
// NewRedisHandler creates a new Redis-backed Handler
func NewRedisHandler(client *redis.Client, ttnBrokerID string) Handler {
	return &handler{
		devices:      device.NewRedisDeviceStore(client, "handler"),
		applications: application.NewRedisApplicationStore(client, "handler"),
		ttnBrokerID:  ttnBrokerID,
	}
}
Example #4
0
func TestDryDownlinkPayload(t *testing.T) {
	a := New(t)

	store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-downlink"))
	h := &handler{
		applications: store,
	}
	m := &handlerManager{handler: h}

	msg := &pb.DryDownlinkMessage{
		Payload: []byte{0x1, 0x2, 0x3},
		App: &pb.Application{
			Encoder: `function (fields) { return fields.foo }`,
		},
	}

	res, err := m.DryDownlink(context.TODO(), msg)
	a.So(err, ShouldBeNil)

	a.So(res.Payload, ShouldResemble, []byte{0x1, 0x2, 0x3})
	a.So(res.Logs, ShouldResemble, []*pb.LogEntry(nil))

	// make sure no calls to app store were made
	a.So(store.Count("list"), ShouldEqual, 0)
	a.So(store.Count("get"), ShouldEqual, 0)
	a.So(store.Count("set"), ShouldEqual, 0)
	a.So(store.Count("delete"), ShouldEqual, 0)
}
Example #5
0
func TestDryUplinkEmptyApp(t *testing.T) {
	a := New(t)

	store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-uplink"))
	h := &handler{
		applications: store,
	}
	m := &handlerManager{handler: h}

	dryUplinkMessage := &pb.DryUplinkMessage{
		Payload: []byte{11, 22, 33},
	}

	res, err := m.DryUplink(context.TODO(), dryUplinkMessage)
	a.So(err, ShouldBeNil)

	a.So(res.Payload, ShouldResemble, dryUplinkMessage.Payload)
	a.So(res.Fields, ShouldEqual, "")
	a.So(res.Valid, ShouldBeTrue)

	// make sure no calls to app store were made
	a.So(store.Count("list"), ShouldEqual, 0)
	a.So(store.Count("get"), ShouldEqual, 0)
	a.So(store.Count("set"), ShouldEqual, 0)
	a.So(store.Count("delete"), ShouldEqual, 0)
}
func TestConvertFieldsUp(t *testing.T) {
	a := New(t)
	appID := "AppID-1"

	h := &handler{
		applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-convert-fields-up"),
	}

	// No functions
	ttnUp, appUp := buildConversionUplink(appID)
	err := h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp)
	a.So(err, ShouldBeNil)
	a.So(appUp.PayloadFields, ShouldBeEmpty)

	// Normal flow
	app := &application.Application{
		AppID:   appID,
		Decoder: `function Decoder (data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`,
	}
	a.So(h.applications.Set(app), ShouldBeNil)
	defer func() {
		h.applications.Delete(appID)
	}()
	ttnUp, appUp = buildConversionUplink(appID)
	err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp)
	a.So(err, ShouldBeNil)

	a.So(appUp.PayloadFields, ShouldResemble, map[string]interface{}{
		"temperature": 21.6,
	})

	// Invalidate data
	app.StartUpdate()
	app.Validator = `function Validator (data) { return false; }`
	h.applications.Set(app)
	ttnUp, appUp = buildConversionUplink(appID)
	err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp)
	a.So(err, ShouldNotBeNil)
	a.So(appUp.PayloadFields, ShouldBeEmpty)

	// Function error
	app.StartUpdate()
	app.Validator = `function Validator (data) { throw "expected"; }`
	h.applications.Set(app)
	ttnUp, appUp = buildConversionUplink(appID)
	err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp)
	a.So(err, ShouldBeNil)
	a.So(appUp.PayloadFields, ShouldBeEmpty)
}
Example #7
0
func TestDryUplinkFields(t *testing.T) {
	a := New(t)

	store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-uplink"))
	h := &handler{
		applications: store,
	}
	m := &handlerManager{handler: h}

	dryUplinkMessage := &pb.DryUplinkMessage{
		Payload: []byte{11, 22, 33},
		App: &pb.Application{
			AppId: "DryUplinkFields",
			Decoder: `function Decoder (bytes) {
				console.log("hi", 11)
				return { length: bytes.length }}`,
			Converter: `function Converter (obj) {
				console.log("foo")
				return obj
			}`,
			Validator: `function Validator (bytes) { return true; }`,
		},
	}

	res, err := m.DryUplink(context.TODO(), dryUplinkMessage)
	a.So(err, ShouldBeNil)

	a.So(res.Payload, ShouldResemble, dryUplinkMessage.Payload)
	a.So(res.Fields, ShouldEqual, `{"length":3}`)
	a.So(res.Valid, ShouldBeTrue)
	a.So(res.Logs, ShouldResemble, []*pb.LogEntry{
		&pb.LogEntry{
			Function: "decoder",
			Fields:   []string{`"hi"`, "11"},
		},
		&pb.LogEntry{
			Function: "converter",
			Fields:   []string{`"foo"`},
		},
	})

	// make sure no calls to app store were made
	a.So(store.Count("list"), ShouldEqual, 0)
	a.So(store.Count("get"), ShouldEqual, 0)
	a.So(store.Count("set"), ShouldEqual, 0)
	a.So(store.Count("delete"), ShouldEqual, 0)
}
Example #8
0
func TestDryDownlinkEmptyApp(t *testing.T) {
	a := New(t)

	store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-downlink"))
	h := &handler{
		applications: store,
	}
	m := &handlerManager{handler: h}

	msg := &pb.DryDownlinkMessage{
		Fields: `{ "foo": [ 1, 2, 3 ] }`,
	}

	_, err := m.DryDownlink(context.TODO(), msg)
	a.So(err, ShouldNotBeNil)

	// make sure no calls to app store were made
	a.So(store.Count("list"), ShouldEqual, 0)
	a.So(store.Count("get"), ShouldEqual, 0)
	a.So(store.Count("set"), ShouldEqual, 0)
	a.So(store.Count("delete"), ShouldEqual, 0)
}
Example #9
0
func TestDryDownlinkFields(t *testing.T) {
	a := New(t)

	store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-downlink"))
	h := &handler{
		applications: store,
	}
	m := &handlerManager{handler: h}

	msg := &pb.DryDownlinkMessage{
		Fields: `{ "foo": [ 1, 2, 3 ] }`,
		App: &pb.Application{
			Encoder: `
				function Encoder (fields) {
					console.log("hello", { foo: 33 })
					return fields.foo
				}`,
		},
	}

	res, err := m.DryDownlink(context.TODO(), msg)
	a.So(err, ShouldBeNil)

	a.So(res.Payload, ShouldResemble, []byte{1, 2, 3})
	a.So(res.Logs, ShouldResemble, []*pb.LogEntry{
		&pb.LogEntry{
			Function: "encoder",
			Fields:   []string{`"hello"`, `{"foo":33}`},
		},
	})

	// make sure no calls to app store were made
	a.So(store.Count("list"), ShouldEqual, 0)
	a.So(store.Count("get"), ShouldEqual, 0)
	a.So(store.Count("set"), ShouldEqual, 0)
	a.So(store.Count("delete"), ShouldEqual, 0)
}
Example #10
0
func TestHandleUplink(t *testing.T) {
	a := New(t)
	var err error
	var wg WaitGroup
	appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8})
	appID := "appid"
	devEUI := types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8})
	devID := "devid"
	h := &handler{
		Component:    &component.Component{Ctx: GetLogger(t, "TestHandleUplink")},
		devices:      device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-uplink"),
		applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-handle-uplink"),
	}
	h.InitStatus()
	dev := &device.Device{
		AppID:  appID,
		DevID:  devID,
		AppEUI: appEUI,
		DevEUI: devEUI,
	}
	h.devices.Set(dev)
	defer func() {
		h.devices.Delete(appID, devID)
	}()
	h.applications.Set(&application.Application{
		AppID: appID,
	})
	defer func() {
		h.applications.Delete(appID)
	}()
	h.mqttUp = make(chan *types.UplinkMessage)
	h.mqttEvent = make(chan *types.DeviceEvent, 10)
	h.downlink = make(chan *pb_broker.DownlinkMessage)

	uplink, _ := buildLorawanUplink([]byte{0x40, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x0A, 0x4D, 0xDA, 0x23, 0x99, 0x61, 0xD4})

	downlinkEmpty := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x21, 0xEA, 0x8B, 0x0E}
	downlinkACK := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x20, 0x00, 0x00, 0x0A, 0x3B, 0x3F, 0x77, 0x0B}
	downlinkMAC := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x05, 0x00, 0x00, 0x03, 0x30, 0x00, 0x00, 0x00, 0x0A, 0x4D, 0x11, 0x55, 0x01}
	expected := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x66, 0xE6, 0x1D, 0x49, 0x82, 0x84}

	downlink := &pb_broker.DownlinkMessage{
		DownlinkOption: &pb_broker.DownlinkOption{
			ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{
				FCnt: 0,
			}}},
		},
	}

	// Test Uplink, no downlink option available
	wg.Add(1)
	go func() {
		<-h.mqttUp
		wg.Done()
	}()
	err = h.HandleUplink(uplink)
	a.So(err, ShouldBeNil)
	wg.WaitFor(50 * time.Millisecond)

	uplink.ResponseTemplate = downlink

	// Test Uplink, no downlink needed
	wg.Add(1)
	go func() {
		<-h.mqttUp
		wg.Done()
	}()
	downlink.Payload = downlinkEmpty
	err = h.HandleUplink(uplink)
	a.So(err, ShouldBeNil)
	wg.WaitFor(50 * time.Millisecond)

	// Test Uplink, ACK downlink needed
	wg.Add(2)
	go func() {
		<-h.mqttUp
		wg.Done()
	}()
	go func() {
		<-h.downlink
		wg.Done()
	}()
	downlink.Payload = downlinkACK
	err = h.HandleUplink(uplink)
	a.So(err, ShouldBeNil)
	wg.WaitFor(50 * time.Millisecond)

	// Test Uplink, MAC downlink needed
	wg.Add(2)
	go func() {
		<-h.mqttUp
		wg.Done()
	}()
	go func() {
		<-h.downlink
		wg.Done()
	}()
	downlink.Payload = downlinkMAC
	err = h.HandleUplink(uplink)
	a.So(err, ShouldBeNil)
	wg.WaitFor(50 * time.Millisecond)

	dev.StartUpdate()
	dev.NextDownlink = &types.DownlinkMessage{
		PayloadRaw: []byte{0xaa, 0xbc},
	}

	// Test Uplink, Data downlink needed
	h.devices.Set(dev)
	wg.Add(2)
	go func() {
		<-h.mqttUp
		wg.Done()
	}()
	go func() {
		dl := <-h.downlink
		a.So(dl.Payload, ShouldResemble, expected)
		wg.Done()
	}()
	downlink.Payload = downlinkEmpty
	err = h.HandleUplink(uplink)
	a.So(err, ShouldBeNil)
	wg.WaitFor(50 * time.Millisecond)

	dev, _ = h.devices.Get(appID, devID)
	a.So(dev.NextDownlink, ShouldBeNil)
}
Example #11
0
func TestHandleActivation(t *testing.T) {
	a := New(t)

	h := &handler{
		Component:    &component.Component{Ctx: GetLogger(t, "TestHandleActivation")},
		applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-activation"),
		devices:      device.NewRedisDeviceStore(GetRedisClient(), "handler-test-activation"),
	}
	h.InitStatus()
	h.mqttEvent = make(chan *types.DeviceEvent, 10)
	var wg WaitGroup

	appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}
	appID := appEUI.String()
	devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}
	devID := devEUI.String()
	unknownDevEUI := types.DevEUI{8, 7, 6, 5, 4, 3, 2, 1}

	appKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}

	h.applications.Set(&application.Application{
		AppID: appID,
	})
	defer func() {
		h.applications.Delete(appID)
	}()

	h.devices.Set(&device.Device{
		AppID:  appID,
		DevID:  devID,
		AppEUI: appEUI,
		DevEUI: devEUI,
		AppKey: appKey,
	})
	defer func() {
		h.devices.Delete(appID, devID)
	}()

	// Unknown
	res, err := doTestHandleActivation(h,
		appEUI,
		unknownDevEUI,
		[2]byte{1, 2},
		appKey,
	)
	a.So(err, ShouldNotBeNil)
	a.So(res, ShouldBeNil)

	// Wrong AppKey
	res, err = doTestHandleActivation(h,
		appEUI,
		devEUI,
		[2]byte{1, 2},
		[16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	)
	a.So(err, ShouldNotBeNil)
	a.So(res, ShouldBeNil)

	wg.Add(1)
	go func() {
		<-h.mqttEvent
		wg.Done()
	}()

	// Known
	res, err = doTestHandleActivation(h,
		appEUI,
		devEUI,
		[2]byte{1, 2},
		appKey,
	)
	a.So(err, ShouldBeNil)
	a.So(res, ShouldNotBeNil)

	wg.WaitFor(50 * time.Millisecond)

	// Same DevNonce used twice
	res, err = doTestHandleActivation(h,
		appEUI,
		devEUI,
		[2]byte{1, 2},
		appKey,
	)
	a.So(err, ShouldNotBeNil)
	a.So(res, ShouldBeNil)

	wg.Add(1)
	go func() {
		<-h.mqttEvent
		wg.Done()
	}()

	// Other DevNonce
	res, err = doTestHandleActivation(h,
		appEUI,
		devEUI,
		[2]byte{2, 1},
		appKey,
	)
	a.So(err, ShouldBeNil)
	a.So(res, ShouldNotBeNil)

	wg.WaitFor(50 * time.Millisecond)

	// TODO: Validate response

	// TODO: Check DB

}
Example #12
0
func TestHandleDownlink(t *testing.T) {
	a := New(t)
	var err error
	var wg WaitGroup
	appID := "app2"
	devID := "dev2"
	appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8})
	devEUI := types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8})
	h := &handler{
		Component:    &component.Component{Ctx: GetLogger(t, "TestHandleDownlink")},
		devices:      device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-downlink"),
		applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-enqueue-downlink"),
		downlink:     make(chan *pb_broker.DownlinkMessage),
		mqttEvent:    make(chan *types.DeviceEvent, 10),
	}
	h.InitStatus()
	// Neither payload nor Fields provided : ERROR
	err = h.HandleDownlink(&types.DownlinkMessage{
		AppID: appID,
		DevID: devID,
	}, &pb_broker.DownlinkMessage{
		AppEui: &appEUI,
		DevEui: &devEUI,
	})
	a.So(err, ShouldNotBeNil)

	h.devices.Set(&device.Device{
		AppID: appID,
		DevID: devID,
	})
	defer func() {
		h.devices.Delete(appID, devID)
	}()
	err = h.HandleDownlink(&types.DownlinkMessage{
		AppID: appID,
		DevID: devID,
	}, &pb_broker.DownlinkMessage{
		AppEui:  &appEUI,
		DevEui:  &devEUI,
		Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0},
	})
	a.So(err, ShouldBeNil)

	// Payload provided
	wg.Add(1)
	go func() {
		dl := <-h.downlink
		a.So(dl.Payload, ShouldNotBeEmpty)
		wg.Done()
	}()
	err = h.HandleDownlink(&types.DownlinkMessage{
		AppID:      appID,
		DevID:      devID,
		PayloadRaw: []byte{0xAA, 0xBC},
	}, &pb_broker.DownlinkMessage{
		AppEui:         &appEUI,
		DevEui:         &devEUI,
		Payload:        []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0},
		DownlinkOption: &pb_broker.DownlinkOption{},
	})
	a.So(err, ShouldBeNil)
	wg.WaitFor(100 * time.Millisecond)

	// Both Payload and Fields provided
	h.applications.Set(&application.Application{
		AppID: appID,
		Encoder: `function Encoder (payload){
	  		return [96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0]
			}`,
	})
	defer func() {
		h.applications.Delete(appID)
	}()
	jsonFields := map[string]interface{}{"temperature": 11}
	err = h.HandleDownlink(&types.DownlinkMessage{
		FPort:         1,
		AppID:         appID,
		DevID:         devID,
		PayloadFields: jsonFields,
		PayloadRaw:    []byte{0xAA, 0xBC},
	}, &pb_broker.DownlinkMessage{
		AppEui:  &appEUI,
		DevEui:  &devEUI,
		Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0},
	})
	a.So(err, ShouldNotBeNil)

	// JSON Fields provided
	wg.Add(1)
	go func() {
		dl := <-h.downlink
		a.So(dl.Payload, ShouldNotBeEmpty)
		wg.Done()
	}()
	err = h.HandleDownlink(&types.DownlinkMessage{
		FPort:         1,
		AppID:         appID,
		DevID:         devID,
		PayloadFields: jsonFields,
	}, &pb_broker.DownlinkMessage{
		AppEui:         &appEUI,
		DevEui:         &devEUI,
		Payload:        []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0},
		DownlinkOption: &pb_broker.DownlinkOption{},
	})
	a.So(err, ShouldBeNil)
	wg.WaitFor(100 * time.Millisecond)
}