Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix linter errors on PR-113 #121

Closed
wants to merge 13 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 119 additions & 71 deletions src/lib/LoraPacket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,74 @@ import { recalculateMIC } from "./mic";
import { Buffer } from "buffer";

enum MType {
JOIN_REQUEST,
JOIN_ACCEPT,
UNCONFIRMED_DATA_UP,
UNCONFIRMED_DATA_DOWN,
CONFIRMED_DATA_UP,
CONFIRMED_DATA_DOWN,
REJOIN_REQUEST,
JOIN_REQUEST = 0,
JOIN_ACCEPT = 1,
UNCONFIRMED_DATA_UP = 2,
UNCONFIRMED_DATA_DOWN = 3,
CONFIRMED_DATA_UP = 4,
CONFIRMED_DATA_DOWN = 5,
REJOIN_REQUEST = 6,
}

const MTYPE_DESCRIPTIONS: { [key: number]: string } = {
[MType.JOIN_REQUEST]: "Join Request",
[MType.JOIN_ACCEPT]: "Join Accept",
[MType.UNCONFIRMED_DATA_UP]: "Unconfirmed Data Up",
[MType.UNCONFIRMED_DATA_DOWN]: "Unconfirmed Data Down",
[MType.CONFIRMED_DATA_UP]: "Confirmed Data Up",
[MType.CONFIRMED_DATA_DOWN]: "Confirmed Data Down",
[MType.REJOIN_REQUEST]: "Rejoin Request",
};

const DESCRIPTIONS_MTYPE: { [description: string]: MType } = Object.keys(MTYPE_DESCRIPTIONS).reduce((acc, key) => {
const mTypeKey = (key as unknown) as MType; // Cast the key to MType
const description = MTYPE_DESCRIPTIONS[mTypeKey];
acc[description] = mTypeKey;
return acc;
}, {} as { [description: string]: MType });

type Range = {
start: number;
end: number;
};

type PacketStructures = {
[key: string]: {
[key: string]: Range;
};
};

const PACKET_STRUCTURES: PacketStructures = {
JOIN_REQUEST: {
AppEUI: { start: 1, end: 9 },
DevEUI: { start: 9, end: 17 },
DevNonce: { start: 17, end: 19 },
},
JOIN_ACCEPT: {
AppNonce: { start: 1, end: 4 },
NetID: { start: 4, end: 7 },
DevAddr: { start: 7, end: 11 },
DLSettings: { start: 11, end: 12 },
RxDelay: { start: 12, end: 13 },
},
REJOIN_TYPE_1: {
NetID: { start: 2, end: 5 },
DevEUI: { start: 5, end: 13 },
RJCount0: { start: 13, end: 15 },
},
REJOIN_TYPE_2: {
JoinEUI: { start: 2, end: 10 },
DevEUI: { start: 10, end: 18 },
RJCount1: { start: 13, end: 15 },
},
};

