@onekeyfe/react-native-range-downloader 3.0.81-alpha.8 → 3.0.81-alpha.9

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 (24) hide show
  1. package/ReactNativeRangeDownloader.podspec +1 -1
  2. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveRules.kt +12 -5
  3. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactOrphanSweep.kt +111 -0
  4. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactStore.kt +132 -68
  5. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/ReactNativeRangeDownloader.kt +1 -1
  6. package/android/src/test/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveRulesTest.kt +39 -0
  7. package/android/src/test/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactOrphanSweepTest.kt +107 -0
  8. package/ios/FirmwareArtifactStore.swift +242 -172
  9. package/ios/RangeDownloadLogic.swift +194 -36
  10. package/ios/ReactNativeRangeDownloader.swift +9 -423
  11. package/lib/typescript/src/ReactNativeRangeDownloader.nitro.d.ts +3 -3
  12. package/lib/typescript/src/ReactNativeRangeDownloader.nitro.d.ts.map +1 -1
  13. package/nitrogen/generated/android/c++/JFirmwareArchiveMaterializeParams.hpp +7 -6
  14. package/nitrogen/generated/android/c++/JFirmwareArtifactDownloadParams.hpp +7 -7
  15. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveMaterializeParams.kt +2 -2
  16. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactDownloadParams.kt +3 -3
  17. package/nitrogen/generated/ios/ReactNativeRangeDownloader-Swift-Cxx-Bridge.hpp +15 -0
  18. package/nitrogen/generated/ios/swift/FirmwareArchiveMaterializeParams.swift +32 -13
  19. package/nitrogen/generated/ios/swift/FirmwareArtifactDownloadParams.swift +39 -8
  20. package/nitrogen/generated/shared/c++/FirmwareArchiveMaterializeParams.hpp +6 -5
  21. package/nitrogen/generated/shared/c++/FirmwareArtifactDownloadParams.hpp +9 -9
  22. package/package.json +1 -1
  23. package/src/ReactNativeRangeDownloader.nitro.ts +3 -3
  24. package/ios/FirmwareBackgroundSessionEventRouter.swift +0 -17
@@ -1,6 +1,23 @@
1
1
  import Foundation
2
2
  import CommonCrypto
3
3
 
4
+ enum FirmwareArtifactStoreError: Error, CustomStringConvertible {
5
+ case invalidInput(String)
6
+ case downloadFailed(String)
7
+ case integrityMismatch(String)
8
+ case readerInvalid(String)
9
+ case archiveInvalid(String)
10
+
11
+ var description: String {
12
+ switch self {
13
+ case let .invalidInput(message), let .downloadFailed(message),
14
+ let .integrityMismatch(message), let .readerInvalid(message),
15
+ let .archiveInvalid(message):
16
+ return message
17
+ }
18
+ }
19
+ }
20
+
4
21
  enum FirmwareArtifactDeadlineError: Error {
5
22
  case exceeded
6
23
  }
@@ -30,16 +47,32 @@ enum FirmwareArtifactWallClockDeadline {
30
47
 
31
48
  func firmwareArtifactDownloadKey(
32
49
  transactionId: String,
33
- expectedSize: Int64,
34
- expectedSha256: String
50
+ taskId: String,
51
+ expectedSize: Int64?,
52
+ expectedSha256: String?
35
53
  ) -> String {
36
- "\(transactionId)|\(expectedSize)|\(expectedSha256)"
54
+ "\(transactionId)|\(taskId)|\(expectedSize.map(String.init) ?? "unknown")|\(expectedSha256 ?? "unknown")"
55
+ }
56
+
57
+ func firmwareArtifactDownloadToken(
58
+ expectedSha256: String?,
59
+ url: String
60
+ ) -> String {
61
+ if let expectedSha256 {
62
+ return expectedSha256.lowercased()
63
+ }
64
+ let urlData = Data(url.utf8)
65
+ var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
66
+ urlData.withUnsafeBytes {
67
+ _ = CC_SHA256($0.baseAddress, CC_LONG(urlData.count), &hash)
68
+ }
69
+ return hash.map { String(format: "%02x", $0) }.joined()
37
70
  }
38
71
 
39
72
  func firmwareArtifactPartialFileName(
40
73
  transactionId: String,
41
74
  taskId: String,
42
- expectedSha256: String
75
+ downloadToken: String
43
76
  ) -> String {
44
77
  let transactionData = Data(transactionId.utf8)
45
78
  var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
@@ -50,13 +83,14 @@ func firmwareArtifactPartialFileName(
50
83
  .prefix(8)
51
84
  .map { String(format: "%02x", $0) }
52
85
  .joined()
53
- return "\(expectedSha256).\(taskId).\(transactionToken).partial"
86
+ return "\(downloadToken).\(taskId).\(transactionToken).partial"
54
87
  }
55
88
 
56
89
  func firmwareArtifactContentRangeIsValid(
57
90
  _ value: String,
58
91
  expectedStart: Int64,
59
- expectedTotal: Int64
92
+ expectedTotal: Int64?,
93
+ maxBytes: Int64
60
94
  ) -> Bool {
61
95
  guard value.lowercased().hasPrefix("bytes ") else {
62
96
  return false
@@ -67,10 +101,11 @@ func firmwareArtifactContentRangeIsValid(
67
101
  else {
68
102
  return false
69
103
  }
104
+ let totalMatches = expectedTotal.map { total == $0 } ?? (total > 0 && total <= maxBytes)
70
105
  return bounds.start == expectedStart &&
71
106
  bounds.end >= bounds.start &&
72
107
  bounds.end < total &&
73
- total == expectedTotal
108
+ totalMatches
74
109
  }
75
110
 
76
111
  func firmwareArtifactResponseFits(
@@ -85,40 +120,163 @@ func firmwareArtifactResponseFits(
85
120
  expectedContentLength <= maxBytes - baseOffset
86
121
  }
87
122
 
88
- enum FirmwareBackgroundDownloadError: LocalizedError, CustomStringConvertible {
89
- case cancelled
90
- case invalidTask
91
- case deadlineExceeded
92
- case redirectRejected
93
- case responseRejected
94
- case sizeRejected
95
- case tlsRejected
96
- case transferFailed
123
+ let firmwareArtifactFinalGrace: TimeInterval = 24 * 60 * 60
124
+ let firmwareArtifactPartialGrace: TimeInterval = 7 * 24 * 60 * 60
125
+ let firmwareArtifactScratchGrace: TimeInterval = firmwareArtifactPartialGrace
97
126
 
98
- var errorDescription: String? {
99
- switch self {
100
- case .cancelled:
101
- return "ARTIFACT_CANCELLED: firmware background download was cancelled"
102
- case .invalidTask:
103
- return "ARTIFACT_PROTOCOL_INVALID: firmware background task is invalid"
104
- case .deadlineExceeded:
105
- return "ARTIFACT_DEADLINE_EXCEEDED: firmware download exceeded its deadline"
106
- case .redirectRejected:
107
- return "ARTIFACT_REDIRECT_REJECTED: firmware redirect changed canonical identity"
108
- case .responseRejected:
109
- return "ARTIFACT_PROTOCOL_INVALID: firmware response is invalid"
110
- case .sizeRejected:
111
- return "ARTIFACT_PROTOCOL_INVALID: firmware artifact size is invalid"
112
- case .tlsRejected:
113
- return "ARTIFACT_TLS_FAILED: firmware TLS validation failed"
114
- case .transferFailed:
115
- return "ARTIFACT_NETWORK_FAILED: firmware background transfer failed"
127
+ func firmwareArtifactIdentifierIsSafe(_ value: String) -> Bool {
128
+ value.range(
129
+ of: "^[A-Za-z0-9._:-]{1,160}$",
130
+ options: .regularExpression
131
+ ) != nil
132
+ }
133
+
134
+ private func firmwareArtifactScratchNameIsValid(
135
+ _ name: String,
136
+ prefix: String
137
+ ) -> Bool {
138
+ guard name.hasPrefix(prefix) else {
139
+ return false
140
+ }
141
+ let rawUUID = String(name.dropFirst(prefix.count))
142
+ guard
143
+ rawUUID.count == 36,
144
+ let uuid = UUID(uuidString: rawUUID)
145
+ else {
146
+ return false
147
+ }
148
+ return uuid.uuidString.caseInsensitiveCompare(rawUUID) == .orderedSame
149
+ }
150
+
151
+ private func firmwareArtifactEntrySize(
152
+ _ entryURL: URL,
153
+ values: URLResourceValues,
154
+ fileManager: FileManager
155
+ ) -> Int64 {
156
+ if values.isRegularFile == true {
157
+ return Int64(values.fileSize ?? 0)
158
+ }
159
+ guard
160
+ values.isDirectory == true,
161
+ let enumerator = fileManager.enumerator(
162
+ at: entryURL,
163
+ includingPropertiesForKeys: [
164
+ .isRegularFileKey,
165
+ .isSymbolicLinkKey,
166
+ .fileSizeKey,
167
+ ],
168
+ options: [],
169
+ errorHandler: nil
170
+ )
171
+ else {
172
+ return 0
173
+ }
174
+ var size: Int64 = 0
175
+ for case let childURL as URL in enumerator {
176
+ guard
177
+ let childValues = try? childURL.resourceValues(
178
+ forKeys: [.isRegularFileKey, .isSymbolicLinkKey, .fileSizeKey]
179
+ ),
180
+ childValues.isRegularFile == true,
181
+ childValues.isSymbolicLink != true
182
+ else {
183
+ continue
116
184
  }
185
+ size += Int64(childValues.fileSize ?? 0)
117
186
  }
187
+ return size
188
+ }
118
189
 
119
- var description: String {
120
- errorDescription ?? "ARTIFACT_NETWORK_FAILED: firmware background transfer failed"
190
+ func sweepFirmwareArtifactOrphansAtRoot(
191
+ _ rootURL: URL,
192
+ retainedSha256: Set<String>,
193
+ activeSha256: Set<String>,
194
+ openPaths: Set<String>,
195
+ now: Date = Date(),
196
+ fileManager: FileManager = .default
197
+ ) throws -> (deletedFiles: Int, deletedBytes: Int64) {
198
+ var deletedFiles = 0
199
+ var deletedBytes: Int64 = 0
200
+ let resourceKeys: Set<URLResourceKey> = [
201
+ .isRegularFileKey,
202
+ .isDirectoryKey,
203
+ .isSymbolicLinkKey,
204
+ .contentModificationDateKey,
205
+ .fileSizeKey,
206
+ ]
207
+ let entries = try fileManager.contentsOfDirectory(
208
+ at: rootURL,
209
+ includingPropertiesForKeys: Array(resourceKeys),
210
+ options: []
211
+ )
212
+ for entryURL in entries {
213
+ let name = entryURL.lastPathComponent
214
+ let values = try entryURL.resourceValues(forKeys: resourceKeys)
215
+ let isArchiveScratch = firmwareArtifactScratchNameIsValid(
216
+ name,
217
+ prefix: "archive-"
218
+ )
219
+ let isPromoteScratch = firmwareArtifactScratchNameIsValid(
220
+ name,
221
+ prefix: ".promote-"
222
+ )
223
+ let isScratchCandidate = values.isSymbolicLink != true &&
224
+ ((isArchiveScratch && values.isDirectory == true) ||
225
+ (isPromoteScratch && values.isRegularFile == true))
226
+ if isScratchCandidate {
227
+ guard
228
+ let modifiedAt = values.contentModificationDate,
229
+ now.timeIntervalSince(modifiedAt) >= firmwareArtifactScratchGrace
230
+ else {
231
+ continue
232
+ }
233
+ let size = firmwareArtifactEntrySize(
234
+ entryURL,
235
+ values: values,
236
+ fileManager: fileManager
237
+ )
238
+ try fileManager.removeItem(at: entryURL)
239
+ deletedFiles += 1
240
+ deletedBytes += size
241
+ continue
242
+ }
243
+
244
+ guard
245
+ values.isRegularFile == true,
246
+ values.isSymbolicLink != true,
247
+ name.count >= 64
248
+ else {
249
+ continue
250
+ }
251
+ let sha256 = String(name.prefix(64))
252
+ guard
253
+ sha256.range(of: "^[a-f0-9]{64}$", options: .regularExpression) != nil,
254
+ !retainedSha256.contains(sha256),
255
+ !activeSha256.contains(sha256),
256
+ !openPaths.contains(entryURL.path)
257
+ else {
258
+ continue
259
+ }
260
+ let grace: TimeInterval
261
+ if name.hasSuffix(".bin") {
262
+ grace = firmwareArtifactFinalGrace
263
+ } else if name.hasSuffix(".partial") {
264
+ grace = firmwareArtifactPartialGrace
265
+ } else {
266
+ continue
267
+ }
268
+ guard
269
+ let modifiedAt = values.contentModificationDate,
270
+ now.timeIntervalSince(modifiedAt) >= grace
271
+ else {
272
+ continue
273
+ }
274
+ let size = Int64(values.fileSize ?? 0)
275
+ try fileManager.removeItem(at: entryURL)
276
+ deletedFiles += 1
277
+ deletedBytes += size
121
278
  }
279
+ return (deletedFiles, deletedBytes)
122
280
  }
123
281
 
124
282
  // MARK: - Dependency-free RangeDownloader logic (OCDS §4 / §5)