Skip to content

Commit 377c1b5

Browse files
committed
Move some PutBytes helper funcs from cobble android
1 parent fc224dd commit 377c1b5

File tree

7 files changed

+152
-7
lines changed

7 files changed

+152
-7
lines changed

src/androidMain/kotlin/util/DataBuffer.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ actual class DataBuffer {
2626
actual val readPosition: Int
2727
get() = actualBuf.position()
2828

29+
actual val remaining: Int
30+
get() = actualBuf.remaining()
31+
2932
actual fun putUShort(short: UShort) {
3033
actualBuf.putShort(short.toShort())
3134
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.rebble.libpebblecommon.metadata
2+
import kotlinx.serialization.Serializable
3+
import kotlinx.serialization.Serializer
4+
import kotlinx.serialization.KSerializer
5+
import kotlinx.serialization.encoding.Decoder
6+
import kotlinx.serialization.encoding.Encoder
7+
8+
@Serializable
9+
data class StringOrBoolean(val value: Boolean) {
10+
@Serializer(forClass = StringOrBoolean::class)
11+
companion object : KSerializer<StringOrBoolean> {
12+
override fun serialize(encoder: Encoder, value: StringOrBoolean) {
13+
encoder.encodeString(if (value.value) "true" else "false")
14+
}
15+
16+
override fun deserialize(decoder: Decoder): StringOrBoolean = try {
17+
StringOrBoolean(decoder.decodeString() == "true")
18+
} catch (e: Error) {
19+
StringOrBoolean(decoder.decodeBoolean())
20+
}
21+
}
22+
}

src/commonMain/kotlin/io/rebble/libpebblecommon/services/PutBytesService.kt

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package io.rebble.libpebblecommon.services
22

33
import io.rebble.libpebblecommon.ProtocolHandler
4-
import io.rebble.libpebblecommon.packets.PutBytesOutgoingPacket
5-
import io.rebble.libpebblecommon.packets.PutBytesResponse
4+
import io.rebble.libpebblecommon.metadata.WatchType
5+
import io.rebble.libpebblecommon.metadata.pbw.manifest.PbwBlob
6+
import io.rebble.libpebblecommon.packets.*
67
import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
78
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint
9+
import io.rebble.libpebblecommon.util.Crc32Calculator
10+
import io.rebble.libpebblecommon.util.DataBuffer
11+
import io.rebble.libpebblecommon.util.getPutBytesMaximumDataSize
812
import kotlinx.coroutines.channels.Channel
13+
import kotlinx.coroutines.withTimeout
914

1015
class PutBytesService(private val protocolHandler: ProtocolHandler) : ProtocolService {
1116
val receivedMessages = Channel<PutBytesResponse>(Channel.BUFFERED)
@@ -15,6 +20,10 @@ class PutBytesService(private val protocolHandler: ProtocolHandler) : ProtocolSe
1520
}
1621

1722
suspend fun send(packet: PutBytesOutgoingPacket) {
23+
if (packet is PutBytesAbort) {
24+
lastCookie = null
25+
}
26+
1827
protocolHandler.send(packet)
1928
}
2029

@@ -26,4 +35,109 @@ class PutBytesService(private val protocolHandler: ProtocolHandler) : ProtocolSe
2635
receivedMessages.trySend(packet)
2736
}
2837

38+
var lastCookie: UInt? = null
39+
40+
class PutBytesException(val cookie: UInt?, message: String, cause: Throwable? = null) : Error(message, cause);
41+
42+
/**
43+
* Inits a PutBytes session on the device and sends an app, leaves aborting to the caller
44+
*/
45+
@Throws(PutBytesException::class, IllegalStateException::class)
46+
suspend fun sendAppPart(
47+
appId: UInt,
48+
blob: ByteArray,
49+
watchType: WatchType,
50+
watchVersion: WatchVersion.WatchVersionResponse,
51+
manifestEntry: PbwBlob,
52+
type: ObjectType
53+
) {
54+
println("Send app part $watchType $appId $manifestEntry $type ${type.value}")
55+
send(
56+
PutBytesAppInit(manifestEntry.size.toUInt(), type, appId)
57+
)
58+
59+
val cookie = awaitCookieAndPutByteArray(
60+
blob,
61+
manifestEntry.crc,
62+
watchVersion
63+
)
64+
65+
println("Sending install")
66+
67+
send(
68+
PutBytesInstall(cookie)
69+
)
70+
awaitAck()
71+
72+
println("Install complete")
73+
}
74+
75+
suspend fun awaitCookieAndPutByteArray(
76+
byteArray: ByteArray,
77+
expectedCrc: Long?,
78+
watchVersion: WatchVersion.WatchVersionResponse
79+
): UInt {
80+
try {
81+
val cookie = awaitAck().cookie.get()
82+
lastCookie = cookie
83+
84+
val maxDataSize = getPutBytesMaximumDataSize(watchVersion)
85+
val buffer = DataBuffer(byteArray.asUByteArray())
86+
val crcCalculator = Crc32Calculator()
87+
88+
var totalBytes = 0
89+
while (true) {
90+
val dataToRead = maxDataSize.coerceAtMost(buffer.remaining)
91+
if (dataToRead <= 0) {
92+
break
93+
}
94+
val payload = buffer.getBytes(dataToRead)
95+
96+
crcCalculator.addBytes(payload)
97+
98+
send(PutBytesPut(cookie, payload))
99+
awaitAck()
100+
totalBytes += dataToRead
101+
}
102+
println("$totalBytes/${byteArray.size}")
103+
val calculatedCrc = crcCalculator.finalize()
104+
if (expectedCrc != null && calculatedCrc != expectedCrc.toUInt()) {
105+
throw IllegalStateException(
106+
"Sending fail: Crc mismatch ($calculatedCrc != $expectedCrc)"
107+
)
108+
}
109+
110+
println("Sending commit")
111+
send(
112+
PutBytesCommit(cookie, calculatedCrc)
113+
)
114+
awaitAck()
115+
return cookie
116+
} catch (e: Error) {
117+
throw PutBytesException(lastCookie, "awaitCookieAndPutByteArray failed: ${e.message}", e)
118+
}
119+
}
120+
121+
private suspend fun getResponse(): PutBytesResponse {
122+
return withTimeout(20_000) {
123+
val iterator = receivedMessages.iterator()
124+
if (!iterator.hasNext()) {
125+
throw IllegalStateException("Received messages channel is closed")
126+
}
127+
128+
iterator.next()
129+
}
130+
}
131+
132+
private suspend fun awaitAck(): PutBytesResponse {
133+
val response = getResponse()
134+
135+
val result = response.result.get()
136+
if (result != PutBytesResult.ACK.value) {
137+
throw PutBytesException(lastCookie, "Watch responded with NACK ($result). Aborting transfer")
138+
}
139+
140+
return response
141+
}
142+
29143
}

