1use crate::bytes::Endian;
11use crate::error::UfsError;
12
13pub const CG_MAGIC: u32 = 0x0009_0255;
15
16const OFF_MAGIC: usize = 4;
18const OFF_CGX: usize = 12;
19const OFF_NDBLK: usize = 20;
20const OFF_IUSEDOFF: usize = 92;
21const OFF_FREEOFF: usize = 96;
22const OFF_CLUSTEROFF: usize = 108;
23const OFF_NIBLK: usize = 116;
24const OFF_INITEDIBLK: usize = 120;
25
26const CG_MIN_LEN: usize = 124;
29
30#[derive(Debug, Clone, PartialEq, Eq)]
37#[non_exhaustive]
38pub struct CylinderGroup {
39 pub cgx: u32,
41 pub ndblk: u32,
43 pub niblk: i32,
45 pub initediblk: u32,
47 pub iusedoff: u32,
50 pub freeoff: u32,
53 pub clusteroff: u32,
55}
56
57impl CylinderGroup {
58 pub fn parse(data: &[u8], endian: Endian) -> Result<Self, UfsError> {
68 if data.len() < CG_MIN_LEN {
69 return Err(UfsError::Truncated {
70 structure: "cylinder group",
71 need: CG_MIN_LEN,
72 have: data.len(),
73 });
74 }
75 let magic = endian.u32(data, OFF_MAGIC);
76 if magic != CG_MAGIC {
77 return Err(UfsError::BadCgMagic {
78 found: magic,
79 endian,
80 });
81 }
82 Ok(Self {
83 cgx: endian.u32(data, OFF_CGX),
84 ndblk: endian.u32(data, OFF_NDBLK),
85 niblk: endian.i32(data, OFF_NIBLK),
86 initediblk: endian.u32(data, OFF_INITEDIBLK),
87 iusedoff: endian.u32(data, OFF_IUSEDOFF),
88 freeoff: endian.u32(data, OFF_FREEOFF),
89 clusteroff: endian.u32(data, OFF_CLUSTEROFF),
90 })
91 }
92
93 #[must_use]
95 pub fn inosused_off(&self) -> usize {
96 self.iusedoff as usize
97 }
98
99 #[must_use]
101 pub fn blksfree_off(&self) -> usize {
102 self.freeoff as usize
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 fn synthetic(endian: Endian) -> Vec<u8> {
111 let mut d = vec![0u8; CG_MIN_LEN];
112 let wr32 = |d: &mut [u8], off: usize, v: u32| {
113 let b = match endian {
114 Endian::Little => v.to_le_bytes(),
115 Endian::Big => v.to_be_bytes(),
116 };
117 d[off..off + 4].copy_from_slice(&b);
118 };
119 wr32(&mut d, OFF_MAGIC, CG_MAGIC);
120 wr32(&mut d, OFF_CGX, 0);
121 wr32(&mut d, OFF_NDBLK, 256);
122 wr32(&mut d, OFF_NIBLK, 8);
123 wr32(&mut d, OFF_INITEDIBLK, 128);
124 wr32(&mut d, OFF_IUSEDOFF, 168);
125 wr32(&mut d, OFF_FREEOFF, 184);
126 wr32(&mut d, OFF_CLUSTEROFF, 200);
127 d
128 }
129
130 #[test]
131 fn parses_valid_cg_little_endian() {
132 let d = synthetic(Endian::Little);
133 let cg = CylinderGroup::parse(&d, Endian::Little).unwrap();
134 assert_eq!(cg.cgx, 0);
135 assert_eq!(cg.ndblk, 256);
136 assert_eq!(cg.niblk, 8);
137 assert_eq!(cg.iusedoff, 168);
138 assert_eq!(cg.freeoff, 184);
139 assert_eq!(cg.inosused_off(), 168);
140 assert_eq!(cg.blksfree_off(), 184);
141 }
142
143 #[test]
144 fn parses_valid_cg_big_endian() {
145 let d = synthetic(Endian::Big);
146 let cg = CylinderGroup::parse(&d, Endian::Big).unwrap();
147 assert_eq!(cg.ndblk, 256);
148 assert_eq!(cg.iusedoff, 168);
149 }
150
151 #[test]
152 fn bad_cg_magic_fails_loud() {
153 let mut d = synthetic(Endian::Little);
154 d[OFF_MAGIC..OFF_MAGIC + 4].copy_from_slice(&0x1234_5678_u32.to_le_bytes());
155 let err = CylinderGroup::parse(&d, Endian::Little).unwrap_err();
156 assert!(
157 matches!(&err, UfsError::BadCgMagic { found, .. } if *found == 0x1234_5678),
158 "expected BadCgMagic with the offending value, got {err:?}"
159 );
160 }
161
162 #[test]
163 fn wrong_endian_is_detected_as_bad_magic() {
164 let d = synthetic(Endian::Little);
166 assert!(matches!(
167 CylinderGroup::parse(&d, Endian::Big),
168 Err(UfsError::BadCgMagic { .. })
169 ));
170 }
171
172 #[test]
173 fn truncated_cg_does_not_panic() {
174 let d = vec![0u8; CG_MIN_LEN - 1];
175 assert!(matches!(
176 CylinderGroup::parse(&d, Endian::Little),
177 Err(UfsError::Truncated {
178 structure: "cylinder group",
179 ..
180 })
181 ));
182 }
183}