Specified algorithms
CBOT contains a number of algorithms that must match within each client and implementation.
ID
ID is a 24 bit long number which is encoded in base64-like format to 4 characters. IDs are used to inject these numbers so that the length of the characters is always known.
Encoding Algorithm
Encoding algorithm is described as follows:
function numberToId(id: number): string {
const FC = 32; // Character offset
const part1 = ((id >> 18) & 63) + FC;
const part2 = ((id >> 12) & 63) + FC;
const part3 = ((id >> 6) & 63) + FC;
const part4 = (id & 63) + FC;
return String.fromCharCode(part1, part2, part3, part4);
}
Decoding Algorithm
Decoding algorithm is described as follows:
function idToNumber(str: string): number {
const FC = 32; // Character offset
if (str.length !== 4) {
throw new Error("Invalid Id-length (4 bytes required)");
}
const part1 = str.charCodeAt(0) - FC;
const part2 = str.charCodeAt(1) - FC;
const part3 = str.charCodeAt(2) - FC;
const part4 = str.charCodeAt(3) - FC;
return (part1 << 18) | (part2 << 12) | (part3 << 6) | part4;
}
Key-ID Mapping
Key-ID -mapping is used to map arbitrary non-newline string into an ID so that the string does not need to be repeated or included in the message at all. This mapping is mainly used for transporting property names within the message but other purposes may exist in the future (for instance enumerated values).
By convention ID-generation is reserved as:
- Range from 0 - 8388607 is reserved for static keys
- Range 8388608 - 16777216 for dynamically allocated keys
Protocol does not force a specific order how IDs are allocated but in practice:
- Static keys are allocated in alphabetical order, since they are known in advance. However Model upgrades break this convention but within each upgrade allocation is alphabetical
- Dynamic keys are allocated in simple ascending order whenever new
id is required. The Key-ID -pair is injected to the message via
DEFN-opcode.
Checksum Calculation
When Key-ID -pairs are reserved statically both sender and receiver must agree on how they are mapped. For this an 8-byte long checksum is calculated. The checksum itself is simple and does not require cryptographic security. It is mainly used as a quick sanity check.
Calculation Algorithm
type KeyId = {
id: string,
key: string
};
function calculateChecksum(protocolVersion: string, keyIds: KeyId[]): string {
let chk = 0x12345678;
const s = protocolVersion + ':' + [...keyIds]
.sort((v1, v2) => v1.key.localeCompare(v2.key))
.map(ki => `${ki.key}_${ki.id}`)
.join(',');
const length = s.length;
for (let i = 0; i < length; i++) {
chk += (s.charCodeAt(i) * (i + 1));
}
return (chk & 0xffffffff).toString(16);
}