osc-mcp-server
Version:
Model Context Protocol server for OSC (Open Sound Control) endpoint management
246 lines • 9.19 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseOSCMessage = parseOSCMessage;
exports.extractAddressPattern = extractAddressPattern;
exports.extractTypeTags = extractTypeTags;
exports.extractArguments = extractArguments;
exports.isValidOSCMessage = isValidOSCMessage;
const index_1 = require("../types/index");
function parseOSCMessage(data, sourceIp, sourcePort) {
try {
if (data.length < 8) {
return {
error: {
code: index_1.ErrorCode.INVALID_OSC_MESSAGE,
message: 'OSC message too short (minimum 8 bytes required)',
details: { messageLength: data.length },
},
};
}
let offset = 0;
const addressResult = extractAddressPattern(data, offset);
if (addressResult.error) {
return { error: addressResult.error };
}
const address = addressResult.value;
offset = addressResult.nextOffset;
const typeTagsResult = extractTypeTags(data, offset);
if (typeTagsResult.error) {
return { error: typeTagsResult.error };
}
const typeTags = typeTagsResult.value;
offset = typeTagsResult.nextOffset;
const argumentsResult = extractArguments(data, offset, typeTags);
if (argumentsResult.error) {
return { error: argumentsResult.error };
}
const arguments_ = argumentsResult.value;
const message = {
timestamp: new Date(),
address,
typeTags,
arguments: arguments_,
sourceIp,
sourcePort,
};
return { message };
}
catch (error) {
return {
error: {
code: index_1.ErrorCode.MESSAGE_PARSE_ERROR,
message: `Failed to parse OSC message: ${error instanceof Error ? error.message : 'Unknown error'}`,
details: { originalError: error },
},
};
}
}
function extractAddressPattern(data, offset) {
try {
const nullIndex = data.indexOf(0, offset);
if (nullIndex === -1) {
return {
error: {
code: index_1.ErrorCode.INVALID_OSC_MESSAGE,
message: 'OSC address pattern not null-terminated',
},
};
}
const address = data.subarray(offset, nullIndex).toString('utf8');
if (!address.startsWith('/')) {
return {
error: {
code: index_1.ErrorCode.INVALID_OSC_MESSAGE,
message: 'OSC address pattern must start with "/"',
details: { address },
},
};
}
const nextOffset = alignTo4Bytes(nullIndex + 1);
return {
value: address,
nextOffset,
};
}
catch (error) {
return {
error: {
code: index_1.ErrorCode.MESSAGE_PARSE_ERROR,
message: `Failed to extract address pattern: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
};
}
}
function extractTypeTags(data, offset) {
try {
if (offset >= data.length) {
return {
error: {
code: index_1.ErrorCode.INVALID_OSC_MESSAGE,
message: 'Insufficient data for type tags',
},
};
}
if (data[offset] !== 0x2c) {
return {
error: {
code: index_1.ErrorCode.INVALID_OSC_MESSAGE,
message: 'OSC type tags must start with ","',
},
};
}
const nullIndex = data.indexOf(0, offset);
if (nullIndex === -1) {
return {
error: {
code: index_1.ErrorCode.INVALID_OSC_MESSAGE,
message: 'OSC type tags not null-terminated',
},
};
}
const typeTags = data.subarray(offset + 1, nullIndex).toString('utf8');
const nextOffset = alignTo4Bytes(nullIndex + 1);
return {
value: typeTags,
nextOffset,
};
}
catch (error) {
return {
error: {
code: index_1.ErrorCode.MESSAGE_PARSE_ERROR,
message: `Failed to extract type tags: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
};
}
}
function extractArguments(data, offset, typeTags) {
try {
const arguments_ = [];
let currentOffset = offset;
for (const typeTag of typeTags) {
if (currentOffset >= data.length) {
return {
error: {
code: index_1.ErrorCode.INVALID_OSC_MESSAGE,
message: `Insufficient data for argument of type '${typeTag}'`,
},
};
}
switch (typeTag) {
case index_1.OSCType.INT32:
{
if (currentOffset + 4 > data.length) {
return {
error: {
code: index_1.ErrorCode.INVALID_OSC_MESSAGE,
message: 'Insufficient data for int32 argument',
},
};
}
const value = data.readInt32BE(currentOffset);
arguments_.push(value);
currentOffset += 4;
}
break;
case index_1.OSCType.FLOAT32:
{
if (currentOffset + 4 > data.length) {
return {
error: {
code: index_1.ErrorCode.INVALID_OSC_MESSAGE,
message: 'Insufficient data for float32 argument',
},
};
}
const value = data.readFloatBE(currentOffset);
arguments_.push(value);
currentOffset += 4;
}
break;
case index_1.OSCType.STRING:
{
const nullIndex = data.indexOf(0, currentOffset);
if (nullIndex === -1) {
return {
error: {
code: index_1.ErrorCode.INVALID_OSC_MESSAGE,
message: 'String argument not null-terminated',
},
};
}
const value = data.subarray(currentOffset, nullIndex).toString('utf8');
arguments_.push(value);
currentOffset = alignTo4Bytes(nullIndex + 1);
}
break;
case index_1.OSCType.BLOB:
{
if (currentOffset + 4 > data.length) {
return {
error: {
code: index_1.ErrorCode.INVALID_OSC_MESSAGE,
message: 'Insufficient data for blob size',
},
};
}
const blobSize = data.readInt32BE(currentOffset);
currentOffset += 4;
if (currentOffset + blobSize > data.length) {
return {
error: {
code: index_1.ErrorCode.INVALID_OSC_MESSAGE,
message: 'Insufficient data for blob content',
},
};
}
const value = data.subarray(currentOffset, currentOffset + blobSize);
arguments_.push(value);
currentOffset = alignTo4Bytes(currentOffset + blobSize);
}
break;
default:
console.warn(`Unsupported OSC type tag '${typeTag}', skipping argument`);
break;
}
}
return {
value: arguments_,
};
}
catch (error) {
return {
error: {
code: index_1.ErrorCode.MESSAGE_PARSE_ERROR,
message: `Failed to extract arguments: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
};
}
}
function alignTo4Bytes(offset) {
return (offset + 3) & ~3;
}
function isValidOSCMessage(data) {
return data.length >= 8 && data[0] === 0x2f;
}
//# sourceMappingURL=parser.js.map