1use crate::errors::{
6 BlobIdFromSliceError, BlobIdParseError, CupMissingField, ResolutionContextError,
7};
8use fidl_fuchsia_pkg as fidl;
9use proptest_derive::Arbitrary;
10use serde::{Deserialize, Serialize};
11use std::{fmt, str};
12use typed_builder::TypedBuilder;
13
14pub(crate) const BLOB_ID_SIZE: usize = 32;
15
16#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, Arbitrary)]
18pub struct BlobId(#[serde(with = "hex_serde")] [u8; BLOB_ID_SIZE]);
19
20impl BlobId {
21 pub fn parse(s: &str) -> Result<Self, BlobIdParseError> {
32 s.parse()
33 }
34 pub fn as_bytes(&self) -> &[u8] {
36 &self.0[..]
37 }
38}
39
40impl str::FromStr for BlobId {
41 type Err = BlobIdParseError;
42
43 fn from_str(s: &str) -> Result<Self, Self::Err> {
44 let bytes = hex::decode(s)?;
45 if bytes.len() != BLOB_ID_SIZE {
46 return Err(BlobIdParseError::InvalidLength(bytes.len()));
47 }
48 if s.chars().any(|c| c.is_uppercase()) {
49 return Err(BlobIdParseError::CannotContainUppercase);
50 }
51 let mut res: [u8; BLOB_ID_SIZE] = [0; BLOB_ID_SIZE];
52 res.copy_from_slice(&bytes[..]);
53 Ok(Self(res))
54 }
55}
56
57impl From<[u8; BLOB_ID_SIZE]> for BlobId {
58 fn from(bytes: [u8; BLOB_ID_SIZE]) -> Self {
59 Self(bytes)
60 }
61}
62
63impl From<fidl::BlobId> for BlobId {
64 fn from(id: fidl::BlobId) -> Self {
65 Self(id.merkle_root)
66 }
67}
68
69impl From<BlobId> for fidl::BlobId {
70 fn from(id: BlobId) -> Self {
71 Self { merkle_root: id.0 }
72 }
73}
74
75impl From<fuchsia_hash::Hash> for BlobId {
76 fn from(hash: fuchsia_hash::Hash) -> Self {
77 Self(hash.into())
78 }
79}
80
81impl TryFrom<&[u8]> for BlobId {
82 type Error = BlobIdFromSliceError;
83
84 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
85 Ok(Self(bytes.try_into().map_err(|_| Self::Error::InvalidLength(bytes.len()))?))
86 }
87}
88
89impl From<BlobId> for fuchsia_hash::Hash {
90 fn from(id: BlobId) -> Self {
91 id.0.into()
92 }
93}
94
95impl fmt::Display for BlobId {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 f.write_str(&hex::encode(self.0))
98 }
99}
100
101impl fmt::Debug for BlobId {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 f.write_str(&self.to_string())
104 }
105}
106
107#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Arbitrary)]
109pub struct BlobInfo {
110 pub blob_id: BlobId,
111 pub length: u64,
112}
113
114impl BlobInfo {
115 pub fn new(blob_id: BlobId, length: u64) -> Self {
116 BlobInfo { blob_id: blob_id, length: length }
117 }
118}
119
120impl From<fidl::BlobInfo> for BlobInfo {
121 fn from(info: fidl::BlobInfo) -> Self {
122 BlobInfo { blob_id: info.blob_id.into(), length: info.length }
123 }
124}
125
126impl From<BlobInfo> for fidl::BlobInfo {
127 fn from(info: BlobInfo) -> Self {
128 Self { blob_id: info.blob_id.into(), length: info.length }
129 }
130}
131
132#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
134pub struct ResolutionContext {
135 blob_id: Option<BlobId>,
136}
137
138impl ResolutionContext {
139 pub fn new() -> Self {
141 Self { blob_id: None }
142 }
143
144 pub fn blob_id(&self) -> Option<&BlobId> {
146 self.blob_id.as_ref()
147 }
148}
149
150impl From<BlobId> for ResolutionContext {
151 fn from(blob_id: BlobId) -> Self {
152 Self { blob_id: Some(blob_id) }
153 }
154}
155
156impl From<fuchsia_hash::Hash> for ResolutionContext {
157 fn from(hash: fuchsia_hash::Hash) -> Self {
158 Self { blob_id: Some(hash.into()) }
159 }
160}
161
162impl TryFrom<&[u8]> for ResolutionContext {
163 type Error = ResolutionContextError;
164
165 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
166 if bytes.is_empty() {
167 Ok(Self { blob_id: None })
168 } else {
169 Ok(Self { blob_id: Some(bytes.try_into().map_err(Self::Error::InvalidBlobId)?) })
170 }
171 }
172}
173
174impl TryFrom<&fidl::ResolutionContext> for ResolutionContext {
175 type Error = ResolutionContextError;
176
177 fn try_from(context: &fidl::ResolutionContext) -> Result<Self, Self::Error> {
178 Self::try_from(context.bytes.as_slice())
179 }
180}
181
182impl From<ResolutionContext> for Vec<u8> {
183 fn from(context: ResolutionContext) -> Self {
184 match context.blob_id {
185 Some(blob_id) => blob_id.as_bytes().to_vec(),
186 None => vec![],
187 }
188 }
189}
190
191impl From<ResolutionContext> for fidl::ResolutionContext {
192 fn from(context: ResolutionContext) -> Self {
193 Self { bytes: context.into() }
194 }
195}
196
197mod hex_serde {
198 use super::BLOB_ID_SIZE;
199 use hex::FromHex;
200 use serde::Deserialize;
201
202 pub fn serialize<S>(bytes: &[u8; BLOB_ID_SIZE], serializer: S) -> Result<S::Ok, S::Error>
203 where
204 S: serde::Serializer,
205 {
206 let s = hex::encode(bytes);
207 serializer.serialize_str(&s)
208 }
209
210 pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; BLOB_ID_SIZE], D::Error>
211 where
212 D: serde::Deserializer<'de>,
213 {
214 let value = String::deserialize(deserializer)?;
215 <[u8; BLOB_ID_SIZE]>::from_hex(value.as_bytes())
216 .map_err(|e| serde::de::Error::custom(format!("bad hex value: {:?}: {}", value, e)))
217 }
218}
219
220#[derive(Clone, Debug, Eq, PartialEq, TypedBuilder)]
221pub struct CupData {
222 #[builder(default, setter(into))]
223 pub request: Vec<u8>,
224 #[builder(default, setter(into))]
225 pub key_id: u64,
226 #[builder(default, setter(into))]
227 pub nonce: [u8; 32],
228 #[builder(default, setter(into))]
229 pub response: Vec<u8>,
230 #[builder(default, setter(into))]
231 pub signature: Vec<u8>,
232}
233
234impl From<CupData> for fidl::CupData {
235 fn from(c: CupData) -> Self {
236 fidl::CupData {
237 request: Some(c.request),
238 key_id: Some(c.key_id),
239 nonce: Some(c.nonce),
240 response: Some(c.response),
241 signature: Some(c.signature),
242 ..Default::default()
243 }
244 }
245}
246
247impl TryFrom<fidl::CupData> for CupData {
248 type Error = CupMissingField;
249 fn try_from(c: fidl::CupData) -> Result<Self, Self::Error> {
250 Ok(CupData::builder()
251 .request(c.request.ok_or(CupMissingField::Request)?)
252 .response(c.response.ok_or(CupMissingField::Response)?)
253 .key_id(c.key_id.ok_or(CupMissingField::KeyId)?)
254 .nonce(c.nonce.ok_or(CupMissingField::Nonce)?)
255 .signature(c.signature.ok_or(CupMissingField::Signature)?)
256 .build())
257 }
258}
259
260#[cfg(test)]
261mod test_blob_id {
262 use super::*;
263 use assert_matches::assert_matches;
264 use proptest::prelude::*;
265
266 prop_compose! {
267 fn invalid_hex_char()(c in "[[:ascii:]&&[^0-9a-fA-F]]") -> char {
268 assert_eq!(c.len(), 1);
269 c.chars().next().unwrap()
270 }
271 }
272
273 proptest! {
274 #![proptest_config(ProptestConfig{
275 failure_persistence: None,
278 .. ProptestConfig::default()
279 })]
280
281 #[test]
282 fn parse_is_inverse_of_display(ref id in "[0-9a-f]{64}") {
283 prop_assert_eq!(
284 id,
285 &format!("{}", id.parse::<BlobId>().unwrap())
286 );
287 }
288
289 #[test]
290 fn parse_is_inverse_of_debug(ref id in "[0-9a-f]{64}") {
291 prop_assert_eq!(
292 id,
293 &format!("{:?}", id.parse::<BlobId>().unwrap())
294 );
295 }
296
297 #[test]
298 fn parse_rejects_uppercase(ref id in "[0-9A-F]{64}") {
299 prop_assert_eq!(
300 id.parse::<BlobId>(),
301 Err(BlobIdParseError::CannotContainUppercase)
302 );
303 }
304
305 #[test]
306 fn parse_rejects_unexpected_characters(mut id in "[0-9a-f]{64}", c in invalid_hex_char(), index in 0usize..63) {
307 id.remove(index);
308 id.insert(index, c);
309 prop_assert_eq!(
310 id.parse::<BlobId>(),
311 Err(BlobIdParseError::FromHexError(
312 hex::FromHexError::InvalidHexCharacter { c: c, index: index }
313 ))
314 );
315 }
316
317 #[test]
318 fn parse_expects_even_sized_strings(ref id in "[0-9a-f]([0-9a-f]{2})*") {
319 prop_assert_eq!(
320 id.parse::<BlobId>(),
321 Err(BlobIdParseError::FromHexError(hex::FromHexError::OddLength))
322 );
323 }
324
325 #[test]
326 fn parse_expects_precise_count_of_bytes(ref id in "([0-9a-f]{2})*") {
327 prop_assume!(id.len() != BLOB_ID_SIZE * 2);
328 prop_assert_eq!(
329 id.parse::<BlobId>(),
330 Err(BlobIdParseError::InvalidLength(id.len() / 2))
331 );
332 }
333
334 #[test]
335 fn fidl_conversions_are_inverses(id: BlobId) {
336 let temp : fidl::BlobId = id.into();
337 prop_assert_eq!(
338 id,
339 BlobId::from(temp)
340 );
341 }
342 }
343
344 #[test]
345 fn try_from_slice_rejects_invalid_length() {
346 assert_matches!(
347 BlobId::try_from([0u8; BLOB_ID_SIZE - 1].as_slice()),
348 Err(BlobIdFromSliceError::InvalidLength(31))
349 );
350 assert_matches!(
351 BlobId::try_from([0u8; BLOB_ID_SIZE + 1].as_slice()),
352 Err(BlobIdFromSliceError::InvalidLength(33))
353 );
354 }
355
356 #[test]
357 fn try_from_slice_succeeds() {
358 let bytes = [1u8; 32];
359 assert_eq!(BlobId::try_from(bytes.as_slice()).unwrap().as_bytes(), bytes.as_slice());
360 }
361}
362
363#[cfg(test)]
364mod test_resolution_context {
365 use super::*;
366 use assert_matches::assert_matches;
367
368 #[test]
369 fn try_from_slice_succeeds() {
370 assert_eq!(
371 ResolutionContext::try_from([].as_slice()).unwrap(),
372 ResolutionContext { blob_id: None }
373 );
374
375 assert_eq!(
376 ResolutionContext::try_from([1u8; 32].as_slice()).unwrap(),
377 ResolutionContext { blob_id: Some([1u8; 32].into()) }
378 );
379 }
380
381 #[test]
382 fn try_from_slice_fails() {
383 assert_matches!(
384 ResolutionContext::try_from([1u8].as_slice()),
385 Err(ResolutionContextError::InvalidBlobId(_))
386 );
387 }
388
389 #[test]
390 fn into_vec() {
391 assert_eq!(Vec::from(ResolutionContext::new()), Vec::<u8>::new());
392
393 assert_eq!(Vec::from(ResolutionContext::from(BlobId::from([1u8; 32]))), vec![1u8; 32]);
394 }
395
396 #[test]
397 fn try_from_fidl_succeeds() {
398 assert_eq!(
399 ResolutionContext::try_from(&fidl::ResolutionContext { bytes: vec![] }).unwrap(),
400 ResolutionContext { blob_id: None }
401 );
402
403 assert_eq!(
404 ResolutionContext::try_from(&fidl::ResolutionContext { bytes: vec![1u8; 32] }).unwrap(),
405 ResolutionContext { blob_id: Some([1u8; 32].into()) }
406 );
407 }
408
409 #[test]
410 fn try_from_fidl_fails() {
411 assert_matches!(
412 ResolutionContext::try_from(&fidl::ResolutionContext { bytes: vec![1u8; 1] }),
413 Err(ResolutionContextError::InvalidBlobId(_))
414 );
415 }
416
417 #[test]
418 fn into_fidl() {
419 assert_eq!(
420 fidl::ResolutionContext::from(ResolutionContext::new()),
421 fidl::ResolutionContext { bytes: vec![] }
422 );
423
424 assert_eq!(
425 fidl::ResolutionContext::from(ResolutionContext::from(BlobId::from([1u8; 32]))),
426 fidl::ResolutionContext { bytes: vec![1u8; 32] }
427 );
428 }
429}