#pragma once #include #include #include #include // Local labels, not part of a hashtag PSK. Keep the original app label/key. enum class CompanionChannelKind : uint8_t { MeshCore, Radio2, Meshtext, Invalid }; struct CompanionChannelAlias { CompanionChannelKind kind; bool hashtag; const char* tail; }; inline CompanionChannelAlias parseCompanionChannelAlias(const char* name) { if (name == nullptr) return {CompanionChannelKind::Invalid, false, ""}; const bool hashtag = name[0] == '#'; const char* tail = name + (hashtag ? 1 : 0); CompanionChannelKind kind = CompanionChannelKind::MeshCore; if (strncmp(tail, "r2-", 3) == 0) { kind = CompanionChannelKind::Radio2; tail += 3; } else if (strncmp(tail, "mt-", 3) == 0) { kind = CompanionChannelKind::Meshtext; tail += 3; } if (kind != CompanionChannelKind::MeshCore && (tail[0] == 0 || tail[0] == '#' || strncmp(tail, "r2-", 3) == 0 || strncmp(tail, "mt-", 3) == 0)) kind = CompanionChannelKind::Invalid; return {kind, hashtag, tail}; } // Only exact named radio2 aliases request RF setup. Ordinary channels and // arbitrary r2 labels keep the operator's existing profile. inline const char* companionChannelRadio2Preset(const char* name) { if (!name) return nullptr; if (*name == '#') ++name; if (strncmp(name, "r2-", 3)) return nullptr; name += 3; // An exact preset tail also excludes every invalid/nested alias form. return mesh::cli::radio2PresetTuple(name) ? name : nullptr; } inline bool companionChannelCanonicalName(const char* name, char* out, size_t size) { if (out == nullptr) return false; const auto alias = parseCompanionChannelAlias(name); if (alias.kind == CompanionChannelKind::Invalid) return false; const size_t offset = alias.hashtag ? 1 : 0; const size_t length = strlen(alias.tail); if (size <= offset + length) return false; if (offset) out[0] = '#'; memcpy(out + offset, alias.tail, length + 1); return true; } template bool resolveCompanionChannelKey(const char* name, uint8_t secret[32], Hash hash) { const auto alias = parseCompanionChannelAlias(name); if (alias.kind == CompanionChannelKind::Invalid) return false; if (alias.kind == CompanionChannelKind::MeshCore || !alias.hashtag) return true; for (size_t i = 16; i < 32; ++i) { if (secret[i] != 0) return true; // Never rewrite an explicit 256-bit PSK. } uint8_t app_key[16]; hash(app_key, sizeof(app_key), reinterpret_cast(name), strlen(name)); if (memcmp(secret, app_key, sizeof(app_key)) != 0) return true; // Custom PSK. char canonical[32]; if (!companionChannelCanonicalName(name, canonical, sizeof(canonical))) return false; hash(secret, 16, reinterpret_cast(canonical), strlen(canonical)); return true; } inline int companionChannelReceivePriority(const char* name, uint8_t origin, bool radio2_defined = true) { const auto alias = parseCompanionChannelAlias(name); if (alias.kind == CompanionChannelKind::Invalid) return -1; if (radio2_defined) { if (alias.kind == CompanionChannelKind::Meshtext) return -1; if (alias.kind == CompanionChannelKind::Radio2) return origin == 1 ? 0 : -1; } return origin == 1 ? 1 : 0; }