feat(bedrock): finish raknet acks/nacks logic

This commit is contained in:
Alexander Medvedev 2026-05-01 08:48:23 +02:00
parent 4616289fbe
commit a58ccdd33d
3 changed files with 200 additions and 67 deletions

View File

@ -1,7 +1,5 @@
use std::io::{Error, ErrorKind, Read, Write}; use std::io::{Error, ErrorKind, Read, Write};
use pumpkin_macros::packet;
const MAX_ACK_RECORDS: u16 = 4096; const MAX_ACK_RECORDS: u16 = 4096;
use crate::{ use crate::{
@ -9,12 +7,11 @@ use crate::{
serial::{PacketRead, PacketWrite}, serial::{PacketRead, PacketWrite},
}; };
#[packet(0xC0)] pub struct Acknowledge {
pub struct Ack { pub sequences: Vec<u32>,
sequences: Vec<u32>,
} }
impl Ack { impl Acknowledge {
#[must_use] #[must_use]
pub const fn new(sequences: Vec<u32>) -> Self { pub const fn new(sequences: Vec<u32>) -> Self {
Self { sequences } Self { sequences }
@ -37,7 +34,7 @@ impl Ack {
if size > MAX_ACK_RECORDS { if size > MAX_ACK_RECORDS {
return Err(Error::new( return Err(Error::new(
ErrorKind::InvalidData, ErrorKind::InvalidData,
"ACK packet range is too large.", "Acknowledge packet range is too large.",
)); ));
} }
@ -49,7 +46,7 @@ impl Ack {
} else { } else {
let start = u24::read(reader)?.0; let start = u24::read(reader)?.0;
let end = u24::read(reader)?.0; let end = u24::read(reader)?.0;
for i in start..end { for i in start..=end {
sequences.push(i); sequences.push(i);
} }
} }
@ -57,15 +54,22 @@ impl Ack {
Ok(Self { sequences }) Ok(Self { sequences })
} }
pub fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> { pub fn write<W: Write>(&self, writer: &mut W, id: u8) -> Result<(), Error> {
0xC0u8.write(writer)?; id.write(writer)?;
if self.sequences.is_empty() {
0u16.write_be(writer)?;
return Ok(());
}
let mut count: u16 = 0; let mut count: u16 = 0;
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut start = self.sequences[0]; let mut sequences = self.sequences.clone();
sequences.sort_unstable();
let mut start = sequences[0];
let mut end = start; let mut end = start;
for seq in self.sequences.iter().copied() { for seq in sequences.iter().copied().skip(1) {
if seq != end + 1 { if seq != end + 1 {
Self::write_range(start, end, &mut buf)?; Self::write_range(start, end, &mut buf)?;
count += 1; count += 1;

View File

@ -507,10 +507,11 @@ impl PumpkinServer {
} else if let Some(sock) = self.udp_socket.as_ref() } else if let Some(sock) = self.udp_socket.as_ref()
&& let Ok(packet) = BedrockClient::is_connection_request(&mut Cursor::new(&udp_buf[4..len])) { && let Ok(packet) = BedrockClient::is_connection_request(&mut Cursor::new(&udp_buf[4..len])) {
*master_client_id_counter += 1; *master_client_id_counter += 1;
let mut platform = BedrockClient::new(sock.clone(), client_addr, be_clients); let platform = BedrockClient::new(sock.clone(), client_addr, be_clients);
platform.handle_connection_request(packet).await; platform.handle_connection_request(packet).await;
let platform = Arc::new(platform);
platform.start_outgoing_packet_task(); platform.start_outgoing_packet_task();
clients_guard.insert(client_addr, Arc::new(platform)); clients_guard.insert(client_addr, platform);
} }
} else if let Some(sock) = self.udp_socket.as_ref() { } else if let Some(sock) = self.udp_socket.as_ref() {
let _ = BedrockClient::handle_offline_packet(&self.server, id, &mut Cursor::new(&udp_buf[1..len]), client_addr, sock).await; let _ = BedrockClient::handle_offline_packet(&self.server, id, &mut Cursor::new(&udp_buf[1..len]), client_addr, sock).await;

View File

@ -1,6 +1,6 @@
pub mod play; pub mod play;
use std::{ use std::{
collections::HashMap, collections::{BTreeMap, HashMap, HashSet},
io::{Cursor, Error, Write}, io::{Cursor, Error, Write},
sync::{ sync::{
Arc, Arc,
@ -16,7 +16,7 @@ use pumpkin_protocol::{
BClientPacket, PacketDecodeError, RawPacket, BClientPacket, PacketDecodeError, RawPacket,
bedrock::{ bedrock::{
MTU, RAKNET_ACK, RAKNET_GAME_PACKET, RAKNET_NACK, RakReliability, SubClient, MTU, RAKNET_ACK, RAKNET_GAME_PACKET, RAKNET_NACK, RakReliability, SubClient,
ack::Ack, ack::Acknowledge,
client::disconnect_player::CDisconnectPlayer, client::disconnect_player::CDisconnectPlayer,
frame_set::{Frame, FrameSet}, frame_set::{Frame, FrameSet},
packet_decoder::UDPNetworkDecoder, packet_decoder::UDPNetworkDecoder,
@ -74,7 +74,7 @@ pub struct BedrockClient {
tasks: TaskTracker, tasks: TaskTracker,
outgoing_packet_queue_send: Sender<Bytes>, outgoing_packet_queue_send: Sender<Bytes>,
/// A queue of serialized packets to send to the network /// A queue of serialized packets to send to the network
outgoing_packet_queue_recv: Option<Receiver<Bytes>>, outgoing_packet_queue_recv: Mutex<Option<Receiver<Bytes>>>,
/// The packet encoder for outgoing packets. /// The packet encoder for outgoing packets.
network_writer: Arc<Mutex<UDPNetworkEncoder>>, network_writer: Arc<Mutex<UDPNetworkEncoder>>,
@ -92,6 +92,12 @@ pub struct BedrockClient {
/// Store Fragments until the packet is complete /// Store Fragments until the packet is complete
compounds: Arc<Mutex<HashMap<u16, Vec<Option<Frame>>>>>, compounds: Arc<Mutex<HashMap<u16, Vec<Option<Frame>>>>>,
//input_sequence_number: AtomicU32, //input_sequence_number: AtomicU32,
received_sequences: Mutex<HashSet<u32>>,
pending_acks: Mutex<Vec<u32>>,
unacked_outgoing_frames: Mutex<HashMap<u32, (u8, Vec<u8>, std::time::Instant)>>,
expected_order_index: Mutex<HashMap<u8, u32>>,
highest_sequence_index: Mutex<HashMap<u8, u32>>,
ordered_queues: Mutex<HashMap<u8, BTreeMap<u32, Frame>>>,
} }
impl BedrockClient { impl BedrockClient {
@ -111,7 +117,7 @@ impl BedrockClient {
network_reader: Mutex::new(UDPNetworkDecoder::new()), network_reader: Mutex::new(UDPNetworkDecoder::new()),
tasks: TaskTracker::new(), tasks: TaskTracker::new(),
outgoing_packet_queue_send: send, outgoing_packet_queue_send: send,
outgoing_packet_queue_recv: Some(recv), outgoing_packet_queue_recv: Mutex::new(Some(recv)),
_use_frame_sets: AtomicBool::new(false), _use_frame_sets: AtomicBool::new(false),
output_sequence_number: AtomicU32::new(0), output_sequence_number: AtomicU32::new(0),
output_reliable_number: AtomicU32::new(0), output_reliable_number: AtomicU32::new(0),
@ -120,53 +126,81 @@ impl BedrockClient {
output_ordered_index: AtomicU32::new(0), output_ordered_index: AtomicU32::new(0),
compounds: Arc::new(Mutex::new(HashMap::new())), compounds: Arc::new(Mutex::new(HashMap::new())),
close_token: CancellationToken::new(), close_token: CancellationToken::new(),
received_sequences: Mutex::new(HashSet::new()),
pending_acks: Mutex::new(Vec::new()),
unacked_outgoing_frames: Mutex::new(HashMap::new()),
expected_order_index: Mutex::new(HashMap::new()),
highest_sequence_index: Mutex::new(HashMap::new()),
ordered_queues: Mutex::new(HashMap::new()),
//input_sequence_number: AtomicU32::new(0), //input_sequence_number: AtomicU32::new(0),
} }
} }
pub fn start_outgoing_packet_task(&mut self) { pub fn start_outgoing_packet_task(self: &Arc<Self>) {
let Some(mut packet_receiver) = self.outgoing_packet_queue_recv.take() else { let client = self.clone();
error!(
"Outgoing packet receiver was already taken for {}",
self.address
);
return;
};
let close_token = self.close_token.clone();
let writer = self.network_writer.clone();
let addr = self.address;
let socket = self.socket.clone();
self.spawn_task(async move { self.spawn_task(async move {
while !close_token.is_cancelled() { let mut packet_receiver = {
let recv_result = tokio::select! { let mut guard = client.outgoing_packet_queue_recv.lock().await;
() = close_token.cancelled() => { guard
None .take()
}, .expect("Outgoing packet receiver was already taken")
recv_result = packet_receiver.recv() => {
recv_result
}
}; };
let mut interval = tokio::time::interval(std::time::Duration::from_millis(100));
while !client.close_token.is_cancelled() {
tokio::select! {
_ = interval.tick() => {
// Flush ACKs
let mut pending = client.pending_acks.lock().await;
if !pending.is_empty() {
let ack = Acknowledge::new(pending.clone());
pending.clear();
let _ = client.send_acknowledgement(&ack, RAKNET_ACK).await;
}
// Check retransmission
let now = std::time::Instant::now();
let mut resend = Vec::new();
{
let unacked = client.unacked_outgoing_frames.lock().await;
for (seq, (id, data, timestamp)) in unacked.iter() {
if now.duration_since(*timestamp) > std::time::Duration::from_secs(1) {
resend.push((*seq, *id, data.clone()));
}
}
}
for (seq, id, data) in resend {
debug!("Resending reliable sequence {} (ID: {})", seq, id);
// Update timestamp
if let Some(entry) = client.unacked_outgoing_frames.lock().await.get_mut(&seq) {
entry.2 = now;
}
if let Err(err) = client.network_writer.lock().await.write_packet(&data, client.address, &client.socket).await {
warn!("Failed to resend packet for sequence {}: {}", seq, err);
}
}
}
recv_result = packet_receiver.recv() => {
let Some(packet_data) = recv_result else { let Some(packet_data) = recv_result else {
break; break;
}; };
if let Err(err) = writer if let Err(err) = client.network_writer
.lock() .lock()
.await .await
.write_packet(&packet_data, addr, &socket) .write_packet(&packet_data, client.address, &client.socket)
.await .await
{ {
// It is expected that the packet will fail if we are closed if !client.close_token.is_cancelled() {
if !close_token.is_cancelled() {
warn!("Failed to send packet to client: {err}",); warn!("Failed to send packet to client: {err}",);
// We now need to close the connection to the client since the stream is in an client.close_token.cancel();
// unknown state
close_token.cancel();
break; break;
} }
} }
} }
}
}
}); });
} }
@ -357,14 +391,22 @@ impl BedrockClient {
} }
pub async fn send_frame_set(&self, mut frame_set: FrameSet, id: u8) { pub async fn send_frame_set(&self, mut frame_set: FrameSet, id: u8) {
frame_set.sequence = u24(self.output_sequence_number.fetch_add(1, Ordering::Relaxed)); let sequence = self.output_sequence_number.fetch_add(1, Ordering::Relaxed);
let mut frame_set_buf = Vec::new(); frame_set.sequence = u24(sequence);
let mut frame_set_buf = Vec::new();
if let Err(err) = frame_set.write_packet_data(&mut frame_set_buf, id) { if let Err(err) = frame_set.write_packet_data(&mut frame_set_buf, id) {
error!("Failed to write frame set data: {err}"); error!("Failed to write frame set data: {err}");
return; return;
} }
if frame_set.frames.iter().any(|f| f.reliability.is_reliable()) {
self.unacked_outgoing_frames.lock().await.insert(
sequence,
(id, frame_set_buf.clone(), std::time::Instant::now()),
);
}
if let Err(err) = self if let Err(err) = self
.network_writer .network_writer
.lock() .lock()
@ -393,9 +435,9 @@ impl BedrockClient {
self.close_token.is_cancelled() self.close_token.is_cancelled()
} }
pub async fn send_ack(&self, ack: &Ack) -> Result<(), Error> { pub async fn send_acknowledgement(&self, ack: &Acknowledge, id: u8) -> Result<(), Error> {
let mut packet_buf = Vec::new(); let mut packet_buf = Vec::new();
ack.write(&mut packet_buf)?; ack.write(&mut packet_buf, id)?;
if let Err(err) = self if let Err(err) = self
.network_writer .network_writer
@ -404,7 +446,7 @@ impl BedrockClient {
.write_packet(&packet_buf, self.address, &self.socket) .write_packet(&packet_buf, self.address, &self.socket)
.await .await
{ {
warn!("Failed to send ACK to {}: {err}", self.address); warn!("Failed to send acknowledgement to {}: {err}", self.address);
self.close().await; self.close().await;
return Err(err); return Err(err);
} }
@ -420,10 +462,10 @@ impl BedrockClient {
match u8::read(reader)? { match u8::read(reader)? {
RAKNET_ACK => { RAKNET_ACK => {
Self::handle_ack(&Ack::read(reader)?); self.handle_ack(&Acknowledge::read(reader)?).await;
} }
RAKNET_NACK => { RAKNET_NACK => {
debug!("received nack, client is missing packets"); self.handle_nack(&Acknowledge::read(reader)?).await;
} }
0x80..0x8d => { 0x80..0x8d => {
self.handle_frame_set(server, FrameSet::read(reader)?) self.handle_frame_set(server, FrameSet::read(reader)?)
@ -436,16 +478,54 @@ impl BedrockClient {
Ok(()) Ok(())
} }
const fn handle_ack(_ack: &Ack) {} async fn handle_ack(&self, ack: &Acknowledge) {
let mut unacked = self.unacked_outgoing_frames.lock().await;
for seq in &ack.sequences {
unacked.remove(seq);
}
}
async fn handle_nack(&self, nack: &Acknowledge) {
debug!("Received NACK for sequences: {:?}", nack.sequences);
let unacked = self.unacked_outgoing_frames.lock().await;
for seq in &nack.sequences {
if let Some((_id, data, _timestamp)) = unacked.get(seq) {
if let Err(err) = self
.network_writer
.lock()
.await
.write_packet(data, self.address, &self.socket)
.await
{
warn!("Failed to resend packet for sequence {}: {}", seq, err);
}
}
}
}
async fn handle_frame_set( async fn handle_frame_set(
self: &Arc<Self>, self: &Arc<Self>,
server: &Arc<Server>, server: &Arc<Server>,
frame_set: FrameSet, frame_set: FrameSet,
) -> Result<(), Error> { ) -> Result<(), Error> {
// TODO: Send all ACKs in short intervals in batches let sequence = frame_set.sequence.0;
self.send_ack(&Ack::new(vec![frame_set.sequence.0])).await?;
// TODO {
let mut received = self.received_sequences.lock().await;
if received.contains(&sequence) {
debug!("Received duplicate RakNet sequence: {}", sequence);
return Ok(());
}
received.insert(sequence);
// Limit the size of received sequences to avoid memory leak
if received.len() > 4096 {
// This is a very simple way to clear it, ideally we'd use a sliding window
received.clear();
}
}
self.pending_acks.lock().await.push(sequence);
for frame in frame_set.frames { for frame in frame_set.frames {
self.handle_frame(server, frame).await?; self.handle_frame(server, frame).await?;
} }
@ -502,7 +582,55 @@ impl BedrockClient {
frame.split_size = 0; frame.split_size = 0;
} }
let mut payload = Cursor::new(frame.payload); // Handling Sequencing
if frame.reliability.is_sequenced() {
let mut highest_sequenced = self.highest_sequence_index.lock().await;
let current_highest = highest_sequenced.entry(frame.order_channel).or_insert(0);
if frame.sequence_index < *current_highest {
return Ok(());
}
*current_highest = frame.sequence_index;
}
// Handling Ordering
if frame.reliability.is_ordered() {
let mut expected_order = self.expected_order_index.lock().await;
let expected = expected_order.entry(frame.order_channel).or_insert(0);
if frame.order_index == *expected {
*expected += 1;
self.process_frame_payload(server, frame.payload).await?;
// Check for queued frames
let mut ordered_queues = self.ordered_queues.lock().await;
if let Some(queue) = ordered_queues.get_mut(&frame.order_channel) {
while let Some(next_frame) = queue.remove(expected) {
*expected += 1;
self.process_frame_payload(server, next_frame.payload)
.await?;
}
}
} else if frame.order_index > *expected {
let mut ordered_queues = self.ordered_queues.lock().await;
let queue = ordered_queues
.entry(frame.order_channel)
.or_insert_with(BTreeMap::new);
queue.insert(frame.order_index, frame);
}
// If frame.order_index < *expected, it's an old frame, discard it.
} else {
self.process_frame_payload(server, frame.payload).await?;
}
Ok(())
}
async fn process_frame_payload(
self: &Arc<Self>,
server: &Arc<Server>,
payload: Vec<u8>,
) -> Result<(), Error> {
let mut payload = Cursor::new(payload);
let id = u8::read(&mut payload)?; let id = u8::read(&mut payload)?;
self.handle_raknet_packet(server, i32::from(id), payload) self.handle_raknet_packet(server, i32::from(id), payload)
.await .await