From 1a444762d8fd7d48e4f56a87c6bd77f837522d5d Mon Sep 17 00:00:00 2001
From: Thomas Karpiniec <tom.karpiniec@outlook.com>
Date: Wed, 22 Jan 2025 22:06:05 +1100
Subject: [PATCH] Round trip packet modulation and demodulation via RRC

---
 Cargo.lock                       | 21 ++++++++++++++++++
 Cargo.toml                       |  2 +-
 m17app/src/adapter.rs            | 32 ++++++++++++++++-----------
 m17app/src/app.rs                | 11 +++-------
 m17app/src/link_setup.rs         |  8 +++++++
 m17codec2/src/lib.rs             |  2 +-
 m17core/src/tnc.rs               | 15 +++++++------
 tools/m17rt-rxpacket/Cargo.toml  | 15 +++++++++++++
 tools/m17rt-rxpacket/src/main.rs | 37 ++++++++++++++++++++++++++++++++
 tools/m17rt-txpacket/Cargo.toml  | 14 ++++++++++++
 tools/m17rt-txpacket/src/main.rs | 33 ++++++++++++++++++++++++++++
 11 files changed, 161 insertions(+), 29 deletions(-)
 create mode 100755 tools/m17rt-rxpacket/Cargo.toml
 create mode 100755 tools/m17rt-rxpacket/src/main.rs
 create mode 100644 tools/m17rt-txpacket/Cargo.toml
 create mode 100644 tools/m17rt-txpacket/src/main.rs

diff --git a/Cargo.lock b/Cargo.lock
index 8faa53c..a1a9c8b 100755
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -505,6 +505,27 @@ dependencies = [
  "m17core",
 ]
 
+[[package]]
+name = "m17rt-rxpacket"
+version = "0.1.0"
+dependencies = [
+ "cpal",
+ "env_logger",
+ "log",
+ "m17app",
+ "m17core",
+]
+
+[[package]]
+name = "m17rt-txpacket"
+version = "0.1.0"
+dependencies = [
+ "env_logger",
+ "log",
+ "m17app",
+ "m17core",
+]
+
 [[package]]
 name = "mach2"
 version = "0.4.2"
diff --git a/Cargo.toml b/Cargo.toml
index 2e53dec..8d1b91d 100755
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
 [workspace]
 resolver = "2"
 members = [
-    "m17app", "m17codec2", "m17core", "tools/m17rt-demod", "tools/m17rt-mod",
+    "m17app", "m17codec2", "m17core", "tools/m17rt-demod", "tools/m17rt-mod", "tools/m17rt-txpacket", "tools/m17rt-rxpacket"
 ]
diff --git a/m17app/src/adapter.rs b/m17app/src/adapter.rs
index a03cf09..57e01bb 100644
--- a/m17app/src/adapter.rs
+++ b/m17app/src/adapter.rs
@@ -1,22 +1,28 @@
-use crate::app::TxHandle;
-use m17core::protocol::{LsfFrame, PacketType};
+use crate::{app::TxHandle, link_setup::LinkSetup};
+use m17core::protocol::PacketType;
 use std::sync::Arc;
 
 pub trait PacketAdapter: Send + Sync + 'static {
-    fn adapter_registered(&self, id: usize, handle: TxHandle);
-    fn adapter_removed(&self);
-    fn tnc_started(&self);
-    fn tnc_closed(&self);
-    fn packet_received(&self, lsf: LsfFrame, packet_type: PacketType, content: Arc<[u8]>);
+    fn adapter_registered(&self, _id: usize, _handle: TxHandle) {}
+    fn adapter_removed(&self) {}
+    fn tnc_started(&self) {}
+    fn tnc_closed(&self) {}
+    fn packet_received(
+        &self,
+        _link_setup: LinkSetup,
+        _packet_type: PacketType,
+        _content: Arc<[u8]>,
+    ) {
+    }
 }
 
 pub trait StreamAdapter: Send + Sync + 'static {
-    fn adapter_registered(&self, id: usize, handle: TxHandle);
-    fn adapter_removed(&self);
-    fn tnc_started(&self);
-    fn tnc_closed(&self);
-    fn stream_began(&self, lsf: LsfFrame);
-    fn stream_data(&self, frame_number: u16, is_final: bool, data: Arc<[u8; 16]>);
+    fn adapter_registered(&self, _id: usize, _handle: TxHandle) {}
+    fn adapter_removed(&self) {}
+    fn tnc_started(&self) {}
+    fn tnc_closed(&self) {}
+    fn stream_began(&self, _link_setup: LinkSetup) {}
+    fn stream_data(&self, _frame_number: u16, _is_final: bool, _data: Arc<[u8; 16]>) {}
 
     // TODO
     // fn stream_lost(&self);
