1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use crate::{
    connection::{IpmiCommand, Message, NetFn, ParseResponseError},
    fmt::LogItem,
    log_vec,
    storage::Timestamp,
    Loggable,
};

pub struct GetRepositoryInfo;

impl From<GetRepositoryInfo> for Message {
    fn from(_: GetRepositoryInfo) -> Self {
        Message::new_request(NetFn::Storage, 0x20, Vec::new())
    }
}

impl IpmiCommand for GetRepositoryInfo {
    type Output = RepositoryInfo;

    type Error = ();

    fn parse_response(
        completion_code: crate::connection::CompletionCode,
        data: &[u8],
    ) -> Result<Self::Output, ParseResponseError<Self::Error>> {
        Self::check_cc_success(completion_code)?;

        RepositoryInfo::parse(data).ok_or(ParseResponseError::NotEnoughData)
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FreeSpace {
    Full,
    AtLeast { bytes: u16 },
    Unspecified,
}

impl From<u16> for FreeSpace {
    fn from(value: u16) -> Self {
        match value {
            0x0000 => Self::Full,
            0xFFFF => Self::Unspecified,
            v => Self::AtLeast { bytes: v },
        }
    }
}

impl core::fmt::Display for FreeSpace {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            FreeSpace::Full => write!(f, "Full"),
            FreeSpace::AtLeast { bytes } => write!(f, "At least {} bytes", bytes),
            FreeSpace::Unspecified => write!(f, "Unspecified"),
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Operation {
    ModalityUnspecified,
    NonModalUpdate,
    ModalUpdate,
    NonModalAndModalUpdate,
    Delete,
    PartialAdd,
    Reserve,
    GetAllocInfo,
}

#[derive(Clone, Debug)]
pub struct RepositoryInfo {
    pub version_major: u8,
    pub version_minor: u8,
    pub record_count: u16,
    pub free_space: FreeSpace,
    pub most_recent_addition: Timestamp,
    pub most_recent_erase: Timestamp,
    pub overflow: bool,
    pub supported_ops: Vec<Operation>,
}

impl RepositoryInfo {
    pub fn parse(v: &[u8]) -> Option<Self> {
        let version_minor = (v[0] & 0xF0) >> 4;
        let version_major = v[0] & 0x0F;
        let record_count = u16::from_le_bytes([v[1], v[2]]);
        let free_space = FreeSpace::from(u16::from_le_bytes([v[3], v[4]]));
        let most_recent_addition = Timestamp::from(u32::from_le_bytes([v[5], v[6], v[7], v[8]]));
        let most_recent_erase = Timestamp::from(u32::from_le_bytes([v[9], v[10], v[11], v[12]]));
        let overflow = (v[13] & 0x80) == 0x80;

        let modality = v[13] & 0x60 >> 5;
        let modality = match modality {
            0b00 => Operation::ModalityUnspecified,
            0b01 => Operation::NonModalUpdate,
            0b10 => Operation::ModalUpdate,
            0b11 => Operation::NonModalAndModalUpdate,
            _ => unreachable!(),
        };

        let mut ops = Vec::with_capacity(5);
        ops.push(modality);

        for (offset, command) in [
            Operation::GetAllocInfo,
            Operation::Reserve,
            Operation::PartialAdd,
            Operation::Delete,
        ]
        .into_iter()
        .enumerate()
        {
            if v[13] & (1 << offset) == (1 << offset) {
                ops.push(command);
            }
        }

        Some(Self {
            version_major,
            version_minor,
            record_count,
            free_space,
            most_recent_addition,
            most_recent_erase,
            overflow,
            supported_ops: ops,
        })
    }
}

impl Loggable for RepositoryInfo {
    fn into_log(&self) -> Vec<LogItem> {
        let Self {
            version_major,
            version_minor,
            record_count,
            free_space,
            most_recent_addition,
            most_recent_erase,
            overflow,
            supported_ops,
        } = self;

        let (v_maj, v_min) = (version_major, version_minor);

        log_vec![
            (0, "SDR Repository Information"),
            (1, "Version", format!("{v_maj}.{v_min}")),
            (1, "Record count", record_count),
            (1, "Free space", free_space),
            (1, "Most recent add", most_recent_addition),
            (1, "Most recent erase", most_recent_erase),
            (1, "SDR Overflow", overflow),
            (1, "Supported ops", format!("{supported_ops:?}"))
        ]
    }
}