mirror of
https://github.com/signalapp/libsignal.git
synced 2026-04-26 01:35:22 +02:00
227 lines
6.5 KiB
TypeScript
227 lines
6.5 KiB
TypeScript
//
|
|
// Copyright 2021 Signal Messenger, LLC.
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import * as Native from './Native.js';
|
|
|
|
import { Buffer } from 'node:buffer';
|
|
import * as uuid from './uuid.js';
|
|
|
|
export enum ServiceIdKind {
|
|
Aci = 0,
|
|
Pni,
|
|
}
|
|
|
|
const SERVICE_ID_FIXED_WIDTH_BINARY_LEN = 17;
|
|
|
|
/**
|
|
* Typed representation of a Signal service ID, which can be one of various types.
|
|
*
|
|
* Conceptually this is a UUID in a particular "namespace" representing a particular way to reach a
|
|
* user on the Signal service.
|
|
*/
|
|
export abstract class ServiceId extends Object {
|
|
private readonly serviceIdFixedWidthBinary: Uint8Array<ArrayBuffer>;
|
|
|
|
// This has to be public for `InstanceType<T>`, which we use below.
|
|
constructor(serviceIdFixedWidthBinary: Uint8Array<ArrayBuffer>) {
|
|
super();
|
|
if (serviceIdFixedWidthBinary.length != SERVICE_ID_FIXED_WIDTH_BINARY_LEN) {
|
|
throw new TypeError('invalid Service-Id-FixedWidthBinary');
|
|
}
|
|
this.serviceIdFixedWidthBinary = serviceIdFixedWidthBinary;
|
|
}
|
|
|
|
protected static fromUuidBytesAndKind<T extends typeof ServiceId>(
|
|
// Why the explicit constructor signature?
|
|
// Because ServiceId is abstract, and TypeScript won't let us construct an abstract class.
|
|
// Strictly speaking we don't need the 'typeof' and 'InstanceType',
|
|
// but it's more consistent with the factory methods below.
|
|
this: new (
|
|
serviceIdFixedWidthBinary: Uint8Array<ArrayBuffer>
|
|
) => InstanceType<T>,
|
|
uuidBytes: ArrayLike<number>,
|
|
kind: ServiceIdKind
|
|
): InstanceType<T> {
|
|
const buffer = new Uint8Array(SERVICE_ID_FIXED_WIDTH_BINARY_LEN);
|
|
buffer[0] = kind;
|
|
buffer.set(uuidBytes, 1);
|
|
return new this(buffer);
|
|
}
|
|
|
|
getServiceIdBinary(): Uint8Array<ArrayBuffer> {
|
|
return Native.ServiceId_ServiceIdBinary(this.serviceIdFixedWidthBinary);
|
|
}
|
|
|
|
getServiceIdFixedWidthBinary(): Uint8Array<ArrayBuffer> {
|
|
return Uint8Array.from(this.serviceIdFixedWidthBinary);
|
|
}
|
|
|
|
getServiceIdString(): string {
|
|
return Native.ServiceId_ServiceIdString(this.serviceIdFixedWidthBinary);
|
|
}
|
|
|
|
override toString(): string {
|
|
return Native.ServiceId_ServiceIdLog(this.serviceIdFixedWidthBinary);
|
|
}
|
|
|
|
private downcastTo<T extends typeof ServiceId>(subclass: T): InstanceType<T> {
|
|
// Omitting `as object` results in TypeScript mistakenly assuming the branch is always taken.
|
|
if ((this as object) instanceof subclass) {
|
|
return this as InstanceType<T>;
|
|
}
|
|
throw new TypeError(
|
|
`expected ${subclass.name}, got ${this.constructor.name}`
|
|
);
|
|
}
|
|
|
|
static parseFromServiceIdFixedWidthBinary<T extends typeof ServiceId>(
|
|
this: T,
|
|
serviceIdFixedWidthBinary: Uint8Array<ArrayBuffer>
|
|
): InstanceType<T> {
|
|
let result: ServiceId;
|
|
switch (serviceIdFixedWidthBinary[0]) {
|
|
case ServiceIdKind.Aci as number:
|
|
result = new Aci(serviceIdFixedWidthBinary);
|
|
break;
|
|
case ServiceIdKind.Pni as number:
|
|
result = new Pni(serviceIdFixedWidthBinary);
|
|
break;
|
|
default:
|
|
throw new TypeError('unknown type in Service-Id-FixedWidthBinary');
|
|
}
|
|
return result.downcastTo(this);
|
|
}
|
|
|
|
static parseFromServiceIdBinary<T extends typeof ServiceId>(
|
|
this: T,
|
|
serviceIdBinary: Uint8Array<ArrayBuffer>
|
|
): InstanceType<T> {
|
|
const result = ServiceId.parseFromServiceIdFixedWidthBinary(
|
|
Native.ServiceId_ParseFromServiceIdBinary(serviceIdBinary)
|
|
);
|
|
return result.downcastTo(this);
|
|
}
|
|
|
|
static parseFromServiceIdString<T extends typeof ServiceId>(
|
|
this: T,
|
|
serviceIdString: string
|
|
): InstanceType<T> {
|
|
const result = this.parseFromServiceIdFixedWidthBinary(
|
|
Native.ServiceId_ParseFromServiceIdString(serviceIdString)
|
|
);
|
|
return result.downcastTo(this);
|
|
}
|
|
|
|
getRawUuid(): string {
|
|
return uuid.stringify(this.getRawUuidBytes());
|
|
}
|
|
|
|
getRawUuidBytes(): Uint8Array<ArrayBuffer> {
|
|
return this.serviceIdFixedWidthBinary.subarray(1);
|
|
}
|
|
|
|
isEqual(other: ServiceId): boolean {
|
|
return ServiceId.comparator(this, other) == 0;
|
|
}
|
|
|
|
/**
|
|
* Orders ServiceIds by kind, then lexicographically by the bytes of the UUID.
|
|
*
|
|
* Compatible with <code>Array.sort</code>.
|
|
*/
|
|
static comparator(this: void, lhs: ServiceId, rhs: ServiceId): number {
|
|
return Buffer.compare(
|
|
lhs.serviceIdFixedWidthBinary,
|
|
rhs.serviceIdFixedWidthBinary
|
|
);
|
|
}
|
|
|
|
static toConcatenatedFixedWidthBinary(
|
|
serviceIds: ServiceId[]
|
|
): Uint8Array<ArrayBuffer> {
|
|
const result = new Uint8Array(
|
|
serviceIds.length * SERVICE_ID_FIXED_WIDTH_BINARY_LEN
|
|
);
|
|
let offset = 0;
|
|
for (const serviceId of serviceIds) {
|
|
result.set(serviceId.serviceIdFixedWidthBinary, offset);
|
|
offset += SERVICE_ID_FIXED_WIDTH_BINARY_LEN;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
export class Aci extends ServiceId {
|
|
private readonly __type?: never;
|
|
|
|
static fromUuid(uuidString: string): Aci {
|
|
return this.fromUuidBytes(uuid.parse(uuidString));
|
|
}
|
|
|
|
static fromUuidBytes(uuidBytes: ArrayLike<number>): Aci {
|
|
return this.fromUuidBytesAndKind(uuidBytes, ServiceIdKind.Aci);
|
|
}
|
|
}
|
|
|
|
export class Pni extends ServiceId {
|
|
private readonly __type?: never;
|
|
|
|
static fromUuid(uuidString: string): Pni {
|
|
return this.fromUuidBytes(uuid.parse(uuidString));
|
|
}
|
|
|
|
static fromUuidBytes(uuidBytes: ArrayLike<number>): Pni {
|
|
return this.fromUuidBytesAndKind(uuidBytes, ServiceIdKind.Pni);
|
|
}
|
|
}
|
|
|
|
export class ProtocolAddress {
|
|
readonly _nativeHandle: Native.ProtocolAddress;
|
|
|
|
private constructor(handle: Native.ProtocolAddress) {
|
|
this._nativeHandle = handle;
|
|
}
|
|
|
|
static _fromNativeHandle(handle: Native.ProtocolAddress): ProtocolAddress {
|
|
return new ProtocolAddress(handle);
|
|
}
|
|
|
|
/**
|
|
* @param name the identifer for the recipient, usually a `ServiceId`
|
|
* @param deviceId the identifier for the device; must be in the range 1-127 inclusive
|
|
*/
|
|
static new(name: string | ServiceId, deviceId: number): ProtocolAddress {
|
|
if (typeof name !== 'string') {
|
|
name = name.getServiceIdString();
|
|
}
|
|
return new ProtocolAddress(Native.ProtocolAddress_New(name, deviceId));
|
|
}
|
|
|
|
name(): string {
|
|
return Native.ProtocolAddress_Name(this);
|
|
}
|
|
|
|
/**
|
|
* Returns a ServiceId if this address contains a valid ServiceId, `null` otherwise.
|
|
*
|
|
* In a future release ProtocolAddresses will *only* support ServiceIds.
|
|
*/
|
|
serviceId(): ServiceId | null {
|
|
try {
|
|
return ServiceId.parseFromServiceIdString(this.name());
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
deviceId(): number {
|
|
return Native.ProtocolAddress_DeviceId(this);
|
|
}
|
|
|
|
toString(): string {
|
|
return `${this.name()}.${this.deviceId()}`;
|
|
}
|
|
}
|