From 8448d539375e20522a711b26966d9de231b9c336 Mon Sep 17 00:00:00 2001 From: Stephen D <webmaster@scd31.com> Date: Sat, 14 Oct 2023 13:15:30 -0300 Subject: [PATCH 1/2] two-way FELINET <-> APRS --- Cargo.lock | 299 ++++++++++++++++++++++++++++++++++++++++++++-------- Cargo.toml | 2 +- src/aprs.rs | 66 +++++++++--- src/main.rs | 189 +++++++++++++++++++++++++++++++-- 4 files changed, 492 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d6859d7..23c2c78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -176,6 +176,12 @@ dependencies = [ "wyz", ] +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + [[package]] name = "bytes" version = "1.5.0" @@ -197,6 +203,22 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + [[package]] name = "crc" version = "3.0.1" @@ -247,25 +269,14 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "errno-dragonfly", "libc", "windows-sys", ] -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "fastrand" version = "2.0.1" @@ -614,6 +625,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "labrador-ldpc" version = "1.1.1" @@ -634,9 +654,9 @@ checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "linux-raw-sys" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45786cec4d5e54a224b15cb9f06751883103a27c19c93eda09b0b4f5f08fefac" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "lock_api" @@ -723,6 +743,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "parking_lot" version = "0.12.1" @@ -818,9 +844,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -958,9 +984,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.6" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" +checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87" dependencies = [ "aho-corasick", "memchr", @@ -970,9 +996,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.9" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b" dependencies = [ "aho-corasick", "memchr", @@ -981,9 +1007,24 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "56d84fdd47036b038fc80dd333d10b6aab10d5d31f4a366e20014def75328d33" + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] [[package]] name = "rustc-demangle" @@ -993,9 +1034,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.17" +version = "0.38.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7" +checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed" dependencies = [ "bitflags 2.4.0", "errno", @@ -1004,32 +1045,117 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "rustls" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" -version = "1.0.188" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" dependencies = [ "proc-macro2", "quote", @@ -1127,6 +1253,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "syn" version = "1.0.109" @@ -1196,9 +1328,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.32.0" +version = "1.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" dependencies = [ "backtrace", "bytes", @@ -1234,6 +1366,16 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -1312,7 +1454,11 @@ dependencies = [ "percent-encoding", "pin-project", "prost 0.12.1", + "rustls", + "rustls-native-certs", + "rustls-pemfile", "tokio", + "tokio-rustls", "tokio-stream", "tower", "tower-layer", @@ -1367,11 +1513,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "ee2ef2af84856a50c1d430afce2fdded0a4ec7eda868db86409b4543df0797f9" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1379,9 +1524,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", @@ -1390,9 +1535,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] @@ -1409,6 +1554,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "want" version = "0.3.1" @@ -1424,6 +1575,70 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.38", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "which" version = "4.4.2" @@ -1526,9 +1741,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.16" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711d82167854aff2018dfd193aa0fef5370f456732f0d5a0c59b0f1b4b907" +checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 478008a..a940ad1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ serde = { version = "1.0.188", features = ["derive"] } simple-aprs = "0.3.2" tokio = { version = "1.32.0", features = ["full"] } toml = "0.8.2" -tonic = "0.10.2" +tonic = { version = "0.10.2", features = ["tls", "tls-roots"] } ham-cats = { git = "https://gitlab.scd31.com/cats/ham-cats"} half = "2.3.1" geoutils = "0.5.1" diff --git a/src/aprs.rs b/src/aprs.rs index 5c9e281..8147482 100644 --- a/src/aprs.rs +++ b/src/aprs.rs @@ -3,13 +3,20 @@ use std::time::Duration; use aprs_parser::AprsPacket; use futures::StreamExt; use simple_aprs::{ISConnection, ISSettings, RawPacket}; -use tokio::sync::broadcast; +use tokio::{ + pin, select, + sync::{broadcast, mpsc, oneshot}, +}; use crate::config::AprsConfig; -pub async fn connect(config: &AprsConfig, tx: broadcast::Sender<AprsPacket>) { +pub async fn connect( + config: &AprsConfig, + tx: broadcast::Sender<AprsPacket>, + mut rx: mpsc::Receiver<AprsPacket>, +) { loop { - tokio::time::sleep(Duration::from_millis(100)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; println!("Connecting to APRS..."); @@ -33,17 +40,52 @@ pub async fn connect(config: &AprsConfig, tx: broadcast::Sender<AprsPacket>) { println!("Connected to APRS."); - let (mut aprs_read, aprs_write) = aprs_is.split(); + let (mut aprs_read, mut aprs_write) = aprs_is.split(); - aprs_read - .stream() - .for_each(|x| async { - match x { - Ok(packet) => handle_aprs_packet(&packet, &tx).await, - Err(e) => eprintln!("APRS-IS error: {e}"), + // if this task dies we need a way to restart the loop + // we use a oneshot for this + let (reader_died_tx, reader_died_rx) = oneshot::channel(); + let (writer_died_tx, writer_died_rx) = oneshot::channel(); + + { + let tx = tx.clone(); + + tokio::task::spawn(async move { + let stream = aprs_read.stream(); + pin!(stream); + + select! { + _ = async { + while let Some(x) = stream.next().await { + match x { + Ok(packet) => handle_aprs_packet(&packet, &tx).await, + Err(e) => { + eprintln!("APRS-IS error: {e}"); + reader_died_tx.send(()).ok(); + break; + } + } + } + } => {} + + _ = writer_died_rx => {} } - }) - .await; + }); + } + + select! { + _ = async { + while let Some(pkt) = rx.recv().await { + if let Err(e) = aprs_write.send(&pkt).await { + eprintln!("APRS-IS error: {e}"); + writer_died_tx.send(()).ok(); + break; + } + } + } => {}, + + _ = reader_died_rx => {} + }; } } diff --git a/src/main.rs b/src/main.rs index 01130be..a7ad7a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,31 @@ use std::time::Duration; -use anyhow::{anyhow, Context}; -use aprs_parser::{AprsData, AprsPacket}; +use anyhow::{anyhow, bail, Context}; +use aprs_parser::{ + AprsCst, AprsData, AprsMessage, AprsPacket, Callsign, Latitude, Longitude, Precision, + QConstruct, Via, +}; use async_stream::stream; use config::{Config, FelinetConfig}; -use felinet::{handler_client::HandlerClient, Packet as SemiPacket}; +use felinet::{ + handler_client::HandlerClient, packet_filter::Filter, Packet as SemiPacket, PacketFilter, +}; +use futures::StreamExt; use geoutils::Location; use half::f16; use ham_cats::{ + error::CommentError, packet::Packet as CatsPacket, - whisker::{Destination, Gps, Identification, Route}, + whisker::{Arbitrary, Destination, Gps, Identification, Route, RouteNode}, }; -use tokio::sync::broadcast; +use tokio::sync::{broadcast, mpsc}; use tonic::Request; mod aprs; mod config; +const MAX_PACKET_LEN: usize = 8191; + pub mod felinet { tonic::include_proto!("felinet"); } @@ -24,19 +33,36 @@ pub mod felinet { #[tokio::main] async fn main() -> anyhow::Result<()> { let config = Config::load()?; + let aprs_callsign = Callsign::new(&config.aprs.callsign) + .context("Given APRS callsign is not a valid callsign")?; let aprs_recv_tx = broadcast::Sender::new(1024); + let (aprs_send_tx, aprs_send_rx) = mpsc::channel(1024); { + let config = config.clone(); let aprs_recv_tx = aprs_recv_tx.clone(); tokio::task::spawn(async move { - aprs::connect(&config.aprs, aprs_recv_tx).await; + aprs::connect(&config.aprs, aprs_recv_tx, aprs_send_rx).await; + }); + } + + { + let config = config.clone(); + tokio::task::spawn(async move { + loop { + if let Err(e) = felinet_tx_forever(&config.felinet, aprs_recv_tx.subscribe()).await + { + eprintln!("FELINET TX error: {e}"); + tokio::time::sleep(Duration::from_secs(1)).await; + } + } }); } loop { - if let Err(e) = felinet_tx_forever(&config.felinet, aprs_recv_tx.subscribe()).await { - eprintln!("FELINET error: {e}"); + if let Err(e) = felinet_rx_forever(&config.felinet, &aprs_callsign, &aprs_send_tx).await { + eprintln!("FELINET RX error: {e}"); tokio::time::sleep(Duration::from_secs(1)).await; } } @@ -62,12 +88,57 @@ async fn felinet_tx_forever( client.push_packets(Request::new(felinet_stream)).await?; + bail!("TX stream ended"); +} + +async fn felinet_rx_forever( + config: &FelinetConfig, + aprs_callsign: &Callsign, + aprs_send_tx: &mpsc::Sender<AprsPacket>, +) -> anyhow::Result<()> { + let mut client = HandlerClient::connect(config.address.clone()).await?; + + let mut stream = client + .get_packets(Request::new(PacketFilter { + filter: Some(Filter::All(0)), + })) + .await? + .into_inner(); + + while let Some(semi) = stream.next().await { + handle_semi(semi?, aprs_callsign, aprs_send_tx).await?; + } + + bail!("RX stream ended"); +} + +async fn handle_semi( + semi: SemiPacket, + aprs_callsign: &Callsign, + aprs_send_tx: &mpsc::Sender<AprsPacket>, +) -> anyhow::Result<()> { + let pkt = CatsPacket::<MAX_PACKET_LEN>::semi_decode( + semi.raw[..].try_into().ok().context("Capacity error")?, + )?; + + if pkt.arbitrary_iter().any(|a| a.0[..] == [0xC0]) { + // packet came from APRS initially + return Ok(()); + } + + let aprs = felinet_to_aprs(pkt, aprs_callsign)?; + + for p in aprs { + aprs_send_tx.send(p).await?; + } + Ok(()) } fn aprs_to_felinet(aprs: &AprsPacket) -> anyhow::Result<Option<SemiPacket>> { - let mut cats: CatsPacket<8191> = CatsPacket::default(); + let mut cats: CatsPacket<MAX_PACKET_LEN> = CatsPacket::default(); + // TODO map icons between standards? cats.add_identification( Identification::new( 0, @@ -78,10 +149,15 @@ fn aprs_to_felinet(aprs: &AprsPacket) -> anyhow::Result<Option<SemiPacket>> { )?; // 3 is arbitrary + // TODO could be a bit smarter by using the packet via let mut r = Route::new(3); r.push_internet(); cats.add_route(r)?; + // used so that we can tell which CATS packets came from APRS + // so that we don't digipeat them back to APRS + cats.add_arbitrary(Arbitrary::new(&[0xC0]).unwrap())?; + // TODO can definitely make this better match &aprs.data { // TODO should do something with CST in here @@ -162,3 +238,98 @@ fn aprs_to_felinet(aprs: &AprsPacket) -> anyhow::Result<Option<SemiPacket>> { Ok(Some(semi)) } + +// TODO need a way to prevent loops like FELINET -> APRS -> FELINET +// Can probably use the destination field, except we'll need to do something special for Mic-E + +// CATS can encode things in one packet that may require separate packets in APRS. +// The solution is to create multiple APRS packets +fn felinet_to_aprs( + cats: CatsPacket<MAX_PACKET_LEN>, + aprs_callsign: &Callsign, +) -> anyhow::Result<Vec<AprsPacket>> { + // (comment & destination) -> (message) + // (comment & destination & gps) -> (message, position) + // (comment & gps) -> (position) + let mut out = vec![]; + + let ident = match cats.identification() { + Some(x) => x, + None => return Ok(out), + }; + let from = Callsign::new_with_ssid(ident.callsign.as_str(), format!("{}", ident.ssid)); + + let mut via = vec![]; + + if let Some(r) = cats.route() { + for x in r.iter() { + match x { + RouteNode::Internet => { + via.push(Via::QConstruct(QConstruct::Ar)); + via.push(Via::Callsign(aprs_callsign.to_owned(), true)); + } + RouteNode::Identity(callsign, ssid, false) => { + via.push(Via::Callsign( + Callsign::new_with_ssid(callsign, format!("{}", ssid)), + true, + )); + } + RouteNode::Identity(_, _, true) => {} + } + } + }; + + let mut buf = [0; MAX_PACKET_LEN]; + let comment = match cats.comment(&mut buf) { + Ok(c) => Some(c), + Err(CommentError::NoComment) => None, + // buffer is as long as the max packet length + Err(CommentError::BufferOverflow) => unreachable!(), + }; + + // one message packet per destination + if let Some(text) = comment { + for dest in cats.destination_iter() { + let id = match dest.ack_num() { + 0 => None, + x => Some(format!("{}", x).into_bytes()), + }; + + out.push(AprsPacket { + from: from.clone(), + via: via.clone(), + data: AprsData::Message(AprsMessage { + to: Callsign::new_no_ssid("APRS"), + addressee: format!("{}-{}", ident.callsign, ident.ssid).into_bytes(), + text: format!("[CATS] {}", text).into_bytes(), + id, + }), + }); + } + } + + if let Some(gps) = cats.gps() { + let text = format!("[CATS] {}", comment.unwrap_or("")); + + out.push(AprsPacket { + from, + via, + data: AprsData::Position(aprs_parser::AprsPosition { + to: Callsign::new_no_ssid("APRS"), + timestamp: None, + messaging_supported: true, + latitude: Latitude::new(gps.latitude()).context("Invalid latitude")?, + longitude: Longitude::new(gps.longitude()).context("Invalid longitude")?, + precision: Precision::TenthMinute, + // TODO map icons between standards? + symbol_table: '/', + symbol_code: 'p', + + comment: text.into(), + cst: AprsCst::Uncompressed, + }), + }); + } + + Ok(out) +} -- GitLab From 1b714bf3f73ffe6f6fb23c5c399a92b404f331d0 Mon Sep 17 00:00:00 2001 From: Stephen D <webmaster@scd31.com> Date: Sat, 14 Oct 2023 13:24:56 -0300 Subject: [PATCH 2/2] prevent FELINET -> APRS -> FELINET loop --- src/main.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index a7ad7a0..a03b550 100644 --- a/src/main.rs +++ b/src/main.rs @@ -136,6 +136,11 @@ async fn handle_semi( } fn aprs_to_felinet(aprs: &AprsPacket) -> anyhow::Result<Option<SemiPacket>> { + // Was a FELINET packet initially, so we don't want to repeat it again + if aprs.to() == Some(&Callsign::new_no_ssid("KITTY")) { + return Ok(None); + } + let mut cats: CatsPacket<MAX_PACKET_LEN> = CatsPacket::default(); // TODO map icons between standards? @@ -239,9 +244,6 @@ fn aprs_to_felinet(aprs: &AprsPacket) -> anyhow::Result<Option<SemiPacket>> { Ok(Some(semi)) } -// TODO need a way to prevent loops like FELINET -> APRS -> FELINET -// Can probably use the destination field, except we'll need to do something special for Mic-E - // CATS can encode things in one packet that may require separate packets in APRS. // The solution is to create multiple APRS packets fn felinet_to_aprs( @@ -299,7 +301,7 @@ fn felinet_to_aprs( from: from.clone(), via: via.clone(), data: AprsData::Message(AprsMessage { - to: Callsign::new_no_ssid("APRS"), + to: Callsign::new_no_ssid("KITTY"), addressee: format!("{}-{}", ident.callsign, ident.ssid).into_bytes(), text: format!("[CATS] {}", text).into_bytes(), id, @@ -315,7 +317,7 @@ fn felinet_to_aprs( from, via, data: AprsData::Position(aprs_parser::AprsPosition { - to: Callsign::new_no_ssid("APRS"), + to: Callsign::new_no_ssid("KITTY"), timestamp: None, messaging_supported: true, latitude: Latitude::new(gps.latitude()).context("Invalid latitude")?, -- GitLab