use std::fmt;
use std::io::{Read, Write};
use anyhow::Result;
use crate::resource::prp::PlasmaRead;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LocFlags(u16);
impl LocFlags {
pub const LOCAL_ONLY: u16 = 0x1;
pub const VOLATILE: u16 = 0x2;
pub const RESERVED: u16 = 0x4;
pub const BUILT_IN: u16 = 0x8;
pub const ITINERANT: u16 = 0x10;
}
const GLOBAL_FIXED_LOC_IDX: u32 = 0;
const LOCAL_LOC_START_IDX: u32 = 3;
const LOCAL_LOC_END_IDX: u32 = 32;
const NORMAL_LOC_START_IDX: u32 = LOCAL_LOC_END_IDX + 1;
const RESERVED_LOC_START: u32 = 0xFF000000;
const GLOBAL_SERVER_LOC_IDX: u32 = RESERVED_LOC_START;
const RESERVED_LOC_AVAILABLE_START: u32 = GLOBAL_SERVER_LOC_IDX + 1;
const INVALID_LOC_IDX: u32 = 0xFFFFFFFF;
#[derive(Clone, Copy)]
pub struct Location {
pub sequence_number: u32,
pub flags: u16,
}
impl Location {
pub fn invalid() -> Self {
Self {
sequence_number: INVALID_LOC_IDX,
flags: 0,
}
}
pub fn new(sequence_number: u32, flags: u16) -> Self {
Self {
sequence_number,
flags,
}
}
pub fn make_reserved(number: u32) -> Self {
Self {
sequence_number: RESERVED_LOC_AVAILABLE_START + number,
flags: LocFlags::RESERVED,
}
}
pub fn make_normal(number: u32) -> Self {
Self {
sequence_number: NORMAL_LOC_START_IDX + number,
flags: 0,
}
}
pub fn is_valid(&self) -> bool {
self.sequence_number != INVALID_LOC_IDX
}
pub fn is_reserved(&self) -> bool {
self.flags & LocFlags::RESERVED != 0
}
pub fn is_itinerant(&self) -> bool {
self.flags & LocFlags::ITINERANT != 0
}
pub fn is_virtual(&self) -> bool {
self.sequence_number == GLOBAL_FIXED_LOC_IDX
}
pub fn is_local(&self) -> bool {
self.sequence_number >= LOCAL_LOC_START_IDX
&& self.sequence_number <= LOCAL_LOC_END_IDX
}
pub fn read(reader: &mut impl Read) -> Result<Self> {
let sequence_number = reader.read_u32()?;
let flags = reader.read_u16()?;
Ok(Self {
sequence_number,
flags,
})
}
pub fn write(&self, writer: &mut impl Write) -> Result<()> {
writer.write_all(&self.sequence_number.to_le_bytes())?;
writer.write_all(&self.flags.to_le_bytes())?;
Ok(())
}
pub const GLOBAL_FIXED: Location = Location {
sequence_number: GLOBAL_FIXED_LOC_IDX,
flags: 0,
};
pub const LOCAL_START: Location = Location {
sequence_number: LOCAL_LOC_START_IDX,
flags: 0,
};
pub const LOCAL_END: Location = Location {
sequence_number: LOCAL_LOC_END_IDX,
flags: 0,
};
pub const NORMAL_START: Location = Location {
sequence_number: NORMAL_LOC_START_IDX,
flags: 0,
};
pub const GLOBAL_SERVER: Location = Location {
sequence_number: GLOBAL_SERVER_LOC_IDX,
flags: LocFlags::RESERVED,
};
pub const INVALID: Location = Location {
sequence_number: INVALID_LOC_IDX,
flags: 0,
};
}
impl PartialEq for Location {
fn eq(&self, other: &Self) -> bool {
self.sequence_number == other.sequence_number
&& (self.flags & !LocFlags::ITINERANT) == (other.flags & !LocFlags::ITINERANT)
}
}
impl Eq for Location {}
impl std::hash::Hash for Location {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.sequence_number.hash(state);
(self.flags & !LocFlags::ITINERANT).hash(state);
}
}
impl PartialOrd for Location {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Location {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.sequence_number.cmp(&other.sequence_number)
}
}
impl Default for Location {
fn default() -> Self {
Self::invalid()
}
}
impl fmt::Debug for Location {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Location(S:{:#x} F:{:#x})", self.sequence_number, self.flags)
}
}
impl fmt::Display for Location {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "S{:#x}F{:#x}", self.sequence_number, self.flags)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_invalid_location() {
let loc = Location::invalid();
assert!(!loc.is_valid());
}
#[test]
fn test_valid_location() {
let loc = Location::new(100, 0);
assert!(loc.is_valid());
}
#[test]
fn test_round_trip() {
let loc = Location::new(0x12345678, 0x0003);
let mut buf = Vec::new();
loc.write(&mut buf).unwrap();
assert_eq!(buf.len(), 6);
let mut cursor = Cursor::new(&buf);
let loc2 = Location::read(&mut cursor).unwrap();
assert_eq!(loc, loc2);
}
#[test]
fn test_itinerant_equality() {
let loc1 = Location::new(100, 0);
let loc2 = Location::new(100, LocFlags::ITINERANT);
assert_eq!(loc1, loc2);
}
#[test]
fn test_reserved() {
let loc = Location::make_reserved(5);
assert!(loc.is_reserved());
assert!(loc.is_valid());
}
#[test]
fn test_well_known() {
assert!(Location::GLOBAL_FIXED.is_virtual());
assert!(Location::GLOBAL_SERVER.is_reserved());
assert!(!Location::INVALID.is_valid());
}
}