diff --git a/m17app/src/app.rs b/m17app/src/app.rs
index a7bb3cd..7d363dd 100644
--- a/m17app/src/app.rs
+++ b/m17app/src/app.rs
@@ -84,12 +84,7 @@ pub struct TxHandle {
 }
 
 impl TxHandle {
-    pub fn transmit_packet(
-        &self,
-        link_setup: &LinkSetup,
-        packet_type: &PacketType,
-        payload: &[u8],
-    ) {
+    pub fn transmit_packet(&self, link_setup: &LinkSetup, packet_type: PacketType, payload: &[u8]) {
         let (pack_type, pack_type_len) = packet_type.as_proto();
         if pack_type_len + payload.len() > 823 {
             // TODO: error for invalid transmission type
@@ -204,7 +199,7 @@ fn spawn_reader<T: Tnc>(mut tnc: T, adapters: Arc<RwLock<Adapters>>) {
                             adapters.read().unwrap().packet.values().cloned().collect();
                         for s in subs {
                             s.packet_received(
-                                lsf.clone(),
+                                LinkSetup::new_raw(lsf.clone()),
                                 packet_type.clone(),
                                 packet_payload.clone(),
                             );
@@ -226,7 +221,7 @@ fn spawn_reader<T: Tnc>(mut tnc: T, adapters: Arc<RwLock<Adapters>>) {
                             let subs: Vec<_> =
                                 adapters.read().unwrap().stream.values().cloned().collect();
                             for s in subs {
-                                s.stream_began(lsf.clone());
+                                s.stream_began(LinkSetup::new_raw(lsf.clone()));
                             }
                         } else if n == 26 {
                             if !stream_running {
diff --git a/m17app/src/link_setup.rs b/m17app/src/link_setup.rs
index 007dc78..2a701de 100644
--- a/m17app/src/link_setup.rs
+++ b/m17app/src/link_setup.rs
@@ -17,6 +17,14 @@ impl LinkSetup {
         Self { raw: frame }
     }
 
+    pub fn source(&self) -> M17Address {
+        M17Address(self.raw.source())
+    }
+
+    pub fn destination(&self) -> M17Address {
+        M17Address(self.raw.destination())
+    }
+
     /// Set up an unencrypted voice stream with channel access number 0 and the given source and destination.
     pub fn new_voice(source: &M17Address, destination: &M17Address) -> Self {
         Self {
diff --git a/m17codec2/src/lib.rs b/m17codec2/src/lib.rs
index 5eb54a4..0f3c8a5 100755
--- a/m17codec2/src/lib.rs
+++ b/m17codec2/src/lib.rs
@@ -93,7 +93,7 @@ impl StreamAdapter for Codec2Adapter {
 
     fn tnc_closed(&self) {}
 
-    fn stream_began(&self, _lsf: LsfFrame) {
+    fn stream_began(&self, _link_setup: LinkSetup) {
         // for now we will assume:
         // - unencrypted
         // - data type is Voice (Codec2 3200), not Voice+Data
diff --git a/m17core/src/tnc.rs b/m17core/src/tnc.rs
index 8cd0152..93a4363 100644
--- a/m17core/src/tnc.rs
+++ b/m17core/src/tnc.rs
@@ -117,7 +117,10 @@ impl SoftTnc {
                     Mode::Stream => {
                         let kiss = KissFrame::new_stream_setup(&lsf.0).unwrap();
                         self.kiss_to_host(kiss);
-                        self.state = State::RxStream(RxStreamState { lsf, index: 0 });
+                        self.state = State::RxStream(RxStreamState {
+                            _lsf: lsf,
+                            index: 0,
+                        });
                     }
                 }
             }
@@ -139,7 +142,7 @@ impl SoftTnc {
                                 let start = 25 * rx.count;
                                 let end = start + payload_len;
                                 rx.packet[start..(start + payload_len)]
-                                    .copy_from_slice(&packet.payload);
+                                    .copy_from_slice(&packet.payload[0..payload_len]);
                                 // TODO: compatible packets should be sent on port 0 too
                                 let kiss =
                                     KissFrame::new_full_packet(&rx.lsf.0, &rx.packet[0..end])
@@ -186,7 +189,7 @@ impl SoftTnc {
                                 // TODO: avoid discarding the first data payload here
                                 // need a queue depth of 2 for outgoing kiss
                                 self.state = State::RxStream(RxStreamState {
-                                    lsf,
+                                    _lsf: lsf,
                                     index: stream.frame_number + 1,
                                 });
                             }
@@ -431,7 +434,7 @@ impl SoftTnc {
                 }
                 pending.lsf = Some(lsf);
                 let app_data_len = len - 30;
-                pending.app_data[0..app_data_len].copy_from_slice(&payload[30..]);
+                pending.app_data[0..app_data_len].copy_from_slice(&payload[30..len]);
                 pending.app_data_len = app_data_len;
                 self.packet_queue[self.packet_next] = pending;
                 self.packet_next = (self.packet_next + 1) % 4;
@@ -531,7 +534,7 @@ struct RxAcquiringStreamState {
 
 struct RxStreamState {
     /// Track identifying information for this transmission so we can tell if it changes.
-    lsf: LsfFrame,
+    _lsf: LsfFrame,
 
     /// Expected next frame number. Allowed to skip values on RX, but not go backwards.
     index: u16,
@@ -594,7 +597,7 @@ impl PendingPacket {
             )
         };
         let mut payload = [0u8; 25];
-        payload.copy_from_slice(
+        payload[0..data_len].copy_from_slice(
             &self.app_data[self.app_data_transmitted..(self.app_data_transmitted + data_len)],
         );
         self.app_data_transmitted += data_len;
diff --git a/tools/m17rt-rxpacket/Cargo.toml b/tools/m17rt-rxpacket/Cargo.toml
new file mode 100755
index 0000000..2cab9a5
--- /dev/null
+++ b/tools/m17rt-rxpacket/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "m17rt-rxpacket"
+version = "0.1.0"
+edition = "2021"
+license = "MIT"
+authors = ["Thomas Karpiniec <tom.karpiniec@outlook.com"]
+publish = false
+
+[dependencies]
+m17core = { path = "../../m17core" }
+m17app = { path = "../../m17app" }
+
+cpal = "0.15.3"
+env_logger = "0.11.6"
+log = "0.4.22"
diff --git a/tools/m17rt-rxpacket/src/main.rs b/tools/m17rt-rxpacket/src/main.rs
new file mode 100755
index 0000000..1cdd1d8
--- /dev/null
+++ b/tools/m17rt-rxpacket/src/main.rs
@@ -0,0 +1,37 @@
+use m17app::adapter::PacketAdapter;
+use m17app::app::M17App;
+use m17app::link_setup::LinkSetup;
+use m17app::soundmodem::{InputRrcFile, NullOutputSink, NullPtt, Soundmodem};
+use m17core::protocol::PacketType;
+use std::path::PathBuf;
+use std::sync::Arc;
+
+fn main() {
+    let path = PathBuf::from("../../../Data/mypacket.rrc");
+    let soundmodem = Soundmodem::new(
+        InputRrcFile::new(path),
+        NullOutputSink::new(),
+        NullPtt::new(),
+    );
+    let app = M17App::new(soundmodem);
+    app.add_packet_adapter(PacketPrinter);
+    app.start();
+
+    loop {
+        std::thread::park();
+    }
+}
+
+struct PacketPrinter;
+impl PacketAdapter for PacketPrinter {
+    fn packet_received(&self, link_setup: LinkSetup, packet_type: PacketType, content: Arc<[u8]>) {
+        println!(
+            "from {} to {} type {:?} len {}",
+            link_setup.source(),
+            link_setup.destination(),
+            packet_type,
+            content.len()
+        );
+        println!("{}", String::from_utf8_lossy(&content));
+    }
+}
diff --git a/tools/m17rt-txpacket/Cargo.toml b/tools/m17rt-txpacket/Cargo.toml
new file mode 100644
index 0000000..4f0283c
--- /dev/null
+++ b/tools/m17rt-txpacket/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "m17rt-txpacket"
+version = "0.1.0"
+edition = "2021"
+license = "MIT"
+authors = ["Thomas Karpiniec <tom.karpiniec@outlook.com"]
+publish = false
+
+[dependencies]
+m17core = { path = "../../m17core" }
+m17app = { path = "../../m17app" }
+
+env_logger = "0.11.6"
+log = "0.4.22"
diff --git a/tools/m17rt-txpacket/src/main.rs b/tools/m17rt-txpacket/src/main.rs
new file mode 100644
index 0000000..538291a
--- /dev/null
+++ b/tools/m17rt-txpacket/src/main.rs
@@ -0,0 +1,33 @@
+use m17app::app::M17App;
+use m17app::link_setup::{LinkSetup, M17Address};
+use m17app::soundmodem::{
+    InputRrcFile, InputSoundcard, NullInputSource, NullOutputSink, NullPtt, OutputRrcFile,
+    OutputSoundcard, Soundmodem,
+};
+use m17core::protocol::PacketType;
+use std::path::PathBuf;
+
+pub fn mod_test() {
+    let out_path = PathBuf::from("../../../Data/mypacket.rrc");
+    let output = OutputRrcFile::new(out_path);
+    //let output = OutputSoundcard::new();
+    let soundmodem = Soundmodem::new(NullInputSource::new(), output, NullPtt::new());
+    let app = M17App::new(soundmodem);
+    app.start();
+    std::thread::sleep(std::time::Duration::from_secs(1));
+    println!("Transmitting packet...");
+
+    let source = M17Address::from_callsign("VK7XT").unwrap();
+    let destination = M17Address::new_broadcast();
+    let link_setup = LinkSetup::new_packet(&source, &destination);
+    let payload = b"Hello, world!";
+    app.tx()
+        .transmit_packet(&link_setup, PacketType::Raw, payload);
+
+    std::thread::sleep(std::time::Duration::from_secs(5));
+}
+
+fn main() {
+    env_logger::init();
+    mod_test();
+}
-- 
2.39.5