Skip to content

Commit

Permalink
add app install packets
Browse files Browse the repository at this point in the history
  • Loading branch information
matejdro committed Mar 17, 2021
1 parent c349a6b commit 39bbc54
Show file tree
Hide file tree
Showing 7 changed files with 334 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package io.rebble.libpebblecommon.packets

import io.rebble.libpebblecommon.protocolhelpers.PacketRegistry
import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint
import io.rebble.libpebblecommon.structmapper.SUByte
import io.rebble.libpebblecommon.structmapper.SUInt
import io.rebble.libpebblecommon.structmapper.SUUID

sealed class AppFetchIncomingPacket() : PebblePacket(ProtocolEndpoint.APP_FETCH) {
/**
* Request command. See [AppFetchRequestCommand].
*/
val command = SUByte(m)

}

sealed class AppFetchOutgoingPacket(command: AppFetchRequestCommand) :
PebblePacket(ProtocolEndpoint.APP_FETCH) {
/**
* Request command. See [AppFetchRequestCommand].
*/
val command = SUByte(m, command.value)

}


/**
* Packet sent from the watch when user opens an app that is not in the watch storage.
*/
class AppFetchRequest : AppFetchIncomingPacket() {

/**
* UUID of the app to request
*/
val uuid = SUUID(m)

/**
* ID of the app bank. Use in the [PutBytesAppInit] packet to identify this app install.
*/
val appId = SUInt(m, endianness = '<')
}

/**
* Packet sent from the watch when user opens an app that is not in the watch storage.
*/
class AppFetchResponse(
status: AppFetchResponseStatus
) : AppFetchOutgoingPacket(AppFetchRequestCommand.FETCH_APP) {
/**
* Response status
*/
val status = SUByte(m, status.value)

}

enum class AppFetchRequestCommand(val value: UByte) {
FETCH_APP(0x01u)
}

enum class AppFetchResponseStatus(val value: UByte) {
/**
* Sent right before starting to send PutBytes data
*/
START(0x01u),

/**
* Sent when phone PutBytes is already busy sending something else
*/
BUSY(0x02u),

/**
* Sent when UUID that watch sent is not in the locker
*/
INVALID_UUID(0x03u),

/**
* Sent when there is generic data sending error (such as failure to read the local pbw file)
*/
NO_DATA(0x01u),
}


fun appFetchIncomingPacketsRegister() {
PacketRegistry.register(
ProtocolEndpoint.APP_FETCH,
AppFetchRequestCommand.FETCH_APP.value
) { AppFetchRequest() }
}
137 changes: 137 additions & 0 deletions src/commonMain/kotlin/io/rebble/libpebblecommon/packets/PutBytes.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package io.rebble.libpebblecommon.packets

import io.rebble.libpebblecommon.protocolhelpers.PacketRegistry
import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint
import io.rebble.libpebblecommon.structmapper.SBytes
import io.rebble.libpebblecommon.structmapper.SNullTerminatedString
import io.rebble.libpebblecommon.structmapper.SUByte
import io.rebble.libpebblecommon.structmapper.SUInt

sealed class PutBytesOutgoingPacket(command: PutBytesCommand) :
PebblePacket(ProtocolEndpoint.PUT_BYTES) {
/**
* Request command. See [PutBytesCommand].
*/
val command = SUByte(m, command.value)

}

class PutBytesResponse : PebblePacket(ProtocolEndpoint.PUT_BYTES) {

/**
* See [PutBytesResult]
*/
val result = SUByte(m)

/**
* Cookie to send to all other put bytes requests
*/
val cookie = SUInt(m)
}

/**
* Send to init non-app related file transfer
*/
class PutBytesInit(
objectSize: UInt,
objectType: ObjectType,
bank: UByte,
filename: String
) : PutBytesOutgoingPacket(PutBytesCommand.INIT) {
val objectSize = SUInt(m, objectSize)
val objectType = SUByte(m, objectType.value)
val bank = SUByte(m, bank)
val filename = SNullTerminatedString(m, filename)
}

/**
* Send to init app-specific file transfer.
*/
class PutBytesAppInit(
objectSize: UInt,
objectType: ObjectType,
appId: UInt
) : PutBytesOutgoingPacket(PutBytesCommand.INIT) {
val objectSize = SUInt(m, objectSize)

// Object type in app init packet must have 8th bit set (?)
val objectType = SUByte(m, objectType.value or (1u shl 7).toUByte())
val appId = SUInt(m, appId)
}

/**
* Send file data to the watch. After every put you have to wait for response from the watch.
*/
class PutBytesPut(
cookie: UInt,
payload: UByteArray
) : PutBytesOutgoingPacket(PutBytesCommand.PUT) {
val cookie = SUInt(m, cookie)
val payloadSize = SUInt(m, payload.size.toUInt())
val payload = SBytes(m, payload.size, payload)
}

/**
* Sent when current file transfer is complete. [objectCrc] is the CRC32 hash of the sent payload.
*/
class PutBytesCommit(
cookie: UInt,
objectCrc: UInt
) : PutBytesOutgoingPacket(PutBytesCommand.COMMIT) {
val cookie = SUInt(m, cookie)
val objectCrc = SUInt(m, objectCrc)
}

/**
* Send when there was an error during transfer and transfer cannot complete.
*/
class PutBytesAbort(
cookie: UInt
) : PutBytesOutgoingPacket(PutBytesCommand.ABORT) {
val cookie = SUInt(m, cookie)
}

