Skip to content

Encoding and crypto ​

ts
import { DAAP, NSKeyedArchiver, NTP, OPack, Plist, RTI, TLV8 } from '@corewire/core/encoding';
import { AES, ChaCha20, Curve25519, Ed25519, HKDF } from '@corewire/core/crypto';

Each codec and each cipher is a namespace. Acronyms stay in capitals in their names. They work on Buffer and Uint8Array and have no dependencies.

Codecs ​

NamespaceFormatUsed by
TLV8Type-length-value with 8-bit types, as HAP pairing uses itPairing
OPackApple's compact binary object formatCompanion Link messages
PlistBinary property listsAirPlay SETUP, /info, play queue commands, data channel framing
DAAPDMAP tagsRAOP metadata
NTPNTP timestamps and packetsAudio timing
NSKeyedArchiverKeyed archivesArtwork URLs and metadata in MRP, Companion Link text input
RTIRemote text input sessionsText input over AirPlay

TLV8 ​

ts
import { TLV8 } from '@corewire/core/encoding';

const payload = TLV8.encode([
    [TLV8.Value.State, 1],
    [TLV8.Value.Method, 0]
]);

const fields = TLV8.decode(payload);
console.log(fields.get(TLV8.Value.State));

encode(entries) splits values over 255 bytes into fragments. decode(buffer) joins adjacent fragments again into a Map of type to value. When a type comes back after another type, the map keeps the first value. For a list in which a type repeats, decodeEntries(buffer) returns every [type, value] in order, with adjacent fragments joined. bail(fields) throws a TLV8PairingError (a PairingError from @corewire/core, with the TLV8 error code) when a pairing reply carries an error or a back-off. Value, State, Method, Flags and ErrorCode name the HAP constants.

OPack ​

ts
import { OPack } from '@corewire/core/encoding';

const bytes = OPack.encode({_i: '_launchApp', _c: {_bundleID: 'com.apple.TVMusic'}, _x: OPack.int(42)});
const message = OPack.decode(bytes);

console.log(message._i);

encode() picks the smallest encoding for a number. A negative number gets 8 bytes. OPack.int(value), OPack.float(value) and OPack.sizedInteger(value, size) force a type when the device expects one. Integers are signed. decode() returns a sized integer as sizedInteger, holding a bigint when the value falls outside the safe number range. Dates travel as Date.

A repeated value becomes a reference to its first occurrence. That holds for strings, byte strings of 2 bytes or more, dates, floats and integers outside -1 to 39. Arrays and objects are always written in full. A value may sit at most 32 containers deep. decode() throws a TypeError on deeper nesting, a truncated buffer, a reference to a value that does not exist and a tag OPack does not define.

Plist ​

ts
import { Plist } from '@corewire/core/encoding';

const bytes = Plist.serialize({deviceID: 'AA:BB:CC:DD:EE:FF', streams: [{type: 130}]});
const value = Plist.parse(bytes as ArrayBuffer);

console.log(value);

serialize(value) writes a binary plist and parse(buffer) reads one. Integers of 1, 2 and 4 bytes read as unsigned, an 8-byte integer comes back as a bigint, and a date as a Date.

NTP ​

NTP.now() returns the current time as a 64-bit NTP timestamp from the wall clock. NTP.parts(timestamp) splits it into seconds and fraction. encode() and decode() handle a timing packet.

Crypto ​

ExportContents
ChaCha20encrypt(key, nonce, aad, plaintext) and decrypt(key, nonce, aad, ciphertext, authTag) with ChaCha20-Poly1305; padNonce()
Ed25519generateKeyPair(), sign(), verify()
Curve25519generateKeyPair(), generateSharedSecKey()
AESencrypt() and decrypt() with AES-128-CTR
HKDFderive(options): HKDF (RFC 5869) with the hash you pass, such as sha512. deriveEncryptionKeys(sharedSecret, salt, readInfo, writeInfo): read and write keys from a shared secret
ts
import { randomBytes } from 'node:crypto';
import { ChaCha20 } from '@corewire/core/crypto';

const key = randomBytes(32);
const nonce = ChaCha20.padNonce(Buffer.from([1, 0, 0, 0, 0, 0, 0, 0]));

const {ciphertext, authTag} = ChaCha20.encrypt(key, nonce, null, Buffer.from('hello'));
const plaintext = ChaCha20.decrypt(key, nonce, null, ciphertext, authTag);

console.log(plaintext.toString());

A wrong key, nonce or tag throws DecryptionError, an EncryptionError from @corewire/core.

CoreWire is released under the MIT license.