@mtcute/core 0.30.0 → 0.30.2

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.
Files changed (148) hide show
  1. package/client.cjs +8 -0
  2. package/client.js +8 -0
  3. package/highlevel/client.d.cts +63 -1
  4. package/highlevel/client.d.ts +63 -1
  5. package/highlevel/methods/files/download-iterable.cjs +80 -45
  6. package/highlevel/methods/files/download-iterable.js +80 -45
  7. package/highlevel/methods/files/normalize-input-media.cjs +9 -1
  8. package/highlevel/methods/files/normalize-input-media.js +10 -2
  9. package/highlevel/methods/files/upload-file.cjs +6 -1
  10. package/highlevel/methods/files/upload-file.js +6 -1
  11. package/highlevel/methods/messages/create-rich-streaming-draft.cjs +39 -0
  12. package/highlevel/methods/messages/create-rich-streaming-draft.d.cts +42 -0
  13. package/highlevel/methods/messages/create-rich-streaming-draft.d.ts +42 -0
  14. package/highlevel/methods/messages/create-rich-streaming-draft.js +34 -0
  15. package/highlevel/methods/messages/normalize-rich-message.cjs +168 -0
  16. package/highlevel/methods/messages/normalize-rich-message.d.cts +10 -0
  17. package/highlevel/methods/messages/normalize-rich-message.d.ts +10 -0
  18. package/highlevel/methods/messages/normalize-rich-message.js +163 -0
  19. package/highlevel/methods/messages/send-common.cjs +3 -2
  20. package/highlevel/methods/messages/send-common.d.cts +4 -1
  21. package/highlevel/methods/messages/send-common.d.ts +4 -1
  22. package/highlevel/methods/messages/send-common.js +3 -2
  23. package/highlevel/methods/messages/send-rich-message.cjs +49 -0
  24. package/highlevel/methods/messages/send-rich-message.d.cts +38 -0
  25. package/highlevel/methods/messages/send-rich-message.d.ts +38 -0
  26. package/highlevel/methods/messages/send-rich-message.js +44 -0
  27. package/highlevel/methods.d.cts +3 -0
  28. package/highlevel/methods.d.ts +3 -0
  29. package/highlevel/types/bots/inline-message/factories.cjs +14 -0
  30. package/highlevel/types/bots/inline-message/factories.d.cts +9 -1
  31. package/highlevel/types/bots/inline-message/factories.d.ts +9 -1
  32. package/highlevel/types/bots/inline-message/factories.js +14 -0
  33. package/highlevel/types/bots/inline-message/types.d.cts +17 -1
  34. package/highlevel/types/bots/inline-message/types.d.ts +17 -1
  35. package/highlevel/types/files/utils.d.cts +9 -0
  36. package/highlevel/types/files/utils.d.ts +9 -0
  37. package/highlevel/types/media/input-media/types.d.cts +1 -1
  38. package/highlevel/types/media/input-media/types.d.ts +1 -1
  39. package/highlevel/types/messages/index.d.cts +1 -1
  40. package/highlevel/types/messages/index.d.ts +1 -1
  41. package/highlevel/types/messages/message.cjs +1 -1
  42. package/highlevel/types/messages/message.d.cts +1 -1
  43. package/highlevel/types/messages/message.d.ts +1 -1
  44. package/highlevel/types/messages/message.js +1 -1
  45. package/highlevel/types/messages/rich/index.d.cts +4 -0
  46. package/highlevel/types/messages/rich/index.d.ts +4 -0
  47. package/highlevel/types/messages/rich/inner.cjs +219 -0
  48. package/highlevel/types/messages/rich/inner.d.cts +127 -0
  49. package/highlevel/types/messages/rich/inner.d.ts +127 -0
  50. package/highlevel/types/messages/rich/inner.js +214 -0
  51. package/highlevel/types/messages/{rich-message.cjs → rich/rich-message.cjs} +6 -6
  52. package/highlevel/types/messages/{rich-message.d.cts → rich/rich-message.d.cts} +5 -5
  53. package/highlevel/types/messages/{rich-message.d.ts → rich/rich-message.d.ts} +5 -5
  54. package/highlevel/types/messages/{rich-message.js → rich/rich-message.js} +6 -6
  55. package/highlevel/types/messages/rich/types.d.cts +76 -0
  56. package/highlevel/types/messages/rich/types.d.ts +76 -0
  57. package/highlevel/updates/manager.cjs +10 -169
  58. package/highlevel/updates/manager.js +10 -169
  59. package/highlevel/utils/entities.cjs +176 -0
  60. package/highlevel/utils/entities.d.cts +18 -0
  61. package/highlevel/utils/entities.d.ts +18 -0
  62. package/highlevel/utils/entities.js +177 -1
  63. package/highlevel/utils/index.d.cts +1 -0
  64. package/highlevel/utils/index.d.ts +1 -0
  65. package/highlevel/utils/walk-page-blocks.cjs +164 -0
  66. package/highlevel/utils/walk-page-blocks.d.cts +9 -0
  67. package/highlevel/utils/walk-page-blocks.d.ts +9 -0
  68. package/highlevel/utils/walk-page-blocks.js +159 -0
  69. package/index.cjs +3 -1
  70. package/index.js +3 -1
  71. package/methods.cjs +4 -0
  72. package/methods.js +4 -0
  73. package/network/authorization.cjs +1 -1
  74. package/network/authorization.js +1 -1
  75. package/network/delay-gate.cjs +29 -0
  76. package/network/delay-gate.d.cts +5 -0
  77. package/network/delay-gate.d.ts +5 -0
  78. package/network/delay-gate.js +24 -0
  79. package/network/delay-gate.test.d.cts +1 -0
  80. package/network/delay-gate.test.d.ts +1 -0
  81. package/network/message-id-store.cjs +30 -0
  82. package/network/message-id-store.d.cts +7 -0
  83. package/network/message-id-store.d.ts +7 -0
  84. package/network/message-id-store.js +25 -0
  85. package/network/message-id-store.test.d.cts +1 -0
  86. package/network/message-id-store.test.d.ts +1 -0
  87. package/network/middlewares/bundle.cjs +2 -0
  88. package/network/middlewares/bundle.d.cts +1 -0
  89. package/network/middlewares/bundle.d.ts +1 -0
  90. package/network/middlewares/bundle.js +2 -0
  91. package/network/middlewares/default.cjs +6 -1
  92. package/network/middlewares/default.js +6 -1
  93. package/network/middlewares/flood-waiter.test.d.cts +1 -0
  94. package/network/middlewares/flood-waiter.test.d.ts +1 -0
  95. package/network/middlewares/internal-errors.test.d.cts +1 -0
  96. package/network/middlewares/internal-errors.test.d.ts +1 -0
  97. package/network/middlewares/media-throttle.cjs +65 -0
  98. package/network/middlewares/media-throttle.d.cts +5 -0
  99. package/network/middlewares/media-throttle.d.ts +5 -0
  100. package/network/middlewares/media-throttle.js +60 -0
  101. package/network/middlewares/media-throttle.test.d.cts +1 -0
  102. package/network/middlewares/media-throttle.test.d.ts +1 -0
  103. package/network/middlewares/on-error.test.d.cts +1 -0
  104. package/network/middlewares/on-error.test.d.ts +1 -0
  105. package/network/middlewares/on-method.test.d.cts +1 -0
  106. package/network/middlewares/on-method.test.d.ts +1 -0
  107. package/network/mtproto-session.cjs +9 -2
  108. package/network/mtproto-session.d.cts +5 -2
  109. package/network/mtproto-session.d.ts +5 -2
  110. package/network/mtproto-session.js +9 -2
  111. package/network/mtproxy/_fake-tls.cjs +169 -107
  112. package/network/mtproxy/_fake-tls.js +169 -107
  113. package/network/mtproxy/_fake-tls.test.d.cts +1 -0
  114. package/network/mtproxy/_fake-tls.test.d.ts +1 -0
  115. package/network/multi-session-connection.cjs +7 -0
  116. package/network/multi-session-connection.d.cts +2 -0
  117. package/network/multi-session-connection.d.ts +2 -0
  118. package/network/multi-session-connection.js +7 -0
  119. package/network/network-manager.cjs +7 -1
  120. package/network/network-manager.js +7 -1
  121. package/network/resource-limiter.cjs +60 -0
  122. package/network/resource-limiter.d.cts +11 -0
  123. package/network/resource-limiter.d.ts +11 -0
  124. package/network/resource-limiter.js +55 -0
  125. package/network/resource-limiter.test.d.cts +1 -0
  126. package/network/resource-limiter.test.d.ts +1 -0
  127. package/network/server-salt.cjs +7 -3
  128. package/network/server-salt.d.cts +2 -0
  129. package/network/server-salt.d.ts +2 -0
  130. package/network/server-salt.js +7 -3
  131. package/network/session-connection.cjs +106 -20
  132. package/network/session-connection.d.cts +11 -0
  133. package/network/session-connection.d.ts +11 -0
  134. package/network/session-connection.js +106 -20
  135. package/package.json +1 -1
  136. package/tl/api-schema.json +1 -1
  137. package/tl/binary/reader.cjs +115 -109
  138. package/tl/binary/reader.js +115 -109
  139. package/tl/binary/writer.cjs +243 -245
  140. package/tl/binary/writer.js +243 -245
  141. package/tl/index.d.cts +227 -216
  142. package/tl/index.d.ts +227 -216
  143. package/tl/inner-tl.cjs +3 -1
  144. package/tl/inner-tl.js +3 -1
  145. package/types/utils.d.cts +4 -0
  146. package/types/utils.d.ts +4 -0
  147. package/utils.cjs +4 -0
  148. package/utils.js +5 -1
