rsocket-rxjs
Version:
RSocket Protocol Client Implementation
404 lines (403 loc) • 15.1 kB
JavaScript
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;
}
}