gpg_inspector_lib/packet/
user_attribute.rs1use crate::error::Result;
10use crate::stream::ByteStream;
11
12use super::Field;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ImageFormat {
17 Jpeg,
19 Private(u8),
21 Unknown(u8),
23}
24
25impl ImageFormat {
26 fn from_u8(b: u8) -> Self {
27 match b {
28 1 => ImageFormat::Jpeg,
29 100..=110 => ImageFormat::Private(b),
30 _ => ImageFormat::Unknown(b),
31 }
32 }
33
34 fn description(&self) -> String {
35 match self {
36 ImageFormat::Jpeg => "1 (JPEG)".to_string(),
37 ImageFormat::Private(n) => format!("{} (Private/Experimental)", n),
38 ImageFormat::Unknown(n) => format!("{} (Unknown)", n),
39 }
40 }
41}
42
43#[derive(Debug, Clone)]
45pub enum UserAttributeSubpacket {
46 Image {
48 header_version: u8,
50 format: ImageFormat,
52 image_data: Vec<u8>,
54 },
55 Unknown {
57 subpacket_type: u8,
59 data: Vec<u8>,
61 },
62}
63
64#[derive(Debug, Clone)]
66pub struct UserAttributePacket {
67 pub subpackets: Vec<UserAttributeSubpacket>,
69}
70
71pub fn parse_user_attribute(
73 stream: &mut ByteStream,
74 fields: &mut Vec<Field>,
75 body_offset: usize,
76) -> Result<UserAttributePacket> {
77 let mut subpackets = Vec::new();
78
79 while !stream.is_empty() {
80 let subpacket_start = body_offset + stream.pos();
81 let (subpacket_len, _len_size) = parse_subpacket_length(stream)?;
82
83 if subpacket_len == 0 {
84 continue;
85 }
86
87 let _type_pos = body_offset + stream.pos();
88 let subpacket_type = stream.octet()?;
89 let data_len = subpacket_len - 1; let subpacket = match subpacket_type {
92 1 => parse_image_subpacket(stream, fields, body_offset, data_len)?,
93 100..=110 => {
94 let data_start = body_offset + stream.pos();
95 let data = stream.bytes(data_len)?;
96 fields.push(Field::field(
97 "Private Subpacket",
98 format!("Type {}, {} bytes", subpacket_type, data.len()),
99 (subpacket_start, data_start + data_len),
100 ));
101 UserAttributeSubpacket::Unknown {
102 subpacket_type,
103 data,
104 }
105 }
106 _ => {
107 let data_start = body_offset + stream.pos();
108 let data = stream.bytes(data_len)?;
109 fields.push(Field::field(
110 "Unknown Subpacket",
111 format!("Type {}, {} bytes", subpacket_type, data.len()),
112 (subpacket_start, data_start + data_len),
113 ));
114 UserAttributeSubpacket::Unknown {
115 subpacket_type,
116 data,
117 }
118 }
119 };
120
121 subpackets.push(subpacket);
122 }
123
124 Ok(UserAttributePacket { subpackets })
125}
126
127fn parse_image_subpacket(
129 stream: &mut ByteStream,
130 fields: &mut Vec<Field>,
131 body_offset: usize,
132 data_len: usize,
133) -> Result<UserAttributeSubpacket> {
134 let header_start = body_offset + stream.pos();
135
136 let header_len_lo = stream.octet()? as u16;
139 let header_len_hi = stream.octet()? as u16;
140 let header_len = header_len_lo | (header_len_hi << 8);
141
142 let header_version = stream.octet()?;
143
144 fields.push(Field::field(
145 "Image Subpacket",
146 format!("Header v{}, {} bytes total", header_version, header_len),
147 (header_start, header_start + 3),
148 ));
149
150 if header_version == 1 {
151 let format_pos = body_offset + stream.pos();
152 let format_byte = stream.octet()?;
153 let format = ImageFormat::from_u8(format_byte);
154 fields.push(Field::subfield(
155 "Image Format",
156 format.description(),
157 (format_pos, format_pos + 1),
158 ));
159
160 let remaining_header = header_len as usize - 4; if remaining_header > 0 && stream.remaining() >= remaining_header {
163 stream.skip(remaining_header)?;
164 }
165
166 let image_start = body_offset + stream.pos();
168 let image_bytes_available = data_len.saturating_sub(header_len as usize);
169 let image_data = if image_bytes_available > 0 && stream.remaining() >= image_bytes_available
170 {
171 stream.bytes(image_bytes_available)?
172 } else {
173 stream.rest()
174 };
175
176 let detected_type = detect_image_type(&image_data);
178 let image_desc = if let Some(detected) = detected_type {
179 format!("{} bytes ({})", image_data.len(), detected)
180 } else {
181 format!("{} bytes", image_data.len())
182 };
183
184 fields.push(Field::subfield(
185 "Image Data",
186 image_desc,
187 (image_start, image_start + image_data.len()),
188 ));
189
190 Ok(UserAttributeSubpacket::Image {
191 header_version,
192 format,
193 image_data,
194 })
195 } else {
196 let data_start = body_offset + stream.pos();
198 let remaining = data_len.saturating_sub(3); let data = if remaining > 0 {
200 stream.bytes(remaining)?
201 } else {
202 Vec::new()
203 };
204
205 fields.push(Field::subfield(
206 "Image Data",
207 format!("{} bytes (unknown header v{})", data.len(), header_version),
208 (data_start, data_start + data.len()),
209 ));
210
211 Ok(UserAttributeSubpacket::Image {
212 header_version,
213 format: ImageFormat::Unknown(0),
214 image_data: data,
215 })
216 }
217}
218
219fn parse_subpacket_length(stream: &mut ByteStream) -> Result<(usize, usize)> {
221 let first = stream.octet()? as usize;
222 if first < 192 {
223 Ok((first, 1))
224 } else if first < 255 {
225 let second = stream.octet()? as usize;
226 Ok((((first - 192) << 8) + second + 192, 2))
227 } else {
228 let len = stream.uint32()? as usize;
229 Ok((len, 5))
230 }
231}
232
233fn detect_image_type(data: &[u8]) -> Option<&'static str> {
235 if data.len() < 4 {
236 return None;
237 }
238
239 if data.starts_with(&[0xFF, 0xD8, 0xFF]) {
241 return Some("JPEG detected");
242 }
243
244 if data.starts_with(&[0x89, 0x50, 0x4E, 0x47]) {
246 return Some("PNG detected");
247 }
248
249 if data.starts_with(&[0x47, 0x49, 0x46, 0x38]) {
251 return Some("GIF detected");
252 }
253
254 if data.starts_with(&[0x42, 0x4D]) {
256 return Some("BMP detected");
257 }
258
259 None
260}