Skip to content

Commit 30dd388

Browse files
committed
1.0.0 and add way better deduplication
1 parent 3c1d68d commit 30dd388

File tree

6 files changed

+84
-39
lines changed

6 files changed

+84
-39
lines changed

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33

44
pub mod error;
55
pub mod net;
6+
pub mod ringbuffer;
67
pub mod ui;

src/net.rs

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,23 @@ use pnet::datalink::{
88
use pnet::packet::ethernet::{EtherTypes, EthernetPacket, MutableEthernetPacket};
99
use pnet::packet::Packet as PnetPacket;
1010
use pnet::util::MacAddr;
11+
use rand::Rng;
1112
use serde::{Deserialize, Serialize};
1213

1314
use crate::error::ArpchatError;
15+
use crate::ringbuffer::Ringbuffer;
1416

1517
const ARP_HTYPE: &[u8] = &[0x00, 0x01]; // Hardware Type (Ethernet)
1618
const ARP_HLEN: u8 = 6; // Hardware Address Length
1719
const ARP_OPER: &[u8] = &[0, 1]; // Operation (Request)
1820
const PACKET_PREFIX: &[u8] = b"uwu";
1921

20-
// Tag, seq, and total, are each one byte, thus the `+ 3`.
21-
const PACKET_PART_SIZE: usize = u8::MAX as usize - (PACKET_PREFIX.len() + 3);
22-
2322
pub const ID_SIZE: usize = 8;
2423
pub type Id = [u8; ID_SIZE];
2524

25+
// Tag, seq, and total, are each one byte, thus the `+ 3`.
26+
const PACKET_PART_SIZE: usize = u8::MAX as usize - (PACKET_PREFIX.len() + 3 + ID_SIZE);
27+
2628
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
2729
pub enum EtherType {
2830
#[default]
@@ -134,15 +136,16 @@ pub struct Channel {
134136
tx: Box<dyn DataLinkSender>,
135137
rx: Box<dyn DataLinkReceiver>,
136138

137-
/// Buffer of received packet parts, keyed by the number of parts and
138-
/// its tag. This keying method is far from foolproof but reduces
139-
/// the chance of colliding packets just a tiny bit.
139+
/// Buffer of received packet parts, keyed by the packet id.
140140
///
141141
/// Each value is the Vec of its parts, and counts as a packet when
142142
/// every part is non-empty. There are probably several optimization
143143
/// opportunities here, but c'mon, a naive approach is perfectly fine
144144
/// for a program this cursed.
145-
buffer: HashMap<(u8, u8), Vec<Vec<u8>>>,
145+
buffer: HashMap<Id, Vec<Vec<u8>>>,
146+
147+
/// Recent packet buffer for deduplication.
148+
recent: Ringbuffer<Id>,
146149
}
147150

148151
impl Channel {
@@ -159,6 +162,7 @@ impl Channel {
159162
tx,
160163
rx,
161164
buffer: HashMap::new(),
165+
recent: Ringbuffer::with_capacity(16),
162166
})
163167
}
164168

@@ -171,23 +175,33 @@ impl Channel {
171175
let mut parts: Vec<&[u8]> = data.chunks(PACKET_PART_SIZE).collect();
172176

173177
if parts.is_empty() {
174-
// Empty packets still need one byte of data to go through :)
178+
// We need to send some data so empty enums go through! Not entirely
179+
// sure *why* this is the case... pushing an empty string feels like
180+
// it should be fine, but it doesn't work.
175181
parts.push(b".");
176182
}
177183
if parts.len() - 1 > u8::MAX as usize {
178184
return Err(ArpchatError::MsgTooLong);
179185
}
180186

181187
let total = (parts.len() - 1) as u8;
188+
let id: Id = rand::thread_rng().gen();
182189
for (seq, part) in parts.into_iter().enumerate() {
183-
self.send_part(packet.tag(), seq as u8, total, part)?;
190+
self.send_part(packet.tag(), seq as u8, total, id, part)?;
184191
}
185192

186193
Ok(())
187194
}
188195

189-
fn send_part(&mut self, tag: u8, seq: u8, total: u8, part: &[u8]) -> Result<(), ArpchatError> {
190-
let data = &[PACKET_PREFIX, &[tag, seq, total], part].concat();
196+
fn send_part(
197+
&mut self,
198+
tag: u8,
199+
seq: u8,
200+
total: u8,
201+
id: Id,
202+
part: &[u8],
203+
) -> Result<(), ArpchatError> {
204+
let data = &[PACKET_PREFIX, &[tag, seq, total], &id, part].concat();
191205

192206
// The length of the data must fit in a u8. This should also
193207
// guarantee that we'll be inside the MTU.
@@ -247,28 +261,39 @@ impl Channel {
247261
}
248262

249263
if let &[tag, seq, total, ref inner @ ..] = &data[PACKET_PREFIX.len()..] {
250-
let key = (tag, total);
251-
252-
if let Some(parts) = self.buffer.get_mut(&key) {
253-
parts[seq as usize] = inner.to_vec();
254-
} else {
255-
let mut parts = vec![vec![]; total as usize + 1];
256-
parts[seq as usize] = inner.to_vec();
257-
self.buffer.insert(key, parts);
258-
}
264+
Ok(try {
265+
let id: Id = inner[..ID_SIZE].try_into().ok()?;
266+
let inner = &inner[ID_SIZE..];
259267

260-
// SAFETY: Guaranteed to exist because it's populated directly above.
261-
let parts = unsafe { self.buffer.get(&key).unwrap_unchecked() };
268+
// Skip if we already have this packet.
269+
if self.recent.contains(&id) {
270+
None?;
271+
}
262272

263-
if parts.iter().all(|p| !p.is_empty()) {
273+
if let Some(parts) = self.buffer.get_mut(&id) {
274+
parts[seq as usize] = inner.to_vec();
275+
} else {
276+
let mut parts = vec![vec![]; total as usize + 1];
277+
parts[seq as usize] = inner.to_vec();
278+
self.buffer.insert(id, parts);
279+
}
280+
281+
// SAFETY: Guaranteed to exist because it's populated directly above.
282+
let parts = unsafe { self.buffer.get(&id).unwrap_unchecked() };
283+
284+
// Short-circuit if we don't have all the parts yet.
285+
if !parts.iter().all(|p| !p.is_empty()) {
286+
None?;
287+
}
288+
289+
// Put the packet together.
264290
let packet = Packet::deserialize(tag, &parts.concat());
265291
if packet.is_some() {
266-
self.buffer.remove(&key);
292+
self.buffer.remove(&id);
293+
self.recent.push(id);
267294
}
268-
Ok(packet)
269-
} else {
270-
Ok(None)
271-
}
295+
packet?
296+
})
272297
} else {
273298
Ok(None)
274299
}

src/ringbuffer.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#[derive(Clone, Debug)]
2+
pub struct Ringbuffer<T> {
3+
data: Box<[Option<T>]>,
4+
index: usize,
5+
}
6+
7+
impl<T: Clone> Ringbuffer<T> {
8+
pub fn with_capacity(capacity: usize) -> Self {
9+
Self {
10+
data: vec![None; capacity].into_boxed_slice(),
11+
index: 0,
12+
}
13+
}
14+
15+
pub fn push(&mut self, item: T) {
16+
self.data[self.index] = Some(item);
17+
self.index = (self.index + 1) % self.data.len();
18+
}
19+
}
20+
21+
impl<T: PartialEq> Ringbuffer<T> {
22+
pub fn contains(&self, item: &T) -> bool {
23+
self.data.iter().any(|x| x.as_ref() == Some(item))
24+
}
25+
}

src/ui.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ use std::thread;
1616

1717
use crossbeam_channel::unbounded;
1818
use cursive::backends::crossterm::crossterm::style::Stylize;
19-
use cursive::traits::Nameable;
20-
use cursive::views::{Dialog, LinearLayout, TextView};
19+
use cursive::views::{Dialog, LinearLayout};
2120

2221
use self::config::CONFIG;
2322
use self::dialog::interface::show_iface_dialog;
@@ -45,13 +44,8 @@ pub fn run() {
4544
while siv.is_running() {
4645
while let Ok(cmd) = ui_rx.try_recv() {
4746
match cmd {
48-
UICommand::NewMessage(username, id, msg) => {
49-
siv.call_on_name("chat_inner", |chat_inner: &mut LinearLayout| {
50-
chat_inner.add_child(
51-
TextView::new(format!("[{username}] {msg}"))
52-
.with_name(format!("{id:x?}_msg")),
53-
);
54-
});
47+
UICommand::NewMessage(username, msg) => {
48+
append_txt(&mut siv, "chat_inner", format!("[{username}] {msg}"));
5549
}
5650
UICommand::UpdateUsername(new_username) => {
5751
if new_username == username {

src/ui/net_thread.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ pub(super) fn start_net_thread(tx: Sender<UICommand>, rx: Receiver<NetCommand>)
8080
Some((_, username)) => username.clone(),
8181
None => "unknown".to_string(),
8282
};
83-
tx.send(UICommand::NewMessage(username, id, msg)).unwrap()
83+
tx.send(UICommand::NewMessage(username, msg)).unwrap()
8484
}
8585
Some(Packet::PresenceReq) => {
8686
if state == NetThreadState::NeedsInitialPresence {

src/ui/util.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub enum UICommand {
1717
SendMessage(String),
1818
SetInterface(String),
1919
SetEtherType(EtherType),
20-
NewMessage(String, Id, String),
20+
NewMessage(String, String),
2121
PresenceUpdate(Id, String, bool, UpdatePresenceKind),
2222
RemovePresence(Id, String),
2323
Error(ArpchatError),

0 commit comments

Comments
 (0)