Compare commits

..

10 Commits

Author SHA1 Message Date
61082d32e7 chore: update dependencies in package.json
- Added @3t/romulus dependency with version ^1.0.2
- Removed romulus-js file dependency
2025-09-06 18:57:58 +01:00
dd8e6ee49f Improve library 2025-09-06 18:50:08 +01:00
02c9cfdabc Update romulus-js path in package.json and tsconfig.json 2025-09-03 20:22:30 +01:00
695964a636 Prettier 2025-09-03 19:18:44 +01:00
eb620087c9 Initial updates 2025-09-03 19:18:19 +01:00
Jack Hadrill
8a6a73206e Get history fix 2024-11-20 00:25:21 +00:00
Jack Hadrill
26af3b8b69 Fix get history 2024-11-19 23:46:23 +00:00
Jack Hadrill
386e8c3ed1 Add get history message 2024-11-19 23:15:50 +00:00
Jack Hadrill
3a163df991 Fix outbound user data request/response 2022-03-18 22:31:50 +00:00
Jack Hadrill
0b19b83271 Fixed user data request/response definitions 2022-03-18 17:33:10 +00:00
35 changed files with 2147 additions and 1412 deletions

View File

@@ -39,7 +39,7 @@ steps:
- test - test
- build - build
when: when:
event: event:
- tag - tag
settings: settings:
base_url: https://git.jacknet.io base_url: https://git.jacknet.io
@@ -49,4 +49,4 @@ steps:
- bennc-m.zip - bennc-m.zip
checksum: checksum:
- md5 - md5
- sha256 - sha256

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
@3t:registry=https://git.3t.network/api/packages/3t.network/npm/

View File

@@ -1,5 +1,3 @@
{ {
"recommendations": [ "recommendations": ["orta.vscode-jest"]
"orta.vscode-jest" }
]
}

34
.vscode/launch.json vendored
View File

@@ -1,20 +1,16 @@
{ {
// Use IntelliSense to learn about possible attributes. // Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes. // Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"type": "pwa-node", "type": "pwa-node",
"request": "launch", "request": "launch",
"name": "Launch Program", "name": "Launch Program",
"skipFiles": [ "skipFiles": ["<node_internals>/**"],
"<node_internals>/**" "program": "${workspaceFolder}/dist/index.js",
], "outFiles": ["${workspaceFolder}/**/*.js"]
"program": "${workspaceFolder}/dist/index.js", }
"outFiles": [ ]
"${workspaceFolder}/**/*.js" }
]
}
]
}

View File

@@ -1,3 +1,3 @@
{ {
"editor.tabSize": 2 "editor.tabSize": 2
} }

32
.vscode/tasks.json vendored
View File

@@ -1,18 +1,16 @@
{ {
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{ {
"type": "typescript", "type": "typescript",
"tsconfig": "tsconfig.json", "tsconfig": "tsconfig.json",
"option": "watch", "option": "watch",
"problemMatcher": [ "problemMatcher": ["$tsc-watch"],
"$tsc-watch" "group": {
], "kind": "build",
"group": { "isDefault": true
"kind": "build", },
"isDefault": true "label": "tsc: watch - tsconfig.json"
}, }
"label": "tsc: watch - tsconfig.json" ]
} }
]
}

208
BENNC_PROTOCOL_SPEC.md Normal file
View File

@@ -0,0 +1,208 @@
# BENNC Protocol Implementation Guide
**BENNCv1** (Butlersaurus Ephemeral No NONCEnse Chat) - Complete implementation documentation for a binary pub/sub messaging protocol with encryption and compression.
## Quick Implementation Checklist
1. **Connect** to server (TCP or WebSocket)
2. **Subscribe** to message types you want to receive
3. **Encrypt** data with Romulus-M (except subscribe/unsubscribe)
4. **Send keepalive** every 30 seconds when idle
5. **Handle** incoming messages and sender IDs
## Connection Endpoints
| Protocol | Endpoint |
| --------------- | --------------------------------- |
| TCP | `chat.3t.network:10009` |
| WebSocket (TLS) | `wss://chat.3t.network:443/BENNC` |
| WebSocket | `ws://chat.3t.network:80/BENNC` |
## Core Requirements
- **Encoding**: Big-endian integers, UTF-8 strings
- **Encryption**: Romulus-M with 16-byte nonce + message type as additional data (big-endian uint16)
- **Compression**: ZSTD (when specified per message type)
- **Max Data Size**: 1000 bytes per message (including nonce for encrypted messages)
- **Keepalive**: Send every 30 seconds when idle
- **Reserved IDs**: 0xFFFFFF00-0xFFFFFFFF for client internal use (256 IDs for local messages, system notifications, etc.)
## Message Structure
**Client → Server:**
```
┌─────────────┬─────────────┬─────────────────────────┐
│ Message Type│ Length │ Data │
│ (2 bytes) │ (2 bytes) │ (0-1000 bytes) │
└─────────────┴─────────────┴─────────────────────────┘
```
**Server → Client:**
```
┌─────────────┬─────────────┬─────────────┬─────────────────────┐
│ Message Type│ Sender ID │ Length │ Data │
│ (2 bytes) │ (4 bytes) │ (2 bytes) │ (0-1000 bytes) │
└─────────────┴─────────────┴─────────────┴─────────────────────┘
```
_Sender ID is randomly generated per connection to identify message source._
## Encryption Implementation
**For encrypted messages (types 0x0001, 0x0002, 0x0003, 0x0006, 0x0007):**
1. Generate random 16-byte nonce
2. Prepare additional data: message type as big-endian uint16
3. Encrypt plaintext using Romulus-M AEAD
4. Prepend nonce to ciphertext
5. Total data = nonce (16 bytes) + ciphertext ≤ 1000 bytes
## Compression Implementation
- **Algorithm**: ZSTD (Zstandard) at default compression level
- **Application**: Per message type (all messages of type or none)
- **Order**: Compression applied **before** encryption
- **Scope**: Only the data portion, headers remain uncompressed
- **Currently used by**: Advanced Text Messages (0x0006, 0x0007)
## 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 |
---
## 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)
---
### Basic Message (0x0001)
UTF-8 chat messages. 16-byte nonce + encrypted data ≤ 1000 bytes total.
**Data:** Encrypted UTF-8 string
---
### 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)
- Client identifier length (2 bytes, big-endian)
- Client identifier (up to 32 bytes, UTF-8)
---
### 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)
- Client identifier length (2 bytes, big-endian)
- Client identifier (up to 32 bytes, UTF-8)
---
### 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
---
### Unsubscribe (0xFFFF)
Stop receiving messages of specified type.
**Data:** Message type to unsubscribe from (2 bytes, big-endian)
---
### 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
- Message ID identifies the complete message, not individual packets
- 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)
---
## 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
4. **Resubscribe** after any disconnection
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
- **Advanced Text garbled**: Reconstruct packets in correct order before decompression
---
## Protocol Features
- **Binary pub/sub messaging** with server-side routing
- **End-to-end encryption** using Romulus-M AEAD
- **ZSTD compression** for large text messages
- **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.

60
CLAUDE.md Normal file
View File

