node-osc 11.2.1 → 11.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/nodejs.yml +1 -1
- package/README.md +3 -2
- package/agent.md +330 -0
- package/dist/lib/Server.js +7 -12
- package/dist/lib/internal/decode.js +3 -1
- package/dist/lib/osc.js +29 -1
- package/dist/test/lib/osc.js +29 -1
- package/dist/test/test-bundle.js +13 -12
- package/dist/test/test-client.js +49 -40
- package/dist/test/test-decode.js +35 -0
- package/dist/test/test-e2e.js +9 -9
- package/dist/test/test-encode-decode.js +96 -0
- package/dist/test/test-error-handling.js +14 -13
- package/dist/test/test-message.js +169 -119
- package/dist/test/test-osc-internal.js +151 -0
- package/dist/test/test-promises.js +71 -29
- package/dist/test/test-server.js +19 -16
- package/docs/README.md +81 -0
- package/examples/README.md +3 -1
- package/lib/Server.mjs +7 -12
- package/lib/internal/decode.mjs +3 -1
- package/lib/osc.mjs +29 -1
- package/package.json +2 -2
- package/test/test-bundle.mjs +14 -13
- package/test/test-client.mjs +50 -41
- package/test/test-decode.mjs +35 -0
- package/test/test-e2e.mjs +10 -10
- package/test/test-encode-decode.mjs +96 -0
- package/test/test-error-handling.mjs +15 -14
- package/test/test-message.mjs +171 -122
- package/test/test-osc-internal.mjs +151 -0
- package/test/test-promises.mjs +72 -30
- package/test/test-server.mjs +20 -17
- package/types/Server.d.mts.map +1 -1
- package/types/internal/decode.d.mts.map +1 -1
- package/types/osc.d.mts.map +1 -1
- package/dist/test/test-getPort.js +0 -20
- package/dist/test/util.js +0 -34
- package/test/test-getPort.mjs +0 -18
- package/test/util.mjs +0 -34
package/README.md
CHANGED
|
@@ -47,8 +47,9 @@ server.on('message', (msg) => {
|
|
|
47
47
|
|
|
48
48
|
## Documentation
|
|
49
49
|
|
|
50
|
-
-
|
|
51
|
-
-
|
|
50
|
+
- 📂 **[Documentation Hub](./docs/)** - Complete documentation with navigation guide
|
|
51
|
+
- 📚 **[API Reference](./docs/API.md)** - Complete API reference generated from source code
|
|
52
|
+
- 📘 **[Usage Guide](./docs/GUIDE.md)** - Best practices, error handling, and troubleshooting
|
|
52
53
|
- 📖 **[Examples](./examples/)** - Working examples for various use cases
|
|
53
54
|
|
|
54
55
|
## Compatibility
|
package/agent.md
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# Agent Instructions for node-osc
|
|
2
|
+
|
|
3
|
+
This document provides context and instructions for AI agents (GitHub Copilot, Cursor, and other agentic platforms) working on the node-osc project.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
**node-osc** is a Node.js library for sending and receiving [Open Sound Control (OSC)](http://opensoundcontrol.org) messages over UDP. It provides a simple, no-frills API inspired by pyOSC.
|
|
8
|
+
|
|
9
|
+
### Key Features
|
|
10
|
+
- Send and receive OSC messages and bundles
|
|
11
|
+
- Dual module support (ESM and CommonJS)
|
|
12
|
+
- Both callback and async/await APIs
|
|
13
|
+
- TypeScript type definitions generated from JSDoc
|
|
14
|
+
- Well-tested with comprehensive test coverage
|
|
15
|
+
- Supports Node.js 20, 22, and 24
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
### Core Components
|
|
20
|
+
|
|
21
|
+
1. **Server** (`lib/Server.mjs`) - EventEmitter-based OSC server for receiving messages
|
|
22
|
+
- Listens on UDP socket
|
|
23
|
+
- Emits events: `listening`, `message`, `bundle`, `error`, and address-specific events
|
|
24
|
+
|
|
25
|
+
2. **Client** (`lib/Client.mjs`) - OSC client for sending messages
|
|
26
|
+
- Sends messages over UDP
|
|
27
|
+
- Supports both callbacks and async/await
|
|
28
|
+
|
|
29
|
+
3. **Message** (`lib/Message.mjs`) - Represents a single OSC message
|
|
30
|
+
- Contains address (string) and arguments (array)
|
|
31
|
+
- Can append additional arguments
|
|
32
|
+
|
|
33
|
+
4. **Bundle** (`lib/Bundle.mjs`) - Represents a collection of OSC messages
|
|
34
|
+
- Contains timetag and array of elements (messages or nested bundles)
|
|
35
|
+
- Used for sending multiple messages together
|
|
36
|
+
|
|
37
|
+
5. **Low-level encoding/decoding** (`lib/osc.mjs`, `lib/internal/`) - Binary OSC protocol implementation
|
|
38
|
+
- `encode()` - Converts Message/Bundle objects to binary Buffer
|
|
39
|
+
- `decode()` - Parses binary Buffer into Message/Bundle objects
|
|
40
|
+
|
|
41
|
+
### Module System
|
|
42
|
+
|
|
43
|
+
The project uses **ESM as the source format** but provides **dual ESM/CommonJS support**:
|
|
44
|
+
- Source files: `lib/**/*.mjs` (ESM)
|
|
45
|
+
- Built CommonJS files: `dist/lib/**/*.js` (transpiled via Rollup)
|
|
46
|
+
- TypeScript definitions: `types/index.d.mts` (generated from JSDoc)
|
|
47
|
+
|
|
48
|
+
**Important:** The single `.d.mts` type definition file works for both ESM and CommonJS consumers.
|
|
49
|
+
|
|
50
|
+
### Package Exports
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"exports": {
|
|
55
|
+
"types": "./types/index.d.mts",
|
|
56
|
+
"require": "./dist/lib/index.js",
|
|
57
|
+
"import": "./lib/index.mjs",
|
|
58
|
+
"default": "./lib/index.mjs"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Development Workflow
|
|
64
|
+
|
|
65
|
+
### Essential Commands
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Install dependencies
|
|
69
|
+
npm install
|
|
70
|
+
|
|
71
|
+
# Run linter (ESLint)
|
|
72
|
+
npm run lint
|
|
73
|
+
|
|
74
|
+
# Build the project (clean, transpile to CJS, generate types)
|
|
75
|
+
npm run build
|
|
76
|
+
|
|
77
|
+
# Run all tests (lint + build + ESM tests + CJS tests)
|
|
78
|
+
npm test
|
|
79
|
+
|
|
80
|
+
# Run only ESM tests
|
|
81
|
+
npm run test:esm
|
|
82
|
+
|
|
83
|
+
# Run only CJS tests
|
|
84
|
+
npm run test:cjs
|
|
85
|
+
|
|
86
|
+
# Generate API documentation from JSDoc
|
|
87
|
+
npm run docs
|
|
88
|
+
|
|
89
|
+
# Clean build artifacts
|
|
90
|
+
npm run clean
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Testing Strategy
|
|
94
|
+
|
|
95
|
+
- Tests are written in ESM format in `test/test-*.mjs`
|
|
96
|
+
- Tests are run against both ESM source (`lib/`) and transpiled CJS (`dist/`)
|
|
97
|
+
- Uses `tap` test framework
|
|
98
|
+
- Test utilities in `test/util.mjs` provide helpers like `getPort()` for getting available ports
|
|
99
|
+
- Always run `npm run build` before running CJS tests
|
|
100
|
+
- **100% test coverage is required** - All lines, branches, functions, and statements must be covered
|
|
101
|
+
|
|
102
|
+
### Build Process
|
|
103
|
+
|
|
104
|
+
1. **Clean**: Removes `dist/` and `types/` directories
|
|
105
|
+
2. **Rollup**: Transpiles ESM to CommonJS in `dist/` directory
|
|
106
|
+
3. **TypeScript**: Generates type definitions from JSDoc in `types/` directory
|
|
107
|
+
|
|
108
|
+
The build is automatically run before publishing (`prepublishOnly` script).
|
|
109
|
+
|
|
110
|
+
## Coding Standards
|
|
111
|
+
|
|
112
|
+
### JavaScript Style
|
|
113
|
+
|
|
114
|
+
- **ES Modules**: Use ESM syntax (`import`/`export`)
|
|
115
|
+
- **File extension**: Use `.mjs` for ESM files
|
|
116
|
+
- **Linting**: Follow ESLint rules in `eslint.config.mjs`
|
|
117
|
+
- **Modern JavaScript**: Use async/await, arrow functions, destructuring
|
|
118
|
+
- **Error handling**: Always handle errors in async operations
|
|
119
|
+
|
|
120
|
+
### Documentation
|
|
121
|
+
|
|
122
|
+
- **JSDoc comments**: All public APIs must have JSDoc comments
|
|
123
|
+
- **Type annotations**: Use JSDoc types for TypeScript generation
|
|
124
|
+
- **Examples**: Include code examples in JSDoc comments
|
|
125
|
+
- **Auto-generated docs**: Run `npm run docs` after changing JSDoc comments
|
|
126
|
+
|
|
127
|
+
Example JSDoc pattern:
|
|
128
|
+
```javascript
|
|
129
|
+
/**
|
|
130
|
+
* Sends an OSC message or bundle.
|
|
131
|
+
*
|
|
132
|
+
* @param {Message|Bundle|string} msg - The message, bundle, or address to send.
|
|
133
|
+
* @param {...*} args - Additional arguments (used when first param is a string address).
|
|
134
|
+
* @returns {Promise<void>}
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* await client.send('/test', 123);
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* const message = new Message('/test', 123);
|
|
141
|
+
* await client.send(message);
|
|
142
|
+
*/
|
|
143
|
+
async send(msg, ...args) { ... }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Type System
|
|
147
|
+
|
|
148
|
+
- TypeScript definitions are **generated** from JSDoc comments
|
|
149
|
+
- Do not manually edit `types/*.d.mts` files
|
|
150
|
+
- Update JSDoc comments in source files instead
|
|
151
|
+
- Run `npm run build:types` to regenerate types
|
|
152
|
+
|
|
153
|
+
### Naming Conventions
|
|
154
|
+
|
|
155
|
+
- **Classes**: PascalCase (e.g., `Client`, `Server`, `Message`, `Bundle`)
|
|
156
|
+
- **Functions**: camelCase (e.g., `encode`, `decode`, `toBuffer`)
|
|
157
|
+
- **Private functions**: Prefix with underscore (e.g., `_oscType`)
|
|
158
|
+
- **Constants**: UPPER_SNAKE_CASE for true constants
|
|
159
|
+
- **Files**: Match class names or use descriptive kebab-case
|
|
160
|
+
|
|
161
|
+
### Dual Module Support Patterns
|
|
162
|
+
|
|
163
|
+
When writing code that needs to work in both ESM and CJS:
|
|
164
|
+
|
|
165
|
+
1. **Imports**: Use ESM imports in source (Rollup handles conversion)
|
|
166
|
+
2. **Exports**: Use named exports for all public APIs
|
|
167
|
+
3. **Testing**: Test both ESM and CJS builds
|
|
168
|
+
4. **Package imports**: Use `#decode` subpath import for internal modules (defined in `package.json` imports field)
|
|
169
|
+
|
|
170
|
+
## Important Files and Directories
|
|
171
|
+
|
|
172
|
+
### Source Files
|
|
173
|
+
- `lib/` - ESM source code (the canonical source)
|
|
174
|
+
- `lib/index.mjs` - Main entry point, exports all public APIs
|
|
175
|
+
- `lib/internal/` - Internal utilities (decode, encode, helpers)
|
|
176
|
+
- `lib/osc.mjs` - Low-level encode/decode functions
|
|
177
|
+
|
|
178
|
+
### Build Artifacts
|
|
179
|
+
- `dist/` - Transpiled CommonJS files (generated, do not edit)
|
|
180
|
+
- `types/` - TypeScript type definitions (generated, do not edit)
|
|
181
|
+
|
|
182
|
+
### Tests
|
|
183
|
+
- `test/test-*.mjs` - Test files using tap framework
|
|
184
|
+
- `test/util.mjs` - Test utilities and helpers
|
|
185
|
+
- `test/fixtures/` - Test data and fixtures
|
|
186
|
+
|
|
187
|
+
### Documentation
|
|
188
|
+
- `README.md` - Main documentation with quick start guide
|
|
189
|
+
- `docs/API.md` - Auto-generated API reference (do not edit manually)
|
|
190
|
+
- `docs/GUIDE.md` - Best practices, error handling, troubleshooting
|
|
191
|
+
- `examples/` - Working example code for various use cases
|
|
192
|
+
|
|
193
|
+
### Configuration
|
|
194
|
+
- `package.json` - Package configuration, scripts, exports
|
|
195
|
+
- `eslint.config.mjs` - ESLint configuration
|
|
196
|
+
- `rollup.config.mjs` - Rollup build configuration (ESM to CJS)
|
|
197
|
+
- `tsconfig.json` - TypeScript compiler options for type generation
|
|
198
|
+
- `jsdoc.json` - JSDoc configuration for documentation generation
|
|
199
|
+
|
|
200
|
+
## Making Changes
|
|
201
|
+
|
|
202
|
+
### Adding a New Feature
|
|
203
|
+
|
|
204
|
+
1. **Write ESM source** in `lib/`
|
|
205
|
+
2. **Add JSDoc comments** with types and examples
|
|
206
|
+
3. **Export** from `lib/index.mjs` if it's a public API
|
|
207
|
+
4. **Write tests** in `test/test-*.mjs` - **must achieve 100% coverage** (lines, branches, functions, statements)
|
|
208
|
+
5. **Run tests**: `npm test` (tests both ESM and CJS)
|
|
209
|
+
6. **Update docs**: `npm run docs` to regenerate API.md
|
|
210
|
+
7. **Update README.md** if adding user-facing functionality
|
|
211
|
+
|
|
212
|
+
### Fixing a Bug
|
|
213
|
+
|
|
214
|
+
1. **Write a failing test** that demonstrates the bug
|
|
215
|
+
2. **Fix the bug** in the ESM source files
|
|
216
|
+
3. **Run tests**: `npm test` to verify fix works in both ESM and CJS
|
|
217
|
+
4. **Verify coverage**: Ensure 100% test coverage is maintained
|
|
218
|
+
5. **Check no regressions**: Ensure all tests pass
|
|
219
|
+
|
|
220
|
+
### Modifying the API
|
|
221
|
+
|
|
222
|
+
1. **Update JSDoc** in source files
|
|
223
|
+
2. **Regenerate types**: `npm run build:types`
|
|
224
|
+
3. **Update tests** to cover new behavior - **must maintain 100% coverage**
|
|
225
|
+
4. **Regenerate docs**: `npm run docs`
|
|
226
|
+
5. **Update README.md** and `docs/GUIDE.md` as appropriate
|
|
227
|
+
|
|
228
|
+
## Common Patterns
|
|
229
|
+
|
|
230
|
+
### Creating a Server
|
|
231
|
+
```javascript
|
|
232
|
+
import { Server } from 'node-osc';
|
|
233
|
+
|
|
234
|
+
const server = new Server(3333, '0.0.0.0');
|
|
235
|
+
server.on('message', (msg, rinfo) => {
|
|
236
|
+
console.log('Message:', msg);
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Creating a Client
|
|
241
|
+
```javascript
|
|
242
|
+
import { Client } from 'node-osc';
|
|
243
|
+
|
|
244
|
+
const client = new Client('127.0.0.1', 3333);
|
|
245
|
+
await client.send('/test', 123);
|
|
246
|
+
await client.close();
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Working with Bundles
|
|
250
|
+
```javascript
|
|
251
|
+
import { Bundle } from 'node-osc';
|
|
252
|
+
|
|
253
|
+
const bundle = new Bundle(['/one', 1], ['/two', 2]);
|
|
254
|
+
await client.send(bundle);
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Low-level Encoding/Decoding
|
|
258
|
+
```javascript
|
|
259
|
+
import { Message, encode, decode } from 'node-osc';
|
|
260
|
+
|
|
261
|
+
const message = new Message('/test', 123);
|
|
262
|
+
const buffer = encode(message);
|
|
263
|
+
const decoded = decode(buffer);
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Troubleshooting
|
|
267
|
+
|
|
268
|
+
### Build Issues
|
|
269
|
+
|
|
270
|
+
- **"Cannot find module"**: Run `npm install` to install dependencies
|
|
271
|
+
- **Type generation fails**: Check JSDoc syntax in source files
|
|
272
|
+
- **CJS tests fail but ESM pass**: Run `npm run build` before testing
|
|
273
|
+
|
|
274
|
+
### Test Issues
|
|
275
|
+
|
|
276
|
+
- **Port conflicts**: Tests use dynamic port allocation via `getPort()` utility
|
|
277
|
+
- **Timing issues**: Use async/await and proper event handling
|
|
278
|
+
- **ESM/CJS differences**: Ensure code works in both environments
|
|
279
|
+
|
|
280
|
+
### Module Resolution
|
|
281
|
+
|
|
282
|
+
- **Dual package hazard**: The package exports both ESM and CJS - don't mix them
|
|
283
|
+
- **Type imports**: TypeScript consumers get types automatically from `types/index.d.mts`
|
|
284
|
+
- **Internal imports**: Use `#decode` subpath for internal modules
|
|
285
|
+
|
|
286
|
+
## Dependencies
|
|
287
|
+
|
|
288
|
+
### Runtime Dependencies
|
|
289
|
+
- **None** - This is a zero-dependency library for production use
|
|
290
|
+
|
|
291
|
+
### Development Dependencies
|
|
292
|
+
- **eslint** - Code linting
|
|
293
|
+
- **tap** - Test framework
|
|
294
|
+
- **rollup** - Module bundler for ESM → CJS transpilation
|
|
295
|
+
- **typescript** - Type definition generation from JSDoc
|
|
296
|
+
- **jsdoc** - Documentation generation
|
|
297
|
+
- **globals** - ESLint globals configuration
|
|
298
|
+
|
|
299
|
+
## OSC Protocol Knowledge
|
|
300
|
+
|
|
301
|
+
When working with OSC message encoding/decoding:
|
|
302
|
+
|
|
303
|
+
- OSC addresses start with `/` (e.g., `/oscillator/frequency`)
|
|
304
|
+
- OSC types: integer (i), float (f), string (s), blob (b), time tag (t)
|
|
305
|
+
- Messages are null-padded to 4-byte boundaries
|
|
306
|
+
- Bundles have time tags (when to execute) and can contain nested bundles
|
|
307
|
+
- See [OSC Specification](http://opensoundcontrol.org/spec-1_0) for protocol details
|
|
308
|
+
|
|
309
|
+
## Security Considerations
|
|
310
|
+
|
|
311
|
+
- Always validate input data when decoding OSC messages
|
|
312
|
+
- Be careful with buffer operations to avoid out-of-bounds access
|
|
313
|
+
- Limit message and bundle sizes to prevent DoS attacks
|
|
314
|
+
- Sanitize OSC addresses before using them as event names
|
|
315
|
+
- Handle malformed OSC data gracefully (emit errors, don't crash)
|
|
316
|
+
|
|
317
|
+
## License
|
|
318
|
+
|
|
319
|
+
This project uses the Apache-2.0 license. When contributing code:
|
|
320
|
+
- Ensure all new code is compatible with Apache-2.0
|
|
321
|
+
- Do not introduce dependencies with incompatible licenses
|
|
322
|
+
- Include proper attribution for any third-party code
|
|
323
|
+
|
|
324
|
+
## Getting Help
|
|
325
|
+
|
|
326
|
+
- **API Documentation**: See `docs/API.md`
|
|
327
|
+
- **Usage Guide**: See `docs/GUIDE.md`
|
|
328
|
+
- **Examples**: See `examples/` directory
|
|
329
|
+
- **Issues**: Check existing GitHub issues for similar problems
|
|
330
|
+
- **OSC Protocol**: Refer to http://opensoundcontrol.org for protocol details
|
package/dist/lib/Server.js
CHANGED
|
@@ -91,18 +91,13 @@ class Server extends node_events.EventEmitter {
|
|
|
91
91
|
});
|
|
92
92
|
this._sock.bind(port, host);
|
|
93
93
|
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
// For promise support, still emit the event but don't require a callback
|
|
102
|
-
this._sock.on('listening', () => {
|
|
103
|
-
this.emit('listening');
|
|
104
|
-
});
|
|
105
|
-
}
|
|
94
|
+
// Update port and emit listening event when socket is ready
|
|
95
|
+
this._sock.on('listening', () => {
|
|
96
|
+
// Update port with actual bound port (important when using port 0)
|
|
97
|
+
this.port = this._sock.address().port;
|
|
98
|
+
this.emit('listening');
|
|
99
|
+
if (cb) cb();
|
|
100
|
+
});
|
|
106
101
|
|
|
107
102
|
this._sock.on('message', (msg, rinfo) => {
|
|
108
103
|
try {
|
|
@@ -5,7 +5,8 @@ var osc = require('../osc.js');
|
|
|
5
5
|
function sanitizeMessage(decoded) {
|
|
6
6
|
const message = [];
|
|
7
7
|
message.push(decoded.address);
|
|
8
|
-
decoded.args
|
|
8
|
+
const args = decoded.args ?? [];
|
|
9
|
+
args.forEach(arg => {
|
|
9
10
|
message.push(arg.value);
|
|
10
11
|
});
|
|
11
12
|
return message;
|
|
@@ -15,6 +16,7 @@ function sanitizeBundle(decoded) {
|
|
|
15
16
|
decoded.elements = decoded.elements.map(element => {
|
|
16
17
|
if (element.oscType === 'bundle') return sanitizeBundle(element);
|
|
17
18
|
else if (element.oscType === 'message') return sanitizeMessage(element);
|
|
19
|
+
throw new Error('Malformed Packet');
|
|
18
20
|
});
|
|
19
21
|
return decoded;
|
|
20
22
|
}
|
package/dist/lib/osc.js
CHANGED
|
@@ -18,6 +18,9 @@ function readString(buffer, offset) {
|
|
|
18
18
|
while (end < buffer.length && buffer[end] !== 0) {
|
|
19
19
|
end++;
|
|
20
20
|
}
|
|
21
|
+
if (end >= buffer.length) {
|
|
22
|
+
throw new Error('Malformed Packet: Missing null terminator for string');
|
|
23
|
+
}
|
|
21
24
|
const str = buffer.subarray(offset, end).toString('utf8');
|
|
22
25
|
// Find next 4-byte boundary
|
|
23
26
|
const paddedLength = Math.ceil((end - offset + 1) / 4) * 4;
|
|
@@ -31,6 +34,9 @@ function writeInt32(value) {
|
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
function readInt32(buffer, offset) {
|
|
37
|
+
if (offset + 4 > buffer.length) {
|
|
38
|
+
throw new Error('Malformed Packet: Not enough bytes for int32');
|
|
39
|
+
}
|
|
34
40
|
const value = buffer.readInt32BE(offset);
|
|
35
41
|
return { value, offset: offset + 4 };
|
|
36
42
|
}
|
|
@@ -42,6 +48,9 @@ function writeFloat32(value) {
|
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
function readFloat32(buffer, offset) {
|
|
51
|
+
if (offset + 4 > buffer.length) {
|
|
52
|
+
throw new Error('Malformed Packet: Not enough bytes for float32');
|
|
53
|
+
}
|
|
45
54
|
const value = buffer.readFloatBE(offset);
|
|
46
55
|
return { value, offset: offset + 4 };
|
|
47
56
|
}
|
|
@@ -57,9 +66,18 @@ function writeBlob(value) {
|
|
|
57
66
|
function readBlob(buffer, offset) {
|
|
58
67
|
const lengthResult = readInt32(buffer, offset);
|
|
59
68
|
const length = lengthResult.value;
|
|
69
|
+
if (length < 0) {
|
|
70
|
+
throw new Error('Malformed Packet: Invalid blob length');
|
|
71
|
+
}
|
|
72
|
+
if (lengthResult.offset + length > buffer.length) {
|
|
73
|
+
throw new Error('Malformed Packet: Not enough bytes for blob');
|
|
74
|
+
}
|
|
60
75
|
const data = buffer.subarray(lengthResult.offset, lengthResult.offset + length);
|
|
61
76
|
const padding = 4 - (length % 4);
|
|
62
77
|
const nextOffset = lengthResult.offset + length + (padding === 4 ? 0 : padding);
|
|
78
|
+
if (nextOffset > buffer.length) {
|
|
79
|
+
throw new Error('Malformed Packet: Not enough bytes for blob padding');
|
|
80
|
+
}
|
|
63
81
|
return { value: data, offset: nextOffset };
|
|
64
82
|
}
|
|
65
83
|
|
|
@@ -67,7 +85,11 @@ function writeTimeTag(value) {
|
|
|
67
85
|
// For now, treat timetag as a double (8 bytes)
|
|
68
86
|
// OSC timetag is 64-bit: 32-bit seconds since 1900, 32-bit fractional
|
|
69
87
|
const buffer = node_buffer.Buffer.alloc(8);
|
|
70
|
-
if (
|
|
88
|
+
if (value === 0 || value === null || value === undefined) {
|
|
89
|
+
// Immediate execution
|
|
90
|
+
buffer.writeUInt32BE(0, 0);
|
|
91
|
+
buffer.writeUInt32BE(1, 4);
|
|
92
|
+
} else if (typeof value === 'number') {
|
|
71
93
|
// Convert to OSC timetag format
|
|
72
94
|
const seconds = Math.floor(value);
|
|
73
95
|
const fraction = Math.floor((value - seconds) * 0x100000000);
|
|
@@ -82,6 +104,9 @@ function writeTimeTag(value) {
|
|
|
82
104
|
}
|
|
83
105
|
|
|
84
106
|
function readTimeTag(buffer, offset) {
|
|
107
|
+
if (offset + 8 > buffer.length) {
|
|
108
|
+
throw new Error('Malformed Packet: Not enough bytes for timetag');
|
|
109
|
+
}
|
|
85
110
|
const seconds = buffer.readUInt32BE(offset);
|
|
86
111
|
const fraction = buffer.readUInt32BE(offset + 4);
|
|
87
112
|
|
|
@@ -377,6 +402,9 @@ function decodeBundleFromBuffer(buffer) {
|
|
|
377
402
|
const sizeResult = readInt32(buffer, offset);
|
|
378
403
|
const size = sizeResult.value;
|
|
379
404
|
offset = sizeResult.offset;
|
|
405
|
+
if (size <= 0 || offset + size > buffer.length) {
|
|
406
|
+
throw new Error('Malformed Packet');
|
|
407
|
+
}
|
|
380
408
|
|
|
381
409
|
// Read element data
|
|
382
410
|
const elementBuffer = buffer.subarray(offset, offset + size);
|
package/dist/test/lib/osc.js
CHANGED
|
@@ -18,6 +18,9 @@ function readString(buffer, offset) {
|
|
|
18
18
|
while (end < buffer.length && buffer[end] !== 0) {
|
|
19
19
|
end++;
|
|
20
20
|
}
|
|
21
|
+
if (end >= buffer.length) {
|
|
22
|
+
throw new Error('Malformed Packet: Missing null terminator for string');
|
|
23
|
+
}
|
|
21
24
|
const str = buffer.subarray(offset, end).toString('utf8');
|
|
22
25
|
// Find next 4-byte boundary
|
|
23
26
|
const paddedLength = Math.ceil((end - offset + 1) / 4) * 4;
|
|
@@ -31,6 +34,9 @@ function writeInt32(value) {
|
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
function readInt32(buffer, offset) {
|
|
37
|
+
if (offset + 4 > buffer.length) {
|
|
38
|
+
throw new Error('Malformed Packet: Not enough bytes for int32');
|
|
39
|
+
}
|
|
34
40
|
const value = buffer.readInt32BE(offset);
|
|
35
41
|
return { value, offset: offset + 4 };
|
|
36
42
|
}
|
|
@@ -42,6 +48,9 @@ function writeFloat32(value) {
|
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
function readFloat32(buffer, offset) {
|
|
51
|
+
if (offset + 4 > buffer.length) {
|
|
52
|
+
throw new Error('Malformed Packet: Not enough bytes for float32');
|
|
53
|
+
}
|
|
45
54
|
const value = buffer.readFloatBE(offset);
|
|
46
55
|
return { value, offset: offset + 4 };
|
|
47
56
|
}
|
|
@@ -57,9 +66,18 @@ function writeBlob(value) {
|
|
|
57
66
|
function readBlob(buffer, offset) {
|
|
58
67
|
const lengthResult = readInt32(buffer, offset);
|
|
59
68
|
const length = lengthResult.value;
|
|
69
|
+
if (length < 0) {
|
|
70
|
+
throw new Error('Malformed Packet: Invalid blob length');
|
|
71
|
+
}
|
|
72
|
+
if (lengthResult.offset + length > buffer.length) {
|
|
73
|
+
throw new Error('Malformed Packet: Not enough bytes for blob');
|
|
74
|
+
}
|
|
60
75
|
const data = buffer.subarray(lengthResult.offset, lengthResult.offset + length);
|
|
61
76
|
const padding = 4 - (length % 4);
|
|
62
77
|
const nextOffset = lengthResult.offset + length + (padding === 4 ? 0 : padding);
|
|
78
|
+
if (nextOffset > buffer.length) {
|
|
79
|
+
throw new Error('Malformed Packet: Not enough bytes for blob padding');
|
|
80
|
+
}
|
|
63
81
|
return { value: data, offset: nextOffset };
|
|
64
82
|
}
|
|
65
83
|
|
|
@@ -67,7 +85,11 @@ function writeTimeTag(value) {
|
|
|
67
85
|
// For now, treat timetag as a double (8 bytes)
|
|
68
86
|
// OSC timetag is 64-bit: 32-bit seconds since 1900, 32-bit fractional
|
|
69
87
|
const buffer = node_buffer.Buffer.alloc(8);
|
|
70
|
-
if (
|
|
88
|
+
if (value === 0 || value === null || value === undefined) {
|
|
89
|
+
// Immediate execution
|
|
90
|
+
buffer.writeUInt32BE(0, 0);
|
|
91
|
+
buffer.writeUInt32BE(1, 4);
|
|
92
|
+
} else if (typeof value === 'number') {
|
|
71
93
|
// Convert to OSC timetag format
|
|
72
94
|
const seconds = Math.floor(value);
|
|
73
95
|
const fraction = Math.floor((value - seconds) * 0x100000000);
|
|
@@ -82,6 +104,9 @@ function writeTimeTag(value) {
|
|
|
82
104
|
}
|
|
83
105
|
|
|
84
106
|
function readTimeTag(buffer, offset) {
|
|
107
|
+
if (offset + 8 > buffer.length) {
|
|
108
|
+
throw new Error('Malformed Packet: Not enough bytes for timetag');
|
|
109
|
+
}
|
|
85
110
|
const seconds = buffer.readUInt32BE(offset);
|
|
86
111
|
const fraction = buffer.readUInt32BE(offset + 4);
|
|
87
112
|
|
|
@@ -377,6 +402,9 @@ function decodeBundleFromBuffer(buffer) {
|
|
|
377
402
|
const sizeResult = readInt32(buffer, offset);
|
|
378
403
|
const size = sizeResult.value;
|
|
379
404
|
offset = sizeResult.offset;
|
|
405
|
+
if (size <= 0 || offset + size > buffer.length) {
|
|
406
|
+
throw new Error('Malformed Packet');
|
|
407
|
+
}
|
|
380
408
|
|
|
381
409
|
// Read element data
|
|
382
410
|
const elementBuffer = buffer.subarray(offset, offset + size);
|
package/dist/test/test-bundle.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var node_events = require('node:events');
|
|
3
4
|
var tap = require('tap');
|
|
4
5
|
var nodeOsc = require('node-osc');
|
|
5
|
-
var util = require('./util.js');
|
|
6
6
|
|
|
7
|
-
tap.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const client = new nodeOsc.Client('127.0.0.1', t.context.port);
|
|
7
|
+
tap.test('bundle: verbose bundle', async (t) => {
|
|
8
|
+
const server = new nodeOsc.Server(0, '127.0.0.1');
|
|
9
|
+
await node_events.once(server, 'listening');
|
|
10
|
+
const client = new nodeOsc.Client('127.0.0.1', server.port);
|
|
12
11
|
|
|
13
12
|
t.plan(2);
|
|
14
13
|
|
|
@@ -35,9 +34,10 @@ tap.test('bundle: verbose bundle', (t) => {
|
|
|
35
34
|
}));
|
|
36
35
|
});
|
|
37
36
|
|
|
38
|
-
tap.test('bundle: array syntax', (t) => {
|
|
39
|
-
const server = new nodeOsc.Server(
|
|
40
|
-
|
|
37
|
+
tap.test('bundle: array syntax', async (t) => {
|
|
38
|
+
const server = new nodeOsc.Server(0, '127.0.0.1');
|
|
39
|
+
await node_events.once(server, 'listening');
|
|
40
|
+
const client = new nodeOsc.Client('127.0.0.1', server.port);
|
|
41
41
|
|
|
42
42
|
t.plan(2);
|
|
43
43
|
|
|
@@ -57,9 +57,10 @@ tap.test('bundle: array syntax', (t) => {
|
|
|
57
57
|
));
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
tap.test('bundle: nested bundle', (t) => {
|
|
61
|
-
const server = new nodeOsc.Server(
|
|
62
|
-
|
|
60
|
+
tap.test('bundle: nested bundle', async (t) => {
|
|
61
|
+
const server = new nodeOsc.Server(0, '127.0.0.1');
|
|
62
|
+
await node_events.once(server, 'listening');
|
|
63
|
+
const client = new nodeOsc.Client('127.0.0.1', server.port);
|
|
63
64
|
|
|
64
65
|
t.plan(4);
|
|
65
66
|
|