dbus-sdk
Version:
A Node.js SDK for interacting with DBus, enabling seamless service calling and exposure with TypeScript support
238 lines (237 loc) • 12.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DBusMessage = void 0;
const DBusMessageType_1 = require("./enums/DBusMessageType");
const DBusMessageFlags_1 = require("./enums/DBusMessageFlags");
const Errors_1 = require("./Errors");
const DBusMessageEndianness_1 = require("./enums/DBusMessageEndianness");
const DBusBufferEncoder_1 = require("./DBusBufferEncoder");
const DBusSignedValue_1 = require("./DBusSignedValue");
const DBusBufferDecoder_1 = require("./DBusBufferDecoder");
/**
* A class representing a DBus message.
* Handles the creation, encoding, and decoding of DBus messages, including headers and bodies.
* Supports various message types such as method calls, replies, signals, and errors.
* This class is central to DBus communication, providing the means to construct and parse messages
* according to the DBus wire protocol.
*/
class DBusMessage {
/**
* Constructor for creating a DBusMessage instance.
* Initializes the message with a header and optional body content, applying default values for required fields.
* Defaults ensure that a message is always in a valid initial state for encoding or transmission.
*
* @param header - The message header or partial header object, with fields like type, flags, and serial.
* @param body - Variable number of arguments representing the message body (payload data).
*/
constructor(header, ...body) {
/**
* The endianness used for encoding and decoding DBus messages.
* Defaults to little-endian (LE) as it is the most common in DBus implementations.
* Determines the byte order for multi-byte values in the message.
*/
this.endianness = DBusMessageEndianness_1.DBusMessageEndianness.LE;
/**
* The body of the DBus message.
* Contains the payload or arguments of the message, such as method parameters or signal data.
* This is an array that can hold various data types as per the DBus specification.
*/
this.body = [];
this.header = {
type: DBusMessageType_1.DBusMessageType.METHOD_CALL, // Default to METHOD_CALL if not specified
flags: DBusMessageFlags_1.DBusMessageFlags.REPLY_EXPECTED, // Default to expecting a reply
protocolVersion: 1, // DBus protocol version, default to 1
serial: 1, // Default serial number for message identification
destination: '', // Destination bus name, empty by default
path: '', // Object path, empty by default
interfaceName: '', // Interface name, empty by default
member: '', // Method or signal name, empty by default
signature: '', // Type signature for body, empty by default
sender: '', // Sender bus name, empty by default
...header // Override defaults with provided values
};
this.body = [...body]; // Copy provided body arguments into the message
}
/**
* Converts the DBus message instance to a Buffer for transmission.
* Encodes the header and body into a binary format following the DBus wire protocol.
* Ensures proper alignment and padding as required by the specification, including
* an 8-byte boundary for the total header length.
*
* @returns A Buffer containing the encoded DBus message ready to be sent over the wire.
* @throws {SerialError} If the message serial number is missing or invalid.
*/
toBuffer() {
if (!this.header.serial)
throw new Errors_1.SerialError('Missing or invalid serial');
const flags = this.header.flags || DBusMessageFlags_1.DBusMessageFlags.REPLY_EXPECTED;
const type = this.header.type || DBusMessageType_1.DBusMessageType.METHOD_CALL;
let bodyLength = 0;
let bodyBuff = Buffer.alloc(0);
if (this.header.signature && this.body.length > 0) {
const bodyEncoder = new DBusBufferEncoder_1.DBusBufferEncoder(this.endianness);
// Encode the body based on the signature; handle single vs. multiple arguments
bodyBuff = bodyEncoder.encode(this.header.signature, this.body.length > 1 ? this.body : this.body[0]);
bodyLength = bodyBuff.length;
}
// Create header fields array for encoding additional metadata
const fields = [];
DBusMessage.headerTypeName.forEach((fieldName) => {
if (fieldName && this.header[fieldName]) {
fields.push([
DBusMessage.headerTypeId[fieldName], // Assign the correct type ID for the field
new DBusSignedValue_1.DBusSignedValue(DBusMessage.fieldSignature[fieldName], this.header[fieldName]) // Wrap the field value with its signature
]);
}
});
// Build basic header with only 12 bytes (without headerFieldsLength)
const headerBasic = [
DBusMessageEndianness_1.DBusMessageEndianness.LE, // Endianness indicator
type, // Message type
flags, // Message flags
this.header.protocolVersion, // Protocol version
bodyLength, // Length of the body in bytes
this.header.serial // Serial number for message tracking
];
const headerAndFieldsEncoder = new DBusBufferEncoder_1.DBusBufferEncoder(this.endianness);
const headerAndFieldsBuffer = headerAndFieldsEncoder.encode('yyyyuua(yv)', [...headerBasic, fields]);
const totalHeaderLen = headerAndFieldsBuffer.length;
const headerLenAligned = Math.ceil(totalHeaderLen / 8) * 8;
const paddingLen = headerLenAligned - totalHeaderLen;
const paddingBuff = Buffer.alloc(paddingLen, 0); // Add padding bytes to align to 8-byte boundary
return Buffer.concat([headerAndFieldsBuffer, paddingBuff, bodyBuff]);
}
/**
* Retrieves the data (body) of the DBus message.
* Returns the payload or arguments contained in the message as an array.
*
* @returns An array of data elements from the message body.
*/
data() {
return this.body;
}
/**
* A static array mapping header field type IDs to their corresponding names.
* Used for encoding and decoding header fields in DBus messages.
* Null at index 0 as type IDs start from 1 in the DBus protocol.
*/
static { this.headerTypeName = [
null,
'path',
'interfaceName',
'member',
'errorName',
'replySerial',
'destination',
'sender',
'signature'
]; }
/**
* A static record mapping header field names to their type IDs.
* Used during encoding to assign the correct type ID to each header field
* as defined by the DBus specification.
*/
static { this.headerTypeId = {
path: 1,
interfaceName: 2,
member: 3,
errorName: 4,
replySerial: 5,
destination: 6,
sender: 7,
signature: 8
}; }
/**
* A static record mapping header field names to their DBus type signatures.
* Used to encode header field values with the correct DBus type during message creation.
*/
static { this.fieldSignature = {
path: 'o', // Object path
interfaceName: 's', // String (interface name)
member: 's', // String (method or signal name)
errorName: 's', // String (error name)
replySerial: 'u', // Unsigned integer (reply serial number)
destination: 's', // String (destination bus name)
sender: 's', // String (sender bus name)
signature: 'g' // Signature (type signature for body)
}; }
/**
* Encodes a DBus message from a partial header and body data into a Buffer.
* Static utility method to create and encode a message without instantiating it permanently.
* Provides a convenient way to build a message with defaults and serialize it directly.
*
* @param header - A partial DBus message header with fields like type, flags, and serial.
* @param body - Variable number of arguments representing the message body (payload data).
* @returns A Buffer containing the encoded DBus message ready for transmission.
*/
static encode(header, ...body) {
const message = new DBusMessage({
type: DBusMessageType_1.DBusMessageType.METHOD_CALL,
flags: DBusMessageFlags_1.DBusMessageFlags.REPLY_EXPECTED,
protocolVersion: 1,
serial: 1,
destination: '',
path: '',
interfaceName: '',
member: '',
signature: '',
sender: '',
...header // Override defaults with provided header fields
}, ...body);
return message.toBuffer();
}
/**
* Decodes a DBus message from raw buffers into a DBusMessage instance.
* Parses the header and body from binary data following the DBus wire protocol.
* Handles endianness, header fields, padding, and body decoding based on the signature.
*
* @param header - A Buffer containing the initial 16 bytes of the DBus message header.
* @param fieldsAndBody - A Buffer containing the header fields and body data.
* @param fieldsLength - The length of the header fields section in bytes.
* @param bodyLength - The length of the body section in bytes.
* @param advancedResponse - Boolean flag to enable advanced response handling, where DBus return messages are organized using DBusTypeClass instances.
* @param convertBigIntToNumber - Boolean flag to enable auto convert bigint to javascript number.
* @returns A DBusMessage instance with parsed header and body content.
*/
static decode(header, fieldsAndBody, fieldsLength, bodyLength, advancedResponse = false, convertBigIntToNumber = false) {
// Determine the endianness from the first byte of the header
const endianness = header[0] === DBusMessageEndianness_1.DBusMessageEndianness.LE ? DBusMessageEndianness_1.DBusMessageEndianness.LE : DBusMessageEndianness_1.DBusMessageEndianness.BE;
const headerDecoder = new DBusBufferDecoder_1.DBusBufferDecoder(endianness, header, 0, convertBigIntToNumber);
const headers = headerDecoder.decode('yyyyuuu');
const type = headers[1]; // Message type (e.g., METHOD_CALL, SIGNAL)
const flags = headers[2]; // Message flags (e.g., REPLY_EXPECTED)
const protocolVersion = headers[3]; // Protocol version
const serial = headers[5]; // Serial number for message tracking
// Concatenate remaining header bytes with fields data for full field parsing
const headerWithFieldsBuffer = Buffer.concat([header, fieldsAndBody.subarray(0, fieldsLength)]);
const fieldsDecoder = new DBusBufferDecoder_1.DBusBufferDecoder(endianness, headerWithFieldsBuffer, 12, convertBigIntToNumber);
const [fields] = fieldsDecoder.decode('a(yv)'); // Decode array of (type ID, value) pairs for header fields
const messageHeader = {
type: type,
flags: flags,
protocolVersion: protocolVersion,
serial: serial
};
// Map decoded field type IDs to header field names and assign values
for (const field of fields) {
const [typeId, fieldValue] = field;
const headerTypeName = this.headerTypeName[typeId];
if (!headerTypeName)
continue;
messageHeader[headerTypeName] = fieldValue;
}
// If there is no body or no signature, return message with only header
if (!bodyLength || !messageHeader.signature)
return new DBusMessage(messageHeader);
// Calculate aligned offset before body to account for padding (must align to 8-byte boundary)
let paddingLength = 8 - (header.length + fieldsLength) % 8;
paddingLength = paddingLength === 8 ? 0 : paddingLength;
const bodyOffset = fieldsLength + paddingLength;
const bodyBuffer = fieldsAndBody.subarray(bodyOffset);
const bodyDecoder = new DBusBufferDecoder_1.DBusBufferDecoder(endianness, bodyBuffer, 0, convertBigIntToNumber);
// Decode the body based on the signature provided in the header
const body = bodyDecoder.decode(messageHeader.signature, advancedResponse);
return new DBusMessage(messageHeader, ...body);
}
}
exports.DBusMessage = DBusMessage;