1008 lines
27 KiB
JavaScript
1008 lines
27 KiB
JavaScript
'use strict';
|
|
|
|
var tap = require('tap');
|
|
var osc = require('./lib/osc.js');
|
|
|
|
tap.test('osc: timetag encoding with non-number value', (t) => {
|
|
// Test the else branch in writeTimeTag that writes zeros for non-number values
|
|
const bundle = {
|
|
oscType: 'bundle',
|
|
timetag: 'immediate', // Non-number value
|
|
elements: [
|
|
{
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: []
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(bundle);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.oscType, 'bundle', 'should decode as bundle');
|
|
t.equal(decoded.timetag, 0, 'should decode timetag as 0 for immediate execution');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: timetag encoding with zero value', (t) => {
|
|
const bundle = {
|
|
oscType: 'bundle',
|
|
timetag: 0,
|
|
elements: [
|
|
{
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: []
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(bundle);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.timetag, 0, 'should encode 0 as immediate execution');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: timetag encoding with numeric epoch value', (t) => {
|
|
const bundle = {
|
|
oscType: 'bundle',
|
|
timetag: 42,
|
|
elements: [
|
|
{
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: []
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(bundle);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.timetag, 42, 'should preserve numeric timetag values');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: timetag with immediate execution values', (t) => {
|
|
// Test readTimeTag with seconds === 0 && fraction === 1
|
|
const bundle = {
|
|
oscType: 'bundle',
|
|
timetag: null, // This will trigger the non-number path
|
|
elements: [
|
|
{
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: []
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(bundle);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.timetag, 0, 'should handle immediate execution timetag');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: argument encoding with unknown type', (t) => {
|
|
// Test encodeArgument with unknown argument type to trigger line 122
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [
|
|
{
|
|
type: 'unknown',
|
|
value: 'test'
|
|
}
|
|
]
|
|
};
|
|
|
|
t.throws(() => {
|
|
osc.encode(message);
|
|
}, /Unknown argument type: unknown/, 'should throw error for unknown argument type');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: argument encoding with boolean false', (t) => {
|
|
// Test explicit boolean false encoding to cover lines 132-133
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [
|
|
{
|
|
type: 'boolean',
|
|
value: false
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value, false, 'should encode and decode false boolean');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: argument encoding with unsupported object', (t) => {
|
|
// Test encodeArgument with unsupported object to trigger lines 139-142
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [
|
|
{ unsupported: 'object' } // Object without type/value properties
|
|
]
|
|
};
|
|
|
|
t.throws(() => {
|
|
osc.encode(message);
|
|
}, /Don't know how to encode argument/, 'should throw error for unsupported object');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: argument encoding with undefined value', (t) => {
|
|
// Test encodeArgument with undefined to trigger error case
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [undefined]
|
|
};
|
|
|
|
t.throws(() => {
|
|
osc.encode(message);
|
|
}, /Don't know how to encode argument/, 'should throw error for undefined argument');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: argument decoding with unknown type tag', (t) => {
|
|
// Test decodeArgument with unknown type tag to trigger line 161
|
|
// We need to manually create a buffer with an invalid type tag
|
|
const addressPart = '/test\0\0\0';
|
|
const typeTagPart = ',X\0\0'; // X is not a valid OSC type tag
|
|
const buffer = Buffer.from(addressPart + typeTagPart);
|
|
|
|
t.throws(() => {
|
|
osc.decode(buffer);
|
|
}, /I don't understand the argument code X/, 'should throw error for unknown type tag');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: null argument encoding and decoding', (t) => {
|
|
// Test null argument handling (N type tag)
|
|
// Since our current implementation doesn't directly support null in encoding,
|
|
// let's test that we can at least decode it if we manually create the buffer
|
|
const addressPart = '/test\0\0\0';
|
|
const typeTagPart = ',N\0\0'; // N is null type tag
|
|
const buffer = Buffer.from(addressPart + typeTagPart);
|
|
|
|
const decoded = osc.decode(buffer);
|
|
t.equal(decoded.args[0].value, null, 'should decode null argument');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: double type argument encoding', (t) => {
|
|
// Test double type argument which should fall back to float
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [
|
|
{
|
|
type: 'd',
|
|
value: 3.14159
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.ok(Math.abs(decoded.args[0].value - 3.14159) < 0.001, 'should encode double as float');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: blob argument with Buffer', (t) => {
|
|
// Test blob encoding with actual Buffer to ensure Buffer.isBuffer path is covered
|
|
const testBuffer = Buffer.from('test data');
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [testBuffer] // Direct Buffer without type wrapper
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
|
|
t.equal(decoded.args[0].value.toString(), 'test data', 'should preserve blob content');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: float number encoding', (t) => {
|
|
// Test encoding of float numbers to cover lines 132-133
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [3.14159] // Non-integer number should be encoded as float
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.ok(typeof decoded.args[0].value === 'number', 'should decode as number');
|
|
t.ok(!Number.isInteger(decoded.args[0].value), 'should be float, not integer');
|
|
t.ok(Math.abs(decoded.args[0].value - 3.14159) < 0.001, 'should preserve float value');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: explicit integer type encoding', (t) => {
|
|
// Test explicit integer type to cover line 102
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [
|
|
{
|
|
type: 'i',
|
|
value: 42
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value, 42, 'should encode and decode integer');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: explicit float type encoding', (t) => {
|
|
// Test explicit float type to cover line 105
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [
|
|
{
|
|
type: 'f',
|
|
value: 3.14
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.ok(Math.abs(decoded.args[0].value - 3.14) < 0.001, 'should encode and decode float');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: explicit string type encoding', (t) => {
|
|
// Test explicit string type to cover line 108
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [
|
|
{
|
|
type: 's',
|
|
value: 'hello'
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value, 'hello', 'should encode and decode string');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: explicit blob type encoding', (t) => {
|
|
// Test explicit blob type to cover line 111
|
|
const testData = Buffer.from('blob data');
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [
|
|
{
|
|
type: 'b',
|
|
value: testData
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
|
|
t.equal(decoded.args[0].value.toString(), 'blob data', 'should preserve blob data');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: explicit boolean true type encoding', (t) => {
|
|
// Test explicit boolean true type to cover line 118
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [
|
|
{
|
|
type: 'T',
|
|
value: true
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value, true, 'should encode and decode boolean true');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: MIDI type encoding with Buffer', (t) => {
|
|
// Test MIDI type with 4-byte Buffer
|
|
const midiData = Buffer.from([0x01, 0x90, 0x3C, 0x7F]); // port 1, note on, middle C, velocity 127
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/midi',
|
|
args: [
|
|
{
|
|
type: 'm',
|
|
value: midiData
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
|
|
t.equal(decoded.args[0].value.length, 4, 'should be 4 bytes');
|
|
t.equal(decoded.args[0].value[0], 0x01, 'port id should match');
|
|
t.equal(decoded.args[0].value[1], 0x90, 'status byte should match');
|
|
t.equal(decoded.args[0].value[2], 0x3C, 'data1 should match');
|
|
t.equal(decoded.args[0].value[3], 0x7F, 'data2 should match');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: MIDI type encoding with object', (t) => {
|
|
// Test MIDI type with object format
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/midi',
|
|
args: [
|
|
{
|
|
type: 'midi',
|
|
value: {
|
|
port: 2,
|
|
status: 0x80,
|
|
data1: 0x40,
|
|
data2: 0x00
|
|
}
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
|
|
t.equal(decoded.args[0].value[0], 2, 'port should match');
|
|
t.equal(decoded.args[0].value[1], 0x80, 'status should match');
|
|
t.equal(decoded.args[0].value[2], 0x40, 'data1 should match');
|
|
t.equal(decoded.args[0].value[3], 0x00, 'data2 should match');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: MIDI type with invalid buffer length', (t) => {
|
|
// Test MIDI type with wrong buffer length
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/midi',
|
|
args: [
|
|
{
|
|
type: 'm',
|
|
value: Buffer.from([0x90, 0x3C]) // Only 2 bytes
|
|
}
|
|
]
|
|
};
|
|
|
|
t.throws(() => {
|
|
osc.encode(message);
|
|
}, /MIDI message must be exactly 4 bytes/, 'should throw error for wrong buffer length');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: MIDI type with invalid value type', (t) => {
|
|
// Test MIDI type with invalid value
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/midi',
|
|
args: [
|
|
{
|
|
type: 'm',
|
|
value: 'invalid'
|
|
}
|
|
]
|
|
};
|
|
|
|
t.throws(() => {
|
|
osc.encode(message);
|
|
}, /MIDI value must be a 4-byte Buffer or object/, 'should throw error for invalid value type');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: MIDI type with partial object', (t) => {
|
|
// Test MIDI type with object having only some properties (should default others to 0)
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/midi',
|
|
args: [
|
|
{
|
|
type: 'm',
|
|
value: {
|
|
status: 0x90,
|
|
data1: 0x3C
|
|
// port and data2 should default to 0
|
|
}
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value[0], 0, 'port should default to 0');
|
|
t.equal(decoded.args[0].value[1], 0x90, 'status should match');
|
|
t.equal(decoded.args[0].value[2], 0x3C, 'data1 should match');
|
|
t.equal(decoded.args[0].value[3], 0, 'data2 should default to 0');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: MIDI type decoding with insufficient buffer data', (t) => {
|
|
// Test the error case in readMidi when buffer doesn't have enough bytes
|
|
// This manually crafts a malformed OSC buffer with MIDI type tag but insufficient data
|
|
|
|
// Create a minimal OSC message buffer with MIDI type but truncated data
|
|
// OSC Format: address + typetags + arguments
|
|
// Address: "/m" (padded to 4 bytes)
|
|
const address = Buffer.from('/m\0\0', 'ascii'); // 4 bytes
|
|
|
|
// Type tags: ",m" (padded to 4 bytes)
|
|
const typeTags = Buffer.from(',m\0\0', 'ascii'); // 4 bytes
|
|
|
|
// MIDI data: only 2 bytes instead of required 4
|
|
const insufficientMidiData = Buffer.from([0x90, 0x3C]); // Only 2 bytes, need 4
|
|
|
|
// Combine into malformed OSC buffer
|
|
const malformedBuffer = Buffer.concat([address, typeTags, insufficientMidiData]);
|
|
|
|
t.throws(() => {
|
|
osc.decode(malformedBuffer);
|
|
}, /Not enough bytes for MIDI message/, 'should throw error when MIDI data is truncated');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: MIDI type with falsy status and data1 values', (t) => {
|
|
// Test MIDI type with object having undefined/falsy status and data1 (should default to 0)
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/midi',
|
|
args: [
|
|
{
|
|
type: 'm',
|
|
value: {
|
|
port: 5,
|
|
data2: 0x40
|
|
// status and data1 are undefined, should default to 0
|
|
}
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value[0], 5, 'port should match');
|
|
t.equal(decoded.args[0].value[1], 0, 'status should default to 0');
|
|
t.equal(decoded.args[0].value[2], 0, 'data1 should default to 0');
|
|
t.equal(decoded.args[0].value[3], 0x40, 'data2 should match');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: MIDI type with explicit zero status and data1 values', (t) => {
|
|
// Test MIDI type with object having explicit 0 values for status and data1
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/midi',
|
|
args: [
|
|
{
|
|
type: 'm',
|
|
value: {
|
|
port: 3,
|
|
status: 0,
|
|
data1: 0,
|
|
data2: 0x60
|
|
}
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value[0], 3, 'port should match');
|
|
t.equal(decoded.args[0].value[1], 0, 'status should be 0');
|
|
t.equal(decoded.args[0].value[2], 0, 'data1 should be 0');
|
|
t.equal(decoded.args[0].value[3], 0x60, 'data2 should match');
|
|
t.end();
|
|
|
|
});
|
|
tap.test('osc: timetag encoding with numeric value and fractions', (t) => {
|
|
// Test writeTimeTag with actual numeric timetag (lines 70-74)
|
|
const bundle = {
|
|
oscType: 'bundle',
|
|
timetag: 1234567890.5, // Numeric value with fractional part
|
|
elements: [
|
|
{
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: []
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(bundle);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.oscType, 'bundle', 'should decode as bundle');
|
|
t.ok(Math.abs(decoded.timetag - 1234567890.5) < 0.01, 'should preserve numeric timetag with fractions');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: timetag decoding with actual timestamp', (t) => {
|
|
// Test readTimeTag with non-zero, non-immediate values (lines 92-96)
|
|
// We encode a bundle with a real timestamp, then decode it
|
|
const bundle = {
|
|
oscType: 'bundle',
|
|
timetag: 1609459200.25, // A real timestamp: 2021-01-01 00:00:00.25
|
|
elements: [
|
|
{
|
|
oscType: 'message',
|
|
address: '/timestamp',
|
|
args: [{ type: 'i', value: 123 }]
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(bundle);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.oscType, 'bundle', 'should decode as bundle');
|
|
t.ok(decoded.timetag > 0, 'timetag should be positive');
|
|
t.ok(Math.abs(decoded.timetag - 1609459200.25) < 0.01, 'should preserve timestamp value');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: inferred integer encoding from raw number', (t) => {
|
|
// Test line 167: encoding raw integer without type wrapper
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [42] // Raw integer, not wrapped
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value, 42, 'should encode and decode raw integer');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: inferred float encoding from raw number', (t) => {
|
|
// Test line 169: encoding raw float without type wrapper
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [3.14159] // Raw float, not wrapped
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.ok(Math.abs(decoded.args[0].value - 3.14159) < 0.001, 'should encode and decode raw float');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: inferred string encoding from raw string', (t) => {
|
|
// Test line 172: encoding raw string without type wrapper
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: ['hello world'] // Raw string, not wrapped
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value, 'hello world', 'should encode and decode raw string');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: inferred boolean true encoding from raw boolean', (t) => {
|
|
// Test line 174 (true branch): encoding raw boolean true
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [true] // Raw boolean true, not wrapped
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value, true, 'should encode and decode raw boolean true');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: inferred boolean false encoding from raw boolean', (t) => {
|
|
// Test line 174 (false branch): encoding raw boolean false
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [false] // Raw boolean false, not wrapped
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value, false, 'should encode and decode raw boolean false');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: bundle with only message elements (no nested bundles)', (t) => {
|
|
// Test line 252: encoding message elements in bundle (else branch)
|
|
const bundle = {
|
|
oscType: 'bundle',
|
|
timetag: 0,
|
|
elements: [
|
|
{
|
|
oscType: 'message',
|
|
address: '/msg1',
|
|
args: [{ type: 'i', value: 1 }]
|
|
},
|
|
{
|
|
oscType: 'message',
|
|
address: '/msg2',
|
|
args: [{ type: 'i', value: 2 }]
|
|
}
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(bundle);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.oscType, 'bundle', 'should decode as bundle');
|
|
t.equal(decoded.elements.length, 2, 'should have 2 elements');
|
|
t.equal(decoded.elements[0].oscType, 'message', 'first element should be message');
|
|
t.equal(decoded.elements[1].oscType, 'message', 'second element should be message');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: malformed packet with missing comma in type tags', (t) => {
|
|
// Test lines 292-293: decoding malformed packet without comma in type tags
|
|
const addressBuf = Buffer.from('/test\0\0\0', 'utf8');
|
|
const malformedTypeTagsBuf = Buffer.from('iXX\0', 'utf8'); // Missing leading comma
|
|
const buffer = Buffer.concat([addressBuf, malformedTypeTagsBuf]);
|
|
|
|
t.throws(() => {
|
|
osc.decode(buffer);
|
|
}, /Malformed Packet/, 'should throw on malformed type tags');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: bundle with nested bundle element', (t) => {
|
|
// Test line 252: encoding bundle elements in bundle (if branch)
|
|
const innerBundle = {
|
|
oscType: 'bundle',
|
|
timetag: 0,
|
|
elements: [
|
|
{
|
|
oscType: 'message',
|
|
address: '/inner',
|
|
args: [{ type: 'i', value: 99 }]
|
|
}
|
|
]
|
|
};
|
|
|
|
const outerBundle = {
|
|
oscType: 'bundle',
|
|
timetag: 0,
|
|
elements: [
|
|
{
|
|
oscType: 'message',
|
|
address: '/outer',
|
|
args: [{ type: 's', value: 'test' }]
|
|
},
|
|
innerBundle
|
|
]
|
|
};
|
|
|
|
const buffer = osc.encode(outerBundle);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.oscType, 'bundle', 'should decode as bundle');
|
|
t.equal(decoded.elements.length, 2, 'should have 2 elements');
|
|
t.equal(decoded.elements[0].oscType, 'message', 'first element should be message');
|
|
t.equal(decoded.elements[1].oscType, 'bundle', 'second element should be bundle');
|
|
t.equal(decoded.elements[1].elements[0].address, '/inner', 'nested bundle should have correct message');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: explicit integer type name', (t) => {
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [{ type: 'integer', value: 999 }]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value, 999);
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: explicit float type name', (t) => {
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [{ type: 'float', value: 1.414 }]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.ok(Math.abs(decoded.args[0].value - 1.414) < 0.001);
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: explicit string type name', (t) => {
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [{ type: 'string', value: 'test string' }]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value, 'test string');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: explicit blob type name', (t) => {
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [{ type: 'blob', value: Buffer.from([0xAA, 0xBB]) }]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.ok(Buffer.isBuffer(decoded.args[0].value));
|
|
t.same(decoded.args[0].value, Buffer.from([0xAA, 0xBB]));
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: explicit double type name', (t) => {
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [{ type: 'double', value: 2.718281828 }]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.ok(Math.abs(decoded.args[0].value - 2.718281828) < 0.001);
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: malformed packet with missing string terminator', (t) => {
|
|
const buffer = Buffer.from('/test', 'utf8');
|
|
|
|
t.throws(() => {
|
|
osc.decode(buffer);
|
|
}, /Malformed Packet: Missing null terminator for string/, 'should throw on unterminated string');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: malformed packet with truncated int32 argument', (t) => {
|
|
const address = Buffer.from('/i\0\0', 'ascii');
|
|
const typeTags = Buffer.from(',i\0\0', 'ascii');
|
|
const truncated = Buffer.from([0x00, 0x01]);
|
|
const buffer = Buffer.concat([address, typeTags, truncated]);
|
|
|
|
t.throws(() => {
|
|
osc.decode(buffer);
|
|
}, /Malformed Packet: Not enough bytes for int32/, 'should throw on truncated int32');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: malformed packet with truncated float32 argument', (t) => {
|
|
const address = Buffer.from('/f\0\0', 'ascii');
|
|
const typeTags = Buffer.from(',f\0\0', 'ascii');
|
|
const truncated = Buffer.from([0x3f, 0x80, 0x00]);
|
|
const buffer = Buffer.concat([address, typeTags, truncated]);
|
|
|
|
t.throws(() => {
|
|
osc.decode(buffer);
|
|
}, /Malformed Packet: Not enough bytes for float32/, 'should throw on truncated float32');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: malformed packet with invalid blob length', (t) => {
|
|
const address = Buffer.from('/b\0\0', 'ascii');
|
|
const typeTags = Buffer.from(',b\0\0', 'ascii');
|
|
const length = Buffer.alloc(4);
|
|
length.writeInt32BE(-1, 0);
|
|
const buffer = Buffer.concat([address, typeTags, length]);
|
|
|
|
t.throws(() => {
|
|
osc.decode(buffer);
|
|
}, /Malformed Packet: Invalid blob length/, 'should throw on negative blob length');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: malformed packet with truncated blob data', (t) => {
|
|
const address = Buffer.from('/b\0\0', 'ascii');
|
|
const typeTags = Buffer.from(',b\0\0', 'ascii');
|
|
const length = Buffer.alloc(4);
|
|
length.writeInt32BE(4, 0);
|
|
const data = Buffer.from([0x01, 0x02]);
|
|
const buffer = Buffer.concat([address, typeTags, length, data]);
|
|
|
|
t.throws(() => {
|
|
osc.decode(buffer);
|
|
}, /Malformed Packet: Not enough bytes for blob/, 'should throw on truncated blob data');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: malformed packet with missing blob padding', (t) => {
|
|
const address = Buffer.from('/b\0\0', 'ascii');
|
|
const typeTags = Buffer.from(',b\0\0', 'ascii');
|
|
const length = Buffer.alloc(4);
|
|
length.writeInt32BE(3, 0);
|
|
const data = Buffer.from([0x01, 0x02, 0x03]);
|
|
const buffer = Buffer.concat([address, typeTags, length, data]);
|
|
|
|
t.throws(() => {
|
|
osc.decode(buffer);
|
|
}, /Malformed Packet: Not enough bytes for blob padding/, 'should throw on missing blob padding');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: malformed bundle with truncated timetag', (t) => {
|
|
const bundleHeader = Buffer.from('#bundle\0', 'ascii');
|
|
const timetag = Buffer.alloc(4);
|
|
const buffer = Buffer.concat([bundleHeader, timetag]);
|
|
|
|
t.throws(() => {
|
|
osc.decode(buffer);
|
|
}, /Malformed Packet: Not enough bytes for timetag/, 'should throw on truncated timetag');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: malformed bundle with invalid element size', (t) => {
|
|
const bundleHeader = Buffer.from('#bundle\0', 'ascii');
|
|
const timetag = Buffer.alloc(8);
|
|
const size = Buffer.alloc(4);
|
|
size.writeInt32BE(0, 0);
|
|
const buffer = Buffer.concat([bundleHeader, timetag, size]);
|
|
|
|
t.throws(() => {
|
|
osc.decode(buffer);
|
|
}, /Malformed Packet/, 'should throw on invalid bundle element size');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: malformed bundle with oversized element size', (t) => {
|
|
const bundleHeader = Buffer.from('#bundle\0', 'ascii');
|
|
const timetag = Buffer.alloc(8);
|
|
const size = Buffer.alloc(4);
|
|
size.writeInt32BE(12, 0);
|
|
const buffer = Buffer.concat([bundleHeader, timetag, size, Buffer.from([0x01, 0x02])]);
|
|
|
|
t.throws(() => {
|
|
osc.decode(buffer);
|
|
}, /Malformed Packet/, 'should throw on oversized bundle element size');
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: blob padding when length is multiple of 4', (t) => {
|
|
// Test writeBlob line 52: padding === 4 branch (should use 0)
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [{ type: 'b', value: Buffer.from([1, 2, 3, 4]) }] // length 4
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.same(decoded.args[0].value, Buffer.from([1, 2, 3, 4]));
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: blob padding when length is not multiple of 4', (t) => {
|
|
// Test writeBlob line 52: padding !== 4 branch (should use padding value)
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [{ type: 'b', value: Buffer.from([1, 2, 3]) }] // length 3
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.same(decoded.args[0].value, Buffer.from([1, 2, 3]));
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: boolean type true value', (t) => {
|
|
// Test boolean case ternary: true branch
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [{ type: 'boolean', value: true }]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value, true);
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: boolean type false value', (t) => {
|
|
// Test boolean case ternary: false branch
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [{ type: 'boolean', value: false }]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value, false);
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: explicit T type', (t) => {
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [{ type: 'T', value: true }]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value, true);
|
|
t.end();
|
|
});
|
|
|
|
tap.test('osc: explicit F type', (t) => {
|
|
const message = {
|
|
oscType: 'message',
|
|
address: '/test',
|
|
args: [{ type: 'F', value: false }]
|
|
};
|
|
|
|
const buffer = osc.encode(message);
|
|
const decoded = osc.decode(buffer);
|
|
|
|
t.equal(decoded.args[0].value, false);
|
|
t.end();
|
|
});
|