enum LorawanVersion {
V1_0 = "1.0",
V1_1 = "1.1",
}
enum Constants {

enum Masks {
FCTRL_ADR = 0x80,
FCTRL_ADRACKREQ = 0x40,
FCTRL_ACK = 0x20,
Expand All @@ -32,18 +86,7 @@ enum Constants {
RXDELAY_DEL_POS = 0,
}

const MTYPE_DESCRIPTIONS = [
"Join Request",
"Join Accept",
"Unconfirmed Data Up",
"Unconfirmed Data Down",
"Confirmed Data Up",
"Confirmed Data Down",
"Rejoin Request",
"Proprietary",
];

export interface IUserFields {
export interface UserFields {
CFList?: Buffer;
RxDelay?: Buffer | number;
DLSettings?: Buffer | number;
Expand All @@ -67,6 +110,21 @@ export interface IUserFields {
JoinReqType?: Buffer | number;
}

function extractBytesFromBuffer(buffer: Buffer, start: number, end: number): Buffer {
return reverseBuffer(buffer.slice(start, end));
}

function extractStructuredBytesFromBuffer(buffer: Buffer, name: string): { [key: string]: Buffer } {
const structure = PACKET_STRUCTURES[name];
const ret: { [key: string]: Buffer } = {};
for (const key in structure) {
if (structure.hasOwnProperty(key)) {
ret[key] = extractBytesFromBuffer(buffer, structure[key].start, structure[key].end);
}
}
return ret;
}

class LoraPacket {
static fromWire(buffer: Buffer): LoraPacket {
const payload = new LoraPacket();
Expand All @@ -75,7 +133,7 @@ class LoraPacket {
}

static fromFields(
fields: IUserFields,
fields: UserFields,
AppSKey?: Buffer,
NwkSKey?: Buffer,
AppKey?: Buffer,
Expand All @@ -91,7 +149,7 @@ class LoraPacket {

const port = payload.getFPort();

if (port != null && ((port === 0 && NwkSKey?.length === 16) || (port > 0 && AppSKey?.length === 16))) {
if (port !== null && ((port === 0 && NwkSKey?.length === 16) || (port > 0 && AppSKey?.length === 16))) {
// crypto is reversible (just XORs FRMPayload), so we can
// just do "decrypt" on the plaintext to get ciphertext

Expand Down Expand Up @@ -129,6 +187,11 @@ class LoraPacket {
return payload;
}

private assignFromStructuredBuffer(buffer: Buffer, structure: string) {
const fields = extractStructuredBytesFromBuffer(buffer, structure);
Object.assign(this, fields);
}

private _initfromWire(contents: Buffer): void {
const incoming = Buffer.from(contents);

Expand All @@ -145,18 +208,12 @@ class LoraPacket {
if (incoming.length < 5 + 18) {
throw new Error("contents too short for a Join Request");
}
this.AppEUI = reverseBuffer(incoming.slice(1, 1 + 8));
this.DevEUI = reverseBuffer(incoming.slice(9, 9 + 8));
this.DevNonce = reverseBuffer(incoming.slice(17, 17 + 2));
this.assignFromStructuredBuffer(incoming, "JOIN_REQUEST");
} else if (mtype == MType.JOIN_ACCEPT) {
if (incoming.length < 5 + 12) {
throw new Error("contents too short for a Join Accept");
}
this.AppNonce = reverseBuffer(incoming.slice(1, 1 + 3));
this.NetID = reverseBuffer(incoming.slice(4, 4 + 3));
this.DevAddr = reverseBuffer(incoming.slice(7, 7 + 4));
this.DLSettings = incoming.slice(11, 11 + 1);
this.RxDelay = incoming.slice(12, 12 + 1);
this.assignFromStructuredBuffer(incoming, "JOIN_ACCEPT");
this.JoinReqType = Buffer.from([0xff]);

if (incoming.length == 13 + 16 + 4) {
Expand All @@ -170,45 +227,41 @@ class LoraPacket {
if (incoming.length < 5 + 14) {
throw new Error("contents too short for a Rejoin Request (Type 0/2)");
}
this.NetID = reverseBuffer(incoming.slice(2, 2 + 3));
this.DevEUI = reverseBuffer(incoming.slice(5, 5 + 8));
this.RJCount0 = reverseBuffer(incoming.slice(13, 13 + 2));
this.assignFromStructuredBuffer(incoming, "REJOIN_TYPE_1");
} else if (this.RejoinType[0] === 1) {
if (incoming.length < 5 + 19) {
throw new Error("contents too short for a Rejoin Request (Type 1)");
}
this.JoinEUI = reverseBuffer(incoming.slice(2, 2 + 8));
this.DevEUI = reverseBuffer(incoming.slice(10, 10 + 8));
this.RJCount1 = reverseBuffer(incoming.slice(18, 18 + 2));
this.assignFromStructuredBuffer(incoming, "REJOIN_TYPE_2");
}
} else if (this.isDataMessage()) {
this.FCtrl = this.MACPayload.slice(4, 5);
this.DevAddr = reverseBuffer(incoming.slice(1, 5));
this.FCtrl = reverseBuffer(incoming.slice(5, 6));
this.FCnt = reverseBuffer(incoming.slice(6, 8));

const FCtrl = this.FCtrl.readInt8(0);
const FOptsLen = FCtrl & 0x0f;
this.FOpts = this.MACPayload.slice(7, 7 + FOptsLen);
this.FOpts = incoming.slice(8, 8 + FOptsLen);
const FHDR_length = 7 + FOptsLen;
this.FHDR = this.MACPayload.slice(0, 0 + FHDR_length);
this.DevAddr = reverseBuffer(this.FHDR.slice(0, 4));

this.FCnt = reverseBuffer(this.FHDR.slice(5, 7));
this.FHDR = incoming.slice(1, 1 + FHDR_length);

if (FHDR_length == this.MACPayload.length) {
this.FPort = Buffer.alloc(0);
this.FRMPayload = Buffer.alloc(0);
} else {
this.FPort = this.MACPayload.slice(FHDR_length, FHDR_length + 1);
this.FRMPayload = this.MACPayload.slice(FHDR_length + 1);
this.FPort = incoming.slice(FHDR_length + 1, FHDR_length + 2);
this.FRMPayload = incoming.slice(FHDR_length + 2, incoming.length - 4);
}
}
}

private _initFromFields(userFields: IUserFields): void {
private _initFromFields(userFields: UserFields): void {
if (typeof userFields.MType !== "undefined") {
let MTypeNo;
if (typeof userFields.MType === "number") {
MTypeNo = userFields.MType;
} else if (typeof userFields.MType == "string") {
const mhdr_idx = MTYPE_DESCRIPTIONS.indexOf(userFields.MType);
const mhdr_idx = DESCRIPTIONS_MTYPE[userFields.MType];
if (mhdr_idx >= 0) {
MTypeNo = mhdr_idx;
} else {
Expand Down Expand Up @@ -276,7 +329,7 @@ class LoraPacket {
}
}

private _initialiseDataPacketFromFields(userFields: IUserFields): void {
private _initialiseDataPacketFromFields(userFields: UserFields): void {
if (userFields.DevAddr && userFields.DevAddr.length == 4) {
this.DevAddr = Buffer.from(userFields.DevAddr);
} else {
Expand All @@ -294,7 +347,7 @@ class LoraPacket {
this.MHDR = Buffer.alloc(1);
this.MHDR.writeUInt8(userFields.MType << 5, 0);
} else if (typeof userFields.MType === "string") {
const mhdr_idx = MTYPE_DESCRIPTIONS.indexOf(userFields.MType);
const mhdr_idx = DESCRIPTIONS_MTYPE[userFields.MType];
if (mhdr_idx >= 0) {
this.MHDR = Buffer.alloc(1);
this.MHDR.writeUInt8(mhdr_idx << 5, 0);
Expand Down Expand Up @@ -334,16 +387,16 @@ class LoraPacket {

let fctrl = 0;
if (userFields.FCtrl?.ADR) {
fctrl |= Constants.FCTRL_ADR;
fctrl |= Masks.FCTRL_ADR;
}
if (userFields.FCtrl?.ADRACKReq) {
fctrl |= Constants.FCTRL_ADRACKREQ;
fctrl |= Masks.FCTRL_ADRACKREQ;
}
if (userFields.FCtrl?.ACK) {
fctrl |= Constants.FCTRL_ACK;
fctrl |= Masks.FCTRL_ACK;
}
if (userFields.FCtrl?.FPending) {
fctrl |= Constants.FCTRL_FPENDING;
fctrl |= Masks.FCTRL_FPENDING;
}

fctrl |= this.FOpts.length & 0x0f;
Expand Down Expand Up @@ -383,7 +436,7 @@ class LoraPacket {
this._mergeGroupFields();
}

private _initialiseJoinRequestPacketFromFields(userFields: IUserFields): void {
private _initialiseJoinRequestPacketFromFields(userFields: UserFields): void {
if (userFields.AppEUI && userFields.AppEUI.length == 8) {
this.AppEUI = Buffer.from(userFields.AppEUI);
} else {
Expand Down Expand Up @@ -422,7 +475,7 @@ class LoraPacket {
this._mergeGroupFields();
}

private _initialiseJoinAcceptPacketFromFields(userFields: IUserFields): void {
private _initialiseJoinAcceptPacketFromFields(userFields: UserFields): void {
if (userFields.AppNonce && userFields.AppNonce.length == 3) {
this.AppNonce = Buffer.from(userFields.AppNonce);
} else {
Expand Down Expand Up @@ -522,21 +575,19 @@ class LoraPacket {

public isDataMessage(): boolean {
const mtype = this._getMType();
if (mtype >= MType.UNCONFIRMED_DATA_UP && mtype <= MType.CONFIRMED_DATA_DOWN) return true;
return false;
return mtype >= MType.UNCONFIRMED_DATA_UP && mtype <= MType.CONFIRMED_DATA_DOWN;
}

public isConfirmed(): boolean {
const mtype = this._getMType();
if (mtype === MType.CONFIRMED_DATA_DOWN || mtype === MType.CONFIRMED_DATA_UP) return true;
return false;
return mtype === MType.CONFIRMED_DATA_DOWN || mtype === MType.CONFIRMED_DATA_UP;
}

/**
* Provide MType as a string
*/
public getMType(): string {
return MTYPE_DESCRIPTIONS[this._getMType()];
return MTYPE_DESCRIPTIONS[this._getMType()] || "Proprietary";
}

/**
Expand Down Expand Up @@ -570,67 +621,63 @@ class LoraPacket {
*/
public getFCtrlACK(): boolean | null {
if (!this.FCtrl) return null;
return !!(this.FCtrl.readUInt8(0) & Constants.FCTRL_ACK);
return !!(this.FCtrl.readUInt8(0) & Masks.FCTRL_ACK);
}

/**
* Provide FCtrl.ADR as a flag
*/
public getFCtrlADR(): boolean | null {
if (!this.FCtrl) return null;
return !!(this.FCtrl.readUInt8(0) & Constants.FCTRL_ADR);
return !!(this.FCtrl.readUInt8(0) & Masks.FCTRL_ADR);
}

/**
* Provide FCtrl.ADRACKReq as a flag
*/
public getFCtrlADRACKReq(): boolean | null {
if (!this.FCtrl) return null;
return !!(this.FCtrl.readUInt8(0) & Constants.FCTRL_ADRACKREQ);
return !!(this.FCtrl.readUInt8(0) & Masks.FCTRL_ADRACKREQ);
}

/**
* Provide FCtrl.FPending as a flag
*/
public getFCtrlFPending(): boolean | null {
if (!this.FCtrl) return null;
return !!(this.FCtrl.readUInt8(0) & Constants.FCTRL_FPENDING);
return !!(this.FCtrl.readUInt8(0) & Masks.FCTRL_FPENDING);
}

/**
* Provide DLSettings.RX1DRoffset as integer
*/
public getDLSettingsRxOneDRoffset(): number | null {
if (!this.DLSettings) return null;
return (
(this.DLSettings.readUInt8(0) & Constants.DLSETTINGS_RXONEDROFFSET_MASK) >> Constants.DLSETTINGS_RXONEDROFFSET_POS
);
return (this.DLSettings.readUInt8(0) & Masks.DLSETTINGS_RXONEDROFFSET_MASK) >> Masks.DLSETTINGS_RXONEDROFFSET_POS;
}

/**
* Provide DLSettings.RX2DataRate as integer
*/
public getDLSettingsRxTwoDataRate(): number | null {
if (!this.DLSettings) return null;
return (
(this.DLSettings.readUInt8(0) & Constants.DLSETTINGS_RXTWODATARATE_MASK) >> Constants.DLSETTINGS_RXTWODATARATE_POS
);
return (this.DLSettings.readUInt8(0) & Masks.DLSETTINGS_RXTWODATARATE_MASK) >> Masks.DLSETTINGS_RXTWODATARATE_POS;
}

/**
* Provide DLSettings.OptNeg as boolean
*/
public getDLSettingsOptNeg(): boolean | null {
if (!this.DLSettings) return null;
return (this.DLSettings.readUInt8(0) & Constants.DLSETTINGS_OPTNEG_MASK) >> Constants.DLSETTINGS_OPTNEG_POS === 1;
return (this.DLSettings.readUInt8(0) & Masks.DLSETTINGS_OPTNEG_MASK) >> Masks.DLSETTINGS_OPTNEG_POS === 1;
}

/**
* Provide RxDelay.Del as integer
*/
public getRxDelayDel(): number | null {
if (!this.RxDelay) return null;
return (this.RxDelay.readUInt8(0) & Constants.RXDELAY_DEL_MASK) >> Constants.RXDELAY_DEL_POS;
return (this.RxDelay.readUInt8(0) & Masks.RXDELAY_DEL_MASK) >> Masks.RXDELAY_DEL_POS;
}

/**
Expand Down Expand Up @@ -700,6 +747,7 @@ class LoraPacket {
): Buffer {
return this.encryptFOpts(NwkSEncKey, NwkSKey, FCntMSBytes, ConfFCntDownTxDrTxCh);
}

public encryptFOpts(
NwkSEncKey: Buffer,
SNwkSIntKey?: Buffer,
Expand Down
Loading