Skip to content

Commit

Permalink
Remove osc-min dependency
Browse files Browse the repository at this point in the history
Fixes #115

Remove the `osc-min` dependency and implement `toBuffer` and `fromBuffer` functions according to the OSC specification.

* **package.json**
  - Remove the `osc-min` dependency.
  - Update the `imports` section to include `#osc` pointing to `internal/osc.mjs`.

* **lib/Client.mjs**
  - Remove the import of `osc-min`.
  - Import `toBuffer` from `#osc`.
  - Update the `send` method to use `toBuffer` from `#osc`.

* **lib/Server.mjs**
  - Remove the import of `#decode`.
  - Import `fromBuffer` from `#osc`.
  - Update the `_sock.on('message')` handler to use `fromBuffer` from `#osc`.

* **internal/osc.mjs**
  - Implement the `toBuffer` function according to the OSC specification.
  - Implement the `fromBuffer` function according to the OSC specification.

* **test/test-osc.mjs**
  - Add unit tests for `toBuffer` function.
  - Add unit tests for `fromBuffer` function.

* **rollup.config.mjs**
  - Remove `osc-min` from the `external` array in the `walkLib` and `walkTest` functions.
  - Add `#osc` to the `external` array in the `walkLib` and `walkTest` functions.

* **lib/internal/decode.mjs**
  - Delete the file.

* **test/test-decode.mjs**
  - Delete the file.

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/MylesBorins/node-osc/issues/115?shareId=XXXX-XXXX-XXXX-XXXX).
  • Loading branch information
MylesBorins committed Aug 20, 2024
1 parent f2a0602 commit 2191be3
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 79 deletions.
218 changes: 218 additions & 0 deletions internal/osc.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import { Buffer } from 'node:buffer';

function toBuffer(object, strict = false) {
if (typeof object !== 'object' || object === null) {
throw new TypeError('Invalid OSC packet representation');
}

if (object.oscType === 'message') {
return encodeMessage(object, strict);
} else if (object.oscType === 'bundle') {
return encodeBundle(object, strict);
} else {
throw new TypeError('Invalid OSC packet representation');
}
}

function fromBuffer(buffer, strict = false) {
if (!Buffer.isBuffer(buffer)) {
throw new TypeError('Invalid OSC packet buffer');
}

const packet = decodePacket(buffer, strict);
if (packet.oscType === 'message') {
return sanitizeMessage(packet);
} else if (packet.oscType === 'bundle') {
return sanitizeBundle(packet);
} else {
throw new Error('Malformed Packet');
}
}

function encodeMessage(message, strict) {
const address = encodeString(message.address);
const typeTags = encodeString(',' + message.args.map(arg => getTypeTag(arg.type)).join(''));
const args = message.args.map(arg => encodeArgument(arg, strict)).join('');
return Buffer.concat([address, typeTags, Buffer.from(args)]);
}

function encodeBundle(bundle, strict) {
const timetag = encodeTimetag(bundle.timetag);
const elements = bundle.elements.map(element => {
const encodedElement = toBuffer(element, strict);
const size = Buffer.alloc(4);
size.writeUInt32BE(encodedElement.length, 0);
return Buffer.concat([size, encodedElement]);
});
return Buffer.concat([Buffer.from('#bundle\0'), timetag, ...elements]);
}

function decodePacket(buffer, strict) {
const address = decodeString(buffer);
if (address === '#bundle') {
return decodeBundle(buffer, strict);
} else {
return decodeMessage(buffer, strict);
}
}

function decodeMessage(buffer, strict) {
const address = decodeString(buffer);
const typeTags = decodeString(buffer).slice(1);
const args = [];
for (const tag of typeTags) {
args.push(decodeArgument(buffer, tag, strict));
}
return { oscType: 'message', address, args };
}

function decodeBundle(buffer, strict) {
const timetag = decodeTimetag(buffer);
const elements = [];
while (buffer.length > 0) {
const size = buffer.readUInt32BE(0);
const elementBuffer = buffer.slice(4, 4 + size);
elements.push(decodePacket(elementBuffer, strict));
buffer = buffer.slice(4 + size);
}
return { oscType: 'bundle', timetag, elements };
}

function encodeString(str) {
const length = Buffer.byteLength(str);
const paddedLength = Math.ceil((length + 1) / 4) * 4;
const buffer = Buffer.alloc(paddedLength);
buffer.write(str, 0, length, 'ascii');
return buffer;
}

function decodeString(buffer) {
const end = buffer.indexOf(0);
const str = buffer.toString('ascii', 0, end);
buffer = buffer.slice(Math.ceil((end + 1) / 4) * 4);
return str;
}

function encodeArgument(arg, strict) {
switch (arg.type) {
case 'integer':
return encodeInt32(arg.value);
case 'float':
return encodeFloat32(arg.value);
case 'string':
return encodeString(arg.value);
case 'blob':
return encodeBlob(arg.value);
default:
if (strict) {
throw new TypeError(`Unknown argument type: ${arg.type}`);
}
return '';
}
}

