[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:
@@ -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" }
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user