Meshtastic-Android/app/src/main/java/com/geeksville/mesh/model/Channel.kt

109 lines
4.2 KiB
Kotlin
Raw Normal View History

2020-03-24 13:48:22 -07:00
package com.geeksville.mesh.model
import android.graphics.Bitmap
import android.net.Uri
import android.util.Base64
import com.geeksville.mesh.MeshProtos
import com.google.protobuf.ByteString
2020-03-24 13:48:22 -07:00
import com.google.zxing.BarcodeFormat
import com.google.zxing.MultiFormatWriter
import com.journeyapps.barcodescanner.BarcodeEncoder
import java.net.MalformedURLException
/** 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() }
2020-03-24 13:48:22 -07:00
data class Channel(
val settings: MeshProtos.ChannelSettings = MeshProtos.ChannelSettings.getDefaultInstance()
2020-03-24 13:48:22 -07:00
) {
companion object {
// Note: this string _SHOULD NOT BE LOCALIZED_ because it directly hashes to values used on the device for the default channel name.
val defaultChannelName = "Default"
// These bytes must match the well known and not secret bytes used the default channel AES128 key device code
val channelDefaultKey = byteArrayOfInts(
0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf
)
2020-03-24 13:48:22 -07:00
// Placeholder when emulating
val emulated = Channel(
MeshProtos.ChannelSettings.newBuilder().setName(defaultChannelName)
2020-03-24 13:48:22 -07:00
.setModemConfig(MeshProtos.ChannelSettings.ModemConfig.Bw125Cr45Sf128).build()
)
const val prefix = "https://www.meshtastic.org/c/#"
2020-03-24 13:48:22 -07:00
private const val base64Flags = Base64.URL_SAFE + Base64.NO_WRAP
private fun urlToSettings(url: Uri): MeshProtos.ChannelSettings {
val urlStr = url.toString()
fix an autobug. Someone with a galaxy tab is trying to open the old version of the URL. Caused by java.net.MalformedURLException: Not a meshtastic URL at com.geeksville.mesh.model.Channel$Companion.urlToSettings(Channel.java:43) at com.geeksville.mesh.model.Channel$Companion.access$urlToSettings(Channel.java:19) at com.geeksville.mesh.model.Channel.<init>(Channel.java:50) at com.geeksville.mesh.MainActivity.perhapsChangeChannel(MainActivity.java:631) at com.geeksville.mesh.MainActivity.handleIntent(MainActivity.java:467) at com.geeksville.mesh.MainActivity.onNewIntent(MainActivity.java:447) at android.app.Activity.performNewIntent(Activity.java:7971) at android.app.Instrumentation.callActivityOnNewIntent(Instrumentation.java:1407) at android.app.Instrumentation.callActivityOnNewIntent(Instrumentation.java:1420) at android.app.ActivityThread.deliverNewIntents(ActivityThread.java:3838) at android.app.ActivityThread.handleNewIntent(ActivityThread.java:3850) at android.app.servertransaction.NewIntentItem.execute(NewIntentItem.java:53) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2261) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:237) at android.app.ActivityThread.main(ActivityThread.java:8107) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:496) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1100)
2020-08-29 19:07:29 -07:00
// We no longer support the super old (about 0.8ish? verison of the URLs that don't use the # separator - I doubt
// anyone is still using that old format
val pathRegex = Regex("$prefix(.*)")
2020-03-24 13:48:22 -07:00
val (base64) = pathRegex.find(urlStr)?.destructured
2020-09-08 12:05:19 -07:00
?: throw MalformedURLException("Not a meshtastic URL: ${urlStr.take(40)}")
2020-03-24 13:48:22 -07:00
val bytes = Base64.decode(base64, base64Flags)
return MeshProtos.ChannelSettings.parseFrom(bytes)
}
}
constructor(url: Uri) : this(urlToSettings(url))
/// Return the name of our channel as a human readable string. If empty string, assume "Default" per mesh.proto spec
val name: String get() = if(settings.name.isEmpty()) defaultChannelName else settings.name
val modemConfig: MeshProtos.ChannelSettings.ModemConfig get() = settings.modemConfig
val psk 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)
ByteString.EMPTY // Treat as an empty PSK (no encryption)
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)
}
}
/**
* Return a name that is formatted as #channename-suffix
*
* Where suffix indicates the hash of the PSK
*/
val humanName: String
get() {
2020-08-13 15:24:36 -07:00
val code = settings.psk.fold(0, { acc, x -> acc xor (x.toInt() and 0xff) })
return "#${settings.name}-${'A' + (code % 26)}"
}
2020-03-24 13:48:22 -07:00
/// Can this channel be changed right now?
var editable = false
/// Return an URL that represents the current channel values
fun getChannelUrl(): Uri {
// If we have a valid radio config use it, othterwise use whatever we have saved in the prefs
2020-04-19 16:24:47 -07:00
val channelBytes = settings.toByteArray() ?: ByteArray(0) // if unset just use empty
2020-03-24 13:48:22 -07:00
val enc = Base64.encodeToString(channelBytes, base64Flags)
return Uri.parse("$prefix$enc")
}
fun getChannelQR(): Bitmap {
val multiFormatWriter = MultiFormatWriter()
val bitMatrix =
multiFormatWriter.encode(getChannelUrl().toString(), BarcodeFormat.QR_CODE, 192, 192);
val barcodeEncoder = BarcodeEncoder()
return barcodeEncoder.createBitmap(bitMatrix)
}
}