@h-ai/crypto 0.1.0-alpha.73 → 0.1.0-alpha.75

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/README.en.md CHANGED
@@ -254,6 +254,8 @@ Empty POSTs still send an authenticated envelope for empty plaintext. GET/HEAD c
254
254
 
255
255
  Set `transport.maxPlaintextBytes` for both request and response JSON plaintext, measured in UTF-8 bytes: default 1 MiB, integer range 1 KiB–64 MiB. Clients learn the server limit during key exchange. `resolveTransportBodyLimits()` derives encrypted/proxy bytes as twice the plaintext limit plus 32 KiB. Handshake, public-key, header and replay constraints remain independent. Oversized streams are cancelled and readers released: requests return 413; oversized responses return an encrypted 500 capacity error, recognized as `TransportBodyTooLargeError` / `hai:crypto:070`. [容量配置、文件换算与升级](TRANSPORT-CAPACITY.md)。
256
256
 
257
+ Ordinary routes retain `transport.maxPlaintextBytes` (auto/omitted: 1 MiB). Declare Feedback report intake, evidence, attachments and ZIP download capacity separately using `routePlaintextBytes` and `feedback.budget.maxTransportBytes`; `*` matches one complete path segment and explicit host route values take precedence. Handshakes, adapter-node and proxies use the maximum route capacity. HTTP adapters admit at most 2 explicitly declared material requests above 1 MiB and a separate 16 ordinary business requests, releasing admission after the entire response or failure. Busy admission returns 503 / `hai:crypto:072`; business writes are not automatically replayed.
258
+
257
259
  serv/kit return an encrypted HTTP 400 time error for an out-of-window message only after request binding, signature and MAC verification. The client throws `TransportTimeError` (`hai:crypto:071`) with advice to synchronize device/server clocks and refresh. `api-client` maps it to `VALIDATION_FAILED` and preserves `cause.transportCode`. Requests are not replayed automatically and replay windows remain unchanged; tampered or unauthenticated messages retain the generic authentication failure.
258
260
 
259
261
  Custom HTTP adapters check `error instanceof TransportTimeError && error.context`, then encrypt the rejection with `createTransportErrorResponse(error, { manager, context: error.context })`. Never classify an unverified message by its timestamp.
@@ -296,6 +298,17 @@ The admin console's online encryption demo calls the Crypto SM3/SM4 APIs directl
296
298
 
297
299
  Production transport registries must declare stateScope=shared and actually use Redis or PostgreSQL/MySQL. Memory, Redis factories over memory, and RelDB factories over SQLite are rejected. All nodes load the same managed versioned key ring. Initialization checks apply when NODE_ENV or HAI_CORE_ENV is production; only explicit HAI_E2E=1/HAI_E2E_MOCK=1 tests are exempt. [State, concurrency and recovery (Chinese)](../../.agents/instructions/distributed-state.md)。
298
300
 
301
+ ## Module requirements and explicit limits
302
+
303
+ `resolveTransportBodyLimits(limit?, { requiredPlaintextBytes? })` derives capacity from enabled modules only when limit is omitted; without module requirements it remains 1 MiB. A smaller explicit limit throws RangeError instead of silently increasing or reducing capacity. All values remain bounded to 1 KiB–64 MiB; encrypted capacity is twice plaintext plus 32 KiB. Serv/Kit configuration accepts `auto`, normalized to undefined at runtime.
304
+
305
+ ```ts
306
+ import { resolveTransportBodyLimits } from '@h-ai/crypto'
307
+
308
+ const limits = resolveTransportBodyLimits(24 * 1024 * 1024, { requiredPlaintextBytes: 11250348 })
309
+ // plaintextBytes: 25165824; encryptedPayloadBytes: 50364416
310
+ ```
311
+
299
312
  ## Development and testing
300
313
 
