UNPKG

@iotize/tap

Version:

IoTize Device client for Javascript

1,088 lines (1,067 loc) 42.4 kB
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