UNPKG

rsocket-rxjs

Version:
404 lines (403 loc) 15.1 kB
import { stringToAsciiArrayBuffer, stringToUtf8ArrayBuffer } from '../../utlities/conversions'; import { Frame, FrameType } from "./frame"; export class FrameBuilder { constructor(initialBufferSize = 128) { this.buffer = new ArrayBuffer(initialBufferSize); this.view = new DataView(this.buffer); } streamId(streamId) { this.view.setUint32(0, streamId); return this; } static setup() { return new SetupFrameBuilder(); } static keepalive() { return new KeepaliveFrameBuilder(); } static cancel() { return new CancelFrameBuilder(); } static requestResponse() { return new RequestResponseFrameBuilder(); } static requestStream() { return new RequestStreamFrameBuilder(); } static requestFNF() { return new RequestFNFFrameBuilder(); } static payload() { return new PayloadFrameBuilder(); } static error() { return new ErrorFrameBuilder(); } static requestN() { return new RequestNFrameBuilder(); } build() { return [new Frame(new Uint8Array(this.buffer, 0, this.writerIndex))]; } setFrameType(type) { this.view.setUint16(4, this.view.getUint16(4) | type << 10); } requireMinFreeBytes(bytes) { let targetLength = this.buffer.byteLength; if (bytes > 1024) { targetLength = this.writerIndex + bytes; } else { while (this.writerIndex + bytes >= targetLength) { targetLength = 2 * targetLength; } } if (targetLength > this.buffer.byteLength) { const newBuffer = new ArrayBuffer(targetLength); new Uint8Array(newBuffer).set(new Uint8Array(this.buffer)); this.buffer = newBuffer; this.view = new DataView(this.buffer); } } flagMetadataPresent() { this.view.setUint16(4, this.view.getUint16(4) | (1 << 8)); return this; } setRequests(requests) { this.view.setUint32(6, requests); } } export class SetupFrameBuilder extends FrameBuilder { constructor() { super(32); this.setFrameType(FrameType.SETUP); this.writerIndex = 18; // First 18 bytes are reserved } buildFromConfig(config) { this.streamId(0); if (config.setupPayload.hasMetadata()) { this.flagMetadataPresent(); } if (config.honorsLease) { this.flagHonorsLease(); } this.majorVersion(config.majorVersion); this.minorVersion(config.minorVersion); this.keepaliveTime(config.keepaliveTime); this.maxLifetime(config.maxLifetime); if (config.resumeIdentificationToken != undefined) { this.flagResumeEnable(); this.resumeToken(config.resumeIdentificationToken); this.writerIndex = 2 + config.resumeIdentificationToken.byteLength; } this.mimeType(stringToAsciiArrayBuffer(config.metadataMimeType)); this.mimeType(stringToAsciiArrayBuffer(config.dataMimeType)); this.payload(config.setupPayload); // if (config.data != undefined && config.metaData != undefined) { // this.payload(new Payload(config.dataMimeType.coder.encoder(config.data, mimeTypeRegistry), config.metadataMimeType.coder.encoder(config.metaData, mimeTypeRegistry))); // } else if (config.data == undefined && config.metaData != undefined) { // this.payload(new Payload(new Uint8Array(0), config.metadataMimeType.coder.encoder(config.metaData, mimeTypeRegistry))); // } else if (config.data != undefined && config.metaData == undefined) { // this.payload(new Payload(config.dataMimeType.coder.encoder(config.data, mimeTypeRegistry))); // } return new Frame(new Uint8Array(this.buffer, 0, this.writerIndex)); } flagMetadataPresent() { this.view.setUint16(4, this.view.getUint16(4) | (1 << 8)); return this; } flagResumeEnable() { this.view.setUint16(4, this.view.getUint16(4) | (1 << 7)); return this; } flagHonorsLease() { this.view.setUint16(4, this.view.getUint16(4) | (1 << 6)); return this; } majorVersion(version) { this.view.setUint16(6, version); return this; } minorVersion(version) { this.view.setUint16(8, version); return this; } keepaliveTime(time) { this.view.setUint32(10, time); return this; } maxLifetime(time) { this.view.setUint32(14, time); return this; } resumeToken(token) { this.requireMinFreeBytes(token.byteLength + 2); this.view.setUint16(18, token.byteLength); new Uint8Array(this.buffer, 20).set(new Uint8Array(token)); this.writerIndex += 2 + token.byteLength; return this; } mimeType(mimeType) { this.requireMinFreeBytes(mimeType.byteLength + 1); const int8View = new Uint8Array(this.buffer, this.writerIndex); int8View[0] = mimeType.byteLength; int8View.set(mimeType, 1); this.writerIndex += 1 + mimeType.byteLength; } payload(payload) { let targetView; if (payload.hasMetadata()) { this.requireMinFreeBytes(3 + payload.metadata.byteLength + payload.data.byteLength); targetView = new Uint8Array(this.buffer, this.writerIndex); this.writerIndex += 3 + payload.metadata.byteLength + payload.data.byteLength; } else { this.requireMinFreeBytes(payload.data.byteLength); targetView = new Uint8Array(this.buffer, this.writerIndex); this.writerIndex += payload.data.byteLength; } let position = 0; if (payload.hasMetadata()) { const length = payload.metadata.byteLength; targetView[position++] = length >> 16 & 0xFF; targetView[position++] = length >> 8 & 0xFF; targetView[position++] = length & 0xFF; targetView.set(payload.metadata, position); position += payload.metadata.byteLength; } targetView.set(payload.data, position); } } export class RequestOrPayloadBuilder extends FrameBuilder { constructor(initialSize) { super(initialSize); this._complete = false; this._fragments = []; this._fragments.push(this); } streamId(streamId) { this._streamId = streamId; return super.streamId(streamId); } payload(data, fragmentationSize) { let metadataSize = (data.hasMetadata() ? data.metadata.byteLength : 0); let dataSize = (data.data ? data.data.byteLength : 0); let payloadSize = metadataSize + dataSize; let payloadFragments = []; let metadataIdx = 0; let dataIdx = 0; while (payloadSize > 0) { // need to consider fragmentation const fragment = { dataSize: 0, dataOffset: dataIdx, metadataSize: 0, metadataOffset: metadataIdx, }; if (metadataSize > 0) { if (metadataSize > fragmentationSize) { fragment.metadataSize = fragmentationSize; metadataSize -= fragmentationSize; payloadSize -= fragmentationSize; metadataIdx += fragmentationSize; } else { fragment.metadataSize = metadataSize; payloadSize -= metadataSize; metadataIdx += metadataSize; metadataSize = 0; } } let leftOver = fragmentationSize - fragment.metadataSize; if (dataSize > 0) { if (dataSize > leftOver) { fragment.dataSize = leftOver; payloadSize -= leftOver; dataSize -= leftOver; dataIdx += leftOver; } else { fragment.dataSize = dataSize; payloadSize -= dataSize; dataIdx += dataSize; dataSize = 0; } } payloadFragments.push(fragment); } let currentFrame; for (let i = 0; i < payloadFragments.length; i++) { const payloadFragment = payloadFragments[i]; if (i == 0) { currentFrame = this; } else { currentFrame = FrameBuilder.payload() .streamId(this._streamId); if (this._complete == true) { currentFrame.flagComplete(); } currentFrame.flagNext(); this._fragments.push(currentFrame); // the root frame is already part of fragments to allow frames without payload } if (i < payloadFragments.length - 1) { currentFrame.flagFollows(); } if (payloadFragment.metadataSize > 0) { currentFrame.flagMetadataPresent(); currentFrame.requireMinFreeBytes(payloadFragment.dataSize + payloadFragment.metadataSize + 3); currentFrame.directMetadataWrite(payloadFragment.metadataSize, buffer => { buffer.set(new Uint8Array(data.metadata, payloadFragment.metadataOffset, metadataSize)); return payloadFragment.metadataSize; }); } else { currentFrame.requireMinFreeBytes(payloadFragment.dataSize); } currentFrame.directDataWrite(payloadFragment.dataSize, buffer => { // const view = new Uint8Array(data.data, payloadFragment.dataOffset, payloadFragment.dataSize); // buffer.set(new Uint8Array(data.data, payloadFragment.dataOffset, payloadFragment.dataSize), 0); buffer.set(data.data.subarray(payloadFragment.dataOffset, payloadFragment.dataOffset + payloadFragment.dataSize), 0); return payloadFragment.dataSize; }); } // if (data.hasMetadata()) { // this.flagMetadataPresent(); // this.requireMinFreeBytes(data.metadata.byteLength + 3 + data.data.byteLength); // this.directMetadataWrite(data.metadata.byteLength, buffer => { // buffer.set(new Uint8Array(data.metadata)); // return data.metadata.byteLength; // }); // } else { // this.requireMinFreeBytes(data.data.byteLength); // } // this.directDataWrite(data.data.byteLength, buffer => { // buffer.set(new Uint8Array(data.data)); // return data.data.byteLength; // }); return this; } build() { return this._fragments.map(frag => new Frame(new Uint8Array(frag.buffer, 0, frag.writerIndex))); } directMetadataWrite(requiredSize, writeCall) { this.flagMetadataPresent(); this.requireMinFreeBytes(requiredSize + 3); const view = new Uint8Array(this.buffer, this.writerIndex); const metadataLength = writeCall(new Uint8Array(this.buffer, this.writerIndex + 3)); view[0] = metadataLength >> 16 & 0xFF; view[1] = metadataLength >> 8 & 0xFF; view[2] = metadataLength & 0xFF; this.writerIndex += metadataLength + 3; return this; } directDataWrite(requiredSize, writeCall) { this.requireMinFreeBytes(requiredSize); const bytesWritten = writeCall(new Uint8Array(this.buffer, this.writerIndex)); this.writerIndex += bytesWritten; return this; } flagFollows() { this.view.setUint8(5, this.view.getUint8(5) | (1 << 7)); return this; } } export class KeepaliveFrameBuilder extends FrameBuilder { constructor() { super(); this.setFrameType(FrameType.KEEPALIVE); this.writerIndex = 14; } lastReceivedPosition(position) { this.view.setUint32(6, position >> 32); this.view.setUint32(10, position); return this; } flagRespond() { this.view.setUint16(4, this.view.getUint16(4) | (1 << 7)); return this; } data(data) { this.requireMinFreeBytes(data.byteLength); new Uint8Array(this.buffer, this.writerIndex, data.byteLength).set(data); this.writerIndex += data.byteLength; return this; } } export class CancelFrameBuilder extends FrameBuilder { constructor() { super(6); this.setFrameType(FrameType.CANCEL); this.writerIndex = 6; } } export class RequestResponseFrameBuilder extends RequestOrPayloadBuilder { constructor() { super(128); this.setFrameType(FrameType.REQUEST_RESPONSE); this.writerIndex = 6; } } export class RequestStreamFrameBuilder extends RequestOrPayloadBuilder { constructor() { super(128); this.setFrameType(FrameType.REQUEST_STREAM); this.writerIndex = 10; } requests(requests) { this.setRequests(requests); return this; } } export class RequestFNFFrameBuilder extends RequestOrPayloadBuilder { constructor() { super(128); this.setFrameType(FrameType.REQUEST_FNF); this.writerIndex = 6; } } export class PayloadFrameBuilder extends RequestOrPayloadBuilder { constructor() { super(128); this.setFrameType(FrameType.PAYLOAD); this.writerIndex = 6; } flagComplete() { this.view.setUint8(5, this.view.getUint8(5) | (1 << 6)); this._complete = true; return this; } flagNext() { this.view.setUint8(5, this.view.getUint8(5) | (1 << 5)); return this; } } export class ErrorFrameBuilder extends FrameBuilder { constructor() { super(128); this.setFrameType(FrameType.ERROR); this.writerIndex = 10; } message(msg) { const codedMsg = stringToUtf8ArrayBuffer(msg); this.requireMinFreeBytes(codedMsg.byteLength); new Uint8Array(this.buffer, this.writerIndex).set(codedMsg); this.writerIndex += codedMsg.length; return this; } errorCode(code) { this.view.setInt32(6, code); return this; } } export class RequestNFrameBuilder extends FrameBuilder { constructor() { super(10); this.setFrameType(FrameType.REQUEST_N); this.writerIndex = 10; } requests(n) { this.setRequests(n); return this; } }