package com.geeksville.mesh.model import com.geeksville.mesh.ChannelProtos import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig.ModemPreset import com.geeksville.mesh.ConfigKt.loRaConfig import com.geeksville.mesh.ConfigProtos import com.geeksville.mesh.channelSettings import com.google.protobuf.ByteString import java.security.SecureRandom /** Utility function to make it easy to declare byte arrays - FIXME move someplace better */ fun byteArrayOfInts(vararg ints: Int) = ByteArray(ints.size) { pos -> ints[pos].toByte() } fun xorHash(b: ByteArray) = b.fold(0) { acc, x -> acc xor (x.toInt() and 0xff) } data class Channel( val settings: ChannelProtos.ChannelSettings = default.settings, val loraConfig: ConfigProtos.Config.LoRaConfig = default.loraConfig, ) { companion object { // These bytes must match the well known and not secret bytes used the default channel AES128 key device code private val channelDefaultKey = byteArrayOfInts( 0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01 ) private val cleartextPSK = ByteString.EMPTY private val defaultPSK = byteArrayOfInts(1) // a shortstring code to indicate we need our default PSK // The default channel that devices ship with val default = Channel( channelSettings { psk = ByteString.copyFrom(defaultPSK) }, // references: NodeDB::installDefaultConfig / Channels::initDefaultChannel loRaConfig { usePreset = true modemPreset = ModemPreset.LONG_FAST hopLimit = 3 txEnabled = true } ) fun getRandomKey(size: Int = 32): ByteString { val bytes = ByteArray(size) val random = SecureRandom() random.nextBytes(bytes) return ByteString.copyFrom(bytes) } } /// Return the name of our channel as a human readable string. If empty string, assume "Default" per mesh.proto spec val name: String get() = settings.name.ifEmpty { // We have a new style 'empty' channel name. Use the same logic from the device to convert that to a human readable name if (loraConfig.usePreset) when (loraConfig.modemPreset) { ModemPreset.SHORT_FAST -> "ShortFast" ModemPreset.SHORT_SLOW -> "ShortSlow" ModemPreset.MEDIUM_FAST -> "MediumFast" ModemPreset.MEDIUM_SLOW -> "MediumSlow" ModemPreset.LONG_FAST -> "LongFast" ModemPreset.LONG_SLOW -> "LongSlow" ModemPreset.LONG_MODERATE -> "LongMod" ModemPreset.VERY_LONG_SLOW -> "VLongSlow" else -> "Invalid" } else "Custom" } val psk: ByteString get() = if (settings.psk.size() != 1) settings.psk // A standard PSK else { // One of our special 1 byte PSKs, see mesh.proto for docs. val pskIndex = settings.psk.byteAt(0).toInt() if (pskIndex == 0) cleartextPSK else { // Treat an index of 1 as the old channelDefaultKey and work up from there val bytes = channelDefaultKey.clone() bytes[bytes.size - 1] = (0xff and (bytes[bytes.size - 1] + pskIndex - 1)).toByte() ByteString.copyFrom(bytes) } } /** * Given a channel name and psk, return the (0 to 255) hash for that channel */ val hash: Int get() = xorHash(name.toByteArray()) xor xorHash(psk.toByteArray()) val channelNum: Int get() = loraConfig.channelNum(name) val radioFreq: Float get() = loraConfig.radioFreq(channelNum) override fun equals(other: Any?): Boolean = (other is Channel) && psk.toByteArray() contentEquals other.psk.toByteArray() && name == other.name override fun hashCode(): Int { var result = settings.hashCode() result = 31 * result + loraConfig.hashCode() return result } }