@@ -0,0 +1,60 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is bennc-js, a TypeScript implementation of the BENNCv1 (Butlersaurus Ephemeral No NONCEnse Chat) protocol specification. It provides client functionality for a binary pub/sub messaging protocol with encryption and compression support.
## Development Commands
- `npm run build` - Compile TypeScript to JavaScript (outputs to `dist/`)
- `npm run test` - Run Jest test suite
- `npm run lint` - Check code formatting with Prettier
- `npm run format` - Format code with Prettier
- `npm install` - Install dependencies (automatically runs `tsc` via postinstall)
## Architecture
The codebase implements the BENNC protocol specification with the following structure:
### Core Files
- `src/index.ts` - Main entry point exporting all public APIs
- `src/mapping.ts` - Central registry mapping message types (0x0000-0xFFFF) to their pack/unpack functions
- `src/common.ts` - Protocol constants including `MAX_DATA_LENGTH` (1000 bytes), `DEFAULT_KEY`, and `MessageTypes` enum
- `src/messages/packet.ts` - Core packet handling with `packOutgoingPacket` and `unpackIncomingPacket` functions
### Message Types Implementation
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
- `userDataRequest.ts` (0x0002) - Request user information
- `userDataResponse.ts` (0x0003) - Respond with user information
- `keepalive.ts` (0x0005) - Connection keepalive messages
- `history.ts` (0xFFFE) - Message history requests
- `unsubscribe.ts` (0xFFFF) - Unsubscribe from message types
### Protocol Implementation Details
- **Binary Protocol**: Big-endian integers, UTF-8 strings
- **Message Structure**: Client sends [Type(2)|Length(2)|Data(0-1000)], Server responds [Type(2)|SenderId(4)|Length(2)|Data(0-1000)]
- **Encryption**: Uses Romulus-M AEAD (via `romulus-js` dependency) with 16-byte nonces for message types 0x0001, 0x0002, 0x0003
- **Max Data Size**: 1000 bytes including nonce for encrypted messages
- **Dependencies**: Includes local `romulus-js` cryptography implementation and `color` library for user data
### Utilities
- `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
- **Mapping System**: Central `packers` and `unpackers` objects in `mapping.ts` provide type-safe message handling
- **Interface Separation**: `IncomingPacket` vs `OutgoingPacket` interfaces handle client/server message structure differences
- **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.

View File

@@ -1,5 +1,6 @@
# BENNC-JS # BENNC-JS
[![Build Status](https://drone.jacknet.io/api/badges/TerribleCodeClub/bennc-js/status.svg)](https://drone.jacknet.io/TerribleCodeClub/bennc-js) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
[![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. 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. To build the BENNC-JS library, first clone this repository.
Run the following commands from the root of the repository: Run the following commands from the root of the repository:
```bash ```bash
$ npm install $ npm install
$ npm run build $ npm run build
``` ```
The build output will be saved to the `dist` directory. The build output will be saved to the `dist` directory.
## Development instructions ## Development instructions
Requirements: Requirements:
- The latest LTS builds of Node and npm. - The latest LTS builds of Node and npm.
Follow the instructions below to lint, test and build BENNC-JS. Follow the instructions below to lint, test and build BENNC-JS.
@@ -34,6 +38,7 @@ $ npm run lint
$ npm install $ npm install
$ npm run test $ npm run test
``` ```
#### Build #### Build
```bash ```bash
@@ -43,14 +48,8 @@ $ npm run build
### Visual Studio Code ### 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. Run the build task (`Ctrl+Shift+B` or `⇧⌘B`) to automatically compile the Typescript source files in the background.
Unit tests use the [Jest](https://jestjs.io/) library. Support for Visual Studio Code is offered through the [Jest marketplace package](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest) maintained by Orta. Unit tests use the [Jest](https://jestjs.io/) library. Support for Visual Studio Code is offered through the [Jest marketplace package](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest) maintained by Orta.
## Contribution guidelines
[![JavaScript Style Guide](https://cdn.rawgit.com/standard/standard/master/badge.svg)](https://github.com/standard/standard)
This library uses [ts-standard](https://github.com/standard/ts-standard), based on [JavaScript Standard Style](https://standardjs.com/rules.html). Please ensure all contributions are ts-standard compliant before submitting a pull request.

2070
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,8 @@
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"scripts": { "scripts": {
"lint": "ts-standard", "lint": "prettier --check .",
"format": "prettier --write .",
"test": "jest", "test": "jest",
"build": "tsc", "build": "tsc",
"postinstall": "tsc" "postinstall": "tsc"
@@ -30,8 +31,8 @@
} }
}, },
"dependencies": { "dependencies": {
"color": "^4.2.0", "@3t/romulus": "^1.0.2",
"@types/color": "^3.0.3", "@types/color": "^3.0.3",
"romulus-js": "git+https://git.jacknet.io/TerribleCodeClub/romulus-js.git" "color": "^4.2.0"
} }
} }

View File

@@ -1,6 +1,6 @@
export const MAX_DATA_LENGTH = 1000 export const MAX_DATA_LENGTH = 1000;
export const DEFAULT_KEY = new Uint8Array(16) export const DEFAULT_KEY = new Uint8Array(16);
export enum MessageTypes { export enum MessageTypes {
Subscribe = 0x0000, Subscribe = 0x0000,
@@ -8,5 +8,6 @@ export enum MessageTypes {
UserDataRequest = 0x0002, UserDataRequest = 0x0002,
UserDataResponse = 0x0003, UserDataResponse = 0x0003,
Keepalive = 0x0005, Keepalive = 0x0005,
Unsubscribe = 0xFFFF GetHistory = 0xfffe,
Unsubscribe = 0xffff,
} }

View File

@@ -1,9 +1,9 @@
export { numberToUint16BE, numberToUint32BE } from './utilities/number' export { numberToUint16BE, numberToUint32BE } from "./utilities/number";
export { unpackIncomingPacket } from './messages/packet' export { unpackIncomingPacket } from "./messages/packet";
export { packers, unpackers } from './mapping' export { packers, unpackers } from "./mapping";
export { MessageTypes } from './common' export type { MessageTypes } from "./common";
export { IncomingPacket, OutgoingPacket } from './messages/packet' export type { IncomingPacket, OutgoingPacket } from "./messages/packet";
export { SubscribeMessage } from './messages/subscribe' export type { SubscribeMessage } from "./messages/subscribe";
export { BasicMessage } from './messages/basic' export type { BasicMessage } from "./messages/basic";
export { UserDataRequestMessage } from './messages/userDataRequest' export type { UserDataRequestMessage } from "./messages/userDataRequest";
export { UserDataResponseMessage } from './messages/userDataResponse' export type { UserDataResponseMessage } from "./messages/userDataResponse";

View File

@@ -1,9 +1,16 @@
import { packSubscribeMessage } from './messages/subscribe' import { packSubscribeMessage } from "./messages/subscribe";
import { packBasicMessage, unpackBasicMessage } from './messages/basic' import { packBasicMessage, unpackBasicMessage } from "./messages/basic";
import { packUserDataRequestMessage, unpackUserDataRequestMessage } from './messages/userDataRequest' import {
import { packUserDataResponseMessage, unpackUserDataResponseMessage } from './messages/userDataResponse' packUserDataRequestMessage,
import { packKeepaliveMessage } from './messages/keepalive' unpackUserDataRequestMessage,
import { packUnsubscribeMessage } from './messages/unsubscribe' } from "./messages/userDataRequest";
import {
packUserDataResponseMessage,
unpackUserDataResponseMessage,
} from "./messages/userDataResponse";
import { packKeepaliveMessage } from "./messages/keepalive";
import { packUnsubscribeMessage } from "./messages/unsubscribe";
import { packGetHistoryMessage } from "./messages/history";
export const packers = { export const packers = {
0x0000: packSubscribeMessage, 0x0000: packSubscribeMessage,
@@ -11,11 +18,12 @@ export const packers = {
0x0002: packUserDataRequestMessage, 0x0002: packUserDataRequestMessage,
0x0003: packUserDataResponseMessage, 0x0003: packUserDataResponseMessage,
0x0005: packKeepaliveMessage, 0x0005: packKeepaliveMessage,
0xffff: packUnsubscribeMessage 0xfffe: packGetHistoryMessage,
} 0xffff: packUnsubscribeMessage,
};
export const unpackers = { export const unpackers = {
0x0001: unpackBasicMessage, 0x0001: unpackBasicMessage,
0x0002: unpackUserDataRequestMessage, 0x0002: unpackUserDataRequestMessage,
0x0003: unpackUserDataResponseMessage 0x0003: unpackUserDataResponseMessage,
} };

View File

@@ -1,12 +1,12 @@
import { encrypt } from 'romulus-js' import { encrypt, decrypt } from "romulus-js";
import { DEFAULT_KEY, MessageTypes } from '../common' import { DEFAULT_KEY, MessageTypes } from "../common";
import { numberToUint16BE } from '../utilities/number' import { numberToUint16BE } from "../utilities/number";
import { packOutgoingPacket } from './packet' import { packOutgoingPacket } from "./packet";
const MESSAGE_TYPE = numberToUint16BE(MessageTypes.Basic) const MESSAGE_TYPE = numberToUint16BE(MessageTypes.Basic);
export interface BasicMessage { export interface BasicMessage {
data: Uint8Array data: Uint8Array;
} }
/** /**
@@ -15,19 +15,30 @@ export interface BasicMessage {
* @param key The key to encrypt the data with. * @param key The key to encrypt the data with.
* @returns An encrypted outgoing basic message (0x0001) packet. * @returns An encrypted outgoing basic message (0x0001) packet.
*/ */
export function packBasicMessage (message: Uint8Array, key: Uint8Array = DEFAULT_KEY): Uint8Array { export function packBasicMessage(
const data = encrypt(message, MESSAGE_TYPE, key) message: Uint8Array,
key: Uint8Array = DEFAULT_KEY,
): Uint8Array {
const data = encrypt(message, MESSAGE_TYPE, key);
return packOutgoingPacket({ return packOutgoingPacket({
messageType: MESSAGE_TYPE, messageType: MESSAGE_TYPE,
data: data data: data,
}) });
} }
/** /**
* Unpack the data section of an incoming basic message (0x0001) message. * Unpack the data section of an incoming basic message (0x0001) message.
* @param data The data section of an incoming basic message (0x0001) message. * @param data The encrypted data section of an incoming basic message (0x0001) message.
* @returns An encrypted unpacked basic message (0x0001) message. * @param key The key to decrypt the data with.
* @returns The decrypted plaintext message.
*/ */
export function unpackBasicMessage (data: Uint8Array): Uint8Array { export function unpackBasicMessage(
return data data: Uint8Array,
key: Uint8Array = DEFAULT_KEY,
): Uint8Array {
const result = decrypt(data, MESSAGE_TYPE, key);
if (result.success) {
return result.plaintext;
}
throw new Error("Failed to decrypt basic message");
} }

15
src/messages/history.ts Normal file
View File

@@ -0,0 +1,15 @@
import { MessageTypes } from "../common";
import { numberToUint16BE } from "../utilities/number";
import { packOutgoingPacket } from "./packet";
const MESSAGE_TYPE = numberToUint16BE(MessageTypes.GetHistory);
/**
* Create an outgoing get history (0xfffe) packet.
* @returns An outgoing get history (0xfffe) packet.
*/
export function packGetHistoryMessage(): Uint8Array {
return packOutgoingPacket({
messageType: MESSAGE_TYPE,
});
}

View File

@@ -1,16 +1,15 @@
import { MessageTypes } from '../common' import { MessageTypes } from "../common";
import { numberToUint16BE } from '../utilities/number' import { numberToUint16BE } from "../utilities/number";
import { packOutgoingPacket } from './packet' import { packOutgoingPacket } from "./packet";
const MESSAGE_TYPE = numberToUint16BE(MessageTypes.Keepalive) const MESSAGE_TYPE = numberToUint16BE(MessageTypes.Keepalive);
/** /**
* Create an outgoing keepalive (0x0005) packet. * Create an outgoing keepalive (0x0005) packet.
* @returns An outgoing keepalive (0x0005) packet. * @returns An outgoing keepalive (0x0005) packet.
*/ */
export function packKeepaliveMessage (): Uint8Array { export function packKeepaliveMessage(): Uint8Array {
return packOutgoingPacket({ return packOutgoingPacket({
messageType: MESSAGE_TYPE, messageType: MESSAGE_TYPE,
data: new Uint8Array(0) });
})
} }

View File

@@ -1,16 +1,16 @@
import { MAX_DATA_LENGTH } from '../common' import { MAX_DATA_LENGTH } from "../common";
import { numberToUint16BE } from '../utilities/number' import { numberToUint16BE } from "../utilities/number";
import { SmartBuffer } from '../utilities/smart-buffer' import { SmartBuffer } from "../utilities/smart-buffer";
export interface IncomingPacket { export interface IncomingPacket {
messageType: number messageType: number;
senderId: number senderId: number;
data: Uint8Array data: Uint8Array;
} }
export interface OutgoingPacket { export interface OutgoingPacket {
messageType: Uint8Array messageType: Uint8Array;
data: Uint8Array data?: Uint8Array;
} }
/** /**
@@ -18,19 +18,24 @@ export interface OutgoingPacket {
* @param outgoingPacket The message type and data to send. * @param outgoingPacket The message type and data to send.
* @returns A buffer containing the ready-to-send packet. * @returns A buffer containing the ready-to-send packet.
*/ */
export function packOutgoingPacket (outgoingPacket: OutgoingPacket): Uint8Array { export function packOutgoingPacket(outgoingPacket: OutgoingPacket): Uint8Array {
// Default to empty data if not provided
const data = outgoingPacket.data ?? new Uint8Array(0);
// Verify that the data does not exceed the maximum data length. // Verify that the data does not exceed the maximum data length.
if (outgoingPacket.data.length > MAX_DATA_LENGTH) { if (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 ${data.length} exceeds max data length ${MAX_DATA_LENGTH}.`,
);
} }
// Prepare the outgoing packet. // Prepare the outgoing packet.
const buffer = new SmartBuffer() const buffer = new SmartBuffer();
buffer.writeBytes(outgoingPacket.messageType) buffer.writeBytes(outgoingPacket.messageType);
buffer.writeBytes(numberToUint16BE(outgoingPacket.data.length)) buffer.writeBytes(numberToUint16BE(data.length));
buffer.writeBytes(outgoingPacket.data) buffer.writeBytes(data);
return buffer.data return buffer.data;
} }
/** /**
@@ -38,17 +43,19 @@ export function packOutgoingPacket (outgoingPacket: OutgoingPacket): Uint8Array
* @param incomingPacket The incoming buffer from a WebSocket to unpack. * @param incomingPacket The incoming buffer from a WebSocket to unpack.
* @returns The unpacked data. * @returns The unpacked data.
*/ */
export function unpackIncomingPacket (incomingPacket: ArrayBuffer): IncomingPacket { export function unpackIncomingPacket(
const buffer = SmartBuffer.from(incomingPacket) incomingPacket: ArrayBuffer,
): IncomingPacket {
const buffer = SmartBuffer.from(incomingPacket);
const messageType = buffer.readUInt16() const messageType = buffer.readUInt16();
const senderId = buffer.readUInt32() const senderId = buffer.readUInt32();
const length = buffer.readUInt16() const length = buffer.readUInt16();
const data = buffer.readBytes(length) const data = buffer.readBytes(length);
return { return {
messageType: messageType, messageType: messageType,
senderId: senderId, senderId: senderId,
data: data data: data,
} };
} }