src/commonMain/kotlin/io/rebble/libpebblecommon/util/DataBuffer.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,9 @@ expect class DataBuffer {
4646
* Current position in the buffer
4747
*/
4848
val readPosition: Int
49+
50+
/**
51+
* Remaining bytes in the buffer
52+
*/
53+
val remaining: Int
4954
}

src/iosMain/kotlin/util/DataBuffer.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ actual class DataBuffer {
1313
actual val length: Int
1414
get() = actualBuf.length.toInt()
1515

16+
actual val remaining: Int
17+
get() = actualBuf.length().toInt()-_readPosition
18+
1619
private var _readPosition: Int = 0
1720

1821
/**

src/iosMain/kotlin/util/UtilFunctions.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
package io.rebble.libpebblecommon.util
2-
32
import kotlinx.cinterop.*
4-
import platform.Foundation.NSArray
53
import platform.Foundation.NSData
64
import platform.Foundation.create
7-
import platform.darwin.UInt8
8-
import platform.darwin.UInt8Var
95
import platform.posix.memcpy
106
import platform.posix.size_t
11-
import kotlin.native.internal.NativePtr
127

138
actual fun runBlocking(block: suspend () -> Unit) = kotlinx.coroutines.runBlocking{block()}
149

src/jvmMain/kotlin/util/DataBuffer.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ actual class DataBuffer {
2626
actual val readPosition: Int
2727
get() = actualBuf.position()
2828

29+
actual val remaining: Int
30+
get() = actualBuf.remaining()
31+
2932

3033
actual fun putUShort(short: UShort) {
3134
actualBuf.putShort(short.toShort())

0 commit comments

Comments
 (0)