function decodeArgument(buffer, tag, strict) {
switch (tag) {
case 'i':
return { type: 'integer', value: decodeInt32(buffer) };
case 'f':
return { type: 'float', value: decodeFloat32(buffer) };
case 's':
return { type: 'string', value: decodeString(buffer) };
case 'b':
return { type: 'blob', value: decodeBlob(buffer) };
default:
if (strict) {
throw new TypeError(`Unknown argument type tag: ${tag}`);
}
return null;
}
}

function encodeInt32(value) {
const buffer = Buffer.alloc(4);
buffer.writeInt32BE(value, 0);
return buffer;
}

function decodeInt32(buffer) {
const value = buffer.readInt32BE(0);
buffer = buffer.slice(4);
return value;
}

function encodeFloat32(value) {
const buffer = Buffer.alloc(4);
buffer.writeFloatBE(value, 0);
return buffer;
}

function decodeFloat32(buffer) {
const value = buffer.readFloatBE(0);
buffer = buffer.slice(4);
return value;
}

function encodeBlob(blob) {
const size = Buffer.alloc(4);
size.writeUInt32BE(blob.length, 0);
const paddedLength = Math.ceil(blob.length / 4) * 4;
const buffer = Buffer.alloc(paddedLength);
blob.copy(buffer);
return Buffer.concat([size, buffer]);
}

function decodeBlob(buffer) {
const size = buffer.readUInt32BE(0);
const blob = buffer.slice(4, 4 + size);
buffer = buffer.slice(4 + size);
return blob;
}

function encodeTimetag(timetag) {
const buffer = Buffer.alloc(8);
buffer.writeUInt32BE(Math.floor(timetag / 4294967296), 0);
buffer.writeUInt32BE(timetag % 4294967296, 4);
return buffer;
}

function decodeTimetag(buffer) {
const seconds = buffer.readUInt32BE(0);
const fraction = buffer.readUInt32BE(4);
buffer = buffer.slice(8);
return seconds * 4294967296 + fraction;
}

function getTypeTag(type) {
switch (type) {
case 'integer':
return 'i';
case 'float':
return 'f';
case 'string':
return 's';
case 'blob':
return 'b';
default:
return '';
}
}

function sanitizeMessage(decoded) {
const message = [];
message.push(decoded.address);
decoded.args.forEach(arg => {
message.push(arg.value);
});
return message;
}

function sanitizeBundle(decoded) {
decoded.elements = decoded.elements.map(element => {
if (element.oscType === 'bundle') return sanitizeBundle(element);
else if (element.oscType === 'message') return sanitizeMessage(element);
});
return decoded;
}

export { toBuffer, fromBuffer };
4 changes: 1 addition & 3 deletions lib/Client.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { createSocket } from 'node:dgram';
import oscMin from 'osc-min';
import { toBuffer } from '#osc';
import Message from './Message.mjs';

const { toBuffer } = oscMin;

class Client {
constructor(host, port) {
this.host = host;
Expand Down
4 changes: 2 additions & 2 deletions lib/Server.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createSocket } from 'node:dgram';
import { EventEmitter } from 'node:events';

import decode from '#decode';
import { fromBuffer } from '#osc';

class Server extends EventEmitter {
constructor(port, host='127.0.0.1', cb) {
Expand All @@ -25,7 +25,7 @@ class Server extends EventEmitter {
});
this._sock.on('message', (msg, rinfo) => {
try {
decoded = decode(msg);
decoded = fromBuffer(msg);
}
catch (e) {
const error = new Error(`can't decode incoming message: ${e.message}`);
Expand Down
33 changes: 0 additions & 33 deletions lib/internal/decode.mjs

This file was deleted.

10 changes: 4 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
"default": "./lib/index.mjs"
},
"imports": {
"#decode": {
"require": "./dist/lib/internal/decode.js",
"default": "./lib/internal/decode.mjs"
"#osc": {
"require": "./dist/lib/internal/osc.js",
"default": "./lib/internal/osc.mjs"
}
},
"author": {
Expand Down Expand Up @@ -42,9 +42,7 @@
"type": "git",
"url": "git+https://github.com/MylesBorins/node-osc.git"
},
"dependencies": {
"osc-min": "^1.1.1"
},
"dependencies": {},
"devDependencies": {
"@eslint/js": "^9.4.0",
"eslint": "^9.4.0",
Expand Down
6 changes: 2 additions & 4 deletions rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@ function walkLib(config) {
external: [
'node:dgram',
'node:events',
'osc-min',
'jspack',
'#decode'
'#osc'
]
});
});
Expand All @@ -59,9 +58,8 @@ function walkTest(config) {
'node:dgram',
'node:net',
'node-osc',
'osc-min',
'tap',
'#decode'
'#osc'
]
});
});
Expand Down
31 changes: 0 additions & 31 deletions test/test-decode.mjs

This file was deleted.

Loading

0 comments on commit 2191be3

Please sign in to comment.