@onekeyfe/react-native-range-downloader 3.0.81-alpha.1 → 3.0.81-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/ReactNativeRangeDownloader.podspec +1 -2
  2. package/android/build.gradle +0 -1
  3. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveRules.kt +12 -5
  4. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactOrphanSweep.kt +111 -0
  5. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactStore.kt +194 -214
  6. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/ReactNativeRangeDownloader.kt +21 -21
  7. package/android/src/test/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveRulesTest.kt +39 -0
  8. package/android/src/test/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactDeadlineTest.kt +26 -0
  9. package/android/src/test/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactOrphanSweepTest.kt +107 -0
  10. package/ios/FirmwareArtifactStore.swift +667 -435
  11. package/ios/RangeDownloadLogic.swift +317 -0
  12. package/ios/ReactNativeRangeDownloader.swift +60 -484
  13. package/lib/typescript/src/ReactNativeRangeDownloader.nitro.d.ts +4 -7
  14. package/lib/typescript/src/ReactNativeRangeDownloader.nitro.d.ts.map +1 -1
  15. package/nitrogen/generated/android/c++/JFirmwareArchiveMaterializeParams.hpp +7 -6
  16. package/nitrogen/generated/android/c++/JFirmwareArtifactDownloadParams.hpp +7 -7
  17. package/nitrogen/generated/android/c++/JFirmwareArtifactReceipt.hpp +7 -3
  18. package/nitrogen/generated/android/c++/JHybridReactNativeRangeDownloaderSpec.cpp +0 -19
  19. package/nitrogen/generated/android/c++/JHybridReactNativeRangeDownloaderSpec.hpp +0 -1
  20. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveMaterializeParams.kt +2 -2
  21. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactDownloadParams.kt +3 -3
  22. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactReceipt.kt +6 -3
  23. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/HybridReactNativeRangeDownloaderSpec.kt +0 -4
  24. package/nitrogen/generated/ios/ReactNativeRangeDownloader-Swift-Cxx-Bridge.hpp +15 -0
  25. package/nitrogen/generated/ios/ReactNativeRangeDownloader-Swift-Cxx-Umbrella.hpp +0 -3
  26. package/nitrogen/generated/ios/c++/HybridReactNativeRangeDownloaderSpecSwift.hpp +0 -11
  27. package/nitrogen/generated/ios/swift/FirmwareArchiveMaterializeParams.swift +32 -13
  28. package/nitrogen/generated/ios/swift/FirmwareArtifactDownloadParams.swift +39 -8
  29. package/nitrogen/generated/ios/swift/FirmwareArtifactReceipt.swift +13 -2
  30. package/nitrogen/generated/ios/swift/HybridReactNativeRangeDownloaderSpec.swift +0 -1
  31. package/nitrogen/generated/ios/swift/HybridReactNativeRangeDownloaderSpec_cxx.swift +0 -19
  32. package/nitrogen/generated/shared/c++/FirmwareArchiveMaterializeParams.hpp +6 -5
  33. package/nitrogen/generated/shared/c++/FirmwareArtifactDownloadParams.hpp +9 -9
  34. package/nitrogen/generated/shared/c++/FirmwareArtifactReceipt.hpp +6 -2
  35. package/nitrogen/generated/shared/c++/HybridReactNativeRangeDownloaderSpec.cpp +0 -1
  36. package/nitrogen/generated/shared/c++/HybridReactNativeRangeDownloaderSpec.hpp +0 -4
  37. package/package.json +1 -2
  38. package/src/ReactNativeRangeDownloader.nitro.ts +7 -11
  39. package/ios/FirmwareBackgroundSessionEventRouter.swift +0 -17
  40. package/nitrogen/generated/android/c++/JFirmwareArtifactLeaseReconcileParams.hpp +0 -76
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactLeaseReconcileParams.kt +0 -38
  42. package/nitrogen/generated/ios/swift/FirmwareArtifactLeaseReconcileParams.swift +0 -48
  43. package/nitrogen/generated/shared/c++/FirmwareArtifactLeaseReconcileParams.hpp +0 -76
@@ -2,6 +2,16 @@ import Foundation
2
2
  import NitroModules
3
3
  import ReactNativeNativeLogger
4
4
 
