Unverified Commit ce8c8d83 authored by SAGAR PATEL's avatar SAGAR PATEL Committed by GitHub
Browse files

Add DevNonce Clear Functionality (#660)



* Add DevNonce Clear Functionality

Details:
- DevNonce clear functionality helps to remove older device
activation records from device_activation table in NS.
- Using this method we can clear older or already generated device activation
records from database to prevent "DevNonce already exits" error in OTAA method

*Note: Server keeps latest 20 records in device_activation table of NS database
for manage recent device activation.

Signed-off-by: default avatarSAGAR PATEL <sagar.a.patel@slscorp.com>

* Update chirpstack-api.

Co-authored-by: default avatarOrne Brocaar <info@brocaar.com>
parent b911e629
......@@ -5,7 +5,7 @@ go 1.16
require (
github.com/NickBall/go-aes-key-wrap v0.0.0-20170929221519-1c3aa3e4dfc5
github.com/aws/aws-sdk-go v1.26.3
github.com/brocaar/chirpstack-api/go/v3 v3.12.4
github.com/brocaar/chirpstack-api/go/v3 v3.12.5
github.com/brocaar/lorawan v0.0.0-20211122090658-49524ce5fb5b
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/eclipse/paho.mqtt.golang v1.3.1
......@@ -53,5 +53,3 @@ require (
gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
)
// replace github.com/brocaar/chirpstack-api/go/v3 => ../chirpstack-api/go
......@@ -967,6 +967,42 @@ func (a *DeviceAPI) GetStats(ctx context.Context, req *pb.GetDeviceStatsRequest)
}, nil
}
// ClearDeviceNonces deletes the device older activation records for the given DevEUI.
// TODO: These are clear older DevNonce records from device activation records in Network Server
// TODO: These clears all DevNonce records but keeps latest 20 records for maintain device activation status
func (a *DeviceAPI) ClearDeviceNonces(ctx context.Context, req *pb.ClearDeviceNoncesRequest) (*empty.Empty, error) {
var eui lorawan.EUI64
if err := eui.UnmarshalText([]byte(req.DevEui)); err != nil {
return nil, grpc.Errorf(codes.InvalidArgument, err.Error())
}
if err := a.validator.Validate(ctx,
auth.ValidateNodeAccess(eui, auth.Update)); err != nil {
return nil, grpc.Errorf(codes.Unauthenticated, "authentication failed: %s", err)
}
d, err := storage.GetDevice(ctx, storage.DB(), eui, false, true)
if err != nil {
return nil, helpers.ErrToRPCError(err)
}
n, err := storage.GetNetworkServerForDevEUI(ctx, storage.DB(), d.DevEUI)
if err != nil {
return nil, helpers.ErrToRPCError(err)
}
nsClient, err := networkserver.GetPool().Get(n.Server, []byte(n.CACert), []byte(n.TLSCert), []byte(n.TLSKey))
if err != nil {
return nil, helpers.ErrToRPCError(err)
}
_, _ = nsClient.ClearDeviceNonces(ctx, &ns.ClearDeviceNoncesRequest{
DevEui: d.DevEUI[:],
})
return &empty.Empty{}, nil
}
func (a *DeviceAPI) returnList(count int, devices []storage.DeviceListItem) (*pb.ListDeviceResponse, error) {
resp := pb.ListDeviceResponse{
TotalCount: int64(count),
......
......@@ -149,6 +149,9 @@ type Client struct {
GetVersionResponse ns.GetVersionResponse
GetADRAlgorithmsResponse ns.GetADRAlgorithmsResponse
ClearDeviceNoncesChan chan ns.ClearDeviceNoncesRequest
ClearDeviceNoncesResponse empty.Empty
}
// NewClient creates a new Client.
......@@ -199,6 +202,7 @@ func NewClient() *Client {
FlushMulticastQueueForMulticastGroupChan: make(chan ns.FlushMulticastQueueForMulticastGroupRequest, 100),
GetMulticastQueueItemsForMulticastGroupChan: make(chan ns.GetMulticastQueueItemsForMulticastGroupRequest, 100),
GenerateGatewayClientCertificateChan: make(chan ns.GenerateGatewayClientCertificateRequest, 100),
ClearDeviceNoncesChan: make(chan ns.ClearDeviceNoncesRequest, 100),
}
}
......@@ -491,3 +495,9 @@ func (n *Client) GenerateGatewayClientCertificate(ctx context.Context, in *ns.Ge
func (n *Client) GetADRAlgorithms(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*ns.GetADRAlgorithmsResponse, error) {
return &n.GetADRAlgorithmsResponse, nil
}
// ClearDeviceNonces method.
func (n *Client) ClearDeviceNonces(ctx context.Context, in *ns.ClearDeviceNoncesRequest, opts ...grpc.CallOption) (*empty.Empty, error) {
n.ClearDeviceNoncesChan <- *in
return &n.ClearDeviceNoncesResponse, nil
}
......@@ -4,10 +4,9 @@ import RobustWebSocket from "robust-websocket";
import Swagger from "swagger-client";
import sessionStore from "./SessionStore";
import {checkStatus, errorHandler, errorHandlerIgnoreNotFoundWithCallback } from "./helpers";
import { checkStatus, errorHandler, errorHandlerIgnoreNotFoundWithCallback } from "./helpers";
import dispatcher from "../dispatcher";
class DeviceStore extends EventEmitter {
constructor() {
super();
......@@ -25,187 +24,200 @@ class DeviceStore extends EventEmitter {
}
create(device, callbackFunc) {
this.swagger.then(client => {
this.swagger.then((client) => {
client.apis.DeviceService.Create({
body: {
device: device,
},
})
.then(checkStatus)
.then(resp => {
this.notify("created");
callbackFunc(resp.obj);
})
.catch(errorHandler);
.then(checkStatus)
.then((resp) => {
this.notify("created");
callbackFunc(resp.obj);
})
.catch(errorHandler);
});
}
get(id, callbackFunc) {
this.swagger.then(client => {
this.swagger.then((client) => {
client.apis.DeviceService.Get({
dev_eui: id,
})
.then(checkStatus)
.then(resp => {
callbackFunc(resp.obj);
})
.catch(errorHandler);
.then(checkStatus)
.then((resp) => {
callbackFunc(resp.obj);
})
.catch(errorHandler);
});
}
update(device, callbackFunc) {
this.swagger.then(client => {
this.swagger.then((client) => {
client.apis.DeviceService.Update({
"device.dev_eui": device.devEUI,
body: {
device: device,
},
})
.then(checkStatus)
.then(resp => {
this.emit("update");
this.notify("updated");
callbackFunc(resp.obj);
})
.catch(errorHandler);
.then(checkStatus)
.then((resp) => {
this.emit("update");
this.notify("updated");
callbackFunc(resp.obj);
})
.catch(errorHandler);
});
}
delete(id, callbackFunc) {
this.swagger.then(client => {
this.swagger.then((client) => {
client.apis.DeviceService.Delete({
dev_eui: id,
})
.then(checkStatus)
.then(resp => {
this.notify("deleted");
callbackFunc(resp.obj);
})
.catch(errorHandler);
.then(checkStatus)
.then((resp) => {
this.notify("deleted");
callbackFunc(resp.obj);
})
.catch(errorHandler);
});
}
list(filters, callbackFunc) {
this.swagger.then(client => {
this.swagger.then((client) => {
client.apis.DeviceService.List(filters)
.then(checkStatus)
.then(resp => {
callbackFunc(resp.obj);
})
.catch(errorHandler);
.then(checkStatus)
.then((resp) => {
callbackFunc(resp.obj);
})
.catch(errorHandler);
});
}
getKeys(devEUI, callbackFunc) {
this.swagger.then(client => {
this.swagger.then((client) => {
client.apis.DeviceService.GetKeys({
dev_eui: devEUI,
})
.then(checkStatus)
.then(resp => {
callbackFunc(resp.obj);
})
.catch(errorHandlerIgnoreNotFoundWithCallback(callbackFunc));
.then(checkStatus)
.then((resp) => {
callbackFunc(resp.obj);
})
.catch(errorHandlerIgnoreNotFoundWithCallback(callbackFunc));
});
}
createKeys(deviceKeys, callbackFunc) {
this.swagger.then(client => {
this.swagger.then((client) => {
client.apis.DeviceService.CreateKeys({
"device_keys.dev_eui": deviceKeys.devEUI,
body: {
deviceKeys: deviceKeys,
},
})
.then(checkStatus)
.then(resp => {
this.notifyKeys("created");
callbackFunc(resp.obj);
})
.catch(errorHandler);
.then(checkStatus)
.then((resp) => {
this.notifyKeys("created");
callbackFunc(resp.obj);
})
.catch(errorHandler);
});
}
updateKeys(deviceKeys, callbackFunc) {
this.swagger.then(client => {
this.swagger.then((client) => {
client.apis.DeviceService.UpdateKeys({
"device_keys.dev_eui": deviceKeys.devEUI,
body: {
deviceKeys: deviceKeys,
},
})
.then(checkStatus)
.then(resp => {
this.notifyKeys("updated");
callbackFunc(resp.obj);
})
.catch(errorHandler);
.then(checkStatus)
.then((resp) => {
this.notifyKeys("updated");
callbackFunc(resp.obj);
})
.catch(errorHandler);
});
}
getActivation(devEUI, callbackFunc) {
this.swagger.then(client => {
this.swagger.then((client) => {
client.apis.DeviceService.GetActivation({
"dev_eui": devEUI,
})
.then(checkStatus)
.then(resp => {
callbackFunc(resp.obj);
dev_eui: devEUI,
})
.catch(errorHandlerIgnoreNotFoundWithCallback(callbackFunc));
.then(checkStatus)
.then((resp) => {
callbackFunc(resp.obj);
})
.catch(errorHandlerIgnoreNotFoundWithCallback(callbackFunc));
});
}
activate(deviceActivation, callbackFunc) {
this.swagger.then(client => {
this.swagger.then((client) => {
client.apis.DeviceService.Activate({
"device_activation.dev_eui": deviceActivation.devEUI,
body: {
deviceActivation: deviceActivation,
},
})
.then(checkStatus)
.then(resp => {
dispatcher.dispatch({
type: "CREATE_NOTIFICATION",
notification: {
type: "success",
message: "device has been (re)activated",
},
});
callbackFunc(resp.obj);
})
.catch(errorHandler);
.then(checkStatus)
.then((resp) => {
dispatcher.dispatch({
type: "CREATE_NOTIFICATION",
notification: {
type: "success",
message: "device has been (re)activated",
},
});
callbackFunc(resp.obj);
})
.catch(errorHandler);
});
}
getRandomDevAddr(devEUI, callbackFunc) {
this.swagger.then(client => {
this.swagger.then((client) => {
client.apis.DeviceService.GetRandomDevAddr({
dev_eui: devEUI,
})
.then(checkStatus)
.then(resp => {
callbackFunc(resp.obj);
})
.catch(errorHandler);
.then(checkStatus)
.then((resp) => {
callbackFunc(resp.obj);
})
.catch(errorHandler);
});
}
getStats(devEUI, start, end, callbackFunc) {
this.swagger.then(client => {
this.swagger.then((client) => {
client.apis.DeviceService.GetStats({
dev_eui: devEUI,
interval: "DAY",
startTimestamp: start,
endTimestamp: end,
})
.then(checkStatus)
.then(resp => {
callbackFunc(resp.obj);
.then(checkStatus)
.then((resp) => {
callbackFunc(resp.obj);
})
.catch(errorHandler);
});
}
clearDevNonces(id, callbackFunc) {
this.swagger.then((client) => {
client.apis.DeviceService.ClearDeviceNonces({
dev_eui: id,
})
.catch(errorHandler);
.then(checkStatus)
.then((resp) => {
this.notifyMsg("device devNonce deleted");
callbackFunc(resp.obj);
})
.catch(errorHandler);
});
}
......@@ -223,7 +235,7 @@ class DeviceStore extends EventEmitter {
const conn = new RobustWebSocket(wsURL, ["Bearer", sessionStore.getToken()], {});
conn.addEventListener("open", () => {
console.log('connected to', wsURL);
console.log("connected to", wsURL);
this.wsDataStatus = "CONNECTED";
this.emit("ws.status.change");
});
......@@ -244,7 +256,7 @@ class DeviceStore extends EventEmitter {
});
conn.addEventListener("close", () => {
console.log('closing', wsURL);
console.log("closing", wsURL);
this.wsDataStatus = null;
this.emit("ws.status.change");
});
......@@ -272,7 +284,7 @@ class DeviceStore extends EventEmitter {
const conn = new RobustWebSocket(wsURL, ["Bearer", sessionStore.getToken()], {});
conn.addEventListener("open", () => {
console.log('connected to', wsURL);
console.log("connected to", wsURL);
this.wsFramesStatus = "CONNECTED";
this.emit("ws.status.change");
});
......@@ -293,7 +305,7 @@ class DeviceStore extends EventEmitter {
});
conn.addEventListener("close", () => {
console.log('closing', wsURL);
console.log("closing", wsURL);
this.wsFramesStatus = null;
this.emit("ws.status.change");
});
......@@ -326,6 +338,16 @@ class DeviceStore extends EventEmitter {
},
});
}
notifyMsg(action) {
dispatcher.dispatch({
type: "CREATE_NOTIFICATION",
notification: {
type: "success",
message: action,
},
});
}
}
const deviceStore = new DeviceStore();
......
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import React, { Component } from "react";
import { withStyles } from "@material-ui/core/styles";
import Typograhy from "@material-ui/core/Typography";
import Card from '@material-ui/core/Card';
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import TextField from "@material-ui/core/TextField";
import { Delete, Close } from "mdi-material-ui";
import { Grid, Dialog, DialogTitle, DialogActions, DialogContent, DialogContentText } from "@material-ui/core";
import FormComponent from "../../classes/FormComponent";
import AESKeyField from "../../components/AESKeyField";
......@@ -13,15 +14,18 @@ import DevAddrField from "../../components/DevAddrField";
import Form from "../../components/Form";
import DeviceStore from "../../stores/DeviceStore";
import theme from "../../theme";
import DeviceAdmin from "../../components/DeviceAdmin";
import TitleBarButton from "../../components/TitleBarButton";
const styles = {
link: {
color: theme.palette.primary.main,
},
buttons: {
textAlign: "right",
},
};
class LW10DeviceActivationForm extends FormComponent {
constructor() {
super();
......@@ -29,21 +33,18 @@ class LW10DeviceActivationForm extends FormComponent {
}
getRandomDevAddr(cb) {
DeviceStore.getRandomDevAddr(this.props.match.params.devEUI, resp => {
DeviceStore.getRandomDevAddr(this.props.match.params.devEUI, (resp) => {
cb(resp.devAddr);
});
}
render() {
if (this.state.object === undefined) {
return(<div></div>);
return <div></div>;
}
return(
<Form
submitLabel={this.props.submitLabel}
onSubmit={this.onSubmit}
>
return (
<Form submitLabel={this.props.submitLabel} onSubmit={this.onSubmit}>
<DevAddrField
id="devAddr"
label="Device address"
......@@ -106,7 +107,6 @@ class LW10DeviceActivationForm extends FormComponent {
}
}
class LW11DeviceActivationForm extends FormComponent {
constructor() {
super();
......@@ -114,21 +114,18 @@ class LW11DeviceActivationForm extends FormComponent {
}
getRandomDevAddr(cb) {
DeviceStore.getRandomDevAddr(this.props.match.params.devEUI, resp => {
DeviceStore.getRandomDevAddr(this.props.match.params.devEUI, (resp) => {
cb(resp.devAddr);
});
}
render() {
if (this.state.object === undefined) {
return(<div></div>);
return <div></div>;
}
return(
<Form
submitLabel={this.props.submitLabel}
onSubmit={this.onSubmit}
>
return (
<Form submitLabel={this.props.submitLabel} onSubmit={this.onSubmit}>
<DevAddrField
id="devAddr"
label="Device address"
......@@ -223,20 +220,21 @@ class LW11DeviceActivationForm extends FormComponent {
}
}
LW10DeviceActivationForm = withStyles(styles)(LW10DeviceActivationForm);
LW11DeviceActivationForm = withStyles(styles)(LW11DeviceActivationForm);
class DeviceActivation extends Component {
constructor() {
super();
this.state = {};
this.state = {
dialogOpen: false,
};
this.onSubmit = this.onSubmit.bind(this);
this.clearDevNonces = this.clearDevNonces.bind(this);
}
componentDidMount() {
DeviceStore.getActivation(this.props.match.params.devEUI, resp => {
DeviceStore.getActivation(this.props.match.params.devEUI, (resp) => {
if (resp === null) {
this.setState({
deviceActivation: {
......@@ -260,9 +258,28 @@ class DeviceActivation extends Component {
act.sNwkSIntKey = act.nwkSEncKey;
}
DeviceStore.activate(act, resp => {
this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}`);
DeviceStore.activate(act, (resp) => {
this.props.history.push(
`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}`,
);
});
}
toggleDevNonceDialog = () => {
this.setState({
dialogOpen: !this.state.dialogOpen,
});
};
clearDevNonces() {
if (window.confirm("Are you sure you want to clear this device devNonce?")) {
DeviceStore.clearDevNonces(this.props.match.params.devEUI, (resp) => {
this.props.history.push(
`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/devices/${this.props.match.params.devEUI}`,
);
this.toggleDevNonceDialog();
});
}
}
render() {
......@@ -276,36 +293,86 @@ class DeviceActivation extends Component {