@onekeyfe/react-native-sni-connect 3.0.81 → 3.0.82
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.md +0 -7
- package/android/src/main/java/com/sniconnect/SniConnectModule.kt +61 -213
- package/android/src/main/java/com/sniconnect/SniConnectValidation.kt +76 -6
- package/android/src/main/java/com/sniconnect/SniPinnedTransport.kt +58 -0
- package/android/src/test/java/com/sniconnect/SniConnectValidationTest.kt +26 -26
- package/ios/SniConnect.mm +0 -19
- package/ios/SniConnect.swift +0 -29
- package/ios/SniConnectClient.swift +86 -75
- package/ios/SniConnectCore.swift +4 -0
- package/ios/SniConnectPinnedTransport.swift +92 -0
- package/ios/SniConnectValidation.swift +41 -214
- package/ios/Tests/SniConnectValidationTests/SniConnectValidationTests.swift +17 -206
- package/lib/module/index.js +0 -3
- package/lib/typescript/src/NativeSniConnect.d.ts +0 -13
- package/lib/typescript/src/index.d.ts +2 -3
- package/package.json +1 -1
- package/src/NativeSniConnect.ts +0 -17
- package/src/index.tsx +0 -10
- package/android/src/main/java/com/sniconnect/SniConnectRequestAdmission.kt +0 -276
- package/android/src/test/java/com/sniconnect/SniConnectRequestAdmissionTest.kt +0 -299
|
@@ -32,7 +32,6 @@ enum SniConnectValidation {
|
|
|
32
32
|
static let maxTotalHeaderBytes = 32 * 1024
|
|
33
33
|
static let maxActiveRequests = 64
|
|
34
34
|
static let maxActiveRequestsPerPair = 16
|
|
35
|
-
static let maxPendingRequests = 256
|
|
36
35
|
|
|
37
36
|
/// HTTP methods the module is allowed to issue.
|
|
38
37
|
private static let allowedMethods: Set<String> = [
|
|
@@ -213,16 +212,6 @@ enum SniConnectValidation {
|
|
|
213
212
|
throw ValidationError.invalidIP(ip)
|
|
214
213
|
}
|
|
215
214
|
|
|
216
|
-
static func canonicalIPKey(_ ip: String) -> String {
|
|
217
|
-
if let v4 = parseIPv4(ip) {
|
|
218
|
-
return "4:" + v4.map { String($0) }.joined(separator: ".")
|
|
219
|
-
}
|
|
220
|
-
if let v6 = parseIPv6(ip) {
|
|
221
|
-
return "6:" + v6.map { String($0) }.joined(separator: ".")
|
|
222
|
-
}
|
|
223
|
-
return ip
|
|
224
|
-
}
|
|
225
|
-
|
|
226
215
|
private static func parseIPv4(_ ip: String) -> [UInt8]? {
|
|
227
216
|
var addr = in_addr()
|
|
228
217
|
guard ip.withCString({ inet_pton(AF_INET, $0, &addr) }) == 1 else { return nil }
|
|
@@ -312,27 +301,16 @@ enum SniConnectValidation {
|
|
|
312
301
|
}
|
|
313
302
|
}
|
|
314
303
|
|
|
315
|
-
final class SniConnectRequestLimiter
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
struct Snapshot {
|
|
319
|
-
let activeRequests: Int
|
|
320
|
-
let activeRequestsForPair: Int
|
|
321
|
-
let pendingRequests: Int
|
|
322
|
-
let pendingRequestsForPair: Int
|
|
323
|
-
let activeRequestIdsForPair: [String]
|
|
324
|
-
let pendingRequestIdsForPair: [String]
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
final class Token: @unchecked Sendable {
|
|
304
|
+
final class SniConnectRequestLimiter {
|
|
305
|
+
final class Token {
|
|
328
306
|
private weak var limiter: SniConnectRequestLimiter?
|
|
329
|
-
private let
|
|
307
|
+
private let key: String
|
|
330
308
|
private let lock = NSLock()
|
|
331
309
|
private var released = false
|
|
332
310
|
|
|
333
|
-
fileprivate init(limiter: SniConnectRequestLimiter,
|
|
311
|
+
fileprivate init(limiter: SniConnectRequestLimiter, key: String) {
|
|
334
312
|
self.limiter = limiter
|
|
335
|
-
self.
|
|
313
|
+
self.key = key
|
|
336
314
|
}
|
|
337
315
|
|
|
338
316
|
func release() {
|
|
@@ -343,7 +321,7 @@ final class SniConnectRequestLimiter: @unchecked Sendable {
|
|
|
343
321
|
}
|
|
344
322
|
released = true
|
|
345
323
|
lock.unlock()
|
|
346
|
-
limiter?.release(
|
|
324
|
+
limiter?.release(key: key)
|
|
347
325
|
}
|
|
348
326
|
|
|
349
327
|
deinit {
|
|
@@ -351,216 +329,65 @@ final class SniConnectRequestLimiter: @unchecked Sendable {
|
|
|
351
329
|
}
|
|
352
330
|
}
|
|
353
331
|
|
|
354
|
-
private final class CancellationState: @unchecked Sendable {
|
|
355
|
-
private let lock = NSLock()
|
|
356
|
-
private var cancelled = false
|
|
357
|
-
|
|
358
|
-
var isCancelled: Bool {
|
|
359
|
-
lock.lock()
|
|
360
|
-
defer { lock.unlock() }
|
|
361
|
-
return cancelled
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
func cancel() {
|
|
365
|
-
lock.lock()
|
|
366
|
-
cancelled = true
|
|
367
|
-
lock.unlock()
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
private struct PendingRequest {
|
|
372
|
-
let id: UUID
|
|
373
|
-
let key: String
|
|
374
|
-
let requestId: String?
|
|
375
|
-
let continuation: CheckedContinuation<Token, Error>
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
private struct ActiveRequest {
|
|
379
|
-
let key: String
|
|
380
|
-
let requestId: String?
|
|
381
|
-
}
|
|
382
|
-
|
|
383
332
|
private let maxActiveRequests: Int
|
|
384
333
|
private let maxActiveRequestsPerPair: Int
|
|
385
|
-
private let maxPendingRequests: Int
|
|
386
334
|
private let queue = DispatchQueue(label: "com.onekey.sni.connect.request-limiter")
|
|
387
335
|
private var activeRequests = 0
|
|
388
336
|
private var activeRequestsByPair: [String: Int] = [:]
|
|
389
|
-
private var activeRequestsByID: [UUID: ActiveRequest] = [:]
|
|
390
|
-
private var pendingRequests: [PendingRequest] = []
|
|
391
337
|
|
|
392
338
|
init(
|
|
393
339
|
maxActiveRequests: Int = SniConnectValidation.maxActiveRequests,
|
|
394
|
-
maxActiveRequestsPerPair: Int = SniConnectValidation.maxActiveRequestsPerPair
|
|
395
|
-
maxPendingRequests: Int = SniConnectValidation.maxPendingRequests
|
|
340
|
+
maxActiveRequestsPerPair: Int = SniConnectValidation.maxActiveRequestsPerPair
|
|
396
341
|
) {
|
|
397
342
|
self.maxActiveRequests = maxActiveRequests
|
|
398
343
|
self.maxActiveRequestsPerPair = maxActiveRequestsPerPair
|
|
399
|
-
self.maxPendingRequests = maxPendingRequests
|
|
400
344
|
}
|
|
401
345
|
|
|
402
|
-
|
|
403
|
-
queue.sync {
|
|
404
|
-
pendingRequests.count
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
func snapshot(hostname: String, ip: String) -> Snapshot {
|
|
346
|
+
func acquire(hostname: String, ip: String) throws -> Token {
|
|
409
347
|
let key = pairKey(hostname: hostname, ip: ip)
|
|
410
|
-
return queue.sync {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
func acquire(hostname: String, ip: String, requestId: String? = nil) async throws -> Token {
|
|
425
|
-
try Task.checkCancellation()
|
|
426
|
-
|
|
427
|
-
let id = UUID()
|
|
428
|
-
let key = pairKey(hostname: hostname, ip: ip)
|
|
429
|
-
let trackedRequestId = requestId.flatMap { $0.isEmpty ? nil : $0 }
|
|
430
|
-
let cancellationState = CancellationState()
|
|
431
|
-
|
|
432
|
-
let token = try await withTaskCancellationHandler {
|
|
433
|
-
try Task.checkCancellation()
|
|
434
|
-
return try await withCheckedThrowingContinuation { continuation in
|
|
435
|
-
var immediateToken: Token?
|
|
436
|
-
var immediateError: Error?
|
|
437
|
-
|
|
438
|
-
queue.sync {
|
|
439
|
-
if cancellationState.isCancelled {
|
|
440
|
-
immediateError = CancellationError()
|
|
441
|
-
return
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
if hasCapacity(for: key) {
|
|
445
|
-
retainSlot(id: id, key: key, requestId: trackedRequestId)
|
|
446
|
-
immediateToken = Token(limiter: self, id: id)
|
|
447
|
-
return
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if pendingRequests.count >= maxPendingRequests {
|
|
451
|
-
SniConnectCoreDiagnostics.warn(SniConnectCoreDiagnostics.event("sni_resource_limit", [
|
|
452
|
-
("activeCount", activeRequests),
|
|
453
|
-
("pairCount", activeRequestsByPair[key] ?? 0),
|
|
454
|
-
("pendingCount", pendingRequests.count),
|
|
455
|
-
("limit", maxPendingRequests),
|
|
456
|
-
("reason", "max_pending_requests"),
|
|
457
|
-
("hostname", hostname.lowercased()),
|
|
458
|
-
("ipHash", SniConnectCoreDiagnostics.shortHash(ip)),
|
|
459
|
-
]))
|
|
460
|
-
immediateError = SniConnectValidation.ValidationError.resourceLimit(
|
|
461
|
-
"Too many pending SNI requests"
|
|
462
|
-
)
|
|
463
|
-
return
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
pendingRequests.append(PendingRequest(
|
|
467
|
-
id: id,
|
|
468
|
-
key: key,
|
|
469
|
-
requestId: trackedRequestId,
|
|
470
|
-
continuation: continuation
|
|
471
|
-
))
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
if let immediateToken {
|
|
475
|
-
continuation.resume(returning: immediateToken)
|
|
476
|
-
} else if let immediateError {
|
|
477
|
-
continuation.resume(throwing: immediateError)
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
} onCancel: {
|
|
481
|
-
cancellationState.cancel()
|
|
482
|
-
self.cancelPendingRequest(id: id)
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// Cancellation can race with a pending-to-active handoff. Never return a
|
|
486
|
-
// token to an already-cancelled caller without releasing its retained slot.
|
|
487
|
-
do {
|
|
488
|
-
try Task.checkCancellation()
|
|
489
|
-
return token
|
|
490
|
-
} catch {
|
|
491
|
-
token.release()
|
|
492
|
-
throw error
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
private func release(id: UUID) {
|
|
497
|
-
var nextRequest: PendingRequest?
|
|
498
|
-
|
|
499
|
-
queue.sync {
|
|
500
|
-
guard releaseSlot(id: id) else { return }
|
|
501
|
-
|
|
502
|
-
guard activeRequests < maxActiveRequests,
|
|
503
|
-
let index = pendingRequests.firstIndex(where: { hasCapacity(for: $0.key) }) else {
|
|
504
|
-
return
|
|
348
|
+
return try queue.sync {
|
|
349
|
+
if activeRequests >= maxActiveRequests {
|
|
350
|
+
SniConnectCoreDiagnostics.warn(SniConnectCoreDiagnostics.event("sni_resource_limit", [
|
|
351
|
+
("activeCount", activeRequests),
|
|
352
|
+
("pairCount", activeRequestsByPair[key] ?? 0),
|
|
353
|
+
("limit", maxActiveRequests),
|
|
354
|
+
("reason", "max_active_requests"),
|
|
355
|
+
("hostname", hostname.lowercased()),
|
|
356
|
+
("ipHash", SniConnectCoreDiagnostics.shortHash(ip)),
|
|
357
|
+
]))
|
|
358
|
+
throw SniConnectValidation.ValidationError.resourceLimit("Too many active SNI requests")
|
|
505
359
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
360
|
+
let pairCount = activeRequestsByPair[key] ?? 0
|
|
361
|
+
if pairCount >= maxActiveRequestsPerPair {
|
|
362
|
+
SniConnectCoreDiagnostics.warn(SniConnectCoreDiagnostics.event("sni_resource_limit", [
|
|
363
|
+
("activeCount", activeRequests),
|
|
364
|
+
("pairCount", pairCount),
|
|
365
|
+
("limit", maxActiveRequestsPerPair),
|
|
366
|
+
("reason", "max_active_requests_per_pair"),
|
|
367
|
+
("hostname", hostname.lowercased()),
|
|
368
|
+
("ipHash", SniConnectCoreDiagnostics.shortHash(ip)),
|
|
369
|
+
]))
|
|
370
|
+
throw SniConnectValidation.ValidationError.resourceLimit("Too many active SNI requests for destination")
|
|
514
371
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
nextRequest.continuation.resume(returning: Token(limiter: self, id: nextRequest.id))
|
|
372
|
+
activeRequests += 1
|
|
373
|
+
activeRequestsByPair[key] = pairCount + 1
|
|
374
|
+
return Token(limiter: self, key: key)
|
|
519
375
|
}
|
|
520
376
|
}
|
|
521
377
|
|
|
522
|
-
private func
|
|
523
|
-
var cancelledRequest: PendingRequest?
|
|
524
|
-
|
|
378
|
+
private func release(key: String) {
|
|
525
379
|
queue.sync {
|
|
526
|
-
|
|
527
|
-
|
|
380
|
+
activeRequests = max(0, activeRequests - 1)
|
|
381
|
+
guard let pairCount = activeRequestsByPair[key] else { return }
|
|
382
|
+
if pairCount <= 1 {
|
|
383
|
+
activeRequestsByPair.removeValue(forKey: key)
|
|
384
|
+
} else {
|
|
385
|
+
activeRequestsByPair[key] = pairCount - 1
|
|
528
386
|
}
|
|
529
|
-
cancelledRequest = pendingRequests.remove(at: index)
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
cancelledRequest?.continuation.resume(throwing: CancellationError())
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
private func hasCapacity(for key: String) -> Bool {
|
|
536
|
-
activeRequests < maxActiveRequests &&
|
|
537
|
-
(activeRequestsByPair[key] ?? 0) < maxActiveRequestsPerPair
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
private func retainSlot(id: UUID, key: String, requestId: String?) {
|
|
541
|
-
activeRequestsByID[id] = ActiveRequest(key: key, requestId: requestId)
|
|
542
|
-
activeRequests += 1
|
|
543
|
-
activeRequestsByPair[key, default: 0] += 1
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
private func releaseSlot(id: UUID) -> Bool {
|
|
547
|
-
guard let activeRequest = activeRequestsByID.removeValue(forKey: id) else {
|
|
548
|
-
return false
|
|
549
|
-
}
|
|
550
|
-
let key = activeRequest.key
|
|
551
|
-
activeRequests = max(0, activeRequests - 1)
|
|
552
|
-
guard let pairCount = activeRequestsByPair[key] else {
|
|
553
|
-
return true
|
|
554
|
-
}
|
|
555
|
-
if pairCount <= 1 {
|
|
556
|
-
activeRequestsByPair.removeValue(forKey: key)
|
|
557
|
-
} else {
|
|
558
|
-
activeRequestsByPair[key] = pairCount - 1
|
|
559
387
|
}
|
|
560
|
-
return true
|
|
561
388
|
}
|
|
562
389
|
|
|
563
390
|
private func pairKey(hostname: String, ip: String) -> String {
|
|
564
|
-
return "\(hostname.lowercased())|\(
|
|
391
|
+
return "\(hostname.lowercased())|\(ip)"
|
|
565
392
|
}
|
|
566
393
|
}
|
|
@@ -3,6 +3,15 @@ import XCTest
|
|
|
3
3
|
|
|
4
4
|
final class SniConnectValidationTests: XCTestCase {
|
|
5
5
|
|
|
6
|
+
func testSessionWithoutForwardingDataDelegateAllowsResponse() {
|
|
7
|
+
switch SniConnectSessionDelegatePolicy.responseDispositionWithoutForwardingDelegate {
|
|
8
|
+
case .allow:
|
|
9
|
+
break
|
|
10
|
+
default:
|
|
11
|
+
XCTFail("A session without a forwarding data delegate must allow its response")
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
6
15
|
func testAcceptsValidRequestBoundaryValues() throws {
|
|
7
16
|
XCTAssertNoThrow(try SniConnectValidation.validateRequestId("req-1"))
|
|
8
17
|
XCTAssertNoThrow(try SniConnectValidation.validateTimeout(120_000))
|
|
@@ -113,18 +122,12 @@ final class SniConnectValidationTests: XCTestCase {
|
|
|
113
122
|
}
|
|
114
123
|
|
|
115
124
|
func testEnforcesRequestIdTimeoutAndBodyLimits() {
|
|
116
|
-
XCTAssertNoThrow(
|
|
117
|
-
try SniConnectValidation.validateRequestId(String(repeating: "界", count: 42))
|
|
118
|
-
)
|
|
119
125
|
assertValidationFails {
|
|
120
126
|
try SniConnectValidation.validateRequestId("")
|
|
121
127
|
}
|
|
122
128
|
assertValidationFails {
|
|
123
129
|
try SniConnectValidation.validateRequestId(String(repeating: "x", count: 129))
|
|
124
130
|
}
|
|
125
|
-
assertValidationFails {
|
|
126
|
-
try SniConnectValidation.validateRequestId(String(repeating: "界", count: 43))
|
|
127
|
-
}
|
|
128
131
|
assertValidationFails {
|
|
129
132
|
try SniConnectValidation.validateRequestId("req\n1")
|
|
130
133
|
}
|
|
@@ -201,196 +204,24 @@ final class SniConnectValidationTests: XCTestCase {
|
|
|
201
204
|
XCTAssertNoThrow(try SniConnectValidation.validateMethodBody(method: "OPTIONS", body: nil))
|
|
202
205
|
}
|
|
203
206
|
|
|
204
|
-
func
|
|
205
|
-
let limiter = SniConnectRequestLimiter()
|
|
206
|
-
var tasks: [String: Task<SniConnectRequestLimiter.Token, Error>] = [:]
|
|
207
|
-
|
|
208
|
-
for index in 0..<20 {
|
|
209
|
-
let requestId = String(format: "req-%02d", index)
|
|
210
|
-
tasks[requestId] = Task {
|
|
211
|
-
try await limiter.acquire(
|
|
212
|
-
hostname: "Example.com",
|
|
213
|
-
ip: index.isMultiple(of: 2)
|
|
214
|
-
? "2001:4860:4860::8888"
|
|
215
|
-
: "2001:4860:4860:0:0:0:0:8888",
|
|
216
|
-
requestId: requestId
|
|
217
|
-
)
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
await waitForPendingRequests(4, in: limiter)
|
|
222
|
-
let saturated = limiter.snapshot(
|
|
223
|
-
hostname: "EXAMPLE.COM",
|
|
224
|
-
ip: "2001:4860:4860::8888"
|
|
225
|
-
)
|
|
226
|
-
XCTAssertEqual(saturated.activeRequests, 16)
|
|
227
|
-
XCTAssertEqual(saturated.activeRequestsForPair, 16)
|
|
228
|
-
XCTAssertEqual(saturated.pendingRequests, 4)
|
|
229
|
-
XCTAssertEqual(saturated.pendingRequestsForPair, 4)
|
|
230
|
-
XCTAssertEqual(saturated.activeRequestIdsForPair.count, 16)
|
|
231
|
-
XCTAssertEqual(saturated.pendingRequestIdsForPair.count, 4)
|
|
232
|
-
|
|
233
|
-
for requestId in saturated.pendingRequestIdsForPair {
|
|
234
|
-
tasks[requestId]?.cancel()
|
|
235
|
-
}
|
|
236
|
-
for requestId in saturated.pendingRequestIdsForPair {
|
|
237
|
-
do {
|
|
238
|
-
_ = try await tasks[requestId]!.value
|
|
239
|
-
XCTFail("Expected pending request \(requestId) to be cancelled")
|
|
240
|
-
} catch is CancellationError {
|
|
241
|
-
// Expected.
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
XCTAssertEqual(limiter.pendingRequestCount, 0)
|
|
246
|
-
for requestId in saturated.activeRequestIdsForPair {
|
|
247
|
-
let token = try await tasks[requestId]!.value
|
|
248
|
-
token.release()
|
|
249
|
-
}
|
|
250
|
-
let drained = limiter.snapshot(
|
|
251
|
-
hostname: "example.com",
|
|
252
|
-
ip: "2001:4860:4860:0:0:0:0:8888"
|
|
253
|
-
)
|
|
254
|
-
XCTAssertEqual(drained.activeRequests, 0)
|
|
255
|
-
XCTAssertEqual(drained.pendingRequests, 0)
|
|
256
|
-
|
|
257
|
-
let recovery = try await limiter.acquire(
|
|
258
|
-
hostname: "example.com",
|
|
259
|
-
ip: "2001:4860:4860::8888",
|
|
260
|
-
requestId: "recovery"
|
|
261
|
-
)
|
|
262
|
-
recovery.release()
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
func testRequestLimiterQueuesGlobalAndPerDestinationLimits() async throws {
|
|
207
|
+
func testRequestLimiterEnforcesGlobalAndPerDestinationLimits() throws {
|
|
266
208
|
let limiter = SniConnectRequestLimiter(maxActiveRequests: 2, maxActiveRequestsPerPair: 1)
|
|
267
|
-
let firstToken = try
|
|
209
|
+
let firstToken = try limiter.acquire(hostname: "Example.com", ip: "93.184.216.34")
|
|
268
210
|
|
|
269
|
-
|
|
270
|
-
try
|
|
211
|
+
assertValidationFails {
|
|
212
|
+
_ = try limiter.acquire(hostname: "example.com", ip: "93.184.216.34")
|
|
271
213
|
}
|
|
272
|
-
await waitForPendingRequests(1, in: limiter)
|
|
273
214
|
|
|
274
|
-
let secondToken = try
|
|
275
|
-
|
|
276
|
-
try
|
|
215
|
+
let secondToken = try limiter.acquire(hostname: "example.com", ip: "93.184.216.35")
|
|
216
|
+
assertValidationFails {
|
|
217
|
+
_ = try limiter.acquire(hostname: "example.net", ip: "93.184.216.36")
|
|
277
218
|
}
|
|
278
|
-
await waitForPendingRequests(2, in: limiter)
|
|
279
219
|
|
|
280
220
|
firstToken.release()
|
|
281
|
-
let replacementToken = try
|
|
221
|
+
let replacementToken = try limiter.acquire(hostname: "example.com", ip: "93.184.216.34")
|
|
282
222
|
firstToken.release()
|
|
283
223
|
secondToken.release()
|
|
284
|
-
let globallyQueuedToken = try await globallyQueuedTask.value
|
|
285
224
|
replacementToken.release()
|
|
286
|
-
globallyQueuedToken.release()
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
func testRequestLimiterSnapshotCanonicalizesTargetAndReturnsRequestIDs() async throws {
|
|
290
|
-
let limiter = SniConnectRequestLimiter(maxActiveRequests: 2, maxActiveRequestsPerPair: 1)
|
|
291
|
-
let activeTarget = try await limiter.acquire(
|
|
292
|
-
hostname: "Example.com",
|
|
293
|
-
ip: "2001:4860:4860::8888",
|
|
294
|
-
requestId: "active-target"
|
|
295
|
-
)
|
|
296
|
-
let activeOther = try await limiter.acquire(
|
|
297
|
-
hostname: "example.com",
|
|
298
|
-
ip: "93.184.216.34",
|
|
299
|
-
requestId: "active-other"
|
|
300
|
-
)
|
|
301
|
-
let pendingTargetTask = Task {
|
|
302
|
-
try await limiter.acquire(
|
|
303
|
-
hostname: "example.com",
|
|
304
|
-
ip: "2001:4860:4860:0:0:0:0:8888",
|
|
305
|
-
requestId: "pending-target"
|
|
306
|
-
)
|
|
307
|
-
}
|
|
308
|
-
let pendingWithoutIDTask = Task {
|
|
309
|
-
try await limiter.acquire(
|
|
310
|
-
hostname: "example.com",
|
|
311
|
-
ip: "2001:4860:4860::8888",
|
|
312
|
-
requestId: ""
|
|
313
|
-
)
|
|
314
|
-
}
|
|
315
|
-
await waitForPendingRequests(2, in: limiter)
|
|
316
|
-
|
|
317
|
-
let snapshot = limiter.snapshot(
|
|
318
|
-
hostname: "EXAMPLE.COM",
|
|
319
|
-
ip: "2001:4860:4860:0:0:0:0:8888"
|
|
320
|
-
)
|
|
321
|
-
XCTAssertEqual(snapshot.activeRequests, 2)
|
|
322
|
-
XCTAssertEqual(snapshot.activeRequestsForPair, 1)
|
|
323
|
-
XCTAssertEqual(snapshot.pendingRequests, 2)
|
|
324
|
-
XCTAssertEqual(snapshot.pendingRequestsForPair, 2)
|
|
325
|
-
XCTAssertEqual(snapshot.activeRequestIdsForPair, ["active-target"])
|
|
326
|
-
XCTAssertEqual(snapshot.pendingRequestIdsForPair, ["pending-target"])
|
|
327
|
-
|
|
328
|
-
pendingTargetTask.cancel()
|
|
329
|
-
pendingWithoutIDTask.cancel()
|
|
330
|
-
_ = try? await pendingTargetTask.value
|
|
331
|
-
_ = try? await pendingWithoutIDTask.value
|
|
332
|
-
activeTarget.release()
|
|
333
|
-
activeOther.release()
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
func testRequestLimiterRejectsTheTwoHundredFiftySeventhPendingRequest() async throws {
|
|
337
|
-
let limiter = SniConnectRequestLimiter(
|
|
338
|
-
maxActiveRequests: 1,
|
|
339
|
-
maxActiveRequestsPerPair: 1,
|
|
340
|
-
maxPendingRequests: 256
|
|
341
|
-
)
|
|
342
|
-
let activeToken = try await limiter.acquire(
|
|
343
|
-
hostname: "example.com",
|
|
344
|
-
ip: "93.184.216.34"
|
|
345
|
-
)
|
|
346
|
-
let queuedTasks = (0..<256).map { index in
|
|
347
|
-
Task {
|
|
348
|
-
try await limiter.acquire(
|
|
349
|
-
hostname: "example.com",
|
|
350
|
-
ip: "93.184.216.34",
|
|
351
|
-
requestId: "pending-\(index)"
|
|
352
|
-
)
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
await waitForPendingRequests(256, in: limiter)
|
|
356
|
-
|
|
357
|
-
do {
|
|
358
|
-
_ = try await limiter.acquire(hostname: "example.com", ip: "93.184.216.34")
|
|
359
|
-
XCTFail("Expected the bounded pending queue to reject overflow")
|
|
360
|
-
} catch SniConnectValidation.ValidationError.resourceLimit(_) {
|
|
361
|
-
// Expected.
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
queuedTasks.forEach { $0.cancel() }
|
|
365
|
-
for task in queuedTasks {
|
|
366
|
-
_ = try? await task.value
|
|
367
|
-
}
|
|
368
|
-
XCTAssertEqual(limiter.pendingRequestCount, 0)
|
|
369
|
-
activeToken.release()
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
func testWallClockDeadlineCancelsPendingLimiterWait() async throws {
|
|
373
|
-
let limiter = SniConnectRequestLimiter(maxActiveRequests: 1, maxActiveRequestsPerPair: 1)
|
|
374
|
-
let activeToken = try await limiter.acquire(
|
|
375
|
-
hostname: "example.com",
|
|
376
|
-
ip: "93.184.216.34"
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
do {
|
|
380
|
-
_ = try await SniConnectWallClockDeadline.run(timeoutMilliseconds: 20) {
|
|
381
|
-
try await limiter.acquire(
|
|
382
|
-
hostname: "example.com",
|
|
383
|
-
ip: "93.184.216.34",
|
|
384
|
-
requestId: "deadline-pending"
|
|
385
|
-
)
|
|
386
|
-
}
|
|
387
|
-
XCTFail("Expected queue wait to consume the wall-clock deadline")
|
|
388
|
-
} catch let error as SniConnectTimeout {
|
|
389
|
-
XCTAssertEqual(error, .deadlineExceeded)
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
XCTAssertEqual(limiter.pendingRequestCount, 0)
|
|
393
|
-
activeToken.release()
|
|
394
225
|
}
|
|
395
226
|
|
|
396
227
|
func testResolverRegistryKeepsResolverClassUntilAllSessionsReleaseIt() throws {
|
|
@@ -500,24 +331,4 @@ final class SniConnectValidationTests: XCTestCase {
|
|
|
500
331
|
private func assertValidationFails(_ block: () throws -> Void, file: StaticString = #filePath, line: UInt = #line) {
|
|
501
332
|
XCTAssertThrowsError(try block(), file: file, line: line)
|
|
502
333
|
}
|
|
503
|
-
|
|
504
|
-
private func waitForPendingRequests(
|
|
505
|
-
_ expectedCount: Int,
|
|
506
|
-
in limiter: SniConnectRequestLimiter,
|
|
507
|
-
file: StaticString = #filePath,
|
|
508
|
-
line: UInt = #line
|
|
509
|
-
) async {
|
|
510
|
-
let deadline = Date().addingTimeInterval(5)
|
|
511
|
-
while Date() < deadline {
|
|
512
|
-
if limiter.pendingRequestCount == expectedCount {
|
|
513
|
-
return
|
|
514
|
-
}
|
|
515
|
-
try? await Task.sleep(nanoseconds: 1_000_000)
|
|
516
|
-
}
|
|
517
|
-
XCTFail(
|
|
518
|
-
"Expected \(expectedCount) pending requests, got \(limiter.pendingRequestCount)",
|
|
519
|
-
file: file,
|
|
520
|
-
line: line
|
|
521
|
-
)
|
|
522
|
-
}
|
|
523
334
|
}
|
package/lib/module/index.js
CHANGED
|
@@ -13,9 +13,6 @@ export function cancelAllRequests() {
|
|
|
13
13
|
export function clearDNSCache() {
|
|
14
14
|
return NativeSniConnect.clearDNSCache();
|
|
15
15
|
}
|
|
16
|
-
export function getDebugSnapshot(target) {
|
|
17
|
-
return NativeSniConnect.getDebugSnapshot(target);
|
|
18
|
-
}
|
|
19
16
|
export function isProxyActiveForUrl(url) {
|
|
20
17
|
return NativeSniConnect.isProxyActiveForUrl(url);
|
|
21
18
|
}
|
|
@@ -39,18 +39,6 @@ export type SniConnectResponse = {
|
|
|
39
39
|
headers: HeaderMap;
|
|
40
40
|
multiValueHeaders?: MultiValueHeaderMap;
|
|
41
41
|
};
|
|
42
|
-
export type SniConnectDebugTarget = {
|
|
43
|
-
ip: string;
|
|
44
|
-
hostname: string;
|
|
45
|
-
};
|
|
46
|
-
export type SniConnectDebugSnapshot = {
|
|
47
|
-
activeRequests: Int32;
|
|
48
|
-
activeRequestsForPair: Int32;
|
|
49
|
-
pendingRequests: Int32;
|
|
50
|
-
pendingRequestsForPair: Int32;
|
|
51
|
-
activeRequestIdsForPair: string[];
|
|
52
|
-
pendingRequestIdsForPair: string[];
|
|
53
|
-
};
|
|
54
42
|
export interface Spec extends TurboModule {
|
|
55
43
|
request(config: NativeSniConnectRequest): Promise<SniConnectResponse>;
|
|
56
44
|
cancelRequest(requestId: string): Promise<{
|
|
@@ -62,7 +50,6 @@ export interface Spec extends TurboModule {
|
|
|
62
50
|
clearDNSCache(): Promise<{
|
|
63
51
|
success: boolean;
|
|
64
52
|
}>;
|
|
65
|
-
getDebugSnapshot(target: SniConnectDebugTarget): Promise<SniConnectDebugSnapshot>;
|
|
66
53
|
isProxyActiveForUrl(url: string): Promise<boolean>;
|
|
67
54
|
}
|
|
68
55
|
export type SniConnectModule = Spec;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type SniConnectRequest, type SniConnectResponse } from './NativeSniConnect';
|
|
2
2
|
export declare function request(config: SniConnectRequest): Promise<SniConnectResponse>;
|
|
3
3
|
export declare function cancelRequest(requestId: string): Promise<{
|
|
4
4
|
success: boolean;
|
|
@@ -9,7 +9,6 @@ export declare function cancelAllRequests(): Promise<{
|
|
|
9
9
|
export declare function clearDNSCache(): Promise<{
|
|
10
10
|
success: boolean;
|
|
11
11
|
}>;
|
|
12
|
-
export declare function getDebugSnapshot(target: SniConnectDebugTarget): Promise<SniConnectDebugSnapshot>;
|
|
13
12
|
export declare function isProxyActiveForUrl(url: string): Promise<boolean>;
|
|
14
|
-
export type { SniConnectBodylessMethod,
|
|
13
|
+
export type { SniConnectBodylessMethod, SniConnectMethod, SniConnectOptionalBodyMethod, SniConnectRequest, SniConnectRequiredBodyMethod, SniConnectResponse, } from './NativeSniConnect';
|
|
15
14
|
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/react-native-sni-connect",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.82",
|
|
4
4
|
"description": "A React Native library for SNI-based HTTP requests with DNS caching and request management",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/src/NativeSniConnect.ts
CHANGED
|
@@ -58,28 +58,11 @@ export type SniConnectResponse = {
|
|
|
58
58
|
multiValueHeaders?: MultiValueHeaderMap;
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
export type SniConnectDebugTarget = {
|
|
62
|
-
ip: string;
|
|
63
|
-
hostname: string;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
export type SniConnectDebugSnapshot = {
|
|
67
|
-
activeRequests: Int32;
|
|
68
|
-
activeRequestsForPair: Int32;
|
|
69
|
-
pendingRequests: Int32;
|
|
70
|
-
pendingRequestsForPair: Int32;
|
|
71
|
-
activeRequestIdsForPair: string[];
|
|
72
|
-
pendingRequestIdsForPair: string[];
|
|
73
|
-
};
|
|
74
|
-
|
|
75
61
|
export interface Spec extends TurboModule {
|
|
76
62
|
request(config: NativeSniConnectRequest): Promise<SniConnectResponse>;
|
|
77
63
|
cancelRequest(requestId: string): Promise<{ success: boolean }>;
|
|
78
64
|
cancelAllRequests(): Promise<{ success: boolean }>;
|
|
79
65
|
clearDNSCache(): Promise<{ success: boolean }>;
|
|
80
|
-
getDebugSnapshot(
|
|
81
|
-
target: SniConnectDebugTarget
|
|
82
|
-
): Promise<SniConnectDebugSnapshot>;
|
|
83
66
|
isProxyActiveForUrl(url: string): Promise<boolean>;
|
|
84
67
|
}
|
|
85
68
|
|