mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Compare commits
3 Commits
bscotch/pr
...
feat_flash
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e89e64e27 | ||
|
|
bd6d9b693a | ||
|
|
57d74747c2 |
@@ -11,6 +11,7 @@ import dev.slimevr.serial.SerialPort
|
||||
import dev.slimevr.tracking.trackers.Tracker
|
||||
import dev.slimevr.tracking.trackers.TrackerStatus
|
||||
import dev.slimevr.tracking.trackers.TrackerStatusListener
|
||||
import dev.slimevr.tracking.trackers.udp.MCUType
|
||||
import dev.slimevr.tracking.trackers.udp.UDPDevice
|
||||
import io.eiren.util.logging.LogManager
|
||||
import kotlinx.coroutines.*
|
||||
@@ -101,11 +102,27 @@ class FirmwareUpdateHandler(private val server: VRServer) :
|
||||
)
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// - Use the Firmware Builder to get the expected MCU
|
||||
// It would be wrong to assume that the Target MCU is the correct one,
|
||||
// just because the device is listening on the correct port.
|
||||
// The Upload protocol does not verify the compatibility of the firmware with the MCU.
|
||||
|
||||
val port = when (udpDevice.mcuType) {
|
||||
MCUType.ESP8266 -> 8266
|
||||
MCUType.ESP32, MCUType.ESP32_C3 -> 3232
|
||||
else -> error("MCU-Typ: ${udpDevice.mcuType} not supported for OTA updates")
|
||||
}
|
||||
|
||||
LogManager.info("[FirmwareUpdateHandler] Starting OTA update for device ${deviceId.id} at ${udpDevice.ipAddress.hostAddress}:$port and MCU ${udpDevice.mcuType}")
|
||||
|
||||
val task = OTAUpdateTask(
|
||||
part.firmware,
|
||||
deviceId,
|
||||
udpDevice.ipAddress,
|
||||
::onStatusChange,
|
||||
port,
|
||||
)
|
||||
c.invokeOnCancellation {
|
||||
task.cancel()
|
||||
@@ -156,6 +173,15 @@ class FirmwareUpdateHandler(private val server: VRServer) :
|
||||
flasher.addBin(part.firmware, part.offset.toInt())
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// - Check if FW is able to use flashmode
|
||||
// - Add check if the flashmode was successfully set to surpress the request
|
||||
// for manual flashmode setting prompt in gui
|
||||
|
||||
server.serialHandler.openSerial(deviceId.id, false)
|
||||
server.serialHandler.write("SET FLASHMODE\r\n".toByteArray())
|
||||
server.serialHandler.closeSerial()
|
||||
|
||||
flasher.addProgressListener(object : FlashingProgressListener {
|
||||
override fun progress(progress: Float) {
|
||||
onStatusChange(
|
||||
|
||||
@@ -14,6 +14,8 @@ import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
import javax.crypto.SecretKeyFactory
|
||||
import javax.crypto.spec.PBEKeySpec
|
||||
import kotlin.math.min
|
||||
|
||||
class OTAUpdateTask(
|
||||
@@ -21,8 +23,9 @@ class OTAUpdateTask(
|
||||
private val deviceId: UpdateDeviceId<Int>,
|
||||
private val deviceIp: InetAddress,
|
||||
private val statusCallback: Consumer<UpdateStatusEvent<Int>>,
|
||||
private val port: Int = 8266,
|
||||
) {
|
||||
private val receiveBuffer: ByteArray = ByteArray(38)
|
||||
private val receiveBuffer: ByteArray = ByteArray(69)
|
||||
var socketServer: ServerSocket? = null
|
||||
var uploadSocket: Socket? = null
|
||||
var authSocket: DatagramSocket? = null
|
||||
@@ -40,17 +43,35 @@ class OTAUpdateTask(
|
||||
return md5str.toString()
|
||||
}
|
||||
|
||||
@Throws(NoSuchAlgorithmException::class)
|
||||
private fun bytesToSha256(bytes: ByteArray): String {
|
||||
val sha256 = MessageDigest.getInstance("SHA-256")
|
||||
sha256.update(bytes)
|
||||
val digest = sha256.digest()
|
||||
val sha256str = StringBuilder()
|
||||
for (b in digest) {
|
||||
sha256str.append(String.format("%02x", b))
|
||||
}
|
||||
return sha256str.toString()
|
||||
}
|
||||
|
||||
fun pbkdf2Hmac(password: String, salt: ByteArray, iterations: Int, keyLength: Int): ByteArray {
|
||||
val spec = PBEKeySpec(password.toCharArray(), salt, iterations, keyLength * 8)
|
||||
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
||||
return factory.generateSecret(spec).encoded
|
||||
}
|
||||
|
||||
private fun authenticate(localPort: Int): Boolean {
|
||||
try {
|
||||
DatagramSocket().use { socket ->
|
||||
authSocket = socket
|
||||
statusCallback.accept(UpdateStatusEvent(deviceId, FirmwareUpdateStatus.AUTHENTICATING))
|
||||
LogManager.info("[OTAUpdate] Sending OTA invitation to: $deviceIp")
|
||||
LogManager.info("[OTAUpdate] Sending OTA invitation to: $deviceIp:$port")
|
||||
|
||||
val fileMd5 = bytesToMd5(firmware)
|
||||
val message = "$FLASH $localPort ${firmware.size} $fileMd5\n"
|
||||
|
||||
socket.send(DatagramPacket(message.toByteArray(), message.length, deviceIp, PORT))
|
||||
socket.send(DatagramPacket(message.toByteArray(), message.length, deviceIp, port))
|
||||
socket.soTimeout = 10000
|
||||
|
||||
val authPacket = DatagramPacket(receiveBuffer, receiveBuffer.size)
|
||||
@@ -68,13 +89,26 @@ class OTAUpdateTask(
|
||||
if (args.size != 2 || args[0] != "AUTH") return false
|
||||
|
||||
LogManager.info("[OTAUpdate] Authenticating...")
|
||||
var payload = ""
|
||||
var signature = ""
|
||||
|
||||
val authToken = args[1]
|
||||
val signature = bytesToMd5(UUID.randomUUID().toString().toByteArray())
|
||||
val hashedPassword = bytesToMd5(PASSWORD.toByteArray())
|
||||
val resultText = "$hashedPassword:$authToken:$signature"
|
||||
val payload = bytesToMd5(resultText.toByteArray())
|
||||
|
||||
if (authToken.length == 32) {
|
||||
signature =
|
||||
bytesToMd5(UUID.randomUUID().toString().toByteArray())
|
||||
val hashedPassword = bytesToMd5(PASSWORD.toByteArray())
|
||||
val resultText = "$hashedPassword:$authToken:$signature"
|
||||
payload = bytesToMd5(resultText.toByteArray())
|
||||
} else if (authToken.length == 64) {
|
||||
signature =
|
||||
bytesToSha256(UUID.randomUUID().toString().toByteArray())
|
||||
val salt = "$authToken:$signature"
|
||||
val hashedPassword = bytesToSha256(PASSWORD.toByteArray())
|
||||
val derivedkey = pbkdf2Hmac(hashedPassword, salt.toByteArray(), 10000, 32)
|
||||
val derivedkeyHex = derivedkey.joinToString("") { "%02x".format(it) }
|
||||
val challenge = "$derivedkeyHex:$authToken:$signature"
|
||||
payload = bytesToSha256(challenge.toByteArray())
|
||||
}
|
||||
val authMessage = "$AUTH $signature $payload\n"
|
||||
|
||||
socket.soTimeout = 10000
|
||||
@@ -83,7 +117,7 @@ class OTAUpdateTask(
|
||||
authMessage.toByteArray(),
|
||||
authMessage.length,
|
||||
deviceIp,
|
||||
PORT,
|
||||
port,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -198,7 +232,6 @@ class OTAUpdateTask(
|
||||
|
||||
companion object {
|
||||
private const val FLASH = 0
|
||||
private const val PORT = 8266
|
||||
private const val PASSWORD = "SlimeVR-OTA"
|
||||
private const val AUTH = 200
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ class DesktopSerialHandler :
|
||||
}
|
||||
|
||||
override fun write(buff: ByteArray) {
|
||||
LogManager.info("[SerialHandler] WRITING $buff")
|
||||
LogManager.info("[SerialHandler] WRITING ${buff.toString(Charsets.UTF_8)}")
|
||||
currentPort?.outputStream?.write(buff)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user