UNPKG

p2p-media-loader-core

Version:
225 lines 8.51 kB
import * as Serialization from "./binary-serialization.js"; const FRAME_PART_LENGTH = 4; const commandFrameStart = stringToUtf8CodesBuffer("cstr", FRAME_PART_LENGTH); const commandFrameEnd = stringToUtf8CodesBuffer("cend", FRAME_PART_LENGTH); const commandDivFrameStart = stringToUtf8CodesBuffer("dstr", FRAME_PART_LENGTH); const commandDivFrameEnd = stringToUtf8CodesBuffer("dend", FRAME_PART_LENGTH); const startFrames = [commandFrameStart, commandDivFrameStart]; const endFrames = [commandFrameEnd, commandDivFrameEnd]; const commandFramesLength = commandFrameStart.length + commandFrameEnd.length; export function isCommandChunk(buffer) { const { length } = commandFrameStart; const bufferEndingToCompare = buffer.slice(-length); return (startFrames.some((frame) => areBuffersEqual(buffer, frame, FRAME_PART_LENGTH)) && endFrames.some((frame) => areBuffersEqual(bufferEndingToCompare, frame, FRAME_PART_LENGTH))); } function isFirstCommandChunk(buffer) { return areBuffersEqual(buffer, commandFrameStart, FRAME_PART_LENGTH); } function isLastCommandChunk(buffer) { return areBuffersEqual(buffer.slice(-FRAME_PART_LENGTH), commandFrameEnd, FRAME_PART_LENGTH); } export class BinaryCommandJoiningError extends Error { constructor(type) { super(); Object.defineProperty(this, "type", { enumerable: true, configurable: true, writable: true, value: type }); } } export class BinaryCommandChunksJoiner { constructor(onComplete) { Object.defineProperty(this, "onComplete", { enumerable: true, configurable: true, writable: true, value: onComplete }); Object.defineProperty(this, "chunks", { enumerable: true, configurable: true, writable: true, value: new Serialization.ResizableUint8Array() }); Object.defineProperty(this, "status", { enumerable: true, configurable: true, writable: true, value: "joining" }); } addCommandChunk(chunk) { if (this.status === "completed") return; const isFirstChunk = isFirstCommandChunk(chunk); if (!this.chunks.length && !isFirstChunk) { throw new BinaryCommandJoiningError("no-first-chunk"); } if (this.chunks.length && isFirstChunk) { throw new BinaryCommandJoiningError("incomplete-joining"); } this.chunks.push(this.unframeCommandChunk(chunk)); if (!isLastCommandChunk(chunk)) return; this.status = "completed"; this.onComplete(this.chunks.getBuffer()); } unframeCommandChunk(chunk) { return chunk.slice(FRAME_PART_LENGTH, chunk.length - FRAME_PART_LENGTH); } } export class BinaryCommandCreator { constructor(commandType, maxChunkLength) { Object.defineProperty(this, "maxChunkLength", { enumerable: true, configurable: true, writable: true, value: maxChunkLength }); Object.defineProperty(this, "bytes", { enumerable: true, configurable: true, writable: true, value: new Serialization.ResizableUint8Array() }); Object.defineProperty(this, "resultBuffers", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "status", { enumerable: true, configurable: true, writable: true, value: "creating" }); this.bytes.push(commandType); } addInteger(name, value) { this.bytes.push(name.charCodeAt(0)); const bytes = Serialization.serializeInt(BigInt(value)); this.bytes.push(bytes); } addSimilarIntArr(name, arr) { this.bytes.push(name.charCodeAt(0)); const bytes = Serialization.serializeSimilarIntArray(arr.map((num) => BigInt(num))); this.bytes.push(bytes); } addString(name, string) { this.bytes.push(name.charCodeAt(0)); const bytes = Serialization.serializeString(string); this.bytes.push(bytes); } complete() { if (!this.bytes.length) throw new Error("Buffer is empty"); if (this.status === "completed") return; this.status = "completed"; const unframedBuffer = this.bytes.getBuffer(); if (unframedBuffer.length + commandFramesLength <= this.maxChunkLength) { this.resultBuffers.push(frameBuffer(unframedBuffer, commandFrameStart, commandFrameEnd)); return; } let chunksCount = Math.ceil(unframedBuffer.length / this.maxChunkLength); if (Math.ceil(unframedBuffer.length / chunksCount) + commandFramesLength > this.maxChunkLength) { chunksCount++; } for (const [i, chunk] of splitBufferToEqualChunks(unframedBuffer, chunksCount)) { if (i === 0) { this.resultBuffers.push(frameBuffer(chunk, commandFrameStart, commandDivFrameEnd)); } else if (i === chunksCount - 1) { this.resultBuffers.push(frameBuffer(chunk, commandDivFrameStart, commandFrameEnd)); } else { this.resultBuffers.push(frameBuffer(chunk, commandDivFrameStart, commandDivFrameEnd)); } } } getResultBuffers() { if (this.status === "creating" || !this.resultBuffers.length) { throw new Error("Command is not complete."); } return this.resultBuffers; } } export function deserializeCommand(bytes) { const [commandCode] = bytes; const deserializedCommand = { c: commandCode, }; let offset = 1; while (offset < bytes.length) { const name = String.fromCharCode(bytes[offset]); offset++; const dataType = getDataTypeFromByte(bytes[offset]); switch (dataType) { case Serialization.SerializedItem.Int: { const { number, byteLength } = Serialization.deserializeInt(bytes.slice(offset)); deserializedCommand[name] = Number(number); offset += byteLength; } break; case Serialization.SerializedItem.SimilarIntArray: { const { numbers, byteLength } = Serialization.deserializeSimilarIntArray(bytes.slice(offset)); deserializedCommand[name] = numbers.map((n) => Number(n)); offset += byteLength; } break; case Serialization.SerializedItem.String: { const { string, byteLength } = Serialization.deserializeString(bytes.slice(offset)); deserializedCommand[name] = string; offset += byteLength; } break; } } return deserializedCommand; } function getDataTypeFromByte(byte) { const typeCode = byte >> 4; if (typeCode <= Serialization.SerializedItem.Min || typeCode >= Serialization.SerializedItem.Max) { throw new Error("Not existing type"); } return typeCode; } function stringToUtf8CodesBuffer(string, length) { if (length && string.length !== length) { throw new Error("Wrong string length"); } const buffer = new Uint8Array(length ?? string.length); for (let i = 0; i < string.length; i++) buffer[i] = string.charCodeAt(i); return buffer; } function* splitBufferToEqualChunks(buffer, chunksCount) { const chunkLength = Math.ceil(buffer.length / chunksCount); for (let i = 0; i < chunksCount; i++) { yield [i, buffer.slice(i * chunkLength, (i + 1) * chunkLength)]; } } function frameBuffer(buffer, frameStart, frameEnd) { const result = new Uint8Array(buffer.length + frameStart.length + frameEnd.length); result.set(frameStart); result.set(buffer, frameStart.length); result.set(frameEnd, frameStart.length + buffer.length); return result; } function areBuffersEqual(buffer1, buffer2, length) { for (let i = 0; i < length; i++) { if (buffer1[i] !== buffer2[i]) return false; } return true; } //# sourceMappingURL=binary-command-creator.js.map