func TestgetRandomDevAddr(t *testing.T) { conf := getConfig() Convey("Given a Redis database and NetID 010203", t, func() { p := NewRedisPool(conf.RedisURL) netID := lorawan.NetID{1, 2, 3} Convey("When calling getRandomDevAddr many times, it should always return an unique DevAddr", func() { log := make(map[lorawan.DevAddr]struct{}) for i := 0; i < 1000; i++ { devAddr, err := getRandomDevAddr(p, netID) if err != nil { t.Fatal(err) } if devAddr.NwkID() != netID.NwkID() { t.Fatalf("%b must equal %b", devAddr.NwkID(), netID.NwkID()) } if len(log) != i { t.Fatalf("%d must equal %d", len(log), i) } log[devAddr] = struct{}{} } }) }) }
// getRandomDevAddr returns a random free DevAddr. Note that the 7 MSB will be // set to the NwkID (based on the configured NetID). // TODO: handle collission with retry? func getRandomDevAddr(p *redis.Pool, netID lorawan.NetID) (lorawan.DevAddr, error) { var d lorawan.DevAddr b := make([]byte, len(d)) if _, err := rand.Read(b); err != nil { return d, fmt.Errorf("could not read from random reader: %s", err) } copy(d[:], b) d[0] = d[0] & 1 // zero out 7 msb d[0] = d[0] ^ (netID.NwkID() << 1) // set 7 msb to NwkID c := p.Get() defer c.Close() key := "node_session_" + d.String() val, err := redis.Int(c.Do("EXISTS", key)) if err != nil { return lorawan.DevAddr{}, fmt.Errorf("test DevAddr %s exist error: %s", d, err) } if val == 1 { return lorawan.DevAddr{}, fmt.Errorf("DevAddr %s already exists", d) } return d, nil }
func run(c *cli.Context) error { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() // parse the NetID var netID lorawan.NetID if err := netID.UnmarshalText([]byte(c.String("net-id"))); err != nil { log.Fatalf("NetID parse error: %s", err) } // get the band config if c.String("band") == "" { log.Fatalf("--band is undefined, valid options are: %s", strings.Join(bands, ", ")) } bandConfig, err := band.GetConfig(band.Name(c.String("band"))) if err != nil { log.Fatal(err) } common.Band = bandConfig log.WithFields(log.Fields{ "version": version, "net_id": netID.String(), "band": c.String("band"), "docs": "https://docs.loraserver.io/", }).Info("starting LoRa Server") // connect to the database log.Info("connecting to postgresql") db, err := storage.OpenDatabase(c.String("postgres-dsn")) if err != nil { log.Fatalf("database connection error: %s", err) } // setup redis pool log.Info("setup redis connection pool") rp := storage.NewRedisPool(c.String("redis-url")) // setup gateway backend gw, err := gateway.NewBackend(c.String("gw-mqtt-server"), c.String("gw-mqtt-username"), c.String("gw-mqtt-password")) if err != nil { log.Fatalf("gateway-backend setup failed: %s", err) } // setup application backend app, err := application.NewBackend(rp, c.String("app-mqtt-server"), c.String("app-mqtt-username"), c.String("app-mqtt-password")) if err != nil { log.Fatalf("application-backend setup failed: %s", err) } // setup controller backend ctrl, err := controller.NewBackend(rp, c.String("controller-mqtt-server"), c.String("controller-mqtt-username"), c.String("controller-mqtt-password")) if err != nil { log.Fatalf("controller-backend setup failed: %s", err) } // auto-migrate the database if c.Bool("db-automigrate") { log.Info("applying database migrations") m := &migrate.AssetMigrationSource{ Asset: migrations.Asset, AssetDir: migrations.AssetDir, Dir: "", } n, err := migrate.Exec(db.DB, "postgres", m, migrate.Up) if err != nil { log.Fatalf("applying migrations failed: %s", err) } log.WithField("count", n).Info("migrations applied") } lsCtx := loraserver.Context{ DB: db, RedisPool: rp, Gateway: gw, Application: app, Controller: ctrl, NetID: netID, } // start the loraserver server := loraserver.NewServer(lsCtx) if err := server.Start(); err != nil { log.Fatal(err) } // setup the grpc api go func() { server := api.GetGRPCServer(ctx, lsCtx) list, err := net.Listen("tcp", c.String("grpc-bind")) if err != nil { log.Fatalf("error creating gRPC listener: %s", err) } log.WithField("bind", c.String("grpc-bind")).Info("starting gRPC server") log.Fatal(server.Serve(list)) }() // setup the http server r := mux.NewRouter() // setup json api jsonHandler, err := api.GetJSONGateway(ctx, lsCtx, c.String("grpc-bind")) if err != nil { log.Fatalf("get json gateway error: %s", err) } log.WithField("path", "/api/v1").Info("registering api handler and documentation endpoint") r.HandleFunc("/api/v1", api.SwaggerHandlerFunc).Methods("get") r.PathPrefix("/api/v1/").Handler(jsonHandler) // setup static file server (for the gui) log.WithField("path", "/").Info("registering gui handler") r.PathPrefix("/").Handler(http.FileServer(&assetfs.AssetFS{ Asset: static.Asset, AssetDir: static.AssetDir, AssetInfo: static.AssetInfo, Prefix: "", })) // start the http server go func() { log.WithField("bind", c.String("http-bind")).Info("starting rest api / gui server") log.Fatal(http.ListenAndServe(c.String("http-bind"), r)) }() sigChan := make(chan os.Signal) exitChan := make(chan struct{}) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) log.WithField("signal", <-sigChan).Info("signal received") go func() { log.Warning("stopping loraserver") if err := server.Stop(); err != nil { log.Fatal(err) } exitChan <- struct{}{} }() select { case <-exitChan: case s := <-sigChan: log.WithField("signal", s).Info("signal received, stopping immediately") } return nil }
func run(c *cli.Context) { // parse the NetID var netID lorawan.NetID if err := netID.UnmarshalText([]byte(c.String("net-id"))); err != nil { log.Fatalf("NetID parse error: %s", err) } // get the band config if c.String("band") == "" { log.Fatalf("--band is undefined, valid options are: %s", strings.Join(bands, ", ")) } bandConfig, err := band.GetConfig(band.Name(c.String("band"))) if err != nil { log.Fatal(err) } loraserver.Band = bandConfig log.WithFields(log.Fields{ "version": version, "net_id": netID.String(), "band": c.String("band"), }).Info("starting LoRa Server") // connect to the database log.Info("connecting to postgresql") db, err := loraserver.OpenDatabase(c.String("postgres-dsn")) if err != nil { log.Fatalf("database connection error: %s", err) } // setup redis pool log.Info("setup redis connection pool") rp := loraserver.NewRedisPool(c.String("redis-url")) // setup gateway backend gw, err := gateway.NewBackend(c.String("gw-mqtt-server"), c.String("gw-mqtt-username"), c.String("gw-mqtt-password")) if err != nil { log.Fatalf("gateway-backend setup failed: %s", err) } // setup application backend app, err := application.NewBackend(rp, c.String("app-mqtt-server"), c.String("app-mqtt-username"), c.String("app-mqtt-password")) if err != nil { log.Fatalf("application-backend setup failed: %s", err) } // auto-migrate the database if c.Bool("db-automigrate") { log.Info("applying database migrations") m := &migrate.AssetMigrationSource{ Asset: migrations.Asset, AssetDir: migrations.AssetDir, Dir: "", } n, err := migrate.Exec(db.DB, "postgres", m, migrate.Up) if err != nil { log.Fatalf("applying migrations failed: %s", err) } log.WithField("count", n).Info("migrations applied") } ctx := loraserver.Context{ DB: db, RedisPool: rp, Gateway: gw, Application: app, NetID: netID, } // start the loraserver server := loraserver.NewServer(ctx) if err := server.Start(); err != nil { log.Fatal(err) } // setup json-rpc api handler apiHandler, err := loraserver.NewJSONRPCHandler( loraserver.NewApplicationAPI(ctx), loraserver.NewNodeAPI(ctx), loraserver.NewNodeSessionAPI(ctx), loraserver.NewChannelListAPI(ctx), loraserver.NewChannelAPI(ctx), ) if err != nil { log.Fatal(err) } log.WithField("path", "/rpc").Info("registering json-rpc handler") http.Handle("/rpc", apiHandler) // setup static file server (for the gui) log.WithField("path", "/").Info("registering gui handler") http.Handle("/", http.FileServer(&assetfs.AssetFS{ Asset: static.Asset, AssetDir: static.AssetDir, AssetInfo: static.AssetInfo, Prefix: "", })) // start the http server go func() { log.WithField("bind", c.String("http-bind")).Info("starting http server") log.Fatal(http.ListenAndServe(c.String("http-bind"), nil)) }() sigChan := make(chan os.Signal) exitChan := make(chan struct{}) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) log.WithField("signal", <-sigChan).Info("signal received") go func() { log.Warning("stopping loraserver") if err := server.Stop(); err != nil { log.Fatal(err) } exitChan <- struct{}{} }() select { case <-exitChan: case s := <-sigChan: log.WithField("signal", s).Info("signal received, stopping immediately") } }