@threadedstack/wmd-web 0.1.12 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/net/wisp.js CHANGED
@@ -24,6 +24,9 @@
24
24
  // 0xE4 wmd-dns packet 0xE5 LOOKUP qtype u16 | name utf8 client -> server
25
25
  // packet 0xE6 ANSWER rcode u8 | ttl u32 | count u8 | addresses | reason utf8
26
26
  // 0xE7 wmd-instance (no packet) client metadata: a 16-byte instance id
27
+ // 0xE9 wmd-reason packet 0xE8 REASON kind u8 | until f64 | rule length u8 | rule | words
28
+ // server -> client: why a refusal or slowdown,
29
+ // and when it lifts (stream 0: the session)
27
30
 
28
31
  export const SUBPROTOCOL = 'wisp-v2'
29
32
  export const VERSION = { major: 2, minor: 0 }
@@ -33,14 +36,18 @@ export const INSTANCE_BYTES = 16
33
36
 
34
37
  export const PT = {
35
38
  CONNECT: 0x01, DATA: 0x02, CONTINUE: 0x03, CLOSE: 0x04, INFO: 0x05,
36
- CREDIT: 0xe0, SHUTDOWN: 0xe3, LOOKUP: 0xe5, ANSWER: 0xe6,
39
+ CREDIT: 0xe0, SHUTDOWN: 0xe3, LOOKUP: 0xe5, ANSWER: 0xe6, REASON: 0xe8,
37
40
  }
38
41
 
39
42
  export const EXT = {
40
43
  UDP: 0x01, PASSWORD: 0x02, KEY: 0x03, MOTD: 0x04, CONFIRM: 0x05,
41
- WMD_CREDIT: 0xe1, WMD_HALFCLOSE: 0xe2, WMD_DNS: 0xe4, WMD_INSTANCE: 0xe7,
44
+ WMD_CREDIT: 0xe1, WMD_HALFCLOSE: 0xe2, WMD_DNS: 0xe4, WMD_INSTANCE: 0xe7, WMD_REASON: 0xe9,
42
45
  }
43
46
 
47
+ /** REASON kinds: a refusal (a CLOSE follows), a slowdown, and a slowdown that has lifted. */
48
+ export const REASON_KIND = { REFUSED: 1, SLOWED: 2, CLEARED: 3 }
49
+ const RULE_MAX = 64
50
+
44
51
  export const STREAM = { TCP: 0x01, UDP: 0x02 }
45
52
 
46
53
  /** CLOSE reasons (spec, plus the extension-specified auth reasons). */
@@ -121,6 +128,21 @@ export function lookupPacket(id, name, qtype) {
121
128
  return packet(PT.LOOKUP, id, p)
122
129
  }
123
130
 
131
+ /** until: ms since 1970 UTC when the rule lifts, 0 when it has no time. */
132
+ export function reasonPacket(id, kind, rule, until, words) {
133
+ if (!/^[\x20-\x7e]*$/.test(rule)) throw new RangeError('wisp: a rule name is ASCII')
134
+ if (rule.length > RULE_MAX) throw new RangeError(`wisp: a rule name is at most ${RULE_MAX} characters`)
135
+ const w = enc.encode(words || '')
136
+ const p = new Uint8Array(10 + rule.length + w.length)
137
+ const dv = new DataView(p.buffer)
138
+ p[0] = kind
139
+ dv.setFloat64(1, until || 0, true)
140
+ p[9] = rule.length
141
+ for (let i = 0; i < rule.length; i++) p[10 + i] = rule.charCodeAt(i)
142
+ p.set(w, 10 + rule.length)
143
+ return packet(PT.REASON, id, p)
144
+ }
145
+
124
146
  /** addrs: arrays of 4 (A) or 16 (AAAA) bytes. */
