first commit
This commit is contained in:
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Compare Node compact-encoding vectors with Dart encodeBytes output.
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
NODE_OUT="$(node "$ROOT/tool/interop_node/compact_encoding_vectors.js")"
|
||||
echo "$NODE_OUT" | dart run "$ROOT/tool/interop_node/check_vectors.dart"
|
||||
@@ -0,0 +1,67 @@
|
||||
// ignore_for_file: avoid_print
|
||||
|
||||
import 'dart:convert' as convert;
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:compact_encoding/compact_encoding.dart';
|
||||
|
||||
Uint8List _hexDecode(String hex) {
|
||||
final out = Uint8List(hex.length ~/ 2);
|
||||
for (var i = 0; i < out.length; i++) {
|
||||
out[i] = int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
String _hexEncode(Uint8List bytes) =>
|
||||
bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||
|
||||
void main() async {
|
||||
final input = await stdin.transform(convert.utf8.decoder).join();
|
||||
final Map<String, dynamic> vectors =
|
||||
convert.jsonDecode(input) as Map<String, dynamic>;
|
||||
|
||||
final checks = <String, Uint8List>{
|
||||
'uint_42': encodeBytes(uint, 42),
|
||||
'uint_4200': encodeBytes(uint, 4200),
|
||||
'uint_max_safe': encodeBytes(uint, maxSafeInteger),
|
||||
'uint_0x10000': encodeBytes(uint, 0x10000),
|
||||
'int_42': encodeBytes(intZigZag, 42),
|
||||
'int_neg_4200': encodeBytes(intZigZag, -4200),
|
||||
'buffer_hi': encodeBytes(buffer, Uint8List.fromList('hi'.codeUnits)),
|
||||
'string_wheat': encodeBytes(utf8, '🌾'),
|
||||
'frame_uint_4200': encodeBytes(frame(uint), 4200),
|
||||
'ipv4': encodeBytes(ipv4, '1.2.3.4'),
|
||||
'ipv6_abbrev': encodeBytes(ipv6, '1:2::7:8'),
|
||||
'float64_162': encodeBytes(float64, 162.2377294),
|
||||
};
|
||||
|
||||
var failed = 0;
|
||||
for (final entry in checks.entries) {
|
||||
final expected = _hexDecode(vectors[entry.key] as String);
|
||||
final actual = entry.value;
|
||||
if (!_eq(expected, actual)) {
|
||||
stderr.writeln(
|
||||
'MISMATCH ${entry.key}: node=${vectors[entry.key]} dart=${_hexEncode(actual)}',
|
||||
);
|
||||
failed++;
|
||||
} else {
|
||||
print('OK ${entry.key}');
|
||||
}
|
||||
}
|
||||
|
||||
if (failed > 0) {
|
||||
stderr.writeln('FAILED $failed vectors');
|
||||
exit(1);
|
||||
}
|
||||
print('INTEROP_OK ${checks.length} vectors');
|
||||
}
|
||||
|
||||
bool _eq(Uint8List a, Uint8List b) {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Encode compact-encoding fixtures in Node for Dart cross-checks.
|
||||
*
|
||||
* Usage (from holepunch clone or with compact-encoding installed):
|
||||
* node tool/interop_node/compact_encoding_vectors.js
|
||||
*
|
||||
* Or with NODE_PATH pointing at holepunch repos.
|
||||
*/
|
||||
const path = require('path')
|
||||
|
||||
const holepunch =
|
||||
process.env.HOLEPUNCH_REPOS ||
|
||||
path.join(
|
||||
process.env.HOME || '',
|
||||
'dev/pearcli/holepunch-repos/holepunchto_repos'
|
||||
)
|
||||
|
||||
const enc = require(path.join(holepunch, 'compact-encoding'))
|
||||
|
||||
function hex(buf) {
|
||||
return Buffer.from(buf).toString('hex')
|
||||
}
|
||||
|
||||
const vectors = {
|
||||
uint_42: hex(enc.encode(enc.uint, 42)),
|
||||
uint_4200: hex(enc.encode(enc.uint, 4200)),
|
||||
uint_max_safe: hex(enc.encode(enc.uint, Number.MAX_SAFE_INTEGER)),
|
||||
uint_0x10000: hex(enc.encode(enc.uint, 0x10000)),
|
||||
int_42: hex(enc.encode(enc.int, 42)),
|
||||
int_neg_4200: hex(enc.encode(enc.int, -4200)),
|
||||
buffer_hi: hex(enc.encode(enc.buffer, Buffer.from('hi'))),
|
||||
string_wheat: hex(enc.encode(enc.string, '🌾')),
|
||||
frame_uint_4200: hex(enc.encode(enc.frame(enc.uint), 4200)),
|
||||
ipv4: hex(enc.encode(enc.ipv4, '1.2.3.4')),
|
||||
ipv6_abbrev: hex(enc.encode(enc.ipv6, '1:2::7:8')),
|
||||
float64_162: hex(enc.encode(enc.float64, 162.2377294))
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(vectors, null, 2))
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Connect to a Dart HyperDHT server (OPEN LAN).
|
||||
* Args: <bootstrap host:port> <serverPublicKeyHex>
|
||||
*/
|
||||
const HyperDHT = require('/Users/raven/dev/pearcli/holepunch-repos/holepunchto_repos/hyperdht')
|
||||
const b4a = require('b4a')
|
||||
|
||||
async function main() {
|
||||
const bootstrap = process.argv[2]
|
||||
const pkHex = process.argv[3]
|
||||
if (!bootstrap || !pkHex) {
|
||||
console.error('usage: node hyperdht_connect_client.cjs <host:port> <pkHex>')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
const dht = new HyperDHT({
|
||||
bootstrap: [bootstrap],
|
||||
ephemeral: false,
|
||||
firewalled: false,
|
||||
})
|
||||
await dht.ready()
|
||||
|
||||
const pk = b4a.from(pkHex, 'hex')
|
||||
const socket = dht.connect(pk)
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const t = setTimeout(() => reject(new Error('open timeout')), 15000)
|
||||
socket.on('open', () => {
|
||||
clearTimeout(t)
|
||||
resolve()
|
||||
})
|
||||
socket.on('error', reject)
|
||||
})
|
||||
|
||||
console.log('node client open')
|
||||
socket.write(b4a.from('hello from node'))
|
||||
|
||||
const reply = await new Promise((resolve, reject) => {
|
||||
const t = setTimeout(() => reject(new Error('data timeout')), 10000)
|
||||
socket.once('data', (data) => {
|
||||
clearTimeout(t)
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
|
||||
console.log('node got', b4a.toString(reply))
|
||||
if (b4a.toString(reply) !== 'pong from dart') {
|
||||
throw new Error('unexpected reply')
|
||||
}
|
||||
|
||||
socket.destroy()
|
||||
await dht.destroy()
|
||||
console.log('HYPERDHT_NODE_CLIENT_OK')
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Diagnose Node HyperDHT / dht-rpc against a Dart bootstrap.
|
||||
* Args: <bootstrap host:port> [serverPublicKeyHex]
|
||||
*/
|
||||
const HyperDHT = require('/Users/raven/dev/pearcli/holepunch-repos/holepunchto_repos/hyperdht')
|
||||
const b4a = require('b4a')
|
||||
|
||||
async function main() {
|
||||
const bootstrap = process.argv[2]
|
||||
const pkHex = process.argv[3]
|
||||
if (!bootstrap) {
|
||||
console.error('usage: node hyperdht_diag_client.cjs <host:port> [pkHex]')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
const dht = new HyperDHT({
|
||||
bootstrap: [bootstrap],
|
||||
ephemeral: false,
|
||||
firewalled: false,
|
||||
})
|
||||
|
||||
dht.on('persistent', () => console.log('node persistent', dht.host, dht.port))
|
||||
dht.on('nat-update', (host, port) => console.log('node nat', host, port))
|
||||
|
||||
try {
|
||||
await dht.ready()
|
||||
console.log('node ready id=', dht.id && b4a.toString(dht.id, 'hex').slice(0, 16))
|
||||
console.log('node table size', dht.table.toArray().length)
|
||||
for (const n of dht.table.toArray()) {
|
||||
console.log(
|
||||
' peer',
|
||||
n.host + ':' + n.port,
|
||||
'id=',
|
||||
n.id && b4a.toString(n.id, 'hex').slice(0, 16)
|
||||
)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('ready failed', err)
|
||||
}
|
||||
|
||||
if (pkHex) {
|
||||
const target = HyperDHT.hash(b4a.from(pkHex, 'hex'))
|
||||
console.log('findPeer target', b4a.toString(target, 'hex').slice(0, 16))
|
||||
let hits = 0
|
||||
try {
|
||||
for await (const data of dht.findPeer(b4a.from(pkHex, 'hex'))) {
|
||||
hits++
|
||||
console.log(
|
||||
'findPeer hit from',
|
||||
data.from.host + ':' + data.from.port,
|
||||
'id=',
|
||||
data.from.id && b4a.toString(data.from.id, 'hex').slice(0, 16),
|
||||
'peer=',
|
||||
data.peer && b4a.toString(data.peer.publicKey, 'hex').slice(0, 16),
|
||||
'relays=',
|
||||
data.peer && data.peer.relayAddresses && data.peer.relayAddresses.length
|
||||
)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('findPeer err', err)
|
||||
}
|
||||
console.log('findPeer hits', hits)
|
||||
|
||||
// Also try connect with explicit relay = bootstrap
|
||||
const [host, port] = bootstrap.split(':')
|
||||
const socket = dht.connect(b4a.from(pkHex, 'hex'), {
|
||||
nodes: [{ host, port: Number(port) }],
|
||||
})
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
const t = setTimeout(() => reject(new Error('open timeout')), 10000)
|
||||
socket.on('open', () => {
|
||||
clearTimeout(t)
|
||||
resolve()
|
||||
})
|
||||
socket.on('error', reject)
|
||||
})
|
||||
console.log('CONNECT_OK')
|
||||
socket.destroy()
|
||||
} catch (err) {
|
||||
console.error('connect failed', err && err.message)
|
||||
socket.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
await dht.destroy()
|
||||
console.log('DIAG_DONE')
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Firewalled Node HyperDHT client — forces holepunch (no LAN shortcut).
|
||||
* Args: <bootstrap host:port> <serverPublicKeyHex>
|
||||
*
|
||||
* Prints HYPERDHT_NODE_HOLEPUNCH_CLIENT_OK on success.
|
||||
*/
|
||||
const HyperDHT = require('/Users/raven/dev/pearcli/holepunch-repos/holepunchto_repos/hyperdht')
|
||||
const b4a = require('b4a')
|
||||
|
||||
async function main() {
|
||||
const bootstrap = process.argv[2]
|
||||
const pkHex = process.argv[3]
|
||||
if (!bootstrap || !pkHex) {
|
||||
console.error('usage: node hyperdht_holepunch_client.cjs <host:port> <pkHex>')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
// firewalled: true → handshake firewall UNKNOWN (not OPEN) → server punches.
|
||||
// localConnection: false → skip LAN ping shortcut; exercise real holepunch.
|
||||
const dht = new HyperDHT({
|
||||
bootstrap: [bootstrap],
|
||||
ephemeral: true,
|
||||
firewalled: true,
|
||||
host: '127.0.0.1',
|
||||
})
|
||||
await dht.ready()
|
||||
await dht.fullyBootstrapped()
|
||||
console.log('node client ready nodes=', dht.nodes.length, 'fw=', dht.firewalled)
|
||||
|
||||
const pk = b4a.from(pkHex, 'hex')
|
||||
let usedHolepunch = false
|
||||
const socket = dht.connect(pk, {
|
||||
localConnection: false,
|
||||
holepunch(remoteFirewall, localFirewall) {
|
||||
usedHolepunch = true
|
||||
console.log(
|
||||
'node client holepunch gate remoteFw=',
|
||||
remoteFirewall,
|
||||
'localFw=',
|
||||
localFirewall
|
||||
)
|
||||
return true
|
||||
},
|
||||
})
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const t = setTimeout(() => reject(new Error('open timeout')), 25000)
|
||||
socket.on('open', () => {
|
||||
clearTimeout(t)
|
||||
resolve()
|
||||
})
|
||||
socket.on('error', reject)
|
||||
})
|
||||
|
||||
console.log('node client open usedHolepunch=', usedHolepunch)
|
||||
// Probe-phase initiator connect often finishes before holepunch() gate.
|
||||
// OPEN path increments punches.open — reject that.
|
||||
if (dht.stats.punches.open > 0) {
|
||||
throw new Error('OPEN punch counter incremented')
|
||||
}
|
||||
console.log('node punches=', dht.stats.punches)
|
||||
|
||||
socket.write(b4a.from('hello punch from node'))
|
||||
|
||||
const reply = await new Promise((resolve, reject) => {
|
||||
const t = setTimeout(() => reject(new Error('data timeout')), 10000)
|
||||
socket.once('data', (data) => {
|
||||
clearTimeout(t)
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
|
||||
console.log('node got', b4a.toString(reply))
|
||||
if (b4a.toString(reply) !== 'pong punch from dart') {
|
||||
throw new Error('unexpected reply')
|
||||
}
|
||||
|
||||
socket.destroy()
|
||||
await dht.destroy()
|
||||
console.log('HYPERDHT_NODE_HOLEPUNCH_CLIENT_OK')
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Firewalled Node HyperDHT server for Dart holepunch client.
|
||||
* Args: <bootstrap host:port>
|
||||
* Prints: READY <pkHex>
|
||||
*/
|
||||
const HyperDHT = require('/Users/raven/dev/pearcli/holepunch-repos/holepunchto_repos/hyperdht')
|
||||
const b4a = require('b4a')
|
||||
|
||||
async function main() {
|
||||
const bootstrap = process.argv[2]
|
||||
if (!bootstrap) {
|
||||
console.error('usage: node hyperdht_holepunch_server.cjs <host:port>')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
// firewalled → advertise holepunch relays (remoteAddress() is null).
|
||||
const dht = new HyperDHT({
|
||||
bootstrap: [bootstrap],
|
||||
ephemeral: true,
|
||||
firewalled: true,
|
||||
host: '127.0.0.1',
|
||||
})
|
||||
await dht.ready()
|
||||
await dht.fullyBootstrapped()
|
||||
console.log('node server ready nodes=', dht.nodes.length)
|
||||
|
||||
const server = dht.createServer({ shareLocalAddress: false }, (socket) => {
|
||||
console.log('node server got connection')
|
||||
socket.once('data', (data) => {
|
||||
console.log('node got', b4a.toString(data))
|
||||
socket.write(b4a.from('pong punch from node'))
|
||||
setTimeout(() => process.exit(0), 400)
|
||||
})
|
||||
socket.on('error', () => {})
|
||||
})
|
||||
|
||||
await server.listen()
|
||||
console.log('READY', b4a.toString(server.publicKey, 'hex'))
|
||||
|
||||
await new Promise((resolve) => {
|
||||
process.on('SIGTERM', resolve)
|
||||
process.on('SIGINT', resolve)
|
||||
setTimeout(resolve, 45000)
|
||||
})
|
||||
|
||||
await server.close()
|
||||
await dht.destroy()
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Encode HyperDHT message fixtures with Node hyperdht codecs.
|
||||
* Prints JSON lines: { "name", "hex" }
|
||||
*/
|
||||
const c = require('compact-encoding')
|
||||
const b4a = require('b4a')
|
||||
const m = require('/Users/raven/dev/pearcli/holepunch-repos/holepunchto_repos/hyperdht/lib/messages.js')
|
||||
|
||||
function enc(codec, value) {
|
||||
const state = { start: 0, end: 0, buffer: null }
|
||||
codec.preencode(state, value)
|
||||
state.buffer = b4a.allocUnsafe(state.end)
|
||||
codec.encode(state, value)
|
||||
return b4a.toString(state.buffer, 'hex')
|
||||
}
|
||||
|
||||
const pk = b4a.alloc(32, 9)
|
||||
const sig = b4a.alloc(64, 7)
|
||||
const token = b4a.alloc(32)
|
||||
token[31] = 1
|
||||
|
||||
const vectors = [
|
||||
[
|
||||
'handshake',
|
||||
enc(m.handshake, {
|
||||
mode: 1,
|
||||
noise: b4a.from([0xaa, 0xbb, 0xcc]),
|
||||
peerAddress: { host: '1.2.3.4', port: 1234 },
|
||||
relayAddress: { host: '5.6.7.8', port: 5678 },
|
||||
}),
|
||||
],
|
||||
[
|
||||
'noisePayload',
|
||||
enc(m.noisePayload, {
|
||||
version: 1,
|
||||
error: 0,
|
||||
firewall: 2,
|
||||
holepunch: null,
|
||||
addresses4: [{ host: '10.0.0.1', port: 49737 }],
|
||||
addresses6: [],
|
||||
udx: { version: 1, reusableSocket: true, id: 42, seq: 7 },
|
||||
secretStream: {},
|
||||
relayThrough: null,
|
||||
relayAddresses: null,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'announce',
|
||||
enc(m.announce, {
|
||||
peer: {
|
||||
publicKey: pk,
|
||||
relayAddresses: [{ host: '8.8.8.8', port: 53 }],
|
||||
},
|
||||
refresh: null,
|
||||
signature: null,
|
||||
bump: 3,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'holepunchPayload',
|
||||
enc(m.holepunchPayload, {
|
||||
error: 0,
|
||||
firewall: 1,
|
||||
round: 2,
|
||||
connected: true,
|
||||
punching: true,
|
||||
addresses: [{ host: '1.1.1.1', port: 80 }],
|
||||
remoteAddress: null,
|
||||
token,
|
||||
remoteToken: null,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'peer',
|
||||
enc(m.peer, {
|
||||
publicKey: pk,
|
||||
relayAddresses: [{ host: '127.0.0.1', port: 49737 }],
|
||||
}),
|
||||
],
|
||||
]
|
||||
|
||||
for (const [name, hex] of vectors) {
|
||||
console.log(JSON.stringify({ name, hex }))
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Node HyperDHT client → Dart app via Dart blind-relay (force-relay).
|
||||
*
|
||||
* Args: <bootstrap> <appPkHex> <relayPkHex>
|
||||
*
|
||||
* Handshake must be DHT-relayed (do NOT pass relayAddresses to the app).
|
||||
* Combined with Dart advertising holepunch relays + UNKNOWN firewall, Node
|
||||
* skips the direct-connect shortcuts and waits on blind-relay after aborting
|
||||
* holepunch.
|
||||
*/
|
||||
const HyperDHT = require('/Users/raven/dev/pearcli/holepunch-repos/holepunchto_repos/hyperdht')
|
||||
const b4a = require('b4a')
|
||||
|
||||
async function main() {
|
||||
const bootstrap = process.argv[2]
|
||||
const appPkHex = process.argv[3]
|
||||
const relayPkHex = process.argv[4]
|
||||
if (!bootstrap || !appPkHex || !relayPkHex) {
|
||||
console.error(
|
||||
'usage: node hyperdht_relay_client.cjs <bootstrap> <appPk> <relayPk>'
|
||||
)
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
const dht = new HyperDHT({
|
||||
bootstrap: [bootstrap],
|
||||
ephemeral: true,
|
||||
firewalled: true,
|
||||
})
|
||||
await dht.ready()
|
||||
|
||||
const appPk = b4a.from(appPkHex, 'hex')
|
||||
const relayPk = b4a.from(relayPkHex, 'hex')
|
||||
|
||||
const [bootHost, bootPortStr] = bootstrap.split(':')
|
||||
const bootPort = Number(bootPortStr)
|
||||
|
||||
console.error('node connecting…')
|
||||
const socket = dht.connect(appPk, {
|
||||
relayThrough: relayPk,
|
||||
holepunch: () => false,
|
||||
// Force DHT-relayed handshake via bootstrap — if Node findPeer hits the
|
||||
// app itself, !relayed triggers an immediate direct onsocket and races
|
||||
// the blind-relay path.
|
||||
relayAddresses: [{ host: bootHost, port: bootPort }],
|
||||
localConnection: false,
|
||||
})
|
||||
|
||||
socket.on('error', (err) => console.error('node socket error', err.message))
|
||||
socket.on('close', () => console.error('node socket close'))
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const t = setTimeout(() => reject(new Error('open timeout')), 45000)
|
||||
socket.on('open', () => {
|
||||
clearTimeout(t)
|
||||
console.error(
|
||||
'node open remoteId=',
|
||||
socket.rawStream?.remoteId,
|
||||
'remote=',
|
||||
socket.rawStream?.remoteHost + ':' + socket.rawStream?.remotePort
|
||||
)
|
||||
resolve()
|
||||
})
|
||||
socket.on('error', reject)
|
||||
})
|
||||
|
||||
const chunks = []
|
||||
const reply = new Promise((resolve, reject) => {
|
||||
const t = setTimeout(
|
||||
() =>
|
||||
reject(
|
||||
new Error(
|
||||
'data timeout got=' + chunks.map((c) => b4a.toString(c)).join('|')
|
||||
)
|
||||
),
|
||||
20000
|
||||
)
|
||||
socket.on('data', (data) => {
|
||||
chunks.push(data)
|
||||
console.error('node data', b4a.toString(data))
|
||||
if (b4a.toString(data).startsWith('pong:')) {
|
||||
clearTimeout(t)
|
||||
resolve(data)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
await new Promise((r) => setTimeout(r, 200))
|
||||
console.error('node writing hello')
|
||||
socket.write(b4a.from('hello from node'))
|
||||
|
||||
const data = await reply
|
||||
console.error('node got', b4a.toString(data))
|
||||
if (b4a.toString(data) !== 'pong:hello from node') {
|
||||
throw new Error('unexpected reply: ' + b4a.toString(data))
|
||||
}
|
||||
|
||||
socket.destroy()
|
||||
await dht.destroy()
|
||||
console.log('HYPERDHT_NODE_RELAY_CLIENT_OK')
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Node blind-relay only (app + client are Dart).
|
||||
* Args: <bootstrap host:port>
|
||||
* Prints: READY <relayPkHex>
|
||||
*/
|
||||
const HyperDHT = require('/Users/raven/dev/pearcli/holepunch-repos/holepunchto_repos/hyperdht')
|
||||
const RelayServer = require('/Users/raven/dev/pearcli/holepunch-repos/holepunchto_repos/blind-relay').Server
|
||||
const b4a = require('b4a')
|
||||
|
||||
async function main() {
|
||||
const bootstrap = process.argv[2]
|
||||
if (!bootstrap) {
|
||||
console.error('usage: node hyperdht_relay_only.cjs <host:port>')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
const dht = new HyperDHT({
|
||||
bootstrap: [bootstrap],
|
||||
ephemeral: false,
|
||||
firewalled: false,
|
||||
})
|
||||
await dht.ready()
|
||||
|
||||
const relay = new RelayServer({
|
||||
createStream(opts) {
|
||||
return dht.createRawStream({ ...opts, framed: true })
|
||||
},
|
||||
})
|
||||
|
||||
const server = dht.createServer(function (socket) {
|
||||
console.error('node relay accepted peer')
|
||||
const session = relay.accept(socket, { id: socket.remotePublicKey })
|
||||
session.on('error', (err) => console.error('relay session', err.message))
|
||||
})
|
||||
await server.listen()
|
||||
console.log('READY', b4a.toString(server.publicKey, 'hex'))
|
||||
|
||||
// Stay alive until stdin closes or 60s.
|
||||
await new Promise((resolve) => {
|
||||
const t = setTimeout(resolve, 60000)
|
||||
process.stdin.on('end', () => {
|
||||
clearTimeout(t)
|
||||
resolve()
|
||||
})
|
||||
process.stdin.resume()
|
||||
})
|
||||
|
||||
await relay.close()
|
||||
await dht.destroy()
|
||||
console.log('HYPERDHT_NODE_RELAY_ONLY_OK')
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Node HyperDHT server for Dart client interop.
|
||||
* Args: <bootstrap host:port>
|
||||
* Prints: READY <pkHex>
|
||||
* Expects one connection, replies to first payload, then exits.
|
||||
*/
|
||||
const HyperDHT = require('/Users/raven/dev/pearcli/holepunch-repos/holepunchto_repos/hyperdht')
|
||||
const b4a = require('b4a')
|
||||
|
||||
async function main() {
|
||||
const bootstrap = process.argv[2]
|
||||
if (!bootstrap) {
|
||||
console.error('usage: node hyperdht_server.cjs <host:port>')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
const dht = new HyperDHT({
|
||||
bootstrap: [bootstrap],
|
||||
ephemeral: false,
|
||||
firewalled: false,
|
||||
})
|
||||
await dht.ready()
|
||||
|
||||
const server = dht.createServer((socket) => {
|
||||
console.log('node server got connection')
|
||||
socket.once('data', (data) => {
|
||||
console.log('node got', b4a.toString(data))
|
||||
socket.write(b4a.from('pong from node'))
|
||||
// Allow write to flush, then exit cleanly (parent may also SIGTERM).
|
||||
setTimeout(() => process.exit(0), 250)
|
||||
})
|
||||
socket.on('error', () => {})
|
||||
})
|
||||
|
||||
await server.listen()
|
||||
console.log('READY', b4a.toString(server.publicKey, 'hex'))
|
||||
|
||||
// Keep alive until parent kills or we get a signal.
|
||||
await new Promise((resolve) => {
|
||||
process.on('SIGTERM', resolve)
|
||||
process.on('SIGINT', resolve)
|
||||
setTimeout(resolve, 30000)
|
||||
})
|
||||
|
||||
await server.close()
|
||||
await dht.destroy()
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Node Hyperswarm client — joins topic (client-only) against Dart bootstrap.
|
||||
* Args: <bootstrap host:port> <topicHex>
|
||||
*/
|
||||
const Hyperswarm = require('/Users/raven/dev/pearcli/holepunch-repos/holepunchto_repos/hyperswarm')
|
||||
const b4a = require('b4a')
|
||||
|
||||
async function main() {
|
||||
const bootstrap = process.argv[2]
|
||||
const topicHex = process.argv[3]
|
||||
if (!bootstrap || !topicHex) {
|
||||
console.error('usage: node hyperswarm_client.cjs <host:port> <topicHex>')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
const swarm = new Hyperswarm({
|
||||
bootstrap: [bootstrap],
|
||||
ephemeral: false,
|
||||
firewalled: false,
|
||||
})
|
||||
|
||||
const done = new Promise((resolve, reject) => {
|
||||
const t = setTimeout(() => reject(new Error('connection timeout')), 25000)
|
||||
swarm.on('connection', (conn) => {
|
||||
console.log('node client connection')
|
||||
conn.on('error', () => {})
|
||||
conn.write(b4a.from('hello swarm from node'))
|
||||
conn.once('data', (data) => {
|
||||
clearTimeout(t)
|
||||
console.log('node got', b4a.toString(data))
|
||||
if (b4a.toString(data) !== 'pong swarm from dart') {
|
||||
reject(new Error('unexpected reply'))
|
||||
return
|
||||
}
|
||||
conn.end()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const topic = b4a.from(topicHex, 'hex')
|
||||
swarm.join(topic, { server: false, client: true })
|
||||
await swarm.flush()
|
||||
console.log('node client flushed')
|
||||
|
||||
await done
|
||||
await swarm.destroy()
|
||||
console.log('HYPERSWARM_NODE_CLIENT_OK')
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Node Hyperswarm client → Dart Hyperswarm app via Dart blind-relay.
|
||||
*
|
||||
* Args: <bootstrap host:port> <topicHex> <relayPkHex>
|
||||
*
|
||||
* Monkey-patches dht.connect with the same force-relay-friendly opts as
|
||||
* hyperdht_relay_client.cjs (DHT-relayed handshake, holepunch aborted,
|
||||
* localConnection: false) so Node does not race a direct onsocket against
|
||||
* Dart's forceRelay wait.
|
||||
*/
|
||||
const Hyperswarm = require('/Users/raven/dev/pearcli/holepunch-repos/holepunchto_repos/hyperswarm')
|
||||
const b4a = require('b4a')
|
||||
|
||||
async function main() {
|
||||
const bootstrap = process.argv[2]
|
||||
const topicHex = process.argv[3]
|
||||
const relayPkHex = process.argv[4]
|
||||
if (!bootstrap || !topicHex || !relayPkHex) {
|
||||
console.error(
|
||||
'usage: node hyperswarm_relay_client.cjs <host:port> <topicHex> <relayPk>'
|
||||
)
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
const [bootHost, bootPortStr] = bootstrap.split(':')
|
||||
const bootPort = Number(bootPortStr)
|
||||
const relayPk = b4a.from(relayPkHex, 'hex')
|
||||
const topic = b4a.from(topicHex, 'hex')
|
||||
|
||||
const swarm = new Hyperswarm({
|
||||
bootstrap: [bootstrap],
|
||||
ephemeral: true,
|
||||
firewalled: true,
|
||||
// Always advertise relayThrough (bypass randomized/force gate).
|
||||
relayThrough: () => relayPk,
|
||||
})
|
||||
|
||||
const origConnect = swarm.dht.connect.bind(swarm.dht)
|
||||
swarm.dht.connect = (publicKey, opts = {}) =>
|
||||
origConnect(publicKey, {
|
||||
...opts,
|
||||
relayThrough: relayPk,
|
||||
holepunch: () => false,
|
||||
localConnection: false,
|
||||
// Force DHT-relayed handshake via bootstrap — same as HyperDHT relay client.
|
||||
relayAddresses: [{ host: bootHost, port: bootPort }],
|
||||
})
|
||||
|
||||
const done = new Promise((resolve, reject) => {
|
||||
const t = setTimeout(() => reject(new Error('connection timeout')), 45000)
|
||||
swarm.on('connection', (conn) => {
|
||||
console.error('node swarm connection')
|
||||
conn.on('error', (err) => console.error('node conn error', err.message))
|
||||
|
||||
const chunks = []
|
||||
const reply = new Promise((res, rej) => {
|
||||
const dt = setTimeout(
|
||||
() =>
|
||||
rej(
|
||||
new Error(
|
||||
'data timeout got=' + chunks.map((c) => b4a.toString(c)).join('|')
|
||||
)
|
||||
),
|
||||
20000
|
||||
)
|
||||
conn.on('data', (data) => {
|
||||
chunks.push(data)
|
||||
console.error('node data', b4a.toString(data))
|
||||
if (b4a.toString(data).startsWith('pong:')) {
|
||||
clearTimeout(dt)
|
||||
res(data)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
console.error('node writing hello')
|
||||
conn.write(b4a.from('hello swarm from node'))
|
||||
}, 200)
|
||||
|
||||
reply
|
||||
.then((data) => {
|
||||
clearTimeout(t)
|
||||
if (b4a.toString(data) !== 'pong:hello swarm from node') {
|
||||
reject(new Error('unexpected reply: ' + b4a.toString(data)))
|
||||
return
|
||||
}
|
||||
conn.end()
|
||||
resolve()
|
||||
})
|
||||
.catch(reject)
|
||||
})
|
||||
})
|
||||
|
||||
swarm.join(topic, { server: false, client: true })
|
||||
await swarm.flush()
|
||||
console.error('node client flushed')
|
||||
|
||||
await done
|
||||
await swarm.destroy()
|
||||
console.log('HYPERSWARM_NODE_RELAY_CLIENT_OK')
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Node Hyperswarm server — joins topic (server-only) on Dart bootstrap.
|
||||
* Args: <bootstrap host:port> <topicHex>
|
||||
* Prints: READY
|
||||
*/
|
||||
const Hyperswarm = require('/Users/raven/dev/pearcli/holepunch-repos/holepunchto_repos/hyperswarm')
|
||||
const b4a = require('b4a')
|
||||
|
||||
async function main() {
|
||||
const bootstrap = process.argv[2]
|
||||
const topicHex = process.argv[3]
|
||||
if (!bootstrap || !topicHex) {
|
||||
console.error('usage: node hyperswarm_server.cjs <host:port> <topicHex>')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
const swarm = new Hyperswarm({
|
||||
bootstrap: [bootstrap],
|
||||
ephemeral: false,
|
||||
firewalled: false,
|
||||
})
|
||||
|
||||
swarm.on('connection', (conn) => {
|
||||
console.log('node server connection')
|
||||
conn.on('error', () => {})
|
||||
conn.once('data', (data) => {
|
||||
console.log('node got', b4a.toString(data))
|
||||
conn.write(b4a.from('pong swarm from node'))
|
||||
setTimeout(() => process.exit(0), 400)
|
||||
})
|
||||
})
|
||||
|
||||
const topic = b4a.from(topicHex, 'hex')
|
||||
await swarm.join(topic, { server: true, client: false }).flushed()
|
||||
console.log('READY')
|
||||
|
||||
await new Promise((resolve) => {
|
||||
process.on('SIGTERM', resolve)
|
||||
process.on('SIGINT', resolve)
|
||||
setTimeout(resolve, 45000)
|
||||
})
|
||||
|
||||
await swarm.destroy()
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Deterministic XX + Ed25519 vectors for Dart interop.
|
||||
* Run from noise-curve-ed directory (npm install there first).
|
||||
*
|
||||
* Note: noise-handshake zeros ephemeral key buffers in place on final();
|
||||
* capture hex strings immediately after each message.
|
||||
*/
|
||||
const path = require('path')
|
||||
const ROOT = '/Users/raven/dev/pearcli/holepunch-repos/holepunchto_repos'
|
||||
const Noise = require(path.join(ROOT, 'noise-handshake'))
|
||||
const curve = require(path.join(ROOT, 'noise-curve-ed'))
|
||||
|
||||
function hex(b) {
|
||||
return Buffer.from(b).toString('hex')
|
||||
}
|
||||
|
||||
function copy(b) {
|
||||
return Buffer.from(hex(b), 'hex')
|
||||
}
|
||||
|
||||
const seeds = {
|
||||
initiatorStatic: Buffer.from('01'.repeat(32), 'hex'),
|
||||
responderStatic: Buffer.from('02'.repeat(32), 'hex'),
|
||||
initiatorEph: Buffer.from('03'.repeat(32), 'hex'),
|
||||
responderEph: Buffer.from('04'.repeat(32), 'hex'),
|
||||
}
|
||||
|
||||
const is = curve.generateKeyPair(seeds.initiatorStatic)
|
||||
const rs = curve.generateKeyPair(seeds.responderStatic)
|
||||
const ie = curve.generateKeyPair(seeds.initiatorEph)
|
||||
const re = curve.generateKeyPair(seeds.responderEph)
|
||||
|
||||
const initiator = new Noise('XX', true, is, { curve })
|
||||
const responder = new Noise('XX', false, rs, { curve })
|
||||
|
||||
initiator.initialise(Buffer.alloc(0))
|
||||
responder.initialise(Buffer.alloc(0))
|
||||
|
||||
initiator.e = ie
|
||||
const m1Hex = hex(initiator.send())
|
||||
responder.recv(copy(Buffer.from(m1Hex, 'hex')))
|
||||
|
||||
responder.e = re
|
||||
const m2Hex = hex(responder.send())
|
||||
initiator.recv(copy(Buffer.from(m2Hex, 'hex')))
|
||||
|
||||
const m3Hex = hex(initiator.send())
|
||||
responder.recv(copy(Buffer.from(m3Hex, 'hex')))
|
||||
|
||||
const out = {
|
||||
m1: m1Hex,
|
||||
m2: m2Hex,
|
||||
m3: m3Hex,
|
||||
initiatorRx: hex(initiator.rx),
|
||||
initiatorTx: hex(initiator.tx),
|
||||
responderRx: hex(responder.rx),
|
||||
responderTx: hex(responder.tx),
|
||||
}
|
||||
console.log(JSON.stringify(out))
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "hyperdart-interop-node",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"b4a": "^1.8.1",
|
||||
"compact-encoding": "^2.19.2",
|
||||
"protomux": "^3.11.0",
|
||||
"streamx": "^2.28.0",
|
||||
"udx-native": "1.20.7"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Protomux peer over newline-hex framed duplex (stdin ↔ stdout).
|
||||
* Pairs protocol `interop` and echoes string messages with an `echo:` prefix.
|
||||
*/
|
||||
const Protomux = require('protomux')
|
||||
const c = require('compact-encoding')
|
||||
const { Duplex } = require('streamx')
|
||||
const readline = require('readline')
|
||||
|
||||
const stream = new Duplex({
|
||||
write (data, cb) {
|
||||
process.stdout.write(Buffer.from(data).toString('hex') + '\n')
|
||||
cb(null)
|
||||
}
|
||||
})
|
||||
|
||||
const rl = readline.createInterface({ input: process.stdin })
|
||||
rl.on('line', (line) => {
|
||||
const s = line.trim()
|
||||
if (!s) return
|
||||
stream.push(Buffer.from(s, 'hex'))
|
||||
})
|
||||
rl.on('close', () => {
|
||||
stream.push(null)
|
||||
})
|
||||
|
||||
const mux = new Protomux(stream)
|
||||
|
||||
mux.pair({ protocol: 'interop' }, () => {
|
||||
const ch = mux.createChannel({ protocol: 'interop' })
|
||||
const echo = ch.addMessage({
|
||||
encoding: c.string,
|
||||
onmessage (m) {
|
||||
echo.send('echo:' + m)
|
||||
}
|
||||
})
|
||||
ch.open()
|
||||
})
|
||||
|
||||
process.stdout.write('READY\n')
|
||||
@@ -0,0 +1,10 @@
|
||||
name: interop_check_vectors
|
||||
description: Temporary runner for Node↔Dart compact-encoding vector check.
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.0
|
||||
|
||||
dependencies:
|
||||
compact_encoding:
|
||||
path: ../../packages/compact_encoding
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
UDX_PKG="$ROOT/packages/udx"
|
||||
INTEROP="$ROOT/tool/interop_node"
|
||||
PREFIX="${TMPDIR:-/tmp}/hyperdart_udx_$$"
|
||||
|
||||
# Ensure native lib built
|
||||
if [[ ! -f "$UDX_PKG/native/build/libhyper_udx.dylib" && ! -f "$UDX_PKG/native/build/libhyper_udx.so" ]]; then
|
||||
bash "$UDX_PKG/tool/build_native.sh"
|
||||
fi
|
||||
|
||||
# Ensure Node udx-native available
|
||||
if [[ ! -d "$INTEROP/node_modules/udx-native" ]]; then
|
||||
(cd "$INTEROP" && npm install [email protected] --no-save 2>/dev/null || npm install [email protected])
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
rm -f "$PREFIX.node" "$PREFIX.dart"
|
||||
kill ${NODE_PID:-} 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "=== Dart client → Node echo-server ==="
|
||||
rm -f "$PREFIX.node" "$PREFIX.dart"
|
||||
node "$INTEROP/udx_node_peer.js" echo-server "$PREFIX" &
|
||||
NODE_PID=$!
|
||||
(cd "$UDX_PKG" && dart run bin/node_interop.dart echo-client "$PREFIX")
|
||||
wait $NODE_PID
|
||||
NODE_PID=
|
||||
|
||||
echo "=== Node client → Dart echo-server ==="
|
||||
rm -f "$PREFIX.node" "$PREFIX.dart"
|
||||
node "$INTEROP/udx_node_peer.js" echo-client "$PREFIX" &
|
||||
NODE_PID=$!
|
||||
(cd "$UDX_PKG" && dart run bin/node_interop.dart echo-server "$PREFIX")
|
||||
wait $NODE_PID
|
||||
NODE_PID=
|
||||
|
||||
echo "UDX_NODE_INTEROP_OK"
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* TCP responder for Dart↔Node secret-stream interop.
|
||||
* Usage: node secret_stream_server.js
|
||||
* Prints: PORT <n>
|
||||
* Expects one encrypted message "ping-from-dart", replies "pong-from-node", exits.
|
||||
*/
|
||||
const net = require('net')
|
||||
const path = require('path')
|
||||
const ROOT = '/Users/raven/dev/pearcli/holepunch-repos/holepunchto_repos'
|
||||
const NoiseSecretStream = require(path.join(ROOT, 'hyperswarm-secret-stream'))
|
||||
|
||||
const server = net.createServer((socket) => {
|
||||
const s = new NoiseSecretStream(false, socket)
|
||||
s.on('data', (data) => {
|
||||
const msg = Buffer.from(data).toString()
|
||||
if (msg === 'ping-from-dart') {
|
||||
s.write(Buffer.from('pong-from-node'))
|
||||
}
|
||||
})
|
||||
s.on('error', (err) => {
|
||||
console.error('stream error', err)
|
||||
process.exit(1)
|
||||
})
|
||||
})
|
||||
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
const { port } = server.address()
|
||||
console.log('PORT', port)
|
||||
})
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Node udx-native peer for Dart interop.
|
||||
*
|
||||
* Coord files (prefix from argv[2]):
|
||||
* <prefix>.node — written by this process: "<port>"
|
||||
* <prefix>.dart — written by Dart: "<port>"
|
||||
*
|
||||
* Stream ids: Node=2, Dart=1 (matches udx-native makeTwoStreams style).
|
||||
*
|
||||
* Mode argv[1]:
|
||||
* echo-server — wait for Dart connect, echo one message, exit
|
||||
* echo-client — connect to Dart, send ping, expect pong, exit
|
||||
*/
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const UDX = require('udx-native')
|
||||
|
||||
const mode = process.argv[2] || 'echo-server'
|
||||
const prefix = process.argv[3]
|
||||
if (!prefix) {
|
||||
console.error('usage: node udx_node_peer.js <echo-server|echo-client> <coord-prefix>')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
const NODE_ID = 2
|
||||
const DART_ID = 1
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((r) => setTimeout(r, ms))
|
||||
}
|
||||
|
||||
async function waitFile(file, timeoutMs = 10000) {
|
||||
const start = Date.now()
|
||||
while (!fs.existsSync(file)) {
|
||||
if (Date.now() - start > timeoutMs) throw new Error('timeout waiting for ' + file)
|
||||
await sleep(20)
|
||||
}
|
||||
return fs.readFileSync(file, 'utf8').trim()
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const udx = new UDX()
|
||||
const socket = udx.createSocket()
|
||||
socket.bind(0, '127.0.0.1')
|
||||
const nodePort = socket.address().port
|
||||
fs.writeFileSync(prefix + '.node', String(nodePort))
|
||||
console.log('NODE_PORT', nodePort)
|
||||
|
||||
const dartPort = parseInt(await waitFile(prefix + '.dart'), 10)
|
||||
console.log('DART_PORT', dartPort)
|
||||
|
||||
const stream = udx.createStream(NODE_ID)
|
||||
stream.connect(socket, DART_ID, dartPort, '127.0.0.1')
|
||||
|
||||
if (mode === 'echo-server') {
|
||||
await new Promise((resolve, reject) => {
|
||||
const t = setTimeout(() => reject(new Error('echo-server timeout')), 10000)
|
||||
stream.once('data', (data) => {
|
||||
clearTimeout(t)
|
||||
const msg = Buffer.from(data).toString()
|
||||
console.log('RECV', msg)
|
||||
stream.write(Buffer.from('echo: ' + msg))
|
||||
stream.end()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
await new Promise((r) => stream.once('close', r))
|
||||
console.log('NODE_ECHO_SERVER_OK')
|
||||
} else if (mode === 'echo-client') {
|
||||
await new Promise((resolve, reject) => {
|
||||
const t = setTimeout(() => reject(new Error('echo-client timeout')), 10000)
|
||||
stream.once('data', (data) => {
|
||||
clearTimeout(t)
|
||||
const msg = Buffer.from(data).toString()
|
||||
console.log('RECV', msg)
|
||||
if (msg !== 'pong-from-dart') {
|
||||
reject(new Error('unexpected: ' + msg))
|
||||
return
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
stream.write(Buffer.from('ping-from-node'))
|
||||
})
|
||||
stream.end()
|
||||
await new Promise((r) => stream.once('close', r))
|
||||
console.log('NODE_ECHO_CLIENT_OK')
|
||||
} else {
|
||||
throw new Error('unknown mode ' + mode)
|
||||
}
|
||||
|
||||
socket.close()
|
||||
// Give uv a tick to settle
|
||||
await sleep(50)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user