@@ -0,0 +1,60 @@
1
+ import { combineAbortSignals } from "../../utils/abort-signal.js";
2
+ import { ResourceLimiter } from "../resource-limiter.js";
3
+ const DOWNLOAD_RESOURCE_LIMIT = 2 * 1024 * 1024;
4
+ const DOWNLOAD_RESOURCE_LIMIT_PREMIUM = 16 * 1024 * 1024;
5
+ const UPLOAD_RESOURCE_LIMIT = 4 * 1024 * 1024;
6
+ function defaultResourceLimit(kind, isPremium) {
7
+ if (kind === "upload") return UPLOAD_RESOURCE_LIMIT;
8
+ return isPremium ? DOWNLOAD_RESOURCE_LIMIT_PREMIUM : DOWNLOAD_RESOURCE_LIMIT;
9
+ }
10
+ function mediaThrottle(params) {
11
+ const { getResourceLimit = defaultResourceLimit } = params ?? {};
12
+ const limiters = /* @__PURE__ */ new Map();
13
+ const getLimiter = (kind, dcId, isPremium) => {
14
+ let byDc = limiters.get(kind);
15
+ if (!byDc) {
16
+ byDc = /* @__PURE__ */ new Map();
17
+ limiters.set(kind, byDc);
18
+ }
19
+ const max = getResourceLimit(kind, isPremium);
20
+ let limiter = byDc.get(dcId);
21
+ if (!limiter) {
22
+ limiter = new ResourceLimiter(max);
23
+ byDc.set(dcId, limiter);
24
+ } else if (limiter.max !== max) {
25
+ limiter.setMax(max);
26
+ }
27
+ return limiter;
28
+ };
29
+ return async (ctx, next) => {
30
+ let kind;
31
+ let bytes = 0;
32
+ switch (ctx.request._) {
33
+ case "upload.getFile":
34
+ case "upload.getWebFile":
35
+ kind = ctx.params?.kind ?? "download";
36
+ bytes = ctx.request.limit;
37
+ break;
38
+ case "upload.saveFilePart":
39
+ case "upload.saveBigFilePart":
40
+ kind = "upload";
41
+ bytes = ctx.request.bytes.length;
42
+ break;
43
+ }
44
+ if (kind === void 0) return next(ctx);
45
+ const dcId = ctx.params?.dcId ?? ctx.manager.getPrimaryDcId();
46
+ const limiter = getLimiter(kind, dcId, ctx.manager.params.isPremium);
47
+ if (!limiter.tryAcquire(bytes)) {
48
+ const abortSignal = combineAbortSignals(ctx.manager.params.stopSignal, ctx.params?.abortSignal);
49
+ await limiter.acquire(bytes, abortSignal);
50
+ }
51
+ try {
52
+ return await next(ctx);
53
+ } finally {
54
+ limiter.release(bytes);
55
+ }
56
+ };
57
+ }
58
+ export {
59
+ mediaThrottle
60
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -12,6 +12,7 @@ const longUtils = require("../utils/long-utils.cjs");
12
12
  const miscUtils = require("../utils/misc-utils.cjs");
13
13
  const sortedArray = require("../utils/sorted-array.cjs");
14
14
  const authKey = require("./auth-key.cjs");
15
+ const messageIdStore = require("./message-id-store.cjs");
15
16
  const errors = require("../types/errors.cjs");
16
17
  class MtprotoSession {
17
18
  constructor(_crypto, log, _readerMap, _writerMap, _salts) {
@@ -21,6 +22,7 @@ class MtprotoSession {
21
22
  this._writerMap = _writerMap;
22
23
  this._salts = _salts;
23
24
  this.log.prefix = `[SESSION ${this._sessionId.toString(16)}] `;
25
+ this._salts.setTimeSource(this.getServerTime.bind(this));
24
26
  this._authKey = new authKey.AuthKey(_crypto, log, _readerMap);
25
27
  this._authKeyTemp = new authKey.AuthKey(_crypto, log, _readerMap);
26
28
  this._authKeyTempSecondary = new authKey.AuthKey(_crypto, log, _readerMap);
@@ -35,7 +37,7 @@ class MtprotoSession {
35
37
  /// state ///
36
38
  // recent msg ids
37
39
  recentOutgoingMsgIds = new utils.LruSet(1e3, longUtils.LongSet);
38
- recentIncomingMsgIds = new utils.LruSet(1e3, longUtils.LongSet);
40
+ recentIncomingMsgIds = new messageIdStore.MessageIdStore();
39
41
  // <deno-tsignore>
40
42
  recentStateRequests = new utils.LruMap(1e3, longUtils.LongMap);
41
43
  // queues
@@ -58,6 +60,7 @@ class MtprotoSession {
58
60
  lastPingRtt = Number.NaN;
59
61
  lastPingTime = 0;
60
62
  lastPingMsgId = Long.ZERO;
63
+ lastActivityTime = 0;
61
64
  lastSessionCreatedUid = Long.ZERO;
62
65
  initConnectionCalled = false;
63
66
  authorizationPending = false;
@@ -80,11 +83,15 @@ class MtprotoSession {
80
83
  this._authKeyTemp.reset();
81
84
  this._authKeyTempSecondary.reset();
82
85
  }
83
- updateTimeOffset(offset) {
86
+ updateTimeOffset(offset, force = false) {
87
+ if (!force && offset <= this._timeOffset) return;
84
88
  this.log.debug("time offset updated: %d", offset);
85
89
  this._timeOffset = offset;
86
90
  this._lastMessageId = Long.ZERO;
87
91
  }
92
+ getServerTime() {
93
+ return Math.floor(performance.now() / 1e3) + this._timeOffset;
94
+ }
88
95
  /**
89
96
  * Reset session state and generate a new session ID.
90
97
  *
@@ -5,6 +5,7 @@ import { ICryptoProvider, Logger, LongMap, SortedArray } from '../utils/index.js
5
5
  import { ServerSaltManager } from './server-salt.js';
6
6
  import { default as Long } from 'long';
7
7
  import { AuthKey } from './auth-key.js';
8
+ import { MessageIdStore } from './message-id-store.js';
8
9
  export interface PendingRpc {
9
10
  method: string;
10
11
  data: Uint8Array;
@@ -76,7 +77,7 @@ export declare class MtprotoSession {
76
77
  _lastMessageId: Long;
77
78
  _seqNo: number;
78
79
  recentOutgoingMsgIds: LruSet<Long>;
79
- recentIncomingMsgIds: LruSet<Long>;
80
+ recentIncomingMsgIds: MessageIdStore;
80
81
  recentStateRequests: LruMap<Long, Long[]>;
81
82
  queuedRpc: Deque<PendingRpc>;
82
83
  queuedAcks: Long[];
@@ -91,6 +92,7 @@ export declare class MtprotoSession {
91
92
  lastPingRtt: number;
92
93
  lastPingTime: number;
93
94
  lastPingMsgId: Long;
95
+ lastActivityTime: number;
94
96
  lastSessionCreatedUid: Long;
95
97
  initConnectionCalled: boolean;
96
98
  authorizationPending: boolean;
@@ -101,7 +103,8 @@ export declare class MtprotoSession {
101
103
  */
102
104
  reset(withAuthKey?: boolean): void;
103
105
  resetAuthKey(): void;
104
- updateTimeOffset(offset: number): void;
106
+ updateTimeOffset(offset: number, force?: boolean): void;
107
+ getServerTime(): number;
105
108
  /**
106
109
  * Reset session state and generate a new session ID.
107
110
  *
@@ -5,6 +5,7 @@ import { ICryptoProvider, Logger, LongMap, SortedArray } from '../utils/index.js
5
5
  import { ServerSaltManager } from './server-salt.js';
6
6
  import { default as Long } from 'long';
7
7
  import { AuthKey } from './auth-key.js';
8
+ import { MessageIdStore } from './message-id-store.js';
8
9
  export interface PendingRpc {
9
10
  method: string;
10
11
  data: Uint8Array;
@@ -76,7 +77,7 @@ export declare class MtprotoSession {
76
77
  _lastMessageId: Long;
77
78
  _seqNo: number;
78
79
  recentOutgoingMsgIds: LruSet<Long>;
79
- recentIncomingMsgIds: LruSet<Long>;
80
+ recentIncomingMsgIds: MessageIdStore;
80
81
  recentStateRequests: LruMap<Long, Long[]>;
81
82
  queuedRpc: Deque<PendingRpc>;
82
83
  queuedAcks: Long[];
@@ -91,6 +92,7 @@ export declare class MtprotoSession {
91
92
  lastPingRtt: number;
92
93
  lastPingTime: number;
93
94
  lastPingMsgId: Long;
95
+ lastActivityTime: number;
94
96
  lastSessionCreatedUid: Long;
95
97
  initConnectionCalled: boolean;
96
98
  authorizationPending: boolean;
@@ -101,7 +103,8 @@ export declare class MtprotoSession {
101
103
  */
102
104
  reset(withAuthKey?: boolean): void;
103
105
  resetAuthKey(): void;
104
- updateTimeOffset(offset: number): void;
106
+ updateTimeOffset(offset: number, force?: boolean): void;
107
+ getServerTime(): number;
105
108
  /**
106
109
  * Reset session state and generate a new session ID.
107
110
  *
@@ -5,6 +5,7 @@ import { randomLong, LongSet, LongMap, compareLongs } from "../utils/long-utils.
5
5
  import { getRandomInt } from "../utils/misc-utils.js";
6
6
  import { SortedArray } from "../utils/sorted-array.js";
7
7
  import { AuthKey } from "./auth-key.js";
8
+ import { MessageIdStore } from "./message-id-store.js";
8
9
  import { MtcuteError } from "../types/errors.js";
9
10
  class MtprotoSession {
10
11
  constructor(_crypto, log, _readerMap, _writerMap, _salts) {
@@ -14,6 +15,7 @@ class MtprotoSession {
14
15
  this._writerMap = _writerMap;
15
16
  this._salts = _salts;
16
17
  this.log.prefix = `[SESSION ${this._sessionId.toString(16)}] `;
18
+ this._salts.setTimeSource(this.getServerTime.bind(this));
17
19
  this._authKey = new AuthKey(_crypto, log, _readerMap);
18
20
  this._authKeyTemp = new AuthKey(_crypto, log, _readerMap);
19
21
  this._authKeyTempSecondary = new AuthKey(_crypto, log, _readerMap);
@@ -28,7 +30,7 @@ class MtprotoSession {
28
30
  /// state ///
29
31
  // recent msg ids
30
32
  recentOutgoingMsgIds = new LruSet(1e3, LongSet);
31
- recentIncomingMsgIds = new LruSet(1e3, LongSet);
33
+ recentIncomingMsgIds = new MessageIdStore();
32
34
  // <deno-tsignore>
33
35
  recentStateRequests = new LruMap(1e3, LongMap);
34
36
  // queues
@@ -51,6 +53,7 @@ class MtprotoSession {
51
53
  lastPingRtt = Number.NaN;
52
54
  lastPingTime = 0;
53
55
  lastPingMsgId = Long.ZERO;
56
+ lastActivityTime = 0;
54
57
  lastSessionCreatedUid = Long.ZERO;
55
58
  initConnectionCalled = false;
56
59
  authorizationPending = false;
@@ -73,11 +76,15 @@ class MtprotoSession {
73
76
  this._authKeyTemp.reset();
74
77
  this._authKeyTempSecondary.reset();
75
78
  }
76
- updateTimeOffset(offset) {
79
+ updateTimeOffset(offset, force = false) {
80
+ if (!force && offset <= this._timeOffset) return;
77
81
  this.log.debug("time offset updated: %d", offset);
78
82
  this._timeOffset = offset;
79
83
  this._lastMessageId = Long.ZERO;
80
84
  }
85
+ getServerTime() {
86
+ return Math.floor(performance.now() / 1e3) + this._timeOffset;
87
+ }
81
88
  /**
82
89
  * Reset session state and generate a new session ID.
83
90
  *
@@ -8,6 +8,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
8
  const io = require("@fuman/io");
9
9
  const utils = require("@fuman/utils");
10
10
  const MAX_TLS_PACKET_LENGTH = 2878;
11
+ const MAX_TLS_HELLO_SIZE = 4096;
11
12
  const KEY_MOD = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEDn;
12
13
  const QUAD_RES_MOD = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEDn;
13
14
  const QUAD_RES_POW = 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6n;
@@ -34,7 +35,11 @@ function _isQuadraticResidue(a) {
34
35
  return r === 1n;
35
36
  }
36
37
  function executeTlsOperations(h) {
37
- h.string([22, 3, 1, 2, 0, 1, 0, 1, 252, 3, 3]);
38
+ h.string([22, 3, 1]);
39
+ h.beginScope();
40
+ h.string([1, 0]);
41
+ h.beginScope();
42
+ h.string([3, 3]);
38
43
  h.zero(32);
39
44
  h.string([32]);
40
45
  h.random(32);
@@ -72,67 +77,34 @@ function executeTlsOperations(h) {
72
77
  0,
73
78
  53,
74
79
  1,
75
- 0,
76
- 1,
77
- 147
80
+ 0
78
81
  ]);
79
- h.grease(2);
80
- h.string([0, 0, 0, 0]);
81
- h.beginScope();
82
82
  h.beginScope();
83
- h.string([0]);
84
- h.beginScope();
85
- h.domain();
86
- h.endScope();
87
- h.endScope();
88
- h.endScope();
89
- h.string([0, 23, 0, 0, 255, 1, 0, 1, 0, 0, 10, 0, 10, 0, 8]);
90
- h.grease(4);
91
- h.string(
92
- [
93
- 0,
94
- 29,
95
- 0,
96
- 23,
97
- 0,
98
- 24,
99
- 0,
100
- 11,
101
- 0,
102
- 2,
103
- 1,
104
- 0,
105
- 0,
106
- 35,
107
- 0,
108
- 0,
109
- 0,
110
- 16,
111
- 0,
112
- 14,
113
- 0,
114
- 12,
115
- 2,
116
- 104,
117
- 50,
118
- 8,
119
- 104,
120
- 116,
121
- 116,
122
- 112,
123
- 47,
124
- 49,
125
- 46,
126
- 49,
127
- 0,
128
- 5,
129
- 0,
130
- 5,
131
- 1,
132
- 0,
133
- 0,
134
- 0,
135
- 0,
83
+ h.grease(2);
84
+ h.string([0, 0]);
85
+ h.permutation([
86
+ (w) => {
87
+ w.string([0, 0]);
88
+ w.beginScope();
89
+ w.beginScope();
90
+ w.string([0]);
91
+ w.beginScope();
92
+ w.domain();
93
+ w.endScope();
94
+ w.endScope();
95
+ w.endScope();
96
+ },
97
+ (w) => w.string([0, 5, 0, 5, 1, 0, 0, 0, 0]),
98
+ // status_request
99
+ (w) => {
100
+ w.string([0, 10, 0, 12, 0, 10]);
101
+ w.grease(4);
102
+ w.string([17, 236, 0, 29, 0, 23, 0, 24]);
103
+ },
104
+ (w) => w.string([0, 11, 0, 2, 1, 0]),
105
+ // ec_point_formats
106
+ (w) => w.string([
107
+ // signature_algorithms
136
108
  0,
137
109
  13,
138
110
  0,
@@ -154,27 +126,76 @@ function executeTlsOperations(h) {
154
126
  8,
155
127
  6,
156
128
  6,
157
- 1,
129
+ 1
130
+ ]),
131
+ (w) => w.string([
132
+ // application_layer_protocol_negotiation (ALPN)
158
133
  0,
159
- 18,
160
- 0,
161
- 0,
162
- 0,
163
- 51,
134
+ 16,
164
135
  0,
165
- 43,
136
+ 14,
166
137
  0,
167
- 41
168
- ]
169
- );
170
- h.grease(4);
171
- h.string([0, 1, 0, 0, 29, 0, 32]);
172
- h.key();
173
- h.string([0, 45, 0, 2, 1, 1, 0, 43, 0, 11, 10]);
174
- h.grease(6);
175
- h.string([3, 4, 3, 3, 3, 2, 3, 1, 0, 27, 0, 3, 2, 0, 2]);
138
+ 12,
139
+ 2,
140
+ 104,
141
+ 50,
142
+ 8,
143
+ 104,
144
+ 116,
145
+ 116,
146
+ 112,
147
+ 47,
148
+ 49,
149
+ 46,
150
+ 49
151
+ ]),
152
+ (w) => w.string([0, 18, 0, 0]),
153
+ // signed_certificate_timestamp
154
+ (w) => w.string([0, 23, 0, 0]),
155
+ // extended_master_secret
156
+ (w) => w.string([0, 27, 0, 3, 2, 0, 2]),
157
+ // compress_certificate
158
+ (w) => w.string([0, 35, 0, 0]),
159
+ // session_ticket
160
+ (w) => {
161
+ w.string([0, 43, 0, 7, 6]);
162
+ w.grease(6);
163
+ w.string([3, 4, 3, 3]);
164
+ },
165
+ (w) => w.string([0, 45, 0, 2, 1, 1]),
166
+ // psk_key_exchange_modes
167
+ (w) => {
168
+ w.string([0, 51, 4, 239, 4, 237]);
169
+ w.grease(4);
170
+ w.string([0, 1, 0, 17, 236, 4, 192]);
171
+ w.mlKem768Key();
172
+ w.key();
173
+ w.string([0, 29, 0, 32]);
174
+ w.key();
175
+ },
176
+ (w) => w.string([68, 205, 0, 5, 0, 3, 2, 104, 50]),
177
+ // application_settings (ALPS)
178
+ (w) => {
179
+ w.string([254, 13]);
180
+ w.beginScope();
181
+ w.string([0, 0, 1, 0, 1]);
182
+ w.random(1);
183
+ w.string([0, 32]);
184
+ w.random(32);
185
+ w.beginScope();
186
+ w.echPayload();
187
+ w.endScope();
188
+ w.endScope();
189
+ },
190
+ (w) => w.string([255, 1, 0, 1, 0])
191
+ // renegotiation_info
192
+ ]);
176
193
  h.grease(3);
177
- h.string([0, 1, 0, 0, 21]);
194
+ h.string([0, 1, 0]);
195
+ h.padding();
196
+ h.endScope();
197
+ h.endScope();
198
+ h.endScope();
178
199
  }
179
200
  function initGrease(crypto, size) {
180
201
  const buf = crypto.randomBytes(size);
@@ -189,12 +210,12 @@ function initGrease(crypto, size) {
189
210
  return buf;
190
211
  }
191
212
  class TlsHelloWriter {
192
- constructor(crypto, size, domain) {
213
+ constructor(crypto, domain, grease) {
193
214
  this.crypto = crypto;
194
215
  this._domain = domain;
195
- this.buf = utils.u8.alloc(size);
216
+ this.buf = utils.u8.alloc(MAX_TLS_HELLO_SIZE);
196
217
  this.dv = utils.typed.toDataView(this.buf);
197
- this._grease = initGrease(this.crypto, 7);
218
+ this._grease = grease ?? initGrease(this.crypto, 7);
198
219
  }
199
220
  buf;
200
221
  dv;
@@ -235,6 +256,24 @@ class TlsHelloWriter {
235
256
  }
236
257
  }
237
258
  }
259
+ // fake X25519MLKEM768 hybrid keyshare's ml-kem-768 part: 384 coefficients
260
+ // (mod 3329) packed 3 bytes per 2 coefficients, followed by 32 random bytes
261
+ mlKem768Key() {
262
+ const rnd = this.crypto.randomBytes(384 * 8);
263
+ const dv = utils.typed.toDataView(rnd);
264
+ for (let i = 0; i < 384; i++) {
265
+ const a = dv.getUint32(i * 8, true) % 3329;
266
+ const b = dv.getUint32(i * 8 + 4, true) % 3329;
267
+ this.buf[this.pos++] = a & 255;
268
+ this.buf[this.pos++] = (a >> 8) + ((b & 15) << 4);
269
+ this.buf[this.pos++] = b >> 4;
270
+ }
271
+ this.random(32);
272
+ }
273
+ // GREASE ECH payload: random bytes of length 144/176/208/240
274
+ echPayload() {
275
+ this.random(this._randomInt(4) * 32 + 144);
276
+ }
238
277
  beginScope() {
239
278
  this._scopes.push(this.pos);
240
279
  this.pos += 2;
@@ -244,29 +283,55 @@ class TlsHelloWriter {
244
283
  if (begin === void 0) {
245
284
  throw new Error("endScope called without beginScope");
246
285
  }
247
- const end = this.pos;
248
- const size = end - begin - 2;
249
- this.dv.setUint16(begin, size);
286
+ this.dv.setUint16(begin, this.pos - begin - 2);
250
287
  }
251
- async finish(secret) {
252
- const padSize = 515 - this.pos;
253
- const unixTime = ~~(Date.now() / 1e3);
288
+ // render each part into its own buffer, then emit them in random order —
289
+ // mimics chrome's per-connection tls extension shuffling
290
+ permutation(parts) {
291
+ const rendered = parts.map((fn) => {
292
+ const sub = new TlsHelloWriter(this.crypto, this._domain, this._grease);
293
+ fn(sub);
294
+ return sub.buf.subarray(0, sub.pos);
295
+ });
296
+ for (let i = rendered.length - 1; i > 0; i--) {
297
+ const j = this._randomInt(i + 1);
298
+ const tmp = rendered[i];
299
+ rendered[i] = rendered[j];
300
+ rendered[j] = tmp;
301
+ }
302
+ for (const part of rendered) {
303
+ this.string(part);
304
+ }
305
+ }
306
+ // tls padding extension, only emitted for hellos smaller than 513 bytes
307
+ padding() {
308
+ if (this.pos >= 513) return;
309
+ const size = 513 - this.pos;
310
+ this.string([0, 21]);
254
311
  this.beginScope();
255
- this.zero(padSize);
312
+ this.zero(size);
256
313
  this.endScope();
257
- const hash = await this.crypto.hmacSha256(this.buf, secret);
314
+ }
315
+ _randomInt(max) {
316
+ return utils.typed.toDataView(this.crypto.randomBytes(4)).getUint32(0, true) % max;
317
+ }
318
+ async finish(secret) {
319
+ const data = this.buf.subarray(0, this.pos);
320
+ const unixTime = ~~(Date.now() / 1e3);
321
+ const hash = await this.crypto.hmacSha256(data, secret);
258
322
  const dv = utils.typed.toDataView(hash);
259
323
  const old = dv.getInt32(28, true);
260
324
  dv.setInt32(28, old ^ unixTime, true);
261
- this.buf.set(hash, 11);
262
- return this.buf;
325
+ data.set(hash, 11);
326
+ return data;
263
327
  }
264
328
  }
265
329
  async function generateFakeTlsHeader(domain, secret, crypto) {
266
- const writer = new TlsHelloWriter(crypto, 517, domain);
330
+ const writer = new TlsHelloWriter(crypto, domain);
267
331
  executeTlsOperations(writer);
268
332
  return writer.finish(secret);
269
333
  }
334
+ const EMPTY_BYTES = io.Bytes.from(utils.u8.empty);
270
335
  class FakeTlsPacketCodec {
271
336
  // protected _stream: Buffer = Buffer.alloc(0)
272
337
  constructor(_inner) {
@@ -301,23 +366,20 @@ class FakeTlsPacketCodec {
301
366
  }
302
367
  async decode(reader, eof) {
303
368
  if (eof) return null;
304
- if (reader.available < 5) return null;
305
- const header = reader.readSync(3);
306
- if (header[0] !== 23 || header[1] !== 3 || header[2] !== 3) {
307
- throw new Error("Invalid TLS header");
308
- }
309
- const length = io.read.uint16be(reader);
310
- if (length < reader.available - 5) {
311
- reader.rewind(5);
312
- return null;
313
- }
314
- const packet = reader.readSync(length);
315
- const inner = await this._inner.decode(io.Bytes.from(packet), eof);
316
- if (!inner) {
317
- reader.rewind(5 + length);
318
- return null;
369
+ while (reader.available >= 5) {
370
+ const header = reader.readSync(3);
371
+ if (header[0] !== 23 || header[1] !== 3 || header[2] !== 3) {
372
+ throw new Error("Invalid TLS header");
373
+ }
374
+ const length = io.read.uint16be(reader);
375
+ if (reader.available < length) {
376
+ reader.rewind(5);
377
+ break;
378
+ }
379
+ const inner = await this._inner.decode(io.Bytes.from(reader.readSync(length)), eof);
380
+ if (inner) return inner;
319
381
  }
320
- return inner;
382
+ return this._inner.decode(EMPTY_BYTES, eof);
321
383
  }
322
384
  reset() {
323
385
  this._isFirstTls = true;