n-link/web/components/impl.ts
2020-10-06 11:01:36 -04:00

169 lines
5.1 KiB
TypeScript

import { Encoder } from '@msgpack/msgpack';
export enum UsbError {
NotFound = 'NotFound',
Security = 'Security',
Network = 'Network',
Abort = 'Abort',
InvalidState = 'InvalidState',
InvalidAccess = 'InvalidAccess',
Unknown = 'Unknown',
}
const exceptionMap = globalThis.DOMException
? {
[DOMException.NOT_FOUND_ERR]: UsbError.NotFound,
[DOMException.SECURITY_ERR]: UsbError.Security,
[DOMException.NETWORK_ERR]: UsbError.Network,
[DOMException.ABORT_ERR]: UsbError.Abort,
[DOMException.INVALID_STATE_ERR]: UsbError.InvalidState,
[DOMException.INVALID_ACCESS_ERR]: UsbError.InvalidAccess,
}
: {};
export type Cmd =
| ({ usbCmd: 'bulkTransferOut' } & BulkTransferOut)
| ({ usbCmd: 'bulkTransferIn' } & BulkTransferIn)
| ({ usbCmd: 'selectConfiguration' } & SelectConfiguration)
| ({ usbCmd: 'claimInterface' } & ClaimInterface)
| ({ usbCmd: 'releaseInterface' } & ReleaseInterface)
| ({ usbCmd: 'resetDevice' } & ResetDevice)
| ({ usbCmd: 'activeConfigDescriptor' } & ActiveConfigDescriptor);
export type NullReply = { Ok: null } | { Err: UsbError };
export type BulkTransferOut = {
device: number;
endpoint: number;
data: Uint8Array;
};
export type BulkTransferOutReply = { Ok: number } | { Err: UsbError };
export type BulkTransferIn = {
device: number;
endpoint: number;
length: number;
};
export type Data = Uint8Array;
export type BulkTransferInReply = { Ok: Data } | { Err: UsbError };
export type SelectConfiguration = { device: number; config: number };
export type ClaimInterface = { device: number; number: number };
export type ReleaseInterface = { device: number; number: number };
export type ResetDevice = { device: number };
export type ActiveConfigDescriptor = { device: number };
export type USBEndpoint = { address: number; packetSize: number };
export type USBAlternateInterface = {
alternateSetting: number;
interfaceClass: number;
interfaceSubclass: number;
interfaceProtocol: number;
endpoints: USBEndpoint[];
};
export type USBConfiguration = {
configurationValue: number;
interfaces: USBAlternateInterface[][];
};
export type ActiveConfigDescriptorReply =
| { Ok: USBConfiguration }
| { Err: UsbError };
const encoder = new Encoder();
let count = 0;
export default class UsbCompat {
devices: Record<number, USBDevice> = {};
arr: SharedArrayBuffer;
constructor(arr: SharedArrayBuffer) {
this.arr = arr;
}
addDevice(dev: USBDevice) {
const i = count++;
this.devices[i] = dev;
return i;
}
private async _processCmd(cmd: Cmd) {
try {
if (cmd.usbCmd === 'bulkTransferOut') {
const res = await this.devices[cmd.device].transferOut(
cmd.endpoint & ~0x80,
cmd.data
);
const reply: BulkTransferOutReply = { Ok: res.bytesWritten };
return reply;
} else if (cmd.usbCmd === 'bulkTransferIn') {
const res = await this.devices[cmd.device].transferIn(
cmd.endpoint & ~0x80,
cmd.length
);
const reply: BulkTransferInReply = {
Ok: new Uint8Array(res.data!.buffer),
};
return reply;
} else if (cmd.usbCmd === 'selectConfiguration') {
await this.devices[cmd.device].selectConfiguration(cmd.config);
} else if (cmd.usbCmd === 'claimInterface') {
await this.devices[cmd.device].claimInterface(cmd.number);
} else if (cmd.usbCmd === 'releaseInterface') {
await this.devices[cmd.device].releaseInterface(cmd.number);
} else if (cmd.usbCmd === 'resetDevice') {
await this.devices[cmd.device].reset();
} else if (cmd.usbCmd === 'activeConfigDescriptor') {
const configuration = this.devices[cmd.device].configuration!;
const reply: ActiveConfigDescriptorReply = {
Ok: {
configurationValue: configuration.configurationValue,
interfaces: configuration.interfaces.map(({ alternates }) => {
return alternates.map((alternate) => ({
alternateSetting: alternate.alternateSetting,
interfaceClass: alternate.interfaceClass,
interfaceSubclass: alternate.interfaceSubclass,
interfaceProtocol: alternate.interfaceProtocol,
endpoints: alternate.endpoints.map((endpoint) => ({
address:
endpoint.endpointNumber |
(endpoint.direction === 'in' ? 0x80 : 0),
packetSize: endpoint.packetSize,
})),
}));
}),
},
};
return reply;
}
const reply: NullReply = { Ok: null };
return reply;
} catch (e) {
console.error(e);
return { Err: exceptionMap[e.name] || UsbError.Unknown };
}
}
async processCmd(cmd: Cmd) {
const msg = await this._processCmd(cmd);
const encoded = encoder.encode(msg);
if (encoded.length > this.arr.length - 4) {
throw new Error('too long');
}
new Uint8Array(this.arr).set(encoded, 4);
const notify = new Int32Array(this.arr);
Atomics.store(notify, 0, encoded.length);
Atomics.notify(notify, 0, Infinity);
}
}