node-osc 11.2.0 → 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/bump-version.yml +3 -3
- package/.github/workflows/create-release.yml +4 -4
- package/.github/workflows/nodejs.yml +4 -4
- package/README.md +3 -2
- package/agent.md +330 -0
- package/dist/lib/Client.js +1 -1
- package/dist/lib/Message.js +3 -1
- package/dist/lib/Server.js +7 -12
- package/dist/lib/internal/decode.js +3 -1
- package/dist/lib/osc.js +32 -3
- package/dist/test/lib/osc.js +32 -3
- package/dist/test/test-bundle.js +13 -12
- package/dist/test/test-client.js +68 -37
- package/dist/test/test-decode.js +35 -0
- package/dist/test/test-e2e.js +9 -9
- package/dist/test/test-encode-decode.js +455 -0
- package/dist/test/test-error-handling.js +14 -13
- package/dist/test/test-message.js +261 -64
- package/dist/test/test-osc-internal.js +151 -0
- package/dist/test/test-promises.js +90 -26
- package/dist/test/test-server.js +19 -16
- package/docs/README.md +81 -0
- package/examples/README.md +3 -1
- package/lib/Client.mjs +1 -1
- package/lib/Message.mjs +3 -1
- package/lib/Server.mjs +7 -12
- package/lib/internal/decode.mjs +3 -1
- package/lib/osc.mjs +32 -3
- package/package.json +2 -2
- package/rollup.config.mjs +1 -0
- package/test/test-bundle.mjs +14 -13
- package/test/test-client.mjs +69 -38
- package/test/test-decode.mjs +35 -0
- package/test/test-e2e.mjs +10 -10
- package/test/test-encode-decode.mjs +455 -0
- package/test/test-error-handling.mjs +15 -14
- package/test/test-message.mjs +262 -66
- package/test/test-osc-internal.mjs +151 -0
- package/test/test-promises.mjs +91 -27
- package/test/test-server.mjs +20 -17
- package/types/Message.d.mts.map +1 -1
- 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
|
@@ -293,6 +293,102 @@ test('decode: error on malformed type tags (no leading comma)', (t) => {
|
|
|
293
293
|
t.end();
|
|
294
294
|
});
|
|
295
295
|
|
|
296
|
+
test('decode: error on truncated blob data', (t) => {
|
|
297
|
+
const addressBuf = Buffer.from('/b\0\0', 'ascii');
|
|
298
|
+
const typeTagsBuf = Buffer.from(',b\0\0', 'ascii');
|
|
299
|
+
const lengthBuf = Buffer.alloc(4);
|
|
300
|
+
lengthBuf.writeInt32BE(4, 0);
|
|
301
|
+
const dataBuf = Buffer.from([0x01, 0x02]);
|
|
302
|
+
const buffer = Buffer.concat([addressBuf, typeTagsBuf, lengthBuf, dataBuf]);
|
|
303
|
+
|
|
304
|
+
t.throws(() => {
|
|
305
|
+
decode(buffer);
|
|
306
|
+
}, /Malformed Packet: Not enough bytes for blob/, 'should throw when blob data is truncated');
|
|
307
|
+
|
|
308
|
+
t.end();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('decode: error on missing blob padding', (t) => {
|
|
312
|
+
const addressBuf = Buffer.from('/b\0\0', 'ascii');
|
|
313
|
+
const typeTagsBuf = Buffer.from(',b\0\0', 'ascii');
|
|
314
|
+
const lengthBuf = Buffer.alloc(4);
|
|
315
|
+
lengthBuf.writeInt32BE(3, 0);
|
|
316
|
+
const dataBuf = Buffer.from([0x01, 0x02, 0x03]);
|
|
317
|
+
const buffer = Buffer.concat([addressBuf, typeTagsBuf, lengthBuf, dataBuf]);
|
|
318
|
+
|
|
319
|
+
t.throws(() => {
|
|
320
|
+
decode(buffer);
|
|
321
|
+
}, /Malformed Packet: Not enough bytes for blob padding/, 'should throw when blob padding is missing');
|
|
322
|
+
|
|
323
|
+
t.end();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('decode: error on truncated float32', (t) => {
|
|
327
|
+
const addressBuf = Buffer.from('/f\0\0', 'ascii');
|
|
328
|
+
const typeTagsBuf = Buffer.from(',f\0\0', 'ascii');
|
|
329
|
+
const dataBuf = Buffer.from([0x3f, 0x80, 0x00]);
|
|
330
|
+
const buffer = Buffer.concat([addressBuf, typeTagsBuf, dataBuf]);
|
|
331
|
+
|
|
332
|
+
t.throws(() => {
|
|
333
|
+
decode(buffer);
|
|
334
|
+
}, /Malformed Packet: Not enough bytes for float32/, 'should throw when float32 data is truncated');
|
|
335
|
+
|
|
336
|
+
t.end();
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test('decode: error on truncated int32', (t) => {
|
|
340
|
+
const addressBuf = Buffer.from('/i\0\0', 'ascii');
|
|
341
|
+
const typeTagsBuf = Buffer.from(',i\0\0', 'ascii');
|
|
342
|
+
const dataBuf = Buffer.from([0x00, 0x01]);
|
|
343
|
+
const buffer = Buffer.concat([addressBuf, typeTagsBuf, dataBuf]);
|
|
344
|
+
|
|
345
|
+
t.throws(() => {
|
|
346
|
+
decode(buffer);
|
|
347
|
+
}, /Malformed Packet: Not enough bytes for int32/, 'should throw when int32 data is truncated');
|
|
348
|
+
|
|
349
|
+
t.end();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test('decode: error on negative blob length', (t) => {
|
|
353
|
+
const addressBuf = Buffer.from('/b\0\0', 'ascii');
|
|
354
|
+
const typeTagsBuf = Buffer.from(',b\0\0', 'ascii');
|
|
355
|
+
const lengthBuf = Buffer.alloc(4);
|
|
356
|
+
lengthBuf.writeInt32BE(-1, 0);
|
|
357
|
+
const buffer = Buffer.concat([addressBuf, typeTagsBuf, lengthBuf]);
|
|
358
|
+
|
|
359
|
+
t.throws(() => {
|
|
360
|
+
decode(buffer);
|
|
361
|
+
}, /Malformed Packet: Invalid blob length/, 'should throw when blob length is negative');
|
|
362
|
+
|
|
363
|
+
t.end();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('decode: error on bundle element size overflow', (t) => {
|
|
367
|
+
const bundleHeader = Buffer.from('#bundle\0', 'ascii');
|
|
368
|
+
const timetag = Buffer.alloc(8);
|
|
369
|
+
const sizeBuf = Buffer.alloc(4);
|
|
370
|
+
sizeBuf.writeInt32BE(0, 0);
|
|
371
|
+
const buffer = Buffer.concat([bundleHeader, timetag, sizeBuf]);
|
|
372
|
+
|
|
373
|
+
t.throws(() => {
|
|
374
|
+
decode(buffer);
|
|
375
|
+
}, /Malformed Packet/, 'should throw when bundle element size is invalid');
|
|
376
|
+
|
|
377
|
+
t.end();
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test('decode: error on truncated bundle timetag', (t) => {
|
|
381
|
+
const bundleHeader = Buffer.from('#bundle\0', 'ascii');
|
|
382
|
+
const timetag = Buffer.alloc(4);
|
|
383
|
+
const buffer = Buffer.concat([bundleHeader, timetag]);
|
|
384
|
+
|
|
385
|
+
t.throws(() => {
|
|
386
|
+
decode(buffer);
|
|
387
|
+
}, /Malformed Packet: Not enough bytes for timetag/, 'should throw when timetag is truncated');
|
|
388
|
+
|
|
389
|
+
t.end();
|
|
390
|
+
});
|
|
391
|
+
|
|
296
392
|
test('encode and decode: nested bundle with message and bundle elements', (t) => {
|
|
297
393
|
// Test the else branch in encodeBundleToBuffer for message elements
|
|
298
394
|
const innerBundle = new Bundle(['/inner/message', 123]);
|
|
@@ -845,3 +941,362 @@ test('encode and decode: type "boolean" with false value', (t) => {
|
|
|
845
941
|
t.equal(decoded.args[0].value, false);
|
|
846
942
|
t.end();
|
|
847
943
|
});
|
|
944
|
+
|
|
945
|
+
// Tests for UTF-8 string padding to 4-byte boundaries
|
|
946
|
+
// The padString function ensures OSC strings are padded based on byte length
|
|
947
|
+
// (not character count) to handle multi-byte UTF-8 correctly
|
|
948
|
+
|
|
949
|
+
test('encode and decode: UTF-8 string padding - ASCII 1 char', (t) => {
|
|
950
|
+
// 1 byte + 1 null terminator = 2 bytes, needs 2 padding bytes to reach 4-byte boundary
|
|
951
|
+
const message = {
|
|
952
|
+
oscType: 'message',
|
|
953
|
+
address: '/test',
|
|
954
|
+
args: [{ type: 'string', value: 'a' }]
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
const buffer = encode(message);
|
|
958
|
+
const decoded = decode(buffer);
|
|
959
|
+
|
|
960
|
+
t.equal(decoded.args[0].value, 'a', 'should correctly encode and decode single ASCII character');
|
|
961
|
+
|
|
962
|
+
// Verify the string is properly padded in the buffer
|
|
963
|
+
// Address "/test" is 5 bytes + 1 null = 6 bytes, padded to 8 bytes
|
|
964
|
+
// Type tag ",s" is 2 bytes + 1 null = 3 bytes, padded to 4 bytes
|
|
965
|
+
// String "a" is 1 byte + 1 null = 2 bytes, padded to 4 bytes
|
|
966
|
+
const expectedMinLength = 8 + 4 + 4; // 16 bytes minimum
|
|
967
|
+
t.ok(buffer.length >= expectedMinLength, 'buffer should contain properly padded string');
|
|
968
|
+
|
|
969
|
+
t.end();
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
test('encode and decode: UTF-8 string padding - ASCII 2 chars', (t) => {
|
|
973
|
+
// 2 bytes + 1 null = 3 bytes, needs 1 padding byte to reach 4-byte boundary
|
|
974
|
+
const message = {
|
|
975
|
+
oscType: 'message',
|
|
976
|
+
address: '/test',
|
|
977
|
+
args: [{ type: 'string', value: 'ab' }]
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
const buffer = encode(message);
|
|
981
|
+
const decoded = decode(buffer);
|
|
982
|
+
|
|
983
|
+
t.equal(decoded.args[0].value, 'ab', 'should correctly encode and decode 2-byte string');
|
|
984
|
+
t.end();
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
test('encode and decode: UTF-8 string padding - ASCII 3 chars', (t) => {
|
|
988
|
+
// 3 bytes + 1 null terminator = 4 bytes, needs 0 padding (already aligned)
|
|
989
|
+
const message = {
|
|
990
|
+
oscType: 'message',
|
|
991
|
+
address: '/test',
|
|
992
|
+
args: [{ type: 'string', value: 'abc' }]
|
|
993
|
+
};
|
|
994
|
+
|
|
995
|
+
const buffer = encode(message);
|
|
996
|
+
const decoded = decode(buffer);
|
|
997
|
+
|
|
998
|
+
t.equal(decoded.args[0].value, 'abc', 'should correctly encode and decode 3-char ASCII string');
|
|
999
|
+
t.end();
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
test('encode and decode: UTF-8 string padding - ASCII 5 chars', (t) => {
|
|
1003
|
+
// 5 bytes + 1 null terminator = 6 bytes, needs 2 padding bytes to reach 8-byte boundary
|
|
1004
|
+
const message = {
|
|
1005
|
+
oscType: 'message',
|
|
1006
|
+
address: '/test',
|
|
1007
|
+
args: [{ type: 'string', value: 'hello' }]
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
const buffer = encode(message);
|
|
1011
|
+
const decoded = decode(buffer);
|
|
1012
|
+
|
|
1013
|
+
t.equal(decoded.args[0].value, 'hello', 'should correctly encode and decode 5-char ASCII string');
|
|
1014
|
+
t.end();
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
test('encode and decode: UTF-8 string padding - ASCII 6 chars', (t) => {
|
|
1018
|
+
// 6 bytes + 1 null = 7 bytes, needs 1 padding byte to reach 8-byte boundary
|
|
1019
|
+
const message = {
|
|
1020
|
+
oscType: 'message',
|
|
1021
|
+
address: '/test',
|
|
1022
|
+
args: [{ type: 'string', value: 'abcdef' }]
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
const buffer = encode(message);
|
|
1026
|
+
const decoded = decode(buffer);
|
|
1027
|
+
|
|
1028
|
+
t.equal(decoded.args[0].value, 'abcdef', 'should correctly encode and decode 6-byte string');
|
|
1029
|
+
t.end();
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
test('encode and decode: UTF-8 string padding - ASCII 7 chars', (t) => {
|
|
1033
|
+
// 7 bytes + 1 null terminator = 8 bytes, needs 0 padding (already aligned)
|
|
1034
|
+
const message = {
|
|
1035
|
+
oscType: 'message',
|
|
1036
|
+
address: '/test',
|
|
1037
|
+
args: [{ type: 'string', value: 'testing' }]
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
const buffer = encode(message);
|
|
1041
|
+
const decoded = decode(buffer);
|
|
1042
|
+
|
|
1043
|
+
t.equal(decoded.args[0].value, 'testing', 'should correctly encode and decode 7-char ASCII string');
|
|
1044
|
+
t.end();
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
test('encode and decode: UTF-8 string padding - empty string', (t) => {
|
|
1048
|
+
// 0 bytes + 1 null terminator = 1 byte, needs 3 padding bytes to reach 4-byte boundary
|
|
1049
|
+
const message = {
|
|
1050
|
+
oscType: 'message',
|
|
1051
|
+
address: '/test',
|
|
1052
|
+
args: [{ type: 'string', value: '' }]
|
|
1053
|
+
};
|
|
1054
|
+
|
|
1055
|
+
const buffer = encode(message);
|
|
1056
|
+
const decoded = decode(buffer);
|
|
1057
|
+
|
|
1058
|
+
t.equal(decoded.args[0].value, '', 'should correctly encode and decode empty string');
|
|
1059
|
+
t.end();
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
test('encode and decode: UTF-8 string padding - emoji character', (t) => {
|
|
1063
|
+
// Emoji '😀' is 4 bytes in UTF-8 + 1 null = 5 bytes, needs 3 padding to reach 8-byte boundary
|
|
1064
|
+
const message = {
|
|
1065
|
+
oscType: 'message',
|
|
1066
|
+
address: '/test',
|
|
1067
|
+
args: [{ type: 'string', value: '😀' }]
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
const buffer = encode(message);
|
|
1071
|
+
const decoded = decode(buffer);
|
|
1072
|
+
|
|
1073
|
+
t.equal(decoded.args[0].value, '😀', 'should correctly encode and decode emoji character');
|
|
1074
|
+
|
|
1075
|
+
// Verify byte length calculation is correct
|
|
1076
|
+
const emojiByteLength = Buffer.byteLength('😀');
|
|
1077
|
+
t.equal(emojiByteLength, 4, 'emoji should be 4 bytes in UTF-8');
|
|
1078
|
+
|
|
1079
|
+
t.end();
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
test('encode and decode: UTF-8 string padding - Japanese character', (t) => {
|
|
1083
|
+
// Japanese 'あ' is 3 bytes in UTF-8 + 1 null = 4 bytes, needs 0 padding (already aligned)
|
|
1084
|
+
const message = {
|
|
1085
|
+
oscType: 'message',
|
|
1086
|
+
address: '/test',
|
|
1087
|
+
args: [{ type: 'string', value: 'あ' }]
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
const buffer = encode(message);
|
|
1091
|
+
const decoded = decode(buffer);
|
|
1092
|
+
|
|
1093
|
+
t.equal(decoded.args[0].value, 'あ', 'should correctly encode and decode Japanese character');
|
|
1094
|
+
|
|
1095
|
+
const japaneseByteLength = Buffer.byteLength('あ');
|
|
1096
|
+
t.equal(japaneseByteLength, 3, 'Japanese character should be 3 bytes in UTF-8');
|
|
1097
|
+
|
|
1098
|
+
t.end();
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
test('encode and decode: UTF-8 string padding - Chinese character', (t) => {
|
|
1102
|
+
// Chinese '中' is 3 bytes in UTF-8 + 1 null = 4 bytes, needs 0 padding (already aligned)
|
|
1103
|
+
const message = {
|
|
1104
|
+
oscType: 'message',
|
|
1105
|
+
address: '/test',
|
|
1106
|
+
args: [{ type: 'string', value: '中' }]
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
const buffer = encode(message);
|
|
1110
|
+
const decoded = decode(buffer);
|
|
1111
|
+
|
|
1112
|
+
t.equal(decoded.args[0].value, '中', 'should correctly encode and decode Chinese character');
|
|
1113
|
+
t.end();
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
test('encode and decode: UTF-8 string padding - mixed ASCII and emoji', (t) => {
|
|
1117
|
+
// 'a' (1 byte) + '😀' (4 bytes) + 'b' (1 byte) = 6 bytes + 1 null = 7 bytes
|
|
1118
|
+
// needs 1 padding byte to reach 8-byte boundary
|
|
1119
|
+
const message = {
|
|
1120
|
+
oscType: 'message',
|
|
1121
|
+
address: '/test',
|
|
1122
|
+
args: [{ type: 'string', value: 'a😀b' }]
|
|
1123
|
+
};
|
|
1124
|
+
|
|
1125
|
+
const buffer = encode(message);
|
|
1126
|
+
const decoded = decode(buffer);
|
|
1127
|
+
|
|
1128
|
+
t.equal(decoded.args[0].value, 'a😀b', 'should correctly encode and decode mixed ASCII and emoji');
|
|
1129
|
+
|
|
1130
|
+
const mixedByteLength = Buffer.byteLength('a😀b');
|
|
1131
|
+
t.equal(mixedByteLength, 6, 'mixed string should be 6 bytes in UTF-8');
|
|
1132
|
+
|
|
1133
|
+
t.end();
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
test('encode and decode: UTF-8 string padding - Japanese string', (t) => {
|
|
1137
|
+
// 'こんにちは' (Hello in Japanese) - 5 characters, each 3 bytes = 15 bytes
|
|
1138
|
+
// 15 bytes + 1 null = 16 bytes, needs 0 padding (already aligned)
|
|
1139
|
+
const message = {
|
|
1140
|
+
oscType: 'message',
|
|
1141
|
+
address: '/test',
|
|
1142
|
+
args: [{ type: 'string', value: 'こんにちは' }]
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
const buffer = encode(message);
|
|
1146
|
+
const decoded = decode(buffer);
|
|
1147
|
+
|
|
1148
|
+
t.equal(decoded.args[0].value, 'こんにちは', 'should correctly encode and decode Japanese string');
|
|
1149
|
+
|
|
1150
|
+
const japaneseStringByteLength = Buffer.byteLength('こんにちは');
|
|
1151
|
+
t.equal(japaneseStringByteLength, 15, 'Japanese string should be 15 bytes in UTF-8');
|
|
1152
|
+
|
|
1153
|
+
t.end();
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
test('encode and decode: UTF-8 string padding - accented characters', (t) => {
|
|
1157
|
+
// 'café' - 4 characters but 'é' is 2 bytes in UTF-8
|
|
1158
|
+
// 'c' (1) + 'a' (1) + 'f' (1) + 'é' (2) = 5 bytes + 1 null = 6 bytes
|
|
1159
|
+
// needs 2 padding bytes to reach 8-byte boundary
|
|
1160
|
+
const message = {
|
|
1161
|
+
oscType: 'message',
|
|
1162
|
+
address: '/test',
|
|
1163
|
+
args: [{ type: 'string', value: 'café' }]
|
|
1164
|
+
};
|
|
1165
|
+
|
|
1166
|
+
const buffer = encode(message);
|
|
1167
|
+
const decoded = decode(buffer);
|
|
1168
|
+
|
|
1169
|
+
t.equal(decoded.args[0].value, 'café', 'should correctly encode and decode accented string');
|
|
1170
|
+
|
|
1171
|
+
const accentedByteLength = Buffer.byteLength('café');
|
|
1172
|
+
t.equal(accentedByteLength, 5, 'café should be 5 bytes in UTF-8');
|
|
1173
|
+
|
|
1174
|
+
t.end();
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
test('encode and decode: UTF-8 string padding - multiple strings', (t) => {
|
|
1178
|
+
// Test multiple strings with different byte lengths in one message
|
|
1179
|
+
const message = {
|
|
1180
|
+
oscType: 'message',
|
|
1181
|
+
address: '/multi',
|
|
1182
|
+
args: [
|
|
1183
|
+
{ type: 'string', value: 'a' }, // 1 byte + null
|
|
1184
|
+
{ type: 'string', value: '😀' }, // 4 bytes + null
|
|
1185
|
+
{ type: 'string', value: 'abc' }, // 3 bytes + null
|
|
1186
|
+
{ type: 'string', value: '' } // 0 bytes + null
|
|
1187
|
+
]
|
|
1188
|
+
};
|
|
1189
|
+
|
|
1190
|
+
const buffer = encode(message);
|
|
1191
|
+
const decoded = decode(buffer);
|
|
1192
|
+
|
|
1193
|
+
t.equal(decoded.args.length, 4, 'should have 4 arguments');
|
|
1194
|
+
t.equal(decoded.args[0].value, 'a', 'first string should be correct');
|
|
1195
|
+
t.equal(decoded.args[1].value, '😀', 'second string should be correct');
|
|
1196
|
+
t.equal(decoded.args[2].value, 'abc', 'third string should be correct');
|
|
1197
|
+
t.equal(decoded.args[3].value, '', 'fourth string should be correct');
|
|
1198
|
+
|
|
1199
|
+
t.end();
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
test('encode and decode: UTF-8 string padding - address with emoji', (t) => {
|
|
1203
|
+
// OSC addresses can also contain UTF-8 characters and must be properly padded
|
|
1204
|
+
const message = {
|
|
1205
|
+
oscType: 'message',
|
|
1206
|
+
address: '/test/😀',
|
|
1207
|
+
args: [{ type: 'string', value: 'data' }]
|
|
1208
|
+
};
|
|
1209
|
+
|
|
1210
|
+
const buffer = encode(message);
|
|
1211
|
+
const decoded = decode(buffer);
|
|
1212
|
+
|
|
1213
|
+
t.equal(decoded.address, '/test/😀', 'should correctly encode and decode address with emoji');
|
|
1214
|
+
t.equal(decoded.args[0].value, 'data', 'should correctly encode and decode argument');
|
|
1215
|
+
|
|
1216
|
+
t.end();
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
test('encode and decode: UTF-8 string padding - long mixed string', (t) => {
|
|
1220
|
+
// Test a longer string with mixed content
|
|
1221
|
+
const longString = 'Hello 世界 🌍! Testing UTF-8 encoding with café and naïve.';
|
|
1222
|
+
const message = {
|
|
1223
|
+
oscType: 'message',
|
|
1224
|
+
address: '/test',
|
|
1225
|
+
args: [{ type: 'string', value: longString }]
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
const buffer = encode(message);
|
|
1229
|
+
const decoded = decode(buffer);
|
|
1230
|
+
|
|
1231
|
+
t.equal(decoded.args[0].value, longString, 'should correctly encode and decode long mixed UTF-8 string');
|
|
1232
|
+
|
|
1233
|
+
t.end();
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
test('encode and decode: UTF-8 string padding - special characters', (t) => {
|
|
1237
|
+
// Test various special characters that may have different byte lengths
|
|
1238
|
+
const specialChars = '!@#$%^&*()_+-=[]{}|;:,.<>?/~`';
|
|
1239
|
+
const message = {
|
|
1240
|
+
oscType: 'message',
|
|
1241
|
+
address: '/test',
|
|
1242
|
+
args: [{ type: 'string', value: specialChars }]
|
|
1243
|
+
};
|
|
1244
|
+
|
|
1245
|
+
const buffer = encode(message);
|
|
1246
|
+
const decoded = decode(buffer);
|
|
1247
|
+
|
|
1248
|
+
t.equal(decoded.args[0].value, specialChars, 'should correctly encode and decode special ASCII characters');
|
|
1249
|
+
t.end();
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
test('encode and decode: UTF-8 string padding - control characters', (t) => {
|
|
1253
|
+
// Test control characters
|
|
1254
|
+
const controlChars = 'line1\nline2\ttab';
|
|
1255
|
+
const message = {
|
|
1256
|
+
oscType: 'message',
|
|
1257
|
+
address: '/test',
|
|
1258
|
+
args: [{ type: 'string', value: controlChars }]
|
|
1259
|
+
};
|
|
1260
|
+
|
|
1261
|
+
const buffer = encode(message);
|
|
1262
|
+
const decoded = decode(buffer);
|
|
1263
|
+
|
|
1264
|
+
t.equal(decoded.args[0].value, controlChars, 'should correctly encode and decode strings with newlines and tabs');
|
|
1265
|
+
t.end();
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
test('encode and decode: UTF-8 string padding - surrogate pairs', (t) => {
|
|
1269
|
+
// Test various emoji that are 4-byte UTF-8 sequences
|
|
1270
|
+
const emojis = '🎉🎊🎈🎁';
|
|
1271
|
+
const message = {
|
|
1272
|
+
oscType: 'message',
|
|
1273
|
+
address: '/test',
|
|
1274
|
+
args: [{ type: 'string', value: emojis }]
|
|
1275
|
+
};
|
|
1276
|
+
|
|
1277
|
+
const buffer = encode(message);
|
|
1278
|
+
const decoded = decode(buffer);
|
|
1279
|
+
|
|
1280
|
+
t.equal(decoded.args[0].value, emojis, 'should correctly encode and decode multiple 4-byte emoji');
|
|
1281
|
+
|
|
1282
|
+
const emojisByteLength = Buffer.byteLength(emojis);
|
|
1283
|
+
t.equal(emojisByteLength, 16, 'four 4-byte emoji should total 16 bytes');
|
|
1284
|
+
|
|
1285
|
+
t.end();
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
test('encode and decode: UTF-8 string padding - zero-width characters', (t) => {
|
|
1289
|
+
// Test zero-width joiner and other special Unicode characters
|
|
1290
|
+
const zwj = 'a\u200Db'; // zero-width joiner
|
|
1291
|
+
const message = {
|
|
1292
|
+
oscType: 'message',
|
|
1293
|
+
address: '/test',
|
|
1294
|
+
args: [{ type: 'string', value: zwj }]
|
|
1295
|
+
};
|
|
1296
|
+
|
|
1297
|
+
const buffer = encode(message);
|
|
1298
|
+
const decoded = decode(buffer);
|
|
1299
|
+
|
|
1300
|
+
t.equal(decoded.args[0].value, zwj, 'should correctly encode and decode strings with zero-width characters');
|
|
1301
|
+
t.end();
|
|
1302
|
+
});
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { once } from 'node:events';
|
|
2
|
+
import { test } from 'tap';
|
|
3
3
|
|
|
4
4
|
import { Server, Client } from 'node-osc';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
test('server: socket error event is emitted', (t) => {
|
|
6
|
+
test('server: socket error event is emitted', async (t) => {
|
|
9
7
|
t.plan(1);
|
|
10
|
-
const oscServer = new Server(
|
|
8
|
+
const oscServer = new Server(0, '127.0.0.1');
|
|
9
|
+
await once(oscServer, 'listening');
|
|
11
10
|
|
|
12
11
|
oscServer.on('error', (err) => {
|
|
13
12
|
t.ok(err, 'error event should be emitted');
|
|
@@ -18,9 +17,10 @@ test('server: socket error event is emitted', (t) => {
|
|
|
18
17
|
oscServer._sock.emit('error', new Error('test socket error'));
|
|
19
18
|
});
|
|
20
19
|
|
|
21
|
-
test('server: error listener can be added before listening', (t) => {
|
|
20
|
+
test('server: error listener can be added before listening', async (t) => {
|
|
22
21
|
t.plan(2);
|
|
23
|
-
const oscServer = new Server(
|
|
22
|
+
const oscServer = new Server(0, '127.0.0.1');
|
|
23
|
+
await once(oscServer, 'listening');
|
|
24
24
|
|
|
25
25
|
oscServer.on('error', (err) => {
|
|
26
26
|
t.ok(err, 'error event should be emitted');
|
|
@@ -37,7 +37,7 @@ test('server: error listener can be added before listening', (t) => {
|
|
|
37
37
|
|
|
38
38
|
test('client: socket error event is emitted', (t) => {
|
|
39
39
|
t.plan(1);
|
|
40
|
-
const client = new Client('127.0.0.1',
|
|
40
|
+
const client = new Client('127.0.0.1', 9999);
|
|
41
41
|
|
|
42
42
|
client.on('error', (err) => {
|
|
43
43
|
t.ok(err, 'error event should be emitted');
|
|
@@ -50,7 +50,7 @@ test('client: socket error event is emitted', (t) => {
|
|
|
50
50
|
|
|
51
51
|
test('client: error listener can be added at construction', (t) => {
|
|
52
52
|
t.plan(2);
|
|
53
|
-
const client = new Client('127.0.0.1',
|
|
53
|
+
const client = new Client('127.0.0.1', 9999);
|
|
54
54
|
|
|
55
55
|
client.on('error', (err) => {
|
|
56
56
|
t.ok(err, 'error event should be emitted');
|
|
@@ -67,16 +67,17 @@ test('client: error listener can be added at construction', (t) => {
|
|
|
67
67
|
|
|
68
68
|
test('client: is an EventEmitter instance', (t) => {
|
|
69
69
|
t.plan(1);
|
|
70
|
-
const client = new Client('127.0.0.1',
|
|
70
|
+
const client = new Client('127.0.0.1', 9999);
|
|
71
71
|
|
|
72
72
|
t.ok(typeof client.on === 'function', 'client should have EventEmitter methods');
|
|
73
73
|
|
|
74
74
|
client.close();
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
test('server: multiple error listeners can be attached', (t) => {
|
|
77
|
+
test('server: multiple error listeners can be attached', async (t) => {
|
|
78
78
|
t.plan(2);
|
|
79
|
-
const oscServer = new Server(
|
|
79
|
+
const oscServer = new Server(0, '127.0.0.1');
|
|
80
|
+
await once(oscServer, 'listening');
|
|
80
81
|
|
|
81
82
|
oscServer.on('error', (err) => {
|
|
82
83
|
t.ok(err, 'first listener should receive error');
|
|
@@ -96,7 +97,7 @@ test('server: multiple error listeners can be attached', (t) => {
|
|
|
96
97
|
|
|
97
98
|
test('client: multiple error listeners can be attached', (t) => {
|
|
98
99
|
t.plan(2);
|
|
99
|
-
const client = new Client('127.0.0.1',
|
|
100
|
+
const client = new Client('127.0.0.1', 9999);
|
|
100
101
|
|
|
101
102
|
client.on('error', (err) => {
|
|
102
103
|
t.ok(err, 'first listener should receive error');
|