@bota.dev/react-native-app-sdk 2.0.0-beta.1 → 2.0.0-beta.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.
- package/README.md +115 -4
- package/android/build.gradle +1 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDevices.kt +22 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDiagnostics.kt +53 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogs.kt +16 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploads.kt +144 -14
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordings.kt +150 -29
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKEncryptedUploadV2Materials.kt +27 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKModule.kt +86 -3
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKPackage.kt +9 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/upload/BotaUploadV2BackendModule.kt +66 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/upload/NativeUpload.kt +400 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/upload/UploadHttp.kt +158 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/upload/UploadJournal.kt +112 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/upload/UploadModels.kt +171 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDevicesTest.kt +35 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogsTest.kt +33 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploadsTest.kt +135 -6
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingsTest.kt +236 -10
- package/generated/codegen-contract.json +751 -32
- package/ios/BotaDeviceSDK.mm +114 -0
- package/ios/BotaDeviceSDKAppleBridge.swift +141 -4
- package/ios/BotaDeviceSDKAppleDevices.swift +11 -0
- package/ios/BotaDeviceSDKAppleDiagnostics.swift +40 -0
- package/ios/BotaDeviceSDKAppleLogs.swift +18 -0
- package/ios/BotaDeviceSDKAppleRecordingUploads.swift +138 -6
- package/ios/BotaDeviceSDKAppleRecordings.swift +113 -5
- package/ios/BotaDeviceSDKEncryptedUploadV2Materials.swift +40 -0
- package/ios/BotaUploadV2Backend.mm +49 -0
- package/ios/UploadBackend/BotaUploadV2BackendBridge.swift +98 -0
- package/ios/UploadBackend/UploadCoordinator.swift +84 -0
- package/ios/UploadBackend/UploadHTTP.swift +82 -0
- package/ios/UploadBackend/UploadJournal.swift +187 -0
- package/ios/UploadBackend/UploadModels.swift +273 -0
- package/ios/UploadBackend/UploadOperation.swift +391 -0
- package/lib/commonjs/BotaClient.js +16 -2
- package/lib/commonjs/BotaClient.js.map +1 -1
- package/lib/commonjs/ble/constants.js +5 -1
- package/lib/commonjs/ble/constants.js.map +1 -1
- package/lib/commonjs/ble/deviceDiagnostics.js +240 -0
- package/lib/commonjs/ble/deviceDiagnostics.js.map +1 -0
- package/lib/commonjs/client.js +183 -28
- package/lib/commonjs/client.js.map +1 -1
- package/lib/commonjs/clientPresence.js +26 -0
- package/lib/commonjs/clientPresence.js.map +1 -0
- package/lib/commonjs/encryptedUploadV2Backend.js +200 -0
- package/lib/commonjs/encryptedUploadV2Backend.js.map +1 -0
- package/lib/commonjs/index.js +34 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/managers/DeviceManager.js +23 -0
- package/lib/commonjs/managers/DeviceManager.js.map +1 -1
- package/lib/commonjs/managers/NativeUploadQueue.js +386 -0
- package/lib/commonjs/managers/NativeUploadQueue.js.map +1 -0
- package/lib/commonjs/managers/RecordingManager.js +83 -102
- package/lib/commonjs/managers/RecordingManager.js.map +1 -1
- package/lib/commonjs/managers/uploadRecoveryMetadata.js +83 -0
- package/lib/commonjs/managers/uploadRecoveryMetadata.js.map +1 -0
- package/lib/commonjs/sdkIdentity.js +10 -0
- package/lib/commonjs/sdkIdentity.js.map +1 -0
- package/lib/commonjs/specs/NativeBotaDeviceSDK.js.map +1 -1
- package/lib/commonjs/specs/NativeBotaUploadV2Backend.js +11 -0
- package/lib/commonjs/specs/NativeBotaUploadV2Backend.js.map +1 -0
- package/lib/commonjs/uploadRecovery.js +68 -0
- package/lib/commonjs/uploadRecovery.js.map +1 -0
- package/lib/module/BotaClient.js +16 -2
- package/lib/module/BotaClient.js.map +1 -1
- package/lib/module/ble/constants.js +4 -0
- package/lib/module/ble/constants.js.map +1 -1
- package/lib/module/ble/deviceDiagnostics.js +234 -0
- package/lib/module/ble/deviceDiagnostics.js.map +1 -0
- package/lib/module/client.js +183 -28
- package/lib/module/client.js.map +1 -1
- package/lib/module/clientPresence.js +22 -0
- package/lib/module/clientPresence.js.map +1 -0
- package/lib/module/encryptedUploadV2Backend.js +196 -0
- package/lib/module/encryptedUploadV2Backend.js.map +1 -0
- package/lib/module/index.js +7 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/managers/DeviceManager.js +23 -0
- package/lib/module/managers/DeviceManager.js.map +1 -1
- package/lib/module/managers/NativeUploadQueue.js +380 -0
- package/lib/module/managers/NativeUploadQueue.js.map +1 -0
- package/lib/module/managers/RecordingManager.js +83 -102
- package/lib/module/managers/RecordingManager.js.map +1 -1
- package/lib/module/managers/uploadRecoveryMetadata.js +77 -0
- package/lib/module/managers/uploadRecoveryMetadata.js.map +1 -0
- package/lib/module/sdkIdentity.js +6 -0
- package/lib/module/sdkIdentity.js.map +1 -0
- package/lib/module/specs/NativeBotaDeviceSDK.js.map +1 -1
- package/lib/module/specs/NativeBotaUploadV2Backend.js +9 -0
- package/lib/module/specs/NativeBotaUploadV2Backend.js.map +1 -0
- package/lib/module/uploadRecovery.js +64 -0
- package/lib/module/uploadRecovery.js.map +1 -0
- package/lib/typescript/BotaClient.d.ts +2 -0
- package/lib/typescript/BotaClient.d.ts.map +1 -1
- package/lib/typescript/ble/constants.d.ts +4 -0
- package/lib/typescript/ble/constants.d.ts.map +1 -1
- package/lib/typescript/ble/deviceDiagnostics.d.ts +10 -0
- package/lib/typescript/ble/deviceDiagnostics.d.ts.map +1 -0
- package/lib/typescript/client.d.ts +24 -3
- package/lib/typescript/client.d.ts.map +1 -1
- package/lib/typescript/clientPresence.d.ts +26 -0
- package/lib/typescript/clientPresence.d.ts.map +1 -0
- package/lib/typescript/encryptedUploadV2Backend.d.ts +41 -0
- package/lib/typescript/encryptedUploadV2Backend.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +13 -4
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/managers/DeviceManager.d.ts +5 -1
- package/lib/typescript/managers/DeviceManager.d.ts.map +1 -1
- package/lib/typescript/managers/NativeUploadQueue.d.ts +41 -0
- package/lib/typescript/managers/NativeUploadQueue.d.ts.map +1 -0
- package/lib/typescript/managers/RecordingManager.d.ts +3 -8
- package/lib/typescript/managers/RecordingManager.d.ts.map +1 -1
- package/lib/typescript/managers/types.d.ts +6 -1
- package/lib/typescript/managers/types.d.ts.map +1 -1
- package/lib/typescript/managers/uploadRecoveryMetadata.d.ts +6 -0
- package/lib/typescript/managers/uploadRecoveryMetadata.d.ts.map +1 -0
- package/lib/typescript/models/Device.d.ts +42 -0
- package/lib/typescript/models/Device.d.ts.map +1 -1
- package/lib/typescript/models/Recording.d.ts +47 -0
- package/lib/typescript/models/Recording.d.ts.map +1 -1
- package/lib/typescript/models/Status.d.ts +5 -1
- package/lib/typescript/models/Status.d.ts.map +1 -1
- package/lib/typescript/sdkIdentity.d.ts +3 -0
- package/lib/typescript/sdkIdentity.d.ts.map +1 -0
- package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts +70 -0
- package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts.map +1 -1
- package/lib/typescript/specs/NativeBotaUploadV2Backend.d.ts +18 -0
- package/lib/typescript/specs/NativeBotaUploadV2Backend.d.ts.map +1 -0
- package/lib/typescript/uploadRecovery.d.ts +27 -0
- package/lib/typescript/uploadRecovery.d.ts.map +1 -0
- package/package.json +1 -1
- package/scripts/bota_device_sdk_spm_workaround.rb +26 -15
- package/src/BotaClient.ts +17 -3
- package/src/ble/constants.ts +5 -0
- package/src/ble/deviceDiagnostics.ts +238 -0
- package/src/client.ts +189 -18
- package/src/clientPresence.ts +33 -0
- package/src/encryptedUploadV2Backend.ts +171 -0
- package/src/index.ts +24 -0
- package/src/managers/DeviceManager.ts +23 -0
- package/src/managers/NativeUploadQueue.ts +330 -0
- package/src/managers/RecordingManager.ts +83 -116
- package/src/managers/types.ts +7 -1
- package/src/managers/uploadRecoveryMetadata.ts +59 -0
- package/src/models/Device.ts +43 -0
- package/src/models/Recording.ts +52 -0
- package/src/models/Status.ts +5 -1
- package/src/sdkIdentity.ts +3 -0
- package/src/specs/NativeBotaDeviceSDK.ts +66 -0
- package/src/specs/NativeBotaUploadV2Backend.ts +19 -0
- package/src/uploadRecovery.ts +66 -0
package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingsTest.kt
CHANGED
|
@@ -33,13 +33,199 @@ import kotlinx.coroutines.flow.Flow
|
|
|
33
33
|
import kotlinx.coroutines.flow.flowOf
|
|
34
34
|
import kotlinx.coroutines.flow.flow
|
|
35
35
|
import kotlinx.coroutines.test.runTest
|
|
36
|
+
import kotlinx.coroutines.CompletableDeferred
|
|
37
|
+
import kotlinx.coroutines.CancellationException
|
|
38
|
+
import kotlinx.coroutines.NonCancellable
|
|
39
|
+
import kotlinx.coroutines.async
|
|
40
|
+
import kotlinx.coroutines.launch
|
|
41
|
+
import kotlinx.coroutines.withContext
|
|
42
|
+
import kotlinx.coroutines.withTimeout
|
|
43
|
+
import kotlinx.coroutines.yield
|
|
36
44
|
import okhttp3.Request
|
|
37
45
|
import org.junit.Assert.assertEquals
|
|
38
46
|
import org.junit.Assert.assertNull
|
|
39
47
|
import org.junit.Assert.assertTrue
|
|
48
|
+
import org.junit.Assert.assertFalse
|
|
49
|
+
import org.junit.Assert.assertArrayEquals
|
|
40
50
|
import org.junit.Test
|
|
41
51
|
|
|
42
52
|
class BotaDeviceSDKAndroidRecordingsTest {
|
|
53
|
+
@Test
|
|
54
|
+
fun pendingCatalogPreservesMixedProfilesAndExactDecimalMetadata() = runTest {
|
|
55
|
+
val encrypted = encryptedRecording()
|
|
56
|
+
val client = TestAndroidRecordingClient(recording(), context(encrypted))
|
|
57
|
+
val recordings = BotaDeviceSDKAndroidRecordings(client)
|
|
58
|
+
val entries = recordings.listPendingRecordings(connectedDevice())
|
|
59
|
+
assertEquals(2, entries.size)
|
|
60
|
+
assertEquals(recording(), (entries[0] as dev.bota.sdk.PendingRecording.Legacy).recording)
|
|
61
|
+
val metadata = (entries[1] as dev.bota.sdk.PendingRecording.EncryptedV2).recording.toBridgeValue()
|
|
62
|
+
assertEquals(encrypted.uuid, metadata.uuid)
|
|
63
|
+
assertEquals("18446744073709551615", metadata.ciphertextLength)
|
|
64
|
+
assertEquals("9007199254740993", metadata.startedAtMs)
|
|
65
|
+
assertEquals("9007199254740994", metadata.durationMs)
|
|
66
|
+
assertEquals("9007199254740995", metadata.plaintextLength)
|
|
67
|
+
assertEquals(3, metadata.storageFormat)
|
|
68
|
+
assertEquals("5a".repeat(32), metadata.ciphertextSha256)
|
|
69
|
+
assertTrue(metadata.javaClass.declaredFields.none { it.type == ByteArray::class.java })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Test
|
|
73
|
+
fun preCancelledEncryptedOperationNeverEntersNativeSdk() = runTest {
|
|
74
|
+
val client = TestAndroidRecordingClient(recording(), context())
|
|
75
|
+
val recordings = BotaDeviceSDKAndroidRecordings(client)
|
|
76
|
+
recordings.cancelEncryptedRecordingV2("pre-cancelled")
|
|
77
|
+
val outcome = runCatching {
|
|
78
|
+
recordings.syncEncryptedRecordingV2(connectedDevice(), encryptedRecording(), "pre-cancelled", {}, {})
|
|
79
|
+
}
|
|
80
|
+
assertTrue(outcome.exceptionOrNull() is CancellationException)
|
|
81
|
+
assertTrue(client.encryptedOperationIds.isEmpty())
|
|
82
|
+
assertTrue(client.cancelledEncryptedIds.isEmpty())
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@Test
|
|
86
|
+
fun cancellationWhileProfilePendingJoinsExactNativeCleanup() = runTest {
|
|
87
|
+
checkPendingCancellation(bulk = false)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@Test
|
|
91
|
+
fun bulkCancellationWhileProfilePendingJoinsExactNativeCleanup() = runTest {
|
|
92
|
+
checkPendingCancellation(bulk = true)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private suspend fun kotlinx.coroutines.test.TestScope.checkPendingCancellation(bulk: Boolean) {
|
|
96
|
+
val client = TestAndroidRecordingClient(recording(), context())
|
|
97
|
+
val recordings = BotaDeviceSDKAndroidRecordings(client)
|
|
98
|
+
val requested = CompletableDeferred<BotaEncryptedUploadV2ProfileRequest>()
|
|
99
|
+
val cleanupEntered = CompletableDeferred<Unit>()
|
|
100
|
+
val cleanupReleased = CompletableDeferred<Unit>()
|
|
101
|
+
client.encryptedFinally = {
|
|
102
|
+
withContext(NonCancellable) { cleanupEntered.complete(Unit); cleanupReleased.await() }
|
|
103
|
+
}
|
|
104
|
+
val sync = launch {
|
|
105
|
+
runCatching { recordings.syncEncryptedRecordingV2(connectedDevice(), encryptedRecording(), "pending",
|
|
106
|
+
{ requested.complete(it) }, {}) }
|
|
107
|
+
}
|
|
108
|
+
withTimeout(5_000) { requested.await() }
|
|
109
|
+
val cancel = async {
|
|
110
|
+
if (bulk) recordings.cancelAll() else recordings.cancelEncryptedRecordingV2("pending")
|
|
111
|
+
}
|
|
112
|
+
withTimeout(5_000) { cleanupEntered.await() }
|
|
113
|
+
assertFalse(cancel.isCompleted)
|
|
114
|
+
cleanupReleased.complete(Unit)
|
|
115
|
+
withTimeout(5_000) { cancel.await() }
|
|
116
|
+
assertTrue(sync.isCompleted)
|
|
117
|
+
assertEquals(client.encryptedOperationIds, client.cancelledEncryptedIds)
|
|
118
|
+
assertTrue(runCatching { BotaDeviceSDKEncryptedUploadV2Materials.readAuthNonce("pending") }.isFailure)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@Test
|
|
122
|
+
fun selfCancellationCompletesNativeCleanupWithoutJoiningItsOwnJob() = runTest {
|
|
123
|
+
val client = TestAndroidRecordingClient(recording(), context())
|
|
124
|
+
val recordings = BotaDeviceSDKAndroidRecordings(client)
|
|
125
|
+
var cleanupFinished = false
|
|
126
|
+
client.cancelEncrypted = { yield(); cleanupFinished = true }
|
|
127
|
+
client.beforeEncrypted = { recordings.cancelEncryptedRecordingV2("self") }
|
|
128
|
+
val sync = launch {
|
|
129
|
+
runCatching { recordings.syncEncryptedRecordingV2(connectedDevice(), encryptedRecording(), "self", {}, {}) }
|
|
130
|
+
}
|
|
131
|
+
withTimeout(5_000) { sync.join() }
|
|
132
|
+
assertTrue(cleanupFinished)
|
|
133
|
+
assertEquals(client.encryptedOperationIds, client.cancelledEncryptedIds)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@Test
|
|
137
|
+
fun resolvedMaterialIsReleasedWhenCancellationWinsBeforeProviderDelivery() = runTest {
|
|
138
|
+
var releases = 0
|
|
139
|
+
val selected = material { releases++ }
|
|
140
|
+
val registration = BotaDeviceSDKEncryptedUploadV2Materials.register(selected)
|
|
141
|
+
val client = TestAndroidRecordingClient(recording(), context())
|
|
142
|
+
val recordings = BotaDeviceSDKAndroidRecordings(client)
|
|
143
|
+
val requested = CompletableDeferred<BotaEncryptedUploadV2ProfileRequest>()
|
|
144
|
+
val sync = launch {
|
|
145
|
+
runCatching { recordings.syncEncryptedRecordingV2(connectedDevice(), encryptedRecording(), "delivery",
|
|
146
|
+
{ requested.complete(it) }, {}) }
|
|
147
|
+
}
|
|
148
|
+
val request = withTimeout(5_000) { requested.await() }
|
|
149
|
+
recordings.resolveEncryptedUploadV2Profile(request.requestId, "encrypted_upload_v2",
|
|
150
|
+
selected.uploadSessionId.toString(), selected.ownerRevision, "v2_required", registration)
|
|
151
|
+
sync.cancel()
|
|
152
|
+
withTimeout(5_000) { sync.join() }
|
|
153
|
+
assertEquals(1, releases)
|
|
154
|
+
assertNull(client.encryptedMaterialId)
|
|
155
|
+
assertNull(BotaDeviceSDKEncryptedUploadV2Materials.remove(registration))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@Test
|
|
159
|
+
fun lateProfileResolutionAndReleaseAreIdempotentAndDoNotRetainMaterial() = runTest {
|
|
160
|
+
var releases = 0
|
|
161
|
+
val selected = material { releases++ }
|
|
162
|
+
val registration = BotaDeviceSDKEncryptedUploadV2Materials.register(selected)
|
|
163
|
+
val recordings = BotaDeviceSDKAndroidRecordings(TestAndroidRecordingClient(recording(), context()))
|
|
164
|
+
val outcome = runCatching { recordings.resolveEncryptedUploadV2Profile("obsolete", "encrypted_upload_v2",
|
|
165
|
+
selected.uploadSessionId.toString(), selected.ownerRevision, "v2_required", registration) }
|
|
166
|
+
assertTrue(outcome.isFailure)
|
|
167
|
+
assertEquals(1, releases)
|
|
168
|
+
BotaDeviceSDKEncryptedUploadV2Materials.release(registration)
|
|
169
|
+
BotaDeviceSDKEncryptedUploadV2Materials.release(registration)
|
|
170
|
+
assertEquals(1, releases)
|
|
171
|
+
assertNull(BotaDeviceSDKEncryptedUploadV2Materials.remove(registration))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@Test
|
|
175
|
+
fun nonceLeaseEndsOnRejectOrResolveAndRejectsAnInFlightStaleRead() = runTest {
|
|
176
|
+
for (resolve in listOf(false, true)) {
|
|
177
|
+
val reading = CompletableDeferred<Unit>()
|
|
178
|
+
val nonceReady = CompletableDeferred<ByteArray>()
|
|
179
|
+
val client = TestAndroidRecordingClient(recording(), context(readNonce = {
|
|
180
|
+
reading.complete(Unit); nonceReady.await()
|
|
181
|
+
}))
|
|
182
|
+
val recordings = BotaDeviceSDKAndroidRecordings(client)
|
|
183
|
+
val requested = CompletableDeferred<BotaEncryptedUploadV2ProfileRequest>()
|
|
184
|
+
val operationId = "nonce-$resolve"
|
|
185
|
+
val sync = launch {
|
|
186
|
+
runCatching { recordings.syncEncryptedRecordingV2(connectedDevice(), encryptedRecording(), operationId,
|
|
187
|
+
{ requested.complete(it) }, {}) }
|
|
188
|
+
}
|
|
189
|
+
val request = withTimeout(5_000) { requested.await() }
|
|
190
|
+
val read = async { runCatching { BotaDeviceSDKEncryptedUploadV2Materials.readAuthNonce(operationId) } }
|
|
191
|
+
withTimeout(5_000) { reading.await() }
|
|
192
|
+
if (resolve) {
|
|
193
|
+
val selected = material()
|
|
194
|
+
recordings.resolveEncryptedUploadV2Profile(request.requestId, "encrypted_upload_v2",
|
|
195
|
+
selected.uploadSessionId.toString(), selected.ownerRevision, "v2_required",
|
|
196
|
+
BotaDeviceSDKEncryptedUploadV2Materials.register(selected))
|
|
197
|
+
} else recordings.rejectEncryptedUploadV2Profile(request.requestId, "application_material_rejected")
|
|
198
|
+
nonceReady.complete(ByteArray(16))
|
|
199
|
+
assertTrue(withTimeout(5_000) { read.await() }.isFailure)
|
|
200
|
+
assertTrue(runCatching { BotaDeviceSDKEncryptedUploadV2Materials.readAuthNonce(operationId) }.isFailure)
|
|
201
|
+
withTimeout(5_000) { sync.join() }
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@Test
|
|
206
|
+
fun registryNonceIsCopiedAndCannotSurviveAnOperationLeaseReplacement() = runTest {
|
|
207
|
+
val nonce = ByteArray(16) { 5 }
|
|
208
|
+
BotaDeviceSDKEncryptedUploadV2Materials.registerContext("lease", context(readNonce = { nonce }))
|
|
209
|
+
try {
|
|
210
|
+
val value = BotaDeviceSDKEncryptedUploadV2Materials.readAuthNonce("lease")
|
|
211
|
+
value.fill(1)
|
|
212
|
+
assertArrayEquals(ByteArray(16) { 5 }, nonce)
|
|
213
|
+
val readStarted = CompletableDeferred<Unit>()
|
|
214
|
+
val finishRead = CompletableDeferred<Unit>()
|
|
215
|
+
BotaDeviceSDKEncryptedUploadV2Materials.removeContext("lease")
|
|
216
|
+
BotaDeviceSDKEncryptedUploadV2Materials.registerContext("lease", context(readNonce = {
|
|
217
|
+
readStarted.complete(Unit); finishRead.await(); nonce
|
|
218
|
+
}))
|
|
219
|
+
val read = async { runCatching { BotaDeviceSDKEncryptedUploadV2Materials.readAuthNonce("lease") } }
|
|
220
|
+
withTimeout(5_000) { readStarted.await() }
|
|
221
|
+
BotaDeviceSDKEncryptedUploadV2Materials.removeContext("lease")
|
|
222
|
+
BotaDeviceSDKEncryptedUploadV2Materials.registerContext("lease", context(readNonce = { ByteArray(16) { 7 } }))
|
|
223
|
+
finishRead.complete(Unit)
|
|
224
|
+
assertTrue(withTimeout(5_000) { read.await() }.isFailure)
|
|
225
|
+
assertArrayEquals(ByteArray(16) { 7 }, BotaDeviceSDKEncryptedUploadV2Materials.readAuthNonce("lease"))
|
|
226
|
+
} finally { BotaDeviceSDKEncryptedUploadV2Materials.removeContext("lease") }
|
|
227
|
+
}
|
|
228
|
+
|
|
43
229
|
@Test
|
|
44
230
|
fun encryptedUploadV2UsesOneShotNativeMaterialAndSafeBridgeValues() = runTest {
|
|
45
231
|
val recordingUuid = "00112233-4455-6677-8899-aabbccddeeff"
|
|
@@ -98,14 +284,16 @@ class BotaDeviceSDKAndroidRecordingsTest {
|
|
|
98
284
|
"operation-1",
|
|
99
285
|
onProfileRequest = { request ->
|
|
100
286
|
requests += request
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
287
|
+
launch {
|
|
288
|
+
recordings.resolveEncryptedUploadV2Profile(
|
|
289
|
+
request.requestId,
|
|
290
|
+
"encrypted_upload_v2",
|
|
291
|
+
uploadSessionId.toString(),
|
|
292
|
+
2u,
|
|
293
|
+
"v2_required",
|
|
294
|
+
registrationId,
|
|
295
|
+
)
|
|
296
|
+
}
|
|
109
297
|
},
|
|
110
298
|
onProgress = progress::add,
|
|
111
299
|
)
|
|
@@ -176,7 +364,7 @@ class BotaDeviceSDKAndroidRecordingsTest {
|
|
|
176
364
|
isEncrypted = true,
|
|
177
365
|
)
|
|
178
366
|
val client = TestAndroidRecordingClient(recording)
|
|
179
|
-
val recordings = BotaDeviceSDKAndroidRecordings(client)
|
|
367
|
+
val recordings = BotaDeviceSDKAndroidRecordings(client, fileSize = { 48_036L })
|
|
180
368
|
val progress = mutableListOf<RecordingTransferProgress>()
|
|
181
369
|
|
|
182
370
|
assertEquals(listOf(recording), recordings.listRecordings(connected))
|
|
@@ -185,6 +373,7 @@ class BotaDeviceSDKAndroidRecordingsTest {
|
|
|
185
373
|
"/tmp/bota-recordings/recording-1.ogg",
|
|
186
374
|
true,
|
|
187
375
|
"5a".repeat(32),
|
|
376
|
+
48_036L,
|
|
188
377
|
),
|
|
189
378
|
recordings.syncRecording(connected, recording, "sink-1", progress::add),
|
|
190
379
|
)
|
|
@@ -280,6 +469,23 @@ class BotaDeviceSDKAndroidRecordingsTest {
|
|
|
280
469
|
isEncrypted = true,
|
|
281
470
|
)
|
|
282
471
|
|
|
472
|
+
private fun encryptedRecording() = EncryptedUploadV2Recording(
|
|
473
|
+
"00112233-4455-6677-8899-aabbccddeeff", 4u, ULong.MAX_VALUE, ByteArray(32) { 0x5a },
|
|
474
|
+
9007199254740993u, 9007199254740994u, 9007199254740995u, 3u,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
private fun context(recording: EncryptedUploadV2Recording = encryptedRecording(),
|
|
478
|
+
readNonce: suspend () -> ByteArray = { ByteArray(16) { 6 } },
|
|
479
|
+
) = EncryptedUploadV2ProviderContext(recording,
|
|
480
|
+
EncryptedUploadV2CapabilitySnapshot(ByteArray(24), ByteArray(32),
|
|
481
|
+
EncryptedUploadV2Capabilities(0x37fu, 580u, 580u, 128u, 8u, 1u, 8u)), null, readNonce)
|
|
482
|
+
|
|
483
|
+
private fun material(cancel: suspend () -> Unit = {}) = EncryptedUploadV2Material(
|
|
484
|
+
"native-material", encryptedRecording().uuid, UUID.fromString("10213243-5465-7687-98a9-bacbdcedfe0f"),
|
|
485
|
+
2u, EncryptedUploadV2SecurityPolicy.V2Required, ByteArray(408),
|
|
486
|
+
{ Request.Builder().url("https://native.invalid/stage").build() }, { _, _ -> }, {}, { ByteArray(336) }, cancel,
|
|
487
|
+
)
|
|
488
|
+
|
|
283
489
|
private class TestAndroidRecordingClient(
|
|
284
490
|
private val recording: DeviceRecording,
|
|
285
491
|
private val encryptedUploadV2Context: EncryptedUploadV2ProviderContext? = null,
|
|
@@ -288,10 +494,19 @@ class BotaDeviceSDKAndroidRecordingsTest {
|
|
|
288
494
|
var cancelled = false
|
|
289
495
|
val sinkIds = mutableListOf<String>()
|
|
290
496
|
var encryptedMaterialId: String? = null
|
|
497
|
+
val encryptedOperationIds = mutableListOf<UUID>()
|
|
498
|
+
val cancelledEncryptedIds = mutableListOf<UUID>()
|
|
499
|
+
var beforeEncrypted: suspend () -> Unit = {}
|
|
500
|
+
var encryptedFinally: suspend () -> Unit = {}
|
|
501
|
+
var cancelEncrypted: suspend () -> Unit = {}
|
|
291
502
|
|
|
292
503
|
override suspend fun listRecordings(device: ConnectedDevice): List<DeviceRecording> =
|
|
293
504
|
listOf(recording)
|
|
294
505
|
|
|
506
|
+
override suspend fun listPendingRecordings(device: ConnectedDevice): List<dev.bota.sdk.PendingRecording> =
|
|
507
|
+
listOfNotNull(dev.bota.sdk.PendingRecording.Legacy(recording),
|
|
508
|
+
encryptedUploadV2Context?.let { dev.bota.sdk.PendingRecording.EncryptedV2(it.recording) })
|
|
509
|
+
|
|
295
510
|
override fun syncRecording(
|
|
296
511
|
device: ConnectedDevice,
|
|
297
512
|
recording: DeviceRecording,
|
|
@@ -307,11 +522,22 @@ class BotaDeviceSDKAndroidRecordingsTest {
|
|
|
307
522
|
override suspend fun syncEncryptedRecordingV2(
|
|
308
523
|
device: ConnectedDevice,
|
|
309
524
|
recording: EncryptedUploadV2Recording,
|
|
525
|
+
operationId: UUID,
|
|
310
526
|
provider: EncryptedUploadV2ProfileProvider,
|
|
311
527
|
) {
|
|
528
|
+
encryptedOperationIds += operationId
|
|
312
529
|
encryptedUploadV2Error?.let { throw it }
|
|
313
530
|
val context = requireNotNull(encryptedUploadV2Context)
|
|
314
|
-
|
|
531
|
+
try {
|
|
532
|
+
beforeEncrypted()
|
|
533
|
+
encryptedMaterialId = provider.select(context).materialId
|
|
534
|
+
} finally { encryptedFinally() }
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
override suspend fun cancelEncryptedRecordingV2(operationId: UUID) {
|
|
538
|
+
cancelledEncryptedIds += operationId
|
|
539
|
+
cancelEncrypted()
|
|
540
|
+
cancelled = true
|
|
315
541
|
}
|
|
316
542
|
|
|
317
543
|
override suspend fun confirmRecording(device: ConnectedDevice, recordingUuid: String) = Unit
|