/**
* Send after app-related file was commited to complete install sequence
*/
class PutBytesInstall(
cookie: UInt
) : PutBytesOutgoingPacket(PutBytesCommand.INSTALL) {
val cookie = SUInt(m, cookie)
}

enum class PutBytesCommand(val value: UByte) {
INIT(0x01u),
PUT(0x02u),
COMMIT(0x03u),
ABORT(0x04u),
INSTALL(0x05u)
}

enum class PutBytesResult(val value: UByte) {
ACK(0x01u),
NACK(0x02u)
}

enum class ObjectType(val value: UByte) {
FIRMWARE(0x01u),
RECOVERY(0x02u),
SYSTEM_RESOURCE(0x03u),
APP_RESOURCE(0x04u),
APP_EXECUTABLE(0x05u),
FILE(0x06u),
WORKER(0x07u)
}

fun putBytesIncomingPacketsRegister() {
PacketRegistry.register(
ProtocolEndpoint.PUT_BYTES,
PutBytesResult.ACK.value,
) { PutBytesResponse() }

PacketRegistry.register(
ProtocolEndpoint.PUT_BYTES,
PutBytesResult.NACK.value,
) { PutBytesResponse() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ object PacketRegistry {
appmessagePacketsRegister()
appRunStatePacketsRegister()
musicPacketsRegister()
appFetchIncomingPacketsRegister()
putBytesIncomingPacketsRegister()
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.rebble.libpebblecommon.services

import io.rebble.libpebblecommon.ProtocolHandler
import io.rebble.libpebblecommon.packets.AppFetchIncomingPacket
import io.rebble.libpebblecommon.packets.AppFetchOutgoingPacket
import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint
import kotlinx.coroutines.channels.Channel

class AppFetchService(private val protocolHandler: ProtocolHandler) : ProtocolService {
val receivedMessages = Channel<AppFetchIncomingPacket>(Channel.BUFFERED)

init {
protocolHandler.registerReceiveCallback(ProtocolEndpoint.APP_FETCH, this::receive)
}

suspend fun send(packet: AppFetchOutgoingPacket) {
protocolHandler.send(packet)
}

fun receive(packet: PebblePacket) {
if (packet !is AppFetchIncomingPacket) {
throw IllegalStateException("Received invalid packet type: $packet")
}

receivedMessages.offer(packet)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.rebble.libpebblecommon.services

import io.rebble.libpebblecommon.ProtocolHandler
import io.rebble.libpebblecommon.packets.PutBytesOutgoingPacket
import io.rebble.libpebblecommon.packets.PutBytesResponse
import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint
import kotlinx.coroutines.channels.Channel

class PutBytesService(private val protocolHandler: ProtocolHandler) : ProtocolService {
val receivedMessages = Channel<PutBytesResponse>(Channel.BUFFERED)

init {
protocolHandler.registerReceiveCallback(ProtocolEndpoint.PUT_BYTES, this::receive)
}

suspend fun send(packet: PutBytesOutgoingPacket) {
protocolHandler.send(packet)
}

fun receive(packet: PebblePacket) {
if (packet !is PutBytesResponse) {
throw IllegalStateException("Received invalid packet type: $packet")
}

receivedMessages.offer(packet)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,24 @@ class SFixedString(mapper: StructMapper, size: Int, default: String = "") :
}, mapper, size, default
)

/**
* Upload-only type that writes String as unbound null-terminated byte array.
*/
class SNullTerminatedString(mapper: StructMapper, default: String = "") :
StructElement<String>(
{ buf, el ->
val bytes = el.get().encodeToByteArray()

buf.putBytes(
bytes.toUByteArray()
)
buf.putUByte(0u)
},
{ buf, el ->
throw UnsupportedOperationException("SNullTerminatedString is upload-only")
}, mapper, 0, default
)

/**
* Represents arbitrary bytes in a struct
* @param length the number of bytes, when serializing this is used to pad/truncate the provided value to ensure it's 'length' bytes long (-1 to disable this)
Expand Down Expand Up @@ -358,7 +376,7 @@ class SFixedList<T : Mappable>(
}

override val size: Int
get() = list.fold(0, {t,el -> t+el.size})
get() = list.fold(0, { t, el -> t + el.size })

/**
* Link the count of this element to the value of another struct element. Count will
Expand Down
29 changes: 29 additions & 0 deletions src/commonMain/kotlin/io/rebble/libpebblecommon/util/PacketSize.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.rebble.libpebblecommon.util

import io.rebble.libpebblecommon.packets.ProtocolCapsFlag
import io.rebble.libpebblecommon.packets.WatchVersion
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint

fun getMaxPebblePacketPayloadSize(
endpoint: ProtocolEndpoint,
watchVersion: WatchVersion.WatchVersionResponse?
): Int {
if (endpoint != ProtocolEndpoint.APP_MESSAGE) {
return STANDARD_MAX_PEBBLE_PACKET_SIZE
}

val capabilities = watchVersion?.capabilities?.let { ProtocolCapsFlag.fromFlags(it.get()) }

return if (capabilities?.contains(ProtocolCapsFlag.Supports8kAppMessage) == true) {
8222
} else {
STANDARD_MAX_PEBBLE_PACKET_SIZE
}
}

fun getPutBytesMaximumDataSize(watchVersion: WatchVersion.WatchVersionResponse?): Int {
// 4 bytes get used for the cookie
return getMaxPebblePacketPayloadSize(ProtocolEndpoint.PUT_BYTES, watchVersion) - 4
}

val STANDARD_MAX_PEBBLE_PACKET_SIZE = 2048

0 comments on commit 39bbc54

Please sign in to comment.