use bytes::Bytes;
use crate::packets::PACKET_KIND_SIZE;
mod challenge;
mod keep_alive;
mod ping_latency;
pub mod prelude {
pub use super::{
Encodable, PacketKind, challenge::ChallengePacket, keep_alive::KeepAlivePacket,
ping_latency::PingLatencyPacket,
};
}
pub trait Encodable: Sized {
const KIND: PacketKind;
fn encode(self) -> Option<Bytes> {
None
}
fn encode_with_kind(self) -> Bytes {
use crate::packets::encoder::Encoder;
if let Some(bytes) = self.encode() {
Encoder::with_capacity(PACKET_KIND_SIZE + bytes.len())
.put_u8(Self::KIND as u8)
.put_bytes(bytes)
.finalize()
} else {
Encoder::with_capacity(PACKET_KIND_SIZE)
.put_u8(Self::KIND as u8)
.finalize()
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PacketKind {
KeepAlive = 29,
PingLatency = 30,
Challenge = 31,
}
impl std::fmt::Display for PacketKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?} (0x{:02X})", self, *self as u8)
}
}
#[cfg(test)]
mod tests {
use super::*;
const PAYLOAD: &[u8] = &[1, 2, 3, 4];
struct Packet;
impl Encodable for Packet {
const KIND: PacketKind = PacketKind::PingLatency;
fn encode(self) -> Option<Bytes> {
Some(Bytes::from_static(PAYLOAD))
}
}
#[test]
fn encode_packet_returns_expected_bytes() {
let packet = Packet;
let encoded = packet
.encode()
.expect("Encoding should produce a byte buffer");
assert_eq!(
encoded.as_ref(),
PAYLOAD,
"Encoded bytes should match the predefined payload"
);
}
#[test]
fn encode_with_kind_should_prefix_payload_with_packet_kind() {
let encoded = Packet.encode_with_kind();
assert_eq!(
encoded.as_ref(),
&[PacketKind::PingLatency as u8, 1, 2, 3, 4],
"encode_with_kind should prepend the server packet kind before the encoded payload"
);
}
#[test]
fn packet_kind_display_should_include_hex_identifier() {
assert_eq!(
PacketKind::Challenge.to_string(),
"Challenge (0x1F)",
"Display should include both the variant name and hexadecimal id"
);
}
}