5
+ private func settledFirmwareArtifactPromise<T>(
6
+ _ operation: () throws -> T
7
+ ) -> Promise<T> {
8
+ do {
9
+ return Promise.resolved(withResult: try operation())
10
+ } catch {
11
+ return Promise.rejected(withError: error)
12
+ }
13
+ }
14
+
5
15
  // MARK: - Nitro HybridObject entry point
6
16
  //
7
17
  // Thin Nitro shim over `RangeDownloader.shared`. The heavy lifting — concurrent
@@ -98,8 +108,8 @@ class ReactNativeRangeDownloader: HybridReactNativeRangeDownloaderSpec {
98
108
 
99
109
  func getFirmwareArtifactCapabilities() throws -> FirmwareArtifactCapabilities {
100
110
  FirmwareArtifactCapabilities(
101
- firmwareArtifactProtocolVersion: 1,
102
- supportedRouteTypes: ["domain", "pinnedIp"],
111
+ firmwareArtifactProtocolVersion: 4,
112
+ supportedRouteTypes: ["domain"],
103
113
  supportsArchiveMaterialization: true,
104
114
  maxReadBytes: Double(FirmwareArtifactStore.maxReadBytes)
105
115
  )
@@ -108,12 +118,27 @@ class ReactNativeRangeDownloader: HybridReactNativeRangeDownloaderSpec {
108
118
  func downloadFirmwareArtifact(
109
119
  params: FirmwareArtifactDownloadParams
110
120
  ) throws -> Promise<FirmwareArtifactReceipt> {
111
- Promise.async {
121
+ do {
122
+ if let artifact = try FirmwareArtifactStore.shared.cachedArtifact(params) {
123
+ return Promise.resolved(
124
+ withResult: FirmwareArtifactReceipt(
125
+ artifactRef: artifact.artifactRef,
126
+ size: Double(artifact.size),
127
+ sha256: artifact.sha256,
128
+ expectedSha256Verified: params.expectedSha256 != nil
129
+ )
130
+ )
131
+ }
132
+ } catch {
133
+ return Promise.rejected(withError: error)
134
+ }
135
+ return Promise.async {
112
136
  let artifact = try await FirmwareArtifactStore.shared.download(params)
113
137
  return FirmwareArtifactReceipt(
114
138
  artifactRef: artifact.artifactRef,
115
139
  size: Double(artifact.size),
116
- sha256: artifact.sha256
140
+ sha256: artifact.sha256,
141
+ expectedSha256Verified: params.expectedSha256 != nil
117
142
  )
118
143
  }
119
144
  }
@@ -121,7 +146,14 @@ class ReactNativeRangeDownloader: HybridReactNativeRangeDownloaderSpec {
121
146
  func cancelFirmwareArtifactDownloads(
122
147
  params: FirmwareArtifactCancelParams
123
148
  ) throws -> Promise<Void> {
124
- Promise.async {
149
+ guard FirmwareArtifactStore.isSafeIdentifier(params.transactionId) else {
150
+ return Promise.rejected(
151
+ withError: FirmwareArtifactStoreError.invalidInput(
152
+ "Invalid firmware transactionId"
153
+ )
154
+ )
155
+ }
156
+ return Promise.async {
125
157
  try await FirmwareArtifactStore.shared.cancelDownloads(
126
158
  transactionId: params.transactionId
127
159
  )
@@ -131,7 +163,7 @@ class ReactNativeRangeDownloader: HybridReactNativeRangeDownloaderSpec {
131
163
  func discardFirmwareArtifact(
132
164
  params: FirmwareArtifactRefParams
133
165
  ) throws -> Promise<Void> {
134
- Promise.async {
166
+ settledFirmwareArtifactPromise {
135
167
  try FirmwareArtifactStore.shared.discard(artifactRef: params.artifactRef)
136
168
  }
137
169
  }
@@ -139,7 +171,7 @@ class ReactNativeRangeDownloader: HybridReactNativeRangeDownloaderSpec {
139
171
  func openFirmwareArtifact(
140
172
  params: FirmwareArtifactRefParams
141
173
  ) throws -> Promise<FirmwareArtifactReaderInfo> {
142
- Promise.async {
174
+ settledFirmwareArtifactPromise {
143
175
  let reader = try FirmwareArtifactStore.shared.open(
144
176
  artifactRef: params.artifactRef
145
177
  )
@@ -153,16 +185,18 @@ class ReactNativeRangeDownloader: HybridReactNativeRangeDownloaderSpec {
153
185
  func readFirmwareArtifact(
154
186
  params: FirmwareArtifactReaderReadParams
155
187
  ) throws -> Promise<ArrayBuffer> {
156
- Promise.async {
188
+ settledFirmwareArtifactPromise {
157
189
  guard
158
- params.offset.isFinite,
159
- params.offset >= 0,
160
- params.offset <= Double(Int64.max),
161
- params.offset.rounded() == params.offset,
162
- params.length.isFinite,
163
- params.length > 0,
164
- params.length <= Double(Int.max),
165
- params.length.rounded() == params.length
190
+ let offset = firmwareArtifactExactInt64(
191
+ params.offset,
192
+ minimum: 0,
193
+ maximum: FirmwareArtifactStore.maxArtifactBytes
194
+ ),
195
+ let length = firmwareArtifactExactInt64(
196
+ params.length,
197
+ minimum: 1,
198
+ maximum: firmwareArtifactMaxReadBytes
199
+ )
166
200
  else {
167
201
  throw FirmwareArtifactStoreError.readerInvalid(
168
202
  "Invalid firmware artifact read"
@@ -170,8 +204,8 @@ class ReactNativeRangeDownloader: HybridReactNativeRangeDownloaderSpec {
170
204
  }
171
205
  let data = try FirmwareArtifactStore.shared.read(
172
206
  readerId: params.readerId,
173
- offset: Int64(params.offset),
174
- length: Int(params.length)
207
+ offset: offset,
208
+ length: Int(length)
175
209
  )
176
210
  return try ArrayBuffer.copy(data: data)
177
211
  }
@@ -180,7 +214,7 @@ class ReactNativeRangeDownloader: HybridReactNativeRangeDownloaderSpec {
180
214
  func closeFirmwareArtifact(
181
215
  params: FirmwareArtifactReaderCloseParams
182
216
  ) throws -> Promise<Void> {
183
- Promise.async {
217
+ settledFirmwareArtifactPromise {
184
218
  try FirmwareArtifactStore.shared.close(readerId: params.readerId)
185
219
  }
186
220
  }
@@ -188,7 +222,7 @@ class ReactNativeRangeDownloader: HybridReactNativeRangeDownloaderSpec {
188
222
  func materializeFirmwareArchive(
189
223
  params: FirmwareArchiveMaterializeParams
190
224
  ) throws -> Promise<FirmwareArchiveMaterializeResult> {
191
- Promise.async {
225
+ settledFirmwareArtifactPromise {
192
226
  let entries = try FirmwareArtifactStore.shared.materializeArchive(
193
227
  leaseRef: params.leaseRef,
194
228
  artifactRef: params.archiveArtifactRef,
@@ -201,7 +235,8 @@ class ReactNativeRangeDownloader: HybridReactNativeRangeDownloaderSpec {
201
235
  receipt: FirmwareArtifactReceipt(
202
236
  artifactRef: entry.artifact.artifactRef,
203
237
  size: Double(entry.artifact.size),
204
- sha256: entry.artifact.sha256
238
+ sha256: entry.artifact.sha256,
239
+ expectedSha256Verified: params.expectedEntries != nil
205
240
  )
206
241
  )
207
242
  }
@@ -212,7 +247,7 @@ class ReactNativeRangeDownloader: HybridReactNativeRangeDownloaderSpec {
212
247
  func createFirmwareArtifactLease(
213
248
  params: FirmwareArtifactLeaseCreateParams
214
249
  ) throws -> Promise<FirmwareArtifactLease> {
215
- Promise.async {
250
+ settledFirmwareArtifactPromise {
216
251
  FirmwareArtifactLease(
217
252
  leaseRef: try FirmwareArtifactStore.shared.createLease(
218
253
  transactionId: params.transactionId
@@ -224,7 +259,7 @@ class ReactNativeRangeDownloader: HybridReactNativeRangeDownloaderSpec {
224
259
  func retainFirmwareArtifact(
225
260
  params: FirmwareArtifactLeaseRetainParams
226
261
  ) throws -> Promise<Void> {
227
- Promise.async {
262
+ settledFirmwareArtifactPromise {
228
263
  try FirmwareArtifactStore.shared.retain(
229
264
  leaseRef: params.leaseRef,
230
265
  artifactRef: params.artifactRef
@@ -235,7 +270,7 @@ class ReactNativeRangeDownloader: HybridReactNativeRangeDownloaderSpec {
235
270
  func releaseFirmwareArtifactLease(
236
271
  params: FirmwareArtifactLeaseReleaseParams
237
272
  ) throws -> Promise<Void> {
238
- Promise.async {
273
+ settledFirmwareArtifactPromise {
239
274
  try FirmwareArtifactStore.shared.releaseLease(
240
275
  leaseRef: params.leaseRef,
241
276
  disposition: params.disposition
@@ -243,18 +278,8 @@ class ReactNativeRangeDownloader: HybridReactNativeRangeDownloaderSpec {
243
278
  }
244
279
  }
245
280
 
246
- func reconcileFirmwareArtifactLeases(
247
- params: FirmwareArtifactLeaseReconcileParams
248
- ) throws -> Promise<Void> {
249
- Promise.async {
250
- try FirmwareArtifactStore.shared.reconcileLeases(
251
- activeLeaseRefs: params.activeLeaseRefs
252
- )
253
- }
254
- }
255
-
256
281
  func sweepFirmwareArtifactOrphans() throws -> Promise<FirmwareArtifactSweepResult> {
257
- Promise.async {
282
+ settledFirmwareArtifactPromise {
258
283
  let result = try FirmwareArtifactStore.shared.sweepOrphans()
259
284
  return FirmwareArtifactSweepResult(
260
285
  deletedFiles: Double(result.deletedFiles),
@@ -323,79 +348,10 @@ extension RangeFallbackClass {
323
348
  }
324
349
  }
325
350
 
326
- private struct FirmwareBackgroundTaskDescriptor: Codable, Equatable {
327
- let schemaVersion: Int
328
- let taskId: String
329
- let transactionId: String
330
- let leaseRef: String
331
- let expectedSize: Int64
332
- let expectedSha256: String
333
- let hostname: String
334
- let deadlineAt: TimeInterval
335
-
336
- var key: String {
337
- "\(leaseRef)|\(taskId)|\(expectedSha256)"
338
- }
339
-
340
- func hasSameArtifactIdentity(
341
- as other: FirmwareBackgroundTaskDescriptor
342
- ) -> Bool {
343
- taskId == other.taskId &&
344
- transactionId == other.transactionId &&
345
- leaseRef == other.leaseRef &&
346
- expectedSize == other.expectedSize &&
347
- expectedSha256 == other.expectedSha256 &&
348
- hostname == other.hostname
349
- }
350
- }
351
-
352
- private enum FirmwareBackgroundDownloadError: LocalizedError {
353
- case cancelled
354
- case invalidTask
355
- case deadlineExceeded
356
- case redirectRejected
357
- case responseRejected
358
- case sizeRejected
359
- case transferFailed
360
-
361
- var errorDescription: String? {
362
- switch self {
363
- case .cancelled:
364
- return "ARTIFACT_CANCELLED: firmware background download was cancelled"
365
- case .invalidTask:
366
- return "ARTIFACT_PROTOCOL_INVALID: firmware background task is invalid"
367
- case .deadlineExceeded:
368
- return "ARTIFACT_DEADLINE_EXCEEDED: firmware download exceeded its deadline"
369
- case .redirectRejected:
370
- return "ARTIFACT_REDIRECT_REJECTED: firmware redirect changed canonical identity"
371
- case .responseRejected:
372
- return "ARTIFACT_PROTOCOL_INVALID: firmware response is invalid"
373
- case .sizeRejected:
374
- return "ARTIFACT_PROTOCOL_INVALID: firmware artifact size is invalid"
375
- case .transferFailed:
376
- return "ARTIFACT_NETWORK_FAILED: firmware background transfer failed"
377
- }
378
- }
379
- }
380
-
381
351
  public final class RangeDownloader: NSObject, URLSessionDownloadDelegate {
382
352
 
383
353
  public static let shared = RangeDownloader()
384
354
 
385
- public static func routeFirmwareBackgroundEvents(
386
- identifier: String,
387
- completionHandler: @escaping () -> Void
388
- ) -> Bool {
389
- guard identifier == sessionIdentifier(for: .firmware) else {
390
- return false
391
- }
392
- shared.attachBackgroundEvents(
393
- identifier: identifier,
394
- completionHandler: completionHandler
395
- )
396
- return true
397
- }
398
-
399
355
  /// Posted by the AppDelegate from
400
356
  /// application(_:handleEventsForBackgroundURLSession:completionHandler:).
401
357
  /// userInfo: ["identifier": String, "completionHandler": () -> Void].
@@ -432,11 +388,6 @@ public final class RangeDownloader: NSObject, URLSessionDownloadDelegate {
432
388
  /// notification), keyed by session identifier, so we can call each back once
433
389
  /// all its queued background events have been delivered.
434
390
  private var backgroundCompletionHandlers: [String: () -> Void] = [:]
435
- private var firmwareWaiters: [
436
- String: [CheckedContinuation<StoredFirmwareArtifact, Error>]
437
- ] = [:]
438
- private var firmwareTaskErrors: [Int: Error] = [:]
439
- private var completedFirmwareTasks: Set<Int> = []
440
391
 
441
392
  // Per-run mutable state.
442
393
  private final class RunState {
@@ -694,240 +645,6 @@ public final class RangeDownloader: NSObject, URLSessionDownloadDelegate {
694
645
  return (r, decoded.segIndex)
695
646
  }
696
647
 
697
- private static let firmwareTaskDescriptionPrefix = "firmware-v1:"
698
-
699
- private static func encodeFirmwareTaskDescription(
700
- _ descriptor: FirmwareBackgroundTaskDescriptor
701
- ) throws -> String {
702
- let data = try JSONEncoder().encode(descriptor)
703
- return firmwareTaskDescriptionPrefix + data.base64EncodedString()
704
- }
705
-
706
- private static func decodeFirmwareTaskDescription(
707
- _ description: String?
708
- ) -> FirmwareBackgroundTaskDescriptor? {
709
- guard
710
- let description,
711
- description.hasPrefix(firmwareTaskDescriptionPrefix),
712
- let data = Data(
713
- base64Encoded: String(
714
- description.dropFirst(firmwareTaskDescriptionPrefix.count)
715
- )
716
- ),
717
- data.count <= 2048,
718
- let descriptor = try? JSONDecoder().decode(
719
- FirmwareBackgroundTaskDescriptor.self,
720
- from: data
721
- ),
722
- descriptor.schemaVersion == 1,
723
- descriptor.taskId.range(
724
- of: "^[A-Za-z0-9._-]{1,100}$",
725
- options: .regularExpression
726
- ) != nil,
727
- descriptor.transactionId.range(
728
- of: "^[A-Za-z0-9._:-]{1,160}$",
729
- options: .regularExpression
730
- ) != nil,
731
- descriptor.leaseRef.range(
732
- of: "^fwlease:[a-f0-9-]{36}$",
733
- options: .regularExpression
734
- ) != nil,
735
- descriptor.expectedSize > 0,
736
- descriptor.expectedSize <= 512 * 1024 * 1024,
737
- descriptor.expectedSha256.range(
738
- of: "^[a-f0-9]{64}$",
739
- options: .regularExpression
740
- ) != nil,
741
- !descriptor.hostname.isEmpty,
742
- descriptor.hostname.count <= 253,
743
- descriptor.deadlineAt.isFinite,
744
- descriptor.deadlineAt > 0
745
- else {
746
- return nil
747
- }
748
- return descriptor
749
- }
750
-
751
- func downloadFirmwareArtifact(
752
- params: FirmwareArtifactDownloadParams
753
- ) async throws -> StoredFirmwareArtifact {
754
- guard
755
- params.routeType == "domain",
756
- let url = URL(string: params.url),
757
- let hostname = url.host?.lowercased()
758
- else {
759
- throw FirmwareBackgroundDownloadError.invalidTask
760
- }
761
- let deadlineSeconds = params.overallDeadlineSeconds ?? 180
762
- guard
763
- deadlineSeconds.isFinite,
764
- deadlineSeconds > 0,
765
- deadlineSeconds <= 24 * 60 * 60
766
- else {
767
- throw FirmwareBackgroundDownloadError.invalidTask
768
- }
769
- let descriptor = FirmwareBackgroundTaskDescriptor(
770
- schemaVersion: 1,
771
- taskId: params.taskId,
772
- transactionId: params.transactionId,
773
- leaseRef: params.leaseRef,
774
- expectedSize: Int64(params.expectedSize),
775
- expectedSha256: params.expectedSha256.lowercased(),
776
- hostname: hostname,
777
- deadlineAt: Date().timeIntervalSince1970 + deadlineSeconds
778
- )
779
- guard !FirmwareArtifactStore.shared.isTransactionCancelled(
780
- descriptor.transactionId
781
- ) else {
782
- throw FirmwareBackgroundDownloadError.cancelled
783
- }
784
- if let stored = try FirmwareArtifactStore.shared.storedArtifact(
785
- expectedSize: descriptor.expectedSize,
786
- expectedSha256: descriptor.expectedSha256
787
- ) {
788
- return stored
789
- }
790
-
791
- return try await withCheckedThrowingContinuation { continuation in
792
- lock.withLockValue {
793
- firmwareWaiters[descriptor.key, default: []].append(continuation)
794
- }
795
- Task { [weak self] in
796
- await self?.reconcileOrStartFirmwareTask(
797
- descriptor: descriptor,
798
- url: url
799
- )
800
- }
801
- }
802
- }
803
-
804
- func cancelFirmwareArtifactDownloads(
805
- transactionId: String
806
- ) async throws {
807
- guard transactionId.range(
808
- of: "^[A-Za-z0-9._:-]{1,160}$",
809
- options: .regularExpression
810
- ) != nil else {
811
- throw FirmwareBackgroundDownloadError.invalidTask
812
- }
813
- let session = session(forChannel: .firmware, segmentCount: 1)
814
- let tasks = await allTasks(in: session)
815
- for task in tasks {
816
- guard
817
- let descriptor = Self.decodeFirmwareTaskDescription(
818
- task.taskDescription
819
- ),
820
- descriptor.transactionId == transactionId
821
- else {
822
- continue
823
- }
824
- recordFirmwareTaskError(
825
- taskIdentifier: task.taskIdentifier,
826
- error: FirmwareBackgroundDownloadError.cancelled
827
- )
828
- task.cancel()
829
- }
830
- }
831
-
832
- private func reconcileOrStartFirmwareTask(
833
- descriptor: FirmwareBackgroundTaskDescriptor,
834
- url: URL
835
- ) async {
836
- guard !FirmwareArtifactStore.shared.isTransactionCancelled(
837
- descriptor.transactionId
838
- ) else {
839
- finishFirmwareTask(
840
- key: descriptor.key,
841
- result: .failure(FirmwareBackgroundDownloadError.cancelled)
842
- )
843
- return
844
- }
845
- let session = session(forChannel: .firmware, segmentCount: 1)
846
- let tasks = await allTasks(in: session)
847
- var matchingTaskFound = false
848
- for task in tasks {
849
- guard
850
- let candidate = Self.decodeFirmwareTaskDescription(
851
- task.taskDescription
852
- )
853
- else {
854
- continue
855
- }
856
- if candidate.hasSameArtifactIdentity(as: descriptor) {
857
- matchingTaskFound = true
858
- } else if candidate.taskId == descriptor.taskId {
859
- task.cancel()
860
- }
861
- }
862
- if matchingTaskFound {
863
- return
864
- }
865
- do {
866
- if let stored = try FirmwareArtifactStore.shared.storedArtifact(
867
- expectedSize: descriptor.expectedSize,
868
- expectedSha256: descriptor.expectedSha256
869
- ) {
870
- finishFirmwareTask(
871
- key: descriptor.key,
872
- result: .success(stored)
873
- )
874
- return
875
- }
876
- guard Date().timeIntervalSince1970 < descriptor.deadlineAt else {
877
- throw FirmwareBackgroundDownloadError.deadlineExceeded
878
- }
879
- guard !FirmwareArtifactStore.shared.isTransactionCancelled(
880
- descriptor.transactionId
881
- ) else {
882
- throw FirmwareBackgroundDownloadError.cancelled
883
- }
884
- var request = URLRequest(url: url)
885
- request.cachePolicy = .reloadIgnoringLocalCacheData
886
- request.timeoutInterval = max(
887
- 1,
888
- descriptor.deadlineAt - Date().timeIntervalSince1970
889
- )
890
- request.setValue("identity", forHTTPHeaderField: "Accept-Encoding")
891
- let task = session.downloadTask(with: request)
892
- task.taskDescription = try Self.encodeFirmwareTaskDescription(
893
- descriptor
894
- )
895
- task.resume()
896
- } catch {
897
- finishFirmwareTask(
898
- key: descriptor.key,
899
- result: .failure(error)
900
- )
901
- }
902
- }
903
-
904
- private func allTasks(in session: URLSession) async -> [URLSessionTask] {
905
- await withCheckedContinuation { continuation in
906
- session.getAllTasks { tasks in
907
- continuation.resume(returning: tasks)
908
- }
909
- }
910
- }
911
-
912
- private func finishFirmwareTask(
913
- key: String,
914
- result: Result<StoredFirmwareArtifact, Error>
915
- ) {
916
- let continuations: [
917
- CheckedContinuation<StoredFirmwareArtifact, Error>
918
- ] = lock.withLockValue {
919
- firmwareWaiters.removeValue(forKey: key) ?? []
920
- }
921
- for continuation in continuations {
922
- switch result {
923
- case let .success(artifact):
924
- continuation.resume(returning: artifact)
925
- case let .failure(error):
926
- continuation.resume(throwing: error)
927
- }
928
- }
929
- }
930
-
931
648
  // MARK: - Public entry
932
649
 
933
650
  /// Downloads [urlString] into [filePath] using concurrent background ranges.
@@ -1463,28 +1180,6 @@ public final class RangeDownloader: NSObject, URLSessionDownloadDelegate {
1463
1180
  public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
1464
1181
  didWriteData bytesWritten: Int64, totalBytesWritten: Int64,
1465
1182
  totalBytesExpectedToWrite: Int64) {
1466
- if let descriptor = Self.decodeFirmwareTaskDescription(
1467
- downloadTask.taskDescription
1468
- ) {
1469
- let exceedsBound =
1470
- totalBytesWritten > descriptor.expectedSize ||
1471
- (
1472
- totalBytesExpectedToWrite != NSURLSessionTransferSizeUnknown &&
1473
- totalBytesExpectedToWrite != descriptor.expectedSize
1474
- )
1475
- let expired =
1476
- Date().timeIntervalSince1970 >= descriptor.deadlineAt
1477
- if exceedsBound || expired {
1478
- recordFirmwareTaskError(
1479
- taskIdentifier: downloadTask.taskIdentifier,
1480
- error: exceedsBound
1481
- ? FirmwareBackgroundDownloadError.sizeRejected
1482
- : FirmwareBackgroundDownloadError.deadlineExceeded
1483
- )
1484
- downloadTask.cancel()
1485
- }
1486
- return
1487
- }
1488
1183
  guard let desc = downloadTask.taskDescription,
1489
1184
  let (state, idx) = run(for: desc) else { return }
1490
1185
  lock.lock()
@@ -1515,53 +1210,6 @@ public final class RangeDownloader: NSObject, URLSessionDownloadDelegate {
1515
1210
 
1516
1211
  public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
1517
1212
  didFinishDownloadingTo location: URL) {
1518
- if let descriptor = Self.decodeFirmwareTaskDescription(
1519
- downloadTask.taskDescription
1520
- ) {
1521
- guard firmwareTaskError(
1522
- taskIdentifier: downloadTask.taskIdentifier
1523
- ) == nil else {
1524
- return
1525
- }
1526
- do {
1527
- guard
1528
- !FirmwareArtifactStore.shared.isTransactionCancelled(
1529
- descriptor.transactionId
1530
- ),
1531
- Date().timeIntervalSince1970 < descriptor.deadlineAt,
1532
- let response = downloadTask.response as? HTTPURLResponse,
1533
- response.statusCode == 200,
1534
- response.url?.scheme?.lowercased() == "https",
1535
- response.url?.host?.lowercased() == descriptor.hostname,
1536
- response.url?.port == nil || response.url?.port == 443
1537
- else {
1538
- throw FirmwareBackgroundDownloadError.responseRejected
1539
- }
1540
- let artifact = try FirmwareArtifactStore.shared.acceptBackgroundDownload(
1541
- temporaryURL: location,
1542
- leaseRef: descriptor.leaseRef,
1543
- transactionId: descriptor.transactionId,
1544
- expectedSize: descriptor.expectedSize,
1545
- expectedSha256: descriptor.expectedSha256
1546
- )
1547
- lock.withLockValue {
1548
- firmwareTaskErrors.removeValue(
1549
- forKey: downloadTask.taskIdentifier
1550
- )
1551
- completedFirmwareTasks.insert(downloadTask.taskIdentifier)
1552
- }
1553
- finishFirmwareTask(
1554
- key: descriptor.key,
1555
- result: .success(artifact)
1556
- )
1557
- } catch {
1558
- recordFirmwareTaskError(
1559
- taskIdentifier: downloadTask.taskIdentifier,
1560
- error: error
1561
- )
1562
- }
1563
- return
1564
- }
1565
1213
  guard let desc = downloadTask.taskDescription,
1566
1214
  let (state, idx) = run(for: desc) else { return }
1567
1215
  let ranges = lock.withLockValue { state.ranges }
@@ -1660,39 +1308,6 @@ public final class RangeDownloader: NSObject, URLSessionDownloadDelegate {
1660
1308
  }
1661
1309
 
1662
1310
  public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
1663
- if let descriptor = Self.decodeFirmwareTaskDescription(
1664
- task.taskDescription
1665
- ) {
1666
- let completion: (completed: Bool, error: Error?) =
1667
- lock.withLockValue {
1668
- let completed = completedFirmwareTasks.remove(
1669
- task.taskIdentifier
1670
- ) != nil
1671
- let recordedError = firmwareTaskErrors.removeValue(
1672
- forKey: task.taskIdentifier
1673
- )
1674
- return (completed, recordedError)
1675
- }
1676
- if completion.completed {
1677
- return
1678
- }
1679
- finishFirmwareTask(
1680
- key: descriptor.key,
1681
- result: .failure(
1682
- completion.error ??
1683
- (
1684
- FirmwareArtifactStore.shared.isTransactionCancelled(
1685
- descriptor.transactionId
1686
- )
1687
- ? FirmwareBackgroundDownloadError.cancelled
1688
- : nil
1689
- ) ??
1690
- error ??
1691
- FirmwareBackgroundDownloadError.transferFailed
1692
- )
1693
- )
1694
- return
1695
- }
1696
1311
  guard let desc = task.taskDescription,
1697
1312
  let (state, idx) = run(for: desc) else { return }
1698
1313
  let ranges = lock.withLockValue { state.ranges }
@@ -1868,26 +1483,6 @@ public final class RangeDownloader: NSObject, URLSessionDownloadDelegate {
1868
1483
  willPerformHTTPRedirection response: HTTPURLResponse,
1869
1484
  newRequest request: URLRequest,
1870
1485
  completionHandler: @escaping (URLRequest?) -> Void) {
1871
- if let descriptor = Self.decodeFirmwareTaskDescription(
1872
- task.taskDescription
1873
- ) {
1874
- guard
1875
- request.url?.scheme?.lowercased() == "https",
1876
- request.url?.host?.lowercased() == descriptor.hostname,
1877
- request.url?.port == nil || request.url?.port == 443,
1878
- request.url?.user == nil,
1879
- request.url?.password == nil
1880
- else {
1881
- recordFirmwareTaskError(
1882
- taskIdentifier: task.taskIdentifier,
1883
- error: FirmwareBackgroundDownloadError.redirectRejected
1884
- )
1885
- completionHandler(nil)
1886
- return
1887
- }
1888
- completionHandler(request)
1889
- return
1890
- }
1891
1486
  if request.url?.scheme?.lowercased() != "https" {
1892
1487
  OneKeyLog.error("RangeDownloader", "blocked redirect to non-HTTPS URL")
1893
1488
  if let desc = task.taskDescription, let (state, _) = run(for: desc) {
@@ -1899,25 +1494,6 @@ public final class RangeDownloader: NSObject, URLSessionDownloadDelegate {
1899
1494
  }
1900
1495
  }
1901
1496
 
1902
- private func recordFirmwareTaskError(
1903
- taskIdentifier: Int,
1904
- error: Error
1905
- ) {
1906
- lock.withLockValue {
1907
- if firmwareTaskErrors[taskIdentifier] == nil {
1908
- firmwareTaskErrors[taskIdentifier] = error
1909
- }
1910
- }
1911
- }
1912
-
1913
- private func firmwareTaskError(
1914
- taskIdentifier: Int
1915
- ) -> Error? {
1916
- lock.withLockValue {
1917
- firmwareTaskErrors[taskIdentifier]
1918
- }
1919
- }
1920
-
1921
1497
  /// Called on the session delegate queue when all background events for this
1922
1498
  /// session have been delivered after a background relaunch.
1923
1499
  public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {