@onekeyfe/react-native-range-downloader 3.0.81-alpha.3 → 3.0.81-alpha.4
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/ios/FirmwareArtifactStore.swift +301 -119
- package/ios/RangeDownloadLogic.swift +49 -0
- package/package.json +2 -2
|
@@ -56,7 +56,100 @@ private struct StagedFirmwareArchiveEntry {
|
|
|
56
56
|
let stagingURL: URL
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
private final class
|
|
59
|
+
private final class FirmwareArtifactStreamDelegate: NSObject, URLSessionDataDelegate {
|
|
60
|
+
private let partialURL: URL
|
|
61
|
+
private let hostname: String
|
|
62
|
+
private let resumeOffset: Int64
|
|
63
|
+
private let expectedSize: Int64
|
|
64
|
+
private let maxBytes: Int64
|
|
65
|
+
private let isCancelled: () -> Bool
|
|
66
|
+
private let stateLock = NSLock()
|
|
67
|
+
private var continuation: CheckedContinuation<Void, Error>?
|
|
68
|
+
private var dataTask: URLSessionDataTask?
|
|
69
|
+
private var cancellationRequested = false
|
|
70
|
+
private var completed = false
|
|
71
|
+
private var responseAccepted = false
|
|
72
|
+
private var handle: FileHandle?
|
|
73
|
+
private var written: Int64 = 0
|
|
74
|
+
|
|
75
|
+
init(
|
|
76
|
+
partialURL: URL,
|
|
77
|
+
hostname: String,
|
|
78
|
+
resumeOffset: Int64,
|
|
79
|
+
expectedSize: Int64,
|
|
80
|
+
maxBytes: Int64,
|
|
81
|
+
isCancelled: @escaping () -> Bool
|
|
82
|
+
) {
|
|
83
|
+
self.partialURL = partialURL
|
|
84
|
+
self.hostname = hostname
|
|
85
|
+
self.resumeOffset = resumeOffset
|
|
86
|
+
self.expectedSize = expectedSize
|
|
87
|
+
self.maxBytes = maxBytes
|
|
88
|
+
self.isCancelled = isCancelled
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func run(session: URLSession, request: URLRequest) async throws {
|
|
92
|
+
try await withTaskCancellationHandler {
|
|
93
|
+
try await withCheckedThrowingContinuation {
|
|
94
|
+
(continuation: CheckedContinuation<Void, Error>) in
|
|
95
|
+
let task = session.dataTask(with: request)
|
|
96
|
+
stateLock.lock()
|
|
97
|
+
if cancellationRequested || completed {
|
|
98
|
+
stateLock.unlock()
|
|
99
|
+
task.cancel()
|
|
100
|
+
continuation.resume(throwing: CancellationError())
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
self.continuation = continuation
|
|
104
|
+
dataTask = task
|
|
105
|
+
stateLock.unlock()
|
|
106
|
+
task.resume()
|
|
107
|
+
}
|
|
108
|
+
} onCancel: {
|
|
109
|
+
self.cancel()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private func cancel() {
|
|
114
|
+
stateLock.lock()
|
|
115
|
+
cancellationRequested = true
|
|
116
|
+
let task = dataTask
|
|
117
|
+
stateLock.unlock()
|
|
118
|
+
task?.cancel()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private func finish(_ result: Result<Void, Error>) {
|
|
122
|
+
stateLock.lock()
|
|
123
|
+
guard !completed else {
|
|
124
|
+
stateLock.unlock()
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
completed = true
|
|
128
|
+
let continuation = continuation
|
|
129
|
+
self.continuation = nil
|
|
130
|
+
dataTask = nil
|
|
131
|
+
let handle = handle
|
|
132
|
+
self.handle = nil
|
|
133
|
+
stateLock.unlock()
|
|
134
|
+
|
|
135
|
+
try? handle?.close()
|
|
136
|
+
continuation?.resume(with: result)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private func reject(
|
|
140
|
+
_ dataTask: URLSessionDataTask,
|
|
141
|
+
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void,
|
|
142
|
+
message: String
|
|
143
|
+
) {
|
|
144
|
+
completionHandler(.cancel)
|
|
145
|
+
fail(dataTask, message: message)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private func fail(_ task: URLSessionTask, message: String) {
|
|
149
|
+
task.cancel()
|
|
150
|
+
finish(.failure(FirmwareArtifactStoreError.downloadFailed(message)))
|
|
151
|
+
}
|
|
152
|
+
|
|
60
153
|
func urlSession(
|
|
61
154
|
_ session: URLSession,
|
|
62
155
|
task: URLSessionTask,
|
|
@@ -65,6 +158,192 @@ private final class FirmwareArtifactRedirectDelegate: NSObject, URLSessionTaskDe
|
|
|
65
158
|
completionHandler: @escaping (URLRequest?) -> Void
|
|
66
159
|
) {
|
|
67
160
|
completionHandler(nil)
|
|
161
|
+
fail(
|
|
162
|
+
task,
|
|
163
|
+
message:
|
|
164
|
+
"ARTIFACT_REDIRECT_REJECTED: firmware response changed canonical identity"
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
func urlSession(
|
|
169
|
+
_ session: URLSession,
|
|
170
|
+
dataTask: URLSessionDataTask,
|
|
171
|
+
didReceive response: URLResponse,
|
|
172
|
+
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void
|
|
173
|
+
) {
|
|
174
|
+
guard !isCancelled() else {
|
|
175
|
+
reject(
|
|
176
|
+
dataTask,
|
|
177
|
+
completionHandler: completionHandler,
|
|
178
|
+
message: "ARTIFACT_CANCELLED: firmware artifact download was cancelled"
|
|
179
|
+
)
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
guard let httpResponse = response as? HTTPURLResponse else {
|
|
183
|
+
reject(
|
|
184
|
+
dataTask,
|
|
185
|
+
completionHandler: completionHandler,
|
|
186
|
+
message: "Firmware response is not HTTP"
|
|
187
|
+
)
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
guard
|
|
191
|
+
let responseURL = httpResponse.url,
|
|
192
|
+
responseURL.scheme?.lowercased() == "https",
|
|
193
|
+
responseURL.host?.lowercased() == hostname.lowercased(),
|
|
194
|
+
responseURL.port == nil || responseURL.port == 443
|
|
195
|
+
else {
|
|
196
|
+
reject(
|
|
197
|
+
dataTask,
|
|
198
|
+
completionHandler: completionHandler,
|
|
199
|
+
message:
|
|
200
|
+
"ARTIFACT_REDIRECT_REJECTED: firmware response changed canonical identity"
|
|
201
|
+
)
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
guard httpResponse.statusCode == 200 || httpResponse.statusCode == 206 else {
|
|
205
|
+
reject(
|
|
206
|
+
dataTask,
|
|
207
|
+
completionHandler: completionHandler,
|
|
208
|
+
message:
|
|
209
|
+
"ARTIFACT_HTTP_\(httpResponse.statusCode): firmware request failed"
|
|
210
|
+
)
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let append = resumeOffset > 0 && httpResponse.statusCode == 206
|
|
215
|
+
if httpResponse.statusCode == 206 {
|
|
216
|
+
guard
|
|
217
|
+
let contentRange = httpResponse.value(
|
|
218
|
+
forHTTPHeaderField: "Content-Range"
|
|
219
|
+
),
|
|
220
|
+
firmwareArtifactContentRangeIsValid(
|
|
221
|
+
contentRange,
|
|
222
|
+
expectedStart: append ? resumeOffset : 0,
|
|
223
|
+
expectedTotal: expectedSize
|
|
224
|
+
)
|
|
225
|
+
else {
|
|
226
|
+
reject(
|
|
227
|
+
dataTask,
|
|
228
|
+
completionHandler: completionHandler,
|
|
229
|
+
message:
|
|
230
|
+
"ARTIFACT_PROTOCOL_INVALID: firmware resume Content-Range is invalid"
|
|
231
|
+
)
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
let baseOffset = append ? resumeOffset : 0
|
|
237
|
+
guard
|
|
238
|
+
firmwareArtifactResponseFits(
|
|
239
|
+
expectedContentLength: httpResponse.expectedContentLength,
|
|
240
|
+
baseOffset: baseOffset,
|
|
241
|
+
maxBytes: maxBytes
|
|
242
|
+
)
|
|
243
|
+
else {
|
|
244
|
+
reject(
|
|
245
|
+
dataTask,
|
|
246
|
+
completionHandler: completionHandler,
|
|
247
|
+
message:
|
|
248
|
+
"ARTIFACT_PROTOCOL_INVALID: firmware artifact exceeds maxBytes"
|
|
249
|
+
)
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
do {
|
|
254
|
+
let handle = try FileHandle(forWritingTo: partialURL)
|
|
255
|
+
if append {
|
|
256
|
+
try handle.seekToEnd()
|
|
257
|
+
} else {
|
|
258
|
+
try handle.truncate(atOffset: 0)
|
|
259
|
+
}
|
|
260
|
+
self.handle = handle
|
|
261
|
+
written = baseOffset
|
|
262
|
+
responseAccepted = true
|
|
263
|
+
completionHandler(.allow)
|
|
264
|
+
} catch {
|
|
265
|
+
reject(
|
|
266
|
+
dataTask,
|
|
267
|
+
completionHandler: completionHandler,
|
|
268
|
+
message:
|
|
269
|
+
"ARTIFACT_NETWORK_FAILED: firmware partial file could not be opened"
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
func urlSession(
|
|
275
|
+
_ session: URLSession,
|
|
276
|
+
dataTask: URLSessionDataTask,
|
|
277
|
+
didReceive data: Data
|
|
278
|
+
) {
|
|
279
|
+
guard responseAccepted, let handle else {
|
|
280
|
+
fail(
|
|
281
|
+
dataTask,
|
|
282
|
+
message:
|
|
283
|
+
"ARTIFACT_PROTOCOL_INVALID: firmware response stream was not accepted"
|
|
284
|
+
)
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
guard !isCancelled() else {
|
|
288
|
+
fail(
|
|
289
|
+
dataTask,
|
|
290
|
+
message: "ARTIFACT_CANCELLED: firmware artifact download was cancelled"
|
|
291
|
+
)
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
guard
|
|
295
|
+
firmwareArtifactResponseFits(
|
|
296
|
+
expectedContentLength: Int64(data.count),
|
|
297
|
+
baseOffset: written,
|
|
298
|
+
maxBytes: maxBytes
|
|
299
|
+
)
|
|
300
|
+
else {
|
|
301
|
+
fail(
|
|
302
|
+
dataTask,
|
|
303
|
+
message:
|
|
304
|
+
"ARTIFACT_PROTOCOL_INVALID: firmware artifact exceeds maxBytes"
|
|
305
|
+
)
|
|
306
|
+
return
|
|
307
|
+
}
|
|
308
|
+
do {
|
|
309
|
+
try handle.write(contentsOf: data)
|
|
310
|
+
written += Int64(data.count)
|
|
311
|
+
} catch {
|
|
312
|
+
fail(
|
|
313
|
+
dataTask,
|
|
314
|
+
message:
|
|
315
|
+
"ARTIFACT_NETWORK_FAILED: firmware response stream could not be persisted"
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
func urlSession(
|
|
321
|
+
_ session: URLSession,
|
|
322
|
+
task: URLSessionTask,
|
|
323
|
+
didCompleteWithError error: Error?
|
|
324
|
+
) {
|
|
325
|
+
if let error {
|
|
326
|
+
finish(.failure(error))
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
guard responseAccepted, let handle else {
|
|
330
|
+
finish(
|
|
331
|
+
.failure(FirmwareArtifactStoreError.downloadFailed(
|
|
332
|
+
"ARTIFACT_PROTOCOL_INVALID: firmware response stream was not accepted"
|
|
333
|
+
))
|
|
334
|
+
)
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
do {
|
|
338
|
+
try handle.synchronize()
|
|
339
|
+
finish(.success(()))
|
|
340
|
+
} catch {
|
|
341
|
+
finish(
|
|
342
|
+
.failure(FirmwareArtifactStoreError.downloadFailed(
|
|
343
|
+
"ARTIFACT_NETWORK_FAILED: firmware partial file could not be synchronized"
|
|
344
|
+
))
|
|
345
|
+
)
|
|
346
|
+
}
|
|
68
347
|
}
|
|
69
348
|
}
|
|
70
349
|
|
|
@@ -286,7 +565,11 @@ final class FirmwareArtifactStore {
|
|
|
286
565
|
}
|
|
287
566
|
|
|
288
567
|
let partialURL = rootURL.appendingPathComponent(
|
|
289
|
-
|
|
568
|
+
firmwareArtifactPartialFileName(
|
|
569
|
+
transactionId: params.transactionId,
|
|
570
|
+
taskId: params.taskId,
|
|
571
|
+
expectedSha256: expectedSha256
|
|
572
|
+
),
|
|
290
573
|
isDirectory: false
|
|
291
574
|
)
|
|
292
575
|
if !fileManager.fileExists(atPath: partialURL.path) {
|
|
@@ -953,19 +1236,30 @@ final class FirmwareArtifactStore {
|
|
|
953
1236
|
request.setValue("bytes=\(resumeOffset)-", forHTTPHeaderField: "Range")
|
|
954
1237
|
}
|
|
955
1238
|
|
|
1239
|
+
let streamDelegate = FirmwareArtifactStreamDelegate(
|
|
1240
|
+
partialURL: partialURL,
|
|
1241
|
+
hostname: hostname,
|
|
1242
|
+
resumeOffset: resumeOffset,
|
|
1243
|
+
expectedSize: Int64(params.expectedSize),
|
|
1244
|
+
maxBytes: Int64(params.maxBytes),
|
|
1245
|
+
isCancelled: { [weak self] in
|
|
1246
|
+
Task.isCancelled ||
|
|
1247
|
+
self?.isTransactionCancelled(params.transactionId) == true
|
|
1248
|
+
}
|
|
1249
|
+
)
|
|
956
1250
|
let pinnedSession: SniConnectPinnedSession?
|
|
957
1251
|
if params.routeType == "pinnedIp" {
|
|
958
1252
|
pinnedSession = try SniConnectPinnedTransport.makeSession(
|
|
959
1253
|
hostname: hostname,
|
|
960
|
-
ip: params.resolvedIp
|
|
1254
|
+
ip: params.resolvedIp!,
|
|
1255
|
+
dataDelegate: streamDelegate
|
|
961
1256
|
)
|
|
962
1257
|
} else {
|
|
963
1258
|
pinnedSession = nil
|
|
964
1259
|
}
|
|
965
|
-
let domainDelegate = FirmwareArtifactRedirectDelegate()
|
|
966
1260
|
let session = pinnedSession?.session ?? URLSession(
|
|
967
1261
|
configuration: .ephemeral,
|
|
968
|
-
delegate:
|
|
1262
|
+
delegate: streamDelegate,
|
|
969
1263
|
delegateQueue: nil
|
|
970
1264
|
)
|
|
971
1265
|
defer {
|
|
@@ -976,91 +1270,8 @@ final class FirmwareArtifactStore {
|
|
|
976
1270
|
}
|
|
977
1271
|
}
|
|
978
1272
|
|
|
979
|
-
let downloadedURL: URL
|
|
980
|
-
let response: URLResponse
|
|
981
|
-
do {
|
|
982
|
-
(downloadedURL, response) = try await session.download(for: request)
|
|
983
|
-
} catch {
|
|
984
|
-
if isCancellationError(
|
|
985
|
-
error,
|
|
986
|
-
transactionId: params.transactionId
|
|
987
|
-
) {
|
|
988
|
-
throw FirmwareArtifactStoreError.downloadFailed(
|
|
989
|
-
"ARTIFACT_CANCELLED: firmware artifact download was cancelled"
|
|
990
|
-
)
|
|
991
|
-
}
|
|
992
|
-
if isFirmwareArtifactTLSError(error) {
|
|
993
|
-
throw FirmwareArtifactStoreError.downloadFailed(
|
|
994
|
-
"ARTIFACT_TLS_FAILED: firmware TLS validation failed"
|
|
995
|
-
)
|
|
996
|
-
}
|
|
997
|
-
throw FirmwareArtifactStoreError.downloadFailed(
|
|
998
|
-
"ARTIFACT_NETWORK_FAILED: firmware request failed"
|
|
999
|
-
)
|
|
1000
|
-
}
|
|
1001
|
-
defer { try? fileManager.removeItem(at: downloadedURL) }
|
|
1002
|
-
guard let httpResponse = response as? HTTPURLResponse else {
|
|
1003
|
-
throw FirmwareArtifactStoreError.downloadFailed("Firmware response is not HTTP")
|
|
1004
|
-
}
|
|
1005
|
-
guard
|
|
1006
|
-
let responseURL = httpResponse.url,
|
|
1007
|
-
responseURL.scheme?.lowercased() == "https",
|
|
1008
|
-
responseURL.host?.lowercased() == hostname.lowercased(),
|
|
1009
|
-
responseURL.port == nil || responseURL.port == 443
|
|
1010
|
-
else {
|
|
1011
|
-
throw FirmwareArtifactStoreError.downloadFailed(
|
|
1012
|
-
"ARTIFACT_REDIRECT_REJECTED: firmware response changed canonical identity"
|
|
1013
|
-
)
|
|
1014
|
-
}
|
|
1015
|
-
guard httpResponse.statusCode == 200 || httpResponse.statusCode == 206 else {
|
|
1016
|
-
throw FirmwareArtifactStoreError.downloadFailed(
|
|
1017
|
-
"ARTIFACT_HTTP_\(httpResponse.statusCode): firmware request failed"
|
|
1018
|
-
)
|
|
1019
|
-
}
|
|
1020
|
-
let append = resumeOffset > 0 && httpResponse.statusCode == 206
|
|
1021
|
-
if httpResponse.statusCode == 206 {
|
|
1022
|
-
guard
|
|
1023
|
-
let contentRange = httpResponse.value(forHTTPHeaderField: "Content-Range"),
|
|
1024
|
-
validateContentRange(
|
|
1025
|
-
contentRange,
|
|
1026
|
-
expectedStart: append ? resumeOffset : 0,
|
|
1027
|
-
expectedTotal: Int64(params.expectedSize)
|
|
1028
|
-
)
|
|
1029
|
-
else {
|
|
1030
|
-
throw FirmwareArtifactStoreError.downloadFailed(
|
|
1031
|
-
"ARTIFACT_PROTOCOL_INVALID: firmware resume Content-Range is invalid"
|
|
1032
|
-
)
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
let handle = try FileHandle(forWritingTo: partialURL)
|
|
1036
|
-
defer { try? handle.close() }
|
|
1037
|
-
if append {
|
|
1038
|
-
try handle.seekToEnd()
|
|
1039
|
-
} else {
|
|
1040
|
-
try handle.truncate(atOffset: 0)
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
var written = append ? resumeOffset : 0
|
|
1044
|
-
let source = try FileHandle(forReadingFrom: downloadedURL)
|
|
1045
|
-
defer { try? source.close() }
|
|
1046
1273
|
do {
|
|
1047
|
-
|
|
1048
|
-
try Task.checkCancellation()
|
|
1049
|
-
try rejectIfCancelled(transactionId: params.transactionId)
|
|
1050
|
-
guard
|
|
1051
|
-
let buffer = try source.read(upToCount: 64 * 1024),
|
|
1052
|
-
!buffer.isEmpty
|
|
1053
|
-
else {
|
|
1054
|
-
break
|
|
1055
|
-
}
|
|
1056
|
-
written += Int64(buffer.count)
|
|
1057
|
-
guard written <= Int64(params.maxBytes) else {
|
|
1058
|
-
throw FirmwareArtifactStoreError.downloadFailed(
|
|
1059
|
-
"ARTIFACT_PROTOCOL_INVALID: firmware artifact exceeds maxBytes"
|
|
1060
|
-
)
|
|
1061
|
-
}
|
|
1062
|
-
try handle.write(contentsOf: buffer)
|
|
1063
|
-
}
|
|
1274
|
+
try await streamDelegate.run(session: session, request: request)
|
|
1064
1275
|
} catch let error as FirmwareArtifactStoreError {
|
|
1065
1276
|
throw error
|
|
1066
1277
|
} catch {
|
|
@@ -1078,10 +1289,9 @@ final class FirmwareArtifactStore {
|
|
|
1078
1289
|
)
|
|
1079
1290
|
}
|
|
1080
1291
|
throw FirmwareArtifactStoreError.downloadFailed(
|
|
1081
|
-
"ARTIFACT_NETWORK_FAILED: firmware
|
|
1292
|
+
"ARTIFACT_NETWORK_FAILED: firmware request failed"
|
|
1082
1293
|
)
|
|
1083
1294
|
}
|
|
1084
|
-
try handle.synchronize()
|
|
1085
1295
|
try rejectIfCancelled(transactionId: params.transactionId)
|
|
1086
1296
|
}
|
|
1087
1297
|
|
|
@@ -1112,34 +1322,6 @@ final class FirmwareArtifactStore {
|
|
|
1112
1322
|
nsError.code == NSURLErrorCancelled
|
|
1113
1323
|
}
|
|
1114
1324
|
|
|
1115
|
-
private func validateContentRange(
|
|
1116
|
-
_ value: String,
|
|
1117
|
-
expectedStart: Int64,
|
|
1118
|
-
expectedTotal: Int64
|
|
1119
|
-
) -> Bool {
|
|
1120
|
-
let pattern = #"^bytes ([0-9]+)-([0-9]+)/([0-9]+)$"#
|
|
1121
|
-
guard
|
|
1122
|
-
let expression = try? NSRegularExpression(pattern: pattern),
|
|
1123
|
-
let match = expression.firstMatch(
|
|
1124
|
-
in: value.lowercased(),
|
|
1125
|
-
range: NSRange(value.startIndex..., in: value)
|
|
1126
|
-
),
|
|
1127
|
-
match.range.location != NSNotFound,
|
|
1128
|
-
let startRange = Range(match.range(at: 1), in: value),
|
|
1129
|
-
let endRange = Range(match.range(at: 2), in: value),
|
|
1130
|
-
let totalRange = Range(match.range(at: 3), in: value),
|
|
1131
|
-
let start = Int64(value[startRange]),
|
|
1132
|
-
let end = Int64(value[endRange]),
|
|
1133
|
-
let total = Int64(value[totalRange])
|
|
1134
|
-
else {
|
|
1135
|
-
return false
|
|
1136
|
-
}
|
|
1137
|
-
return start == expectedStart &&
|
|
1138
|
-
end >= start &&
|
|
1139
|
-
end < total &&
|
|
1140
|
-
total == expectedTotal
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
1325
|
private func validateStoredArtifact(
|
|
1144
1326
|
fileURL: URL,
|
|
1145
1327
|
expectedSize: Int64,
|
|
@@ -36,6 +36,55 @@ func firmwareArtifactDownloadKey(
|
|
|
36
36
|
"\(transactionId)|\(expectedSize)|\(expectedSha256)"
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
func firmwareArtifactPartialFileName(
|
|
40
|
+
transactionId: String,
|
|
41
|
+
taskId: String,
|
|
42
|
+
expectedSha256: String
|
|
43
|
+
) -> String {
|
|
44
|
+
let transactionData = Data(transactionId.utf8)
|
|
45
|
+
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
|
|
46
|
+
transactionData.withUnsafeBytes {
|
|
47
|
+
_ = CC_SHA256($0.baseAddress, CC_LONG(transactionData.count), &hash)
|
|
48
|
+
}
|
|
49
|
+
let transactionToken = hash
|
|
50
|
+
.prefix(8)
|
|
51
|
+
.map { String(format: "%02x", $0) }
|
|
52
|
+
.joined()
|
|
53
|
+
return "\(expectedSha256).\(taskId).\(transactionToken).partial"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func firmwareArtifactContentRangeIsValid(
|
|
57
|
+
_ value: String,
|
|
58
|
+
expectedStart: Int64,
|
|
59
|
+
expectedTotal: Int64
|
|
60
|
+
) -> Bool {
|
|
61
|
+
guard value.lowercased().hasPrefix("bytes ") else {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
guard
|
|
65
|
+
let bounds = RangeDownloadLogic.parseContentRangeBounds(value),
|
|
66
|
+
let total = RangeDownloadLogic.parseContentRangeTotal(value)
|
|
67
|
+
else {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
return bounds.start == expectedStart &&
|
|
71
|
+
bounds.end >= bounds.start &&
|
|
72
|
+
bounds.end < total &&
|
|
73
|
+
total == expectedTotal
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
func firmwareArtifactResponseFits(
|
|
77
|
+
expectedContentLength: Int64,
|
|
78
|
+
baseOffset: Int64,
|
|
79
|
+
maxBytes: Int64
|
|
80
|
+
) -> Bool {
|
|
81
|
+
guard baseOffset >= 0, baseOffset <= maxBytes else {
|
|
82
|
+
return false
|
|
83
|
+
}
|
|
84
|
+
return expectedContentLength < 0 ||
|
|
85
|
+
expectedContentLength <= maxBytes - baseOffset
|
|
86
|
+
}
|
|
87
|
+
|
|
39
88
|
enum FirmwareBackgroundDownloadError: LocalizedError, CustomStringConvertible {
|
|
40
89
|
case cancelled
|
|
41
90
|
case invalidTask
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/react-native-range-downloader",
|
|
3
|
-
"version": "3.0.81-alpha.
|
|
3
|
+
"version": "3.0.81-alpha.4",
|
|
4
4
|
"description": "react-native-range-downloader",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"typescript": "^5.9.2"
|
|
89
89
|
},
|
|
90
90
|
"peerDependencies": {
|
|
91
|
-
"@onekeyfe/react-native-sni-connect": "3.0.81-alpha.
|
|
91
|
+
"@onekeyfe/react-native-sni-connect": "3.0.81-alpha.4",
|
|
92
92
|
"react": "*",
|
|
93
93
|
"react-native": "*",
|
|
94
94
|
"react-native-nitro-modules": "0.33.2"
|