11package io.rebble.libpebblecommon.services
22
33import 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.*
67import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
78import 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
812import kotlinx.coroutines.channels.Channel
13+ import kotlinx.coroutines.withTimeout
914
1015class 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}
0 commit comments