feat(device-initialization, core): Plan-USB-Backup hinzugefügt, BackupService implementiert und UI-Export-Button ergänzt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
+23
@@ -0,0 +1,23 @@
|
||||
package at.mocode.frontend.core.network.backup
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class BackupPayload(
|
||||
val timestamp: Long,
|
||||
val deviceName: String,
|
||||
val data: String,
|
||||
val checksum: String
|
||||
)
|
||||
|
||||
interface BackupService {
|
||||
/**
|
||||
* Schreibt Daten verschlüsselt in das Backup-Verzeichnis.
|
||||
*/
|
||||
fun exportDelta(data: String, targetPath: String, sharedKey: String): Result<String>
|
||||
|
||||
/**
|
||||
* Liest Daten aus einer verschlüsselten Datei ein.
|
||||
*/
|
||||
fun importDelta(filePath: String, sharedKey: String): Result<String>
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@ package at.mocode.frontend.core.network.discovery
|
||||
import org.koin.core.module.Module
|
||||
|
||||
/**
|
||||
* Erwartetes Koin-Modul für die Netzwerk-Discovery.
|
||||
* Erwartetes Koin-Modul für die Netzwerk-Discovery und Backup.
|
||||
* Plattform-spezifische Implementierungen (JVM mit JmDNS, JS/Wasm evtl. No-op)
|
||||
* müssen hier injiziert werden.
|
||||
*/
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package at.mocode.frontend.core.network.backup
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class FileBackupService(private val deviceName: String) : BackupService {
|
||||
private val json = Json { prettyPrint = true }
|
||||
|
||||
override fun exportDelta(data: String, targetPath: String, sharedKey: String): Result<String> {
|
||||
return try {
|
||||
val timestamp = System.currentTimeMillis()
|
||||
val checksum = calculateChecksum(data)
|
||||
val payload = BackupPayload(timestamp, deviceName, data, checksum)
|
||||
val jsonContent = json.encodeToString(payload)
|
||||
|
||||
val encryptedData = encrypt(jsonContent, sharedKey)
|
||||
|
||||
val dir = File(targetPath)
|
||||
if (!dir.exists()) dir.mkdirs()
|
||||
|
||||
val fileName = "delta_${timestamp}_${deviceName}.msbackup"
|
||||
val file = File(dir, fileName)
|
||||
file.writeText(encryptedData)
|
||||
|
||||
println("[Plan-USB] Export erfolgreich: ${file.absolutePath}")
|
||||
Result.success(file.absoluteName)
|
||||
} catch (e: Exception) {
|
||||
println("[Plan-USB] Export fehlgeschlagen: ${e.message}")
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun importDelta(filePath: String, sharedKey: String): Result<String> {
|
||||
return try {
|
||||
val file = File(filePath)
|
||||
val encryptedData = file.readText()
|
||||
val jsonContent = decrypt(encryptedData, sharedKey)
|
||||
val payload = json.decodeFromString<BackupPayload>(jsonContent)
|
||||
|
||||
if (calculateChecksum(payload.data) != payload.checksum) {
|
||||
throw Exception("Checksummenfehler: Daten wurden möglicherweise manipuliert.")
|
||||
}
|
||||
|
||||
println("[Plan-USB] Import erfolgreich von ${payload.deviceName}")
|
||||
Result.success(payload.data)
|
||||
} catch (e: Exception) {
|
||||
println("[Plan-USB] Import fehlgeschlagen: ${e.message}")
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateChecksum(data: String): String {
|
||||
val bytes = MessageDigest.getInstance("SHA-256").digest(data.toByteArray())
|
||||
return bytes.joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
|
||||
private fun encrypt(data: String, key: String): String {
|
||||
val secretKey = generateKey(key)
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
val iv = IvParameterSpec(ByteArray(16)) // Vereinfacht für PoC
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv)
|
||||
val encrypted = cipher.doFinal(data.toByteArray())
|
||||
return Base64.getEncoder().encodeToString(encrypted)
|
||||
}
|
||||
|
||||
private fun decrypt(encrypted: String, key: String): String {
|
||||
val secretKey = generateKey(key)
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
val iv = IvParameterSpec(ByteArray(16))
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv)
|
||||
val decrypted = cipher.doFinal(Base64.getDecoder().decode(encrypted))
|
||||
return String(decrypted)
|
||||
}
|
||||
|
||||
private fun generateKey(key: String): SecretKeySpec {
|
||||
val sha = MessageDigest.getInstance("SHA-256")
|
||||
val keyBytes = sha.digest(key.toByteArray()).copyOf(16) // AES-128 für Kompatibilität
|
||||
return SecretKeySpec(keyBytes, "AES")
|
||||
}
|
||||
}
|
||||
|
||||
private val File.absoluteName: String get() = this.name
|
||||
+3
@@ -1,5 +1,7 @@
|
||||
package at.mocode.frontend.core.network.discovery
|
||||
|
||||
import at.mocode.frontend.core.network.backup.BackupService
|
||||
import at.mocode.frontend.core.network.backup.FileBackupService
|
||||
import org.koin.core.module.Module
|
||||
import org.koin.dsl.module
|
||||
|
||||
@@ -8,4 +10,5 @@ import org.koin.dsl.module
|
||||
*/
|
||||
actual val discoveryModule: Module = module {
|
||||
single<NetworkDiscoveryService> { JmDnsDiscoveryService() }
|
||||
single<BackupService> { (deviceName: String) -> FileBackupService(deviceName) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user