package com.peachpasswords.sync import android.content.Context import java.util.UUID private const val DEVICE_ID_KEY = "peach_chirp_device_id" private const val DEVICE_NAME_KEY = "peach_chirp_device_name" private const val PREFS_FILE = "peach_chirp_identity" data class LocalChirpIdentity( val deviceId: String, val deviceName: String, val deviceType: DeviceType, val longTermPublicKeyHex: String, val signingPublicKeyHex: String, val wsPort: Int? = null, val relayUrl: String = Rendezvous.DEFAULT_RELAY_URL, /** * This device's signed Pro license blob (`PPK-…`), set only when Pro. Sent * in the outbound PAIR_IDENTITY so the peer can activate it after SAS — one * Pro device grants Pro to the other when pairing. Either direction. */ val license: String? = null, ) fun getOrCreateDeviceId(context: Context): String { val prefs = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE) val current = prefs.getString(DEVICE_ID_KEY, null) if (!current.isNullOrEmpty()) return current val fresh = UUID.randomUUID().toString() prefs.edit().putString(DEVICE_ID_KEY, fresh).apply() return fresh } fun getDeviceName(context: Context): String { val prefs = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE) return prefs.getString(DEVICE_NAME_KEY, null).orEmpty().ifBlank { defaultDeviceName() } } fun setDeviceName(context: Context, name: String) { val trimmed = name.trim() require(trimmed.isNotEmpty()) { "device name must not be empty" } require(trimmed.length <= 64) { "device name must be 64 chars or fewer" } context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE) .edit() .putString(DEVICE_NAME_KEY, trimmed) .apply() } fun buildLocalIdentity(context: Context, masterKey: ByteArray): LocalChirpIdentity { val deviceId = getOrCreateDeviceId(context) val deviceName = getDeviceName(context) val kp = deriveSyncIdentityKeypair(masterKey, deviceId) val signing = deriveSyncSigningKeypair(masterKey, deviceId) return LocalChirpIdentity( deviceId = deviceId, deviceName = deviceName, deviceType = extensionDeviceType(), longTermPublicKeyHex = pubkeyToHex(kp.publicKey), signingPublicKeyHex = pubkeyToHex(signing.publicKeyRaw), // Where peers should dial to reconnect without a pairing code. The // pairing window uses an ephemeral port and then closes, so a peer that // cached that one would never reach us again. wsPort = SyncReconnectListener.SYNC_PORT, relayUrl = RelaySettingsStore.readRelayUrl(context), ) } fun deriveLocalSyncKeypair(masterKey: ByteArray, deviceId: String): SyncIdentityKeypair { return deriveSyncIdentityKeypair(masterKey, deviceId) } private fun defaultDeviceName(): String { return "Android" } private fun extensionDeviceType(): DeviceType { return DeviceType.ANDROID }