@mt-tl/server 0.1.0
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/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/auth/handshake.d.ts +35 -0
- package/dist/auth/handshake.d.ts.map +1 -0
- package/dist/auth/handshake.js +208 -0
- package/dist/auth/handshake.js.map +1 -0
- package/dist/auth/nonce-store.d.ts +22 -0
- package/dist/auth/nonce-store.d.ts.map +1 -0
- package/dist/auth/nonce-store.js +23 -0
- package/dist/auth/nonce-store.js.map +1 -0
- package/dist/bootstrap.d.ts +36 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +82 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/config.d.ts +103 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -0
- package/dist/core/context.d.ts +57 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +27 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/errors.d.ts +33 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +47 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +5 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/rpc.d.ts +83 -0
- package/dist/core/rpc.d.ts.map +1 -0
- package/dist/core/rpc.js +102 -0
- package/dist/core/rpc.js.map +1 -0
- package/dist/core/updates.d.ts +56 -0
- package/dist/core/updates.d.ts.map +1 -0
- package/dist/core/updates.js +34 -0
- package/dist/core/updates.js.map +1 -0
- package/dist/create-server.d.ts +89 -0
- package/dist/create-server.d.ts.map +1 -0
- package/dist/create-server.js +109 -0
- package/dist/create-server.js.map +1 -0
- package/dist/crypto/aes-ige.d.ts +3 -0
- package/dist/crypto/aes-ige.d.ts.map +1 -0
- package/dist/crypto/aes-ige.js +55 -0
- package/dist/crypto/aes-ige.js.map +1 -0
- package/dist/crypto/dh.d.ts +21 -0
- package/dist/crypto/dh.d.ts.map +1 -0
- package/dist/crypto/dh.js +99 -0
- package/dist/crypto/dh.js.map +1 -0
- package/dist/crypto/hashes.d.ts +6 -0
- package/dist/crypto/hashes.d.ts.map +1 -0
- package/dist/crypto/hashes.js +14 -0
- package/dist/crypto/hashes.js.map +1 -0
- package/dist/crypto/msg-key.d.ts +15 -0
- package/dist/crypto/msg-key.d.ts.map +1 -0
- package/dist/crypto/msg-key.js +24 -0
- package/dist/crypto/msg-key.js.map +1 -0
- package/dist/crypto/rsa.d.ts +27 -0
- package/dist/crypto/rsa.d.ts.map +1 -0
- package/dist/crypto/rsa.js +50 -0
- package/dist/crypto/rsa.js.map +1 -0
- package/dist/dispatch/dispatcher.d.ts +72 -0
- package/dist/dispatch/dispatcher.d.ts.map +1 -0
- package/dist/dispatch/dispatcher.js +503 -0
- package/dist/dispatch/dispatcher.js.map +1 -0
- package/dist/dispatch/forwarders/in-process.d.ts +12 -0
- package/dist/dispatch/forwarders/in-process.d.ts.map +1 -0
- package/dist/dispatch/forwarders/in-process.js +15 -0
- package/dist/dispatch/forwarders/in-process.js.map +1 -0
- package/dist/dispatch/forwarders/print.d.ts +14 -0
- package/dist/dispatch/forwarders/print.d.ts.map +1 -0
- package/dist/dispatch/forwarders/print.js +23 -0
- package/dist/dispatch/forwarders/print.js.map +1 -0
- package/dist/dispatch/rpc-forwarder.d.ts +14 -0
- package/dist/dispatch/rpc-forwarder.d.ts.map +1 -0
- package/dist/dispatch/rpc-forwarder.js +2 -0
- package/dist/dispatch/rpc-forwarder.js.map +1 -0
- package/dist/dispatch/types.d.ts +32 -0
- package/dist/dispatch/types.d.ts.map +1 -0
- package/dist/dispatch/types.js +23 -0
- package/dist/dispatch/types.js.map +1 -0
- package/dist/gateway.d.ts +57 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +150 -0
- package/dist/gateway.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.d.ts +12 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +13 -0
- package/dist/lib.js.map +1 -0
- package/dist/server/message-pipeline.d.ts +57 -0
- package/dist/server/message-pipeline.d.ts.map +1 -0
- package/dist/server/message-pipeline.js +199 -0
- package/dist/server/message-pipeline.js.map +1 -0
- package/dist/session/inbound-tracker.d.ts +118 -0
- package/dist/session/inbound-tracker.d.ts.map +1 -0
- package/dist/session/inbound-tracker.js +170 -0
- package/dist/session/inbound-tracker.js.map +1 -0
- package/dist/session/message-id.d.ts +19 -0
- package/dist/session/message-id.d.ts.map +1 -0
- package/dist/session/message-id.js +30 -0
- package/dist/session/message-id.js.map +1 -0
- package/dist/session/salts.d.ts +51 -0
- package/dist/session/salts.d.ts.map +1 -0
- package/dist/session/salts.js +132 -0
- package/dist/session/salts.js.map +1 -0
- package/dist/session/session-manager.d.ts +18 -0
- package/dist/session/session-manager.d.ts.map +1 -0
- package/dist/session/session-manager.js +43 -0
- package/dist/session/session-manager.js.map +1 -0
- package/dist/storage/index.d.ts +14 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +16 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/memory.d.ts +3 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +91 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/storage/mongo.d.ts +3 -0
- package/dist/storage/mongo.d.ts.map +1 -0
- package/dist/storage/mongo.js +175 -0
- package/dist/storage/mongo.js.map +1 -0
- package/dist/storage/types.d.ts +85 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +3 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/testkit.d.ts +11 -0
- package/dist/testkit.d.ts.map +1 -0
- package/dist/testkit.js +17 -0
- package/dist/testkit.js.map +1 -0
- package/dist/tl/codec.d.ts +37 -0
- package/dist/tl/codec.d.ts.map +1 -0
- package/dist/tl/codec.js +297 -0
- package/dist/tl/codec.js.map +1 -0
- package/dist/tl/layered-registry.d.ts +29 -0
- package/dist/tl/layered-registry.d.ts.map +1 -0
- package/dist/tl/layered-registry.js +118 -0
- package/dist/tl/layered-registry.js.map +1 -0
- package/dist/tl/protocol.d.ts +126 -0
- package/dist/tl/protocol.d.ts.map +1 -0
- package/dist/tl/protocol.js +22 -0
- package/dist/tl/protocol.js.map +1 -0
- package/dist/tl/reader.d.ts +30 -0
- package/dist/tl/reader.d.ts.map +1 -0
- package/dist/tl/reader.js +87 -0
- package/dist/tl/reader.js.map +1 -0
- package/dist/tl/registry.d.ts +39 -0
- package/dist/tl/registry.d.ts.map +1 -0
- package/dist/tl/registry.js +52 -0
- package/dist/tl/registry.js.map +1 -0
- package/dist/tl/writer.d.ts +24 -0
- package/dist/tl/writer.d.ts.map +1 -0
- package/dist/tl/writer.js +95 -0
- package/dist/tl/writer.js.map +1 -0
- package/dist/transport/connection-registry.d.ts +31 -0
- package/dist/transport/connection-registry.d.ts.map +1 -0
- package/dist/transport/connection-registry.js +84 -0
- package/dist/transport/connection-registry.js.map +1 -0
- package/dist/transport/connection.d.ts +62 -0
- package/dist/transport/connection.d.ts.map +1 -0
- package/dist/transport/connection.js +77 -0
- package/dist/transport/connection.js.map +1 -0
- package/dist/transport/framing.d.ts +39 -0
- package/dist/transport/framing.d.ts.map +1 -0
- package/dist/transport/framing.js +212 -0
- package/dist/transport/framing.js.map +1 -0
- package/dist/transport/proxy-protocol.d.ts +23 -0
- package/dist/transport/proxy-protocol.d.ts.map +1 -0
- package/dist/transport/proxy-protocol.js +108 -0
- package/dist/transport/proxy-protocol.js.map +1 -0
- package/dist/transport/server-common.d.ts +27 -0
- package/dist/transport/server-common.d.ts.map +1 -0
- package/dist/transport/server-common.js +27 -0
- package/dist/transport/server-common.js.map +1 -0
- package/dist/transport/tcp-server.d.ts +26 -0
- package/dist/transport/tcp-server.d.ts.map +1 -0
- package/dist/transport/tcp-server.js +91 -0
- package/dist/transport/tcp-server.js.map +1 -0
- package/dist/transport/ws-server.d.ts +19 -0
- package/dist/transport/ws-server.d.ts.map +1 -0
- package/dist/transport/ws-server.js +78 -0
- package/dist/transport/ws-server.js.map +1 -0
- package/dist/update-publisher.d.ts +34 -0
- package/dist/update-publisher.d.ts.map +1 -0
- package/dist/update-publisher.js +29 -0
- package/dist/update-publisher.js.map +1 -0
- package/dist/updates/mongo-update-log.d.ts +13 -0
- package/dist/updates/mongo-update-log.d.ts.map +1 -0
- package/dist/updates/mongo-update-log.js +39 -0
- package/dist/updates/mongo-update-log.js.map +1 -0
- package/dist/updates/presence-binder.d.ts +29 -0
- package/dist/updates/presence-binder.d.ts.map +1 -0
- package/dist/updates/presence-binder.js +36 -0
- package/dist/updates/presence-binder.js.map +1 -0
- package/dist/updates/presence.d.ts +31 -0
- package/dist/updates/presence.d.ts.map +1 -0
- package/dist/updates/presence.js +44 -0
- package/dist/updates/presence.js.map +1 -0
- package/dist/updates/push.d.ts +25 -0
- package/dist/updates/push.d.ts.map +1 -0
- package/dist/updates/push.js +72 -0
- package/dist/updates/push.js.map +1 -0
- package/dist/updates/redis-bus.d.ts +45 -0
- package/dist/updates/redis-bus.d.ts.map +1 -0
- package/dist/updates/redis-bus.js +59 -0
- package/dist/updates/redis-bus.js.map +1 -0
- package/dist/updates/redis-presence.d.ts +43 -0
- package/dist/updates/redis-presence.d.ts.map +1 -0
- package/dist/updates/redis-presence.js +65 -0
- package/dist/updates/redis-presence.js.map +1 -0
- package/dist/updates/render.d.ts +16 -0
- package/dist/updates/render.d.ts.map +1 -0
- package/dist/updates/render.js +46 -0
- package/dist/updates/render.js.map +1 -0
- package/dist/updates/router.d.ts +27 -0
- package/dist/updates/router.d.ts.map +1 -0
- package/dist/updates/router.js +36 -0
- package/dist/updates/router.js.map +1 -0
- package/dist/updates/types.d.ts +23 -0
- package/dist/updates/types.d.ts.map +1 -0
- package/dist/updates/types.js +2 -0
- package/dist/updates/types.js.map +1 -0
- package/dist/updates/update-bus.d.ts +29 -0
- package/dist/updates/update-bus.d.ts.map +1 -0
- package/dist/updates/update-bus.js +24 -0
- package/dist/updates/update-bus.js.map +1 -0
- package/dist/util/bytes.d.ts +12 -0
- package/dist/util/bytes.d.ts.map +1 -0
- package/dist/util/bytes.js +46 -0
- package/dist/util/bytes.js.map +1 -0
- package/package.json +84 -0
- package/src/auth/handshake.ts +262 -0
- package/src/auth/nonce-store.ts +39 -0
- package/src/bootstrap.ts +114 -0
- package/src/config.ts +103 -0
- package/src/core/context.ts +94 -0
- package/src/core/errors.ts +52 -0
- package/src/core/index.ts +4 -0
- package/src/core/rpc.ts +165 -0
- package/src/core/updates.ts +69 -0
- package/src/create-server.ts +181 -0
- package/src/crypto/aes-ige.ts +57 -0
- package/src/crypto/dh.ts +101 -0
- package/src/crypto/hashes.ts +17 -0
- package/src/crypto/msg-key.ts +29 -0
- package/src/crypto/rsa.ts +70 -0
- package/src/dispatch/dispatcher.ts +586 -0
- package/src/dispatch/forwarders/in-process.ts +14 -0
- package/src/dispatch/forwarders/print.ts +22 -0
- package/src/dispatch/rpc-forwarder.ts +15 -0
- package/src/dispatch/types.ts +60 -0
- package/src/gateway.ts +214 -0
- package/src/index.ts +53 -0
- package/src/lib.ts +24 -0
- package/src/server/message-pipeline.ts +256 -0
- package/src/session/inbound-tracker.ts +221 -0
- package/src/session/message-id.ts +43 -0
- package/src/session/salts.ts +162 -0
- package/src/session/session-manager.ts +66 -0
- package/src/storage/index.ts +26 -0
- package/src/storage/memory.ts +101 -0
- package/src/storage/mongo.ts +215 -0
- package/src/storage/types.ts +92 -0
- package/src/testkit.ts +19 -0
- package/src/tl/codec.ts +292 -0
- package/src/tl/layered-registry.ts +132 -0
- package/src/tl/protocol.ts +146 -0
- package/src/tl/reader.ts +99 -0
- package/src/tl/registry.ts +78 -0
- package/src/tl/writer.ts +104 -0
- package/src/transport/connection-registry.ts +91 -0
- package/src/transport/connection.ts +113 -0
- package/src/transport/framing.ts +223 -0
- package/src/transport/proxy-protocol.ts +109 -0
- package/src/transport/server-common.ts +49 -0
- package/src/transport/tcp-server.ts +102 -0
- package/src/transport/ws-server.ts +94 -0
- package/src/update-publisher.ts +47 -0
- package/src/updates/mongo-update-log.ts +49 -0
- package/src/updates/presence-binder.ts +51 -0
- package/src/updates/presence.ts +61 -0
- package/src/updates/push.ts +90 -0
- package/src/updates/redis-bus.ts +86 -0
- package/src/updates/redis-presence.ts +87 -0
- package/src/updates/render.ts +53 -0
- package/src/updates/router.ts +52 -0
- package/src/updates/types.ts +24 -0
- package/src/updates/update-bus.ts +49 -0
- package/src/util/bytes.ts +49 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { MongoClient, Binary, type Db, type Collection, type AnyBulkWriteOperation } from 'mongodb'
|
|
2
|
+
import type {
|
|
3
|
+
AuthKeyMeta,
|
|
4
|
+
AuthKeyRecord,
|
|
5
|
+
AuthKeyRepo,
|
|
6
|
+
SaltRepo,
|
|
7
|
+
SaltScheduleEntry,
|
|
8
|
+
SessionRecord,
|
|
9
|
+
SessionRepo,
|
|
10
|
+
Storage,
|
|
11
|
+
} from './types.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* MongoDB-backed storage. Uses the gateway's own collections (auth keys are not
|
|
15
|
+
* shared byte-for-byte with the legacy server's encoding — that's a migration
|
|
16
|
+
* concern, out of Phase-1 scope). bigints are stored as decimal strings.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
interface AuthKeyDoc {
|
|
20
|
+
_id: string
|
|
21
|
+
key: Binary
|
|
22
|
+
expiresIn: boolean
|
|
23
|
+
createdAt: Date
|
|
24
|
+
subject: string | null
|
|
25
|
+
isBlocked?: boolean
|
|
26
|
+
meta?: AuthKeyRecord['meta']
|
|
27
|
+
}
|
|
28
|
+
interface SaltDoc {
|
|
29
|
+
/** `${authKeyId}:${validSince}` — one document per scheduled window. */
|
|
30
|
+
_id: string
|
|
31
|
+
authKeyId: string
|
|
32
|
+
salt: string
|
|
33
|
+
validSince: number
|
|
34
|
+
validUntil: number
|
|
35
|
+
}
|
|
36
|
+
interface SessionDoc {
|
|
37
|
+
_id: string
|
|
38
|
+
authKeyId: string
|
|
39
|
+
uniqueId: string
|
|
40
|
+
apiLayer: number
|
|
41
|
+
subject?: string
|
|
42
|
+
lastActivity: number
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function toBuf(b: Binary): Buffer {
|
|
46
|
+
return Buffer.from(b.buffer)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class MongoAuthKeyRepo implements AuthKeyRepo {
|
|
50
|
+
constructor(private readonly col: Collection<AuthKeyDoc>) {}
|
|
51
|
+
async create(rec: AuthKeyRecord): Promise<void> {
|
|
52
|
+
await this.col.updateOne(
|
|
53
|
+
{ _id: rec.id.toString() },
|
|
54
|
+
{
|
|
55
|
+
$set: {
|
|
56
|
+
key: new Binary(rec.key),
|
|
57
|
+
expiresIn: rec.expiresIn,
|
|
58
|
+
createdAt: rec.createdAt,
|
|
59
|
+
subject: rec.subject,
|
|
60
|
+
isBlocked: rec.isBlocked ?? false,
|
|
61
|
+
meta: rec.meta,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{ upsert: true },
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
async getById(id: bigint): Promise<AuthKeyRecord | null> {
|
|
68
|
+
const doc = await this.col.findOne({ _id: id.toString() })
|
|
69
|
+
if (!doc) return null
|
|
70
|
+
return {
|
|
71
|
+
id,
|
|
72
|
+
key: toBuf(doc.key),
|
|
73
|
+
expiresIn: doc.expiresIn,
|
|
74
|
+
createdAt: doc.createdAt,
|
|
75
|
+
subject: doc.subject,
|
|
76
|
+
isBlocked: doc.isBlocked,
|
|
77
|
+
meta: doc.meta,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async setBlocked(id: bigint, blocked: boolean): Promise<void> {
|
|
81
|
+
await this.col.updateOne({ _id: id.toString() }, { $set: { isBlocked: blocked } })
|
|
82
|
+
}
|
|
83
|
+
async bindUser(id: bigint, subject: string | null): Promise<void> {
|
|
84
|
+
await this.col.updateOne({ _id: id.toString() }, { $set: { subject } })
|
|
85
|
+
}
|
|
86
|
+
async updateMeta(id: bigint, patch: AuthKeyMeta): Promise<void> {
|
|
87
|
+
// Set defined fields under dotted `meta.*` paths so we never clobber the
|
|
88
|
+
// sibling fields (e.g. `meta.apiLayer` set at handshake time).
|
|
89
|
+
const set: Record<string, unknown> = {}
|
|
90
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
91
|
+
if (v !== undefined) set[`meta.${k}`] = v
|
|
92
|
+
}
|
|
93
|
+
if (Object.keys(set).length) await this.col.updateOne({ _id: id.toString() }, { $set: set })
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
class MongoSaltRepo implements SaltRepo {
|
|
98
|
+
constructor(private readonly col: Collection<SaltDoc>) {}
|
|
99
|
+
async append(authKeyId: bigint, entries: SaltScheduleEntry[]): Promise<void> {
|
|
100
|
+
if (!entries.length) return
|
|
101
|
+
const key = authKeyId.toString()
|
|
102
|
+
// $setOnInsert keeps the first writer's salt for a window, so all nodes
|
|
103
|
+
// that derive the same (deterministic) window converge on one salt.
|
|
104
|
+
const ops: AnyBulkWriteOperation<SaltDoc>[] = entries.map(e => ({
|
|
105
|
+
updateOne: {
|
|
106
|
+
filter: { _id: `${key}:${e.validSince}` },
|
|
107
|
+
update: {
|
|
108
|
+
$setOnInsert: {
|
|
109
|
+
authKeyId: key,
|
|
110
|
+
salt: e.salt.toString(),
|
|
111
|
+
validSince: e.validSince,
|
|
112
|
+
validUntil: e.validUntil,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
upsert: true,
|
|
116
|
+
},
|
|
117
|
+
}))
|
|
118
|
+
await this.col.bulkWrite(ops, { ordered: false })
|
|
119
|
+
}
|
|
120
|
+
async list(authKeyId: bigint): Promise<SaltScheduleEntry[]> {
|
|
121
|
+
const docs = await this.col
|
|
122
|
+
.find({ authKeyId: authKeyId.toString() })
|
|
123
|
+
.sort({ validSince: 1 })
|
|
124
|
+
.toArray()
|
|
125
|
+
return docs.map(d => ({ salt: BigInt(d.salt), validSince: d.validSince, validUntil: d.validUntil }))
|
|
126
|
+
}
|
|
127
|
+
async prune(authKeyId: bigint, before: number): Promise<void> {
|
|
128
|
+
await this.col.deleteMany({ authKeyId: authKeyId.toString(), validUntil: { $lte: before } })
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
class MongoSessionRepo implements SessionRepo {
|
|
133
|
+
constructor(private readonly col: Collection<SessionDoc>) {}
|
|
134
|
+
async get(sessionId: bigint): Promise<SessionRecord | null> {
|
|
135
|
+
const doc = await this.col.findOne({ _id: sessionId.toString() })
|
|
136
|
+
if (!doc) return null
|
|
137
|
+
return {
|
|
138
|
+
sessionId,
|
|
139
|
+
authKeyId: BigInt(doc.authKeyId),
|
|
140
|
+
uniqueId: BigInt(doc.uniqueId),
|
|
141
|
+
apiLayer: doc.apiLayer,
|
|
142
|
+
subject: doc.subject,
|
|
143
|
+
lastActivity: doc.lastActivity,
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async save(rec: SessionRecord): Promise<void> {
|
|
147
|
+
await this.col.updateOne(
|
|
148
|
+
{ _id: rec.sessionId.toString() },
|
|
149
|
+
{
|
|
150
|
+
$set: {
|
|
151
|
+
authKeyId: rec.authKeyId.toString(),
|
|
152
|
+
uniqueId: rec.uniqueId.toString(),
|
|
153
|
+
apiLayer: rec.apiLayer,
|
|
154
|
+
subject: rec.subject,
|
|
155
|
+
lastActivity: rec.lastActivity,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{ upsert: true },
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
async update(sessionId: bigint, patch: Partial<SessionRecord>): Promise<void> {
|
|
162
|
+
const set: Record<string, unknown> = {}
|
|
163
|
+
if (patch.apiLayer !== undefined) set.apiLayer = patch.apiLayer
|
|
164
|
+
if (patch.subject !== undefined) set.subject = patch.subject
|
|
165
|
+
if (patch.lastActivity !== undefined) set.lastActivity = patch.lastActivity
|
|
166
|
+
if (Object.keys(set).length) await this.col.updateOne({ _id: sessionId.toString() }, { $set: set })
|
|
167
|
+
}
|
|
168
|
+
async delete(sessionId: bigint): Promise<void> {
|
|
169
|
+
await this.col.deleteOne({ _id: sessionId.toString() })
|
|
170
|
+
}
|
|
171
|
+
async touch(sessionId: bigint): Promise<boolean> {
|
|
172
|
+
const res = await this.col.updateOne(
|
|
173
|
+
{ _id: sessionId.toString() },
|
|
174
|
+
{ $set: { lastActivity: Date.now() } },
|
|
175
|
+
)
|
|
176
|
+
return res.matchedCount > 0
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function createMongoStorage(url: string, dbName: string): Promise<Storage> {
|
|
181
|
+
const client = new MongoClient(url)
|
|
182
|
+
await client.connect()
|
|
183
|
+
const db: Db = client.db(dbName)
|
|
184
|
+
await ensureIndexes(db)
|
|
185
|
+
return {
|
|
186
|
+
authKeys: new MongoAuthKeyRepo(db.collection<AuthKeyDoc>('authKeys')),
|
|
187
|
+
salts: new MongoSaltRepo(db.collection<SaltDoc>('serverSalts')),
|
|
188
|
+
sessions: new MongoSessionRepo(db.collection<SessionDoc>('sessions')),
|
|
189
|
+
async close() {
|
|
190
|
+
await client.close()
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Create the secondary indexes the protocol collections rely on. Idempotent
|
|
197
|
+
* (Mongo no-ops an index that already exists), so it's safe to run on every
|
|
198
|
+
* startup. `_id`-keyed lookups (get/save/touch by id) need no extra index.
|
|
199
|
+
*/
|
|
200
|
+
async function ensureIndexes(db: Db): Promise<void> {
|
|
201
|
+
await Promise.all([
|
|
202
|
+
// serverSalts: list() filters by authKeyId + sorts by validSince; prune()
|
|
203
|
+
// filters by authKeyId + validUntil.
|
|
204
|
+
db.collection('serverSalts').createIndex({ authKeyId: 1, validSince: 1 }),
|
|
205
|
+
db.collection('serverSalts').createIndex({ authKeyId: 1, validUntil: 1 }),
|
|
206
|
+
// sessions: looked up / aggregated by the key, the user, and recency.
|
|
207
|
+
db.collection('sessions').createIndex({ authKeyId: 1 }),
|
|
208
|
+
db.collection('sessions').createIndex({ subject: 1 }),
|
|
209
|
+
db.collection('sessions').createIndex({ lastActivity: 1 }),
|
|
210
|
+
// authKeys: "all devices for a user", moderation, and creation-over-time.
|
|
211
|
+
db.collection('authKeys').createIndex({ subject: 1 }),
|
|
212
|
+
db.collection('authKeys').createIndex({ isBlocked: 1 }),
|
|
213
|
+
db.collection('authKeys').createIndex({ createdAt: 1 }),
|
|
214
|
+
])
|
|
215
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** Persistence contracts for auth keys, server salts and sessions. */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Connection/device metadata for an auth key. An auth key is one device
|
|
5
|
+
* authorization (one app install), so this — captured from `initConnection` — is
|
|
6
|
+
* stable per key and lives here rather than being duplicated across the key's
|
|
7
|
+
* sessions. `apiLayer` is the last layer negotiated via `invokeWithLayer`,
|
|
8
|
+
* restored for a fresh session before it re-negotiates.
|
|
9
|
+
*/
|
|
10
|
+
export interface AuthKeyMeta {
|
|
11
|
+
apiLayer?: number
|
|
12
|
+
/** `initConnection.api_id` — the client app id. */
|
|
13
|
+
apiId?: number
|
|
14
|
+
/** `initConnection.device_model`. */
|
|
15
|
+
deviceModel?: string
|
|
16
|
+
/** `initConnection.system_version`. */
|
|
17
|
+
systemVersion?: string
|
|
18
|
+
/** `initConnection.app_version`. */
|
|
19
|
+
appVersion?: string
|
|
20
|
+
/** `initConnection.system_lang_code`. */
|
|
21
|
+
systemLangCode?: string
|
|
22
|
+
/** `initConnection.lang_code`. */
|
|
23
|
+
langCode?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface AuthKeyRecord {
|
|
27
|
+
id: bigint
|
|
28
|
+
key: Buffer
|
|
29
|
+
expiresIn: boolean
|
|
30
|
+
createdAt: Date
|
|
31
|
+
/** The bound subject (your internal user id), or null for an anonymous key. */
|
|
32
|
+
subject: string | null
|
|
33
|
+
isBlocked?: boolean
|
|
34
|
+
meta?: AuthKeyMeta
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SessionRecord {
|
|
38
|
+
sessionId: bigint
|
|
39
|
+
authKeyId: bigint
|
|
40
|
+
uniqueId: bigint
|
|
41
|
+
apiLayer: number
|
|
42
|
+
subject?: string
|
|
43
|
+
lastActivity: number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface AuthKeyRepo {
|
|
47
|
+
create(rec: AuthKeyRecord): Promise<void>
|
|
48
|
+
getById(id: bigint): Promise<AuthKeyRecord | null>
|
|
49
|
+
setBlocked(id: bigint, blocked: boolean): Promise<void>
|
|
50
|
+
/** Bind (or clear, with null) the subject authorized on this auth key. */
|
|
51
|
+
bindUser(id: bigint, subject: string | null): Promise<void>
|
|
52
|
+
/** Merge `initConnection`-derived device/app fields into the key's meta. */
|
|
53
|
+
updateMeta(id: bigint, patch: AuthKeyMeta): Promise<void>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** One scheduled server salt and its validity window (unix seconds). */
|
|
57
|
+
export interface SaltScheduleEntry {
|
|
58
|
+
salt: bigint
|
|
59
|
+
/** Inclusive lower bound (unix seconds). */
|
|
60
|
+
validSince: number
|
|
61
|
+
/** Exclusive upper bound (unix seconds). */
|
|
62
|
+
validUntil: number
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface SaltRepo {
|
|
66
|
+
/**
|
|
67
|
+
* Persist salt-schedule entries for an auth key. Inserts only windows not
|
|
68
|
+
* already present (keyed by `validSince`) and never overwrites an existing
|
|
69
|
+
* window's salt, so concurrent gateway nodes converge on one salt per window.
|
|
70
|
+
*/
|
|
71
|
+
append(authKeyId: bigint, entries: SaltScheduleEntry[]): Promise<void>
|
|
72
|
+
/** Full salt schedule for an auth key, ascending by `validSince`. */
|
|
73
|
+
list(authKeyId: bigint): Promise<SaltScheduleEntry[]>
|
|
74
|
+
/** Drop entries that fully expired before `before` (unix seconds). */
|
|
75
|
+
prune(authKeyId: bigint, before: number): Promise<void>
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface SessionRepo {
|
|
79
|
+
get(sessionId: bigint): Promise<SessionRecord | null>
|
|
80
|
+
save(rec: SessionRecord): Promise<void>
|
|
81
|
+
update(sessionId: bigint, patch: Partial<SessionRecord>): Promise<void>
|
|
82
|
+
delete(sessionId: bigint): Promise<void>
|
|
83
|
+
/** Refresh last-activity; returns true if the session existed. */
|
|
84
|
+
touch(sessionId: bigint): Promise<boolean>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface Storage {
|
|
88
|
+
authKeys: AuthKeyRepo
|
|
89
|
+
salts: SaltRepo
|
|
90
|
+
sessions: SessionRepo
|
|
91
|
+
close(): Promise<void>
|
|
92
|
+
}
|
package/src/testkit.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// @mt-tl/server/testkit — protocol primitives a TEST CLIENT needs (TL codec +
|
|
2
|
+
// the client side of the MTProto 2.0 crypto). NOT part of the consumer surface:
|
|
3
|
+
// servers are built with `createServer` from the package root. This subpath
|
|
4
|
+
// exists so `@mt-tl/testing` can drive a real handshake + encrypted RPC
|
|
5
|
+
// against a gateway without deep-importing internal files. The crypto here is
|
|
6
|
+
// the same code pinned byte-for-byte by `test/crypto.kat.test.ts`.
|
|
7
|
+
|
|
8
|
+
export { TlReader } from './tl/reader.js'
|
|
9
|
+
export { TlWriter } from './tl/writer.js'
|
|
10
|
+
export { TlCodec } from './tl/codec.js'
|
|
11
|
+
export { loadSchema, type LoadSchemaResult } from './tl/registry.js'
|
|
12
|
+
|
|
13
|
+
export { igeEncrypt, igeDecrypt } from './crypto/aes-ige.js'
|
|
14
|
+
export { sha1 } from './crypto/hashes.js'
|
|
15
|
+
export { generateMessageKey, computeMsgKey } from './crypto/msg-key.js'
|
|
16
|
+
export { modPow } from './crypto/dh.js'
|
|
17
|
+
export { rsaEncryptNoPadding } from './crypto/rsa.js'
|
|
18
|
+
|
|
19
|
+
export { toBigIntBE, toBigIntLE, toBufferBE, toBufferLE, xorBuffers } from './util/bytes.js'
|
package/src/tl/codec.ts
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import type { TlDef, TlType } from '@mt-tl/tl'
|
|
2
|
+
import type { TlRegistry } from './registry.js'
|
|
3
|
+
import type { LayeredRegistry } from './layered-registry.js'
|
|
4
|
+
import { TlReader, BOOL_TRUE_ID, BOOL_FALSE_ID, VECTOR_ID } from './reader.js'
|
|
5
|
+
import { TlWriter } from './writer.js'
|
|
6
|
+
import type { TlObject, TlValue } from '@mt-tl/tl'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* IR-driven (de)serializer for arbitrary TL values. Constructed types are tagged
|
|
10
|
+
* objects `{ _: name, ...fields }`. Hand-written protocol codecs (registered on
|
|
11
|
+
* the registry) take precedence for their ids; everything else routes here.
|
|
12
|
+
*
|
|
13
|
+
* When a {@link LayeredRegistry} is supplied, `encode(value, layer)` writes each
|
|
14
|
+
* type with the constructor id/fields valid at the client's layer (decoding
|
|
15
|
+
* stays global by id). Without a layer, the merged registry is used.
|
|
16
|
+
*/
|
|
17
|
+
export class TlCodec {
|
|
18
|
+
/** Set transiently during a single `encode(value, layer)` call. */
|
|
19
|
+
private encodeLayer?: number
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
private readonly registry: TlRegistry,
|
|
23
|
+
private readonly layered?: LayeredRegistry,
|
|
24
|
+
) {}
|
|
25
|
+
|
|
26
|
+
encode(value: TlObject, layer?: number): Buffer {
|
|
27
|
+
this.encodeLayer = this.layered && layer !== undefined ? layer : undefined
|
|
28
|
+
try {
|
|
29
|
+
const w = new TlWriter()
|
|
30
|
+
this.writeObject(w, value)
|
|
31
|
+
return w.toBuffer()
|
|
32
|
+
} finally {
|
|
33
|
+
this.encodeLayer = undefined
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
decode(buf: Buffer): TlValue {
|
|
38
|
+
return this.readObject(new TlReader(buf))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Resolve the def to serialize a name with, honoring the active encode layer. */
|
|
42
|
+
private defForWrite(name: string): TlDef | undefined {
|
|
43
|
+
if (this.layered && this.encodeLayer !== undefined) {
|
|
44
|
+
const layerDef = this.layered.resolve(name, this.encodeLayer)
|
|
45
|
+
if (layerDef) return layerDef
|
|
46
|
+
}
|
|
47
|
+
return this.registry.getByName(name)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- boxed object (with constructor id) ---------------------------------
|
|
51
|
+
|
|
52
|
+
writeObject(w: TlWriter, value: TlValue): void {
|
|
53
|
+
if (typeof value === 'boolean') {
|
|
54
|
+
w.writeBool(value)
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
if (Array.isArray(value)) {
|
|
58
|
+
w.writeUInt32(VECTOR_ID)
|
|
59
|
+
w.writeUInt32(value.length)
|
|
60
|
+
for (const el of value) this.writeObject(w, el)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
if (value && typeof value === 'object' && '_' in value) {
|
|
64
|
+
const obj = value as TlObject
|
|
65
|
+
const codec = this.protocolCodecByName(obj._)
|
|
66
|
+
if (codec) {
|
|
67
|
+
codec.write(w, obj)
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
const def = this.defForWrite(obj._)
|
|
71
|
+
if (!def) throw new Error(`Cannot serialize unknown TL type: ${obj._}`)
|
|
72
|
+
w.writeUInt32(def.idNum)
|
|
73
|
+
this.writeFields(w, def.params, obj)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
throw new Error(`Cannot serialize value as boxed object: ${String(value)}`)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
readObject(r: TlReader): TlValue {
|
|
80
|
+
const id = r.readUInt32()
|
|
81
|
+
if (id === BOOL_TRUE_ID) return true
|
|
82
|
+
if (id === BOOL_FALSE_ID) return false
|
|
83
|
+
if (id === VECTOR_ID) {
|
|
84
|
+
const count = r.readUInt32()
|
|
85
|
+
const out: TlValue[] = []
|
|
86
|
+
for (let i = 0; i < count; i++) out.push(this.readObject(r))
|
|
87
|
+
return out
|
|
88
|
+
}
|
|
89
|
+
const protocolCodec = this.registry.getProtocolCodec(id)
|
|
90
|
+
if (protocolCodec) return protocolCodec.read(r) as TlValue
|
|
91
|
+
const def = this.registry.getById(id)
|
|
92
|
+
if (!def) {
|
|
93
|
+
throw new Error(`Cannot read unknown TL id 0x${(id >>> 0).toString(16).padStart(8, '0')}`)
|
|
94
|
+
}
|
|
95
|
+
const obj: TlObject = { _: def.name }
|
|
96
|
+
this.readFields(r, def.params, obj)
|
|
97
|
+
return obj
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// --- bare constructor (no id) -------------------------------------------
|
|
101
|
+
|
|
102
|
+
private writeBare(w: TlWriter, name: string, value: TlValue): void {
|
|
103
|
+
const def = this.defForWrite(name)
|
|
104
|
+
if (!def) throw new Error(`Cannot serialize unknown bare type: ${name}`)
|
|
105
|
+
this.writeFields(w, def.params, (value ?? { _: name }) as TlObject)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private readBare(r: TlReader, name: string): TlObject {
|
|
109
|
+
const def = this.registry.getByName(name)
|
|
110
|
+
if (!def) throw new Error(`Cannot read unknown bare type: ${name}`)
|
|
111
|
+
const obj: TlObject = { _: name }
|
|
112
|
+
this.readFields(r, def.params, obj)
|
|
113
|
+
return obj
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// --- fields (with flags handling) ---------------------------------------
|
|
117
|
+
|
|
118
|
+
private writeFields(w: TlWriter, params: import('@mt-tl/tl').TlParam[], obj: TlObject): void {
|
|
119
|
+
const bitmaskFields = collectBitmaskFields(params)
|
|
120
|
+
const flags: Record<string, number> = {}
|
|
121
|
+
for (const p of params) {
|
|
122
|
+
if (p.type.kind !== 'flag') continue
|
|
123
|
+
const v = obj[p.name]
|
|
124
|
+
const present = p.type.inner.kind === 'true' ? v === true : v !== undefined && v !== null
|
|
125
|
+
if (present) flags[p.type.flagsField] = (flags[p.type.flagsField] ?? 0) | (1 << p.type.bit)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const p of params) {
|
|
129
|
+
const t = p.type
|
|
130
|
+
if (bitmaskFields.has(p.name)) {
|
|
131
|
+
// Bitmask field (`flags:#` or `flags:int`): write the value computed
|
|
132
|
+
// from which conditional fields are present.
|
|
133
|
+
w.writeUInt32((flags[p.name] ?? 0) >>> 0)
|
|
134
|
+
} else if (t.kind === 'flag') {
|
|
135
|
+
const set = ((flags[t.flagsField] ?? 0) >>> t.bit) & 1
|
|
136
|
+
if (set && t.inner.kind !== 'true') this.writeType(w, t.inner, obj[p.name])
|
|
137
|
+
} else {
|
|
138
|
+
this.writeType(w, t, obj[p.name])
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private readFields(r: TlReader, params: import('@mt-tl/tl').TlParam[], obj: TlObject): void {
|
|
144
|
+
const bitmaskFields = collectBitmaskFields(params)
|
|
145
|
+
const flags: Record<string, number> = {}
|
|
146
|
+
for (const p of params) {
|
|
147
|
+
const t = p.type
|
|
148
|
+
if (bitmaskFields.has(p.name)) {
|
|
149
|
+
const v = r.readUInt32()
|
|
150
|
+
flags[p.name] = v
|
|
151
|
+
obj[p.name] = v
|
|
152
|
+
} else if (t.kind === 'flag') {
|
|
153
|
+
const set = ((flags[t.flagsField] ?? 0) >>> t.bit) & 1
|
|
154
|
+
if (set) obj[p.name] = t.inner.kind === 'true' ? true : this.readType(r, t.inner)
|
|
155
|
+
} else {
|
|
156
|
+
obj[p.name] = this.readType(r, t)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// --- single typed value --------------------------------------------------
|
|
162
|
+
|
|
163
|
+
private writeType(w: TlWriter, t: TlType, value: TlValue): void {
|
|
164
|
+
switch (t.kind) {
|
|
165
|
+
case 'int':
|
|
166
|
+
w.writeInt32(Number(value ?? 0))
|
|
167
|
+
return
|
|
168
|
+
case 'long':
|
|
169
|
+
w.writeLong(typeof value === 'bigint' ? value : BigInt((value as number | string) ?? 0))
|
|
170
|
+
return
|
|
171
|
+
case 'double':
|
|
172
|
+
w.writeDouble(Number(value ?? 0))
|
|
173
|
+
return
|
|
174
|
+
case 'string':
|
|
175
|
+
// Binary may be supplied for a `string` field (the protocol schema
|
|
176
|
+
// declares pq/g_a/g_b/encrypted_data as `string`). Preserve bytes
|
|
177
|
+
// exactly; `string` and `bytes` share the same wire encoding.
|
|
178
|
+
if (Buffer.isBuffer(value)) w.writeBytes(value)
|
|
179
|
+
else w.writeString(value == null ? '' : String(value))
|
|
180
|
+
return
|
|
181
|
+
case 'bytes':
|
|
182
|
+
w.writeBytes(asBuffer(value))
|
|
183
|
+
return
|
|
184
|
+
case 'int128':
|
|
185
|
+
w.writeFixed(asBuffer(value), 16)
|
|
186
|
+
return
|
|
187
|
+
case 'int256':
|
|
188
|
+
w.writeFixed(asBuffer(value), 32)
|
|
189
|
+
return
|
|
190
|
+
case 'bool':
|
|
191
|
+
w.writeBool(Boolean(value))
|
|
192
|
+
return
|
|
193
|
+
case 'true':
|
|
194
|
+
return
|
|
195
|
+
case 'flags':
|
|
196
|
+
w.writeUInt32(Number(value ?? 0) >>> 0)
|
|
197
|
+
return
|
|
198
|
+
case 'vector':
|
|
199
|
+
this.writeVector(w, t, Array.isArray(value) ? value : [])
|
|
200
|
+
return
|
|
201
|
+
case 'object':
|
|
202
|
+
case 'boxed':
|
|
203
|
+
this.writeObject(w, value)
|
|
204
|
+
return
|
|
205
|
+
case 'bare':
|
|
206
|
+
this.writeBare(w, t.name, value)
|
|
207
|
+
return
|
|
208
|
+
case 'flag':
|
|
209
|
+
throw new Error('flag must be handled by writeFields')
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private readType(r: TlReader, t: TlType): TlValue {
|
|
214
|
+
switch (t.kind) {
|
|
215
|
+
case 'int':
|
|
216
|
+
return r.readInt32()
|
|
217
|
+
case 'long':
|
|
218
|
+
return r.readLong()
|
|
219
|
+
case 'double':
|
|
220
|
+
return r.readDouble()
|
|
221
|
+
case 'string':
|
|
222
|
+
return r.readString()
|
|
223
|
+
case 'bytes':
|
|
224
|
+
return r.readBytes()
|
|
225
|
+
case 'int128':
|
|
226
|
+
return r.readInt128()
|
|
227
|
+
case 'int256':
|
|
228
|
+
return r.readInt256()
|
|
229
|
+
case 'bool':
|
|
230
|
+
return r.readUInt32() === BOOL_TRUE_ID
|
|
231
|
+
case 'true':
|
|
232
|
+
return true
|
|
233
|
+
case 'flags':
|
|
234
|
+
return r.readUInt32()
|
|
235
|
+
case 'vector':
|
|
236
|
+
return this.readVector(r, t)
|
|
237
|
+
case 'object':
|
|
238
|
+
case 'boxed':
|
|
239
|
+
return this.readObject(r)
|
|
240
|
+
case 'bare':
|
|
241
|
+
return this.readBare(r, t.name)
|
|
242
|
+
case 'flag':
|
|
243
|
+
throw new Error('flag must be handled by readFields')
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private writeVector(w: TlWriter, t: Extract<TlType, { kind: 'vector' }>, arr: TlValue[]): void {
|
|
248
|
+
if (t.boxed) w.writeUInt32(VECTOR_ID)
|
|
249
|
+
w.writeUInt32(arr.length)
|
|
250
|
+
for (const el of arr) this.writeType(w, t.inner, el)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private readVector(r: TlReader, t: Extract<TlType, { kind: 'vector' }>): TlValue[] {
|
|
254
|
+
if (t.boxed) {
|
|
255
|
+
const id = r.readUInt32()
|
|
256
|
+
if (id !== VECTOR_ID) {
|
|
257
|
+
throw new Error(`Expected Vector id, got 0x${(id >>> 0).toString(16).padStart(8, '0')}`)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const count = r.readUInt32()
|
|
261
|
+
const out: TlValue[] = []
|
|
262
|
+
for (let i = 0; i < count; i++) out.push(this.readType(r, t.inner))
|
|
263
|
+
return out
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private protocolCodecByName(name: string) {
|
|
267
|
+
const def = this.registry.getByName(name)
|
|
268
|
+
if (!def) return undefined
|
|
269
|
+
return this.registry.getProtocolCodec(def.idNum)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Names of the bitmask fields in a param list: any `#` field plus any field
|
|
275
|
+
* referenced by a conditional (`name.bit?...`). Handles schemas that declare
|
|
276
|
+
* the bitmask as `flags:int` rather than the canonical `flags:#`.
|
|
277
|
+
*/
|
|
278
|
+
function collectBitmaskFields(params: import('@mt-tl/tl').TlParam[]): Set<string> {
|
|
279
|
+
const names = new Set<string>()
|
|
280
|
+
for (const p of params) {
|
|
281
|
+
if (p.type.kind === 'flags') names.add(p.name)
|
|
282
|
+
else if (p.type.kind === 'flag') names.add(p.type.flagsField)
|
|
283
|
+
}
|
|
284
|
+
return names
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function asBuffer(value: TlValue): Buffer {
|
|
288
|
+
if (Buffer.isBuffer(value)) return value
|
|
289
|
+
if (value == null) return Buffer.alloc(0)
|
|
290
|
+
if (typeof value === 'string') return Buffer.from(value, 'hex')
|
|
291
|
+
throw new Error(`Expected Buffer, got ${typeof value}`)
|
|
292
|
+
}
|