Add readme

This commit is contained in:
Jazz Turner-Baggs 2026-07-12 22:23:37 -07:00
parent 4dbd004298
commit a0e1226f21
No known key found for this signature in database
7 changed files with 109 additions and 32 deletions

15
Cargo.lock generated
View File

@ -3706,6 +3706,21 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "meshshark"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"crossbeam-channel",
"crossterm 0.29.0",
"libc",
"logos-delivery",
"ratatui",
"tracing",
"tracing-subscriber",
]
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.9" version = "0.8.9"

68
bin/meshshark/README.md Normal file
View File

@ -0,0 +1,68 @@
# meshshark
![Project-Type](https://img.shields.io/badge/Type-Developer_Utility-blue)
![meshshark watching a logos-delivery network](docs/meshshark_screen.png)
A sniffer for logos-delivery networks. meshshark starts an embedded
logos-delivery node, subscribes to the content topics you ask for, and shows
arriving-message metadata live in a terminal UI — one pane per subscription, or
a single merged stream.
It reports metadata only: arrival time (as a delta from start), a color-coded
content topic, and the payload size. Payload bytes are never inspected, only
measured, and an FNV-1a hash of each payload is used as a message id so you can
spot duplicate deliveries.
## Usage
```
meshshark --sub <ADDRESS|TOPIC> [--sub …] [--all] [--preset …] [--port …]
```
A value passed to `--sub` that starts with `/` is used verbatim as a content
topic; anything else is treated as a delivery address and wrapped as
`/logos-chat/1/<address>/proto`.
```sh
# Watch two delivery addresses
meshshark --sub saro --sub raya
# Watch a full content topic, plus a firehose of everything the node receives
meshshark --sub /logos-chat/1/saro/proto --all
```
You need at least one `--sub` to join a shard and receive anything, even when
using `--all`.
### Options
| Flag | Default | Description |
| --- | --- | --- |
| `-s, --sub <ADDRESS\|TOPIC>` | — | Topic to watch (repeatable). One pane each. |
| `--all` | off | Add a firehose pane showing every message on any content topic. |
| `--preset <NAME>` | `logos.dev` | logos-delivery network preset. |
| `--log-level <LEVEL>` | `ERROR` | Node log level. Kept quiet so it doesn't corrupt the TUI. |
| `--port <PORT>` | OS-assigned | TCP/UDP port for the node. A free port is chosen by default so multiple instances can run side by side. |
## Keys
| Key | Action |
| --- | --- |
| `v` / `Tab` | Toggle between grid and unified views. |
| `a` | Add a subscription at runtime (type an address or topic, `Enter` to confirm, `Esc` to cancel). |
| `q` / `Esc` | Quit. |
| `Ctrl-C` | Quit from any mode. |
## Views
- **Grid** — one pane per subscription, each keeping the last 500 messages.
- **Unified** — a single newest-first merged stream across all subscriptions,
keeping the last 1000 messages.
## Notes
meshshark links the native logos-delivery node through the `logos-delivery`
crate. Because the embedded Nim/libwaku runtime installs `atexit` handlers that
block on node teardown, meshshark exits via `_exit` rather than a normal
shutdown — the OS reclaims the node at process exit.

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 KiB

View File

@ -40,7 +40,10 @@ fn fnv1a_64(bytes: &[u8]) -> u64 {
} }
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(name = "meshshark", about = "See what is happening on a logos-delivery network")] #[command(
name = "meshshark",
about = "See what is happening on a logos-delivery network"
)]
struct Cli { struct Cli {
/// Topic to watch (repeatable). One pane each. A value starting with `/` /// Topic to watch (repeatable). One pane each. A value starting with `/`
/// is used as a full content topic; otherwise it is treated as a delivery /// is used as a full content topic; otherwise it is treated as a delivery
@ -92,7 +95,7 @@ fn main() {
// never needs the bytes themselves. // never needs the bytes themselves.
let start_cfg = P2pConfig { let start_cfg = P2pConfig {
preset: cli.preset.clone(), preset: cli.preset.clone(),
tcp_port: port, port,
log_level: cli.log_level.clone(), log_level: cli.log_level.clone(),
}; };
let mut delivery = match ThreadedDeliveryWrapper::start(start_cfg, |event: WakuEvent| { let mut delivery = match ThreadedDeliveryWrapper::start(start_cfg, |event: WakuEvent| {

View File

@ -125,7 +125,11 @@ impl App {
/// unified stream. A message on a named topic lands in both its pane and any /// unified stream. A message on a named topic lands in both its pane and any
/// firehose (`ALL`) pane. /// firehose (`ALL`) pane.
pub fn record(&mut self, msg: ObservedMessage) { pub fn record(&mut self, msg: ObservedMessage) {
for pane in self.panes.iter_mut().filter(|p| p.matches(&msg.content_topic)) { for pane in self
.panes
.iter_mut()
.filter(|p| p.matches(&msg.content_topic))
{
pane.record(msg.clone()); pane.record(msg.clone());
} }
if self.unified.len() == UNIFIED_HISTORY { if self.unified.len() == UNIFIED_HISTORY {
@ -205,7 +209,10 @@ fn draw_header(frame: &mut Frame, app: &App, area: Rect) {
.fg(Color::Cyan) .fg(Color::Cyan)
.add_modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
), ),
Span::styled(format!(" · port {}", app.port), Style::default().fg(Color::DarkGray)), Span::styled(
format!(" · port {}", app.port),
Style::default().fg(Color::DarkGray),
),
]; ];
frame.render_widget(Paragraph::new(Line::from(spans)), area); frame.render_widget(Paragraph::new(Line::from(spans)), area);
} }
@ -272,7 +279,12 @@ fn draw_pane(frame: &mut Frame, pane: &Pane, area: Rect, start: Instant) {
} else { } else {
pane.content_topic.as_str() pane.content_topic.as_str()
}; };
let title = format!(" {} ({} msgs, {}) ", label, pane.count, human_bytes(pane.total_bytes),); let title = format!(
" {} ({} msgs, {}) ",
label,
pane.count,
human_bytes(pane.total_bytes),
);
// Color the border by topic so a pane's color matches its lines in the // Color the border by topic so a pane's color matches its lines in the
// unified view. A firehose pane mixes topics, so leave it neutral. // unified view. A firehose pane mixes topics, so leave it neutral.
let border_color = if pane.match_all { let border_color = if pane.match_all {
@ -357,7 +369,12 @@ fn draw_status(frame: &mut Frame, app: &App, area: Rect) {
ViewMode::Unified => "unified", ViewMode::Unified => "unified",
}; };
let text = if app.status.is_empty() { let text = if app.status.is_empty() {
format!(" {} subs · {} msgs · view {} ", app.panes.len(), total, view) format!(
" {} subs · {} msgs · view {} ",
app.panes.len(),
total,
view
)
} else { } else {
format!(" {} ", app.status) format!(" {} ", app.status)
}; };

View File

@ -29,21 +29,12 @@ pub const DEFAULT_NETWORK_PRESET: &str = "logos.dev";
/// Default TCP port for the embedded logos-delivery node. /// Default TCP port for the embedded logos-delivery node.
pub const DEFAULT_TCP_PORT: u16 = 60000; pub const DEFAULT_TCP_PORT: u16 = 60000;
<<<<<<< HEAD
/// The content-topic prefix carrying logos-chat traffic. /// The content-topic prefix carrying logos-chat traffic.
const CHAT_TOPIC_PREFIX: &str = "/logos-chat/1/"; const CHAT_TOPIC_PREFIX: &str = "/logos-chat/1/";
pub fn content_topic_for(delivery_address: &str) -> String { pub fn content_topic_for(delivery_address: &str) -> String {
format!("{CHAT_TOPIC_PREFIX}{delivery_address}/proto") format!("{CHAT_TOPIC_PREFIX}{delivery_address}/proto")
} }
=======
pub fn content_topic_for(delivery_address: &str) -> String {
format!("/logos-chat/1/{delivery_address}/proto")
}
/// The content-topic prefix carrying logos-chat traffic.
const CHAT_TOPIC_PREFIX: &str = "/logos-chat/1/";
>>>>>>> 08e9b4f (Isolate logos-delivery)
// ── EmbeddedLogosDelivery ────────────────────────────────────────────────── // ── EmbeddedLogosDelivery ──────────────────────────────────────────────────

View File

@ -49,20 +49,12 @@ type SubscriberList<T> = Arc<Mutex<Vec<Sender<T>>>>;
pub const DEFAULT_NETWORK_PRESET: &str = "logos.dev"; pub const DEFAULT_NETWORK_PRESET: &str = "logos.dev";
/// Default TCP port for the embedded logos-delivery node. /// Default TCP port for the embedded logos-delivery node.
<<<<<<< HEAD
pub const DEFAULT_PORT: u16 = 60000; pub const DEFAULT_PORT: u16 = 60000;
=======
pub const DEFAULT_TCP_PORT: u16 = 60000;
>>>>>>> 08e9b4f (Isolate logos-delivery)
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct P2pConfig { pub struct P2pConfig {
pub preset: String, pub preset: String,
<<<<<<< HEAD
pub port: u16, pub port: u16,
=======
pub tcp_port: u16,
>>>>>>> 08e9b4f (Isolate logos-delivery)
pub log_level: String, pub log_level: String,
} }
@ -70,11 +62,7 @@ impl Default for P2pConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
preset: DEFAULT_NETWORK_PRESET.into(), preset: DEFAULT_NETWORK_PRESET.into(),
<<<<<<< HEAD
port: DEFAULT_PORT, port: DEFAULT_PORT,
=======
tcp_port: DEFAULT_TCP_PORT,
>>>>>>> 08e9b4f (Isolate logos-delivery)
log_level: "ERROR".into(), log_level: "ERROR".into(),
} }
} }
@ -293,13 +281,8 @@ impl<T> ThreadedDeliveryWrapper<T> {
"logLevel": cfg.log_level, "logLevel": cfg.log_level,
"mode": "Core", "mode": "Core",
"preset": cfg.preset, "preset": cfg.preset,
<<<<<<< HEAD
"tcpPort": cfg.port, "tcpPort": cfg.port,
"discv5UdpPort": cfg.port, "discv5UdpPort": cfg.port,
=======
"tcpPort": cfg.tcp_port,
"discv5UdpPort": cfg.tcp_port,
>>>>>>> 08e9b4f (Isolate logos-delivery)
}) })
.to_string(); .to_string();