diff --git a/Cargo.toml b/Cargo.toml index 6553358..99b684a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ default-members = ["insim", "insim_*"] resolver = "2" [workspace.package] -version = "1.1.1" +version = "2.0.0" authors = ["Karl Southern "] edition = "2021" rust-version = "1.75" diff --git a/insim/Cargo.toml b/insim/Cargo.toml index db45bb6..0e425d2 100644 --- a/insim/Cargo.toml +++ b/insim/Cargo.toml @@ -37,9 +37,9 @@ from_variants = { workspace = true } futures-util = { workspace = true, optional = true } if_chain = { workspace = true } indexmap = { workspace = true } -insim_core = { path = "../insim_core", version = "1.1.1" } -insim_pth = { path = "../insim_pth", optional = true, version = "1.1.1" } -insim_smx = { path = "../insim_smx", optional = true, version = "1.1.1" } +insim_core = { path = "../insim_core", version = "2.0.0" } +insim_pth = { path = "../insim_pth", optional = true, version = "2.0.0" } +insim_smx = { path = "../insim_smx", optional = true, version = "2.0.0" } serde = { workspace = true, features = ["derive"], optional = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["rt", "time", "net", "macros", "sync", "tracing"], optional = true } diff --git a/insim/src/insim/acr.rs b/insim/src/insim/acr.rs index 0bb4ed1..8365f83 100644 --- a/insim/src/insim/acr.rs +++ b/insim/src/insim/acr.rs @@ -11,6 +11,7 @@ use crate::identifiers::{ConnectionId, RequestId}; #[repr(u8)] #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[non_exhaustive] pub enum AcrResult { /// Command was processed #[default] diff --git a/insim/src/insim/axm.rs b/insim/src/insim/axm.rs index 04c002d..12a9f1b 100644 --- a/insim/src/insim/axm.rs +++ b/insim/src/insim/axm.rs @@ -30,6 +30,7 @@ pub struct ObjectInfo { #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] pub enum PmoAction { #[default] /// Sent by the layout loading system only diff --git a/insim/src/insim/btn.rs b/insim/src/insim/btn.rs index eb85575..ee9e4ca 100644 --- a/insim/src/insim/btn.rs +++ b/insim/src/insim/btn.rs @@ -92,6 +92,7 @@ bitflags::bitflags! { #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] /// Used within [Bfn] to specify the action to take. pub enum BfnType { #[default] diff --git a/insim/src/insim/cch.rs b/insim/src/insim/cch.rs index d5ec9e9..7a91676 100644 --- a/insim/src/insim/cch.rs +++ b/insim/src/insim/cch.rs @@ -7,6 +7,7 @@ use crate::identifiers::{PlayerId, RequestId}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] /// Camera/view identifiers pub enum CameraView { /// Arcade "follow" view diff --git a/insim/src/insim/cim.rs b/insim/src/insim/cim.rs index e4c5135..5ba0142 100644 --- a/insim/src/insim/cim.rs +++ b/insim/src/insim/cim.rs @@ -4,6 +4,7 @@ use crate::identifiers::{ConnectionId, RequestId}; #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[non_exhaustive] /// Used within the [Cim] packet to indicate the mode. pub enum CimMode { /// Not in a special mode @@ -48,6 +49,7 @@ impl Default for CimMode { #[repr(u8)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[non_exhaustive] /// CimMode::Normal, submode pub enum CimSubModeNormal { #[default] @@ -87,6 +89,7 @@ impl From for CimSubModeNormal { #[repr(u8)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[non_exhaustive] /// CimMode::Garage, submode pub enum CimSubModeGarage { #[default] @@ -142,6 +145,7 @@ impl From for CimSubModeGarage { #[repr(u8)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[non_exhaustive] /// CimMode::ShiftU, submode pub enum CimSubModeShiftU { #[default] diff --git a/insim/src/insim/cnl.rs b/insim/src/insim/cnl.rs index 5c69b2e..2f59e0e 100644 --- a/insim/src/insim/cnl.rs +++ b/insim/src/insim/cnl.rs @@ -7,6 +7,7 @@ use crate::identifiers::{ConnectionId, RequestId}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] /// Used within [Cnl] to indicate the leave reason. pub enum CnlReason { #[default] diff --git a/insim/src/insim/csc.rs b/insim/src/insim/csc.rs index 752d794..cd5ea1b 100644 --- a/insim/src/insim/csc.rs +++ b/insim/src/insim/csc.rs @@ -13,6 +13,7 @@ use crate::identifiers::{PlayerId, RequestId}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] /// Used within the [Csc] packet to indicate the type of state change. pub enum CscAction { #[default] diff --git a/insim/src/insim/flg.rs b/insim/src/insim/flg.rs index 205dd83..b842f30 100644 --- a/insim/src/insim/flg.rs +++ b/insim/src/insim/flg.rs @@ -8,6 +8,7 @@ use crate::identifiers::{PlayerId, RequestId}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] pub enum FlgType { #[default] /// Blue flag diff --git a/insim/src/insim/hlv.rs b/insim/src/insim/hlv.rs index 88f31d5..1e0bc09 100644 --- a/insim/src/insim/hlv.rs +++ b/insim/src/insim/hlv.rs @@ -13,6 +13,7 @@ use crate::identifiers::{PlayerId, RequestId}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] /// Used within [Hlv] to indicate the hotlap validity failure reason. pub enum Hlvc { /// Ground diff --git a/insim/src/insim/ipb.rs b/insim/src/insim/ipb.rs new file mode 100644 index 0000000..e66e8a0 --- /dev/null +++ b/insim/src/insim/ipb.rs @@ -0,0 +1,120 @@ +use std::{default::Default, net::Ipv4Addr}; + +use indexmap::{set::Iter as IndexSetIter, IndexSet}; +use insim_core::binrw::{self, binrw, BinRead, BinResult, BinWrite}; + +use crate::identifiers::RequestId; + +const IPB_MAX_BANS: usize = 120; + +#[binrw::parser(reader, endian)] +fn binrw_parse_ipb_bans(count: u8) -> BinResult> { + let mut data = IndexSet::new(); + for _i in 0..count { + let ip = Ipv4Addr::from(u32::read_options(reader, endian, ())?); + let _ = data.insert(ip); + } + Ok(data) +} + +#[binrw::writer(writer, endian)] +fn binrw_write_ipb_bans(input: &IndexSet) -> BinResult<()> { + for i in input.iter() { + u32::from(*i).write_options(writer, endian, ())?; + } + + Ok(()) +} + +#[binrw] +#[bw(assert(banips.len() <= IPB_MAX_BANS))] +#[derive(Debug, Clone, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +/// Mods Allowed - restrict the mods that can be used +pub struct Ipb { + /// Non-zero if the packet is a packet request or a reply to a request + pub reqi: RequestId, + + /// Number of bans in this packet, from the wire + /// This value is not to be trusted as we use an IndexSet to record the bans internally. It is technically + /// possible that LFS could return duplicate entries, but we have no way of verifying that. + #[bw(calc = banips.len() as u8)] + #[brw(pad_after = 4)] + numb: u8, + + #[br(parse_with = binrw_parse_ipb_bans, args(numb))] + #[bw(write_with = binrw_write_ipb_bans)] + banips: IndexSet, +} + +impl Ipb { + /// Returns `true` if a Vehicle is contained in this packet + pub fn contains(&self, v: &Ipv4Addr) -> bool { + self.banips.contains(v) + } + + /// Push a compressed form of a mod onto the list of allowed mods + /// and update the count. + pub fn insert(&mut self, ip: Ipv4Addr) -> bool { + self.banips.insert(ip) + } + + /// Remove a Vehicle from this packet + pub fn remove(&mut self, ip: &Ipv4Addr) -> bool { + self.banips.shift_remove(ip) + } + + /// Does this packet have no vehicles associated? + pub fn is_empty(&self) -> bool { + self.banips.is_empty() + } + + /// Clear any previously allowed mods. + pub fn clear(&mut self) { + self.banips.clear() + } + + /// Iterator for all allowed mods + pub fn iter(&self) -> IndexSetIter<'_, Ipv4Addr> { + self.banips.iter() + } + + /// Returns the number of allowed mods + pub fn len(&self) -> usize { + self.banips.len() + } +} + +#[cfg(test)] +mod tests { + use std::io::{Cursor, Seek}; + + use super::*; + + #[test] + fn test_encoding() { + let mut bans = Ipb::default(); + bans.reqi = RequestId(2); + let _ = bans.insert(Ipv4Addr::new(127, 0, 0, 1)); + + let mut buf = Cursor::new(Vec::new()); + bans.write_le(&mut buf).unwrap(); + buf.rewind().unwrap(); + + let buf2 = buf.clone().into_inner(); + assert_eq!( + buf2, + [ + 2, // reqi + 1, // numb + 0, 0, 0, 0, // padding / unused + 1, 0, 0, 127, // mod 1 + ] + ); + + let data2 = Ipb::read_le(&mut buf).unwrap(); + assert_eq!(bans, data2); + assert_eq!(data2.len(), 1); + assert!(data2.contains(&Ipv4Addr::new(127, 0, 0, 1))); + } +} diff --git a/insim/src/insim/jrr.rs b/insim/src/insim/jrr.rs index e28d71b..a1b2153 100644 --- a/insim/src/insim/jrr.rs +++ b/insim/src/insim/jrr.rs @@ -8,6 +8,7 @@ use crate::identifiers::{ConnectionId, PlayerId, RequestId}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] /// Used within the [Jrr] packet. pub enum JrrAction { #[default] diff --git a/insim/src/insim/lap.rs b/insim/src/insim/lap.rs index 6d9cb45..ea30ec5 100644 --- a/insim/src/insim/lap.rs +++ b/insim/src/insim/lap.rs @@ -13,6 +13,7 @@ use crate::identifiers::{PlayerId, RequestId}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[derive(Debug, Default, Clone)] +#[non_exhaustive] /// When /showfuel yes: double fuel percent / no: 255 pub enum Fuel200 { /// Double fuel percent @@ -62,6 +63,7 @@ impl BinRead for Fuel200 { #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[derive(Debug, Default, Clone)] +#[non_exhaustive] /// When /showfuel yes: fuel added percent / no: 255 pub enum Fuel { /// Double fuel percent diff --git a/insim/src/insim/mod.rs b/insim/src/insim/mod.rs index e2665f0..fe7cb0c 100644 --- a/insim/src/insim/mod.rs +++ b/insim/src/insim/mod.rs @@ -20,6 +20,7 @@ mod flg; mod hcp; mod hlv; mod iii; +mod ipb; mod isi; mod ism; mod jrr; @@ -82,6 +83,7 @@ pub use flg::{Flg, FlgType}; pub use hcp::{Hcp, HcpCarHandicap}; pub use hlv::{Hlv, Hlvc}; pub use iii::Iii; +pub use ipb::Ipb; pub use isi::{Isi, IsiFlags}; pub use ism::Ism; pub use jrr::{Jrr, JrrAction}; diff --git a/insim/src/insim/msl.rs b/insim/src/insim/msl.rs index e0c7a44..0190e37 100644 --- a/insim/src/insim/msl.rs +++ b/insim/src/insim/msl.rs @@ -11,6 +11,7 @@ use crate::identifiers::RequestId; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] pub enum SoundType { #[default] /// Silent diff --git a/insim/src/insim/mso.rs b/insim/src/insim/mso.rs index 7cf97ff..f001753 100644 --- a/insim/src/insim/mso.rs +++ b/insim/src/insim/mso.rs @@ -14,6 +14,7 @@ use crate::identifiers::{ConnectionId, PlayerId, RequestId}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] pub enum MsoUserType { /// System message. #[default] diff --git a/insim/src/insim/npl.rs b/insim/src/insim/npl.rs index 27a3fc1..88869e4 100644 --- a/insim/src/insim/npl.rs +++ b/insim/src/insim/npl.rs @@ -13,6 +13,7 @@ use crate::identifiers::{ConnectionId, PlayerId, RequestId}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] /// Tyre compounds/types pub enum TyreCompound { /// R1 diff --git a/insim/src/insim/oco.rs b/insim/src/insim/oco.rs index 1d77366..c620641 100644 --- a/insim/src/insim/oco.rs +++ b/insim/src/insim/oco.rs @@ -8,6 +8,7 @@ use crate::identifiers::RequestId; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] /// Object Control action to take. Used within [Oco]. pub enum OcoAction { #[default] @@ -26,6 +27,7 @@ pub enum OcoAction { #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] /// Which lights to manipulate. See [Oco]. pub enum OcoIndex { /// Layout lights 1 diff --git a/insim/src/insim/pen.rs b/insim/src/insim/pen.rs index 22c57e4..331ed22 100644 --- a/insim/src/insim/pen.rs +++ b/insim/src/insim/pen.rs @@ -7,6 +7,7 @@ use crate::identifiers::{PlayerId, RequestId}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] /// Penalty types pub enum PenaltyInfo { /// None, or cleared diff --git a/insim/src/insim/pit.rs b/insim/src/insim/pit.rs index 8f999d9..a451182 100644 --- a/insim/src/insim/pit.rs +++ b/insim/src/insim/pit.rs @@ -114,6 +114,7 @@ pub struct Psf { #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] /// Pit lane fact, or info. Used in [Pla]. pub enum PitLaneFact { #[default] diff --git a/insim/src/insim/racelaps.rs b/insim/src/insim/racelaps.rs index ce5a7ea..11d1ded 100644 --- a/insim/src/insim/racelaps.rs +++ b/insim/src/insim/racelaps.rs @@ -5,6 +5,7 @@ use insim_core::binrw::{self, BinRead, BinWrite}; /// Handles the rules around how RaceLaps are described within Insim automatically for you. #[derive(Debug, Default, Clone, Copy)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[non_exhaustive] pub enum RaceLaps { /// This is a practise session #[default] diff --git a/insim/src/insim/small.rs b/insim/src/insim/small.rs index fedd0ec..3537489 100644 --- a/insim/src/insim/small.rs +++ b/insim/src/insim/small.rs @@ -125,6 +125,7 @@ bitflags! { #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[non_exhaustive] /// [Small] packet subtype. pub enum SmallType { /// Nothing! diff --git a/insim/src/insim/ssh.rs b/insim/src/insim/ssh.rs index 95cebb7..c40dde9 100644 --- a/insim/src/insim/ssh.rs +++ b/insim/src/insim/ssh.rs @@ -10,6 +10,7 @@ use crate::identifiers::RequestId; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] /// Errors occurred during a [Ssh] request. pub enum SshError { #[default] diff --git a/insim/src/insim/sta.rs b/insim/src/insim/sta.rs index 2295dcc..018fdc0 100644 --- a/insim/src/insim/sta.rs +++ b/insim/src/insim/sta.rs @@ -13,6 +13,7 @@ use crate::identifiers::{PlayerId, RequestId}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] /// Game racing state pub enum RaceInProgress { /// No race in progress diff --git a/insim/src/insim/tiny.rs b/insim/src/insim/tiny.rs index 1e1f1da..86ad5c2 100644 --- a/insim/src/insim/tiny.rs +++ b/insim/src/insim/tiny.rs @@ -7,6 +7,7 @@ use crate::identifiers::RequestId; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] /// [Tiny] Subtype pub enum TinyType { /// Keepalive request/response @@ -96,6 +97,9 @@ pub enum TinyType { /// Request a Plh packet Plh = 28, + + /// Request a Ipb packet + Ipb = 29, } #[binrw] diff --git a/insim/src/insim/ttc.rs b/insim/src/insim/ttc.rs index 1240d72..e161729 100644 --- a/insim/src/insim/ttc.rs +++ b/insim/src/insim/ttc.rs @@ -7,6 +7,7 @@ use crate::identifiers::{ConnectionId, RequestId}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] /// [Ttc] subtype. pub enum TtcType { /// Send Axm for the current layout editor selection diff --git a/insim/src/insim/uco.rs b/insim/src/insim/uco.rs index c648ba3..766fc64 100644 --- a/insim/src/insim/uco.rs +++ b/insim/src/insim/uco.rs @@ -13,6 +13,7 @@ use crate::identifiers::{PlayerId, RequestId}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] /// Action for a [Uco] packet. pub enum UcoAction { #[default] diff --git a/insim/src/insim/vtn.rs b/insim/src/insim/vtn.rs index 1749d1e..c0d2a11 100644 --- a/insim/src/insim/vtn.rs +++ b/insim/src/insim/vtn.rs @@ -8,6 +8,7 @@ use crate::identifiers::{ConnectionId, RequestId}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[repr(u8)] #[brw(repr(u8))] +#[non_exhaustive] pub enum VtnAction { /// No vote, or cancel vote #[default] diff --git a/insim/src/packet.rs b/insim/src/packet.rs index e843af5..24f1ac5 100644 --- a/insim/src/packet.rs +++ b/insim/src/packet.rs @@ -278,6 +278,10 @@ pub enum Packet { #[brw(magic = 66u8)] Plh(Plh), + /// Both - Set/receive player handicap + #[brw(magic = 67u8)] + Ipb(Ipb), + /// Instruction - Ask the LFS World relay if we are an admin #[brw(magic = 250u8)] RelayArq(Arq), diff --git a/insim_pth/Cargo.toml b/insim_pth/Cargo.toml index ecf6818..1425a73 100644 --- a/insim_pth/Cargo.toml +++ b/insim_pth/Cargo.toml @@ -16,5 +16,5 @@ bench = false doctest = false [dependencies] -insim_core = { path = "../insim_core", version = "1.1.1" } +insim_core = { path = "../insim_core", version = "2.0.0" } thiserror = { workspace = true } diff --git a/insim_smx/Cargo.toml b/insim_smx/Cargo.toml index 9abc6b1..1812bbe 100644 --- a/insim_smx/Cargo.toml +++ b/insim_smx/Cargo.toml @@ -17,5 +17,5 @@ bench = false doctest = false [dependencies] -insim_core = { path = "../insim_core", version = "1.1.1" } +insim_core = { path = "../insim_core", version = "2.0.0" } thiserror = { workspace = true }