View File

@@ -1,11 +1,11 @@
import { MessageTypes } from '../common' import { MessageTypes } from "../common";
import { numberToUint16BE } from '../utilities/number' import { numberToUint16BE } from "../utilities/number";
import { packOutgoingPacket } from './packet' import { packOutgoingPacket } from "./packet";
const MESSAGE_TYPE = numberToUint16BE(MessageTypes.Subscribe) const MESSAGE_TYPE = numberToUint16BE(MessageTypes.Subscribe);
export interface SubscribeMessage { export interface SubscribeMessage {
messageType: number messageType: number;
} }
/** /**
@@ -13,10 +13,10 @@ export interface SubscribeMessage {
* @param properties The properties for the message. * @param properties The properties for the message.
* @returns An outgoing subscribe (0x0000) packet. * @returns An outgoing subscribe (0x0000) packet.
*/ */
export function packSubscribeMessage (properties: SubscribeMessage): Uint8Array { export function packSubscribeMessage(properties: SubscribeMessage): Uint8Array {
const data = numberToUint16BE(properties.messageType) const data = numberToUint16BE(properties.messageType);
return packOutgoingPacket({ return packOutgoingPacket({
messageType: MESSAGE_TYPE, messageType: MESSAGE_TYPE,
data: data data: data,
}) });
} }

