use thiserror::Error;
mod accept_market_offer;
mod accept_trade;
mod browse_market;
mod cancel_market_offer;
mod cancel_steps;
mod change_shared_party_experience;
mod close_trade;
mod create_buddy;
mod create_market_offer;
mod delete_buddy;
mod face;
mod inspect_trade;
mod invite_to_party;
mod join_party;
mod keep_alive;
mod leave_market;
mod leave_party;
mod movement;
mod pass_party_leadership;
mod ping_latency;
mod request_trade;
mod revoke_party_invite;
mod steps;
mod update_buddy;
pub mod prelude {
pub use super::{
Decodable, DecodableError, PacketKind,
accept_market_offer::AcceptMarketOfferPacket,
accept_trade::AcceptTradePacket,
browse_market::BrowseMarketPacket,
cancel_market_offer::CancelMarketOfferPacket,
cancel_steps::CancelStepsPacket,
change_shared_party_experience::ChangeSharedPartyExperiencePacket,
close_trade::CloseTradePacket,
create_buddy::CreateBuddyPacket,
create_market_offer::{CreateMarketOfferPacket, MarketOfferKind},
delete_buddy::DeleteBuddyPacket,
face::FacePacket,
inspect_trade::InspectTradePacket,
invite_to_party::InviteToPartyPacket,
join_party::JoinPartyPacket,
keep_alive::KeepAlivePacket,
leave_market::LeaveMarketPacket,
leave_party::LeavePartyPacket,
movement::StepPacket,
pass_party_leadership::PassPartyLeadershipPacket,
ping_latency::PingLatencyPacket,
request_trade::RequestTradePacket,
revoke_party_invite::RevokePartyInvitePacket,
steps::StepsPacket,
update_buddy::UpdateBuddyPacket,
};
}
#[derive(Debug, Error)]
pub enum DecodableError {
#[error("failed to decode packet: {0}")]
Decoder(#[from] crate::packets::decoder::DecoderError),
#[error("invalid value {value} for field '{field}'")]
InvalidFieldValue {
field: &'static str,
value: u8,
},
}
pub trait Decodable: Sized {
const KIND: PacketKind;
fn accepts_kind(kind: PacketKind) -> bool {
kind == Self::KIND
}
fn decode(bytes: &mut &[u8]) -> Result<Self, DecodableError>;
fn decode_with_kind(_: PacketKind, bytes: &mut &[u8]) -> Result<Self, DecodableError> {
Self::decode(bytes)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PacketKind {
ServerName = 0,
Login = 10,
Logout = 20,
Steps = 100,
StepNorth = 101,
StepEast = 102,
StepSouth = 103,
StepWest = 104,
CancelSteps = 105,
StepNorthEast = 106,
StepSouthEast = 107,
StepSouthWest = 108,
StepNorthWest = 109,
FaceNorth = 111,
FaceEast = 112,
FaceSouth = 113,
FaceWest = 114,
PingLatency = 29,
KeepAlive = 30,
RequestTrade = 125,
InspectTrade = 126,
AcceptTrade = 127,
CloseTrade = 128,
CreateBuddy = 220,
DeleteBuddy = 221,
UpdateBuddy = 222,
InviteToParty = 163,
JoinParty = 164,
RevokePartyInvite = 165,
PassPartyLeadership = 166,
LeaveParty = 167,
ChangeSharedPartyExperience = 168,
LeaveMarket = 244,
BrowseMarket = 245,
CreateMarketOffer = 246,
CancelMarketOffer = 247,
AcceptMarketOffer = 248,
}
impl TryFrom<u8> for PacketKind {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::ServerName),
10 => Ok(Self::Login),
20 => Ok(Self::Logout),
29 => Ok(Self::PingLatency),
30 => Ok(Self::KeepAlive),
100 => Ok(Self::Steps),
101 => Ok(Self::StepNorth),
102 => Ok(Self::StepEast),
103 => Ok(Self::StepSouth),
104 => Ok(Self::StepWest),
105 => Ok(Self::CancelSteps),
106 => Ok(Self::StepNorthEast),
107 => Ok(Self::StepSouthEast),
108 => Ok(Self::StepSouthWest),
109 => Ok(Self::StepNorthWest),
111 => Ok(Self::FaceNorth),
112 => Ok(Self::FaceEast),
113 => Ok(Self::FaceSouth),
114 => Ok(Self::FaceWest),
125 => Ok(Self::RequestTrade),
126 => Ok(Self::InspectTrade),
127 => Ok(Self::AcceptTrade),
128 => Ok(Self::CloseTrade),
163 => Ok(Self::InviteToParty),
164 => Ok(Self::JoinParty),
165 => Ok(Self::RevokePartyInvite),
166 => Ok(Self::PassPartyLeadership),
167 => Ok(Self::LeaveParty),
168 => Ok(Self::ChangeSharedPartyExperience),
220 => Ok(Self::CreateBuddy),
221 => Ok(Self::DeleteBuddy),
222 => Ok(Self::UpdateBuddy),
244 => Ok(Self::LeaveMarket),
245 => Ok(Self::BrowseMarket),
246 => Ok(Self::CreateMarketOffer),
247 => Ok(Self::CancelMarketOffer),
248 => Ok(Self::AcceptMarketOffer),
_ => Err(value),
}
}
}
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::*;
#[derive(Debug)]
struct Packet;
impl Decodable for Packet {
const KIND: PacketKind = PacketKind::PingLatency;
fn decode(bytes: &mut &[u8]) -> Result<Self, DecodableError> {
if bytes.is_empty() {
Err(DecodableError::Decoder(
crate::packets::decoder::DecoderError::Incomplete {
expected: 1,
available: 0,
},
))
} else {
Ok(Packet)
}
}
}
#[test]
fn decode_packet_returns_error_on_empty_buffer() {
let mut buffer: &[u8] = &[];
let error = Packet::decode(&mut buffer)
.expect_err("Expected DecoderError::Incomplete for empty buffer");
match error {
DecodableError::Decoder(crate::packets::decoder::DecoderError::Incomplete {
expected,
available,
}) => {
assert!(
expected == 1,
"Expected 1 byte to be required, got {}",
expected
);
assert!(
available == 0,
"Expected 0 bytes available, got {}",
available
);
}
other => {
panic!("Unexpected error variant: {:?}", other);
}
}
}
#[test]
fn decode_packet_succeeds_with_non_empty_buffer() {
const PAYLOAD: &[u8] = &[42];
let mut buffer: &[u8] = PAYLOAD;
let packet_result = Packet::decode(&mut buffer);
assert!(
packet_result.is_ok(),
"Decoding should succeed with non-empty buffer"
);
let packet = packet_result.unwrap();
assert!(
matches!(packet, Packet),
"Decoded packet should be of type Packet"
);
}
#[test]
fn packet_kind_should_convert_from_wire_values_and_format_for_logs() {
assert_eq!(
PacketKind::try_from(30),
Ok(PacketKind::KeepAlive),
"Wire value 30 should decode to the keep-alive client packet kind"
);
assert_eq!(
PacketKind::try_from(101),
Ok(PacketKind::StepNorth),
"Wire value 101 should decode to the step-north client packet kind"
);
assert_eq!(
PacketKind::PingLatency.to_string(),
"PingLatency (0x1D)",
"Display should include both the variant name and hexadecimal id"
);
assert_eq!(
PacketKind::try_from(222),
Ok(PacketKind::UpdateBuddy),
"Wire value 222 should decode to the update-buddy client packet kind"
);
}
}