summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar moon <moon@coffin.ir> 2025-09-14 19:49:26 +0330
committerGravatar moon <moon@coffin.ir> 2025-09-14 19:49:26 +0330
commit50a397c8c7d8df8ea50943d7aa9f97d45ffdbc12 (patch)
tree449a9a8b590a3b19cb28ebe408d354e45c06c84a
parentin-memory compression (diff)
better (independent) client
-rw-r--r--client/3rd-party/client.ts1157
1 files changed, 521 insertions, 636 deletions
diff --git a/client/3rd-party/client.ts b/client/3rd-party/client.ts
index 711dc3b..34bfc91 100644
--- a/client/3rd-party/client.ts
+++ b/client/3rd-party/client.ts
@@ -1,696 +1,581 @@
-// ai-generated (do not trust)
-
-import { dlopen, FFIType, JSCallback, suffix } from 'bun:ffi'
-import * as path from 'path'
-import * as fs from 'fs'
-
-export interface RelayNeighbor {
- active: boolean
- hasIpv4: boolean
- hasIpv6: boolean
- v4Addr: Uint8Array // 4 bytes
- v6Addr: Uint8Array // 16 bytes
+// ai generated, do not trust
+
+const MAX_BUFFER = 1 << 18;
+const MAX_BODY = 1 << 22;
+const MAX_RECIPIENTS = 32;
+const PACKET_SIZE = 4 + 32 + (1 + 8 + 32 + 32 + 1 + (32 * MAX_RECIPIENTS) + 65536 + 64);
+const RELAY_NEIGHBOR_WIRE_LEN = 1 + 1 + 1 + 4 + 16 + 2;
+
+enum MpClientError {
+ SUCCESS = 0,
+ NETWORK_FAIL,
+ CONNECTION_FAILED,
+ SERVER_BUSY,
+ INVALID_RESPONSE,
+ DESERIALIZE_FAILED,
+ STRING_ENCODE_FAILED,
+ BAD_ARGUMENT,
+ RECV_FAILED
}
-export interface RelayCapabilities {
- retentionHours: number
- canStore: boolean
- leadingZeros: number
- acceptsPlain: boolean
+interface RelayNeighbor {
+ active: boolean;
+ has_ipv4: boolean;
+ has_ipv6: boolean;
+ v4_addr: Uint8Array;
+ v6_addr: Uint8Array;
+ port: number;
}
-export interface RelayStatistics {
- neighbors: number
- heldPackets: bigint
- quickConnect: RelayNeighbor[] // 4 elements
+interface RelayInfo {
+ major_version: number;
+ minor_version: number;
+ motd: Uint8Array;
+ capabilities: {
+ retention_hours: number;
+ can_store: boolean;
+ leading_zeros: number;
+ accepts_plain: boolean;
+ };
+ statistics: {
+ neighbors: number;
+ held_packets: bigint;
+ quick_connect: RelayNeighbor[];
+ };
}
-export interface RelayInfo {
- majorVersion: number
- minorVersion: number
- motd: string // 128 bytes max
- capabilities: RelayCapabilities
- statistics: RelayStatistics
+interface DataPacketContents {
+ encrypted: boolean;
+ created_time_ms: bigint;
+ author: Uint8Array;
+ author_signing_pub: Uint8Array;
+ recipient_count: number;
+ recipients: Uint8Array[];
+ data: Uint8Array;
+ signature: Uint8Array;
}
-export interface DataPacketContents {
- encrypted: boolean
- createdTimeMs: bigint
- author: Uint8Array // 32 bytes
- authorSigningPub: Uint8Array // 32 bytes
- recipientCount: number
- recipients: Uint8Array[] // 32 arrays of 32 bytes each
- data: Uint8Array // 65536 bytes
- signature: Uint8Array // 64 bytes
+interface DataPacket {
+ nonce: number;
+ hash: Uint8Array;
+ contents: DataPacketContents;
}
-export interface DataPacket {
- nonce: number
- hash: Uint8Array // 32 bytes
- contents: DataPacketContents
+interface DataPacketList {
+ packets: DataPacket[];
+ count: number;
+ new_offset: bigint;
}
-export interface KeyPair {
- encryptionPub: Uint8Array // 32 bytes
- encryptionPriv: Uint8Array // 32 bytes
- signingPub: Uint8Array // 32 bytes
- signingPriv: Uint8Array // 64 bytes
+interface KeyPair {
+ encryption_pub: Uint8Array;
+ encryption_priv: Uint8Array;
+ signing_pub: Uint8Array;
+ signing_priv: Uint8Array;
}
-export interface DataPacketList {
- packets: DataPacket[]
- count: number
- newOffset: bigint
-}
-
-export enum ClientError {
- Success = 0,
- NetworkFail = 1,
- InvalidAddress = 2,
- ConnectionFailed = 3,
- SendFailed = 4,
- RecvFailed = 5,
- ServerBusy = 6,
- InvalidResponse = 7,
- DeserializeFailed = 8,
- AllocFailed = 9,
- FileNotFound = 10,
- FileReadFailed = 11,
- BadArgument = 12
-}
+class MoonPasteClient {
+ private ip: string | null = null;
+ private port: number | null = null;
-function getRuntime(): 'bun' | 'deno' | 'node' {
- if (typeof Bun !== 'undefined') return 'bun'
- if (typeof Deno !== 'undefined') return 'deno'
- return 'node'
-}
-
-function findLibrary(): string {
- const runtime = getRuntime()
- const libNames = ['libmpclient.so', 'libmpclient.dylib', 'libmpclient.dll']
- const searchPaths = [
- './',
- './lib/',
- '../lib/',
- '/usr/local/lib/',
- '/usr/lib/',
- process.env.MOONPASTE_LIB_PATH || ''
- ].filter(p => p.length > 0)
-
- for (const searchPath of searchPaths) {
- for (const libName of libNames) {
- const fullPath = path.join(searchPath, libName)
- if (fs.existsSync(fullPath)) {
- return fullPath
- }
+ connect(ip: string, port: number): void {
+ this.ip = ip;
+ this.port = port;
}
- }
-
- throw new Error('Could not find moonpaste client library. Searched for: ' + libNames.join(', '))
-}
-class MoonpasteFFI {
- private lib: any
- private runtime: string
-
- constructor() {
- this.runtime = getRuntime()
- const libPath = findLibrary()
-
- switch (this.runtime) {
- case 'bun':
- this.lib = dlopen(libPath, {
- mp_get_server_info: {
- args: [FFIType.cstring, FFIType.i32, FFIType.ptr],
- returns: FFIType.i32
- },
- mp_get_server_neighbors: {
- args: [FFIType.cstring, FFIType.i32, FFIType.u8, FFIType.ptr, FFIType.ptr],
- returns: FFIType.i32
- },
- mp_push_packet: {
- args: [FFIType.cstring, FFIType.i32, FFIType.ptr, FFIType.ptr],
- returns: FFIType.i32
- },
- mp_get_packet: {
- args: [FFIType.cstring, FFIType.i32, FFIType.ptr, FFIType.ptr],
- returns: FFIType.i32
- },
- mp_get_firehose_packets: {
- args: [FFIType.cstring, FFIType.i32, FFIType.u64, FFIType.u16, FFIType.ptr],
- returns: FFIType.i32
- },
- mp_create_packet_from_bytes: {
- args: [FFIType.ptr, FFIType.usize, FFIType.u8, FFIType.ptr, FFIType.ptr],
- returns: FFIType.ptr
- },
- mp_create_packet_from_file: {
- args: [FFIType.cstring, FFIType.u8, FFIType.ptr, FFIType.ptr, FFIType.ptr],
- returns: FFIType.i32
- },
- mp_run_pow: {
- args: [FFIType.ptr, FFIType.u8],
- returns: FFIType.ptr
- },
- mp_generate_keypair: {
- args: [],
- returns: FFIType.ptr
- },
- mp_query_author_packets: {
- args: [FFIType.cstring, FFIType.i32, FFIType.ptr, FFIType.u16, FFIType.u64, FFIType.ptr],
- returns: FFIType.i32
- },
- })
- break
-
- case 'deno':
- this.lib = Deno.dlopen(libPath, {
- mp_get_server_info: {
- parameters: ['buffer', 'i32', 'pointer'],
- result: 'i32'
- },
- mp_get_server_neighbors: {
- parameters: ['buffer', 'i32', 'u8', 'pointer', 'pointer'],
- result: 'i32'
- },
- mp_push_packet: {
- parameters: ['buffer', 'i32', 'pointer', 'pointer'],
- result: 'i32'
- },
- mp_get_packet: {
- parameters: ['buffer', 'i32', 'pointer', 'pointer'],
- result: 'i32'
- },
- mp_get_firehose_packets: {
- parameters: ['buffer', 'i32', 'u64', 'u16', 'pointer'],
- result: 'i32'
- },
- mp_create_packet_from_bytes: {
- parameters: ['pointer', 'usize', 'u8', 'pointer', 'pointer'],
- result: 'pointer'
- },
- mp_create_packet_from_file: {
- parameters: ['buffer', 'u8', 'pointer', 'pointer', 'pointer'],
- result: 'i32'
- },
- mp_run_pow: {
- parameters: ['pointer', 'u8'],
- result: 'pointer'
- },
- mp_generate_keypair: {
- parameters: [],
- result: 'pointer'
- }
- })
- break
-
- case 'node':
- try {
- const ffi = require('ffi-napi') // dependency
- const ref = require('ref-napi') // dependency
-
- this.lib = ffi.Library(libPath, {
- mp_get_server_info: ['int', ['string', 'int', 'pointer']],
- mp_get_server_neighbors: ['int', ['string', 'int', 'uint8', 'pointer', 'pointer']],
- mp_push_packet: ['int', ['string', 'int', 'pointer', 'pointer']],
- mp_get_packet: ['int', ['string', 'int', 'pointer', 'pointer']],
- mp_get_firehose_packets: ['int', ['string', 'int', 'uint64', 'uint16', 'pointer']],
- mp_create_packet_from_bytes: ['pointer', ['pointer', 'size_t', 'uint8', 'pointer', 'pointer']],
- mp_create_packet_from_file: ['int', ['string', 'uint8', 'pointer', 'pointer', 'pointer']],
- mp_run_pow: ['pointer', ['pointer', 'uint8']],
- mp_generate_keypair: ['pointer', []]
- })
- } catch (e) {
- throw new Error('Node.js FFI requires ffi-napi and ref-napi packages: npm install ffi-napi ref-napi')
- }
- break
- }
- }
-
- private allocateBuffer(size: number): any {
- switch (this.runtime) {
- case 'bun':
- return new ArrayBuffer(size)
- case 'deno':
- return new ArrayBuffer(size)
- case 'node':
- const ref = require('ref-napi')
- return ref.alloc(size)
- }
- }
-
- private stringToBuffer(str: string): any {
- switch (this.runtime) {
- case 'bun':
- case 'deno':
- return new TextEncoder().encode(str + '\0')
- case 'node':
- return Buffer.from(str + '\0', 'utf8')
+ private checkConnected(): boolean {
+ return this.ip !== null && this.port !== null;
}
- }
- async getServerInfo(ip: string, port: number): Promise<RelayInfo> {
- const infoBuffer = this.allocateBuffer(1024)
- const ipBuffer = this.stringToBuffer(ip)
-
- const result = this.lib.symbols.mp_get_server_info(ipBuffer, port, infoBuffer)
-
- if (result !== ClientError.Success) {
- throw new Error(`Failed to get server info: ${result}`)
+ private readU16LE(buffer: Uint8Array, offset: number): number {
+ return buffer[offset] | (buffer[offset + 1] << 8);
}
- return this.parseRelayInfo(infoBuffer)
- }
-
- async getServerNeighbors(ip: string, port: number, count: number): Promise<RelayNeighbor[]> {
- const neighborsPtr = this.allocateBuffer(8) // pointer
- const countPtr = this.allocateBuffer(1) // uint8
- const ipBuffer = this.stringToBuffer(ip)
-
- const result = this.lib.symbols.mp_get_server_neighbors(ipBuffer, port, count, neighborsPtr, countPtr)
-
- if (result !== ClientError.Success) {
- throw new Error(`Failed to get neighbors: ${result}`)
+ private writeU16LE(buffer: Uint8Array, value: number, offset: number): void {
+ buffer[offset] = value & 0xFF;
+ buffer[offset + 1] = (value >> 8) & 0xFF;
}
- return this.parseNeighbors(neighborsPtr, countPtr)
- }
-
- async pushPacket(ip: string, port: number, packet: DataPacket): Promise<boolean> {
- const successPtr = this.allocateBuffer(1)
- const ipBuffer = this.stringToBuffer(ip)
- const packetBuffer = this.serializePacket(packet)
-
- const result = this.lib.symbols.mp_push_packet(ipBuffer, port, packetBuffer, successPtr)
-
- if (result !== ClientError.Success) {
- throw new Error(`Failed to push packet: ${result}`)
+ private writeU32LE(buffer: Uint8Array, value: number, offset: number): void {
+ buffer[offset] = value & 0xFF;
+ buffer[offset + 1] = (value >> 8) & 0xFF;
+ buffer[offset + 2] = (value >> 16) & 0xFF;
+ buffer[offset + 3] = (value >> 24) & 0xFF;
}
- return this.readBool(successPtr)
- }
-
- async getPacket(ip: string, port: number, hash: Uint8Array): Promise<DataPacket> {
- const packetBuffer = this.allocateBuffer(70000) // approximation
- const ipBuffer = this.stringToBuffer(ip)
-
- const result = this.lib.symbols.mp_get_packet(ipBuffer, port, hash, packetBuffer)
-
- if (result !== ClientError.Success) {
- throw new Error(`Failed to get packet: ${result}`)
+ private readU32LE(buffer: Uint8Array, offset: number): number {
+ return buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16) | (buffer[offset + 3] << 24);
}
- return this.parseDataPacket(packetBuffer)
- }
-
- async getFirehosePackets(ip: string, port: number, offset: bigint, count: number): Promise<DataPacketList> {
- const listBuffer = this.allocateBuffer(1024)
- const ipBuffer = this.stringToBuffer(ip)
-
- const result = this.lib.symbols.mp_get_firehose_packets(ipBuffer, port, offset, count, listBuffer)
-
- if (result !== ClientError.Success) {
- throw new Error(`Failed to get firehose packets: ${result}`)
+ private readU64LE(buffer: Uint8Array, offset: number): bigint {
+ return (
+ BigInt(buffer[offset]) |
+ (BigInt(buffer[offset + 1]) << 8n) |
+ (BigInt(buffer[offset + 2]) << 16n) |
+ (BigInt(buffer[offset + 3]) << 24n) |
+ (BigInt(buffer[offset + 4]) << 32n) |
+ (BigInt(buffer[offset + 5]) << 40n) |
+ (BigInt(buffer[offset + 6]) << 48n) |
+ (BigInt(buffer[offset + 7]) << 56n)
+ );
}
- return this.parseDataPacketList(listBuffer)
- }
-
- async queryAuthorPackets(ip: string, port: number, authorKeys: KeyPair, count: number, offset: bigint): Promise<DataPacketList> {
- const keysBuffer = this.serializeKeyPair(authorKeys)
- const listBuffer = this.allocateBuffer(1024)
- const ipBuffer = this.stringToBuffer(ip)
-
- const result = this.lib.symbols.mp_query_author_packets(ipBuffer, port, keysBuffer, count, offset, listBuffer)
-
- if (result !== ClientError.Success) {
- throw new Error(`Failed to query author packets: ${result}`)
+ private writeU64LE(buffer: Uint8Array, value: bigint, offset: number): void {
+ buffer[offset] = Number(value & 0xFFn);
+ buffer[offset + 1] = Number((value >> 8n) & 0xFFn);
+ buffer[offset + 2] = Number((value >> 16n) & 0xFFn);
+ buffer[offset + 3] = Number((value >> 24n) & 0xFFn);
+ buffer[offset + 4] = Number((value >> 32n) & 0xFFn);
+ buffer[offset + 5] = Number((value >> 40n) & 0xFFn);
+ buffer[offset + 6] = Number((value >> 48n) & 0xFFn);
+ buffer[offset + 7] = Number((value >> 56n) & 0xFFn);
}
- return this.parseDataPacketList(listBuffer)
- }
-
- createPacketFromBytes(input: Uint8Array, recipientCount: number, recipients: Uint8Array[] | null, authorKeys: KeyPair): DataPacket {
- const recipientsBuffer = recipients ? this.serializeRecipients(recipients) : null
- const keysBuffer = this.serializeKeyPair(authorKeys)
-
- const result = this.lib.symbols.mp_create_packet_from_bytes(
- input,
- input.length,
- recipientCount,
- recipientsBuffer,
- keysBuffer
- )
-
- return this.parseDataPacket(result)
- }
-
- async createPacketFromFile(filename: string, recipientCount: number, recipients: Uint8Array[] | null, authorKeys: KeyPair): Promise<DataPacket> {
- const packetBuffer = this.allocateBuffer(4096)
- const filenameBuffer = this.stringToBuffer(filename)
- const recipientsBuffer = recipients ? this.serializeRecipients(recipients) : null
- const keysBuffer = this.serializeKeyPair(authorKeys)
-
- const result = this.lib.symbols.mp_create_packet_from_file(
- filenameBuffer,
- recipientCount,
- recipientsBuffer,
- keysBuffer,
- packetBuffer
- )
-
- if (result !== ClientError.Success) {
- throw new Error(`Failed to create packet from file: ${result}`)
+ private checkLeadingZeros(hash: Uint8Array, requiredZeros: number): boolean {
+ if (requiredZeros === 0) return true;
+ const fullBytes = Math.floor(requiredZeros / 8);
+ const remainingBits = requiredZeros % 8;
+ for (let i = 0; i < fullBytes; i++) {
+ if (hash[i] !== 0) return false;
+ }
+ if (remainingBits > 0) {
+ const mask = 0xFF << (8 - remainingBits);
+ if ((hash[fullBytes] & mask) !== 0) return false;
+ }
+ return true;
}
- return this.parseDataPacket(packetBuffer)
- }
-
- runProofOfWork(packet: DataPacket, leadingZeros: number): DataPacket {
- const packetBuffer = this.serializePacket(packet)
- const result = this.lib.symbols.mp_run_pow(packetBuffer, leadingZeros)
- return this.parseDataPacket(result)
- }
-
- generateKeyPair(): KeyPair {
- const result = this.lib.symbols.mp_generate_keypair()
- return this.parseKeyPair(result)
- }
-
- private parseRelayInfo(buffer: any): RelayInfo {
- const view = new DataView(buffer instanceof ArrayBuffer ? buffer : buffer.buffer)
- let offset = 0
-
- const majorVersion = view.getUint8(offset++)
- const minorVersion = view.getUint8(offset++)
-
- const motdBytes = new Uint8Array(buffer, offset, 128)
- const motd = new TextDecoder().decode(motdBytes).replace(/\0.*$/, '')
- offset += 128
-
- const retentionHours = view.getUint16(offset, true) // little endian
- offset += 2
- const canStore = view.getUint8(offset++) !== 0
- const leadingZeros = view.getUint8(offset++)
- const acceptsPlain = view.getUint8(offset++) !== 0
-
- const neighbors = view.getUint16(offset, true)
- offset += 2
- const heldPackets = view.getBigUint64(offset, true)
- offset += 8
-
- const quickConnect: RelayNeighbor[] = []
- for (let i = 0; i < 4; i++) {
- quickConnect.push(this.parseRelayNeighborAt(buffer, offset))
- offset += 23 // 1 + 1 + 1 + 4 + 16
+ private async sha256(data: Uint8Array): Promise<Uint8Array> {
+ const digest = await crypto.subtle.digest('SHA-256', data);
+ return new Uint8Array(digest);
}
- return {
- majorVersion,
- minorVersion,
- motd,
- capabilities: {
- retentionHours,
- canStore,
- leadingZeros,
- acceptsPlain
- },
- statistics: {
- neighbors,
- heldPackets,
- quickConnect
- }
+ async generateKeyPair(): Promise<KeyPair> {
+ const { publicKey: encPub, privateKey: encPriv } = await crypto.subtle.generateKey(
+ { name: 'ECDSA', namedCurve: 'P-256' },
+ true,
+ ['sign', 'verify']
+ );
+ const { publicKey: signPub, privateKey: signPriv } = await crypto.subtle.generateKey(
+ { name: 'ECDSA', namedCurve: 'P-256' },
+ true,
+ ['sign', 'verify']
+ );
+ const encPubBytes = new Uint8Array(await crypto.subtle.exportKey('raw', encPub));
+ const encPrivBytes = new Uint8Array(await crypto.subtle.exportKey('raw', encPriv));
+ const signPubBytes = new Uint8Array(await crypto.subtle.exportKey('raw', signPub));
+ const signPrivBytes = new Uint8Array(await crypto.subtle.exportKey('raw', signPriv));
+ return {
+ encryption_pub: encPubBytes.slice(0, 32),
+ encryption_priv: encPrivBytes.slice(0, 32),
+ signing_pub: signPubBytes.slice(0, 32),
+ signing_priv: signPrivBytes.slice(0, 32),
+ };
}
- }
-
- private parseRelayNeighborAt(buffer: any, offset: number): RelayNeighbor {
- const view = new DataView(buffer instanceof ArrayBuffer ? buffer : buffer.buffer)
-
- const active = view.getUint8(offset) !== 0
- const hasIpv4 = view.getUint8(offset + 1) !== 0
- const hasIpv6 = view.getUint8(offset + 2) !== 0
-
- const v4Addr = new Uint8Array(buffer, offset + 3, 4)
- const v6Addr = new Uint8Array(buffer, offset + 7, 16)
- return { active, hasIpv4, hasIpv6, v4Addr, v6Addr }
- }
-
- private parseNeighbors(neighborsPtr: any, countPtr: any): RelayNeighbor[] {
- const count = this.readUint8(countPtr)
- const neighbors: RelayNeighbor[] = []
-
- if (count > 0 && neighborsPtr) {
- for (let i = 0; i < count; i++) {
- const neighborOffset = i * 23 // RELAY_NEIGHBOR_WIRE_LEN
- neighbors.push(this.parseRelayNeighborAt(neighborsPtr, neighborOffset))
- }
+ private async signData(data: Uint8Array, privateKey: Uint8Array): Promise<Uint8Array> {
+ const key = await crypto.subtle.importKey(
+ 'raw',
+ privateKey,
+ { name: 'ECDSA', namedCurve: 'P-256' },
+ false,
+ ['sign']
+ );
+ const signature = await crypto.subtle.sign(
+ { name: 'ECDSA', hash: 'SHA-256' },
+ key,
+ data
+ );
+ return new Uint8Array(signature).slice(0, 64);
}
- return neighbors
- }
-
- private parseDataPacket(buffer: any): DataPacket {
- const view = new DataView(buffer instanceof ArrayBuffer ? buffer : buffer.buffer)
- let offset = 0
-
- const nonce = view.getUint32(offset, true)
- offset += 4
-
- const hash = new Uint8Array(buffer, offset, 32)
- offset += 32
-
- const encrypted = view.getUint8(offset++) !== 0
- const createdTimeMs = view.getBigUint64(offset, true)
- offset += 8
-
- const author = new Uint8Array(buffer, offset, 32)
- offset += 32
- const authorSigningPub = new Uint8Array(buffer, offset, 32)
- offset += 32
-
- const recipientCount = view.getUint8(offset++)
- const recipients: Uint8Array[] = []
- for (let i = 0; i < 32; i++) {
- recipients.push(new Uint8Array(buffer, offset, 32))
- offset += 32
+ private s2relayNeighbor(len: number, stream: Uint8Array): { neighbor: RelayNeighbor; success: boolean } {
+ if (len !== RELAY_NEIGHBOR_WIRE_LEN) {
+ return { neighbor: { active: false, has_ipv4: false, has_ipv6: false, v4_addr: new Uint8Array(4), v6_addr: new Uint8Array(16), port: 0 }, success: false };
+ }
+ const neighbor: RelayNeighbor = {
+ active: stream[0] !== 0,
+ has_ipv4: stream[1] !== 0,
+ has_ipv6: stream[2] !== 0,
+ v4_addr: stream.slice(3, 7),
+ v6_addr: stream.slice(7, 23),
+ port: this.readU16LE(stream, 23),
+ };
+ return { neighbor, success: true };
}
- const data = new Uint8Array(buffer, offset, 65536)
- offset += 65536
-
- const signature = new Uint8Array(buffer, offset, 64)
-
- return {
- nonce,
- hash,
- contents: {
- encrypted,
- createdTimeMs,
- author,
- authorSigningPub,
- recipientCount,
- recipients,
- data,
- signature
- }
+ private s2relayInfo(len: number, stream: Uint8Array): { info: RelayInfo; success: boolean } {
+ const expectedLen = 1 + 1 + 128 + 2 + 1 + 1 + 1 + 2 + 8 + (4 * RELAY_NEIGHBOR_WIRE_LEN);
+ if (len !== expectedLen) {
+ return { info: { major_version: 0, minor_version: 0, motd: new Uint8Array(128), capabilities: { retention_hours: 0, can_store: false, leading_zeros: 0, accepts_plain: false }, statistics: { neighbors: 0, held_packets: 0n, quick_connect: [] } }, success: false };
+ }
+ let off = 0;
+ const info: RelayInfo = {
+ major_version: stream[off++],
+ minor_version: stream[off++],
+ motd: stream.slice(off, off + 128),
+ capabilities: {
+ retention_hours: this.readU16LE(stream, off),
+ can_store: stream[off + 2] !== 0,
+ leading_zeros: stream[off + 3],
+ accepts_plain: stream[off + 4] !== 0,
+ },
+ statistics: {
+ neighbors: this.readU16LE(stream, off + 5),
+ held_packets: this.readU64LE(stream, off + 7),
+ quick_connect: [],
+ },
+ };
+ off += 15;
+ for (let i = 0; i < 4; i++) {
+ const { neighbor, success } = this.s2relayNeighbor(RELAY_NEIGHBOR_WIRE_LEN, stream.slice(off, off + RELAY_NEIGHBOR_WIRE_LEN));
+ if (!success) {
+ return { info: { major_version: 0, minor_version: 0, motd: new Uint8Array(128), capabilities: { retention_hours: 0, can_store: false, leading_zeros: 0, accepts_plain: false }, statistics: { neighbors: 0, held_packets: 0n, quick_connect: [] } }, success: false };
+ }
+ info.statistics.quick_connect.push(neighbor);
+ off += RELAY_NEIGHBOR_WIRE_LEN;
+ }
+ return { info, success: true };
}
- }
-
- private parseDataPacketList(buffer: any): DataPacketList {
- const view = new DataView(buffer instanceof ArrayBuffer ? buffer : buffer.buffer)
- const newOffset = view.getBigUint64(0, true)
- const availableBytes = buffer.byteLength - 8
- const packetSize = 4 + 32 + (1 + 8 + 32 + 32 + 1 + (32 * 32) + 65536 + 64) // PACKET_SIZE
- const packetCount = Math.floor(availableBytes / packetSize)
-
- const packets: DataPacket[] = []
- for (let i = 0; i < packetCount; i++) {
- const packetOffset = 8 + (i * packetSize)
- const packetBuffer = buffer.slice(packetOffset, packetOffset + packetSize)
- packets.push(this.parseDataPacket(packetBuffer))
+ private s2dataPacket(len: number, stream: Uint8Array): { packet: DataPacket; success: boolean } {
+ if (len !== PACKET_SIZE) {
+ return { packet: { nonce: 0, hash: new Uint8Array(32), contents: { encrypted: false, created_time_ms: 0n, author: new Uint8Array(32), author_signing_pub: new Uint8Array(32), recipient_count: 0, recipients: [], data: new Uint8Array(65536), signature: new Uint8Array(64) } }, success: false };
+ }
+ let off = 0;
+ const packet: DataPacket = {
+ nonce: this.readU32LE(stream, off),
+ hash: stream.slice(off + 4, off + 36),
+ contents: {
+ encrypted: stream[off + 36] !== 0,
+ created_time_ms: this.readU64LE(stream, off + 37),
+ author: stream.slice(off + 45, off + 77),
+ author_signing_pub: stream.slice(off + 77, off + 109),
+ recipient_count: stream[off + 109],
+ recipients: [],
+ data: stream.slice(off + 110 + 32 * MAX_RECIPIENTS, off + 110 + 32 * MAX_RECIPIENTS + 65536),
+ signature: stream.slice(off + 110 + 32 * MAX_RECIPIENTS + 65536, off + 110 + 32 * MAX_RECIPIENTS + 65536 + 64),
+ },
+ };
+ off += 110;
+ for (let i = 0; i < MAX_RECIPIENTS; i++) {
+ packet.contents.recipients.push(stream.slice(off, off + 32));
+ off += 32;
+ }
+ return { packet, success: true };
}
- return {
- packets,
- count: packetCount,
- newOffset
- }
- }
-
- private parseKeyPair(buffer: any): KeyPair {
- const encryptionPub = new Uint8Array(buffer, 0, 32)
- const encryptionPriv = new Uint8Array(buffer, 32, 32)
- const signingPub = new Uint8Array(buffer, 64, 32)
- const signingPriv = new Uint8Array(buffer, 96, 64)
-
- return {
- encryptionPub,
- encryptionPriv,
- signingPub,
- signingPriv
- }
- }
-
- private serializePacket(packet: DataPacket): any {
- const packetSize = 4 + 32 + (1 + 8 + 32 + 32 + 1 + (32 * 32) + 65536 + 64)
- const buffer = new ArrayBuffer(packetSize)
- const view = new DataView(buffer)
- let offset = 0
-
- view.setUint32(offset, packet.nonce, true)
- offset += 4
-
- new Uint8Array(buffer, offset, 32).set(packet.hash)
- offset += 32
-
- view.setUint8(offset++, packet.contents.encrypted ? 1 : 0)
- view.setBigUint64(offset, packet.contents.createdTimeMs, true)
- offset += 8
-
- new Uint8Array(buffer, offset, 32).set(packet.contents.author)
- offset += 32
- new Uint8Array(buffer, offset, 32).set(packet.contents.authorSigningPub)
- offset += 32
-
- view.setUint8(offset++, packet.contents.recipientCount)
- for (let i = 0; i < 32; i++) {
- if (i < packet.contents.recipients.length) {
- new Uint8Array(buffer, offset, 32).set(packet.contents.recipients[i])
- }
- offset += 32
+ private dataPacket2s(data: DataPacket): Uint8Array | null {
+ if (data.contents.recipient_count > MAX_RECIPIENTS) {
+ return null;
+ }
+ const stream = new Uint8Array(PACKET_SIZE);
+ let off = 0;
+ this.writeU32LE(stream, data.nonce, off);
+ off += 4;
+ stream.set(data.hash, off);
+ off += 32;
+ stream[off++] = data.contents.encrypted ? 1 : 0;
+ this.writeU64LE(stream, data.contents.created_time_ms, off);
+ off += 8;
+ stream.set(data.contents.author, off);
+ off += 32;
+ stream.set(data.contents.author_signing_pub, off);
+ off += 32;
+ stream[off++] = data.contents.recipient_count;
+ for (let i = 0; i < MAX_RECIPIENTS; i++) {
+ stream.set(data.contents.recipients[i] || new Uint8Array(32), off);
+ off += 32;
+ }
+ stream.set(data.contents.data, off);
+ off += 65536;
+ stream.set(data.contents.signature, off);
+ return stream;
}
- new Uint8Array(buffer, offset, 65536).set(packet.contents.data)
- offset += 65536
-
- new Uint8Array(buffer, offset, 64).set(packet.contents.signature)
-
- return buffer
- }
-
- private serializeRecipients(recipients: Uint8Array[]): any {
- const buffer = new ArrayBuffer(32 * 32) // MAX_RECIPIENTS * 32 bytes each
- for (let i = 0; i < Math.min(recipients.length, 32); i++) {
- new Uint8Array(buffer, i * 32, 32).set(recipients[i])
- }
- return buffer
- }
-
- private serializeKeyPair(keyPair: KeyPair): any {
- const buffer = new ArrayBuffer(32 + 32 + 32 + 64) // total keypair size
- let offset = 0
-
- new Uint8Array(buffer, offset, 32).set(keyPair.encryptionPub)
- offset += 32
- new Uint8Array(buffer, offset, 32).set(keyPair.encryptionPriv)
- offset += 32
- new Uint8Array(buffer, offset, 32).set(keyPair.signingPub)
- offset += 32
- new Uint8Array(buffer, offset, 64).set(keyPair.signingPriv)
-
- return buffer
- }
-
- private readBool(ptr: any): boolean {
- return this.readUint8(ptr) !== 0
- }
-
- private readUint8(ptr: any): number {
- switch (this.runtime) {
- case 'bun':
- case 'deno':
- return new Uint8Array(ptr)[0]
- case 'node':
- return ptr.readUInt8(0)
- default:
- return 0
+ private async clientRequest(data: Uint8Array, dataLen: number): Promise<{ response: Uint8Array | null; respLen: number; error: MpClientError }> {
+ if (!this.checkConnected()) {
+ return { response: null, respLen: 0, error: MpClientError.CONNECTION_FAILED };
+ }
+ const isIpv6 = this.ip!.includes(':');
+ const url = isIpv6 ? `http://[${this.ip}]:${this.port}/` : `http://${this.ip}:${this.port}/`;
+ try {
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Length': dataLen.toString(),
+ 'Connection': 'close',
+ },
+ body: data.slice(0, dataLen),
+ });
+
+ if (!response.ok) {
+ if (response.status === 418) {
+ return { response: null, respLen: 0, error: MpClientError.SERVER_BUSY };
+ }
+ return { response: null, respLen: 0, error: MpClientError.NETWORK_FAIL };
+ }
+
+ const arrayBuffer = await response.arrayBuffer();
+ const responseData = new Uint8Array(arrayBuffer);
+ const contentLength = parseInt(response.headers.get('Content-Length') || '0', 10);
+
+ if (contentLength > 0 && responseData.length !== contentLength) {
+ return { response: null, respLen: 0, error: MpClientError.RECV_FAILED };
+ }
+ if (responseData.length > MAX_BODY) {
+ return { response: null, respLen: 0, error: MpClientError.INVALID_RESPONSE };
+ }
+
+ return { response: responseData, respLen: responseData.length, error: MpClientError.SUCCESS };
+ } catch (err) {
+ return { response: null, respLen: 0, error: MpClientError.CONNECTION_FAILED };
+ }
}
- }
-}
-
-// Main client class
-export class MoonpasteClient {
- private ffi: MoonpasteFFI
-
- constructor() {
- this.ffi = new MoonpasteFFI()
- }
-
- async getServerInfo(ip: string, port: number): Promise<RelayInfo> {
- return this.ffi.getServerInfo(ip, port)
- }
-
- async getNeighbors(ip: string, port: number, count: number = 5): Promise<RelayNeighbor[]> {
- return this.ffi.getServerNeighbors(ip, port, count)
- }
- async pushPacket(ip: string, port: number, packet: DataPacket): Promise<boolean> {
- return this.ffi.pushPacket(ip, port, packet)
- }
-
- async getPacket(ip: string, port: number, hash: Uint8Array): Promise<DataPacket> {
- return this.ffi.getPacket(ip, port, hash)
- }
-
- async getFirehosePackets(ip: string, port: number, offset: bigint = 0n, count: number = 10): Promise<DataPacketList> {
- return this.ffi.getFirehosePackets(ip, port, offset, count)
- }
-
- createPacket(message: string | Uint8Array, recipients?: Uint8Array[], authorKeys?: KeyPair): DataPacket {
- const data = typeof message === 'string' ? new TextEncoder().encode(message) : message
- const keys = authorKeys || this.generateKeyPair()
- const recipientCount = recipients ? recipients.length : 0
-
- return this.ffi.createPacketFromBytes(data, recipientCount, recipients || null, keys)
- }
+ async getServerInfo(): Promise<{ info: RelayInfo; error: MpClientError }> {
+ const data = new Uint8Array([0x10]);
+ const { response, respLen, error } = await this.clientRequest(data, data.length);
+ if (error !== MpClientError.SUCCESS) {
+ return { info: { major_version: 0, minor_version: 0, motd: new Uint8Array(128), capabilities: { retention_hours: 0, can_store: false, leading_zeros: 0, accepts_plain: false }, statistics: { neighbors: 0, held_packets: 0n, quick_connect: [] } }, error };
+ }
+ if (respLen < 1 || response![0] !== 0x17) {
+ return { info: { major_version: 0, minor_version: 0, motd: new Uint8Array(128), capabilities: { retention_hours: 0, can_store: false, leading_zeros: 0, accepts_plain: false }, statistics: { neighbors: 0, held_packets: 0n, quick_connect: [] } }, error: MpClientError.INVALID_RESPONSE };
+ }
+ const { info, success } = this.s2relayInfo(respLen - 1, response!.slice(1));
+ return { info, error: success ? MpClientError.SUCCESS : MpClientError.DESERIALIZE_FAILED };
+ }
- async createPacketFromFile(filename: string, recipients?: Uint8Array[], authorKeys?: KeyPair): Promise<DataPacket> {
- const keys = authorKeys || this.generateKeyPair()
- const recipientCount = recipients ? recipients.length : 0
+ async getServerNeighbors(count: number): Promise<{ neighbors: RelayNeighbor[]; count: number; error: MpClientError }> {
+ const data = new Uint8Array([0x11, count]);
+ const { response, respLen, error } = await this.clientRequest(data, data.length);
+ if (error !== MpClientError.SUCCESS) {
+ return { neighbors: [], count: 0, error };
+ }
+ if (respLen < 1 || response![0] !== 0x18) {
+ return { neighbors: [], count: 0, error: MpClientError.INVALID_RESPONSE };
+ }
+ const numNeighbors = Math.floor((respLen - 1) / RELAY_NEIGHBOR_WIRE_LEN);
+ const neighbors: RelayNeighbor[] = [];
+ let actualCount = 0;
+ for (let i = 0; i < numNeighbors; i++) {
+ const { neighbor, success } = this.s2relayNeighbor(RELAY_NEIGHBOR_WIRE_LEN, response!.slice(1 + i * RELAY_NEIGHBOR_WIRE_LEN));
+ if (success) {
+ neighbors.push(neighbor);
+ actualCount++;
+ }
+ }
+ return { neighbors, count: actualCount, error: MpClientError.SUCCESS };
+ }
- return this.ffi.createPacketFromFile(filename, recipientCount, recipients || null, keys)
- }
+ async pushPacket(packet: DataPacket): Promise<{ success: boolean; error: MpClientError }> {
+ const sendData = this.dataPacket2s(packet);
+ if (!sendData) {
+ return { success: false, error: MpClientError.BAD_ARGUMENT };
+ }
+ const sendLen = 1 + PACKET_SIZE;
+ const fullData = new Uint8Array(sendLen);
+ fullData[0] = 0x13;
+ fullData.set(sendData, 1);
+ const { response, respLen, error } = await this.clientRequest(fullData, sendLen);
+ if (error !== MpClientError.SUCCESS) {
+ return { success: false, error };
+ }
+ if (respLen === 2 && response![0] === 0x1a) {
+ return { success: response![1] !== 0, error: MpClientError.SUCCESS };
+ }
+ return { success: false, error: MpClientError.INVALID_RESPONSE };
+ }
- runProofOfWork(packet: DataPacket, leadingZeros: number): DataPacket {
- return this.ffi.runProofOfWork(packet, leadingZeros)
- }
+ async getPacket(packetHash: Uint8Array): Promise<{ packet: DataPacket; error: MpClientError }> {
+ const sendLen = 1 + 32;
+ const sendData = new Uint8Array(sendLen);
+ sendData[0] = 0x14;
+ sendData.set(packetHash, 1);
+ const { response, respLen, error } = await this.clientRequest(sendData, sendLen);
+ if (error !== MpClientError.SUCCESS) {
+ return { packet: { nonce: 0, hash: new Uint8Array(32), contents: { encrypted: false, created_time_ms: 0n, author: new Uint8Array(32), author_signing_pub: new Uint8Array(32), recipient_count: 0, recipients: [], data: new Uint8Array(65536), signature: new Uint8Array(64) } }, error };
+ }
+ if (respLen < 1 || response![0] !== 0x1b) {
+ return { packet: { nonce: 0, hash: new Uint8Array(32), contents: { encrypted: false, created_time_ms: 0n, author: new Uint8Array(32), author_signing_pub: new Uint8Array(32), recipient_count: 0, recipients: [], data: new Uint8Array(65536), signature: new Uint8Array(64) } }, error: MpClientError.INVALID_RESPONSE };
+ }
+ const { packet, success } = this.s2dataPacket(respLen - 1, response!.slice(1));
+ return { packet, error: success ? MpClientError.SUCCESS : MpClientError.DESERIALIZE_FAILED };
+ }
- generateKeyPair(): KeyPair {
- return this.ffi.generateKeyPair()
- }
+ async getFirehosePackets(offset: bigint, count: number): Promise<{ list: DataPacketList; error: MpClientError }> {
+ const sendLen = 1 + 1 + 8;
+ const sendData = new Uint8Array(sendLen);
+ sendData[0] = 0x12;
+ sendData[1] = count;
+ this.writeU64LE(sendData, offset, 2);
+ const { response, respLen, error } = await this.clientRequest(sendData, sendLen);
+ if (error !== MpClientError.SUCCESS) {
+ return { list: { packets: [], count: 0, new_offset: offset }, error };
+ }
+ if (respLen < 9 || response![0] !== 0x19) {
+ return { list: { packets: [], count: 0, new_offset: offset }, error: MpClientError.INVALID_RESPONSE };
+ }
+ const newOffset = this.readU64LE(response!, 1);
+ const availableBytes = respLen - 9;
+ const availablePackets = Math.floor(availableBytes / PACKET_SIZE);
+ const packets: DataPacket[] = [];
+ let packetCount = 0;
+ for (let i = 0; i < availablePackets; i++) {
+ const { packet, success } = this.s2dataPacket(PACKET_SIZE, response!.slice(9 + i * PACKET_SIZE));
+ if (success) {
+ packets.push(packet);
+ packetCount++;
+ }
+ }
+ return { list: { packets, count: packetCount, new_offset: newOffset }, error: MpClientError.SUCCESS };
+ }
- async queryAuthorPackets(ip: string, port: number, authorKeys: KeyPair, count: number = 10, offset: bigint = 0n): Promise<DataPacketList> {
- return this.ffi.queryAuthorPackets(ip, port, authorKeys, count, offset)
- }
+ async createPacketFromBytes(
+ input: Uint8Array,
+ inputLen: number,
+ recipientCount: number,
+ recipients: Uint8Array[],
+ authorKeys: KeyPair
+ ): Promise<DataPacket> {
+ const packet: DataPacket = {
+ nonce: 0,
+ hash: new Uint8Array(32),
+ contents: {
+ encrypted: false,
+ created_time_ms: BigInt(Date.now()),
+ author: authorKeys.encryption_pub,
+ author_signing_pub: authorKeys.signing_pub,
+ recipient_count: recipientCount > MAX_RECIPIENTS ? MAX_RECIPIENTS : recipientCount,
+ recipients: new Array(MAX_RECIPIENTS).fill(new Uint8Array(32)),
+ data: new Uint8Array(65536),
+ signature: new Uint8Array(64),
+ },
+ };
+ const copyLen = inputLen > 65536 ? 65536 : inputLen;
+ if (input && copyLen > 0) {
+ packet.contents.data.set(input.slice(0, copyLen));
+ }
+ if (recipients) {
+ for (let i = 0; i < packet.contents.recipient_count; i++) {
+ packet.contents.recipients[i] = recipients[i];
+ }
+ }
+ const msgLen = 1 + 8 + 32 + 32 + 1 + 32 * MAX_RECIPIENTS + 65536;
+ const msgBuf = new Uint8Array(msgLen);
+ msgBuf[0] = packet.contents.encrypted ? 1 : 0;
+ this.writeU64LE(msgBuf, packet.contents.created_time_ms, 1);
+ msgBuf.set(packet.contents.author, 9);
+ msgBuf.set(packet.contents.author_signing_pub, 41);
+ msgBuf[73] = packet.contents.recipient_count;
+ for (let i = 0; i < MAX_RECIPIENTS; i++) {
+ msgBuf.set(packet.contents.recipients[i], 74 + 32 * i);
+ }
+ msgBuf.set(packet.contents.data, 74 + 32 * MAX_RECIPIENTS);
+ packet.contents.signature = await this.signData(msgBuf, authorKeys.signing_priv);
+ return packet;
+ }
- // Utility methods
- static hashToHex(hash: Uint8Array): string {
- return Array.from(hash).map(b => b.toString(16).padStart(2, '0')).join('')
- }
+ async createPacketFromString(
+ input: string,
+ recipientCount: number,
+ recipients: Uint8Array[],
+ authorKeys: KeyPair
+ ): Promise<{ packet: DataPacket; error: MpClientError }> {
+ try {
+ const encoder = new TextEncoder();
+ const inputBytes = encoder.encode(input);
+ const packet = await this.createPacketFromBytes(inputBytes, inputBytes.length, recipientCount, recipients, authorKeys);
+ return { packet, error: MpClientError.SUCCESS };
+ } catch (err) {
+ return { packet: { nonce: 0, hash: new Uint8Array(32), contents: { encrypted: false, created_time_ms: 0n, author: new Uint8Array(32), author_signing_pub: new Uint8Array(32), recipient_count: 0, recipients: [], data: new Uint8Array(65536), signature: new Uint8Array(64) } }, error: MpClientError.STRING_ENCODE_FAILED };
+ }
+ }
- static hexToHash(hex: string): Uint8Array {
- const result = new Uint8Array(32)
- for (let i = 0; i < 32; i++) {
- result[i] = parseInt(hex.substr(i * 2, 2), 16)
+ async runPow(packet: DataPacket, leadingZeros: number): Promise<DataPacket> {
+ const hashInputLen = 4 + PACKET_SIZE - 4;
+ const hashInputBuffer = new Uint8Array(hashInputLen);
+ const contentsBuffer = hashInputBuffer.subarray(4);
+ contentsBuffer[0] = packet.contents.encrypted ? 1 : 0;
+ this.writeU64LE(contentsBuffer, packet.contents.created_time_ms, 1);
+ contentsBuffer.set(packet.contents.author, 9);
+ contentsBuffer.set(packet.contents.author_signing_pub, 41);
+ contentsBuffer[73] = packet.contents.recipient_count;
+ for (let i = 0; i < MAX_RECIPIENTS; i++) {
+ contentsBuffer.set(packet.contents.recipients[i], 74 + 32 * i);
+ }
+ contentsBuffer.set(packet.contents.data, 74 + 32 * MAX_RECIPIENTS);
+ contentsBuffer.set(packet.contents.signature, 74 + 32 * MAX_RECIPIENTS + 65536);
+ let nonce = 0;
+ while (true) {
+ this.writeU32LE(hashInputBuffer, nonce, 0);
+ const hash = await this.sha256(hashInputBuffer);
+ if (this.checkLeadingZeros(hash, leadingZeros)) {
+ packet.nonce = nonce;
+ packet.hash = hash;
+ break;
+ }
+ nonce++;
+ }
+ return packet;
}
- return result
- }
-
- static formatIpAddress(neighbor: RelayNeighbor): string {
- if (neighbor.hasIpv4) {
- return Array.from(neighbor.v4Addr).join('.')
- } else if (neighbor.hasIpv6) {
- const parts: string[] = []
- for (let i = 0; i < 8; i++) {
- const word = (neighbor.v6Addr[i * 2] << 8) | neighbor.v6Addr[i * 2 + 1]
- parts.push(word.toString(16))
- }
- return parts.join(':')
+
+ async queryAuthorPackets(
+ authorKeys: KeyPair,
+ count: number,
+ offset: bigint
+ ): Promise<{ list: DataPacketList; error: MpClientError }> {
+ const msg = new Uint8Array(32 + 32 + 2 + 8);
+ msg.set(authorKeys.encryption_pub, 0);
+ msg.set(authorKeys.signing_pub, 32);
+ this.writeU16LE(msg, count, 64);
+ this.writeU64LE(msg, offset, 66);
+ const sig = await this.signData(msg, authorKeys.signing_priv);
+ const sendLen = 1 + 32 + 32 + 64 + 2 + 8;
+ const sendData = new Uint8Array(sendLen);
+ let off = 0;
+ sendData[off++] = 0x16;
+ sendData.set(authorKeys.encryption_pub, off);
+ off += 32;
+ sendData.set(authorKeys.signing_pub, off);
+ off += 32;
+ sendData.set(sig, off);
+ off += 64;
+ this.writeU16LE(sendData, count, off);
+ off += 2;
+ this.writeU64LE(sendData, offset, off);
+ const { response, respLen, error } = await this.clientRequest(sendData, sendLen);
+ if (error !== MpClientError.SUCCESS) {
+ return { list: { packets: [], count: 0, new_offset: offset }, error };
+ }
+ if (respLen < 11 || response![0] !== 0x1d) {
+ return { list: { packets: [], count: 0, new_offset: offset }, error: MpClientError.INVALID_RESPONSE };
+ }
+ const respCount = this.readU16LE(response!, 1);
+ const newOffset = this.readU64LE(response!, 3);
+ const expectedLen = 11 + respCount * PACKET_SIZE;
+ if (respLen !== expectedLen) {
+ return { list: { packets: [], count: 0, new_offset: offset }, error: MpClientError.INVALID_RESPONSE };
+ }
+ const packets: DataPacket[] = [];
+ let packetCount = 0;
+ for (let i = 0; i < respCount; i++) {
+ const { packet, success } = this.s2dataPacket(PACKET_SIZE, response!.slice(11 + i * PACKET_SIZE));
+ if (success) {
+ packets.push(packet);
+ packetCount++;
+ }
+ }
+ return { list: { packets, count: packetCount, new_offset: newOffset }, error: MpClientError.SUCCESS };
}
- return 'N/A'
- }
}
-export default MoonpasteClient \ No newline at end of file
+export {
+ MpClientError,
+ RelayNeighbor,
+ RelayInfo,
+ DataPacket,
+ DataPacketList,
+ KeyPair,
+ MoonPasteClient,
+}; \ No newline at end of file