initial ver
This commit is contained in:
commit
020739397c
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
*.pcap
|
||||
306
Cargo.lock
generated
Normal file
306
Cargo.lock
generated
Normal file
@ -0,0 +1,306 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "ax25"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fec6189a57a9668a17aa3368d110b1451450df47b85200f43d93357fb21f3b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder_slice"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b294e30387378958e8bf8f4242131b930ea615ff81e8cac2440cea0a6013190"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "derive-into-owned"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d94d81e3819a7b06a8638f448bc6339371ca9b6076a99d4a43eece3c4c923"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "kissdump"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ax25",
|
||||
"clap",
|
||||
"pcap-file",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pcap-file"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc1f139757b058f9f37b76c48501799d12c9aa0aa4c0d4c980b062ee925d1b2"
|
||||
dependencies = [
|
||||
"byteorder_slice",
|
||||
"derive-into-owned",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "kissdump"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
ax25 = "0.3.0"
|
||||
clap = { version = "4.4.2", features = ["derive"] }
|
||||
pcap-file = "2.0.0"
|
||||
251
examples/raw2pcap.rs
Normal file
251
examples/raw2pcap.rs
Normal file
@ -0,0 +1,251 @@
|
||||
use clap::Parser;
|
||||
use std::{
|
||||
fs::File,
|
||||
path::PathBuf,
|
||||
};
|
||||
use std::io::Read;
|
||||
|
||||
use anyhow::Result;
|
||||
use pcap_file::{DataLink, pcap::{PcapPacket, PcapWriter, PcapHeader}};
|
||||
use std::time::SystemTime;
|
||||
|
||||
use ax25::frame::{CommandResponse, Ax25Frame, FrameContent};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Arg {
|
||||
#[arg(short, long, default_value_t = false)]
|
||||
verbose: bool,
|
||||
infile: PathBuf,
|
||||
outfile: PathBuf,
|
||||
}
|
||||
trait PrintData {
|
||||
fn type_str_short(&self) -> &'static str;
|
||||
fn print_short(&self);
|
||||
fn print_long(&self);
|
||||
}
|
||||
impl PrintData for Ax25Frame {
|
||||
fn type_str_short(&self) -> &'static str {
|
||||
match self.content {
|
||||
FrameContent::Information(_) => "Info",
|
||||
FrameContent::ReceiveReady(_) => "RecvRdy",
|
||||
FrameContent::ReceiveNotReady(_) => "RecvNotRdy",
|
||||
FrameContent::Reject(_) => "Reject",
|
||||
FrameContent::SetAsynchronousBalancedMode(_) => "AsyncBalMode", //12 long
|
||||
FrameContent::Disconnect(_) => "Disconnect",
|
||||
FrameContent::DisconnectedMode(_) => "DisconMode",
|
||||
FrameContent::UnnumberedAcknowledge(_) => "UnNumAck",
|
||||
FrameContent::FrameReject(_) => "FrameReject",
|
||||
FrameContent::UnnumberedInformation(_) => "UnNumInfo",
|
||||
FrameContent::UnknownContent(_) => "Unknown",
|
||||
}
|
||||
}
|
||||
fn print_short(&self) {
|
||||
//Format: "[source] -> [dest] via [..] [CMD/RSP/NIL] [frameType]: [DATA str rep]"
|
||||
let src = self.source.to_string();
|
||||
let dst = self.destination.to_string();
|
||||
//let via: String = self.route.iter().map(|re| format!(" {} ", re.repeater.to_string())).collect(); //todo
|
||||
let rsp = match self.command_or_response {
|
||||
Some(CommandResponse::Command) => "CMD",
|
||||
Some(CommandResponse::Response) => "RSP",
|
||||
None => "NIL",
|
||||
};
|
||||
let ftype = self.type_str_short();
|
||||
|
||||
//let mut rawdata;
|
||||
let (rawdata, finfo) = match &self.content {
|
||||
FrameContent::Information(_i) =>
|
||||
(_i.info.clone(),
|
||||
format!("pid: {:?}, seq: {}/{}, info: ", _i.pid, _i.send_sequence, _i.receive_sequence)),
|
||||
FrameContent::ReceiveReady(rr) => (vec![], format!("seq: {}, poll/final: {}", rr.receive_sequence, rr.poll_or_final)),
|
||||
FrameContent::ReceiveNotReady(rnr) => (vec![], format!("seq: {}, poll/final: {}", rnr.receive_sequence, rnr.poll_or_final)),
|
||||
FrameContent::Reject(rnr) => (vec![], format!("seq: {}, poll/final: {}", rnr.receive_sequence, rnr.poll_or_final)),
|
||||
FrameContent::SetAsynchronousBalancedMode(abm) => (vec![], format!("poll: {}", abm.poll)),
|
||||
FrameContent::Disconnect(abm) => (vec![], format!("poll: {}", abm.poll)),
|
||||
FrameContent::DisconnectedMode(abm) => (vec![], format!("final bit: {}", abm.final_bit)),
|
||||
FrameContent::UnnumberedAcknowledge(abm) => (vec![], format!("final bit: {}", abm.final_bit)),
|
||||
FrameContent::FrameReject(fr) => (vec![], format!("[{}{}{}{}], seq: {}/{}, raw: {}, cmdRsp: {}, final: {}", if fr.z {"Z"}else{""}, if fr.y {"Y"}else{""}, if fr.x {"X"}else{""}, if fr.w{"W"}else{""}, fr.send_sequence, fr.receive_sequence, fr.rejected_control_field_raw, match fr.command_response {CommandResponse::Command => "CMD", CommandResponse::Response => "RSP"}, fr.final_bit)),
|
||||
FrameContent::UnnumberedInformation(ui) => (
|
||||
ui.info.clone(),
|
||||
format!("pid: {:?} info:", ui.pid)),
|
||||
FrameContent::UnknownContent(ukn) => (ukn.raw.clone(), format!("")),
|
||||
};
|
||||
let mut data = String::new();
|
||||
for byte in rawdata.iter() {
|
||||
//if byte >= ' '.into() && byte <= '~'.into() {
|
||||
if *byte >= 32 && *byte <= 126 {
|
||||
data.push((*byte).into());
|
||||
} else {
|
||||
data.push_str(&format!("\\{:x?} ", *byte));
|
||||
}
|
||||
}
|
||||
println!("AX.25 {:12} {} {:9} -> {:9} {}",ftype, rsp, src, dst, if !data.is_empty() {data} else {finfo});
|
||||
}
|
||||
fn print_long(&self) {
|
||||
//Format: "[source] -> [dest] via [..] [CMD/RSP/NIL] [frameType]: [DATA str rep]"
|
||||
let src = self.source.to_string();
|
||||
let dst = self.destination.to_string();
|
||||
let via: String = self.route.iter().map(|re| format!(" {} ", re.repeater.to_string())).collect(); //todo
|
||||
let rsp = match self.command_or_response {
|
||||
Some(CommandResponse::Command) => "CMD",
|
||||
Some(CommandResponse::Response) => "RSP",
|
||||
None => "NIL",
|
||||
};
|
||||
let ftype = self.type_str_short();
|
||||
|
||||
//let mut rawdata;
|
||||
let (rawdata, finfo) = match &self.content {
|
||||
FrameContent::Information(_i) =>
|
||||
(_i.info.clone(),
|
||||
format!("pid: {:?}, seq: {}/{}, info: ", _i.pid, _i.send_sequence, _i.receive_sequence)),
|
||||
FrameContent::ReceiveReady(rr) => (vec![], format!("seq: {}, poll/final: {}", rr.receive_sequence, rr.poll_or_final)),
|
||||
FrameContent::ReceiveNotReady(rnr) => (vec![], format!("seq: {}, poll/final: {}", rnr.receive_sequence, rnr.poll_or_final)),
|
||||
FrameContent::Reject(rnr) => (vec![], format!("seq: {}, poll/final: {}", rnr.receive_sequence, rnr.poll_or_final)),
|
||||
FrameContent::SetAsynchronousBalancedMode(abm) => (vec![], format!("poll: {}", abm.poll)),
|
||||
FrameContent::Disconnect(abm) => (vec![], format!("poll: {}", abm.poll)),
|
||||
FrameContent::DisconnectedMode(abm) => (vec![], format!("final bit: {}", abm.final_bit)),
|
||||
FrameContent::UnnumberedAcknowledge(abm) => (vec![], format!("final bit: {}", abm.final_bit)),
|
||||
FrameContent::FrameReject(fr) => (vec![], format!("[{}{}{}{}], seq: {}/{}, raw: {}, cmdRsp: {}, final: {}", if fr.z {"Z"}else{""}, if fr.y {"Y"}else{""}, if fr.x {"X"}else{""}, if fr.w{"W"}else{""}, fr.send_sequence, fr.receive_sequence, fr.rejected_control_field_raw, match fr.command_response {CommandResponse::Command => "CMD", CommandResponse::Response => "RSP"}, fr.final_bit)),
|
||||
FrameContent::UnnumberedInformation(ui) => (
|
||||
ui.info.clone(),
|
||||
format!("pid: {:?} info:", ui.pid)),
|
||||
FrameContent::UnknownContent(ukn) => (ukn.raw.clone(), format!("")),
|
||||
};
|
||||
let mut data = String::new();
|
||||
for byte in rawdata.iter() {
|
||||
//if byte >= ' '.into() && byte <= '~'.into() {
|
||||
if *byte >= 32 && *byte <= 126 {
|
||||
data.push((*byte).into());
|
||||
} else {
|
||||
data.push_str(&format!("\\{:x?} ", *byte));
|
||||
}
|
||||
}
|
||||
println!("AX.25 {:12} {} {:9} -> {:9} via [{}] {} {}",ftype, rsp, src, dst, via, finfo, data);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut information = 0;
|
||||
let mut receive_ready = 0;
|
||||
let mut receive_not_ready = 0;
|
||||
let mut reject = 0;
|
||||
let mut set_asynchronous_balanced_mode = 0;
|
||||
let mut disconnect = 0;
|
||||
let mut disconnected_mode = 0;
|
||||
let mut unnumbered_acknowledge = 0;
|
||||
let mut frame_reject = 0;
|
||||
let mut unnumbered_information = 0;
|
||||
let mut unknown_content = 0;
|
||||
|
||||
let arg = Arg::parse();
|
||||
|
||||
let verbose = arg.verbose;
|
||||
|
||||
let indisplay = arg.infile.display();
|
||||
let mut file_in = match File::open(&arg.infile) {
|
||||
Err(why) => panic!("couldn't open {}: {}", indisplay, why),
|
||||
Ok(file) => file,
|
||||
};
|
||||
|
||||
let file_out = File::create(&arg.outfile).expect("Error opening file for writing");
|
||||
let header = PcapHeader{datalink: DataLink::AX25_KISS, ..Default::default()};
|
||||
let mut out_file = PcapWriter::with_header(file_out, header).expect("Error writing file");
|
||||
|
||||
|
||||
let mut buffer = vec![0_u8; 4096];
|
||||
let mut rawframe = vec![];
|
||||
let mut escaped = false;
|
||||
let mut timestamp = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap();
|
||||
let mut orig_len = 0;
|
||||
// while let Ok(n) = file_in.by_ref().take(4096).read_to_end(&mut buffer) {
|
||||
while let Ok(n) = file_in.read(&mut buffer) {
|
||||
if n <= 0 {break;}
|
||||
for byte in buffer[..n].iter() {
|
||||
orig_len += 1;
|
||||
if escaped {
|
||||
match *byte {
|
||||
0xDC => rawframe.push(0xC0),
|
||||
0xDD => rawframe.push(0xDB),
|
||||
_ => { /* ERROR: invalid escape sequence */ }
|
||||
}
|
||||
escaped = false;
|
||||
} else {
|
||||
match *byte {
|
||||
0xDB => escaped = true,
|
||||
0xC0 => {
|
||||
//TODO send and clear frame if not empty
|
||||
if rawframe.len() == 0 {
|
||||
orig_len = 1;
|
||||
timestamp = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap();
|
||||
continue;
|
||||
};
|
||||
|
||||
if verbose {
|
||||
if let Ok(frame) = Ax25Frame::from_bytes(&rawframe[1..]) {
|
||||
match frame.content {
|
||||
FrameContent::Information(_) => information += 1,
|
||||
FrameContent::ReceiveReady(_) => receive_ready += 1,
|
||||
FrameContent::ReceiveNotReady(_) => receive_not_ready += 1,
|
||||
FrameContent::Reject(_) => reject += 1,
|
||||
FrameContent::SetAsynchronousBalancedMode(_) => set_asynchronous_balanced_mode += 1,
|
||||
FrameContent::Disconnect(_) => disconnect += 1,
|
||||
FrameContent::DisconnectedMode(_) => disconnected_mode += 1,
|
||||
FrameContent::UnnumberedAcknowledge(_) => unnumbered_acknowledge += 1,
|
||||
FrameContent::FrameReject(_) => frame_reject += 1,
|
||||
FrameContent::UnnumberedInformation(_) => unnumbered_information += 1,
|
||||
FrameContent::UnknownContent(_) => unknown_content +=1,
|
||||
}
|
||||
frame.print_short();
|
||||
//println!("{{source: {}, dest: {}, route: {:?}, cmd/rsp: {:?}, content: {:?}}}", frame.source, frame.destination, frame.route, frame.command_or_response, frame.content);
|
||||
//println!("{}\n", frame.info_string_lossy().unwrap_or("Could not parse AX.25 frame data".to_string()));
|
||||
}else {
|
||||
let mut s = String::new();
|
||||
for byte in rawframe.iter() {
|
||||
//if byte >= ' '.into() && byte <= '~'.into() {
|
||||
if *byte >= 32 && *byte <= 126 {
|
||||
s.push((*byte).into());
|
||||
} else {
|
||||
s.push_str(&format!("\\{:x?} ", *byte));
|
||||
}
|
||||
}
|
||||
//TODO print time stamp
|
||||
println!("{}", s);
|
||||
}
|
||||
}
|
||||
|
||||
let pkt = PcapPacket::new_owned(timestamp, orig_len, rawframe.clone());
|
||||
out_file.write_packet(&pkt).unwrap();
|
||||
|
||||
//tx.send(KissPkt{ timestamp, orig_len, data: frame.clone()})?;
|
||||
rawframe.clear();
|
||||
orig_len = 1;
|
||||
timestamp = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap();
|
||||
}
|
||||
b => rawframe.push(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n---Frame type counts---");
|
||||
println!(" Info: {}", information);
|
||||
println!(" RecvRdy: {}", receive_ready);
|
||||
println!(" RecvNotRdy: {}", receive_not_ready);
|
||||
println!(" Reject: {}", reject);
|
||||
println!(" AsyncBalMode: {}", set_asynchronous_balanced_mode);
|
||||
println!(" Disconnect: {}", disconnect);
|
||||
println!(" DisconMode: {}", disconnected_mode);
|
||||
println!(" UnNumAck: {}", unnumbered_acknowledge);
|
||||
println!(" FrameReject: {}", frame_reject);
|
||||
println!(" UnNumInfo: {}", unnumbered_information);
|
||||
println!(" Unknown: {}", unknown_content);
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
358
src/main.rs
Normal file
358
src/main.rs
Normal file
@ -0,0 +1,358 @@
|
||||
use clap::Parser;
|
||||
use std::{
|
||||
fs::File,
|
||||
path::PathBuf,
|
||||
sync::mpsc::{channel, Sender},
|
||||
thread,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use ax25::frame::{Ax25Frame, CommandResponse, FrameContent};
|
||||
use pcap_file::{
|
||||
pcap::{PcapHeader, PcapPacket, PcapWriter},
|
||||
DataLink,
|
||||
};
|
||||
use std::io::Read;
|
||||
use std::net::TcpStream;
|
||||
use std::time::Duration;
|
||||
use std::time::SystemTime;
|
||||
|
||||
struct KissPkt {
|
||||
timestamp: Duration,
|
||||
orig_len: u32,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
fn tnc_loop(tx: Sender<KissPkt>, mut stream: TcpStream) -> Result<()> {
|
||||
let mut buffer = [0_u8; 4096];
|
||||
let mut frame = vec![];
|
||||
let mut escaped = false;
|
||||
let mut timestamp = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap();
|
||||
let mut orig_len = 0;
|
||||
loop {
|
||||
let n = stream.read(&mut buffer)?;
|
||||
for byte in buffer[..n].iter() {
|
||||
orig_len += 1;
|
||||
if escaped {
|
||||
match *byte {
|
||||
0xDC => frame.push(0xC0),
|
||||
0xDD => frame.push(0xDB),
|
||||
_ => { /* ERROR: invalid escape sequence */ }
|
||||
}
|
||||
escaped = false;
|
||||
} else {
|
||||
match *byte {
|
||||
0xDB => escaped = true,
|
||||
0xC0 => {
|
||||
//TODO send and clear frame if not empty
|
||||
if frame.len() == 0 {
|
||||
orig_len = 1;
|
||||
timestamp = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap();
|
||||
continue;
|
||||
};
|
||||
tx.send(KissPkt {
|
||||
timestamp,
|
||||
orig_len,
|
||||
data: frame.clone(),
|
||||
})?;
|
||||
frame.clear();
|
||||
}
|
||||
b => frame.push(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//Ok(())
|
||||
}
|
||||
const DEFAULT_ADDR: &str = "127.0.0.1:8001";
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Arg {
|
||||
#[arg(short, long, default_value_t = false)]
|
||||
verbose: bool,
|
||||
#[arg(short, long)]
|
||||
file: Option<PathBuf>,
|
||||
address: Option<String>,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let arg = Arg::parse();
|
||||
let address = arg.address.unwrap_or(DEFAULT_ADDR.to_string());
|
||||
let mut file = arg.file.map(|path| {
|
||||
//TODO automatically add file extension
|
||||
let file_out = File::create(path).expect("Error opening file for writing");
|
||||
let header = PcapHeader {
|
||||
datalink: DataLink::AX25_KISS,
|
||||
..Default::default()
|
||||
};
|
||||
PcapWriter::with_header(file_out, header).expect("Error writing file")
|
||||
});
|
||||
//println!("file: {:?}", arg.file);
|
||||
//println!("address: {}", address);
|
||||
|
||||
let (tx, rx) = channel();
|
||||
let stream = TcpStream::connect(address)?;
|
||||
|
||||
let q = tx.clone();
|
||||
let strm = stream.try_clone()?;
|
||||
thread::spawn(move || tnc_loop(q, strm).unwrap());
|
||||
|
||||
while let Ok(pkt) = rx.recv() {
|
||||
//let time = SystemTime::now();
|
||||
//let timestamp = time.duration_since(SystemTime::UNIX_EPOCH);
|
||||
let mut s = String::new();
|
||||
for byte in pkt.data.iter() {
|
||||
//if byte >= ' '.into() && byte <= '~'.into() {
|
||||
if *byte >= 32 && *byte <= 126 {
|
||||
s.push((*byte).into());
|
||||
} else {
|
||||
s.push_str(&format!("\\{:x?} ", *byte));
|
||||
}
|
||||
}
|
||||
|
||||
if arg.verbose {
|
||||
if let Ok(frame) = Ax25Frame::from_bytes(&pkt.data[1..]) {
|
||||
frame.print_long();
|
||||
} else {
|
||||
//TODO print time stamp
|
||||
println!("{}", s);
|
||||
}
|
||||
} else {
|
||||
if let Ok(frame) = Ax25Frame::from_bytes(&pkt.data[1..]) {
|
||||
frame.print_short();
|
||||
} else {
|
||||
//TODO print time stamp
|
||||
println!("{}", s);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref mut writer) = file {
|
||||
let pkt = PcapPacket::new_owned(pkt.timestamp, pkt.orig_len, pkt.data);
|
||||
writer.write_packet(&pkt).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
trait PrintData {
|
||||
fn type_str_short(&self) -> &'static str;
|
||||
fn print_short(&self);
|
||||
fn print_long(&self);
|
||||
}
|
||||
impl PrintData for Ax25Frame {
|
||||
fn type_str_short(&self) -> &'static str {
|
||||
match self.content {
|
||||
FrameContent::Information(_) => "Info",
|
||||
FrameContent::ReceiveReady(_) => "RecvRdy",
|
||||
FrameContent::ReceiveNotReady(_) => "RecvNotRdy",
|
||||
FrameContent::Reject(_) => "Reject",
|
||||
FrameContent::SetAsynchronousBalancedMode(_) => "AsyncBalMode", //12 long
|
||||
FrameContent::Disconnect(_) => "Disconnect",
|
||||
FrameContent::DisconnectedMode(_) => "DisconMode",
|
||||
FrameContent::UnnumberedAcknowledge(_) => "UnNumAck",
|
||||
FrameContent::FrameReject(_) => "FrameReject",
|
||||
FrameContent::UnnumberedInformation(_) => "UnNumInfo",
|
||||
FrameContent::UnknownContent(_) => "Unknown",
|
||||
}
|
||||
}
|
||||
fn print_short(&self) {
|
||||
//Format: "[source] -> [dest] via [..] [CMD/RSP/NIL] [frameType]: [DATA str rep]"
|
||||
let src = self.source.to_string();
|
||||
let dst = self.destination.to_string();
|
||||
//let via: String = self.route.iter().map(|re| format!(" {} ", re.repeater.to_string())).collect(); //todo
|
||||
let rsp = match self.command_or_response {
|
||||
Some(CommandResponse::Command) => "CMD",
|
||||
Some(CommandResponse::Response) => "RSP",
|
||||
None => "NIL",
|
||||
};
|
||||
let ftype = self.type_str_short();
|
||||
let mut routed = false;
|
||||
for route in self.route.iter() {
|
||||
if route.has_repeated {
|
||||
routed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let arrow_shaft = if routed { "=" } else { "-" };
|
||||
|
||||
//let mut rawdata;
|
||||
let (rawdata, finfo) = match &self.content {
|
||||
FrameContent::Information(_i) => (
|
||||
_i.info.clone(),
|
||||
format!(
|
||||
"pid: {:?}, seq: {}/{}, info: ",
|
||||
_i.pid, _i.send_sequence, _i.receive_sequence
|
||||
),
|
||||
),
|
||||
FrameContent::ReceiveReady(rr) => (
|
||||
vec![],
|
||||
format!(
|
||||
"seq: {}, poll/final: {}",
|
||||
rr.receive_sequence, rr.poll_or_final
|
||||
),
|
||||
),
|
||||
FrameContent::ReceiveNotReady(rnr) => (
|
||||
vec![],
|
||||
format!(
|
||||
"seq: {}, poll/final: {}",
|
||||
rnr.receive_sequence, rnr.poll_or_final
|
||||
),
|
||||
),
|
||||
FrameContent::Reject(rnr) => (
|
||||
vec![],
|
||||
format!(
|
||||
"seq: {}, poll/final: {}",
|
||||
rnr.receive_sequence, rnr.poll_or_final
|
||||
),
|
||||
),
|
||||
FrameContent::SetAsynchronousBalancedMode(abm) => {
|
||||
(vec![], format!("poll: {}", abm.poll))
|
||||
}
|
||||
FrameContent::Disconnect(abm) => (vec![], format!("poll: {}", abm.poll)),
|
||||
FrameContent::DisconnectedMode(abm) => {
|
||||
(vec![], format!("final bit: {}", abm.final_bit))
|
||||
}
|
||||
FrameContent::UnnumberedAcknowledge(abm) => {
|
||||
(vec![], format!("final bit: {}", abm.final_bit))
|
||||
}
|
||||
FrameContent::FrameReject(fr) => (
|
||||
vec![],
|
||||
format!(
|
||||
"[{}{}{}{}], seq: {}/{}, raw: {}, cmdRsp: {}, final: {}",
|
||||
if fr.z { "Z" } else { "" },
|
||||
if fr.y { "Y" } else { "" },
|
||||
if fr.x { "X" } else { "" },
|
||||
if fr.w { "W" } else { "" },
|
||||
fr.send_sequence,
|
||||
fr.receive_sequence,
|
||||
fr.rejected_control_field_raw,
|
||||
match fr.command_response {
|
||||
CommandResponse::Command => "CMD",
|
||||
CommandResponse::Response => "RSP",
|
||||
},
|
||||
fr.final_bit
|
||||
),
|
||||
),
|
||||
FrameContent::UnnumberedInformation(ui) => {
|
||||
(ui.info.clone(), format!("pid: {:?} info:", ui.pid))
|
||||
}
|
||||
FrameContent::UnknownContent(ukn) => (ukn.raw.clone(), format!("")),
|
||||
};
|
||||
let mut data = String::new();
|
||||
for byte in rawdata.iter() {
|
||||
//if byte >= ' '.into() && byte <= '~'.into() {
|
||||
if *byte >= 32 && *byte <= 126 {
|
||||
data.push((*byte).into());
|
||||
} else {
|
||||
data.push_str(&format!("\\{:x?} ", *byte));
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"AX.25 {:12} {} {:9} {}> {:9} {}",
|
||||
ftype,
|
||||
rsp,
|
||||
src,
|
||||
arrow_shaft,
|
||||
dst,
|
||||
if !data.is_empty() { data } else { finfo }
|
||||
);
|
||||
}
|
||||
fn print_long(&self) {
|
||||
//Format: "[source] -> [dest] via [..] [CMD/RSP/NIL] [frameType]: [DATA str rep]"
|
||||
let src = self.source.to_string();
|
||||
let dst = self.destination.to_string();
|
||||
let via: String = self
|
||||
.route
|
||||
.iter()
|
||||
.map(|re| format!(" {} ", re.repeater.to_string()))
|
||||
.collect(); //todo
|
||||
let rsp = match self.command_or_response {
|
||||
Some(CommandResponse::Command) => "CMD",
|
||||
Some(CommandResponse::Response) => "RSP",
|
||||
None => "NIL",
|
||||
};
|
||||
let ftype = self.type_str_short();
|
||||
|
||||
//let mut rawdata;
|
||||
let (rawdata, finfo) = match &self.content {
|
||||
FrameContent::Information(_i) => (
|
||||
_i.info.clone(),
|
||||
format!(
|
||||
"pid: {:?}, seq: {}/{}, info: ",
|
||||
_i.pid, _i.send_sequence, _i.receive_sequence
|
||||
),
|
||||
),
|
||||
FrameContent::ReceiveReady(rr) => (
|
||||
vec![],
|
||||
format!(
|
||||
"seq: {}, poll/final: {}",
|
||||
rr.receive_sequence, rr.poll_or_final
|
||||
),
|
||||
),
|
||||
FrameContent::ReceiveNotReady(rnr) => (
|
||||
vec![],
|
||||
format!(
|
||||
"seq: {}, poll/final: {}",
|
||||
rnr.receive_sequence, rnr.poll_or_final
|
||||
),
|
||||
),
|
||||
FrameContent::Reject(rnr) => (
|
||||
vec![],
|
||||
format!(
|
||||
"seq: {}, poll/final: {}",
|
||||
rnr.receive_sequence, rnr.poll_or_final
|
||||
),
|
||||
),
|
||||
FrameContent::SetAsynchronousBalancedMode(abm) => {
|
||||
(vec![], format!("poll: {}", abm.poll))
|
||||
}
|
||||
FrameContent::Disconnect(abm) => (vec![], format!("poll: {}", abm.poll)),
|
||||
FrameContent::DisconnectedMode(abm) => {
|
||||
(vec![], format!("final bit: {}", abm.final_bit))
|
||||
}
|
||||
FrameContent::UnnumberedAcknowledge(abm) => {
|
||||
(vec![], format!("final bit: {}", abm.final_bit))
|
||||
}
|
||||
FrameContent::FrameReject(fr) => (
|
||||
vec![],
|
||||
format!(
|
||||
"[{}{}{}{}], seq: {}/{}, raw: {}, cmdRsp: {}, final: {}",
|
||||
if fr.z { "Z" } else { "" },
|
||||
if fr.y { "Y" } else { "" },
|
||||
if fr.x { "X" } else { "" },
|
||||
if fr.w { "W" } else { "" },
|
||||
fr.send_sequence,
|
||||
fr.receive_sequence,
|
||||
fr.rejected_control_field_raw,
|
||||
match fr.command_response {
|
||||
CommandResponse::Command => "CMD",
|
||||
CommandResponse::Response => "RSP",
|
||||
},
|
||||
fr.final_bit
|
||||
),
|
||||
),
|
||||
FrameContent::UnnumberedInformation(ui) => {
|
||||
(ui.info.clone(), format!("pid: {:?} info:", ui.pid))
|
||||
}
|
||||
FrameContent::UnknownContent(ukn) => (ukn.raw.clone(), format!("")),
|
||||
};
|
||||
let mut data = String::new();
|
||||
for byte in rawdata.iter() {
|
||||
//if byte >= ' '.into() && byte <= '~'.into() {
|
||||
if *byte >= 32 && *byte <= 126 {
|
||||
data.push((*byte).into());
|
||||
} else {
|
||||
data.push_str(&format!("\\{:x?} ", *byte));
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"AX.25 {:12} {} {:9} -> {:9} via [{}] {} {}",
|
||||
ftype, rsp, src, dst, via, finfo, data
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user