301
314
  ```bash
package/README.ja.md CHANGED
@@ -254,6 +254,8 @@ const server = crypto.transport.createServer({ serverKeyRing, keyStore })
254
254
 
255
255
  `transport.maxPlaintextBytes` でリクエストとレスポンスの JSON 平文容量を共通設定します。単位は UTF-8 バイト、既定値は 1 MiB、有効範囲は 1 KiB~64 MiB の整数です。クライアントは鍵交換で上限を取得し、`resolveTransportBodyLimits()` が暗号文とプロキシ容量(平文の 2 倍 + 32 KiB)を算出します。鍵交換、公開鍵、認証ヘッダー、防リプレイ制約は独立です。超過時は元ストリームを停止して reader を解放します。要求は 413、応答は暗号化された 500 容量エラーとなり、`TransportBodyTooLargeError` / `hai:crypto:070` で識別できます。 [容量配置、文件换算与升级](TRANSPORT-CAPACITY.md)。
256
256
 
257
+ 通常業務は `transport.maxPlaintextBytes`(auto/省略は 1 MiB)を維持します。Feedback の受理・証拠・添付・ZIP ダウンロードは `routePlaintextBytes` と `feedback.budget.maxTransportBytes` で別途宣言します。`*` は完全な1パス区間のみ一致し、ホストの明示値が優先です。ハンドシェイク、adapter-node、プロキシは経路の最大値を使用します。HTTP アダプターは個別宣言された 1 MiB 超の資料経路を最大2件、通常業務を別枠で16件まで許可し、応答完了・失敗時に解放します。混雑は503 / `hai:crypto:072` を返し、業務書込は自動再送しません。
258
+
257
259
  serv/kit は要求の結合、署名、MAC を検証した後、時間窓を超えた報文に暗号化した HTTP 400 時刻エラーを返します。クライアントは `TransportTimeError`(`hai:crypto:071`)で端末とサーバーの時刻同期・再読み込みを案内します。`api-client` は `VALIDATION_FAILED` に変換し `cause.transportCode` に原因を保持します。自動再送やリプレイ時間窓の緩和は行わず、改ざん・未認証の要求は従来の認証エラーです。
258
260
 
259
261
  独自 HTTP アダプターでは `error instanceof TransportTimeError && error.context` を確認し、`createTransportErrorResponse(error, { manager, context: error.context })` で拒否応答を暗号化します。未認証の報文のタイムスタンプだけで時刻異常を診断しないでください。
@@ -296,6 +298,17 @@ if (!result.success) {
296
298
 
297
299
  本番の伝送 registry は stateScope=shared と実際の Redis または PostgreSQL/MySQL が必須です。memory、および memory/SQLite を包む工場も拒否します。各ノードは同じ管理されたバージョン付き鍵セットを読み込みます。 NODE_ENV または HAI_CORE_ENV が production の場合、初期化成功前に検証します。明示的な HAI_E2E=1/HAI_E2E_MOCK=1 テストのみ除外します。 [状態・並行制御・復旧範囲(中国語)](../../.agents/instructions/distributed-state.md)。
298
300
 
301
+ ## モジュール要件と明示的な上限
302
+
303
+ `resolveTransportBodyLimits(limit?, { requiredPlaintextBytes? })` は limit 省略時だけ有効なモジュールの必要量から計算し、要件がなければ 1 MiB です。明示上限が必要量未満なら RangeError とし、勝手に拡張・縮小しません。範囲は 1 KiB~64 MiB、暗号文は平文の2倍+32 KiBです。Serv/Kit の `auto` は実行時に undefined へ正規化します。
304
+
305
+ ```ts
306
+ import { resolveTransportBodyLimits } from '@h-ai/crypto'
307
+
308
+ const limits = resolveTransportBodyLimits(24 * 1024 * 1024, { requiredPlaintextBytes: 11250348 })
309
+ // plaintextBytes: 25165824; encryptedPayloadBytes: 50364416
310
+ ```
311
+
299
312
  ## 開発とテスト
300
313
 
301
314
  ```bash
package/README.md CHANGED
@@ -254,6 +254,8 @@ const server = crypto.transport.createServer({ serverKeyRing, keyStore })
254
254
 
255
255
  业务容量通过 `transport.maxPlaintextBytes` 统一配置:默认 1 MiB,整数范围 1 KiB~64 MiB,请求和响应共用 UTF-8 明文字节预算。客户端从握手自动获取上限,密文与部署代理预算由 `resolveTransportBodyLimits()` 推导(两倍明文 + 32 KiB)。握手、公钥、认证头和防重放约束独立;超限取消原流并释放 reader,请求 413、响应为加密的 500 容量错误,客户端识别 `TransportBodyTooLargeError` / `hai:crypto:070`。 [容量配置、文件换算与升级](TRANSPORT-CAPACITY.md)。
256
256
 
257
+ 普通业务仍使用 `transport.maxPlaintextBytes`(auto/省略为 1 MiB);Feedback 的报告受理、证据、附件及 ZIP 下载路由通过 `routePlaintextBytes` 单独声明 `feedback.budget.maxTransportBytes`。路径中的 `*` 仅匹配一个完整路径段;宿主显式路由值优先。握手、adapter-node 与代理使用各路由最大容量,普通业务不会随材料预算扩大。HTTP 适配器为单独声明且大于 1 MiB 的材料路由保留最多 2 个槽,普通业务另有 16 个槽,完整响应/失败后释放;忙碌返回 503 / `hai:crypto:072`,不自动重放业务写入。
258
+
257
259
  serv/kit 对通过请求绑定、签名和 MAC 校验但超出时间窗口的报文返回加密的 HTTP 400 时间错误;客户端抛 `TransportTimeError`(`hai:crypto:071`),提示同步设备和服务器时间后刷新重试。`api-client` 映射为 `VALIDATION_FAILED`,在 `cause.transportCode` 保留该原因。不会自动重放请求或放宽防重放窗口;篡改或未认证报文仍返回通用认证失败。