125
147
  export function answerPacket(id, rcode, ttl, addrs = [], reason = '') {
126
148
  const d = enc.encode(reason)
@@ -181,6 +203,7 @@ export const ext = {
181
203
  },
182
204
  wmdHalfclose: () => ({ id: EXT.WMD_HALFCLOSE, data: Uint8Array.of(WMD_EXT_VERSION) }),
183
205
  wmdDns: () => ({ id: EXT.WMD_DNS, data: Uint8Array.of(WMD_EXT_VERSION) }),
206
+ wmdReason: () => ({ id: EXT.WMD_REASON, data: Uint8Array.of(WMD_EXT_VERSION) }),
184
207
  /** The client sends its 16-byte id; the server lists the extension with no id. */
185
208
  wmdInstance(idBytes) {
186
209
  if (!idBytes) return { id: EXT.WMD_INSTANCE, data: Uint8Array.of(WMD_EXT_VERSION) }
@@ -229,6 +252,7 @@ export function readExt(e, from) {
229
252
  return { version: d[0] }
230
253
  case EXT.WMD_HALFCLOSE:
231
254
  case EXT.WMD_DNS:
255
+ case EXT.WMD_REASON:
232
256
  if (d.length < 1) return { error: 'wmd extension without a version' }
233
257
  return { version: d[0] }
234
258
  case EXT.WMD_INSTANCE:
@@ -307,6 +331,16 @@ export function parse(u8) {
307
331
  m.count = p[5]
308
332
  m.rest = p.subarray(6) // the addresses (their size depends on the query), then the reason
309
333
  break
334
+ case PT.REASON: {
335
+ if (p.length < 10 || p.length < 10 + p[9]) return bad('REASON too short')
336
+ m.kind = p[0]
337
+ m.until = dv.getFloat64(6, true)
338
+ m.rule = String.fromCharCode(...p.subarray(10, 10 + p[9]))
339
+ const w = text(p.subarray(10 + p[9]))
340
+ if (w === null) return bad('REASON words are not UTF-8')
341
+ m.words = w
342
+ break
343
+ }
310
344
  case PT.INFO: {
311
345
  if (p.length < 2) return bad('INFO too short')
312
346
  m.version = { major: p[0], minor: p[1] }
@@ -343,15 +377,15 @@ export function answerAddrs(m, qtype) {
343
377
  // Both machines are pure: feed packets in, get packets to send and an outcome out. The caller
344
378
  // owns the WebSocket and the timers.
345
379
 
346
- const WMD_EXTS = new Set([EXT.WMD_CREDIT, EXT.WMD_HALFCLOSE, EXT.WMD_DNS, EXT.WMD_INSTANCE])
380
+ const WMD_EXTS = new Set([EXT.WMD_CREDIT, EXT.WMD_HALFCLOSE, EXT.WMD_DNS, EXT.WMD_INSTANCE, EXT.WMD_REASON])
347
381
 
348
382
  /**
349
383
  * The client side. `exts` are the extensions the client offers (built with `ext.*`, without
350
384
  * password auth); `password` is {user, pass} or null.
351
385
  *
352
386
  * step(pkt) returns {send: Uint8Array[], done?: {version: 1|2, buffer, server: Map, common: Set,
353
- * motd}, fail?: {reason, words, state}}. state is one of 'auth-required', 'auth-refused',
354
- * 'incompatible', 'protocol'.
387
+ * motd}, fail?: {reason, words, state, rule?, until?}}. state is one of 'auth-required', 'auth-refused',
388
+ * 'incompatible', 'protocol', or 'limited' when the relay said why (wmd-reason).
355
389
  */
356
390
  export class ClientHandshake {
357
391
  constructor({ exts = [], password = null } = {}) {
@@ -412,7 +446,19 @@ export class ClientHandshake {
412
446
  }
413
447
  if (this.state === 'wait-continue') {
414
448
  if (m.id !== 0) return fail('protocol', CLOSE.UNKNOWN, 'the relay sent a stream packet before the handshake ended')
415
- if (m.type === PT.CLOSE) return fail(closeState(m.reason), m.reason, closeWords(m.reason))
449
+ if (m.type === PT.REASON) {
450
+ this.why = { rule: m.rule, until: m.until, words: m.words }
451
+ return { send: [] }
452
+ }
453
+ if (m.type === PT.CLOSE) {
454
+ if (this.why) {
455
+ const r = fail('limited', m.reason, this.why.words || closeWords(m.reason))
456
+ r.fail.rule = this.why.rule
457
+ r.fail.until = this.why.until
458
+ return r
459
+ }
460
+ return fail(closeState(m.reason), m.reason, closeWords(m.reason))
461
+ }
416
462
  if (m.type !== PT.CONTINUE) return fail('protocol', CLOSE.UNKNOWN, 'the relay did not answer INFO with CONTINUE')
417
463
  this.state = 'done'
418
464
  const common = new Set(this.exts.filter((e) => this.server.has(e.id)).map((e) => e.id))
package/wmdpack.wasm CHANGED
Binary file