View File

@@ -1,11 +1,11 @@
import { MessageTypes } from '../common' import { MessageTypes } from "../common";
import { numberToUint16BE } from '../utilities/number' import { numberToUint16BE } from "../utilities/number";
import { packOutgoingPacket } from './packet' import { packOutgoingPacket } from "./packet";
const MESSAGE_TYPE = numberToUint16BE(MessageTypes.Unsubscribe) const MESSAGE_TYPE = numberToUint16BE(MessageTypes.Unsubscribe);
export interface UnsubscribeMessage { export interface UnsubscribeMessage {
messageType: number messageType: number;
} }
/** /**
@@ -13,10 +13,12 @@ export interface UnsubscribeMessage {
* @param properties The properties for the message. * @param properties The properties for the message.
* @returns An outgoing unsubscribe (0xFFFF) packet. * @returns An outgoing unsubscribe (0xFFFF) packet.
*/ */
export function packUnsubscribeMessage (properties: UnsubscribeMessage): Uint8Array { export function packUnsubscribeMessage(
const data = numberToUint16BE(properties.messageType) properties: UnsubscribeMessage,
): Uint8Array {
const data = numberToUint16BE(properties.messageType);
return packOutgoingPacket({ return packOutgoingPacket({
messageType: MESSAGE_TYPE, messageType: MESSAGE_TYPE,
data: data data: data,
}) });
} }

View File

@@ -1,16 +1,16 @@
import Color from 'color' import Color from "color";
import { encrypt } from 'romulus-js' import { encrypt } from "romulus-js";
import { DEFAULT_KEY, MessageTypes } from '../common' import { DEFAULT_KEY, MessageTypes } from "../common";
import { numberToUint16BE } from '../utilities/number' import { numberToUint16BE } from "../utilities/number";
import { SmartBuffer } from '../utilities/smart-buffer' import { SmartBuffer } from "../utilities/smart-buffer";
import { packOutgoingPacket } from './packet' import { packOutgoingPacket } from "./packet";
const MESSAGE_TYPE = numberToUint16BE(MessageTypes.UserDataRequest) const MESSAGE_TYPE = numberToUint16BE(MessageTypes.UserDataRequest);
export interface UserDataRequestMessage { export interface UserDataRequestMessage {
username: string username: string;
colour: Color colour: Color;
clientId: string clientId: string;
} }
/** /**
@@ -19,31 +19,36 @@ export interface UserDataRequestMessage {
* @param key The key to encrypt the data with. * @param key The key to encrypt the data with.
* @returns An outgoing user data request (0x0002) packet. * @returns An outgoing user data request (0x0002) packet.
*/ */
export function packUserDataRequestMessage (properties: UserDataRequestMessage, key: Uint8Array = DEFAULT_KEY): Uint8Array { export function packUserDataRequestMessage(
const encoder = new TextEncoder() properties: UserDataRequestMessage,
key: Uint8Array = DEFAULT_KEY,
): Uint8Array {
const encoder = new TextEncoder();
// Prepare data in correct format. // Prepare data in correct format.
const username = encoder.encode(properties.username) const username = encoder.encode(properties.username);
const usernameLength = numberToUint16BE(username.length) const usernameLength = numberToUint16BE(username.length);
const colour = new Uint8Array(properties.colour.array()) const colour = new Uint8Array(properties.colour.array());
const clientId = encoder.encode(properties.clientId) const clientId = encoder.encode(properties.clientId);
const clientIdLength = numberToUint16BE(clientId.length) const clientIdLength = numberToUint16BE(clientId.length);
// Pack data. // Pack data.
const packedData = new SmartBuffer() const packedData = new SmartBuffer();
packedData.writeBytes(usernameLength) packedData.writeBytes(usernameLength);
packedData.writeBytes(username) packedData.writeBytes(username);
packedData.writeBytes(colour) packedData.pad(32 - username.length);
packedData.writeBytes(clientIdLength) packedData.writeBytes(colour);
packedData.writeBytes(clientId) packedData.writeBytes(clientIdLength);
packedData.writeBytes(clientId);
packedData.pad(32 - clientId.length);
// Encrypt the data. // Encrypt the data.
const data = encrypt(packedData.data, MESSAGE_TYPE, key) const data = encrypt(packedData.data, MESSAGE_TYPE, key);
return packOutgoingPacket({ return packOutgoingPacket({
messageType: MESSAGE_TYPE, messageType: MESSAGE_TYPE,
data: data data: data,
}) });
} }
/** /**
@@ -51,22 +56,25 @@ export function packUserDataRequestMessage (properties: UserDataRequestMessage,
* @param data The decrypted data section of an incoming user data request (0x0002) message. * @param data The decrypted data section of an incoming user data request (0x0002) message.
* @returns An unpacked 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. // Unpack and read data in correct format.
const packedData = SmartBuffer.from(data) const packedData = SmartBuffer.from(Array.from(data));
const usernameLength = packedData.readUInt16() const usernameLength = packedData.readUInt16();
const username = packedData.readBytes(usernameLength) const username = packedData.readBytes(usernameLength);
const colour = packedData.readBytes(3) packedData.cursor = 34;
const clientIdLength = packedData.readUInt16() const colour = packedData.readBytes(3);
const clientId = packedData.readBytes(clientIdLength) const clientIdLength = packedData.readUInt16();
const clientId = packedData.readBytes(clientIdLength);
const decoder = new TextDecoder() const decoder = new TextDecoder();
// Return data in correct format. // Return data in correct format.
return { return {
username: decoder.decode(username), username: decoder.decode(username),
colour: Color.rgb(colour), colour: Color.rgb(colour),
clientId: decoder.decode(clientId) clientId: decoder.decode(clientId),
} };
} }

View File

@@ -1,16 +1,16 @@
import Color from 'color' import Color from "color";
import { encrypt } from 'romulus-js' import { encrypt } from "romulus-js";
import { DEFAULT_KEY, MessageTypes } from '../common' import { DEFAULT_KEY, MessageTypes } from "../common";
import { numberToUint16BE } from '../utilities/number' import { numberToUint16BE } from "../utilities/number";
import { SmartBuffer } from '../utilities/smart-buffer' import { SmartBuffer } from "../utilities/smart-buffer";
import { packOutgoingPacket } from './packet' import { packOutgoingPacket } from "./packet";
const MESSAGE_TYPE = numberToUint16BE(MessageTypes.UserDataResponse) const MESSAGE_TYPE = numberToUint16BE(MessageTypes.UserDataResponse);
export interface UserDataResponseMessage { export interface UserDataResponseMessage {
username: string username: string;
colour: Color colour: Color;
clientId: string clientId: string;
} }
/** /**
@@ -19,30 +19,35 @@ export interface UserDataResponseMessage {
* @param key The key to encrypt the data with. * @param key The key to encrypt the data with.
* @returns The data section of an outgoing user data response (0x0003) message. * @returns The data section of an outgoing user data response (0x0003) message.
*/ */
export function packUserDataResponseMessage (properties: UserDataResponseMessage, key: Uint8Array = DEFAULT_KEY): Uint8Array { export function packUserDataResponseMessage(
const encoder = new TextEncoder() properties: UserDataResponseMessage,
key: Uint8Array = DEFAULT_KEY,
): Uint8Array {
const encoder = new TextEncoder();
// Prepare data in correct format. // Prepare data in correct format.
const username = encoder.encode(properties.username) const username = encoder.encode(properties.username);
const usernameLength = numberToUint16BE(username.length) const usernameLength = numberToUint16BE(username.length);
const colour = new Uint8Array(properties.colour.array()) const colour = new Uint8Array(properties.colour.array());
const clientId = encoder.encode(properties.clientId) const clientId = encoder.encode(properties.clientId);
const clientIdLength = numberToUint16BE(clientId.length) const clientIdLength = numberToUint16BE(clientId.length);
// Pack data. // Pack data.
const packedData = new SmartBuffer() const packedData = new SmartBuffer();
packedData.writeBytes(usernameLength) packedData.writeBytes(usernameLength);
packedData.writeBytes(username) packedData.writeBytes(username);
packedData.writeBytes(colour) packedData.pad(32 - username.length);
packedData.writeBytes(clientIdLength) packedData.writeBytes(colour);
packedData.writeBytes(clientId) packedData.writeBytes(clientIdLength);
packedData.writeBytes(clientId);
packedData.pad(32 - clientId.length);
// Return encrypted data. // Return encrypted data.
const data = encrypt(packedData.data, MESSAGE_TYPE, key) const data = encrypt(packedData.data, MESSAGE_TYPE, key);
return packOutgoingPacket({ return packOutgoingPacket({
messageType: MESSAGE_TYPE, messageType: MESSAGE_TYPE,
data: data data: data,
}) });
} }
/** /**
@@ -50,22 +55,25 @@ export function packUserDataResponseMessage (properties: UserDataResponseMessage
* @param data The decrypted data section of an incoming user data response (0x0003) message. * @param data The decrypted data section of an incoming user data response (0x0003) message.
* @returns A unpacked 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. // Unpack and read data in correct format.
const packedData = SmartBuffer.from(data) const packedData = SmartBuffer.from(Array.from(data));
const usernameLength = packedData.readUInt16() const usernameLength = packedData.readUInt16();
const username = packedData.readBytes(usernameLength) const username = packedData.readBytes(usernameLength);
const colour = packedData.readBytes(3) packedData.cursor = 34;
const clientIdLength = packedData.readUInt16() const colour = packedData.readBytes(3);
const clientId = packedData.readBytes(clientIdLength) const clientIdLength = packedData.readUInt16();
const clientId = packedData.readBytes(clientIdLength);
const decoder = new TextDecoder() const decoder = new TextDecoder();
// Return data in correct format. // Return data in correct format.
return { return {
username: decoder.decode(username), username: decoder.decode(username),
colour: Color.rgb(colour), colour: Color.rgb(colour),
clientId: decoder.decode(clientId) clientId: decoder.decode(clientId),
} };
} }

View File

@@ -3,11 +3,11 @@
* @param number The number to pack. * @param number The number to pack.
* @returns The packed buffer. * @returns The packed buffer.
*/ */
export function numberToUint16BE (number: number): Uint8Array { export function numberToUint16BE(number: number): Uint8Array {
const ret = new Uint8Array(2) const ret = new Uint8Array(2);
ret[0] = (number & 0xFF00) >> 8 ret[0] = (number & 0xff00) >> 8;
ret[1] = (number & 0x00FF) >> 0 ret[1] = (number & 0x00ff) >> 0;
return ret return ret;
} }
/** /**
@@ -15,11 +15,11 @@ export function numberToUint16BE (number: number): Uint8Array {
* @param number The number to pack. * @param number The number to pack.
* @returns The packed buffer. * @returns The packed buffer.
*/ */
export function numberToUint32BE (number: number): Uint8Array { export function numberToUint32BE(number: number): Uint8Array {
const ret = new Uint8Array(4) const ret = new Uint8Array(4);
ret[0] = (number & 0xFF000000) >> 24 ret[0] = (number & 0xff000000) >> 24;
ret[1] = (number & 0x00FF0000) >> 16 ret[1] = (number & 0x00ff0000) >> 16;
ret[2] = (number & 0x0000FF00) >> 8 ret[2] = (number & 0x0000ff00) >> 8;
ret[3] = (number & 0x000000FF) >> 0 ret[3] = (number & 0x000000ff) >> 0;
return ret return ret;
} }

