diff --git a/.drone.yml b/.drone.yml index edc1b41..fb7033b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -39,7 +39,7 @@ steps: - test - build when: - event: + event: - tag settings: base_url: https://git.jacknet.io @@ -49,4 +49,4 @@ steps: - bennc-m.zip checksum: - md5 - - sha256 \ No newline at end of file + - sha256 diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 80e963b..bd47279 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,3 @@ { - "recommendations": [ - "orta.vscode-jest" - ] -} \ No newline at end of file + "recommendations": ["orta.vscode-jest"] +} diff --git a/.vscode/launch.json b/.vscode/launch.json index ea2385d..18c903f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,20 +1,16 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "pwa-node", - "request": "launch", - "name": "Launch Program", - "skipFiles": [ - "/**" - ], - "program": "${workspaceFolder}/dist/index.js", - "outFiles": [ - "${workspaceFolder}/**/*.js" - ] - } - ] -} \ No newline at end of file + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-node", + "request": "launch", + "name": "Launch Program", + "skipFiles": ["/**"], + "program": "${workspaceFolder}/dist/index.js", + "outFiles": ["${workspaceFolder}/**/*.js"] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index ff30c44..78664b2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "editor.tabSize": 2 -} \ No newline at end of file + "editor.tabSize": 2 +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 98111d0..0632500 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,18 +1,16 @@ { - "version": "2.0.0", - "tasks": [ - { - "type": "typescript", - "tsconfig": "tsconfig.json", - "option": "watch", - "problemMatcher": [ - "$tsc-watch" - ], - "group": { - "kind": "build", - "isDefault": true - }, - "label": "tsc: watch - tsconfig.json" - } - ] -} \ No newline at end of file + "version": "2.0.0", + "tasks": [ + { + "type": "typescript", + "tsconfig": "tsconfig.json", + "option": "watch", + "problemMatcher": ["$tsc-watch"], + "group": { + "kind": "build", + "isDefault": true + }, + "label": "tsc: watch - tsconfig.json" + } + ] +} diff --git a/BENNC_PROTOCOL_SPEC.md b/BENNC_PROTOCOL_SPEC.md index c9b228b..3c5c062 100644 --- a/BENNC_PROTOCOL_SPEC.md +++ b/BENNC_PROTOCOL_SPEC.md @@ -12,11 +12,11 @@ ## Connection Endpoints -| Protocol | Endpoint | -|----------|----------| -| TCP | `chat.3t.network:10009` | +| Protocol | Endpoint | +| --------------- | --------------------------------- | +| TCP | `chat.3t.network:10009` | | WebSocket (TLS) | `wss://chat.3t.network:443/BENNC` | -| WebSocket | `ws://chat.3t.network:80/BENNC` | +| WebSocket | `ws://chat.3t.network:80/BENNC` | ## Core Requirements @@ -30,6 +30,7 @@ ## Message Structure **Client → Server:** + ``` ┌─────────────┬─────────────┬─────────────────────────┐ │ Message Type│ Length │ Data │ @@ -38,6 +39,7 @@ ``` **Server → Client:** + ``` ┌─────────────┬─────────────┬─────────────┬─────────────────────┐ │ Message Type│ Sender ID │ Length │ Data │ @@ -45,7 +47,7 @@ └─────────────┴─────────────┴─────────────┴─────────────────────┘ ``` -*Sender ID is randomly generated per connection to identify message source.* +_Sender ID is randomly generated per connection to identify message source._ ## Encryption Implementation @@ -67,22 +69,23 @@ ## Message Types Reference -| ID | Name | Subscribable | Encrypted | Compressed | Purpose | -|----|------|--------------|-----------|------------|---------| -| 0x0000 | Subscribe | ❌ | ❌ | ❌ | Subscribe to message type | -| 0x0001 | Basic Message | ✅ | ✅ | ❌ | Chat messages | -| 0x0002 | Request User Data | ✅ | ✅ | ❌ | Request user info | -| 0x0003 | User Data Response | ✅ | ✅ | ❌ | Respond with user info | -| 0x0005 | Keepalive | ❌ | ❌ | ❌ | Prevent connection timeout | -| 0x0006 | Advanced Text | ✅ | ✅ | ✅ | Long/rich text messages | -| 0x0007 | Edit Advanced Text | ✅ | ✅ | ✅ | Edit/delete advanced text | -| 0xFFFF | Unsubscribe | ❌ | ❌ | ❌ | Unsubscribe from message type | +| ID | Name | Subscribable | Encrypted | Compressed | Purpose | +| ------ | ------------------ | ------------ | --------- | ---------- | ----------------------------- | +| 0x0000 | Subscribe | ❌ | ❌ | ❌ | Subscribe to message type | +| 0x0001 | Basic Message | ✅ | ✅ | ❌ | Chat messages | +| 0x0002 | Request User Data | ✅ | ✅ | ❌ | Request user info | +| 0x0003 | User Data Response | ✅ | ✅ | ❌ | Respond with user info | +| 0x0005 | Keepalive | ❌ | ❌ | ❌ | Prevent connection timeout | +| 0x0006 | Advanced Text | ✅ | ✅ | ✅ | Long/rich text messages | +| 0x0007 | Edit Advanced Text | ✅ | ✅ | ✅ | Edit/delete advanced text | +| 0xFFFF | Unsubscribe | ❌ | ❌ | ❌ | Unsubscribe from message type | --- ## Message Type Specifications ### Subscribe (0x0000) + Subscribe to receive messages of specified type. Must resubscribe after disconnect. **Data:** Message type to subscribe to (2 bytes, big-endian) @@ -90,6 +93,7 @@ Subscribe to receive messages of specified type. Must resubscribe after disconne --- ### Basic Message (0x0001) + UTF-8 chat messages. 16-byte nonce + encrypted data ≤ 1000 bytes total. **Data:** Encrypted UTF-8 string @@ -97,9 +101,11 @@ UTF-8 chat messages. 16-byte nonce + encrypted data ≤ 1000 bytes total. --- ### Request User Data (0x0002) + Request user information from all users. Clients should respond with User Data Response (0x0003). **Data Structure:** + - Username length (2 bytes, big-endian) - Username (up to 32 bytes, UTF-8) - Color RGB (3 bytes: R, G, B values) @@ -109,9 +115,11 @@ Request user information from all users. Clients should respond with User Data R --- ### User Data Response (0x0003) + Send user information in response to Request User Data (0x0002). **Data Structure:** Same as Request User Data + - Username length (2 bytes, big-endian) - Username (up to 32 bytes, UTF-8) - Color RGB (3 bytes: R, G, B values) @@ -121,6 +129,7 @@ Send user information in response to Request User Data (0x0002). --- ### Keepalive (0x0005) + Prevent connection timeout when idle. Send every 30 seconds when no other traffic. Not forwarded to other clients and cannot be subscribed to. **Data:** None - empty message @@ -128,6 +137,7 @@ Prevent connection timeout when idle. Send every 30 seconds when no other traffi --- ### Unsubscribe (0xFFFF) + Stop receiving messages of specified type. **Data:** Message type to unsubscribe from (2 bytes, big-endian) @@ -135,9 +145,11 @@ Stop receiving messages of specified type. --- ### Advanced Text (0x0006) + Multi-packet messages for long text with markdown formatting and ZSTD compression. **Implementation Notes:** + - Text is compressed **before** splitting into packets - Header is not compressed - Packets may arrive out of order - use packet numbers to reconstruct @@ -145,12 +157,14 @@ Multi-packet messages for long text with markdown formatting and ZSTD compressio - Warn users before displaying very large messages **Data Structure:** + - Message ID (4 bytes, big-endian) - Packet number (2 bytes, big-endian, 0-indexed) - Final packet number (2 bytes, big-endian, 0-indexed) - Compressed markdown text (remaining bytes, ZSTD) ### Edit Advanced Text (0x0007) + Edit or delete existing Advanced Text messages. Use same Message ID to replace existing message. Empty compressed text deletes the message. Client replaces original when final packet received. **Data Structure:** Same as Advanced Text (0x0006) @@ -160,12 +174,14 @@ Edit or delete existing Advanced Text messages. Use same Message ID to replace e ## Implementation Guidelines ### Validation Requirements + - All length fields must not exceed their specified maximums - Username/Client ID lengths must match actual string lengths - Message data must not exceed 1000 bytes (including nonce) - Packet numbers must be sequential and within final packet range ### Connection Lifecycle + 1. **Connect** to server (TCP/WebSocket) 2. **Subscribe** to required message types 3. **Send keepalive** every 30 seconds when idle @@ -173,6 +189,7 @@ Edit or delete existing Advanced Text messages. Use same Message ID to replace e 5. **Handle** out-of-order packets for Advanced Text ### Common Issues + - **Encryption fails**: Ensure message type in additional data matches packet header - **Messages dropped**: Check total size ≤ 1000 bytes including nonce - **Connection timeout**: Verify keepalive frequency @@ -188,4 +205,4 @@ Edit or delete existing Advanced Text messages. Use same Message ID to replace e - **Multi-packet support** for long messages - **TCP and WebSocket** transport options -This is a documentation-only repository. The protocol specification is final and changes are not permitted. \ No newline at end of file +This is a documentation-only repository. The protocol specification is final and changes are not permitted. diff --git a/CLAUDE.md b/CLAUDE.md index 476db5a..1f58578 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,7 +30,7 @@ The codebase implements the BENNC protocol specification with the following stru Each BENNC message type has its own module in `src/messages/`: - `subscribe.ts` (0x0000) - Subscribe to message types -- `basic.ts` (0x0001) - Basic encrypted chat messages +- `basic.ts` (0x0001) - Basic encrypted chat messages - `userDataRequest.ts` (0x0002) - Request user information - `userDataResponse.ts` (0x0003) - Respond with user information - `keepalive.ts` (0x0005) - Connection keepalive messages @@ -47,7 +47,7 @@ Each BENNC message type has its own module in `src/messages/`: ### Utilities -- `src/utilities/number.ts` - Big-endian number conversion functions (`numberToUint16BE`, `numberToUint32BE`) +- `src/utilities/number.ts` - Big-endian number conversion functions (`numberToUint16BE`, `numberToUint32BE`) - `src/utilities/smart-buffer.ts` - Buffer manipulation utility for packet construction/parsing ### Key Architecture Patterns @@ -57,4 +57,4 @@ Each BENNC message type has its own module in `src/messages/`: - **Encryption Integration**: Encrypted message types automatically handle nonce generation and Romulus-M encryption - **Protocol Compliance**: Strict adherence to BENNC specification including reserved sender IDs (0xFFFFFF00-0xFFFFFFFF) -The codebase serves as a complete client-side implementation for connecting to BENNC chat servers via TCP or WebSocket protocols. \ No newline at end of file +The codebase serves as a complete client-side implementation for connecting to BENNC chat servers via TCP or WebSocket protocols. diff --git a/README.md b/README.md index 320cb85..7101b0a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # BENNC-JS + [![Build Status](https://drone.jacknet.io/api/badges/TerribleCodeClub/bennc-js/status.svg)](https://drone.jacknet.io/TerribleCodeClub/bennc-js) An implementation of the [BENNC](https://wiki.jacknet.io/books/simontech/chapter/bennc) client specification. @@ -8,15 +9,18 @@ An implementation of the [BENNC](https://wiki.jacknet.io/books/simontech/chapter To build the BENNC-JS library, first clone this repository. Run the following commands from the root of the repository: + ```bash $ npm install $ npm run build ``` + The build output will be saved to the `dist` directory. ## Development instructions Requirements: + - The latest LTS builds of Node and npm. Follow the instructions below to lint, test and build BENNC-JS. @@ -34,6 +38,7 @@ $ npm run lint $ npm install $ npm run test ``` + #### Build ```bash @@ -43,7 +48,7 @@ $ npm run build ### Visual Studio Code -This repository contains the necessary configuration files to debug, test and build BENNC-JS using only Visual Studio Code. +This repository contains the necessary configuration files to debug, test and build BENNC-JS using only Visual Studio Code. Run the build task (`Ctrl+Shift+B` or `⇧⌘B`) to automatically compile the Typescript source files in the background. diff --git a/src/index.ts b/src/index.ts index df33651..9b280a5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,9 @@ -export { numberToUint16BE, numberToUint32BE } from './utilities/number' -export { unpackIncomingPacket } from './messages/packet' -export { packers, unpackers } from './mapping' -export { MessageTypes } from './common' -export { IncomingPacket, OutgoingPacket } from './messages/packet' -export { SubscribeMessage } from './messages/subscribe' -export { BasicMessage } from './messages/basic' -export { UserDataRequestMessage } from './messages/userDataRequest' -export { UserDataResponseMessage } from './messages/userDataResponse' +export { numberToUint16BE, numberToUint32BE } from "./utilities/number"; +export { unpackIncomingPacket } from "./messages/packet"; +export { packers, unpackers } from "./mapping"; +export { MessageTypes } from "./common"; +export { IncomingPacket, OutgoingPacket } from "./messages/packet"; +export { SubscribeMessage } from "./messages/subscribe"; +export { BasicMessage } from "./messages/basic"; +export { UserDataRequestMessage } from "./messages/userDataRequest"; +export { UserDataResponseMessage } from "./messages/userDataResponse"; diff --git a/src/messages/basic.ts b/src/messages/basic.ts index a8f38ee..1096381 100644 --- a/src/messages/basic.ts +++ b/src/messages/basic.ts @@ -1,12 +1,12 @@ -import { encrypt } from 'romulus-js' -import { DEFAULT_KEY, MessageTypes } from '../common' -import { numberToUint16BE } from '../utilities/number' -import { packOutgoingPacket } from './packet' +import { encrypt } from "romulus-js"; +import { DEFAULT_KEY, MessageTypes } from "../common"; +import { numberToUint16BE } from "../utilities/number"; +import { packOutgoingPacket } from "./packet"; -const MESSAGE_TYPE = numberToUint16BE(MessageTypes.Basic) +const MESSAGE_TYPE = numberToUint16BE(MessageTypes.Basic); export interface BasicMessage { - data: Uint8Array + data: Uint8Array; } /** @@ -15,12 +15,15 @@ export interface BasicMessage { * @param key The key to encrypt the data with. * @returns An encrypted outgoing basic message (0x0001) packet. */ -export function packBasicMessage (message: Uint8Array, key: Uint8Array = DEFAULT_KEY): Uint8Array { - const data = encrypt(message, MESSAGE_TYPE, key) +export function packBasicMessage( + message: Uint8Array, + key: Uint8Array = DEFAULT_KEY, +): Uint8Array { + const data = encrypt(message, MESSAGE_TYPE, key); return packOutgoingPacket({ messageType: MESSAGE_TYPE, - data: data - }) + data: data, + }); } /** @@ -28,6 +31,6 @@ export function packBasicMessage (message: Uint8Array, key: Uint8Array = DEFAULT * @param data The data section of an incoming basic message (0x0001) message. * @returns An encrypted unpacked basic message (0x0001) message. */ -export function unpackBasicMessage (data: Uint8Array): Uint8Array { - return data +export function unpackBasicMessage(data: Uint8Array): Uint8Array { + return data; } diff --git a/src/messages/keepalive.ts b/src/messages/keepalive.ts index f30b8c1..3187948 100644 --- a/src/messages/keepalive.ts +++ b/src/messages/keepalive.ts @@ -1,16 +1,16 @@ -import { MessageTypes } from '../common' -import { numberToUint16BE } from '../utilities/number' -import { packOutgoingPacket } from './packet' +import { MessageTypes } from "../common"; +import { numberToUint16BE } from "../utilities/number"; +import { packOutgoingPacket } from "./packet"; -const MESSAGE_TYPE = numberToUint16BE(MessageTypes.Keepalive) +const MESSAGE_TYPE = numberToUint16BE(MessageTypes.Keepalive); /** * Create an outgoing keepalive (0x0005) packet. * @returns An outgoing keepalive (0x0005) packet. */ -export function packKeepaliveMessage (): Uint8Array { +export function packKeepaliveMessage(): Uint8Array { return packOutgoingPacket({ messageType: MESSAGE_TYPE, - data: new Uint8Array(0) - }) + data: new Uint8Array(0), + }); } diff --git a/src/messages/packet.ts b/src/messages/packet.ts index 2a16043..18fb78a 100644 --- a/src/messages/packet.ts +++ b/src/messages/packet.ts @@ -1,16 +1,16 @@ -import { MAX_DATA_LENGTH } from '../common' -import { numberToUint16BE } from '../utilities/number' -import { SmartBuffer } from '../utilities/smart-buffer' +import { MAX_DATA_LENGTH } from "../common"; +import { numberToUint16BE } from "../utilities/number"; +import { SmartBuffer } from "../utilities/smart-buffer"; export interface IncomingPacket { - messageType: number - senderId: number - data: Uint8Array + messageType: number; + senderId: number; + data: Uint8Array; } export interface OutgoingPacket { - messageType: Uint8Array - data: Uint8Array + messageType: Uint8Array; + data: Uint8Array; } /** @@ -18,19 +18,21 @@ export interface OutgoingPacket { * @param outgoingPacket The message type and data to send. * @returns A buffer containing the ready-to-send packet. */ -export function packOutgoingPacket (outgoingPacket: OutgoingPacket): Uint8Array { +export function packOutgoingPacket(outgoingPacket: OutgoingPacket): Uint8Array { // Verify that the data does not exceed the maximum data length. if (outgoingPacket.data.length > MAX_DATA_LENGTH) { - throw RangeError(`Specified data of length ${outgoingPacket.data.length} exceeds max data length ${MAX_DATA_LENGTH}.`) + throw RangeError( + `Specified data of length ${outgoingPacket.data.length} exceeds max data length ${MAX_DATA_LENGTH}.`, + ); } // Prepare the outgoing packet. - const buffer = new SmartBuffer() - buffer.writeBytes(outgoingPacket.messageType) - buffer.writeBytes(numberToUint16BE(outgoingPacket.data.length)) - buffer.writeBytes(outgoingPacket.data) + const buffer = new SmartBuffer(); + buffer.writeBytes(outgoingPacket.messageType); + buffer.writeBytes(numberToUint16BE(outgoingPacket.data.length)); + buffer.writeBytes(outgoingPacket.data); - return buffer.data + return buffer.data; } /** @@ -38,17 +40,19 @@ export function packOutgoingPacket (outgoingPacket: OutgoingPacket): Uint8Array * @param incomingPacket The incoming buffer from a WebSocket to unpack. * @returns The unpacked data. */ -export function unpackIncomingPacket (incomingPacket: ArrayBuffer): IncomingPacket { - const buffer = SmartBuffer.from(incomingPacket) +export function unpackIncomingPacket( + incomingPacket: ArrayBuffer, +): IncomingPacket { + const buffer = SmartBuffer.from(incomingPacket); - const messageType = buffer.readUInt16() - const senderId = buffer.readUInt32() - const length = buffer.readUInt16() - const data = buffer.readBytes(length) + const messageType = buffer.readUInt16(); + const senderId = buffer.readUInt32(); + const length = buffer.readUInt16(); + const data = buffer.readBytes(length); return { messageType: messageType, senderId: senderId, - data: data - } + data: data, + }; } diff --git a/src/messages/subscribe.ts b/src/messages/subscribe.ts index 37e64d2..ae3b956 100644 --- a/src/messages/subscribe.ts +++ b/src/messages/subscribe.ts @@ -1,11 +1,11 @@ -import { MessageTypes } from '../common' -import { numberToUint16BE } from '../utilities/number' -import { packOutgoingPacket } from './packet' +import { MessageTypes } from "../common"; +import { numberToUint16BE } from "../utilities/number"; +import { packOutgoingPacket } from "./packet"; -const MESSAGE_TYPE = numberToUint16BE(MessageTypes.Subscribe) +const MESSAGE_TYPE = numberToUint16BE(MessageTypes.Subscribe); export interface SubscribeMessage { - messageType: number + messageType: number; } /** @@ -13,10 +13,10 @@ export interface SubscribeMessage { * @param properties The properties for the message. * @returns An outgoing subscribe (0x0000) packet. */ -export function packSubscribeMessage (properties: SubscribeMessage): Uint8Array { - const data = numberToUint16BE(properties.messageType) +export function packSubscribeMessage(properties: SubscribeMessage): Uint8Array { + const data = numberToUint16BE(properties.messageType); return packOutgoingPacket({ messageType: MESSAGE_TYPE, - data: data - }) + data: data, + }); } diff --git a/src/messages/unsubscribe.ts b/src/messages/unsubscribe.ts index 418da76..b70ac43 100644 --- a/src/messages/unsubscribe.ts +++ b/src/messages/unsubscribe.ts @@ -1,11 +1,11 @@ -import { MessageTypes } from '../common' -import { numberToUint16BE } from '../utilities/number' -import { packOutgoingPacket } from './packet' +import { MessageTypes } from "../common"; +import { numberToUint16BE } from "../utilities/number"; +import { packOutgoingPacket } from "./packet"; -const MESSAGE_TYPE = numberToUint16BE(MessageTypes.Unsubscribe) +const MESSAGE_TYPE = numberToUint16BE(MessageTypes.Unsubscribe); export interface UnsubscribeMessage { - messageType: number + messageType: number; } /** @@ -13,10 +13,12 @@ export interface UnsubscribeMessage { * @param properties The properties for the message. * @returns An outgoing unsubscribe (0xFFFF) packet. */ -export function packUnsubscribeMessage (properties: UnsubscribeMessage): Uint8Array { - const data = numberToUint16BE(properties.messageType) +export function packUnsubscribeMessage( + properties: UnsubscribeMessage, +): Uint8Array { + const data = numberToUint16BE(properties.messageType); return packOutgoingPacket({ messageType: MESSAGE_TYPE, - data: data - }) + data: data, + }); } diff --git a/src/messages/userDataRequest.ts b/src/messages/userDataRequest.ts index b7d25d4..45cfa2b 100644 --- a/src/messages/userDataRequest.ts +++ b/src/messages/userDataRequest.ts @@ -1,16 +1,16 @@ -import Color from 'color' -import { encrypt } from 'romulus-js' -import { DEFAULT_KEY, MessageTypes } from '../common' -import { numberToUint16BE } from '../utilities/number' -import { SmartBuffer } from '../utilities/smart-buffer' -import { packOutgoingPacket } from './packet' +import Color from "color"; +import { encrypt } from "romulus-js"; +import { DEFAULT_KEY, MessageTypes } from "../common"; +import { numberToUint16BE } from "../utilities/number"; +import { SmartBuffer } from "../utilities/smart-buffer"; +import { packOutgoingPacket } from "./packet"; -const MESSAGE_TYPE = numberToUint16BE(MessageTypes.UserDataRequest) +const MESSAGE_TYPE = numberToUint16BE(MessageTypes.UserDataRequest); export interface UserDataRequestMessage { - username: string - colour: Color - clientId: string + username: string; + colour: Color; + clientId: string; } /** @@ -19,33 +19,36 @@ export interface UserDataRequestMessage { * @param key The key to encrypt the data with. * @returns An outgoing user data request (0x0002) packet. */ -export function packUserDataRequestMessage (properties: UserDataRequestMessage, key: Uint8Array = DEFAULT_KEY): Uint8Array { - const encoder = new TextEncoder() +export function packUserDataRequestMessage( + properties: UserDataRequestMessage, + key: Uint8Array = DEFAULT_KEY, +): Uint8Array { + const encoder = new TextEncoder(); // Prepare data in correct format. - const username = encoder.encode(properties.username) - const usernameLength = numberToUint16BE(username.length) - const colour = new Uint8Array(properties.colour.array()) - const clientId = encoder.encode(properties.clientId) - const clientIdLength = numberToUint16BE(clientId.length) + const username = encoder.encode(properties.username); + const usernameLength = numberToUint16BE(username.length); + const colour = new Uint8Array(properties.colour.array()); + const clientId = encoder.encode(properties.clientId); + const clientIdLength = numberToUint16BE(clientId.length); // Pack data. - const packedData = new SmartBuffer() - packedData.writeBytes(usernameLength) - packedData.writeBytes(username) - packedData.pad(32 - username.length) - packedData.writeBytes(colour) - packedData.writeBytes(clientIdLength) - packedData.writeBytes(clientId) - packedData.pad(32 - clientId.length) + const packedData = new SmartBuffer(); + packedData.writeBytes(usernameLength); + packedData.writeBytes(username); + packedData.pad(32 - username.length); + packedData.writeBytes(colour); + packedData.writeBytes(clientIdLength); + packedData.writeBytes(clientId); + packedData.pad(32 - clientId.length); // Encrypt the data. - const data = encrypt(packedData.data, MESSAGE_TYPE, key) + const data = encrypt(packedData.data, MESSAGE_TYPE, key); return packOutgoingPacket({ messageType: MESSAGE_TYPE, - data: data - }) + data: data, + }); } /** @@ -53,23 +56,25 @@ export function packUserDataRequestMessage (properties: UserDataRequestMessage, * @param data The decrypted data section of an incoming user data request (0x0002) message. * @returns An unpacked user data request (0x0002) message. */ -export function unpackUserDataRequestMessage (data: Uint8Array): UserDataRequestMessage { +export function unpackUserDataRequestMessage( + data: Uint8Array, +): UserDataRequestMessage { // Unpack and read data in correct format. - const packedData = SmartBuffer.from(data) + const packedData = SmartBuffer.from(data); - const usernameLength = packedData.readUInt16() - const username = packedData.readBytes(usernameLength) - packedData.cursor = 34 - const colour = packedData.readBytes(3) - const clientIdLength = packedData.readUInt16() - const clientId = packedData.readBytes(clientIdLength) + const usernameLength = packedData.readUInt16(); + const username = packedData.readBytes(usernameLength); + packedData.cursor = 34; + const colour = packedData.readBytes(3); + const clientIdLength = packedData.readUInt16(); + const clientId = packedData.readBytes(clientIdLength); - const decoder = new TextDecoder() + const decoder = new TextDecoder(); // Return data in correct format. return { username: decoder.decode(username), colour: Color.rgb(colour), - clientId: decoder.decode(clientId) - } + clientId: decoder.decode(clientId), + }; } diff --git a/src/messages/userDataResponse.ts b/src/messages/userDataResponse.ts index efea90f..87b937d 100644 --- a/src/messages/userDataResponse.ts +++ b/src/messages/userDataResponse.ts @@ -1,16 +1,16 @@ -import Color from 'color' -import { encrypt } from 'romulus-js' -import { DEFAULT_KEY, MessageTypes } from '../common' -import { numberToUint16BE } from '../utilities/number' -import { SmartBuffer } from '../utilities/smart-buffer' -import { packOutgoingPacket } from './packet' +import Color from "color"; +import { encrypt } from "romulus-js"; +import { DEFAULT_KEY, MessageTypes } from "../common"; +import { numberToUint16BE } from "../utilities/number"; +import { SmartBuffer } from "../utilities/smart-buffer"; +import { packOutgoingPacket } from "./packet"; -const MESSAGE_TYPE = numberToUint16BE(MessageTypes.UserDataResponse) +const MESSAGE_TYPE = numberToUint16BE(MessageTypes.UserDataResponse); export interface UserDataResponseMessage { - username: string - colour: Color - clientId: string + username: string; + colour: Color; + clientId: string; } /** @@ -19,32 +19,35 @@ export interface UserDataResponseMessage { * @param key The key to encrypt the data with. * @returns The data section of an outgoing user data response (0x0003) message. */ -export function packUserDataResponseMessage (properties: UserDataResponseMessage, key: Uint8Array = DEFAULT_KEY): Uint8Array { - const encoder = new TextEncoder() +export function packUserDataResponseMessage( + properties: UserDataResponseMessage, + key: Uint8Array = DEFAULT_KEY, +): Uint8Array { + const encoder = new TextEncoder(); // Prepare data in correct format. - const username = encoder.encode(properties.username) - const usernameLength = numberToUint16BE(username.length) - const colour = new Uint8Array(properties.colour.array()) - const clientId = encoder.encode(properties.clientId) - const clientIdLength = numberToUint16BE(clientId.length) + const username = encoder.encode(properties.username); + const usernameLength = numberToUint16BE(username.length); + const colour = new Uint8Array(properties.colour.array()); + const clientId = encoder.encode(properties.clientId); + const clientIdLength = numberToUint16BE(clientId.length); // Pack data. - const packedData = new SmartBuffer() - packedData.writeBytes(usernameLength) - packedData.writeBytes(username) - packedData.pad(32 - username.length) - packedData.writeBytes(colour) - packedData.writeBytes(clientIdLength) - packedData.writeBytes(clientId) - packedData.pad(32 - clientId.length) + const packedData = new SmartBuffer(); + packedData.writeBytes(usernameLength); + packedData.writeBytes(username); + packedData.pad(32 - username.length); + packedData.writeBytes(colour); + packedData.writeBytes(clientIdLength); + packedData.writeBytes(clientId); + packedData.pad(32 - clientId.length); // Return encrypted data. - const data = encrypt(packedData.data, MESSAGE_TYPE, key) + const data = encrypt(packedData.data, MESSAGE_TYPE, key); return packOutgoingPacket({ messageType: MESSAGE_TYPE, - data: data - }) + data: data, + }); } /** @@ -52,23 +55,25 @@ export function packUserDataResponseMessage (properties: UserDataResponseMessage * @param data The decrypted data section of an incoming user data response (0x0003) message. * @returns A unpacked user data response (0x0003) message. */ -export function unpackUserDataResponseMessage (data: Uint8Array): UserDataResponseMessage { +export function unpackUserDataResponseMessage( + data: Uint8Array, +): UserDataResponseMessage { // Unpack and read data in correct format. - const packedData = SmartBuffer.from(data) + const packedData = SmartBuffer.from(data); - const usernameLength = packedData.readUInt16() - const username = packedData.readBytes(usernameLength) - packedData.cursor = 34 - const colour = packedData.readBytes(3) - const clientIdLength = packedData.readUInt16() - const clientId = packedData.readBytes(clientIdLength) + const usernameLength = packedData.readUInt16(); + const username = packedData.readBytes(usernameLength); + packedData.cursor = 34; + const colour = packedData.readBytes(3); + const clientIdLength = packedData.readUInt16(); + const clientId = packedData.readBytes(clientIdLength); - const decoder = new TextDecoder() + const decoder = new TextDecoder(); // Return data in correct format. return { username: decoder.decode(username), colour: Color.rgb(colour), - clientId: decoder.decode(clientId) - } + clientId: decoder.decode(clientId), + }; } diff --git a/src/utilities/number.ts b/src/utilities/number.ts index 978c806..265004c 100644 --- a/src/utilities/number.ts +++ b/src/utilities/number.ts @@ -3,11 +3,11 @@ * @param number The number to pack. * @returns The packed buffer. */ -export function numberToUint16BE (number: number): Uint8Array { - const ret = new Uint8Array(2) - ret[0] = (number & 0xFF00) >> 8 - ret[1] = (number & 0x00FF) >> 0 - return ret +export function numberToUint16BE(number: number): Uint8Array { + const ret = new Uint8Array(2); + ret[0] = (number & 0xff00) >> 8; + ret[1] = (number & 0x00ff) >> 0; + return ret; } /** @@ -15,11 +15,11 @@ export function numberToUint16BE (number: number): Uint8Array { * @param number The number to pack. * @returns The packed buffer. */ -export function numberToUint32BE (number: number): Uint8Array { - const ret = new Uint8Array(4) - ret[0] = (number & 0xFF000000) >> 24 - ret[1] = (number & 0x00FF0000) >> 16 - ret[2] = (number & 0x0000FF00) >> 8 - ret[3] = (number & 0x000000FF) >> 0 - return ret +export function numberToUint32BE(number: number): Uint8Array { + const ret = new Uint8Array(4); + ret[0] = (number & 0xff000000) >> 24; + ret[1] = (number & 0x00ff0000) >> 16; + ret[2] = (number & 0x0000ff00) >> 8; + ret[3] = (number & 0x000000ff) >> 0; + return ret; } diff --git a/src/utilities/smart-buffer.ts b/src/utilities/smart-buffer.ts index 3c80031..f6ce685 100644 --- a/src/utilities/smart-buffer.ts +++ b/src/utilities/smart-buffer.ts @@ -1,62 +1,64 @@ -import { numberToUint16BE, numberToUint32BE } from './number' +import { numberToUint16BE, numberToUint32BE } from "./number"; export class SmartBuffer { - private _data: number[] - private _cursor: number + private _data: number[]; + private _cursor: number; /** - * Wrap a buffer to track position and provide useful read / write functionality. - * @param data Buffer to wrap (optional). - */ - constructor (length: number = 0) { - this._data = new Array(length) - this._cursor = 0 + * Wrap a buffer to track position and provide useful read / write functionality. + * @param data Buffer to wrap (optional). + */ + constructor(length: number = 0) { + this._data = new Array(length); + this._cursor = 0; } /** - * Return a regular buffer. - */ - get data (): Uint8Array { - return new Uint8Array(this._data) + * Return a regular buffer. + */ + get data(): Uint8Array { + return new Uint8Array(this._data); } /** - * Update the smart buffer to wrap new data. - */ - set data (data: Uint8Array) { - this._data = Array.from(data) - this.cursor = 0 + * Update the smart buffer to wrap new data. + */ + set data(data: Uint8Array) { + this._data = Array.from(data); + this.cursor = 0; } /** - * Get the length of the smart buffer data. - */ - get length (): number { - return this._data.length + * Get the length of the smart buffer data. + */ + get length(): number { + return this._data.length; } /** - * Set the length of the smart buffer data. - */ - set length (length: number) { - this._data.length = length + * Set the length of the smart buffer data. + */ + set length(length: number) { + this._data.length = length; } /** - * Get the current cursor position of the smart buffer. - */ - get cursor (): number { - return this._cursor + * Get the current cursor position of the smart buffer. + */ + get cursor(): number { + return this._cursor; } /** - * Set the cursor position of the smart buffer. - */ - set cursor (position: number) { + * Set the cursor position of the smart buffer. + */ + set cursor(position: number) { if (position < 0) { - throw RangeError(`Cannot seek to ${this.cursor} of ${this.length} bytes.`) + throw RangeError( + `Cannot seek to ${this.cursor} of ${this.length} bytes.`, + ); } - this._cursor = position + this._cursor = position; } /** @@ -64,33 +66,33 @@ export class SmartBuffer { * @param data The object to convert to a new SmartBuffer. * @returns A new SmartBuffer. */ - static from (data: number[] | ArrayBuffer): SmartBuffer { - const smartBuffer = new SmartBuffer() + static from(data: number[] | ArrayBuffer): SmartBuffer { + const smartBuffer = new SmartBuffer(); if (data instanceof ArrayBuffer) { - smartBuffer._data = Array.from(new Uint8Array(data)) + smartBuffer._data = Array.from(new Uint8Array(data)); } else { - smartBuffer._data = Array.from(data) + smartBuffer._data = Array.from(data); } - return smartBuffer + return smartBuffer; } /** - * Pads bytes to the end of the smart buffer. - * @param length The number of bytes to pad. - */ - pad (length: number): void { - this._data.push(...Array(length).fill(0)) - this.cursor += length + * Pads bytes to the end of the smart buffer. + * @param length The number of bytes to pad. + */ + pad(length: number): void { + this._data.push(...Array(length).fill(0)); + this.cursor += length; } /** - * Return the data from the specified range. - * @param start The start position. - * @param end The end position. - * @returns A new buffer containing data from the specified range. - */ - slice (start: number, end: number): Uint8Array { - return new Uint8Array(this._data.slice(start, end)) + * Return the data from the specified range. + * @param start The start position. + * @param end The end position. + * @returns A new buffer containing data from the specified range. + */ + slice(start: number, end: number): Uint8Array { + return new Uint8Array(this._data.slice(start, end)); } /** @@ -99,66 +101,66 @@ export class SmartBuffer { * @param deleteCount The number of items to remove before inserting new data. * @param items The items to insert at the specified position. */ - splice (start: number, deleteCount: number, ...items: number[]): void { + splice(start: number, deleteCount: number, ...items: number[]): void { if (this.length < start) { - this._data.push(...Array(start)) + this._data.push(...Array(start)); } - this._data.splice(this.cursor, deleteCount, ...items) + this._data.splice(this.cursor, deleteCount, ...items); } /** - * Read a UInt16 number from the smart buffer at the current cursor position, and increment the cursor. - * @returns A number represented by the bytes at the current cursor position. - */ - readUInt16 (): number { - const num = this.slice(this.cursor, this.cursor + 2) - this.cursor += 2 - return (num[0] << 8 | num[1]) >>> 0 + * Read a UInt16 number from the smart buffer at the current cursor position, and increment the cursor. + * @returns A number represented by the bytes at the current cursor position. + */ + readUInt16(): number { + const num = this.slice(this.cursor, this.cursor + 2); + this.cursor += 2; + return ((num[0] << 8) | num[1]) >>> 0; } /** - * Read a UInt32 number from the smart buffer at the current cursor position, and increment the cursor. - * @returns A number represented by the bytes at the current cursor position. - */ - readUInt32 (): number { - const num = this.slice(this.cursor, this.cursor + 4) - this.cursor += 4 - return (num[0] << 24 | num[1] << 16 | num[2] << 8 | num[3]) >>> 0 + * Read a UInt32 number from the smart buffer at the current cursor position, and increment the cursor. + * @returns A number represented by the bytes at the current cursor position. + */ + readUInt32(): number { + const num = this.slice(this.cursor, this.cursor + 4); + this.cursor += 4; + return ((num[0] << 24) | (num[1] << 16) | (num[2] << 8) | num[3]) >>> 0; } /** - * Read the specified number of bytes from the smart buffer at the current cursor position, and increment the cursor. - * @returns A buffer containing the bytes read from the current cursor position. - */ - readBytes (length: number): Uint8Array { - this.cursor += length - return this.slice(this.cursor - length, this.cursor) + * Read the specified number of bytes from the smart buffer at the current cursor position, and increment the cursor. + * @returns A buffer containing the bytes read from the current cursor position. + */ + readBytes(length: number): Uint8Array { + this.cursor += length; + return this.slice(this.cursor - length, this.cursor); } /** - * Write a UInt16 number to the smart buffer at the current cursor position, and increment the cursor. - * @param number The number to write in UInt16 format at the current cursor position. - */ - writeUInt16 (number: number): void { - this.splice(this.cursor, 2, ...numberToUint16BE(number)) - this.cursor += 2 + * Write a UInt16 number to the smart buffer at the current cursor position, and increment the cursor. + * @param number The number to write in UInt16 format at the current cursor position. + */ + writeUInt16(number: number): void { + this.splice(this.cursor, 2, ...numberToUint16BE(number)); + this.cursor += 2; } /** - * Write a UInt32 number to the smart buffer at the current cursor position, and increment the cursor. - * @param number The number to write in UInt32 format at the current cursor position. - */ - writeUInt32 (number: number): void { - this.splice(this.cursor, 4, ...numberToUint32BE(number)) - this.cursor += 4 + * Write a UInt32 number to the smart buffer at the current cursor position, and increment the cursor. + * @param number The number to write in UInt32 format at the current cursor position. + */ + writeUInt32(number: number): void { + this.splice(this.cursor, 4, ...numberToUint32BE(number)); + this.cursor += 4; } /** - * Write the specified bytes to the smart buffer at the current cursor position, and increment the cursor. - * @param buffer The bytes to write at the current cursor position. - */ - writeBytes (buffer: number[] | Uint8Array): void { - this.splice(this.cursor, buffer.length, ...buffer) - this.cursor += buffer.length + * Write the specified bytes to the smart buffer at the current cursor position, and increment the cursor. + * @param buffer The bytes to write at the current cursor position. + */ + writeBytes(buffer: number[] | Uint8Array): void { + this.splice(this.cursor, buffer.length, ...buffer); + this.cursor += buffer.length; } } diff --git a/tests/messages/basic.test.ts b/tests/messages/basic.test.ts index 7d4c593..05a7d54 100644 --- a/tests/messages/basic.test.ts +++ b/tests/messages/basic.test.ts @@ -1,36 +1,37 @@ -import { MessageTypes } from '../../src/common' -import { packers, unpackers } from '../../src/mapping' +import { MessageTypes } from "../../src/common"; +import { packers, unpackers } from "../../src/mapping"; -const KEY = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) +const KEY = new Uint8Array([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, +]); -test('Create a basic message (0x0001) packet.', () => { +test("Create a basic message (0x0001) packet.", () => { // Given - const encoder = new TextEncoder() - const message = encoder.encode('Hello, World!') + const encoder = new TextEncoder(); + const message = encoder.encode("Hello, World!"); // When - const packedPacket = packers[MessageTypes.Basic]( - message, - KEY - ) + const packedPacket = packers[MessageTypes.Basic](message, KEY); // Then // We can't check the contents of the data as it's encrypted with a random nonce. // Check the message type and length. - expect(packedPacket.slice(0, 4)).toMatchObject(new Uint8Array([0x00, 0x01, 0x00, 0x2D])) + expect(packedPacket.slice(0, 4)).toMatchObject( + new Uint8Array([0x00, 0x01, 0x00, 0x2d]), + ); // Check the total length is as expected. - expect(packedPacket.length).toBe(49) -}) + expect(packedPacket.length).toBe(49); +}); -test('Parse a basic message (0x0001).', () => { +test("Parse a basic message (0x0001).", () => { // Given - const data = new Uint8Array([1, 2, 3, 4]) + const data = new Uint8Array([1, 2, 3, 4]); // When - const unpackedPacket = unpackers[MessageTypes.Basic](data) + const unpackedPacket = unpackers[MessageTypes.Basic](data); // Then - expect(unpackedPacket) - expect(unpackedPacket).toMatchObject(data) -}) + expect(unpackedPacket); + expect(unpackedPacket).toMatchObject(data); +}); diff --git a/tests/messages/keepalive.test.ts b/tests/messages/keepalive.test.ts index 6f1f0c0..1f5cd78 100644 --- a/tests/messages/keepalive.test.ts +++ b/tests/messages/keepalive.test.ts @@ -1,11 +1,11 @@ -import { MessageTypes } from '../../src/common' -import { packers } from '../../src/mapping' +import { MessageTypes } from "../../src/common"; +import { packers } from "../../src/mapping"; -test('Create a keepalive (0x0005) packet.', () => { +test("Create a keepalive (0x0005) packet.", () => { // When - const packedPacket = packers[MessageTypes.Keepalive]() + const packedPacket = packers[MessageTypes.Keepalive](); // Then - const expectedResult = new Uint8Array([0x00, 0x05, 0x00, 0x00]) - expect(packedPacket).toMatchObject(expectedResult) -}) + const expectedResult = new Uint8Array([0x00, 0x05, 0x00, 0x00]); + expect(packedPacket).toMatchObject(expectedResult); +}); diff --git a/tests/messages/packet.test.ts b/tests/messages/packet.test.ts index fab93e5..05062a2 100644 --- a/tests/messages/packet.test.ts +++ b/tests/messages/packet.test.ts @@ -1,15 +1,18 @@ -import { packOutgoingPacket, unpackIncomingPacket } from '../../src/messages/packet' +import { + packOutgoingPacket, + unpackIncomingPacket, +} from "../../src/messages/packet"; -test('Pack an outgoing packet.', () => { +test("Pack an outgoing packet.", () => { // Given - const messageType = new Uint8Array([0x12, 0x34]) - const data = new Uint8Array([0x12, 0x34, 0x56, 0x78]) + const messageType = new Uint8Array([0x12, 0x34]); + const data = new Uint8Array([0x12, 0x34, 0x56, 0x78]); // When const packedPacket = packOutgoingPacket({ messageType: messageType, - data: data - }) + data: data, + }); // Then const expectedResult = new Uint8Array([ @@ -18,12 +21,12 @@ test('Pack an outgoing packet.', () => { // Data length 0x00, 0x04, // Data - 0x12, 0x34, 0x56, 0x78 - ]) - expect(packedPacket).toMatchObject(expectedResult) -}) + 0x12, 0x34, 0x56, 0x78, + ]); + expect(packedPacket).toMatchObject(expectedResult); +}); -test('Unpack an incoming packet.', () => { +test("Unpack an incoming packet.", () => { // Given const incomingPacket = new Uint8Array([ // Message type @@ -33,14 +36,16 @@ test('Unpack an incoming packet.', () => { // Data length 0x00, 0x04, // Data - 0x12, 0x34, 0x56, 0x78 - ]) + 0x12, 0x34, 0x56, 0x78, + ]); // When - const unpackedResult = unpackIncomingPacket(incomingPacket) + const unpackedResult = unpackIncomingPacket(incomingPacket); // Then - expect(unpackedResult.messageType).toBe(0x1234) - expect(unpackedResult.senderId).toBe(0xaabbccdd) - expect(unpackedResult.data).toMatchObject(new Uint8Array([0x12, 0x34, 0x56, 0x78])) -}) + expect(unpackedResult.messageType).toBe(0x1234); + expect(unpackedResult.senderId).toBe(0xaabbccdd); + expect(unpackedResult.data).toMatchObject( + new Uint8Array([0x12, 0x34, 0x56, 0x78]), + ); +}); diff --git a/tests/messages/subscribe.test.ts b/tests/messages/subscribe.test.ts index 49ca087..06c8790 100644 --- a/tests/messages/subscribe.test.ts +++ b/tests/messages/subscribe.test.ts @@ -1,14 +1,16 @@ -import { MessageTypes } from '../../src/common' -import { packers } from '../../src/mapping' +import { MessageTypes } from "../../src/common"; +import { packers } from "../../src/mapping"; -test('Create a subscribe (0x0000) packet.', () => { +test("Create a subscribe (0x0000) packet.", () => { // Given - const messageType = 0xabcd + const messageType = 0xabcd; // When - const packedPacket = packers[MessageTypes.Subscribe]({ messageType: messageType }) + const packedPacket = packers[MessageTypes.Subscribe]({ + messageType: messageType, + }); // Then - const expectedResult = new Uint8Array([0x00, 0x00, 0x00, 0x02, 0xab, 0xcd]) - expect(packedPacket).toMatchObject(expectedResult) -}) + const expectedResult = new Uint8Array([0x00, 0x00, 0x00, 0x02, 0xab, 0xcd]); + expect(packedPacket).toMatchObject(expectedResult); +}); diff --git a/tests/messages/unsubscribe.test.ts b/tests/messages/unsubscribe.test.ts index 46df045..e71ccbc 100644 --- a/tests/messages/unsubscribe.test.ts +++ b/tests/messages/unsubscribe.test.ts @@ -1,14 +1,16 @@ -import { MessageTypes } from '../../src/common' -import { packers } from '../../src/mapping' +import { MessageTypes } from "../../src/common"; +import { packers } from "../../src/mapping"; -test('Create an unsubscribe (0xffff) packet.', () => { +test("Create an unsubscribe (0xffff) packet.", () => { // Given - const messageType = 0xabcd + const messageType = 0xabcd; // When - const packedPacket = packers[MessageTypes.Unsubscribe]({ messageType: messageType }) + const packedPacket = packers[MessageTypes.Unsubscribe]({ + messageType: messageType, + }); // Then - const expectedResult = new Uint8Array([0xff, 0xff, 0x00, 0x02, 0xab, 0xcd]) - expect(packedPacket).toMatchObject(expectedResult) -}) + const expectedResult = new Uint8Array([0xff, 0xff, 0x00, 0x02, 0xab, 0xcd]); + expect(packedPacket).toMatchObject(expectedResult); +}); diff --git a/tests/messages/userDataRequest.test.ts b/tests/messages/userDataRequest.test.ts index 7b6b822..96059e3 100644 --- a/tests/messages/userDataRequest.test.ts +++ b/tests/messages/userDataRequest.test.ts @@ -1,52 +1,56 @@ -import Color from 'color' -import { MessageTypes } from '../../src/common' -import { packers, unpackers } from '../../src/mapping' +import Color from "color"; +import { MessageTypes } from "../../src/common"; +import { packers, unpackers } from "../../src/mapping"; -const KEY = new Uint8Array([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F]) +const KEY = new Uint8Array([ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, + 0x0d, 0x0e, 0x0f, +]); -test('Create a user data request (0x0002) packet.', () => { +test("Create a user data request (0x0002) packet.", () => { // Given - const username = 'Butlersaurus' - const colour = Color('#FF4000') - const clientId = 'Mercury' + const username = "Butlersaurus"; + const colour = Color("#FF4000"); + const clientId = "Mercury"; // When const packedPacket = packers[MessageTypes.UserDataRequest]( { username: username, colour: colour, - clientId: clientId + clientId: clientId, }, - KEY - ) + KEY, + ); // Then // We can't check the contents of the data as it's encrypted with a random nonce. // Check the message type and length. - expect(packedPacket.slice(0, 4)).toMatchObject(new Uint8Array([0x00, 0x02, 0x00, 0x67])) + expect(packedPacket.slice(0, 4)).toMatchObject( + new Uint8Array([0x00, 0x02, 0x00, 0x67]), + ); // Check the total length is as expected. - expect(packedPacket.length).toBe(107) -}) + expect(packedPacket.length).toBe(107); +}); -test('Parse a user data request (0x0002).', () => { +test("Parse a user data request (0x0002).", () => { // Given const data = new Uint8Array([ - 0, 12, - 66, 117, 116, 108, 101, 114, 115, 97, 117, 114, 117, 115, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 255, 64, 0, - 0, 7, - 77, 101, 114, 99, 117, 114, 121, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ]) - const username = 'Butlersaurus' - const colour = Color('#FF4000') - const clientId = 'Mercury' + 0, 12, 66, 117, 116, 108, 101, 114, 115, 97, 117, 114, 117, 115, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 64, 0, 0, 7, 77, 101, + 114, 99, 117, 114, 121, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + ]); + const username = "Butlersaurus"; + const colour = Color("#FF4000"); + const clientId = "Mercury"; // When - const unpackedPacket = unpackers[MessageTypes.UserDataRequest](data) + const unpackedPacket = unpackers[MessageTypes.UserDataRequest](data); // Then - expect(unpackedPacket.username).toBe(username) - expect(unpackedPacket.colour).toMatchObject(colour) - expect(unpackedPacket.clientId).toBe(clientId) -}) + expect(unpackedPacket.username).toBe(username); + expect(unpackedPacket.colour).toMatchObject(colour); + expect(unpackedPacket.clientId).toBe(clientId); +}); diff --git a/tests/messages/userDataResponse.test.ts b/tests/messages/userDataResponse.test.ts index c957dd3..066866f 100644 --- a/tests/messages/userDataResponse.test.ts +++ b/tests/messages/userDataResponse.test.ts @@ -1,52 +1,56 @@ -import Color from 'color' -import { MessageTypes } from '../../src/common' -import { packers, unpackers } from '../../src/mapping' +import Color from "color"; +import { MessageTypes } from "../../src/common"; +import { packers, unpackers } from "../../src/mapping"; -const KEY = new Uint8Array([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F]) +const KEY = new Uint8Array([ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, + 0x0d, 0x0e, 0x0f, +]); -test('Create a user data response (0x0003) packet.', () => { +test("Create a user data response (0x0003) packet.", () => { // Given - const username = 'Butlersaurus' - const colour = Color('#FF4000') - const clientId = 'Mercury' + const username = "Butlersaurus"; + const colour = Color("#FF4000"); + const clientId = "Mercury"; // When const packedPacket = packers[MessageTypes.UserDataResponse]( { username: username, colour: colour, - clientId: clientId + clientId: clientId, }, - KEY - ) + KEY, + ); // Then // We can't check the contents of the data as it's encrypted with a random nonce. // Check the message type and length. - expect(packedPacket.slice(0, 4)).toMatchObject(new Uint8Array([0x00, 0x03, 0x00, 0x67])) + expect(packedPacket.slice(0, 4)).toMatchObject( + new Uint8Array([0x00, 0x03, 0x00, 0x67]), + ); // Check the total length is as expected. - expect(packedPacket.length).toBe(107) -}) + expect(packedPacket.length).toBe(107); +}); -test('Parse a user data response (0x0003).', () => { +test("Parse a user data response (0x0003).", () => { // Given const data = new Uint8Array([ - 0, 12, - 66, 117, 116, 108, 101, 114, 115, 97, 117, 114, 117, 115, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 255, 64, 0, - 0, 7, - 77, 101, 114, 99, 117, 114, 121, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ]) - const username = 'Butlersaurus' - const colour = Color('#FF4000') - const clientId = 'Mercury' + 0, 12, 66, 117, 116, 108, 101, 114, 115, 97, 117, 114, 117, 115, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 64, 0, 0, 7, 77, 101, + 114, 99, 117, 114, 121, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + ]); + const username = "Butlersaurus"; + const colour = Color("#FF4000"); + const clientId = "Mercury"; // When - const unpackedPacket = unpackers[MessageTypes.UserDataResponse](data) + const unpackedPacket = unpackers[MessageTypes.UserDataResponse](data); // Then - expect(unpackedPacket.username).toBe(username) - expect(unpackedPacket.colour).toMatchObject(colour) - expect(unpackedPacket.clientId).toBe(clientId) -}) + expect(unpackedPacket.username).toBe(username); + expect(unpackedPacket.colour).toMatchObject(colour); + expect(unpackedPacket.clientId).toBe(clientId); +}); diff --git a/tests/utilities/number.test.ts b/tests/utilities/number.test.ts index 55d9c79..0af71c5 100644 --- a/tests/utilities/number.test.ts +++ b/tests/utilities/number.test.ts @@ -1,19 +1,19 @@ -import { numberToUint16BE, numberToUint32BE } from '../../src/utilities/number' +import { numberToUint16BE, numberToUint32BE } from "../../src/utilities/number"; -test('Test number conversion to Uint16 big endian buffer.', () => { +test("Test number conversion to Uint16 big endian buffer.", () => { // When - const result = numberToUint16BE(1234) + const result = numberToUint16BE(1234); // Then - const expectedResult = new Uint8Array([0x04, 0xd2]) - expect(result).toMatchObject(expectedResult) -}) + const expectedResult = new Uint8Array([0x04, 0xd2]); + expect(result).toMatchObject(expectedResult); +}); -test('Test number conversion to Uint32 big endian buffer.', () => { +test("Test number conversion to Uint32 big endian buffer.", () => { // When - const result = numberToUint32BE(123456) + const result = numberToUint32BE(123456); // Then - const expectedResult = new Uint8Array([0x00, 0x01, 0xE2, 0x40]) - expect(result).toMatchObject(expectedResult) -}) + const expectedResult = new Uint8Array([0x00, 0x01, 0xe2, 0x40]); + expect(result).toMatchObject(expectedResult); +}); diff --git a/tests/utilities/smart-buffer.test.ts b/tests/utilities/smart-buffer.test.ts index ea629ad..e9b4cd2 100644 --- a/tests/utilities/smart-buffer.test.ts +++ b/tests/utilities/smart-buffer.test.ts @@ -1,230 +1,240 @@ -import { SmartBuffer } from '../../src/utilities/smart-buffer' +import { SmartBuffer } from "../../src/utilities/smart-buffer"; -test('Read a UInt16.', () => { +test("Read a UInt16.", () => { // Given - const buffer = [0x30, 0x39] + const buffer = [0x30, 0x39]; // When - const smartBuffer = SmartBuffer.from(buffer) + const smartBuffer = SmartBuffer.from(buffer); // Then - expect(smartBuffer.readUInt16()).toBe(12345) -}) + expect(smartBuffer.readUInt16()).toBe(12345); +}); -test('Read a UInt32.', () => { +test("Read a UInt32.", () => { // Given - const buffer = [0x49, 0x96, 0x02, 0xD2] + const buffer = [0x49, 0x96, 0x02, 0xd2]; // When - const smartBuffer = SmartBuffer.from(buffer) + const smartBuffer = SmartBuffer.from(buffer); // Then - expect(smartBuffer.readUInt32()).toBe(1234567890) -}) + expect(smartBuffer.readUInt32()).toBe(1234567890); +}); -test('Read a buffer.', () => { +test("Read a buffer.", () => { // Given - const buffer = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + const buffer = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; // When - const smartBuffer = SmartBuffer.from(buffer) + const smartBuffer = SmartBuffer.from(buffer); // Then - const result = smartBuffer.readBytes(4) - expect(result).toMatchObject(new Uint8Array([0, 1, 2, 3])) -}) + const result = smartBuffer.readBytes(4); + expect(result).toMatchObject(new Uint8Array([0, 1, 2, 3])); +}); -test('Read a UInt16 from an offset.', () => { +test("Read a UInt16 from an offset.", () => { // Given - const buffer = [0x00, 0x00, 0x30, 0x39] + const buffer = [0x00, 0x00, 0x30, 0x39]; // When - const smartBuffer = SmartBuffer.from(buffer) - smartBuffer.cursor = 2 + const smartBuffer = SmartBuffer.from(buffer); + smartBuffer.cursor = 2; // Then - expect(smartBuffer.readUInt16()).toBe(12345) -}) + expect(smartBuffer.readUInt16()).toBe(12345); +}); -test('Read a UInt32 from an offset.', () => { +test("Read a UInt32 from an offset.", () => { // Given - const buffer = [0x00, 0x00, 0x49, 0x96, 0x02, 0xD2] + const buffer = [0x00, 0x00, 0x49, 0x96, 0x02, 0xd2]; // When - const smartBuffer = SmartBuffer.from(buffer) - smartBuffer.cursor = 2 + const smartBuffer = SmartBuffer.from(buffer); + smartBuffer.cursor = 2; // Then - expect(smartBuffer.readUInt32()).toBe(1234567890) -}) + expect(smartBuffer.readUInt32()).toBe(1234567890); +}); -test('Read a buffer from an offset.', () => { +test("Read a buffer from an offset.", () => { // Given - const buffer = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + const buffer = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; // When - const smartBuffer = SmartBuffer.from(buffer) - smartBuffer.cursor = 2 + const smartBuffer = SmartBuffer.from(buffer); + smartBuffer.cursor = 2; // Then - expect(smartBuffer.readBytes(4)).toMatchObject(new Uint8Array([2, 3, 4, 5])) -}) + expect(smartBuffer.readBytes(4)).toMatchObject(new Uint8Array([2, 3, 4, 5])); +}); -test('Write a UInt16.', () => { +test("Write a UInt16.", () => { // Given - const smartBuffer = new SmartBuffer() + const smartBuffer = new SmartBuffer(); // When - smartBuffer.writeUInt16(12345) + smartBuffer.writeUInt16(12345); // Then - expect(smartBuffer.data).toMatchObject(new Uint8Array([0x30, 0x39])) -}) + expect(smartBuffer.data).toMatchObject(new Uint8Array([0x30, 0x39])); +}); -test('Write a UInt32.', () => { +test("Write a UInt32.", () => { // Given - const smartBuffer = new SmartBuffer() + const smartBuffer = new SmartBuffer(); // When - smartBuffer.writeUInt32(1234567890) + smartBuffer.writeUInt32(1234567890); // Then - expect(smartBuffer.data).toMatchObject(new Uint8Array([0x49, 0x96, 0x02, 0xD2])) -}) + expect(smartBuffer.data).toMatchObject( + new Uint8Array([0x49, 0x96, 0x02, 0xd2]), + ); +}); -test('Write a buffer.', () => { +test("Write a buffer.", () => { // Given - const smartBuffer = new SmartBuffer() + const smartBuffer = new SmartBuffer(); // When - smartBuffer.writeBytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + smartBuffer.writeBytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); // Then - expect(smartBuffer.data).toMatchObject(new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) -}) + expect(smartBuffer.data).toMatchObject( + new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), + ); +}); -test('Write a UInt16 at an offset.', () => { +test("Write a UInt16 at an offset.", () => { // Given - const smartBuffer = new SmartBuffer() + const smartBuffer = new SmartBuffer(); // When - smartBuffer.cursor = 2 - smartBuffer.writeUInt16(12345) + smartBuffer.cursor = 2; + smartBuffer.writeUInt16(12345); // Then - expect(smartBuffer.data).toMatchObject(new Uint8Array([0x00, 0x00, 0x30, 0x39])) -}) + expect(smartBuffer.data).toMatchObject( + new Uint8Array([0x00, 0x00, 0x30, 0x39]), + ); +}); -test('Write a UInt32 at an offset.', () => { +test("Write a UInt32 at an offset.", () => { // Given - const smartBuffer = new SmartBuffer() + const smartBuffer = new SmartBuffer(); // When - smartBuffer.cursor = 2 - smartBuffer.writeUInt32(1234567890) + smartBuffer.cursor = 2; + smartBuffer.writeUInt32(1234567890); // Then - expect(smartBuffer.data).toMatchObject(new Uint8Array([0x00, 0x00, 0x49, 0x96, 0x02, 0xD2])) -}) + expect(smartBuffer.data).toMatchObject( + new Uint8Array([0x00, 0x00, 0x49, 0x96, 0x02, 0xd2]), + ); +}); -test('Write a buffer at an offset.', () => { +test("Write a buffer at an offset.", () => { // Given - const smartBuffer = new SmartBuffer() + const smartBuffer = new SmartBuffer(); // When - smartBuffer.cursor = 2 - smartBuffer.writeBytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + smartBuffer.cursor = 2; + smartBuffer.writeBytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); // Then - expect(smartBuffer.data).toMatchObject(new Uint8Array([0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) -}) + expect(smartBuffer.data).toMatchObject( + new Uint8Array([0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), + ); +}); -test('Cursor is correctly incremented after reading a UInt16.', () => { +test("Cursor is correctly incremented after reading a UInt16.", () => { // Given - const buffer = new Uint8Array(4) + const buffer = new Uint8Array(4); // When - const smartBuffer = SmartBuffer.from(buffer) + const smartBuffer = SmartBuffer.from(buffer); // Then - smartBuffer.readUInt16() - expect(smartBuffer.cursor).toBe(2) -}) + smartBuffer.readUInt16(); + expect(smartBuffer.cursor).toBe(2); +}); -test('Cursor is correctly incremented after reading a UInt32.', () => { +test("Cursor is correctly incremented after reading a UInt32.", () => { // Given - const buffer = new Uint8Array(4) + const buffer = new Uint8Array(4); // When - const smartBuffer = SmartBuffer.from(buffer) + const smartBuffer = SmartBuffer.from(buffer); // Then - smartBuffer.readUInt32() - expect(smartBuffer.cursor).toBe(4) -}) + smartBuffer.readUInt32(); + expect(smartBuffer.cursor).toBe(4); +}); -test('Cursor is correctly incremented after reading a buffer.', () => { +test("Cursor is correctly incremented after reading a buffer.", () => { // Given - const buffer = new Uint8Array(8) + const buffer = new Uint8Array(8); // When - const smartBuffer = SmartBuffer.from(buffer) + const smartBuffer = SmartBuffer.from(buffer); // Then - smartBuffer.readBytes(4) - expect(smartBuffer.cursor).toBe(4) -}) + smartBuffer.readBytes(4); + expect(smartBuffer.cursor).toBe(4); +}); -test('Cursor is correctly incremented after writing a UInt16.', () => { +test("Cursor is correctly incremented after writing a UInt16.", () => { // Given - const smartBuffer = new SmartBuffer() + const smartBuffer = new SmartBuffer(); // When - smartBuffer.writeUInt16(12345) + smartBuffer.writeUInt16(12345); // Then - expect(smartBuffer.cursor).toBe(2) -}) + expect(smartBuffer.cursor).toBe(2); +}); -test('Cursor is correctly incremented after writing a UInt32.', () => { +test("Cursor is correctly incremented after writing a UInt32.", () => { // Given - const smartBuffer = new SmartBuffer() + const smartBuffer = new SmartBuffer(); // When - smartBuffer.writeUInt32(1234567890) + smartBuffer.writeUInt32(1234567890); // Then - expect(smartBuffer.cursor).toBe(4) -}) + expect(smartBuffer.cursor).toBe(4); +}); -test('Cursor is correctly incremented after writing a buffer.', () => { +test("Cursor is correctly incremented after writing a buffer.", () => { // Given - const smartBuffer = new SmartBuffer() + const smartBuffer = new SmartBuffer(); // When - smartBuffer.writeBytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + smartBuffer.writeBytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); // Then - expect(smartBuffer.cursor).toBe(10) -}) + expect(smartBuffer.cursor).toBe(10); +}); -test('Seek to position below 0 throws range error.', () => { +test("Seek to position below 0 throws range error.", () => { // When - const smartBuffer = new SmartBuffer() + const smartBuffer = new SmartBuffer(); // Then expect(() => { - smartBuffer.cursor = -1 - }).toThrow(RangeError) -}) + smartBuffer.cursor = -1; + }).toThrow(RangeError); +}); -test('Pad some data.', () => { +test("Pad some data.", () => { // Given - const smartBuffer = new SmartBuffer() + const smartBuffer = new SmartBuffer(); // When - smartBuffer.pad(10) + smartBuffer.pad(10); // Then - expect(smartBuffer.length).toBe(10) -}) + expect(smartBuffer.length).toBe(10); +}); diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 21c332e..a8d4317 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": [] -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index eae9b7b..f05358d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,19 +1,16 @@ { "compilerOptions": { /* Visit https://aka.ms/tsconfig.json to read more about this file */ - "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - "module": "commonjs", /* Specify what module code is generated. */ - "rootDir": "src", /* Specify the root folder within your source files. */ - "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - "outDir": "dist", /* Specify an output folder for all emitted files. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - "strict": true, /* Enable all strict type-checking options. */ - "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "module": "commonjs" /* Specify what module code is generated. */, + "rootDir": "src" /* Specify the root folder within your source files. */, + "sourceMap": true /* Create source map files for emitted JavaScript files. */, + "outDir": "dist" /* Specify an output folder for all emitted files. */, + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + "strict": true /* Enable all strict type-checking options. */, + "skipLibCheck": true /* Skip type checking all .d.ts files. */, "declaration": true }, - "exclude": [ - "tests", - "dist" - ] + "exclude": ["tests", "dist"] }