258
260
 
259
261
  自定义 HTTP 适配器检查失败原因 `error instanceof TransportTimeError && error.context` 后,使用 `createTransportErrorResponse(error, { manager, context: error.context })` 加密拒绝响应;不能根据未认证报文的时间戳给出可信时间诊断。
@@ -296,6 +298,17 @@ if (!result.success) {
296
298
 
297
299
  生产传输 registry 必须声明 stateScope=shared 并实际使用 Redis 或 PostgreSQL/MySQL。memory、包装 memory 的 Redis 工厂、包装 SQLite 的 RelDB 工厂会被拒绝;各节点加载相同受管版本化密钥环。 NODE_ENV 或 HAI_CORE_ENV 为 production 时在初始化发布成功前校验;仅 HAI_E2E=1/HAI_E2E_MOCK=1 明确测试可豁免。 [状态、并发和恢复边界](../../.agents/instructions/distributed-state.md)。
298
300
 
301
+ ## 模块容量与显式上限
302
+
303
+ `resolveTransportBodyLimits(limit?, { requiredPlaintextBytes? })` 只在 limit 未设置时按模块需求推导,无模块需求时仍为 1 MiB。显式上限小于模块需求时抛出 RangeError,不能静默扩大或缩小。所有值仍须在 1 KiB~64 MiB 范围;密文预算为明文两倍加 32 KiB。Serv/Kit 配置可用 `auto` 表示未显式限额,运行时将它规范化为 undefined。
304
+
305
+ ```ts
306
+ import { resolveTransportBodyLimits } from '@h-ai/crypto'
307
+
308
+ const limits = resolveTransportBodyLimits(24 * 1024 * 1024, { requiredPlaintextBytes: 11250348 })
309
+ // limits.plaintextBytes === 25165824;limits.encryptedPayloadBytes === 50364416
310
+ ```
311
+
299
312
  ## 开发测试
300
313
 
301
314
  ```bash
package/dist/index.d.ts CHANGED
@@ -52,6 +52,8 @@ declare const TRANSPORT_BODY_LIMITS: TransportBodyLimits;
52
52
  * 校验单一业务明文容量并推导双向密文预算;返回不可变快照。
53
53
  * SM4-CBC 使用十六进制编码,预算为明文的两倍加 32 KiB(填充及认证封装)。
54
54
  * @param maxPlaintextBytes - UTF-8 字节数,默认 1 MiB,有效范围 1 KiB~64 MiB 的整数。
55
+ * @param options - 已启用模块所需的最小明文预算;只在宿主未显式设置上限时自动推导。
56
+ * @param options.requiredPlaintextBytes - 模块需要的最小 UTF-8 明文字节数,仍受 1 KiB~64 MiB 范围约束。
55
57
  * @returns 明文、密文及独立安全预算。
56
58
  * @throws RangeError - 配置无效;不接受字符串、null、无限制或静默回退。
57
59
  * @example
@@ -61,7 +63,9 @@ declare const TRANSPORT_BODY_LIMITS: TransportBodyLimits;
61
63
  * const nginxLimit = limits.encryptedPayloadBytes
62
64
  * ```
63
65
  */
64
- declare function resolveTransportBodyLimits(maxPlaintextBytes?: number): TransportBodyLimits;
66
+ declare function resolveTransportBodyLimits(maxPlaintextBytes?: number, options?: {
67
+ requiredPlaintextBytes?: number;
68
+ }): TransportBodyLimits;
65
69
  /**
66
70
  * 可识别的容量错误;只包含方向、阶段和字节数,不包含报文内容。
67
71
  * @example
@@ -119,6 +123,29 @@ interface ReadTransportBodyOptions {
119
123
  * ```
120
124
  */
121
125
  declare function readTransportBody(stream: ReadableStream<Uint8Array> | null, options: ReadTransportBodyOptions): Promise<string>;
126
+ /**
127
+ * 为真实 HTTP 适配器分离业务与大附件容量;* 只匹配一个完整路径段。
128
+ * 单独声明且超过 1 MiB 的材料路由最多两个,普通业务最多十六个,租约须在响应完成或失败时释放。
129
+ * @param maxPlaintextBytes - 普通业务容量;省略使用 1 MiB。
130
+ * @param routes - 受信任的绝对路径到 UTF-8 容量映射,最多 100 条。
131
+ * @returns 协商最大容量及每请求的限额和可释放并发准入。
132
+ * @throws RangeError 配置路径、数量或容量无效。
133
+ * @example
134
+ * ```ts
135
+ * import { createTransportBodyPolicy } from '@h-ai/crypto'
136
+ * const policy = createTransportBodyPolicy(undefined, { '/api/files/*': 8 * 1024 * 1024 })
137
+ * const lease = policy.acquire('/api/files/attachment')
138
+ * try { if (lease) lease.limits.plaintextBytes }
139
+ * finally { lease?.release() }
140
+ * ```
141
+ */
142
+ declare function createTransportBodyPolicy(maxPlaintextBytes?: number, routes?: Readonly<Record<string, number>>): {
143
+ maximum: TransportBodyLimits;
144
+ acquire(pathname: string): {
145
+ limits: TransportBodyLimits;
146
+ release(): void;
147
+ } | null;
148
+ };
122
149
 
123
150
  /**
124
151
  * 认证传输v2的头名、窗口与默认协商子路径;各HTTP适配器共用,不手写漂移常量。
@@ -855,6 +882,7 @@ declare const HaiCryptoError: {
855
882
  readonly INVALID_IV: _h_ai_core.HaiErrorDef;
856
883
  readonly TRANSPORT_PAYLOAD_TOO_LARGE: _h_ai_core.HaiErrorDef;
857
884
  readonly TRANSPORT_TIME_INVALID: _h_ai_core.HaiErrorDef;
885
+ readonly TRANSPORT_BUSY: _h_ai_core.HaiErrorDef;
858
886
  };
859
887
  /**
860
888
  * SM2 密文排列:1为C1C3C2默认值,0为显式C1C2C3;解密必须匹配加密排列。
@@ -1969,16 +1997,12 @@ declare function createTransportErrorResponse(error: TransportBodyTooLargeError
1969
1997
  */
1970
1998
  declare function parseTransportServerKeyRing(serialized: unknown): HaiResult<TransportServerKeyRing>;
1971
1999
 
1972
- /**
1973
- * @h-ai/crypto — serv/kit 共用的实际 HTTP 请求认证入口。
1974
- * @module crypto-transport-request
1975
- */
1976
-
1977
2000
  /**
1978
2001
  * 从实际HTTP请求提取可信方法/路径/查询/客户端ID并有界认证;成功后才执行业务。
1979
2002
  * 拒绝缺少认证及头/正文重复承载,空请求也认证;不会由payload自证HTTP上下文。
1980
2003
  * @param manager - 使用共享原子nonce registry的已创建管理器。
1981
2004
  * @param request - 未消费正文的真实Request。
2005
+ * @param limits - HTTP 路由容量;默认管理器协商容量。
1982
2006
  * @returns 明文和响应关联上下文;认证失败无明文。
1983
2007
  * @throws RangeError超限;流读取/取消沿用原异常,HTTP适配器负责转响应。
1984
2008
  *
@@ -1994,7 +2018,7 @@ declare function parseTransportServerKeyRing(serialized: unknown): HaiResult<Tra
1994
2018
  * }
1995
2019
  * ```
1996
2020
  */
1997
- declare function authenticateTransportRequest(manager: TransportEncryptionManager, request: Request): Promise<HaiResult<{
2021
+ declare function authenticateTransportRequest(manager: TransportEncryptionManager, request: Request, limits?: TransportBodyLimits): Promise<HaiResult<{
1998
2022
  plaintext: string;
1999
2023
  context: TransportMessageContext;
2000
2024
  }>>;
@@ -2187,4 +2211,4 @@ interface ReldbTransportKeyStore extends TransportKeyStore {
2187
2211
  */
2188
2212
  declare function createReldbTransportKeyStore(options: CreateReldbTransportKeyStoreOptions): ReldbTransportKeyStore;
2189
2213
 
2190
- export { type AsymmetricEncryptOptions, type AsymmetricOperations, type CipherMode, type CreateRedisTransportKeyStoreOptions, type CreateReldbTransportKeyStoreOptions, type CryptoFunctions, type EncryptWithIVResult, type EncryptedPayload, type EncryptedRequestPayload, HaiCryptoError, type HashOperations, type HashOptions, type KeyExchangeRequest, type KeyExchangeResponse, type KeyPair, type PasswordConfig, type PasswordOperations, type ReadTransportBodyOptions, type ReldbTransportKeyStore, type SignOptions, type SymmetricCbcEncryptedPayload, type SymmetricCiphertextEncoding, type SymmetricDecryptInput, type SymmetricEncryptOptions, type SymmetricEncryptedPayload, type SymmetricMode, type SymmetricOperations, TRANSPORT_BODY_LIMITS, TRANSPORT_PLAINTEXT_RANGE, TRANSPORT_PROTOCOL, type TransportBodyLimits, TransportBodyTooLargeError, type TransportClient, type TransportCreateServerOptions, type TransportCryptoServiceLike, type TransportEncryptionManager, type TransportKeyPair, type TransportKeyStore, type TransportMessageContext, type TransportOperations, type TransportRequestContext, type TransportServerKeyRing, TransportTimeError, authenticateTransportRequest, createInMemoryKeyStore, createRedisTransportKeyStore, createReldbTransportKeyStore, createTransportErrorResponse, crypto, isValidSM2PublicKey, parseTransportServerKeyRing, readTransportBody, resolveTransportBodyLimits };
2214
+ export { type AsymmetricEncryptOptions, type AsymmetricOperations, type CipherMode, type CreateRedisTransportKeyStoreOptions, type CreateReldbTransportKeyStoreOptions, type CryptoFunctions, type EncryptWithIVResult, type EncryptedPayload, type EncryptedRequestPayload, HaiCryptoError, type HashOperations, type HashOptions, type KeyExchangeRequest, type KeyExchangeResponse, type KeyPair, type PasswordConfig, type PasswordOperations, type ReadTransportBodyOptions, type ReldbTransportKeyStore, type SignOptions, type SymmetricCbcEncryptedPayload, type SymmetricCiphertextEncoding, type SymmetricDecryptInput, type SymmetricEncryptOptions, type SymmetricEncryptedPayload, type SymmetricMode, type SymmetricOperations, TRANSPORT_BODY_LIMITS, TRANSPORT_PLAINTEXT_RANGE, TRANSPORT_PROTOCOL, type TransportBodyLimits, TransportBodyTooLargeError, type TransportClient, type TransportCreateServerOptions, type TransportCryptoServiceLike, type TransportEncryptionManager, type TransportKeyPair, type TransportKeyStore, type TransportMessageContext, type TransportOperations, type TransportRequestContext, type TransportServerKeyRing, TransportTimeError, authenticateTransportRequest, createInMemoryKeyStore, createRedisTransportKeyStore, createReldbTransportKeyStore, createTransportBodyPolicy, createTransportErrorResponse, crypto, isValidSM2PublicKey, parseTransportServerKeyRing, readTransportBody, resolveTransportBodyLimits };
package/dist/index.js CHANGED
@@ -58,7 +58,9 @@ var en_US_default = {
58
58
  crypto_transportSessionKeyDecryptFailed: "Failed to decrypt transport session key",
59
59
  crypto_transportBodyLimitInvalid: "Transport plaintext limit must be an integer between 1024 and 67108864 bytes",
60
60
  crypto_transportBodyTooLarge: "Encrypted transport payload exceeds the configured byte limit",
61
- crypto_transportTimeInvalid: "Your device and server clocks may be out of sync, or the request has expired. Enable automatic time synchronization and refresh to retry. If this continues, ask an administrator to check the server clock."
61
+ crypto_transportTimeInvalid: "Your device and server clocks may be out of sync, or the request has expired. Enable automatic time synchronization and refresh to retry. If this continues, ask an administrator to check the server clock.",
62
+ crypto_transportBodyLimitConflict: "Explicit transport limit {limit} bytes is below the enabled module requirement of {required} bytes; adjust the module budget or explicit limit",
63
+ crypto_transportBusy: "Material processing is busy. Please retry shortly."
62
64
  };
63
65
 
64
66
  // messages/ja-JP.json
@@ -116,7 +118,9 @@ var ja_JP_default = {
116
118
  crypto_transportSessionKeyDecryptFailed: "\u901A\u4FE1\u30BB\u30C3\u30B7\u30E7\u30F3\u9375\u306E\u5FA9\u53F7\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
117
119
  crypto_transportBodyLimitInvalid: "\u901A\u4FE1\u5E73\u6587\u306E\u4E0A\u9650\u306F 1024 \u304B\u3089 67108864 \u30D0\u30A4\u30C8\u306E\u6574\u6570\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
118
120
  crypto_transportBodyTooLarge: "\u6697\u53F7\u5316\u901A\u4FE1\u306E\u30C7\u30FC\u30BF\u304C\u8A2D\u5B9A\u3055\u308C\u305F\u30D0\u30A4\u30C8\u4E0A\u9650\u3092\u8D85\u3048\u3066\u3044\u307E\u3059",
119
- crypto_transportTimeInvalid: "\u7AEF\u672B\u3068\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u304C\u540C\u671F\u3057\u3066\u3044\u306A\u3044\u304B\u3001\u8981\u6C42\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059\u3002\u81EA\u52D5\u6642\u523B\u540C\u671F\u3092\u6709\u52B9\u306B\u3057\u3066\u30DA\u30FC\u30B8\u3092\u518D\u8AAD\u307F\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u89E3\u6D88\u3057\u306A\u3044\u5834\u5408\u306F\u7BA1\u7406\u8005\u306B\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u78BA\u8A8D\u3092\u4F9D\u983C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
121
+ crypto_transportTimeInvalid: "\u7AEF\u672B\u3068\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u304C\u540C\u671F\u3057\u3066\u3044\u306A\u3044\u304B\u3001\u8981\u6C42\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059\u3002\u81EA\u52D5\u6642\u523B\u540C\u671F\u3092\u6709\u52B9\u306B\u3057\u3066\u30DA\u30FC\u30B8\u3092\u518D\u8AAD\u307F\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u89E3\u6D88\u3057\u306A\u3044\u5834\u5408\u306F\u7BA1\u7406\u8005\u306B\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u78BA\u8A8D\u3092\u4F9D\u983C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
122
+ crypto_transportBodyLimitConflict: "\u660E\u793A\u3055\u308C\u305F\u8EE2\u9001\u4E0A\u9650 {limit} \u30D0\u30A4\u30C8\u306F\u6709\u52B9\u306A\u30E2\u30B8\u30E5\u30FC\u30EB\u306E\u5FC5\u8981\u91CF {required} \u30D0\u30A4\u30C8\u672A\u6E80\u3067\u3059\u3002\u4E88\u7B97\u307E\u305F\u306F\u4E0A\u9650\u3092\u5909\u66F4\u3057\u3066\u304F\u3060\u3055\u3044",
123
+ crypto_transportBusy: "\u8CC7\u6599\u306E\u51E6\u7406\u304C\u6DF7\u307F\u5408\u3063\u3066\u3044\u307E\u3059\u3002\u3057\u3070\u3089\u304F\u3057\u3066\u304B\u3089\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
120
124
  };
121
125
 
122
126
  // messages/zh-CN.json
@@ -174,7 +178,9 @@ var zh_CN_default = {
174
178
  crypto_transportSessionKeyDecryptFailed: "\u4F20\u8F93\u52A0\u5BC6\u4F1A\u8BDD\u5BC6\u94A5\u89E3\u5BC6\u5931\u8D25",
175
179
  crypto_transportBodyLimitInvalid: "\u4F20\u8F93\u660E\u6587\u5BB9\u91CF\u5FC5\u987B\u662F 1024 \u5230 67108864 \u5B57\u8282\u4E4B\u95F4\u7684\u6574\u6570",
176
180
  crypto_transportBodyTooLarge: "\u52A0\u5BC6\u4F20\u8F93\u62A5\u6587\u8D85\u8FC7\u914D\u7F6E\u7684\u5B57\u8282\u4E0A\u9650",
177
- crypto_transportTimeInvalid: "\u8BBE\u5907\u4E0E\u670D\u52A1\u5668\u65F6\u95F4\u53EF\u80FD\u4E0D\u540C\u6B65\uFF0C\u6216\u8BF7\u6C42\u5DF2\u8FC7\u671F\u3002\u8BF7\u5F00\u542F\u81EA\u52A8\u6821\u65F6\u5E76\u5237\u65B0\u91CD\u8BD5\uFF1B\u82E5\u4ECD\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u68C0\u67E5\u670D\u52A1\u5668\u65F6\u95F4\u3002"
181
+ crypto_transportTimeInvalid: "\u8BBE\u5907\u4E0E\u670D\u52A1\u5668\u65F6\u95F4\u53EF\u80FD\u4E0D\u540C\u6B65\uFF0C\u6216\u8BF7\u6C42\u5DF2\u8FC7\u671F\u3002\u8BF7\u5F00\u542F\u81EA\u52A8\u6821\u65F6\u5E76\u5237\u65B0\u91CD\u8BD5\uFF1B\u82E5\u4ECD\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u68C0\u67E5\u670D\u52A1\u5668\u65F6\u95F4\u3002",
182
+ crypto_transportBodyLimitConflict: "\u663E\u5F0F\u4F20\u8F93\u4E0A\u9650 {limit} \u5B57\u8282\u5C0F\u4E8E\u5DF2\u542F\u7528\u6A21\u5757\u6240\u9700\u7684 {required} \u5B57\u8282\uFF1B\u8BF7\u8C03\u6574\u6A21\u5757\u9884\u7B97\u6216\u663E\u5F0F\u4E0A\u9650",
183
+ crypto_transportBusy: "\u6750\u6599\u5904\u7406\u7E41\u5FD9\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
178
184
  };
179
185
 
180
186
  // src/crypto-i18n.ts
@@ -197,7 +203,8 @@ var CryptoErrorInfo = {
197
203
  HMAC_FAILED: "041:500",
198
204
  INVALID_IV: "060:400",
199
205
  TRANSPORT_PAYLOAD_TOO_LARGE: "070:413",
200
- TRANSPORT_TIME_INVALID: "071:400"
206
+ TRANSPORT_TIME_INVALID: "071:400",
207
+ TRANSPORT_BUSY: "072:503"
201
208
  };
202
209
  var HaiCryptoError = core.error.buildHaiErrorsDef("crypto", CryptoErrorInfo);
203
210
 
@@ -852,9 +859,16 @@ var TRANSPORT_BODY_LIMITS = Object.freeze({
852
859
  /** 密文 JSON 的 UTF-8 总字节数(2 MiB + 32 KiB,含 CBC 填充与全部认证字段)。 */
853
860
  encryptedPayloadBytes: 2129920
854
861
  });
855
- function resolveTransportBodyLimits(maxPlaintextBytes = TRANSPORT_BODY_LIMITS.plaintextBytes) {
862
+ function resolveTransportBodyLimits(maxPlaintextBytes, options = {}) {
863
+ const required = options.requiredPlaintextBytes;
864
+ if (required !== void 0 && (!Number.isSafeInteger(required) || required < TRANSPORT_PLAINTEXT_RANGE.min || required > TRANSPORT_PLAINTEXT_RANGE.max))
865
+ throw new RangeError(cryptoM("crypto_transportBodyLimitInvalid"));
866
+ if (maxPlaintextBytes === void 0)
867
+ maxPlaintextBytes = required ?? TRANSPORT_BODY_LIMITS.plaintextBytes;
856
868
  if (!Number.isSafeInteger(maxPlaintextBytes) || maxPlaintextBytes < TRANSPORT_PLAINTEXT_RANGE.min || maxPlaintextBytes > TRANSPORT_PLAINTEXT_RANGE.max)
857
869
  throw new RangeError(cryptoM("crypto_transportBodyLimitInvalid"));
870
+ if (required !== void 0 && maxPlaintextBytes < required)
871
+ throw new RangeError(cryptoM("crypto_transportBodyLimitConflict", { params: { limit: maxPlaintextBytes, required } }));
858
872
  return Object.freeze({ ...TRANSPORT_BODY_LIMITS, plaintextBytes: maxPlaintextBytes, encryptedPayloadBytes: 2 * maxPlaintextBytes + 32768 });
859
873
  }
860
874
  var TransportBodyTooLargeError = class extends RangeError {
@@ -921,6 +935,41 @@ async function readTransportBody(stream, options) {
921
935
  reader.releaseLock();
922
936
  }
923
937
  }
938
+ function createTransportBodyPolicy(maxPlaintextBytes, routes = {}) {
939
+ const ordinary = resolveTransportBodyLimits(maxPlaintextBytes);
940
+ const entries = Object.entries(routes);
941
+ if (entries.length > 100 || entries.some(([path]) => !path.startsWith("/") || path.includes("?") || path.includes("#") || path.split("/").some((part) => part.includes("*") && part !== "*")))
942
+ throw new RangeError(cryptoM("crypto_transportBodyLimitInvalid"));
943
+ const configured = entries.map(([path, bytes]) => ({ parts: path.split("/"), limits: resolveTransportBodyLimits(bytes) }));
944
+ const maximum = resolveTransportBodyLimits(Math.max(ordinary.plaintextBytes, ...configured.map((route) => route.limits.plaintextBytes)));
945
+ let small = 0;
946
+ let large = 0;
947
+ return {
948
+ maximum,
949
+ acquire(pathname) {
950
+ const parts = pathname.split("/");
951
+ const matching = configured.filter((route) => route.parts.length === parts.length && route.parts.every((part, index) => part === "*" || part === parts[index]));
952
+ const limits = matching.length ? matching.reduce((left, right) => left.limits.plaintextBytes < right.limits.plaintextBytes ? left : right).limits : ordinary;
953
+ const heavy = matching.length > 0 && limits.plaintextBytes > TRANSPORT_BODY_LIMITS.plaintextBytes;
954
+ if (heavy ? large >= 2 : small >= 16)
955
+ return null;
956
+ if (heavy)
957
+ large++;
958
+ else
959
+ small++;
960
+ let released = false;
961
+ return { limits, release() {
962
+ if (released)
963
+ return;
964
+ released = true;
965
+ if (heavy)
966
+ large--;
967
+ else
968
+ small--;
969
+ } };
970
+ }
971
+ };
972
+ }
924
973
 
925
974
  // src/transport/crypto-transport-types.ts
926
975
  var TRANSPORT_PROTOCOL = {
@@ -1242,6 +1291,8 @@ function createTransportClient(options) {
1242
1291
  if (response.headers.get(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER) !== TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE) {
1243
1292
  void response.body?.cancel().catch(() => {
1244
1293
  });
1294
+ if (response.status === 503)
1295
+ throw Object.assign(new Error(cryptoM("crypto_transportBusy")), { code: HaiCryptoError.TRANSPORT_BUSY.code, httpStatus: 503 });
1245
1296
  if (response.status === 413)
1246
1297
  throw new TransportBodyTooLargeError(bodyLimits.encryptedPayloadBytes, void 0, { stage: "encrypted" });
1247
1298
  throw new Error(cryptoM("crypto_transportResponseDecryptFailed"));
@@ -1575,7 +1626,7 @@ var crypto2 = {
1575
1626
  logger.info("Crypto module closed");
1576
1627
  }
1577
1628
  };
1578
- async function authenticateTransportRequest(manager, request) {
1629
+ async function authenticateTransportRequest(manager, request, limits = manager.bodyLimits) {
1579
1630
  const reject = () => {
1580
1631
  void request.body?.cancel().catch(() => {
1581
1632
  });
@@ -1593,7 +1644,7 @@ async function authenticateTransportRequest(manager, request) {
1593
1644
  if (envelope && new TextEncoder().encode(envelope).byteLength > TRANSPORT_BODY_LIMITS.envelopeHeaderBytes)
1594
1645
  throw new TransportBodyTooLargeError(TRANSPORT_BODY_LIMITS.envelopeHeaderBytes, new TextEncoder().encode(envelope).byteLength, { stage: "header" });
1595
1646
  const text = envelope ?? await readTransportBody(request.body, {
1596
- maxBytes: manager.bodyLimits.encryptedPayloadBytes,
1647
+ maxBytes: limits.encryptedPayloadBytes,
1597
1648
  stage: "encrypted",
1598
1649
  signal: request.signal
1599
1650
  });
@@ -1603,7 +1654,7 @@ async function authenticateTransportRequest(manager, request) {
1603
1654
  } catch {
1604
1655
  return transportAuthenticationFailure();
1605
1656
  }
1606
- if (!isValidEncryptedPayload(payload, manager.bodyLimits) || payload.direction !== "request" || payload.status !== 0)
1657
+ if (!isValidEncryptedPayload(payload, limits) || payload.direction !== "request" || payload.status !== 0)
1607
1658
  return transportAuthenticationFailure();
1608
1659
  const url = new URL(request.url);
1609
1660
  const decrypted = await manager.decryptRequest(payload, {
@@ -1616,8 +1667,8 @@ async function authenticateTransportRequest(manager, request) {
1616
1667
  throw decrypted.error.cause;
1617
1668
  return decrypted;
1618
1669
  }
1619
- if (new TextEncoder().encode(decrypted.data).byteLength > manager.bodyLimits.plaintextBytes)
1620
- throw new TransportBodyTooLargeError(manager.bodyLimits.plaintextBytes, new TextEncoder().encode(decrypted.data).byteLength);
1670
+ if (new TextEncoder().encode(decrypted.data).byteLength > limits.plaintextBytes)
1671
+ throw new TransportBodyTooLargeError(limits.plaintextBytes, new TextEncoder().encode(decrypted.data).byteLength);
1621
1672
  if (bodyless && decrypted.data !== "")
1622
1673
  return transportAuthenticationFailure();
1623
1674
  return ok({ plaintext: decrypted.data, context: payload.context });
@@ -1922,6 +1973,6 @@ function createReldbTransportKeyStore(options) {
1922
1973
  };
1923
1974
  }
1924
1975
 
1925
- export { HaiCryptoError, TRANSPORT_BODY_LIMITS, TRANSPORT_PLAINTEXT_RANGE, TRANSPORT_PROTOCOL, TransportBodyTooLargeError, TransportTimeError, authenticateTransportRequest, createInMemoryKeyStore, createRedisTransportKeyStore, createReldbTransportKeyStore, createTransportErrorResponse, crypto2 as crypto, isValidSM2PublicKey, parseTransportServerKeyRing, readTransportBody, resolveTransportBodyLimits };
1976
+ export { HaiCryptoError, TRANSPORT_BODY_LIMITS, TRANSPORT_PLAINTEXT_RANGE, TRANSPORT_PROTOCOL, TransportBodyTooLargeError, TransportTimeError, authenticateTransportRequest, createInMemoryKeyStore, createRedisTransportKeyStore, createReldbTransportKeyStore, createTransportBodyPolicy, createTransportErrorResponse, crypto2 as crypto, isValidSM2PublicKey, parseTransportServerKeyRing, readTransportBody, resolveTransportBodyLimits };
1926
1977
  //# sourceMappingURL=index.js.map
1927
1978
  //# sourceMappingURL=index.js.map