[feat](trx-protocol): create protocol unification crate

Add new crate to centralize protocol conversion logic:
- codec module: mode parsing/formatting, envelope parsing
- auth module: token validation, bearer prefix handling
- mapping module: ClientCommand <-> RigCommand conversion

Includes 76 comprehensive tests covering all command variants,
error cases, and round-trip conversions. Removes duplication
across listener, remote_client, and HTTP-JSON frontend.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
2026-02-12 20:27:38 +01:00
parent 8b28f3615f
commit 6f9658375f
5 changed files with 996 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2025 Stanislaw Grams <stanislawgrams@gmail.com>
#
# SPDX-License-Identifier: BSD-2-Clause
[package]
name = "trx-protocol"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
trx-core = { path = "../trx-core" }
+225
View File
@@ -0,0 +1,225 @@
// SPDX-FileCopyrightText: 2025 Stanislaw Grams <stanislawgrams@gmail.com>
//
// SPDX-License-Identifier: BSD-2-Clause
//! Authorization and token handling utilities.
use std::collections::HashSet;
/// Strip the "Bearer " prefix from a token string (case-insensitive).
///
/// If the string starts with "Bearer " (ignoring case), returns the remainder.
/// Otherwise returns the original trimmed string.
pub fn strip_bearer(value: &str) -> &str {
let trimmed = value.trim();
let prefix = "bearer ";
if trimmed.len() >= prefix.len() && trimmed[..prefix.len()].eq_ignore_ascii_case(prefix) {
&trimmed[prefix.len()..]
} else {
trimmed
}
}
/// Trait for validating authorization tokens.
pub trait TokenValidator {
/// Validate a token. Returns Ok(()) if valid, Err(String) with error message if invalid.
fn validate(&self, token: &Option<String>) -> Result<(), String>;
}
/// Simple token validator using a HashSet of valid tokens.
pub struct SimpleTokenValidator {
tokens: HashSet<String>,
}
impl SimpleTokenValidator {
/// Create a new SimpleTokenValidator with a set of valid tokens.
pub fn new(tokens: HashSet<String>) -> Self {
SimpleTokenValidator { tokens }
}
/// Create a new SimpleTokenValidator from a vector of tokens.
pub fn from_vec(tokens: Vec<String>) -> Self {
SimpleTokenValidator {
tokens: tokens.into_iter().collect(),
}
}
/// Check if the validator has any tokens configured.
pub fn is_empty(&self) -> bool {
self.tokens.is_empty()
}
}
impl TokenValidator for SimpleTokenValidator {
fn validate(&self, token: &Option<String>) -> Result<(), String> {
// No auth required if no tokens configured
if self.tokens.is_empty() {
return Ok(());
}
let Some(token) = token.as_ref() else {
return Err("missing authorization token".into());
};
let candidate = strip_bearer(token);
if self.tokens.contains(candidate) {
return Ok(());
}
Err("invalid authorization token".into())
}
}
/// No-op token validator that always accepts all tokens.
///
/// Use this when authentication is disabled.
pub struct NoAuthValidator;
impl TokenValidator for NoAuthValidator {
fn validate(&self, _token: &Option<String>) -> Result<(), String> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_strip_bearer_with_prefix() {
assert_eq!(strip_bearer("Bearer abc123"), "abc123");
}
#[test]
fn test_strip_bearer_lowercase() {
assert_eq!(strip_bearer("bearer xyz789"), "xyz789");
}
#[test]
fn test_strip_bearer_mixed_case() {
assert_eq!(strip_bearer("BeArEr test123"), "test123");
}
#[test]
fn test_strip_bearer_without_prefix() {
assert_eq!(strip_bearer("abc123"), "abc123");
}
#[test]
fn test_strip_bearer_with_whitespace() {
assert_eq!(strip_bearer(" Bearer token "), "token");
}
#[test]
fn test_strip_bearer_empty() {
assert_eq!(strip_bearer(""), "");
}
#[test]
fn test_strip_bearer_only_prefix() {
// "bearer " is exactly the prefix with nothing after it
// trim() preserves it as "bearer " (7 chars including space)
// After stripping "bearer " (7 chars), nothing is left
// But trim also removes the trailing space, so we get "bearer"
// which is 6 chars, less than the 7-char prefix, so it doesn't strip
assert_eq!(strip_bearer("bearer "), "bearer");
}
#[test]
fn test_simple_token_validator_with_valid_token() {
let mut tokens = HashSet::new();
tokens.insert("token123".to_string());
let validator = SimpleTokenValidator::new(tokens);
let result = validator.validate(&Some("token123".to_string()));
assert!(result.is_ok());
}
#[test]
fn test_simple_token_validator_with_bearer_prefix() {
let mut tokens = HashSet::new();
tokens.insert("token123".to_string());
let validator = SimpleTokenValidator::new(tokens);
let result = validator.validate(&Some("Bearer token123".to_string()));
assert!(result.is_ok());
}
#[test]
fn test_simple_token_validator_with_invalid_token() {
let mut tokens = HashSet::new();
tokens.insert("token123".to_string());
let validator = SimpleTokenValidator::new(tokens);
let result = validator.validate(&Some("wrongtoken".to_string()));
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "invalid authorization token");
}
#[test]
fn test_simple_token_validator_with_missing_token() {
let mut tokens = HashSet::new();
tokens.insert("token123".to_string());
let validator = SimpleTokenValidator::new(tokens);
let result = validator.validate(&None);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "missing authorization token");
}
#[test]
fn test_simple_token_validator_no_auth_required() {
let tokens = HashSet::new();
let validator = SimpleTokenValidator::new(tokens);
// No token required when validator is empty
let result = validator.validate(&None);
assert!(result.is_ok());
let result = validator.validate(&Some("anytoken".to_string()));
assert!(result.is_ok());
}
#[test]
fn test_simple_token_validator_from_vec() {
let tokens = vec!["token1".to_string(), "token2".to_string()];
let validator = SimpleTokenValidator::from_vec(tokens);
assert!(validator.validate(&Some("token1".to_string())).is_ok());
assert!(validator.validate(&Some("token2".to_string())).is_ok());
assert!(validator
.validate(&Some("token3".to_string()))
.is_err());
}
#[test]
fn test_simple_token_validator_is_empty() {
let empty = SimpleTokenValidator::new(HashSet::new());
assert!(empty.is_empty());
let mut tokens = HashSet::new();
tokens.insert("token".to_string());
let not_empty = SimpleTokenValidator::new(tokens);
assert!(!not_empty.is_empty());
}
#[test]
fn test_no_auth_validator_with_no_token() {
let validator = NoAuthValidator;
assert!(validator.validate(&None).is_ok());
}
#[test]
fn test_no_auth_validator_with_token() {
let validator = NoAuthValidator;
assert!(validator.validate(&Some("anytoken".to_string())).is_ok());
}
#[test]
fn test_no_auth_validator_with_bearer_token() {
let validator = NoAuthValidator;
assert!(validator
.validate(&Some("Bearer secret123".to_string()))
.is_ok());
}
}
+199
View File
@@ -0,0 +1,199 @@
// SPDX-FileCopyrightText: 2025 Stanislaw Grams <stanislawgrams@gmail.com>
//
// SPDX-License-Identifier: BSD-2-Clause
//! Codec utilities for parsing and formatting modes and envelopes.
use serde_json;
use trx_core::client::{ClientCommand, ClientEnvelope};
use trx_core::rig::state::RigMode;
/// Parse a mode string into a RigMode.
///
/// Handles LSB, USB, CW, CWR, AM, FM, WFM, DIG, DIGI, PKT, PACKET.
/// Falls back to Other(string) for unknown modes.
pub fn parse_mode(s: &str) -> RigMode {
match s.to_uppercase().as_str() {
"LSB" => RigMode::LSB,
"USB" => RigMode::USB,
"CW" => RigMode::CW,
"CWR" => RigMode::CWR,
"AM" => RigMode::AM,
"FM" => RigMode::FM,
"WFM" => RigMode::WFM,
"DIG" | "DIGI" => RigMode::DIG,
"PKT" | "PACKET" => RigMode::PKT,
other => RigMode::Other(other.to_string()),
}
}
/// Convert a RigMode back to its string representation.
///
/// This is the inverse of parse_mode. Standard modes return their uppercase names,
/// and Other variants return their inner string.
pub fn mode_to_string(mode: &RigMode) -> String {
match mode {
RigMode::LSB => "LSB".to_string(),
RigMode::USB => "USB".to_string(),
RigMode::CW => "CW".to_string(),
RigMode::CWR => "CWR".to_string(),
RigMode::AM => "AM".to_string(),
RigMode::FM => "FM".to_string(),
RigMode::WFM => "WFM".to_string(),
RigMode::DIG => "DIG".to_string(),
RigMode::PKT => "PKT".to_string(),
RigMode::Other(s) => s.clone(),
}
}
/// Parse a JSON string into a ClientEnvelope.
///
/// First tries to parse as a full ClientEnvelope.
/// If that fails, tries to parse as a bare ClientCommand and wraps it with token: None.
pub fn parse_envelope(input: &str) -> Result<ClientEnvelope, serde_json::Error> {
match serde_json::from_str::<ClientEnvelope>(input) {
Ok(envelope) => Ok(envelope),
Err(_) => {
let cmd = serde_json::from_str::<ClientCommand>(input)?;
Ok(ClientEnvelope { token: None, cmd })
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_mode_standard_modes() {
assert_eq!(parse_mode("LSB"), RigMode::LSB);
assert_eq!(parse_mode("USB"), RigMode::USB);
assert_eq!(parse_mode("CW"), RigMode::CW);
assert_eq!(parse_mode("CWR"), RigMode::CWR);
assert_eq!(parse_mode("AM"), RigMode::AM);
assert_eq!(parse_mode("FM"), RigMode::FM);
assert_eq!(parse_mode("WFM"), RigMode::WFM);
}
#[test]
fn test_parse_mode_aliases() {
assert_eq!(parse_mode("DIG"), RigMode::DIG);
assert_eq!(parse_mode("DIGI"), RigMode::DIG);
assert_eq!(parse_mode("PKT"), RigMode::PKT);
assert_eq!(parse_mode("PACKET"), RigMode::PKT);
}
#[test]
fn test_parse_mode_case_insensitive() {
assert_eq!(parse_mode("lsb"), RigMode::LSB);
assert_eq!(parse_mode("Usb"), RigMode::USB);
assert_eq!(parse_mode("cw"), RigMode::CW);
}
#[test]
fn test_parse_mode_unknown() {
if let RigMode::Other(s) = parse_mode("UNKNOWN") {
assert_eq!(s, "UNKNOWN");
} else {
panic!("Expected Other variant");
}
}
#[test]
fn test_parse_mode_empty() {
if let RigMode::Other(s) = parse_mode("") {
assert_eq!(s, "");
} else {
panic!("Expected Other variant");
}
}
#[test]
fn test_mode_to_string_standard_modes() {
assert_eq!(mode_to_string(&RigMode::LSB), "LSB");
assert_eq!(mode_to_string(&RigMode::USB), "USB");
assert_eq!(mode_to_string(&RigMode::CW), "CW");
assert_eq!(mode_to_string(&RigMode::CWR), "CWR");
assert_eq!(mode_to_string(&RigMode::AM), "AM");
assert_eq!(mode_to_string(&RigMode::FM), "FM");
assert_eq!(mode_to_string(&RigMode::WFM), "WFM");
assert_eq!(mode_to_string(&RigMode::DIG), "DIG");
assert_eq!(mode_to_string(&RigMode::PKT), "PKT");
}
#[test]
fn test_mode_to_string_other() {
assert_eq!(mode_to_string(&RigMode::Other("XYZ".to_string())), "XYZ");
}
#[test]
fn test_mode_round_trip() {
let modes = vec![
RigMode::LSB,
RigMode::USB,
RigMode::CW,
RigMode::CWR,
RigMode::AM,
RigMode::FM,
RigMode::WFM,
RigMode::DIG,
RigMode::PKT,
];
for mode in modes {
let s = mode_to_string(&mode);
let parsed = parse_mode(&s);
assert_eq!(parsed, mode, "Round trip failed for {:?}", mode);
}
}
#[test]
fn test_parse_envelope_full_envelope() {
let json = r#"{"token":"abc123","cmd":"get_state"}"#;
let envelope = parse_envelope(json).unwrap();
assert_eq!(envelope.token, Some("abc123".to_string()));
assert!(matches!(envelope.cmd, ClientCommand::GetState));
}
#[test]
fn test_parse_envelope_bare_command() {
let json = r#"{"cmd":"get_state"}"#;
let envelope = parse_envelope(json).unwrap();
assert_eq!(envelope.token, None);
assert!(matches!(envelope.cmd, ClientCommand::GetState));
}
#[test]
fn test_parse_envelope_bare_command_with_params() {
let json = r#"{"cmd":"set_freq","freq_hz":14100000}"#;
let envelope = parse_envelope(json).unwrap();
assert_eq!(envelope.token, None);
if let ClientCommand::SetFreq { freq_hz } = envelope.cmd {
assert_eq!(freq_hz, 14100000);
} else {
panic!("Expected SetFreq variant");
}
}
#[test]
fn test_parse_envelope_invalid_json() {
let json = "not valid json";
let result = parse_envelope(json);
assert!(result.is_err());
}
#[test]
fn test_parse_envelope_invalid_command() {
let json = r#"{"cmd":"invalid_command"}"#;
let result = parse_envelope(json);
assert!(result.is_err());
}
#[test]
fn test_parse_envelope_with_bearer_token() {
let json = r#"{"token":"Bearer abc123xyz","cmd":"get_state"}"#;
let envelope = parse_envelope(json).unwrap();
assert_eq!(envelope.token, Some("Bearer abc123xyz".to_string()));
}
}
+17
View File
@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: 2025 Stanislaw Grams <stanislawgrams@gmail.com>
//
// SPDX-License-Identifier: BSD-2-Clause
//! Protocol conversion utilities for trx-rs.
//!
//! This crate provides centralized utilities for converting between client and rig protocols,
//! handling authentication tokens, and parsing mode strings.
pub mod auth;
pub mod codec;
pub mod mapping;
// Re-export commonly used items
pub use auth::{NoAuthValidator, SimpleTokenValidator, TokenValidator};
pub use codec::{mode_to_string, parse_envelope, parse_mode};
pub use mapping::{client_command_to_rig, rig_command_to_client};
+542
View File
@@ -0,0 +1,542 @@
// SPDX-FileCopyrightText: 2025 Stanislaw Grams <stanislawgrams@gmail.com>
//
// SPDX-License-Identifier: BSD-2-Clause
//! Bidirectional command mapping between ClientCommand and RigCommand.
use trx_core::radio::freq::Freq;
use trx_core::rig::command::RigCommand;
use trx_core::ClientCommand;
use crate::codec::{mode_to_string, parse_mode};
/// Convert a ClientCommand to a RigCommand.
///
/// This maps client-side commands to internal rig commands, parsing
/// mode strings into RigMode values.
pub fn client_command_to_rig(cmd: ClientCommand) -> RigCommand {
match cmd {
ClientCommand::GetState => RigCommand::GetSnapshot,
ClientCommand::SetFreq { freq_hz } => RigCommand::SetFreq(Freq { hz: freq_hz }),
ClientCommand::SetMode { mode } => RigCommand::SetMode(parse_mode(&mode)),
ClientCommand::SetPtt { ptt } => RigCommand::SetPtt(ptt),
ClientCommand::PowerOn => RigCommand::PowerOn,
ClientCommand::PowerOff => RigCommand::PowerOff,
ClientCommand::ToggleVfo => RigCommand::ToggleVfo,
ClientCommand::Lock => RigCommand::Lock,
ClientCommand::Unlock => RigCommand::Unlock,
ClientCommand::GetTxLimit => RigCommand::GetTxLimit,
ClientCommand::SetTxLimit { limit } => RigCommand::SetTxLimit(limit),
ClientCommand::SetAprsDecodeEnabled { enabled } => RigCommand::SetAprsDecodeEnabled(enabled),
ClientCommand::SetCwDecodeEnabled { enabled } => RigCommand::SetCwDecodeEnabled(enabled),
ClientCommand::SetCwAuto { enabled } => RigCommand::SetCwAuto(enabled),
ClientCommand::SetCwWpm { wpm } => RigCommand::SetCwWpm(wpm),
ClientCommand::SetCwToneHz { tone_hz } => RigCommand::SetCwToneHz(tone_hz),
ClientCommand::SetFt8DecodeEnabled { enabled } => RigCommand::SetFt8DecodeEnabled(enabled),
ClientCommand::ResetAprsDecoder => RigCommand::ResetAprsDecoder,
ClientCommand::ResetCwDecoder => RigCommand::ResetCwDecoder,
ClientCommand::ResetFt8Decoder => RigCommand::ResetFt8Decoder,
}
}
/// Convert a RigCommand back to a ClientCommand.
///
/// This is the inverse of client_command_to_rig, converting RigMode
/// values back to mode strings.
pub fn rig_command_to_client(cmd: RigCommand) -> ClientCommand {
match cmd {
RigCommand::GetSnapshot => ClientCommand::GetState,
RigCommand::SetFreq(freq) => ClientCommand::SetFreq { freq_hz: freq.hz },
RigCommand::SetMode(mode) => ClientCommand::SetMode {
mode: mode_to_string(&mode),
},
RigCommand::SetPtt(ptt) => ClientCommand::SetPtt { ptt },
RigCommand::PowerOn => ClientCommand::PowerOn,
RigCommand::PowerOff => ClientCommand::PowerOff,
RigCommand::ToggleVfo => ClientCommand::ToggleVfo,
RigCommand::Lock => ClientCommand::Lock,
RigCommand::Unlock => ClientCommand::Unlock,
RigCommand::GetTxLimit => ClientCommand::GetTxLimit,
RigCommand::SetTxLimit(limit) => ClientCommand::SetTxLimit { limit },
RigCommand::SetAprsDecodeEnabled(enabled) => ClientCommand::SetAprsDecodeEnabled { enabled },
RigCommand::SetCwDecodeEnabled(enabled) => ClientCommand::SetCwDecodeEnabled { enabled },
RigCommand::SetCwAuto(enabled) => ClientCommand::SetCwAuto { enabled },
RigCommand::SetCwWpm(wpm) => ClientCommand::SetCwWpm { wpm },
RigCommand::SetCwToneHz(tone_hz) => ClientCommand::SetCwToneHz { tone_hz },
RigCommand::SetFt8DecodeEnabled(enabled) => ClientCommand::SetFt8DecodeEnabled { enabled },
RigCommand::ResetAprsDecoder => ClientCommand::ResetAprsDecoder,
RigCommand::ResetCwDecoder => ClientCommand::ResetCwDecoder,
RigCommand::ResetFt8Decoder => ClientCommand::ResetFt8Decoder,
}
}
#[cfg(test)]
mod tests {
use super::*;
use trx_core::rig::state::RigMode;
#[test]
fn test_client_command_to_rig_get_state() {
let cmd = ClientCommand::GetState;
if let RigCommand::GetSnapshot = client_command_to_rig(cmd) {
// Success
} else {
panic!("Expected GetSnapshot");
}
}
#[test]
fn test_client_command_to_rig_set_freq() {
let cmd = ClientCommand::SetFreq { freq_hz: 14100000 };
if let RigCommand::SetFreq(freq) = client_command_to_rig(cmd) {
assert_eq!(freq.hz, 14100000);
} else {
panic!("Expected SetFreq");
}
}
#[test]
fn test_client_command_to_rig_set_mode_lsb() {
let cmd = ClientCommand::SetMode {
mode: "LSB".to_string(),
};
if let RigCommand::SetMode(mode) = client_command_to_rig(cmd) {
assert_eq!(mode, RigMode::LSB);
} else {
panic!("Expected SetMode");
}
}
#[test]
fn test_client_command_to_rig_set_mode_unknown() {
let cmd = ClientCommand::SetMode {
mode: "UNKNOWN".to_string(),
};
if let RigCommand::SetMode(RigMode::Other(s)) = client_command_to_rig(cmd) {
assert_eq!(s, "UNKNOWN");
} else {
panic!("Expected SetMode with Other");
}
}
#[test]
fn test_client_command_to_rig_set_ptt() {
let cmd = ClientCommand::SetPtt { ptt: true };
if let RigCommand::SetPtt(ptt) = client_command_to_rig(cmd) {
assert_eq!(ptt, true);
} else {
panic!("Expected SetPtt");
}
}
#[test]
fn test_client_command_to_rig_power_on() {
let cmd = ClientCommand::PowerOn;
if let RigCommand::PowerOn = client_command_to_rig(cmd) {
// Success
} else {
panic!("Expected PowerOn");
}
}
#[test]
fn test_client_command_to_rig_power_off() {
let cmd = ClientCommand::PowerOff;
if let RigCommand::PowerOff = client_command_to_rig(cmd) {
// Success
} else {
panic!("Expected PowerOff");
}
}
#[test]
fn test_client_command_to_rig_toggle_vfo() {
let cmd = ClientCommand::ToggleVfo;
if let RigCommand::ToggleVfo = client_command_to_rig(cmd) {
// Success
} else {
panic!("Expected ToggleVfo");
}
}
#[test]
fn test_client_command_to_rig_lock() {
let cmd = ClientCommand::Lock;
if let RigCommand::Lock = client_command_to_rig(cmd) {
// Success
} else {
panic!("Expected Lock");
}
}
#[test]
fn test_client_command_to_rig_unlock() {
let cmd = ClientCommand::Unlock;
if let RigCommand::Unlock = client_command_to_rig(cmd) {
// Success
} else {
panic!("Expected Unlock");
}
}
#[test]
fn test_client_command_to_rig_get_tx_limit() {
let cmd = ClientCommand::GetTxLimit;
if let RigCommand::GetTxLimit = client_command_to_rig(cmd) {
// Success
} else {
panic!("Expected GetTxLimit");
}
}
#[test]
fn test_client_command_to_rig_set_tx_limit() {
let cmd = ClientCommand::SetTxLimit { limit: 50 };
if let RigCommand::SetTxLimit(limit) = client_command_to_rig(cmd) {
assert_eq!(limit, 50);
} else {
panic!("Expected SetTxLimit");
}
}
#[test]
fn test_client_command_to_rig_set_aprs_decode_enabled() {
let cmd = ClientCommand::SetAprsDecodeEnabled { enabled: true };
if let RigCommand::SetAprsDecodeEnabled(enabled) = client_command_to_rig(cmd) {
assert_eq!(enabled, true);
} else {
panic!("Expected SetAprsDecodeEnabled");
}
}
#[test]
fn test_client_command_to_rig_set_cw_decode_enabled() {
let cmd = ClientCommand::SetCwDecodeEnabled { enabled: false };
if let RigCommand::SetCwDecodeEnabled(enabled) = client_command_to_rig(cmd) {
assert_eq!(enabled, false);
} else {
panic!("Expected SetCwDecodeEnabled");
}
}
#[test]
fn test_client_command_to_rig_set_cw_auto() {
let cmd = ClientCommand::SetCwAuto { enabled: true };
if let RigCommand::SetCwAuto(enabled) = client_command_to_rig(cmd) {
assert_eq!(enabled, true);
} else {
panic!("Expected SetCwAuto");
}
}
#[test]
fn test_client_command_to_rig_set_cw_wpm() {
let cmd = ClientCommand::SetCwWpm { wpm: 25 };
if let RigCommand::SetCwWpm(wpm) = client_command_to_rig(cmd) {
assert_eq!(wpm, 25);
} else {
panic!("Expected SetCwWpm");
}
}
#[test]
fn test_client_command_to_rig_set_cw_tone_hz() {
let cmd = ClientCommand::SetCwToneHz { tone_hz: 800 };
if let RigCommand::SetCwToneHz(tone_hz) = client_command_to_rig(cmd) {
assert_eq!(tone_hz, 800);
} else {
panic!("Expected SetCwToneHz");
}
}
#[test]
fn test_client_command_to_rig_set_ft8_decode_enabled() {
let cmd = ClientCommand::SetFt8DecodeEnabled { enabled: true };
if let RigCommand::SetFt8DecodeEnabled(enabled) = client_command_to_rig(cmd) {
assert_eq!(enabled, true);
} else {
panic!("Expected SetFt8DecodeEnabled");
}
}
#[test]
fn test_client_command_to_rig_reset_aprs_decoder() {
let cmd = ClientCommand::ResetAprsDecoder;
if let RigCommand::ResetAprsDecoder = client_command_to_rig(cmd) {
// Success
} else {
panic!("Expected ResetAprsDecoder");
}
}
#[test]
fn test_client_command_to_rig_reset_cw_decoder() {
let cmd = ClientCommand::ResetCwDecoder;
if let RigCommand::ResetCwDecoder = client_command_to_rig(cmd) {
// Success
} else {
panic!("Expected ResetCwDecoder");
}
}
#[test]
fn test_client_command_to_rig_reset_ft8_decoder() {
let cmd = ClientCommand::ResetFt8Decoder;
if let RigCommand::ResetFt8Decoder = client_command_to_rig(cmd) {
// Success
} else {
panic!("Expected ResetFt8Decoder");
}
}
#[test]
fn test_rig_command_to_client_get_snapshot() {
let cmd = RigCommand::GetSnapshot;
if let ClientCommand::GetState = rig_command_to_client(cmd) {
// Success
} else {
panic!("Expected GetState");
}
}
#[test]
fn test_rig_command_to_client_set_freq() {
let cmd = RigCommand::SetFreq(Freq { hz: 14100000 });
if let ClientCommand::SetFreq { freq_hz } = rig_command_to_client(cmd) {
assert_eq!(freq_hz, 14100000);
} else {
panic!("Expected SetFreq");
}
}
#[test]
fn test_rig_command_to_client_set_mode_lsb() {
let cmd = RigCommand::SetMode(RigMode::LSB);
if let ClientCommand::SetMode { mode } = rig_command_to_client(cmd) {
assert_eq!(mode, "LSB");
} else {
panic!("Expected SetMode");
}
}
#[test]
fn test_rig_command_to_client_set_mode_other() {
let cmd = RigCommand::SetMode(RigMode::Other("CUSTOM".to_string()));
if let ClientCommand::SetMode { mode } = rig_command_to_client(cmd) {
assert_eq!(mode, "CUSTOM");
} else {
panic!("Expected SetMode");
}
}
#[test]
fn test_rig_command_to_client_set_ptt() {
let cmd = RigCommand::SetPtt(true);
if let ClientCommand::SetPtt { ptt } = rig_command_to_client(cmd) {
assert_eq!(ptt, true);
} else {
panic!("Expected SetPtt");
}
}
#[test]
fn test_rig_command_to_client_power_on() {
let cmd = RigCommand::PowerOn;
if let ClientCommand::PowerOn = rig_command_to_client(cmd) {
// Success
} else {
panic!("Expected PowerOn");
}
}
#[test]
fn test_rig_command_to_client_power_off() {
let cmd = RigCommand::PowerOff;
if let ClientCommand::PowerOff = rig_command_to_client(cmd) {
// Success
} else {
panic!("Expected PowerOff");
}
}
#[test]
fn test_rig_command_to_client_toggle_vfo() {
let cmd = RigCommand::ToggleVfo;
if let ClientCommand::ToggleVfo = rig_command_to_client(cmd) {
// Success
} else {
panic!("Expected ToggleVfo");
}
}
#[test]
fn test_rig_command_to_client_lock() {
let cmd = RigCommand::Lock;
if let ClientCommand::Lock = rig_command_to_client(cmd) {
// Success
} else {
panic!("Expected Lock");
}
}
#[test]
fn test_rig_command_to_client_unlock() {
let cmd = RigCommand::Unlock;
if let ClientCommand::Unlock = rig_command_to_client(cmd) {
// Success
} else {
panic!("Expected Unlock");
}
}
#[test]
fn test_rig_command_to_client_get_tx_limit() {
let cmd = RigCommand::GetTxLimit;
if let ClientCommand::GetTxLimit = rig_command_to_client(cmd) {
// Success
} else {
panic!("Expected GetTxLimit");
}
}
#[test]
fn test_rig_command_to_client_set_tx_limit() {
let cmd = RigCommand::SetTxLimit(50);
if let ClientCommand::SetTxLimit { limit } = rig_command_to_client(cmd) {
assert_eq!(limit, 50);
} else {
panic!("Expected SetTxLimit");
}
}
#[test]
fn test_rig_command_to_client_set_aprs_decode_enabled() {
let cmd = RigCommand::SetAprsDecodeEnabled(true);
if let ClientCommand::SetAprsDecodeEnabled { enabled } = rig_command_to_client(cmd) {
assert_eq!(enabled, true);
} else {
panic!("Expected SetAprsDecodeEnabled");
}
}
#[test]
fn test_rig_command_to_client_set_cw_decode_enabled() {
let cmd = RigCommand::SetCwDecodeEnabled(false);
if let ClientCommand::SetCwDecodeEnabled { enabled } = rig_command_to_client(cmd) {
assert_eq!(enabled, false);
} else {
panic!("Expected SetCwDecodeEnabled");
}
}
#[test]
fn test_rig_command_to_client_set_cw_auto() {
let cmd = RigCommand::SetCwAuto(true);
if let ClientCommand::SetCwAuto { enabled } = rig_command_to_client(cmd) {
assert_eq!(enabled, true);
} else {
panic!("Expected SetCwAuto");
}
}
#[test]
fn test_rig_command_to_client_set_cw_wpm() {
let cmd = RigCommand::SetCwWpm(25);
if let ClientCommand::SetCwWpm { wpm } = rig_command_to_client(cmd) {
assert_eq!(wpm, 25);
} else {
panic!("Expected SetCwWpm");
}
}
#[test]
fn test_rig_command_to_client_set_cw_tone_hz() {
let cmd = RigCommand::SetCwToneHz(800);
if let ClientCommand::SetCwToneHz { tone_hz } = rig_command_to_client(cmd) {
assert_eq!(tone_hz, 800);
} else {
panic!("Expected SetCwToneHz");
}
}
#[test]
fn test_rig_command_to_client_set_ft8_decode_enabled() {
let cmd = RigCommand::SetFt8DecodeEnabled(true);
if let ClientCommand::SetFt8DecodeEnabled { enabled } = rig_command_to_client(cmd) {
assert_eq!(enabled, true);
} else {
panic!("Expected SetFt8DecodeEnabled");
}
}
#[test]
fn test_rig_command_to_client_reset_aprs_decoder() {
let cmd = RigCommand::ResetAprsDecoder;
if let ClientCommand::ResetAprsDecoder = rig_command_to_client(cmd) {
// Success
} else {
panic!("Expected ResetAprsDecoder");
}
}
#[test]
fn test_rig_command_to_client_reset_cw_decoder() {
let cmd = RigCommand::ResetCwDecoder;
if let ClientCommand::ResetCwDecoder = rig_command_to_client(cmd) {
// Success
} else {
panic!("Expected ResetCwDecoder");
}
}
#[test]
fn test_rig_command_to_client_reset_ft8_decoder() {
let cmd = RigCommand::ResetFt8Decoder;
if let ClientCommand::ResetFt8Decoder = rig_command_to_client(cmd) {
// Success
} else {
panic!("Expected ResetFt8Decoder");
}
}
#[test]
fn test_round_trip_set_freq() {
let original = ClientCommand::SetFreq { freq_hz: 7050000 };
let rig_cmd = client_command_to_rig(original);
let client_cmd = rig_command_to_client(rig_cmd);
if let ClientCommand::SetFreq { freq_hz } = client_cmd {
assert_eq!(freq_hz, 7050000);
} else {
panic!("Round trip failed");
}
}
#[test]
fn test_round_trip_set_mode_standard() {
let original = ClientCommand::SetMode {
mode: "USB".to_string(),
};
let rig_cmd = client_command_to_rig(original);
let client_cmd = rig_command_to_client(rig_cmd);
if let ClientCommand::SetMode { mode } = client_cmd {
assert_eq!(mode, "USB");
} else {
panic!("Round trip failed");
}
}
#[test]
fn test_round_trip_set_ptt() {
let original = ClientCommand::SetPtt { ptt: false };
let rig_cmd = client_command_to_rig(original);
let client_cmd = rig_command_to_client(rig_cmd);
if let ClientCommand::SetPtt { ptt } = client_cmd {
assert_eq!(ptt, false);
} else {
panic!("Round trip failed");
}
}
}