View File

@@ -1,62 +1,64 @@
import { numberToUint16BE, numberToUint32BE } from './number' import { numberToUint16BE, numberToUint32BE } from "./number";
export class SmartBuffer { export class SmartBuffer {
private _data: number[] private _data: number[];
private _cursor: number private _cursor: number;
/** /**
* Wrap a buffer to track position and provide useful read / write functionality. * Wrap a buffer to track position and provide useful read / write functionality.
* @param data Buffer to wrap (optional). * @param data Buffer to wrap (optional).
*/ */
constructor (length: number = 0) { constructor(length: number = 0) {
this._data = new Array<number>(length) this._data = new Array<number>(length);
this._cursor = 0 this._cursor = 0;
} }
/** /**
* Return a regular buffer. * Return a regular buffer.
*/ */
get data (): Uint8Array { get data(): Uint8Array {
return new Uint8Array(this._data) return new Uint8Array(this._data);
} }
/** /**
* Update the smart buffer to wrap new data. * Update the smart buffer to wrap new data.
*/ */
set data (data: Uint8Array) { set data(data: Uint8Array) {
this._data = Array.from(data) this._data = Array.from(data);
this.cursor = 0 this.cursor = 0;
} }
/** /**
* Get the length of the smart buffer data. * Get the length of the smart buffer data.
*/ */
get length (): number { get length(): number {
return this._data.length return this._data.length;
} }
/** /**
* Set the length of the smart buffer data. * Set the length of the smart buffer data.
*/ */
set length (length: number) { set length(length: number) {
this._data.length = length this._data.length = length;
} }
/** /**
* Get the current cursor position of the smart buffer. * Get the current cursor position of the smart buffer.
*/ */
get cursor (): number { get cursor(): number {
return this._cursor return this._cursor;
} }
/** /**
* Set the cursor position of the smart buffer. * Set the cursor position of the smart buffer.
*/ */
set cursor (position: number) { set cursor(position: number) {
if (position < 0) { 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,32 +66,33 @@ export class SmartBuffer {
* @param data The object to convert to a new SmartBuffer. * @param data The object to convert to a new SmartBuffer.
* @returns A new SmartBuffer. * @returns A new SmartBuffer.
*/ */
static from (data: number[] | ArrayBuffer): SmartBuffer { static from(data: number[] | ArrayBuffer): SmartBuffer {
const smartBuffer = new SmartBuffer() const smartBuffer = new SmartBuffer();
if (data instanceof ArrayBuffer) { if (data instanceof ArrayBuffer) {
smartBuffer._data = Array.from(new Uint8Array(data)) smartBuffer._data = Array.from(new Uint8Array(data));
} else { } else {
smartBuffer._data = Array.from(data) smartBuffer._data = Array.from(data);
} }
return smartBuffer return smartBuffer;
} }
/** /**
* Pads bytes to the end of the smart buffer. * Pads bytes to the end of the smart buffer.
* @param length The number of bytes to pad. * @param length The number of bytes to pad.
*/ */
pad (length: number): void { pad(length: number): void {
this._data.push(...Array<number>(length)) this._data.push(...Array<number>(length).fill(0));
this.cursor += length;
} }
/** /**
* Return the data from the specified range. * Return the data from the specified range.
* @param start The start position. * @param start The start position.
* @param end The end position. * @param end The end position.
* @returns A new buffer containing data from the specified range. * @returns A new buffer containing data from the specified range.
*/ */
slice (start: number, end: number): Uint8Array { slice(start: number, end: number): Uint8Array {
return new Uint8Array(this._data.slice(start, end)) return new Uint8Array(this._data.slice(start, end));
} }
/** /**
@@ -98,66 +101,66 @@ export class SmartBuffer {
* @param deleteCount The number of items to remove before inserting new data. * @param deleteCount The number of items to remove before inserting new data.
* @param items The items to insert at the specified position. * @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) { if (this.length < start) {
this.pad(start) this._data.push(...Array<number>(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. * 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. * @returns A number represented by the bytes at the current cursor position.
*/ */
readUInt16 (): number { readUInt16(): number {
const num = this.slice(this.cursor, this.cursor + 2) const num = this.slice(this.cursor, this.cursor + 2);
this.cursor += 2 this.cursor += 2;
return (num[0] << 8 | num[1]) >>> 0 return ((num[0] << 8) | num[1]) >>> 0;
} }
/** /**
* Read a UInt32 number from the smart buffer at the current cursor position, and increment the cursor. * 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. * @returns A number represented by the bytes at the current cursor position.
*/ */
readUInt32 (): number { readUInt32(): number {
const num = this.slice(this.cursor, this.cursor + 4) const num = this.slice(this.cursor, this.cursor + 4);
this.cursor += 4 this.cursor += 4;
return (num[0] << 24 | num[1] << 16 | num[2] << 8 | num[3]) >>> 0 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. * 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. * @returns A buffer containing the bytes read from the current cursor position.
*/ */
readBytes (length: number): Uint8Array { readBytes(length: number): Uint8Array {
this.cursor += length this.cursor += length;
return this.slice(this.cursor - length, this.cursor) 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. * 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. * @param number The number to write in UInt16 format at the current cursor position.
*/ */
writeUInt16 (number: number): void { writeUInt16(number: number): void {
this.splice(this.cursor, 2, ...numberToUint16BE(number)) this.splice(this.cursor, 2, ...numberToUint16BE(number));
this.cursor += 2 this.cursor += 2;
} }
/** /**
* Write a UInt32 number to the smart buffer at the current cursor position, and increment the cursor. * 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. * @param number The number to write in UInt32 format at the current cursor position.
*/ */
writeUInt32 (number: number): void { writeUInt32(number: number): void {
this.splice(this.cursor, 4, ...numberToUint32BE(number)) this.splice(this.cursor, 4, ...numberToUint32BE(number));
this.cursor += 4 this.cursor += 4;
} }
/** /**
* Write the specified bytes to the smart buffer at the current cursor position, and increment the cursor. * 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. * @param buffer The bytes to write at the current cursor position.
*/ */
writeBytes (buffer: number[] | Uint8Array): void { writeBytes(buffer: number[] | Uint8Array): void {
this.splice(this.cursor, buffer.length, ...buffer) this.splice(this.cursor, buffer.length, ...buffer);
this.cursor += buffer.length this.cursor += buffer.length;
} }
} }

View File

@@ -1,36 +1,37 @@
import { MessageTypes } from '../../src/common' import { MessageTypes } from "../../src/common";
import { packers, unpackers } from '../../src/mapping' 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 // Given
const encoder = new TextEncoder() const encoder = new TextEncoder();
const message = encoder.encode('Hello, World!') const message = encoder.encode("Hello, World!");
// When // When
const packedPacket = packers[MessageTypes.Basic]( const packedPacket = packers[MessageTypes.Basic](message, KEY);
message,
KEY
)
// Then // Then
// We can't check the contents of the data as it's encrypted with a random nonce. // We can't check the contents of the data as it's encrypted with a random nonce.
// Check the message type and length. // 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. // 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 // Given
const data = new Uint8Array([1, 2, 3, 4]) const data = new Uint8Array([1, 2, 3, 4]);
// When // When
const unpackedPacket = unpackers[MessageTypes.Basic](data) const unpackedPacket = unpackers[MessageTypes.Basic](data);
// Then // Then
expect(unpackedPacket) expect(unpackedPacket);
expect(unpackedPacket).toMatchObject(data) expect(unpackedPacket).toMatchObject(data);
}) });

View File

@@ -1,11 +1,11 @@
import { MessageTypes } from '../../src/common' import { MessageTypes } from "../../src/common";
import { packers } from '../../src/mapping' import { packers } from "../../src/mapping";
test('Create a keepalive (0x0005) packet.', () => { test("Create a keepalive (0x0005) packet.", () => {
// When // When
const packedPacket = packers[MessageTypes.Keepalive]() const packedPacket = packers[MessageTypes.Keepalive]();
// Then // Then
const expectedResult = new Uint8Array([0x00, 0x05, 0x00, 0x00]) const expectedResult = new Uint8Array([0x00, 0x05, 0x00, 0x00]);
expect(packedPacket).toMatchObject(expectedResult) expect(packedPacket).toMatchObject(expectedResult);
}) });

View File

@@ -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 // Given
const messageType = new Uint8Array([0x12, 0x34]) const messageType = new Uint8Array([0x12, 0x34]);
const data = new Uint8Array([0x12, 0x34, 0x56, 0x78]) const data = new Uint8Array([0x12, 0x34, 0x56, 0x78]);
// When // When
const packedPacket = packOutgoingPacket({ const packedPacket = packOutgoingPacket({
messageType: messageType, messageType: messageType,
data: data data: data,
}) });
// Then // Then
const expectedResult = new Uint8Array([ const expectedResult = new Uint8Array([
@@ -18,12 +21,12 @@ test('Pack an outgoing packet.', () => {
// Data length // Data length
0x00, 0x04, 0x00, 0x04,
// Data // Data
0x12, 0x34, 0x56, 0x78 0x12, 0x34, 0x56, 0x78,
]) ]);
expect(packedPacket).toMatchObject(expectedResult) expect(packedPacket).toMatchObject(expectedResult);
}) });
test('Unpack an incoming packet.', () => { test("Unpack an incoming packet.", () => {
// Given // Given
const incomingPacket = new Uint8Array([ const incomingPacket = new Uint8Array([
// Message type // Message type
@@ -33,14 +36,16 @@ test('Unpack an incoming packet.', () => {
// Data length // Data length
0x00, 0x04, 0x00, 0x04,
// Data // Data
0x12, 0x34, 0x56, 0x78 0x12, 0x34, 0x56, 0x78,
]) ]);
// When // When
const unpackedResult = unpackIncomingPacket(incomingPacket) const unpackedResult = unpackIncomingPacket(incomingPacket);
// Then // Then
expect(unpackedResult.messageType).toBe(0x1234) expect(unpackedResult.messageType).toBe(0x1234);
expect(unpackedResult.senderId).toBe(0xaabbccdd) expect(unpackedResult.senderId).toBe(0xaabbccdd);
expect(unpackedResult.data).toMatchObject(new Uint8Array([0x12, 0x34, 0x56, 0x78])) expect(unpackedResult.data).toMatchObject(
}) new Uint8Array([0x12, 0x34, 0x56, 0x78]),
);
});

View File

@@ -1,14 +1,16 @@
import { MessageTypes } from '../../src/common' import { MessageTypes } from "../../src/common";
import { packers } from '../../src/mapping' import { packers } from "../../src/mapping";
test('Create a subscribe (0x0000) packet.', () => { test("Create a subscribe (0x0000) packet.", () => {
// Given // Given
const messageType = 0xabcd const messageType = 0xabcd;
// When // When
const packedPacket = packers[MessageTypes.Subscribe]({ messageType: messageType }) const packedPacket = packers[MessageTypes.Subscribe]({
messageType: messageType,
});
// Then // Then
const expectedResult = new Uint8Array([0x00, 0x00, 0x00, 0x02, 0xab, 0xcd]) const expectedResult = new Uint8Array([0x00, 0x00, 0x00, 0x02, 0xab, 0xcd]);
expect(packedPacket).toMatchObject(expectedResult) expect(packedPacket).toMatchObject(expectedResult);
}) });

View File

@@ -1,14 +1,16 @@
import { MessageTypes } from '../../src/common' import { MessageTypes } from "../../src/common";
import { packers } from '../../src/mapping' import { packers } from "../../src/mapping";
test('Create an unsubscribe (0xffff) packet.', () => { test("Create an unsubscribe (0xffff) packet.", () => {
// Given // Given
const messageType = 0xabcd const messageType = 0xabcd;
// When // When
const packedPacket = packers[MessageTypes.Unsubscribe]({ messageType: messageType }) const packedPacket = packers[MessageTypes.Unsubscribe]({
messageType: messageType,
});
// Then // Then
const expectedResult = new Uint8Array([0xff, 0xff, 0x00, 0x02, 0xab, 0xcd]) const expectedResult = new Uint8Array([0xff, 0xff, 0x00, 0x02, 0xab, 0xcd]);
expect(packedPacket).toMatchObject(expectedResult) expect(packedPacket).toMatchObject(expectedResult);
}) });

View File

@@ -1,46 +1,56 @@
import Color from 'color' import Color from "color";
import { MessageTypes } from '../../src/common' import { MessageTypes } from "../../src/common";
import { packers, unpackers } from '../../src/mapping' 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 // Given
const username = 'Butlersaurus' const username = "Butlersaurus";
const colour = Color('#FF4000') const colour = Color("#FF4000");
const clientId = 'Mercury' const clientId = "Mercury";
// When // When
const packedPacket = packers[MessageTypes.UserDataRequest]( const packedPacket = packers[MessageTypes.UserDataRequest](
{ {
username: username, username: username,
colour: colour, colour: colour,
clientId: clientId clientId: clientId,
}, },
KEY KEY,
) );
// Then // Then
// We can't check the contents of the data as it's encrypted with a random nonce. // We can't check the contents of the data as it's encrypted with a random nonce.
// Check the message type and length. // Check the message type and length.
expect(packedPacket.slice(0, 4)).toMatchObject(new Uint8Array([0x00, 0x02, 0x00, 0x3A])) expect(packedPacket.slice(0, 4)).toMatchObject(
new Uint8Array([0x00, 0x02, 0x00, 0x67]),
);
// Check the total length is as expected. // Check the total length is as expected.
expect(packedPacket.length).toBe(62) expect(packedPacket.length).toBe(107);
}) });
test('Parse a user data request (0x0002).', () => { test("Parse a user data request (0x0002).", () => {
// Given // Given
const data = new Uint8Array([0, 12, 66, 117, 116, 108, 101, 114, 115, 97, 117, 114, 117, 115, 255, 64, 0, 0, 7, 77, 101, 114, 99, 117, 114, 121]) const data = new Uint8Array([
const username = 'Butlersaurus' 0, 12, 66, 117, 116, 108, 101, 114, 115, 97, 117, 114, 117, 115, 0, 0, 0, 0,
const colour = Color('#FF4000') 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 64, 0, 0, 7, 77, 101,
const clientId = 'Mercury' 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 // When
const unpackedPacket = unpackers[MessageTypes.UserDataRequest](data) const unpackedPacket = unpackers[MessageTypes.UserDataRequest](data);
// Then // Then
expect(unpackedPacket.username).toBe(username) expect(unpackedPacket.username).toBe(username);
expect(unpackedPacket.colour).toMatchObject(colour) expect(unpackedPacket.colour).toMatchObject(colour);
expect(unpackedPacket.clientId).toBe(clientId) expect(unpackedPacket.clientId).toBe(clientId);
}) });

View File

@@ -1,46 +1,56 @@
import Color from 'color' import Color from "color";
import { MessageTypes } from '../../src/common' import { MessageTypes } from "../../src/common";
import { packers, unpackers } from '../../src/mapping' 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 // Given
const username = 'Butlersaurus' const username = "Butlersaurus";
const colour = Color('#FF4000') const colour = Color("#FF4000");
const clientId = 'Mercury' const clientId = "Mercury";
// When // When
const packedPacket = packers[MessageTypes.UserDataResponse]( const packedPacket = packers[MessageTypes.UserDataResponse](
{ {
username: username, username: username,
colour: colour, colour: colour,
clientId: clientId clientId: clientId,
}, },
KEY KEY,
) );
// Then // Then
// We can't check the contents of the data as it's encrypted with a random nonce. // We can't check the contents of the data as it's encrypted with a random nonce.
// Check the message type and length. // Check the message type and length.
expect(packedPacket.slice(0, 4)).toMatchObject(new Uint8Array([0x00, 0x03, 0x00, 0x3A])) expect(packedPacket.slice(0, 4)).toMatchObject(
new Uint8Array([0x00, 0x03, 0x00, 0x67]),
);
// Check the total length is as expected. // Check the total length is as expected.
expect(packedPacket.length).toBe(62) expect(packedPacket.length).toBe(107);
}) });
test('Parse a user data response (0x0003).', () => { test("Parse a user data response (0x0003).", () => {
// Given // Given
const data = new Uint8Array([0, 12, 66, 117, 116, 108, 101, 114, 115, 97, 117, 114, 117, 115, 255, 64, 0, 0, 7, 77, 101, 114, 99, 117, 114, 121]) const data = new Uint8Array([
const username = 'Butlersaurus' 0, 12, 66, 117, 116, 108, 101, 114, 115, 97, 117, 114, 117, 115, 0, 0, 0, 0,
const colour = Color('#FF4000') 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 64, 0, 0, 7, 77, 101,
const clientId = 'Mercury' 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 // When
const unpackedPacket = unpackers[MessageTypes.UserDataResponse](data) const unpackedPacket = unpackers[MessageTypes.UserDataResponse](data);
// Then // Then
expect(unpackedPacket.username).toBe(username) expect(unpackedPacket.username).toBe(username);
expect(unpackedPacket.colour).toMatchObject(colour) expect(unpackedPacket.colour).toMatchObject(colour);
expect(unpackedPacket.clientId).toBe(clientId) expect(unpackedPacket.clientId).toBe(clientId);
}) });

View File

@@ -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 // When
const result = numberToUint16BE(1234) const result = numberToUint16BE(1234);
// Then // Then
const expectedResult = new Uint8Array([0x04, 0xd2]) const expectedResult = new Uint8Array([0x04, 0xd2]);
expect(result).toMatchObject(expectedResult) expect(result).toMatchObject(expectedResult);
}) });
test('Test number conversion to Uint32 big endian buffer.', () => { test("Test number conversion to Uint32 big endian buffer.", () => {
// When // When
const result = numberToUint32BE(123456) const result = numberToUint32BE(123456);
// Then // Then
const expectedResult = new Uint8Array([0x00, 0x01, 0xE2, 0x40]) const expectedResult = new Uint8Array([0x00, 0x01, 0xe2, 0x40]);
expect(result).toMatchObject(expectedResult) expect(result).toMatchObject(expectedResult);
}) });

