@iotize/tap
Version:
IoTize Device client for Javascript
1,088 lines (1,067 loc) • 42.4 kB
JavaScript
import { AbstractService, extendServiceContainer, TapError } from '@iotize/tap';
import { TapRequestFrame, ResultCode } from '@iotize/tap/client/api';
import { TapStreamReader, TapStreamWriter, enumKeyOrValueToNumber, CRC } from '@iotize/tap/client/impl';
import { converters } from '@iotize/tap/service/core';
import { CodeError } from '@iotize/common/error';
import { bufferToHexString } from '@iotize/common/byte-converter';
import { AesEcb128Converter } from '@iotize/common/crypto';
import { ScramAuth } from '@iotize/tap/auth';
import { hmacSHA256 } from '@iotize/tap/crypto';
import { StepOperations } from '@iotize/common/task-manager';
import { sleep } from '@iotize/common/utility';
import { createDebugger } from '@iotize/common/debug';
import { chunkArray } from '@iotize/common/array';
import { KaitaiStreamWriter } from '@iotize/common/byte-stream';
var SinglePacketStoreInfo;
(function (SinglePacketStoreInfo) {
/* eslint-disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
* and run json-schema-to-typescript to regenerate this file.
*/
let State;
(function (State) {
State[State["UNAVAILABLE"] = 0] = "UNAVAILABLE";
State[State["EMPTY"] = 1] = "EMPTY";
State[State["USED"] = 2] = "USED";
State[State["FULL"] = 3] = "FULL";
State[State["ERROR"] = 4] = "ERROR";
})(State = SinglePacketStoreInfo.State || (SinglePacketStoreInfo.State = {}));
})(SinglePacketStoreInfo || (SinglePacketStoreInfo = {}));
var SinglePacket;
(function (SinglePacket) {
/* eslint-disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
* and run json-schema-to-typescript to regenerate this file.
*/
/* eslint-disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
* and run json-schema-to-typescript to regenerate this file.
*/
let PacketType;
(function (PacketType) {
PacketType[PacketType["DATA_LOG"] = 1] = "DATA_LOG";
PacketType[PacketType["COMMAND"] = 2] = "COMMAND";
PacketType[PacketType["CODE_EXEC"] = 3] = "CODE_EXEC";
PacketType[PacketType["CONF_UPDATE"] = 4] = "CONF_UPDATE";
PacketType[PacketType["SECURITY_TOKEN"] = 5] = "SECURITY_TOKEN";
PacketType[PacketType["TARGET_FW_UPDATE"] = 6] = "TARGET_FW_UPDATE";
PacketType[PacketType["MASK"] = 7] = "MASK";
})(PacketType = SinglePacket.PacketType || (SinglePacket.PacketType = {}));
})(SinglePacket || (SinglePacket = {}));
var SinglePacketChunk;
(function (SinglePacketChunk) {
/* eslint-disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
* and run json-schema-to-typescript to regenerate this file.
*/
let Format;
(function (Format) {
Format[Format["OFFSET_ON_2_BYTES"] = 0] = "OFFSET_ON_2_BYTES";
Format[Format["OFFSET_ON_4_BYTES"] = 8] = "OFFSET_ON_4_BYTES";
})(Format = SinglePacketChunk.Format || (SinglePacketChunk.Format = {}));
})(SinglePacketChunk || (SinglePacketChunk = {}));
/**
* Generated file. Do not edit
*/
TapStreamReader.prototype.readSinglePacketStoreInfo =
function () {
const model = {};
this.forwardBits(24);
model.state = this.readUnsigned(1);
return model;
};
TapStreamReader.prototype.readSinglePacket = function () {
const model = {};
model.sendTime = this.readUnsigned(4);
model.header = this.readSinglePacketHeader();
model.payload = this.readSinglePacketPayload();
return model;
};
TapStreamReader.prototype.readSinglePacketHeader =
function () {
const model = {};
model.packetLength = this.readUnsigned(2);
model.packetId = this.readUnsigned(2);
model.configVersion = this.readUnsigned(4);
model.messageType = this.readBits(4);
this.forwardBits(1);
model.isExtended = this.readBoolean(1);
model.encryption = this.readBoolean(1);
model.ack = this.readBoolean(1);
model.senderId = this.readUnsigned(1);
model.salt = this.readUnsigned(2);
return model;
};
TapStreamReader.prototype.readSinglePacketPayload =
function () {
const model = {};
model.logTime = this.readUnsigned(4);
model.dataSize = this.readUnsigned(2);
model.data = this.readBytes((model.dataSize === undefined ? 0 : model.dataSize) == 0
? this.getStreamSize() - 4
: model.dataSize === undefined
? 0
: model.dataSize);
model.padding = this.readBytes((4 - ((model.data.length + 10) % 4)) % 4);
model.crc = this.readUnsigned(4);
return model;
};
TapStreamReader.prototype.readSinglePacketChunk =
function () {
const model = {};
model.format = this.readBits(4);
model.length = this.readBits(12);
model.offset = this.readUnsigned(model.format == 0b1000 ? 4 : 2);
model.data = this.readBytes((model.length === undefined ? 0 : model.length) * 16);
return model;
};
// TapStreamWriter.prototype.write(model: SinglePacketStoreInfo) : TapStreamWriter{
// return this.writeSinglePacketStoreInfo(model)
// }
TapStreamWriter.prototype.writeSinglePacketStoreInfo = function (model) {
this.forwardBits(24);
this.writeBitsInt(enumKeyOrValueToNumber(model.state, SinglePacketStoreInfo.State), 8);
return this;
};
// TapStreamWriter.prototype.write(model: SinglePacket) : TapStreamWriter{
// return this.writeSinglePacket(model)
// }
TapStreamWriter.prototype.writeSinglePacket = function (model) {
this.writeUnsigned(model.sendTime, 4);
this.writeSinglePacketHeader(model.header);
this.writeSinglePacketPayload(model.payload);
return this;
};
TapStreamWriter.prototype.writeSinglePacketHeader = function (model) {
this.writeUnsigned(model.packetLength, 2);
this.writeUnsigned(model.packetId, 2);
this.writeUnsigned(model.configVersion, 4);
this.writeBitsInt(enumKeyOrValueToNumber(model.messageType, SinglePacket.PacketType), 4);
this.forwardBits(1);
this.writeBoolean(model.isExtended, 1);
this.writeBoolean(model.encryption, 1);
this.writeBoolean(model.ack, 1);
this.writeUnsigned(model.senderId, 1);
this.writeUnsigned(model.salt, 2);
return this;
};
TapStreamWriter.prototype.writeSinglePacketPayload = function (model) {
this.writeUnsigned(model.logTime, 4);
this.writeUnsigned(model.dataSize !== undefined
? model.dataSize
: model.data.length > 0xffff
? 0
: model.data.length, 2);
this.writeBytes(model.data, (model.dataSize === undefined ? 0 : model.dataSize) == 0
? model.data.length
: model.dataSize === undefined
? 0
: model.dataSize);
this.writeFunction('padding', undefined, model.padding, (4 - ((model.data.length + 10) % 4)) % 4);
this.writeFunction('crc32', {
offset: 4,
}, model.crc, 4);
return this;
};
// TapStreamWriter.prototype.write(model: SinglePacketChunk) : TapStreamWriter{
// return this.writeSinglePacketChunk(model)
// }
TapStreamWriter.prototype.writeSinglePacketChunk = function (model) {
this.writeBitsInt(enumKeyOrValueToNumber(model.format, SinglePacketChunk.Format), 4);
this.writeBits(model.length !== undefined ? model.length : model.data.length / 16, 12);
this.writeUnsigned(model.offset, model.format == 0b1000 ? 4 : 2);
this.writeBytes(model.data, (model.length === undefined ? 0 : model.length) == 0
? model.data.length
: (model.length === undefined ? 0 : model.length) * 16);
return this;
};
/**
* Generated file. Do not edit
*/
class SinglePacketStoreInfoConverter {
encode(model, stream = new TapStreamWriter()) {
stream.writeSinglePacketStoreInfo(model);
return stream.toBytes;
}
decode(data) {
const stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
return stream.readSinglePacketStoreInfo();
}
}
class SinglePacketConverter {
encode(model, stream = new TapStreamWriter()) {
stream.writeSinglePacket(model);
return stream.toBytes;
}
decode(data) {
const stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
return stream.readSinglePacket();
}
}
class SinglePacketChunkConverter {
encode(model, stream = new TapStreamWriter()) {
stream.writeSinglePacketChunk(model);
return stream.toBytes;
}
decode(data) {
const stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
return stream.readSinglePacketChunk();
}
}
/**
* Generated file. Do not edit
*/
const singlePacketStoreInfo = new SinglePacketStoreInfoConverter();
const singlePacket = new SinglePacketConverter();
const singlePacketChunk = new SinglePacketChunkConverter();
var serviceConverters = /*#__PURE__*/Object.freeze({
__proto__: null,
singlePacketStoreInfo: singlePacketStoreInfo,
singlePacket: singlePacket,
singlePacketChunk: singlePacketChunk
});
/**
* Generated file. Do not edit
*/
const allConverters = Object.assign(Object.assign({}, converters), serviceConverters);
const SERVICE_CALLS = {
getInfo: {
method: TapRequestFrame.MethodType.GET,
pathAlias: '/single-packet/info',
path: '/1024//81',
responseBodyDecoder: allConverters.singlePacketStoreInfo,
},
writeAndExecuteBytes: {
method: TapRequestFrame.MethodType.POST,
pathAlias: '/single-packet/write-and-execute-bytes',
path: '/1024//80',
},
writeAndExecute: {
method: TapRequestFrame.MethodType.POST,
pathAlias: '/single-packet/write-and-execute',
path: '/1024//80',
bodyEncoder: allConverters.singlePacket,
},
writeChunk: {
method: TapRequestFrame.MethodType.POST,
pathAlias: '/single-packet/write-chunk',
path: '/1024//82',
bodyEncoder: allConverters.singlePacketChunk,
},
executeSinglePacketInStore: {
method: TapRequestFrame.MethodType.POST,
pathAlias: '/single-packet/execute-single-packet-in-store',
path: '/1024//83',
bodyEncoder: allConverters.uint32,
},
};
class SinglePacketService extends AbstractService {
constructor() {
super(...arguments);
this.resources = SERVICE_CALLS;
}
/**
* Get information concerning Single Packet Store Status
*
* LWM2M path: /1024//81
*
* @tapVersion(">=1.13")
* @return
*/
getInfo() {
return this.serviceCallRunner.execute(this.getInfoCall());
}
/**
*
*
* LWM2M path: /1024//81
*
* @tapVersion(">=1.13")
* @return call options
*/
getInfoCall() {
const callOptions = Object.assign({}, this.resources.getInfo);
return callOptions;
}
/**
* Send a complete Small Single Packet
*
* LWM2M path: /1024//80
*
* @tapVersion(">=1.13")
* @param data input
* @return
*/
writeAndExecuteBytes(
/*
*
*/
data) {
return this.serviceCallRunner.execute(this.writeAndExecuteBytesCall(data));
}
/**
*
*
* LWM2M path: /1024//80
*
* @tapVersion(">=1.13")
* @param data input
* @return call options
*/
writeAndExecuteBytesCall(
/*
*
*/
data) {
const callOptions = Object.assign({}, this.resources.writeAndExecuteBytes);
callOptions.body = data;
return callOptions;
}
/**
* Send and execute the given Single Packet in one request
*
* LWM2M path: /1024//80
*
* @tapVersion(">=1.13")
* @param data input
* @return
*/
writeAndExecute(
/*
*
*/
data) {
return this.serviceCallRunner.execute(this.writeAndExecuteCall(data));
}
/**
*
*
* LWM2M path: /1024//80
*
* @tapVersion(">=1.13")
* @param data input
* @return call options
*/
writeAndExecuteCall(
/*
*
*/
data) {
const callOptions = Object.assign({}, this.resources.writeAndExecute);
callOptions.body = data;
return callOptions;
}
/**
* Write single packet chunk
*
* LWM2M path: /1024//82
*
* @tapVersion(">=1.13")
* @param data input
* @return
*/
writeChunk(
/*
*
*/
data) {
return this.serviceCallRunner.execute(this.writeChunkCall(data));
}
/**
*
*
* LWM2M path: /1024//82
*
* @tapVersion(">=1.13")
* @param data input
* @return call options
*/
writeChunkCall(
/*
*
*/
data) {
const callOptions = Object.assign({}, this.resources.writeChunk);
callOptions.body = data;
return callOptions;
}
/**
* Execute single packet stored in Tap storage
*
* LWM2M path: /1024//83
*
* @tapVersion(">=1.13")
* @param crc input
* @return
*/
executeSinglePacketInStore(
/*
* CRC of the single packet stored in Tap storage.
*/
crc) {
return this.serviceCallRunner.execute(this.executeSinglePacketInStoreCall(crc));
}
/**
*
*
* LWM2M path: /1024//83
*
* @tapVersion(">=1.13")
* @param crc input
* @return call options
*/
executeSinglePacketInStoreCall(
/*
* CRC of the single packet stored in Tap storage.
*/
crc) {
const callOptions = Object.assign({}, this.resources.executeSinglePacketInStore);
callOptions.body = crc;
return callOptions;
}
}
const _TAP_SERVICE_EXTENSION_SINGLEPACKET_ = extendServiceContainer('singlePacket', SinglePacketService);
class SinglePacketError extends CodeError {
constructor(code, msg) {
super(msg, code);
}
static authSessionRequiredToEncryptSinglePacket() {
return new SinglePacketError(SinglePacketError.Code.AuthSessionRequiredToEncryptSinglePacket, `A valid authenticated Tap session is required to encrypt single packet`);
}
static credentialsRequiredForEncryptedSinglePacket() {
return new SinglePacketError(SinglePacketError.Code.CredentialsRequiredForEncryptedSinglePacket, `Single packet encryption is enabled, you need to provid encryption credentials`);
}
static internalError(msg) {
return new SinglePacketError(SinglePacketError.Code.SinglePacketInternalError, `Single packet related internal error: ${msg}`);
}
static maxSinglePacketPartSizeExceeded(packet, max) {
return new SinglePacketError(SinglePacketError.Code.MaxSinglePacketPartSizeExceeded, `Single packet part size of ${packet.data.length} bytes is above single packet maximum size of ${max} bytes`);
}
static maxSinglePacketSizeExceeded(length, max) {
return new SinglePacketError(SinglePacketError.Code.MaxSinglePacketSizeExceeded, `Single packet size of ${length} bytes is above single packet maximum size of ${max} bytes`);
}
}
(function (SinglePacketError) {
let Code;
(function (Code) {
Code["SinglePacketInternalError"] = "SinglePacketErrorInternalError";
Code["AuthSessionRequiredToEncryptSinglePacket"] = "SinglePacketErrorAuthSessionRequiredToEncryptSinglePacket";
Code["MaxSinglePacketPartSizeExceeded"] = "SinglePacketErrorMaxSinglePacketPartSizeExceeded";
Code["MaxSinglePacketSizeExceeded"] = "SinglePacketErrorMaxSinglePacketSizeExceeded";
Code["CredentialsRequiredForEncryptedSinglePacket"] = "SinglePacketErrorCredentialsRequiredForEncryptedSinglePacket";
})(Code = SinglePacketError.Code || (SinglePacketError.Code = {}));
})(SinglePacketError || (SinglePacketError = {}));
class DefaultSinglePacketKeyGenerator {
/**
* HMsg = UserName | IoTizeSN | PacketHeader1
*/
generate(options) {
const stream = new TapStreamWriter();
if (options.username.length > 16) {
// TODO constant
throw new Error(`Invalid username too long: ${options.username}`);
}
stream.writeStr(options.username);
if (options.serialNumber.length !== 18) {
// TODO constant
// TODO constant
throw new Error(`Invalid sn: ${options.serialNumber}. Length must be 18`);
}
stream.writeStr(options.serialNumber, 18);
if (options.headerBytes.length !== 12) {
throw new Error(`Invalid header length ${options.headerBytes.length}. Expected 12`);
}
stream.writeBytes(options.headerBytes);
const data = stream.toBytes;
const key = hmacSHA256(data, options.hashKey).slice(0, 16);
return key;
}
}
const TAG$1 = 'SinglePacket';
TapStreamReader.prototype.readSinglePacketPayloadFullData =
function () {
const model = {};
model.logTime = this.readU4();
model.dataSize = this.readU2();
model.data = this.readBytes(-4);
model.crc = this.readU4();
return model;
};
class SinglePacketEncryptionOptionsBuilder {
static fromBlankKey(username) {
return {
hashKey: new Uint8Array(32),
serialNumber: 'IoTize_FWUpdate_SP',
username,
};
}
static fromHashKey(hashKey, username) {
return {
hashKey,
serialNumber: 'IoTize_FWUpdate_SP',
username,
};
}
static fromUserCredentials(credentials, options) {
const keys = ScramAuth.computeBaseKeys(credentials.password, options);
return SinglePacketEncryptionOptionsBuilder.fromCurrentSessionData({
username: credentials.username,
serverKey: keys.serverKey,
storedKey: keys.storedKey,
});
}
static fromCurrentSessionData(sessionData) {
const hashKey = TapStreamWriter.create(32)
.writeBytes(sessionData.storedKey)
.writeBytes(sessionData.serverKey).toBytes;
if (hashKey.length !== 32) {
// TODO constant
throw new Error(`Single packet encryption not available`);
}
return {
hashKey,
serialNumber: 'IoTize_FWUpdate_SP',
username: sessionData.username,
};
}
}
class EncryptedSinglePacketConverter {
constructor(options, encryptionAlgo = new AesEcb128Converter(), keyGenerator = new DefaultSinglePacketKeyGenerator()) {
this.options = options;
this.encryptionAlgo = encryptionAlgo;
this.keyGenerator = keyGenerator;
}
static createWithBlankKey() {
return new EncryptedSinglePacketConverter(SinglePacketEncryptionOptionsBuilder.fromBlankKey('IoTize'));
}
setEncryptionOptions(options) {
this.options = Object.assign(Object.assign({}, this.options), options);
}
encode(packet) {
const stream = new TapStreamWriter();
stream.writeU4(packet.sendTime);
stream.writeSinglePacketHeader(packet.header);
const payloadBytesPosition = stream.pos;
stream.writeSinglePacketPayload(packet.payload);
if (packet.header.encryption) {
const bytes = stream.toBytes;
// debug(`SinglePacket before encryption: ${toHexString(bytes)}`);
const encryptionAlgo = this._getEncryptionAlgo(packet.header);
let encryptedPayloadBytes = bytes.slice(payloadBytesPosition);
encryptedPayloadBytes = encryptionAlgo.encode(encryptedPayloadBytes);
stream.pos = payloadBytesPosition;
stream.writeBytes(encryptedPayloadBytes);
}
return stream.toBytes;
}
decode(packetData) {
const stream = TapStreamReader.fromArray(packetData);
const sendTime = stream.readU4();
const header = stream.readSinglePacketHeader();
let payload;
if (header.encryption) {
const encryptedData = stream.readBytesFull();
const encryptionAlgo = this._getEncryptionAlgo(header);
const decryptedData = encryptionAlgo.decode(encryptedData);
const payloadStream = TapStreamReader.fromArray(decryptedData);
// if (header.messageType == SinglePacketModel.PacketType.CODE_EXEC) {
payload = payloadStream.readSinglePacketPayloadFullData();
// } else {
// payload = payloadStream.readSinglePacketPayload();
// }
}
else {
payload = stream.readSinglePacketPayload();
}
const packet = {
sendTime,
header: header,
payload,
};
// TODO check CRC
// let computedCrc = 0;
// if (computedCrc !== packet.payload.crc){
// CRC.fromBytes()
// }
return packet;
}
_getEncryptionAlgo(header) {
const encryptionKey = this.computeKey(header);
this.encryptionAlgo.setOptions({
iv: '00000000000000000000000000000000',
key: bufferToHexString(encryptionKey),
});
return this.encryptionAlgo;
}
computeKey(header) {
if (!(header instanceof Uint8Array)) {
const stream = new TapStreamWriter(12);
header = stream.writeSinglePacketHeader(header).toBytes;
}
const generateOptions = Object.assign(Object.assign({}, this.options), {
headerBytes: header,
});
const key = this.keyGenerator.generate(generateOptions);
return key;
}
}
const debug = createDebugger('@iotize/tap/single-packet');
function computePadding(size, modulo) {
return (modulo - (size % modulo)) % modulo;
}
function senderIdToUserId(senderId) {
if (senderId >= 0x80) {
senderId += 0xff00;
}
return senderId;
}
function userIdToSenderId(userId) {
if (userId > 0xffff) {
throw new Error(`Invalid user id. Should be between 0 and ${0xffff}`); // TODO custom error
}
if (userId > 0x7f && userId < 0xff80) {
throw new Error(`Invalid user id. Should be between 0 and ${0} and ${0x7f} or ${0xffff} and ${0xff80}`);
} // TODO custom error
return 0xff & userId;
}
function usernameToSenderId(username) {
switch (username) {
case 'IoTize':
return 0xfd;
case 'admin':
return userIdToSenderId(0xffff); // TODO constant
case 'anonymous':
return userIdToSenderId(0x0);
case 'supervisor':
return userIdToSenderId(0xfffe);
default:
return 0;
}
}
// function createCredentialsFromCurrentSessionData() {
// const authSessionState = this.tap.auth.sessionStateSnapshot;
// const authSessionData = this.tap.auth.sessionDataSnapshot;
// console.info('Session data: ', authSessionState);
// if (!authSessionData.serverKey || !authSessionData.storedKey || !authSessionState.name) {
// throw SinglePacketError.authSessionRequiredToEncryptSinglePacket();
// }
// return SinglePacketEncryptionOptionsBuilder.fromCurrentSessionData({
// username: authSessionState.name,
// serverKey: authSessionData.serverKey,
// storedKey: authSessionData.storedKey
// });
// }
const TAG = 'SinglePacketBuilder';
const ZEROS_PADDING_GENERATOR = (length) => new Uint8Array(length);
class SinglePacketBuilder {
constructor(type, paddingGenerator = ZEROS_PADDING_GENERATOR) {
this.paddingGenerator = paddingGenerator;
this.model = {
sendTime: 0,
header: {
ack: false,
configVersion: 0,
encryption: false,
isExtended: false,
messageType: type,
packetId: 0,
packetLength: 0,
salt: Math.round(Math.random() * 0xffff),
senderId: 0,
},
payload: {
data: new Uint8Array(),
dataSize: 0,
logTime: SinglePacketBuilder.getTimestampInSeconds(),
padding: undefined, // TODO
},
};
}
// static fillComputedField(packet: SinglePacket): SinglePacket {
// if (!packet.sendTime) {
// packet.sendTime = SinglePacketBuilder.getTimestampInSeconds();
// }
// packet.payload.dataSize = packet.payload.data.length;
// packet.header.packetLength =
// computePadding(packet.payload.data.length + 10, 4) +
// packet.payload.data.length +
// SinglePacketBuilder.PACKET_HEADER_SIZE_WITH_SEND_TIME;
// let multiplicator = SinglePacketBuilder.getLengthMultiplicator(packet);
// if (packet.header.packetLength % multiplicator !== 0){
// throw new Error(`Packet length must be a multiple of ${multiplicator}. But length is ${packet.header.packetLength}`);
// }
// return packet;
// }
static getTimestampInSeconds() {
return Math.round(new Date().getTime() / 1000);
}
static getLengthMultiplicator(packet) {
if (packet.header.isExtended) {
return 256;
}
else if (packet.header.encryption ||
packet.header.messageType === SinglePacket.PacketType.CODE_EXEC) {
return 16;
}
else {
return 1;
}
}
static create(data, type, paddingGenertor = ZEROS_PADDING_GENERATOR) {
const builder = new SinglePacketBuilder(type, paddingGenertor);
builder.setData(data);
return builder;
}
setLogTime(logTime) {
this.model.payload.logTime = logTime;
return this;
}
/**
*
* @param id
*/
setPacketId(id) {
// TODO check length ?
this.model.header.packetId = id;
return this;
}
setSalt(salt) {
this.model.header.salt = salt;
return this;
}
setExtended(value) {
this.model.header.isExtended = value;
return this;
}
/**
*
* @param id
*/
setEncryption(enabled) {
// TODO check length ?
this.model.header.encryption = enabled;
return this;
}
/**
*
* @param id
*/
setSenderId(id) {
// TODO check length ?
this.model.header.senderId = id;
return this;
}
/**
*
* @param id
*/
setSendTime(time) {
this.model.sendTime = time;
return this;
}
/**
*
* @param id
*/
setConfigVersion(configVersion) {
this.model.header.configVersion = configVersion;
return this;
}
setData(data) {
this.model.payload.data = data;
return this;
}
getLengthMultiplicator() {
return SinglePacketBuilder.getLengthMultiplicator(this.model);
}
build() {
if (!this.model.sendTime) {
this.model.sendTime = SinglePacketBuilder.getTimestampInSeconds();
}
let multiplicator = this.getLengthMultiplicator();
if (this.model.payload.data.length > 0xffff * multiplicator) {
this.model.header.isExtended = true;
}
multiplicator = this.getLengthMultiplicator(); // Refresh multiplicator if isExtended
const paddingMultiple = Math.max(4, multiplicator); // TODO constant
// debug(TAG, 'Multiplicator', multiplicator, 'padding mult', paddingMultiple);
const requiredPadding = computePadding(this.model.payload.data.length +
SinglePacketBuilder.PACKET_PAYLOAD_HEADER_SIZE, paddingMultiple);
// debug(TAG, 'requiredPadding', requiredPadding);
this.model.payload.padding = this.paddingGenerator(requiredPadding);
// debug(TAG, 'this.model.payload.padding', this.model.payload.padding);
const payloadLengthInBytes = this.model.payload.data.length +
SinglePacketBuilder.PACKET_PAYLOAD_HEADER_SIZE +
this.model.payload.padding.length;
const packetLengthInBytes = SinglePacketBuilder.PACKET_HEADER_SIZE +
this.model.payload.data.length +
this.model.payload.padding.length;
this.model.payload.dataSize =
this.model.payload.data.length > 0xffff
? undefined
: this.model.payload.data.length;
if (payloadLengthInBytes % multiplicator !== 0) {
throw SinglePacketError.internalError(`Packet length ${packetLengthInBytes} must be a multiple of ${multiplicator}`);
}
if (packetLengthInBytes > SinglePacketBuilder.MAX_FULL_SIZE) {
throw SinglePacketError.maxSinglePacketSizeExceeded(packetLengthInBytes, SinglePacketBuilder.MAX_FULL_SIZE);
}
this.model.header.packetLength = payloadLengthInBytes / multiplicator;
if (multiplicator === 1) {
this.model.header.packetLength +=
SinglePacketBuilder.PACKET_OUTER_HEADER_SIZE;
}
return this.model;
}
}
SinglePacketBuilder.MAX_FULL_SIZE = 0x200000;
SinglePacketBuilder.MAX_PART_SIZE = 0xc0;
SinglePacketBuilder.PACKET_PAYLOAD_HEADER_SIZE = 4 + 2 + 4;
SinglePacketBuilder.PACKET_OUTER_HEADER_SIZE = 12;
SinglePacketBuilder.PACKET_HEADER_SIZE = SinglePacketBuilder.PACKET_OUTER_HEADER_SIZE +
SinglePacketBuilder.PACKET_PAYLOAD_HEADER_SIZE;
SinglePacketBuilder.PACKET_HEADER_SIZE_WITH_SEND_TIME = SinglePacketBuilder.PACKET_HEADER_SIZE + 4;
SinglePacketBuilder.MAX_DATA_SIZE = SinglePacketBuilder.MAX_FULL_SIZE -
(SinglePacketBuilder.PACKET_HEADER_SIZE_WITH_SEND_TIME + 0xff);
/**
* Utility class to split a big single packet data into {@link SinglePacketChunk}
*/
class SinglePacketSplitter {
/**
* @throws {@link #chunkSize} < {@link #unitBlockSize}
*/
constructor(options = {}) {
if (!options.sizeMultiplicator) {
options.sizeMultiplicator = SinglePacketSplitter.SIZE_MULTIPLICATOR;
}
if (!options.chunkSize) {
options.chunkSize = SinglePacketBuilder.MAX_PART_SIZE;
}
if (!options.format) {
options.format = SinglePacketChunk.Format.OFFSET_ON_2_BYTES;
}
if (!options.pad) {
options.pad = false;
}
this.options = options;
this.checkOptions();
}
get sizeMultiplicator() {
return this.options.sizeMultiplicator;
}
checkOptions() {
// Maybe we should force chunk size to be a multiple of unitBlockSize ?
if (this.options.chunkSize < this.sizeMultiplicator) {
throw new Error(`Chunk size must be greater than ${this.sizeMultiplicator}. Given: ${this.options.chunkSize}`);
}
if (!this.options.pad &&
this.options.chunkSize % this.sizeMultiplicator !== 0) {
throw new Error(`Chunk size "${this.options.chunkSize}" is not a multiple of length multiplicator "${this.sizeMultiplicator}"`);
}
}
/**
* @return an array of single packet parts
*/
chunk(data) {
if (!this.options.pad) {
if (data.length % this.sizeMultiplicator !== 0) {
throw new Error(`Padding is disabled and data length (${data.length} byte(s)) is not a multiple of length multiplicator ${this.sizeMultiplicator})`);
}
}
else {
const padding = computePadding(data.length, this.sizeMultiplicator);
if (padding > 0) {
data = KaitaiStreamWriter.mergeArrays(data, new Uint8Array(padding));
}
}
const packets = chunkArray(Array.from(data), this.options.chunkSize).map((value) => Uint8Array.from(value));
let offset = 0;
return packets.map((chunk) => {
const part = {
data: chunk,
length: Math.ceil(chunk.length / this.sizeMultiplicator),
offset: Math.ceil(offset / this.sizeMultiplicator),
format: this.options.format,
};
offset += chunk.length;
return part;
});
}
}
SinglePacketSplitter.SIZE_MULTIPLICATOR = 16;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
class SinglePacketStoreLoader extends StepOperations {
constructor(steps,
// public readonly crc: number,
singlePacketService) {
super(StepOperations.createSequentialContainers(steps));
this.singlePacketService = singlePacketService;
}
static createOperationsFromBytes(data, singlePacketService, options) {
const chunkSize = (options === null || options === void 0 ? void 0 : options.chunkSize) || SinglePacketBuilder.MAX_PART_SIZE;
const offsetFitsOn2Bytes = data.length / chunkSize <= 0xffff;
const singlePacketSplitter = new SinglePacketSplitter({
chunkSize,
format: offsetFitsOn2Bytes
? SinglePacketChunk.Format.OFFSET_ON_2_BYTES
: SinglePacketChunk.Format.OFFSET_ON_4_BYTES,
pad: options && 'pad' in options ? options.pad : true,
});
const packets = singlePacketSplitter.chunk(data);
return SinglePacketStoreLoader.createOperationsFromChunk(packets, singlePacketService, options);
}
static createFromBytes(data, singlePacketService, options) {
const steps = SinglePacketStoreLoader.createOperationsFromBytes(data, singlePacketService, options);
return new SinglePacketStoreLoader(steps,
// expectedCRC,
singlePacketService);
}
/**
* Create single packet loading operations
* @param packets Single packet chunks to load to the Tap
* @param expectedCRC expected CRC sent to the Tap when executing the single packet. if you don't plan to execute single packet, you pass a dummy value
* @param singlePacketService
* @param options
*/
static createOperationsFromChunk(packets, singlePacketService, options, output = {
expectedCRC: 0,
}) {
output.expectedCRC = CRC.FIRST_VALUE;
const operations = packets.map((packet, index) => {
return (observer, context) => __awaiter(this, void 0, void 0, function* () {
if (packet.data.length > SinglePacketBuilder.MAX_PART_SIZE) {
throw SinglePacketError.maxSinglePacketPartSizeExceeded(packet, SinglePacketBuilder.MAX_PART_SIZE);
}
debug('createSinglePacketLoadOperations', `sendSinglePacket`, `sending packet ${index + 1}/${packets.length} offset: ${packet.offset}...${packet.offset + (packet.length || 0)}`);
let retryCount = 0;
const maxRetry = (options === null || options === void 0 ? void 0 : options.maxRetry) || 3;
output.expectedCRC = CRC.fromBytes(packet.data, output.expectedCRC);
while (true) {
retryCount++;
// if (userIncrementalSinglePacketPart) {
// const call = { ...this.tap.service.singlePacket.resources.writeChunk };
// call.bodyEncoder = undefined;
// call.body = encodePartialSinglePacketBig(packet.data, packet.offset * this.singlePacketSplitter.options.sizeMultiplicator);
// response = await this.tap.lwm2m.execute(call);
const response = yield singlePacketService.writeChunk(packet);
// }
// else {
// response = await singlePacketService.writeChunk(packet);
// }
if (retryCount < maxRetry &&
(response.status === ResultCode.SERVICE_UNAVAILABLE ||
response.status === ResultCode.NVM_ERROR)) {
debug('createSinglePacketLoadOperations', `Attempt ${retryCount + 1}/${maxRetry} failed with error ${TapError.reponseStatusError(response).toString()}`);
yield sleep((options === null || options === void 0 ? void 0 : options.retryDelay) || 500);
continue;
}
response.successful();
observer.next({
step: SinglePacketStoreLoader.Step.loadPacketChunk,
progress: {
total: packets.length,
loaded: index + 1,
},
});
return;
}
// debug(TAG, `sendSinglePacket`, `Packet ${index + 1}/${packets.length} SENT!`);
});
});
if (options === null || options === void 0 ? void 0 : options.execute) {
operations.push((observer, context) => __awaiter(this, void 0, void 0, function* () {
observer.next({
step: SinglePacketStoreLoader.Step.executeSinglePacket,
expectedCRC: output.expectedCRC,
});
(yield singlePacketService.executeSinglePacketInStore(output.expectedCRC)).successful();
}));
}
return operations;
}
static createFromChunks(packets, singlePacketService, options) {
return new SinglePacketStoreLoader(SinglePacketStoreLoader.createOperationsFromChunk(packets, singlePacketService, options), singlePacketService);
}
static computeCRC(data) {
if (data instanceof Uint8Array) {
return CRC.mpeg2().compute(data);
}
else {
return data.reduce((crc, packet) => {
return CRC.mpeg2().compute(packet.data, crc);
}, CRC.FIRST_VALUE);
}
}
}
(function (SinglePacketStoreLoader) {
let Step;
(function (Step) {
Step["loadPacketChunk"] = "loadPacketChunk";
Step["executeSinglePacket"] = "executeSinglePacket";
})(Step = SinglePacketStoreLoader.Step || (SinglePacketStoreLoader.Step = {}));
})(SinglePacketStoreLoader || (SinglePacketStoreLoader = {}));
/**
* Load single packet model to store
* SinglePacket will be encoded to a single packet frame.
* If credentials parameter is given, it will be encrypted with it and sender id will be filled
* If no credential is given and SinglePacket.header.encryption is set to true, it will throw an error
*
* @param packet
* @param credentials
*/
SinglePacketService.prototype.createStoreLoader = function (packet, options, credentials) {
const encoder = EncryptedSinglePacketConverter.createWithBlankKey();
if (credentials) {
encoder.setEncryptionOptions(credentials);
packet.header.encryption = true;
packet.header.senderId = usernameToSenderId(credentials.username);
}
else if (packet.header.encryption) {
throw SinglePacketError.credentialsRequiredForEncryptedSinglePacket();
}
const encodedPacket = encoder.encode(packet);
return this.createStoreLoaderFromBytes(encodedPacket, options);
};
SinglePacketService.prototype.createStoreLoaderFromBytes = function (data, options) {
return SinglePacketStoreLoader.createFromBytes(data, this, options);
};
// SinglePacketService.prototype.loadChunksToStore = function (
// this: SinglePacketService,
// packets: SinglePacketChunk[]
// ): Observable<any> {
// return this.createChunksToStoreLoader.asObservable();
// };
// SinglePacketService.prototype.createChunksToStoreLoader = function (
// this: SinglePacketService,
// packets: SinglePacketChunk[]
// ): SinglePacketStoreLoader {
// return SinglePacketStoreLoader.createFromChunks(
// packets,
// this
// );
// };
/**
* Generated file. Do not edit
*/
const ResourceMetaData = {};
/**
* Generated bundle index. Do not edit.
*/
export { EncryptedSinglePacketConverter, SERVICE_CALLS as SINGLEPACKET_SERVICE_CALLS, SinglePacket, SinglePacketBuilder, SinglePacketChunk, SinglePacketChunkConverter, SinglePacketConverter, SinglePacketError, ResourceMetaData as SinglePacketResourceMetaData, SinglePacketService, SinglePacketSplitter, SinglePacketStoreInfo, SinglePacketStoreInfoConverter, SinglePacketStoreLoader, _TAP_SERVICE_EXTENSION_SINGLEPACKET_, serviceConverters as singlePacketConverters, SinglePacketEncryptionOptionsBuilder as ɵa };
//# sourceMappingURL=iotize-tap-service-impl-single-packet.js.map