tee_properties/
lib.rs

1// Copyright 2024 The Fuchsia Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use base64::engine::general_purpose::STANDARD;
6use base64::Engine as _;
7use indexmap::IndexMap;
8use num_traits::FromPrimitive;
9use serde::{Deserialize, Serialize};
10use std::fs::read_to_string;
11use std::path::Path;
12use std::sync::Arc;
13use tee_internal::{Identity, Login, Uuid};
14
15// Maps to GlobalPlatform TEE Internal Core API Section 4.4: Property Access Functions
16#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
17#[serde(rename_all = "snake_case")]
18pub enum PropType {
19    BinaryBlock,
20    UnsignedInt32,
21    UnsignedInt64,
22    Boolean,
23    Uuid,
24    Identity,
25    String,
26}
27
28#[derive(Clone, Debug, thiserror::Error)]
29pub enum PropertyError {
30    #[error("Bad format: Unable to parse type: {prop_type:?} from value: {value}")]
31    BadFormat { prop_type: PropType, value: String },
32    #[error("Item not found: {name}")]
33    ItemNotFound { name: String },
34    // Callers following Internal Core API spec should panic on this error.
35    #[error("Generic TeeProperty error: {msg}")]
36    Generic { msg: String },
37}
38
39#[derive(Debug, Deserialize, Serialize)]
40pub struct TeeProperty {
41    name: String,
42    prop_type: PropType,
43    value: String,
44}
45
46pub type TeeProperties = Vec<TeeProperty>;
47
48// Maps to GlobalPlatform TEE Internal Core API Section 4.2.4: Property Set Pseudo-Handles
49// TeePropSetTeeImplementation = 0xFFFFFFFD,
50// TeePropSetCurrentClient = 0xFFFFFFFE,
51// TeePropsetCurrentTA = 0xFFFFFFFF,
52#[derive(Deserialize, Serialize)]
53#[serde(rename_all = "snake_case")]
54pub enum PropSetType {
55    TeeImplementation,
56    CurrentClient,
57    CurrentTA,
58}
59
60pub type PropertiesMap = IndexMap<String, (PropType, String)>;
61
62#[derive(Clone)]
63pub struct PropSet {
64    properties: PropertiesMap,
65}
66
67impl PropSet {
68    #[cfg(test)]
69    pub(crate) fn new(properties: PropertiesMap) -> Self {
70        Self { properties }
71    }
72
73    pub fn from_config_file(config_path: &Path) -> Result<Self, PropertyError> {
74        match read_to_string(config_path) {
75            Ok(config_string) => Self::from_config_string(&config_string),
76            Err(e) => Err(PropertyError::Generic { msg: e.to_string() }),
77        }
78    }
79
80    pub fn from_config_string(config_string: &str) -> Result<Self, PropertyError> {
81        let props: TeeProperties = match serde_json5::from_str(config_string) {
82            Ok(tee_props) => tee_props,
83            Err(e) => return Err(PropertyError::Generic { msg: e.to_string() }),
84        };
85        let mut property_map = IndexMap::new();
86
87        for property in props {
88            property_map.insert(property.name, (property.prop_type, property.value));
89        }
90
91        Ok(Self { properties: property_map })
92    }
93
94    fn get_value(&self, prop_name: String) -> Result<String, PropertyError> {
95        match self.properties.get(&prop_name) {
96            Some((_, val)) => Ok(val.clone()),
97            None => Err(PropertyError::ItemNotFound { name: prop_name }),
98        }
99    }
100
101    pub fn get_string_property(&self, prop_name: String) -> Result<String, PropertyError> {
102        self.get_value(prop_name)
103    }
104
105    pub fn get_boolean_property(&self, prop_name: String) -> Result<bool, PropertyError> {
106        parse_bool(self.get_value(prop_name)?)
107    }
108
109    pub fn get_uint32_property(&self, prop_name: String) -> Result<u32, PropertyError> {
110        parse_uint32(self.get_value(prop_name)?)
111    }
112
113    pub fn get_uint64_property(&self, prop_name: String) -> Result<u64, PropertyError> {
114        parse_uint64(self.get_value(prop_name)?)
115    }
116
117    pub fn get_binary_block_property(&self, prop_name: String) -> Result<Vec<u8>, PropertyError> {
118        parse_binary_block(self.get_value(prop_name)?)
119    }
120
121    pub fn get_uuid_property(&self, prop_name: String) -> Result<Uuid, PropertyError> {
122        parse_uuid(self.get_value(prop_name)?)
123    }
124
125    pub fn get_identity_property(&self, prop_name: String) -> Result<Identity, PropertyError> {
126        parse_identity(self.get_value(prop_name)?)
127    }
128
129    pub fn get_property_name_at_index(&self, index: usize) -> Result<String, PropertyError> {
130        match self.properties.get_index(index) {
131            Some((prop_name, (_, _))) => Ok(prop_name.to_string()),
132            None => Err(PropertyError::ItemNotFound { name: format!("item at index {}", index) }),
133        }
134    }
135
136    pub fn get_property_type_at_index(&self, index: usize) -> Result<PropType, PropertyError> {
137        match self.properties.get_index(index) {
138            Some((_, (prop_type, _))) => Ok(prop_type.clone()),
139            None => Err(PropertyError::ItemNotFound { name: format!("item at index {}", index) }),
140        }
141    }
142
143    pub fn get_number_of_props(&self) -> usize {
144        self.properties.len()
145    }
146}
147
148pub struct PropEnumerator {
149    properties: Option<Arc<PropSet>>,
150    index: usize,
151}
152
153impl PropEnumerator {
154    pub fn new() -> Self {
155        Self { properties: None, index: 0 }
156    }
157
158    // Spec indicates that callers should start() before doing other operations.
159    // This impl doesn't need to do any functional work here since the initial state is valid.
160    pub fn start(&mut self, propset: Arc<PropSet>) {
161        self.properties = Some(propset);
162        self.index = 0;
163    }
164
165    pub fn reset(&mut self) {
166        self.index = 0;
167    }
168
169    // Spec: return ItemNotFound if enumerator not started or has reached end.
170    pub fn next(&mut self) -> Result<(), PropertyError> {
171        // The get_props()? call will cover the not_started case.
172        let num_props = self.get_props()?.get_number_of_props();
173
174        self.index = self.index + 1;
175        if self.index >= num_props {
176            return Err(PropertyError::ItemNotFound {
177                name: "enumerator has reached the end of the property set".to_string(),
178            });
179        }
180        Ok(())
181    }
182
183    pub fn get_property_name(&self) -> Result<String, PropertyError> {
184        // This will return ItemNotFound when going out-of-bounds, compliant with spec.
185        self.get_props()?.get_property_name_at_index(self.index)
186    }
187
188    pub fn get_property_type(&self) -> Result<PropType, PropertyError> {
189        // This will return ItemNotFound when going out-of-bounds, compliant with spec.
190        self.get_props()?.get_property_type_at_index(self.index)
191    }
192
193    pub fn get_property_as_string(&self) -> Result<String, PropertyError> {
194        let prop_name = self.get_property_name()?;
195        self.get_props()?.get_string_property(prop_name)
196    }
197
198    pub fn get_property_as_bool(&self) -> Result<bool, PropertyError> {
199        let prop_name = self.get_property_name()?;
200        self.get_props()?.get_boolean_property(prop_name)
201    }
202
203    pub fn get_property_as_u32(&self) -> Result<u32, PropertyError> {
204        let prop_name = self.get_property_name()?;
205        self.get_props()?.get_uint32_property(prop_name)
206    }
207
208    pub fn get_property_as_u64(&self) -> Result<u64, PropertyError> {
209        let prop_name = self.get_property_name()?;
210        self.get_props()?.get_uint64_property(prop_name)
211    }
212
213    pub fn get_property_as_binary_block(&self) -> Result<Vec<u8>, PropertyError> {
214        let prop_name = self.get_property_name()?;
215        self.get_props()?.get_binary_block_property(prop_name)
216    }
217
218    pub fn get_property_as_uuid(&self) -> Result<Uuid, PropertyError> {
219        let prop_name = self.get_property_name()?;
220        self.get_props()?.get_uuid_property(prop_name)
221    }
222
223    pub fn get_property_as_identity(&self) -> Result<Identity, PropertyError> {
224        let prop_name = self.get_property_name()?;
225        self.get_props()?.get_identity_property(prop_name)
226    }
227
228    fn get_props(&self) -> Result<&PropSet, PropertyError> {
229        match &self.properties {
230            Some(prop_set) => Ok(prop_set),
231            None => Err(PropertyError::ItemNotFound {
232                name: "enumerator has not been started".to_string(),
233            }),
234        }
235    }
236}
237
238// These parsing functions define format of value strings expected in config files.
239fn parse_bool(value: String) -> Result<bool, PropertyError> {
240    match value.parse::<bool>() {
241        Ok(val) => Ok(val),
242        Err(_) => Err(PropertyError::BadFormat { prop_type: PropType::Boolean, value }),
243    }
244}
245
246// TODO(https://fxbug.dev/369916290): Support the full list of integer value encodings (See 4.4 in
247// the spec).
248fn parse_uint32(value: String) -> Result<u32, PropertyError> {
249    // TODO(b/369916290): Ensure parse matches spec-acceptable string number formats in section 4.4.
250    match value.parse::<u32>() {
251        Ok(val) => Ok(val),
252        Err(_) => Err(PropertyError::BadFormat { prop_type: PropType::UnsignedInt32, value }),
253    }
254}
255
256// TODO(https://fxbug.dev/369916290): Support the full list of integer value encodings (See 4.4 in
257// the spec).
258fn parse_uint64(value: String) -> Result<u64, PropertyError> {
259    // TODO(b/369916290): Ensure parse matches spec-acceptable string number formats in section 4.4.
260    match value.parse::<u64>() {
261        Ok(val) => Ok(val),
262        Err(_) => Err(PropertyError::BadFormat { prop_type: PropType::UnsignedInt64, value }),
263    }
264}
265
266fn parse_binary_block(value: String) -> Result<Vec<u8>, PropertyError> {
267    // The string is expected to be base64 encoded.
268    match STANDARD.decode(value.clone()) {
269        Ok(bytes) => Ok(Vec::from(bytes)),
270        Err(_) => Err(PropertyError::BadFormat { prop_type: PropType::BinaryBlock, value }),
271    }
272}
273
274fn parse_uuid(value: String) -> Result<Uuid, PropertyError> {
275    match uuid::Uuid::parse_str(&value) {
276        Ok(uuid) => {
277            let (time_low, time_mid, time_hi_and_version, clock_seq_and_node) = uuid.as_fields();
278            Ok(Uuid {
279                time_low: time_low,
280                time_mid: time_mid,
281                time_hi_and_version: time_hi_and_version,
282                clock_seq_and_node: *clock_seq_and_node,
283            })
284        }
285        Err(_) => Err(PropertyError::BadFormat { prop_type: PropType::Uuid, value }),
286    }
287}
288
289fn parse_identity(value: String) -> Result<Identity, PropertyError> {
290    // Encoded as `integer (':' uuid)?`.
291    let (login_str, uuid) = match value.split_once(':') {
292        Some((login_str, uuid_str)) => (login_str, parse_uuid(uuid_str.to_string())?),
293        None => (value.as_str(), Uuid::default()),
294    };
295    let login_val = match login_str.parse::<u32>() {
296        Ok(val) => val,
297        Err(_) => return Err(PropertyError::BadFormat { prop_type: PropType::Identity, value }),
298    };
299    let login = Login::from_u32(login_val)
300        .ok_or(PropertyError::BadFormat { prop_type: PropType::Identity, value })?;
301    Ok(Identity { login, uuid })
302}
303
304// TODO(b/366015756): Parsing logic should be fuzzed for production-hardening.
305#[cfg(test)]
306pub mod tests {
307    use super::*;
308
309    const TEST_PROP_NAME_STRING: &str = "gpd.tee.test.string";
310    const TEST_PROP_NAME_BOOL: &str = "gpd.tee.test.bool";
311    const TEST_PROP_NAME_U32: &str = "gpd.tee.test.u32";
312    const TEST_PROP_NAME_U64: &str = "gpd.tee.test.u64";
313    const TEST_PROP_NAME_BINARY_BLOCK: &str = "gpd.tee.test.binaryBlock";
314    const TEST_PROP_NAME_UUID: &str = "gpd.tee.test.uuid";
315    const TEST_PROP_NAME_IDENTITY: &str = "gpd.tee.test.identity";
316
317    const TEST_PROP_VAL_STRING: &str = "asdf";
318    const TEST_PROP_VAL_BOOL: &str = "true";
319    const TEST_PROP_VAL_U32: &str = "57";
320    const TEST_PROP_VAL_U64: &str = "4294967296"; // U32::MAX + 1
321    const TEST_PROP_VAL_BINARY_BLOCK: &str = "ZnVjaHNpYQ=="; // base64 encoding of "fuchsia"
322
323    // Randomly generated UUID for testing.
324    const TEST_PROP_VAL_UUID: &str = "9cccff19-13b5-4d4c-aa9e-5c8901a52e2f";
325    const TEST_PROP_UUID: Uuid = Uuid {
326        time_low: 0x9cccff19,
327        time_mid: 0x13b5,
328        time_hi_and_version: 0x4d4c,
329        clock_seq_and_node: [0xaa, 0x9e, 0x5c, 0x89, 0x01, 0xa5, 0x2e, 0x2f],
330    };
331
332    // TODO(https://fxbug.dev/369916290): Spell as 0xf0000000 when hex encodings are supported.
333    const TEST_PROP_VAL_IDENTITY: &str = "4026531840:9cccff19-13b5-4d4c-aa9e-5c8901a52e2f";
334    const TEST_PROP_IDENTITY: Identity =
335        Identity { login: Login::TrustedApp, uuid: TEST_PROP_UUID };
336
337    fn create_test_prop_map() -> PropertiesMap {
338        let mut props: IndexMap<String, (PropType, String)> = IndexMap::new();
339        props.insert(
340            TEST_PROP_NAME_STRING.to_string(),
341            (PropType::String, TEST_PROP_VAL_STRING.to_string()),
342        );
343        props.insert(
344            TEST_PROP_NAME_BOOL.to_string(),
345            (PropType::Boolean, TEST_PROP_VAL_BOOL.to_string()),
346        );
347        props.insert(
348            TEST_PROP_NAME_U32.to_string(),
349            (PropType::UnsignedInt32, TEST_PROP_VAL_U32.to_string()),
350        );
351        props.insert(
352            TEST_PROP_NAME_U64.to_string(),
353            (PropType::UnsignedInt64, TEST_PROP_VAL_U64.to_string()),
354        );
355        props.insert(
356            TEST_PROP_NAME_BINARY_BLOCK.to_string(),
357            (PropType::BinaryBlock, TEST_PROP_VAL_BINARY_BLOCK.to_string()),
358        );
359        props.insert(
360            TEST_PROP_NAME_UUID.to_string(),
361            (PropType::Uuid, TEST_PROP_VAL_UUID.to_string()),
362        );
363        props.insert(
364            TEST_PROP_NAME_IDENTITY.to_string(),
365            (PropType::Identity, TEST_PROP_VAL_IDENTITY.to_string()),
366        );
367        props
368    }
369
370    fn create_test_prop_set() -> PropSet {
371        PropSet::new(create_test_prop_map())
372    }
373
374    #[test]
375    pub fn test_load_config_from_string() {
376        let config_json = r#"[
377            {
378                "name": "gpd.tee.asdf",
379                "prop_type": "boolean",
380                "value": "true"
381            },
382            {
383                "name": "gpd.tee.other",
384                "prop_type": "binary_block",
385                "value": "testingzz"
386            }
387        ]
388        "#;
389
390        let prop_set: PropSet = PropSet::from_config_string(config_json).expect("loading config");
391
392        let bool_prop_value =
393            prop_set.get_boolean_property("gpd.tee.asdf".to_string()).expect("getting bool prop");
394        assert_eq!(true, bool_prop_value)
395    }
396
397    #[test]
398    pub fn test_load_config_from_string_failure() {
399        // Invalid json (missing opening `{` for first object)
400        let config_json = r#"[
401                "name": "gpd.tee.asdf",
402                "prop_type": "boolean",
403                "value": "true"
404            },
405            {
406                "name": "gpd.tee.other",
407                "prop_type": "binary_block",
408                "value": "testingzz"
409            }
410        ]
411        "#;
412
413        let res = PropSet::from_config_string(config_json);
414
415        match res.err() {
416            Some(PropertyError::Generic { .. }) => (),
417            _ => assert!(false, "Unexpected error type"),
418        }
419    }
420
421    // *** Enumerator Tests ***
422    #[test]
423    pub fn test_enumerator_query_prop_types_success() {
424        let mut enumerator = PropEnumerator::new();
425
426        // Enumerate in the order of test prop set, starting with string.
427        enumerator.start(Arc::new(create_test_prop_set()));
428
429        let prop_name = enumerator.get_property_name().expect("getting prop name");
430        assert_eq!(TEST_PROP_NAME_STRING.to_string(), prop_name);
431
432        let prop_type = enumerator.get_property_type().expect("getting prop type");
433        assert_eq!(PropType::String, prop_type);
434
435        let prop_val = enumerator.get_property_as_string().expect("getting prop as string");
436        assert_eq!(TEST_PROP_VAL_STRING.to_string(), prop_val);
437
438        // bool.
439        enumerator.next().expect("moving enumerator to next prop");
440
441        let prop_name = enumerator.get_property_name().expect("getting prop name");
442        assert_eq!(TEST_PROP_NAME_BOOL.to_string(), prop_name);
443
444        let prop_type = enumerator.get_property_type().expect("getting prop type");
445        assert_eq!(PropType::Boolean, prop_type);
446
447        let prop_val = enumerator.get_property_as_bool().expect("getting prop as bool");
448        assert_eq!(true, prop_val);
449
450        // u32.
451        enumerator.next().expect("moving enumerator to next prop");
452
453        let prop_name = enumerator.get_property_name().expect("getting prop name");
454        assert_eq!(TEST_PROP_NAME_U32.to_string(), prop_name);
455
456        let prop_type = enumerator.get_property_type().expect("getting prop type");
457        assert_eq!(PropType::UnsignedInt32, prop_type);
458
459        let prop_val = enumerator.get_property_as_u32().expect("getting prop as u32");
460        assert_eq!(57, prop_val);
461
462        // u64.
463        enumerator.next().expect("moving enumerator to next prop");
464
465        let prop_name = enumerator.get_property_name().expect("getting prop name");
466        assert_eq!(TEST_PROP_NAME_U64.to_string(), prop_name);
467
468        let prop_type = enumerator.get_property_type().expect("getting prop type");
469        assert_eq!(PropType::UnsignedInt64, prop_type);
470
471        let prop_val = enumerator.get_property_as_u64().expect("getting prop as u64");
472        assert_eq!(4294967296, prop_val);
473
474        // Binary block.
475        enumerator.next().expect("moving enumerator to next prop");
476
477        let prop_name = enumerator.get_property_name().expect("getting prop name");
478        assert_eq!(TEST_PROP_NAME_BINARY_BLOCK.to_string(), prop_name);
479
480        let prop_type = enumerator.get_property_type().expect("getting prop type");
481        assert_eq!(PropType::BinaryBlock, prop_type);
482
483        let prop_val =
484            enumerator.get_property_as_binary_block().expect("getting prop as binary block");
485        let bytes_expected = STANDARD.decode("ZnVjaHNpYQ==").expect("decoding binary block string");
486        assert_eq!(bytes_expected, prop_val);
487
488        // UUID.
489        enumerator.next().expect("moving enumerator to next prop");
490
491        let prop_name = enumerator.get_property_name().expect("getting prop name");
492        assert_eq!(TEST_PROP_NAME_UUID.to_string(), prop_name);
493
494        let prop_type = enumerator.get_property_type().expect("getting prop type");
495        assert_eq!(PropType::Uuid, prop_type);
496
497        let prop_val: Uuid = enumerator.get_property_as_uuid().expect("getting prop as uuid");
498        assert_eq!(TEST_PROP_UUID.time_low, prop_val.time_low);
499        assert_eq!(TEST_PROP_UUID.time_mid, prop_val.time_mid);
500        assert_eq!(TEST_PROP_UUID.time_hi_and_version, prop_val.time_hi_and_version);
501        assert_eq!(TEST_PROP_UUID.clock_seq_and_node, prop_val.clock_seq_and_node);
502
503        // Identity.
504        enumerator.next().expect("moving enumerator to next prop");
505
506        let prop_name = enumerator.get_property_name().expect("getting prop name");
507        assert_eq!(TEST_PROP_NAME_IDENTITY.to_string(), prop_name);
508
509        let prop_type = enumerator.get_property_type().expect("getting prop type");
510        assert_eq!(PropType::Identity, prop_type);
511
512        let prop_val: Identity =
513            enumerator.get_property_as_identity().expect("getting prop as identity");
514        assert_eq!(TEST_PROP_IDENTITY.login, prop_val.login);
515        // This should have the same test UUID; can reuse parsed expected uuid fields.
516        assert_eq!(TEST_PROP_IDENTITY.uuid.time_low, prop_val.uuid.time_low);
517        assert_eq!(TEST_PROP_IDENTITY.uuid.time_mid, prop_val.uuid.time_mid);
518        assert_eq!(TEST_PROP_IDENTITY.uuid.time_hi_and_version, prop_val.uuid.time_hi_and_version);
519        assert_eq!(TEST_PROP_IDENTITY.uuid.clock_seq_and_node, prop_val.uuid.clock_seq_and_node);
520
521        // Test error upon going out of bounds via next().
522        let res = enumerator.next();
523        match res.err() {
524            Some(PropertyError::ItemNotFound { .. }) => (),
525            _ => assert!(false, "Unexpected error type"),
526        }
527    }
528
529    #[test]
530    pub fn test_enumerator_reset() {
531        let mut enumerator = PropEnumerator::new();
532        enumerator.start(Arc::new(create_test_prop_set()));
533
534        let prop_name = enumerator.get_property_name().expect("getting prop name");
535        assert_eq!(TEST_PROP_NAME_STRING.to_string(), prop_name);
536
537        enumerator.next().expect("moving enumerator to next prop");
538
539        let prop_name = enumerator.get_property_name().expect("getting prop name");
540        assert_eq!(TEST_PROP_NAME_BOOL.to_string(), prop_name);
541
542        enumerator.reset();
543
544        let prop_name = enumerator.get_property_name().expect("getting prop name");
545        assert_eq!(TEST_PROP_NAME_STRING.to_string(), prop_name);
546    }
547
548    #[test]
549    pub fn test_enumerator_wrong_prop_type_error() {
550        let mut enumerator = PropEnumerator::new();
551        enumerator.start(Arc::new(create_test_prop_set()));
552
553        // First value is string type and not interpretable as bool, should error.
554        let res = enumerator.get_property_as_bool();
555        match res.err() {
556            Some(PropertyError::BadFormat { .. }) => (),
557            _ => assert!(false, "Unexpected error type"),
558        }
559
560        enumerator.next().expect("moving enumerator to next prop");
561
562        // Second value is bool, not interpretable as identity, should error.
563        let res = enumerator.get_property_as_identity();
564        match res.err() {
565            Some(PropertyError::BadFormat { .. }) => (),
566            _ => assert!(false, "Unexpected error type"),
567        }
568
569        // Getting the same bool value as bool should still work.
570        let res = enumerator.get_property_as_bool();
571        assert!(res.is_ok());
572    }
573
574    #[test]
575    pub fn test_enumerator_not_started_error() {
576        // This enumerator functionally starts in a usable initial state, with internal index = 0.
577        // The spec says callers should call start() first though, so we error if it isn't called.
578        let enumerator = PropEnumerator::new();
579
580        let res = enumerator.get_property_name();
581
582        // Return code should map to item not found error per spec.
583        match res.err() {
584            Some(PropertyError::ItemNotFound { .. }) => (),
585            _ => assert!(false, "Unexpected error type"),
586        }
587    }
588
589    // *** PropSet Getter Tests ***
590    #[test]
591    pub fn test_propset_get_not_found() {
592        let prop_set = create_test_prop_set();
593
594        let res = prop_set.get_boolean_property("name.that.isnt.there".to_string());
595
596        match res.err() {
597            Some(PropertyError::ItemNotFound { .. }) => (),
598            _ => assert!(false, "Unexpected error type"),
599        }
600    }
601
602    #[test]
603    pub fn test_propset_get_string_success() {
604        let prop_set = create_test_prop_set();
605
606        let test_str_val = prop_set
607            .get_string_property(TEST_PROP_NAME_STRING.to_string())
608            .expect("getting str prop val");
609        assert_eq!(TEST_PROP_VAL_STRING.to_string(), test_str_val);
610
611        // Spec indicates that any value can be represented as string, even if it doesn't match the type.
612        let test_bool_val_as_str = prop_set
613            .get_string_property(TEST_PROP_NAME_BOOL.to_string())
614            .expect("getting bool prop val as string");
615        assert_eq!(TEST_PROP_VAL_BOOL.to_string(), test_bool_val_as_str);
616    }
617
618    #[test]
619    pub fn test_propset_get_bool_success() {
620        let prop_set = create_test_prop_set();
621
622        let val = prop_set
623            .get_boolean_property(TEST_PROP_NAME_BOOL.to_string())
624            .expect("getting bool prop val");
625        assert_eq!(true, val);
626    }
627
628    #[test]
629    pub fn test_propset_get_bool_wrong_type() {
630        let prop_set = create_test_prop_set();
631
632        let res = prop_set.get_boolean_property(TEST_PROP_NAME_UUID.to_string());
633        match res.err() {
634            Some(PropertyError::BadFormat { .. }) => (),
635            _ => assert!(false, "Unexpected error type"),
636        }
637    }
638
639    #[test]
640    pub fn test_propset_get_u32_success() {
641        let prop_set = create_test_prop_set();
642
643        let val = prop_set
644            .get_uint32_property(TEST_PROP_NAME_U32.to_string())
645            .expect("getting u32 prop val");
646        assert_eq!(57, val);
647    }
648
649    #[test]
650    pub fn test_propset_get_u32_wrong_type() {
651        let prop_set = create_test_prop_set();
652
653        let res = prop_set.get_uint32_property(TEST_PROP_NAME_BOOL.to_string());
654        match res.err() {
655            Some(PropertyError::BadFormat { .. }) => (),
656            _ => assert!(false, "Unexpected error type"),
657        }
658    }
659
660    #[test]
661    pub fn test_propset_get_u64_success() {
662        let prop_set = create_test_prop_set();
663
664        let val = prop_set
665            .get_uint64_property(TEST_PROP_NAME_U64.to_string())
666            .expect("getting u64 prop val");
667        assert_eq!(4294967296, val);
668    }
669
670    #[test]
671    pub fn test_propset_get_u64_wrong_type() {
672        let prop_set = create_test_prop_set();
673
674        let res = prop_set.get_uint64_property(TEST_PROP_NAME_BOOL.to_string());
675        match res.err() {
676            Some(PropertyError::BadFormat { .. }) => (),
677            _ => assert!(false, "Unexpected error type"),
678        }
679    }
680
681    #[test]
682    pub fn test_propset_get_binary_block_success() {
683        let prop_set = create_test_prop_set();
684
685        let val = prop_set
686            .get_binary_block_property(TEST_PROP_NAME_BINARY_BLOCK.to_string())
687            .expect("getting binary block prop val");
688
689        let expected_bytes = STANDARD
690            .decode(TEST_PROP_VAL_BINARY_BLOCK)
691            .expect("decoding expected binary block bytes");
692        assert_eq!(expected_bytes, val);
693    }
694
695    #[test]
696    pub fn test_propset_get_binary_block_wrong_type() {
697        let prop_set = create_test_prop_set();
698
699        // Technically most of the test values (bool, u32, u64, string) are valid base64 strings.
700        // Use the serialized identity string to trigger parse failure since it has invalid chars.
701        let res = prop_set.get_binary_block_property(TEST_PROP_NAME_IDENTITY.to_string());
702        match res.err() {
703            Some(PropertyError::BadFormat { .. }) => (),
704            _ => assert!(false, "Unexpected error type"),
705        }
706    }
707
708    #[test]
709    pub fn test_propset_get_uuid_success() {
710        let prop_set = create_test_prop_set();
711
712        let val = prop_set
713            .get_uuid_property(TEST_PROP_NAME_UUID.to_string())
714            .expect("getting uuid prop val");
715
716        assert_eq!(TEST_PROP_UUID.time_low, val.time_low);
717        assert_eq!(TEST_PROP_UUID.time_mid, val.time_mid);
718        assert_eq!(TEST_PROP_UUID.time_hi_and_version, val.time_hi_and_version);
719        assert_eq!(TEST_PROP_UUID.clock_seq_and_node, val.clock_seq_and_node);
720    }
721
722    #[test]
723    pub fn test_propset_get_uuid_wrong_type() {
724        let prop_set = create_test_prop_set();
725
726        let res = prop_set.get_uuid_property(TEST_PROP_NAME_BOOL.to_string());
727        match res.err() {
728            Some(PropertyError::BadFormat { .. }) => (),
729            _ => assert!(false, "Unexpected error type"),
730        }
731    }
732
733    #[test]
734    pub fn test_propset_get_identity_success() {
735        let prop_set = create_test_prop_set();
736
737        let val: Identity = prop_set
738            .get_identity_property(TEST_PROP_NAME_IDENTITY.to_string())
739            .expect("getting identity prop val");
740
741        assert_eq!(TEST_PROP_IDENTITY.login, val.login);
742        assert_eq!(TEST_PROP_IDENTITY.uuid.time_low, val.uuid.time_low);
743        assert_eq!(TEST_PROP_IDENTITY.uuid.time_mid, val.uuid.time_mid);
744        assert_eq!(TEST_PROP_IDENTITY.uuid.time_hi_and_version, val.uuid.time_hi_and_version);
745        assert_eq!(TEST_PROP_IDENTITY.uuid.clock_seq_and_node, val.uuid.clock_seq_and_node);
746    }
747
748    #[test]
749    pub fn test_propset_get_identity_wrong_type() {
750        let prop_set = create_test_prop_set();
751
752        let res = prop_set.get_identity_property(TEST_PROP_NAME_BOOL.to_string());
753        match res.err() {
754            Some(PropertyError::BadFormat { .. }) => (),
755            _ => assert!(false, "Unexpected error type"),
756        }
757    }
758
759    // *** Parsing Function Tests ***
760    #[test]
761    pub fn test_parse_bool_success() {
762        let val_true = "true".to_string();
763        let val_false = "false".to_string();
764
765        let res_true = parse_bool(val_true).expect("parsing true");
766        let res_false = parse_bool(val_false).expect("parsing false");
767
768        assert!(res_true);
769        assert!(!res_false);
770    }
771
772    #[test]
773    pub fn test_parse_bool_empty_string() {
774        let val = "".to_string();
775
776        let res = parse_bool(val);
777
778        match res.err() {
779            Some(PropertyError::BadFormat { .. }) => (),
780            _ => assert!(false, "Unexpected error type"),
781        }
782    }
783
784    #[test]
785    pub fn test_parse_bool_bad_format() {
786        let val = "asdf".to_string();
787
788        let res = parse_bool(val);
789
790        match res.err() {
791            Some(PropertyError::BadFormat { .. }) => (),
792            _ => assert!(false, "Unexpected error type"),
793        }
794    }
795
796    #[test]
797    pub fn test_parse_bool_caps_not_accepted() {
798        let val = "TRUE".to_string();
799
800        let res = parse_bool(val);
801
802        match res.err() {
803            Some(PropertyError::BadFormat { .. }) => (),
804            _ => assert!(false, "Unexpected error type"),
805        }
806    }
807
808    #[test]
809    pub fn test_parse_u32_success() {
810        let val = "15".to_string();
811
812        let res = parse_uint32(val).expect("parsing 15");
813
814        assert_eq!(res, 15);
815    }
816
817    #[test]
818    pub fn test_parse_u32_empty_string() {
819        let val = "".to_string();
820
821        let res = parse_uint32(val);
822
823        match res.err() {
824            Some(PropertyError::BadFormat { .. }) => (),
825            _ => assert!(false, "Unexpected error type"),
826        }
827    }
828
829    #[test]
830    pub fn test_parse_u32_negative_value() {
831        let val = "-15".to_string();
832
833        let res = parse_uint32(val);
834
835        match res.err() {
836            Some(PropertyError::BadFormat { .. }) => (),
837            _ => assert!(false, "Unexpected error type"),
838        }
839    }
840
841    #[test]
842    pub fn test_parse_u32_too_large() {
843        // u32::MAX = 4_294_967_295u32
844        let val = "4294967296".to_string();
845
846        let res = parse_uint32(val);
847
848        match res.err() {
849            Some(PropertyError::BadFormat { .. }) => (),
850            _ => assert!(false, "Unexpected error type"),
851        }
852    }
853
854    #[test]
855    pub fn test_parse_u32_bad_format() {
856        let val = "text".to_string();
857
858        let res = parse_uint32(val);
859
860        match res.err() {
861            Some(PropertyError::BadFormat { .. }) => (),
862            _ => assert!(false, "Unexpected error type"),
863        }
864    }
865
866    #[test]
867    pub fn test_parse_u64_success() {
868        let val = "4294967296".to_string();
869
870        let res = parse_uint64(val).expect("parsing 4294967296");
871
872        assert_eq!(res, 4294967296);
873    }
874
875    #[test]
876    pub fn test_parse_u64_empty_string() {
877        let val = "".to_string();
878
879        let res = parse_uint64(val);
880
881        match res.err() {
882            Some(PropertyError::BadFormat { .. }) => (),
883            _ => assert!(false, "Unexpected error type"),
884        }
885    }
886
887    #[test]
888    pub fn test_parse_u64_negative_value() {
889        let val = "-15".to_string();
890
891        let res = parse_uint64(val);
892
893        match res.err() {
894            Some(PropertyError::BadFormat { .. }) => (),
895            _ => assert!(false, "Unexpected error type"),
896        }
897    }
898
899    #[test]
900    pub fn test_parse_u64_too_large() {
901        // u64::MAX = 18_446_744_073_709_551_615u64
902        let val = "18446744073709551616".to_string();
903
904        let res = parse_uint64(val);
905
906        match res.err() {
907            Some(PropertyError::BadFormat { .. }) => (),
908            _ => assert!(false, "Unexpected error type"),
909        }
910    }
911
912    #[test]
913    pub fn test_parse_u64_bad_format() {
914        let val = "text".to_string();
915
916        let res = parse_uint64(val);
917
918        match res.err() {
919            Some(PropertyError::BadFormat { .. }) => (),
920            _ => assert!(false, "Unexpected error type"),
921        }
922    }
923
924    #[test]
925    pub fn test_parse_binary_block_success() {
926        let bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
927        let encoded = STANDARD.encode(bytes);
928
929        let res = parse_binary_block(encoded).expect("parsing binary block");
930
931        assert_eq!(bytes.to_vec(), res);
932    }
933
934    #[test]
935    pub fn test_parse_binary_empty_string() {
936        let res = parse_binary_block("".to_string());
937
938        assert!(res.is_ok());
939    }
940
941    #[test]
942    pub fn test_parse_binary_block_invalid_base64() {
943        // Include characters outside of standard base64 set.
944        let bad_val = "asdf&^%@".to_string();
945
946        let res = parse_binary_block(bad_val);
947
948        match res.err() {
949            Some(PropertyError::BadFormat { .. }) => (),
950            _ => assert!(false, "Unexpected error type"),
951        }
952    }
953
954    #[test]
955    pub fn test_parse_binary_block_invalid_padding_chars_only() {
956        // Include characters outside of standard base64 set.
957        let bad_val = "====".to_string();
958
959        let res = parse_binary_block(bad_val);
960
961        match res.err() {
962            Some(PropertyError::BadFormat { .. }) => (),
963            _ => assert!(false, "Unexpected error type"),
964        }
965    }
966
967    #[test]
968    pub fn test_parse_uuid_success() {
969        let valid_uuid = "9cccff19-13b5-4d4c-aa9e-5c8901a52e2f".to_string();
970
971        let res = parse_uuid(valid_uuid);
972
973        assert!(res.is_ok());
974    }
975
976    #[test]
977    pub fn test_parse_uuid_empty_string() {
978        let res = parse_uuid("".to_string());
979
980        match res.err() {
981            Some(PropertyError::BadFormat { .. }) => (),
982            _ => assert!(false, "Unexpected error type"),
983        }
984    }
985
986    #[test]
987    pub fn test_parse_uuid_invalid_uuid() {
988        let invalid_uuid = "asdf".to_string();
989
990        let res = parse_uuid(invalid_uuid);
991
992        match res.err() {
993            Some(PropertyError::BadFormat { .. }) => (),
994            _ => assert!(false, "Unexpected error type"),
995        }
996    }
997
998    #[test]
999    pub fn test_parse_identity_success() {
1000        let res = parse_identity(TEST_PROP_VAL_IDENTITY.to_string());
1001        assert!(res.is_ok());
1002    }
1003
1004    #[test]
1005    pub fn test_parse_identity_empty_uuid() {
1006        // TODO(https://fxbug.dev/369916290): Spell as 0xf0000000 when hex encodings are supported.
1007        let res = parse_identity("4026531840".to_string());
1008        assert!(res.is_ok());
1009        let id = res.unwrap();
1010        assert_eq!(Uuid::default(), id.uuid);
1011    }
1012
1013    #[test]
1014    pub fn test_parse_identity_invalid_login_type() {
1015        // 0xefffffff is an implementation-reserved value.
1016        //
1017        // TODO(https://fxbug.dev/369916290): Spell as 0xefffffff when hex encodings are supported.
1018        let res = parse_identity("4026531839:9cccff19-13b5-4d4c-aa9e-5c8901a52e2f".to_string());
1019        match res.err() {
1020            Some(PropertyError::BadFormat { .. }) => (),
1021            _ => assert!(false, "Unexpected error type"),
1022        }
1023    }
1024}