View File

@@ -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 // Given
const buffer = [0x30, 0x39] const buffer = [0x30, 0x39];
// When // When
const smartBuffer = SmartBuffer.from(buffer) const smartBuffer = SmartBuffer.from(buffer);
// Then // Then
expect(smartBuffer.readUInt16()).toBe(12345) expect(smartBuffer.readUInt16()).toBe(12345);
}) });
test('Read a UInt32.', () => { test("Read a UInt32.", () => {
// Given // Given
const buffer = [0x49, 0x96, 0x02, 0xD2] const buffer = [0x49, 0x96, 0x02, 0xd2];
// When // When
const smartBuffer = SmartBuffer.from(buffer) const smartBuffer = SmartBuffer.from(buffer);
// Then // Then
expect(smartBuffer.readUInt32()).toBe(1234567890) expect(smartBuffer.readUInt32()).toBe(1234567890);
}) });
test('Read a buffer.', () => { test("Read a buffer.", () => {
// Given // 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 // When
const smartBuffer = SmartBuffer.from(buffer) const smartBuffer = SmartBuffer.from(buffer);
// Then // Then
const result = smartBuffer.readBytes(4) const result = smartBuffer.readBytes(4);
expect(result).toMatchObject(new Uint8Array([0, 1, 2, 3])) expect(result).toMatchObject(new Uint8Array([0, 1, 2, 3]));
}) });
test('Read a UInt16 from an offset.', () => { test("Read a UInt16 from an offset.", () => {
// Given // Given
const buffer = [0x00, 0x00, 0x30, 0x39] const buffer = [0x00, 0x00, 0x30, 0x39];
// When // When
const smartBuffer = SmartBuffer.from(buffer) const smartBuffer = SmartBuffer.from(buffer);
smartBuffer.cursor = 2 smartBuffer.cursor = 2;
// Then // 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 // Given
const buffer = [0x00, 0x00, 0x49, 0x96, 0x02, 0xD2] const buffer = [0x00, 0x00, 0x49, 0x96, 0x02, 0xd2];
// When // When
const smartBuffer = SmartBuffer.from(buffer) const smartBuffer = SmartBuffer.from(buffer);
smartBuffer.cursor = 2 smartBuffer.cursor = 2;
// Then // 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 // 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 // When
const smartBuffer = SmartBuffer.from(buffer) const smartBuffer = SmartBuffer.from(buffer);
smartBuffer.cursor = 2 smartBuffer.cursor = 2;
// Then // 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 // Given
const smartBuffer = new SmartBuffer() const smartBuffer = new SmartBuffer();
// When // When
smartBuffer.writeUInt16(12345) smartBuffer.writeUInt16(12345);
// Then // 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 // Given
const smartBuffer = new SmartBuffer() const smartBuffer = new SmartBuffer();
// When // When
smartBuffer.writeUInt32(1234567890) smartBuffer.writeUInt32(1234567890);
// Then // 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 // Given
const smartBuffer = new SmartBuffer() const smartBuffer = new SmartBuffer();
// When // 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 // 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 // Given
const smartBuffer = new SmartBuffer() const smartBuffer = new SmartBuffer();
// When // When
smartBuffer.cursor = 2 smartBuffer.cursor = 2;
smartBuffer.writeUInt16(12345) smartBuffer.writeUInt16(12345);
// Then // 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 // Given
const smartBuffer = new SmartBuffer() const smartBuffer = new SmartBuffer();
// When // When
smartBuffer.cursor = 2 smartBuffer.cursor = 2;
smartBuffer.writeUInt32(1234567890) smartBuffer.writeUInt32(1234567890);
// Then // 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 // Given
const smartBuffer = new SmartBuffer() const smartBuffer = new SmartBuffer();
// When // When
smartBuffer.cursor = 2 smartBuffer.cursor = 2;
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 // 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 // Given
const buffer = new Uint8Array(4) const buffer = new Uint8Array(4);
// When // When
const smartBuffer = SmartBuffer.from(buffer) const smartBuffer = SmartBuffer.from(buffer);
// Then // Then
smartBuffer.readUInt16() smartBuffer.readUInt16();
expect(smartBuffer.cursor).toBe(2) expect(smartBuffer.cursor).toBe(2);
}) });
test('Cursor is correctly incremented after reading a UInt32.', () => { test("Cursor is correctly incremented after reading a UInt32.", () => {
// Given // Given
const buffer = new Uint8Array(4) const buffer = new Uint8Array(4);
// When // When
const smartBuffer = SmartBuffer.from(buffer) const smartBuffer = SmartBuffer.from(buffer);
// Then // Then
smartBuffer.readUInt32() smartBuffer.readUInt32();
expect(smartBuffer.cursor).toBe(4) expect(smartBuffer.cursor).toBe(4);
}) });
test('Cursor is correctly incremented after reading a buffer.', () => { test("Cursor is correctly incremented after reading a buffer.", () => {
// Given // Given
const buffer = new Uint8Array(8) const buffer = new Uint8Array(8);
// When // When
const smartBuffer = SmartBuffer.from(buffer) const smartBuffer = SmartBuffer.from(buffer);
// Then // Then
smartBuffer.readBytes(4) smartBuffer.readBytes(4);
expect(smartBuffer.cursor).toBe(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 // Given
const smartBuffer = new SmartBuffer() const smartBuffer = new SmartBuffer();
// When // When
smartBuffer.writeUInt16(12345) smartBuffer.writeUInt16(12345);
// Then // 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 // Given
const smartBuffer = new SmartBuffer() const smartBuffer = new SmartBuffer();
// When // When
smartBuffer.writeUInt32(1234567890) smartBuffer.writeUInt32(1234567890);
// Then // 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 // Given
const smartBuffer = new SmartBuffer() const smartBuffer = new SmartBuffer();
// When // 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 // 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 // When
const smartBuffer = new SmartBuffer() const smartBuffer = new SmartBuffer();
// Then // Then
expect(() => { expect(() => {
smartBuffer.cursor = -1 smartBuffer.cursor = -1;
}).toThrow(RangeError) }).toThrow(RangeError);
}) });
test('Pad some data.', () => { test("Pad some data.", () => {
// Given // Given
const smartBuffer = new SmartBuffer() const smartBuffer = new SmartBuffer();
// When // When
smartBuffer.pad(10) smartBuffer.pad(10);
// Then // Then
expect(smartBuffer.length).toBe(10) expect(smartBuffer.length).toBe(10);
}) });

View File

@@ -1,4 +1,4 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"exclude": [] "exclude": []
} }

View File

@@ -1,19 +1,19 @@
{ {
"compilerOptions": { "compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */ "target": "esnext",
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ "module": "node20",
"module": "commonjs", /* Specify what module code is generated. */ "moduleResolution": "node16",
"rootDir": "src", /* Specify the root folder within your source files. */ "rootDir": "src",
"sourceMap": true, /* Create source map files for emitted JavaScript files. */ "outDir": "dist",
"outDir": "dist", /* Specify an output folder for all emitted files. */ "sourceMap": true,
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ "declaration": true,
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ "strict": true,
"strict": true, /* Enable all strict type-checking options. */ "esModuleInterop": true,
"skipLibCheck": true, /* Skip type checking all .d.ts files. */ "forceConsistentCasingInFileNames": true,
"declaration": true "skipLibCheck": true,
"isolatedModules": true,
"noEmitOnError": true
}, },
"exclude": [ "include": ["src/**/*"],
"tests", "exclude": ["tests", "dist"]
"dist"
]
} }