rust/bt-gatt: Refactor CharacteristicProperties to be zero-allocation

Use a static slice of CharacteristicProperty instead of Vec to avoid
allocation. Update all callsites to use this.

While here, define convenience types for common sets of properties.

Test: ./presubmit.sh

Change-Id: Ic6eb4d3e6413d7d9b3d03cf7a461c93e7a275cf7
Reviewed-on: https://bluetooth-review.googlesource.com/c/bluetooth/+/3760
diff --git a/rust/bt-ascs/src/client.rs b/rust/bt-ascs/src/client.rs
index ce1e1e8..89cfefc 100644
--- a/rust/bt-ascs/src/client.rs
+++ b/rust/bt-ascs/src/client.rs
@@ -662,7 +662,7 @@
     use bt_common::core::CodecId;
     use bt_common::generic_audio::metadata_ltv::Metadata;
     use bt_gatt::test_utils::{FakePeerService, FakeTypes};
-    use bt_gatt::types::{AttributePermissions, CharacteristicProperty};
+    use bt_gatt::types::{AttributePermissions, CharacteristicProperties, CharacteristicProperty};
     use bt_gatt::Characteristic;
 
     const CONTROL_POINT_HANDLE: Handle = Handle(1);
@@ -676,9 +676,7 @@
             Characteristic {
                 handle: CONTROL_POINT_HANDLE,
                 uuid: ASE_CONTROL_POINT_UUID,
-                properties: CharacteristicProperty::Write
-                    | CharacteristicProperty::WriteWithoutResponse
-                    | CharacteristicProperty::Notify,
+                properties: CharacteristicProperties::WRITE_NOTIFY,
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -689,7 +687,7 @@
             Characteristic {
                 handle: SINK_ASE_HANDLE,
                 uuid: SINK_ASE_UUID,
-                properties: CharacteristicProperty::Read | CharacteristicProperty::Notify,
+                properties: CharacteristicProperties::READ_NOTIFY,
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -700,7 +698,7 @@
             Characteristic {
                 handle: SOURCE_ASE_HANDLE,
                 uuid: SOURCE_ASE_UUID,
-                properties: CharacteristicProperty::Read | CharacteristicProperty::Notify,
+                properties: CharacteristicProperties::READ_NOTIFY,
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -750,7 +748,7 @@
             Characteristic {
                 handle: SINK_ASE_HANDLE,
                 uuid: SINK_ASE_UUID,
-                properties: CharacteristicProperty::Read | CharacteristicProperty::Notify,
+                properties: CharacteristicProperties::READ_NOTIFY,
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -773,7 +771,8 @@
             Characteristic {
                 handle: CONTROL_POINT_HANDLE,
                 uuid: ASE_CONTROL_POINT_UUID,
-                properties: CharacteristicProperty::Write | CharacteristicProperty::Notify,
+                properties: (&[CharacteristicProperty::Write, CharacteristicProperty::Notify])
+                    .into(),
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -796,7 +795,8 @@
             Characteristic {
                 handle: CONTROL_POINT_HANDLE,
                 uuid: ASE_CONTROL_POINT_UUID,
-                properties: CharacteristicProperty::Write | CharacteristicProperty::Notify,
+                properties: (&[CharacteristicProperty::Write, CharacteristicProperty::Notify])
+                    .into(),
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -807,7 +807,8 @@
             Characteristic {
                 handle: Handle(4),
                 uuid: ASE_CONTROL_POINT_UUID,
-                properties: CharacteristicProperty::Write | CharacteristicProperty::Notify,
+                properties: (&[CharacteristicProperty::Write, CharacteristicProperty::Notify])
+                    .into(),
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
diff --git a/rust/bt-ascs/src/server.rs b/rust/bt-ascs/src/server.rs
index 3ee9823..6c4ae42 100644
--- a/rust/bt-ascs/src/server.rs
+++ b/rust/bt-ascs/src/server.rs
@@ -8,7 +8,7 @@
 use bt_gatt::server::{LocalService, Server, ServiceDefinition, ServiceId};
 use bt_gatt::server::{ReadResponder, WriteResponder};
 use bt_gatt::types::{
-    AttributePermissions, CharacteristicProperty, GattError, Handle, SecurityLevels,
+    AttributePermissions, CharacteristicProperties, GattError, Handle, SecurityLevels,
 };
 use bt_gatt::Characteristic;
 
@@ -281,9 +281,7 @@
     }
 
     fn build_control_point() -> Characteristic {
-        let properties = CharacteristicProperty::Write
-            | CharacteristicProperty::WriteWithoutResponse
-            | CharacteristicProperty::Notify;
+        let properties = CharacteristicProperties::WRITE_NOTIFY;
         let permissions =
             AttributePermissions::with_levels(&properties, &SecurityLevels::encryption_required());
         Characteristic {
@@ -398,7 +396,7 @@
 
 impl From<&AudioStreamEndpoint> for Characteristic {
     fn from(value: &AudioStreamEndpoint) -> Self {
-        let properties = CharacteristicProperty::Read | CharacteristicProperty::Notify;
+        let properties = CharacteristicProperties::READ_NOTIFY;
         let permissions =
             AttributePermissions::with_levels(&properties, &SecurityLevels::encryption_required());
         Characteristic {
diff --git a/rust/bt-bass/src/client.rs b/rust/bt-bass/src/client.rs
index 1a1122f..aa29368 100644
--- a/rust/bt-bass/src/client.rs
+++ b/rust/bt-bass/src/client.rs
@@ -428,10 +428,7 @@
             Characteristic {
                 handle: RECEIVE_STATE_1_HANDLE,
                 uuid: BROADCAST_RECEIVE_STATE_UUID,
-                properties: CharacteristicProperties(vec![
-                    CharacteristicProperty::Broadcast,
-                    CharacteristicProperty::Notify,
-                ]),
+                properties: CharacteristicProperties::BROADCAST_NOTIFY,
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -441,10 +438,7 @@
             Characteristic {
                 handle: RECEIVE_STATE_2_HANDLE,
                 uuid: BROADCAST_RECEIVE_STATE_UUID,
-                properties: CharacteristicProperties(vec![
-                    CharacteristicProperty::Broadcast,
-                    CharacteristicProperty::Notify,
-                ]),
+                properties: CharacteristicProperties::BROADCAST_NOTIFY,
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -454,10 +448,7 @@
             Characteristic {
                 handle: RECEIVE_STATE_3_HANDLE,
                 uuid: BROADCAST_RECEIVE_STATE_UUID,
-                properties: CharacteristicProperties(vec![
-                    CharacteristicProperty::Broadcast,
-                    CharacteristicProperty::Notify,
-                ]),
+                properties: CharacteristicProperties::BROADCAST_NOTIFY,
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -467,7 +458,7 @@
             Characteristic {
                 handle: RANDOME_CHAR_HANDLE,
                 uuid: Uuid::from_u16(0x1234),
-                properties: CharacteristicProperties(vec![CharacteristicProperty::Notify]),
+                properties: CharacteristicProperty::Notify.into(),
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -477,7 +468,7 @@
             Characteristic {
                 handle: AUDIO_SCAN_CONTROL_POINT_HANDLE,
                 uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
-                properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]),
+                properties: CharacteristicProperty::Broadcast.into(),
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -517,10 +508,7 @@
             Characteristic {
                 handle: Handle(1),
                 uuid: BROADCAST_RECEIVE_STATE_UUID,
-                properties: CharacteristicProperties(vec![
-                    CharacteristicProperty::Broadcast,
-                    CharacteristicProperty::Notify,
-                ]),
+                properties: CharacteristicProperties::BROADCAST_NOTIFY,
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -543,7 +531,7 @@
             Characteristic {
                 handle: Handle(1),
                 uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
-                properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]),
+                properties: CharacteristicProperty::Broadcast.into(),
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -568,10 +556,7 @@
             Characteristic {
                 handle: Handle(1),
                 uuid: BROADCAST_RECEIVE_STATE_UUID,
-                properties: CharacteristicProperties(vec![
-                    CharacteristicProperty::Broadcast,
-                    CharacteristicProperty::Notify,
-                ]),
+                properties: CharacteristicProperties::BROADCAST_NOTIFY,
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -581,7 +566,7 @@
             Characteristic {
                 handle: Handle(2),
                 uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
-                properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]),
+                properties: CharacteristicProperty::Broadcast.into(),
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -591,7 +576,7 @@
             Characteristic {
                 handle: Handle(3),
                 uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
-                properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]),
+                properties: CharacteristicProperty::Broadcast.into(),
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -620,10 +605,7 @@
             Characteristic {
                 handle: RECEIVE_STATE_2_HANDLE,
                 uuid: BROADCAST_RECEIVE_STATE_UUID,
-                properties: CharacteristicProperties(vec![
-                    CharacteristicProperty::Broadcast,
-                    CharacteristicProperty::Notify,
-                ]),
+                properties: CharacteristicProperties::BROADCAST_NOTIFY,
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -662,10 +644,7 @@
             Characteristic {
                 handle: RECEIVE_STATE_3_HANDLE,
                 uuid: BROADCAST_RECEIVE_STATE_UUID,
-                properties: CharacteristicProperties(vec![
-                    CharacteristicProperty::Broadcast,
-                    CharacteristicProperty::Notify,
-                ]),
+                properties: CharacteristicProperties::BROADCAST_NOTIFY,
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
diff --git a/rust/bt-bass/src/server.rs b/rust/bt-bass/src/server.rs
index 29a20b8..cbffdad 100644
--- a/rust/bt-bass/src/server.rs
+++ b/rust/bt-bass/src/server.rs
@@ -12,7 +12,8 @@
     WriteResponder,
 };
 use bt_gatt::types::{
-    AttributePermissions, CharacteristicProperty, GattError, Handle, SecurityLevels, ServiceKind,
+    AttributePermissions, CharacteristicProperties, CharacteristicProperty, GattError, Handle,
+    SecurityLevels, ServiceKind,
 };
 use bt_gatt::Characteristic;
 use futures::stream::Stream;
@@ -176,12 +177,12 @@
     /// Constructs the Broadcast Audio Scan Control Point characteristic.
     /// Defined in BASS v1.0 Section 3.1.
     fn build_control_point() -> Characteristic {
-        let cp_properties =
-            CharacteristicProperty::Write | CharacteristicProperty::WriteWithoutResponse;
+        let cp_properties: CharacteristicProperties =
+            (&[CharacteristicProperty::Write, CharacteristicProperty::WriteWithoutResponse]).into();
         Characteristic {
             handle: CONTROL_POINT_HANDLE,
             uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
-            properties: cp_properties.clone(),
+            properties: cp_properties,
             permissions: AttributePermissions::with_levels(
                 &cp_properties,
                 &SecurityLevels::encryption_required(),
@@ -193,11 +194,11 @@
     /// Constructs the Broadcast Receive State characteristic.
     /// Defined in BASS v1.0 Section 3.2.
     fn build_receive_state(handle: Handle) -> Characteristic {
-        let properties = CharacteristicProperty::Read | CharacteristicProperty::Notify;
+        let properties = CharacteristicProperties::READ_NOTIFY;
         Characteristic {
             handle,
             uuid: BROADCAST_RECEIVE_STATE_UUID,
-            properties: properties.clone(),
+            properties,
             permissions: AttributePermissions::with_levels(
                 &properties,
                 &SecurityLevels::encryption_required(),
diff --git a/rust/bt-battery/src/monitor/client.rs b/rust/bt-battery/src/monitor/client.rs
index 0ac3ae3..29e20e4 100644
--- a/rust/bt-battery/src/monitor/client.rs
+++ b/rust/bt-battery/src/monitor/client.rs
@@ -203,7 +203,8 @@
     use bt_common::Uuid;
     use bt_gatt::test_utils::{FakeClient, FakePeerService, FakeTypes};
     use bt_gatt::types::{
-        AttributePermissions, Characteristic, CharacteristicProperties, GattError,
+        AttributePermissions, Characteristic, CharacteristicProperties, CharacteristicProperty,
+        GattError,
     };
     use futures::{pin_mut, FutureExt};
 
@@ -214,10 +215,7 @@
             Characteristic {
                 handle: BATTERY_LEVEL_HANDLE,
                 uuid: BATTERY_LEVEL_UUID,
-                properties: CharacteristicProperties(vec![
-                    CharacteristicProperty::Read,
-                    CharacteristicProperty::Notify,
-                ]),
+                properties: CharacteristicProperties::READ_NOTIFY,
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -257,9 +255,7 @@
             Characteristic {
                 handle: BATTERY_LEVEL_HANDLE,
                 uuid: BATTERY_LEVEL_UUID,
-                properties: CharacteristicProperties(vec![
-                    CharacteristicProperty::Read, // Only read is mandatory
-                ]),
+                properties: CharacteristicProperty::Read.into(), // Only read is mandatory
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -301,10 +297,7 @@
             Characteristic {
                 handle: BATTERY_LEVEL_HANDLE,
                 uuid: Uuid::from_u16(0x1234), // Random UUID, not Battery Level
-                properties: CharacteristicProperties(vec![
-                    CharacteristicProperty::Read,
-                    CharacteristicProperty::Notify,
-                ]),
+                properties: CharacteristicProperties::READ_NOTIFY,
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -329,10 +322,7 @@
             Characteristic {
                 handle: BATTERY_LEVEL_HANDLE,
                 uuid: BATTERY_LEVEL_UUID,
-                properties: CharacteristicProperties(vec![
-                    CharacteristicProperty::Read,
-                    CharacteristicProperty::Notify,
-                ]),
+                properties: CharacteristicProperties::READ_NOTIFY,
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
diff --git a/rust/bt-broadcast-assistant/src/assistant/peer.rs b/rust/bt-broadcast-assistant/src/assistant/peer.rs
index c4a4d10..8587e38 100644
--- a/rust/bt-broadcast-assistant/src/assistant/peer.rs
+++ b/rust/bt-broadcast-assistant/src/assistant/peer.rs
@@ -227,10 +227,7 @@
             Characteristic {
                 handle: RECEIVE_STATE_HANDLE,
                 uuid: bt_bass::types::BROADCAST_RECEIVE_STATE_UUID,
-                properties: CharacteristicProperties(vec![
-                    CharacteristicProperty::Broadcast,
-                    CharacteristicProperty::Notify,
-                ]),
+                properties: CharacteristicProperties::BROADCAST_NOTIFY,
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -240,7 +237,7 @@
             Characteristic {
                 handle: AUDIO_SCAN_CONTROL_POINT_HANDLE,
                 uuid: bt_bass::types::BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
-                properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]),
+                properties: CharacteristicProperty::Broadcast.into(),
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
diff --git a/rust/bt-csis/src/client.rs b/rust/bt-csis/src/client.rs
index f897c3f..38c1e86 100644
--- a/rust/bt-csis/src/client.rs
+++ b/rust/bt-csis/src/client.rs
@@ -195,9 +195,7 @@
     use super::*;
     use assert_matches::assert_matches;
     use bt_gatt::test_utils::*;
-    use bt_gatt::types::{
-        AttributePermissions, CharacteristicProperties, CharacteristicProperty, Handle,
-    };
+    use bt_gatt::types::{AttributePermissions, CharacteristicProperty, Handle};
     use bt_gatt::Characteristic;
     use core::num::NonZeroU8;
     use futures::{
@@ -216,7 +214,7 @@
             Characteristic {
                 handle,
                 uuid,
-                properties: CharacteristicProperties(vec![CharacteristicProperty::Read]),
+                properties: CharacteristicProperty::Read.into(),
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
diff --git a/rust/bt-gatt/src/tests.rs b/rust/bt-gatt/src/tests.rs
index 8b95ec1..78412ed 100644
--- a/rust/bt-gatt/src/tests.rs
+++ b/rust/bt-gatt/src/tests.rs
@@ -26,10 +26,7 @@
         Characteristic {
             handle: Handle(1),
             uuid: TEST_UUID_1,
-            properties: CharacteristicProperties(vec![
-                CharacteristicProperty::Broadcast,
-                CharacteristicProperty::Notify,
-            ]),
+            properties: CharacteristicProperties::BROADCAST_NOTIFY,
             permissions: AttributePermissions::default(),
             descriptors: vec![],
         },
@@ -39,10 +36,7 @@
         Characteristic {
             handle: Handle(2),
             uuid: TEST_UUID_1,
-            properties: CharacteristicProperties(vec![
-                CharacteristicProperty::Broadcast,
-                CharacteristicProperty::Notify,
-            ]),
+            properties: CharacteristicProperties::BROADCAST_NOTIFY,
             permissions: AttributePermissions::default(),
             descriptors: vec![],
         },
@@ -52,10 +46,7 @@
         Characteristic {
             handle: Handle(3),
             uuid: TEST_UUID_1,
-            properties: CharacteristicProperties(vec![
-                CharacteristicProperty::Broadcast,
-                CharacteristicProperty::Notify,
-            ]),
+            properties: CharacteristicProperties::BROADCAST_NOTIFY,
             permissions: AttributePermissions::default(),
             descriptors: vec![],
         },
@@ -65,7 +56,7 @@
         Characteristic {
             handle: Handle(4),
             uuid: TEST_UUID_2,
-            properties: CharacteristicProperties(vec![CharacteristicProperty::Notify]),
+            properties: CharacteristicProperty::Notify.into(),
             permissions: AttributePermissions::default(),
             descriptors: vec![],
         },
@@ -75,7 +66,7 @@
         Characteristic {
             handle: Handle(5),
             uuid: TEST_UUID_3,
-            properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]),
+            properties: CharacteristicProperty::Broadcast.into(),
             permissions: AttributePermissions::default(),
             descriptors: vec![],
         },
@@ -131,10 +122,7 @@
         Characteristic {
             handle: Handle(1),
             uuid: TEST_UUID_1,
-            properties: CharacteristicProperties(vec![
-                CharacteristicProperty::Broadcast,
-                CharacteristicProperty::Notify,
-            ]),
+            properties: CharacteristicProperties::BROADCAST_NOTIFY,
             permissions: AttributePermissions::default(),
             descriptors: vec![],
         },
@@ -244,10 +232,7 @@
         Characteristic {
             handle: Handle(1),
             uuid: TEST_UUID_1,
-            properties: CharacteristicProperties(vec![
-                CharacteristicProperty::Broadcast,
-                CharacteristicProperty::Notify,
-            ]),
+            properties: CharacteristicProperties::BROADCAST_NOTIFY,
             permissions: AttributePermissions::default(),
             descriptors: vec![],
         },
@@ -368,9 +353,12 @@
     def.add_characteristic(Characteristic {
         handle: Handle(3),
         uuid: TEST_UUID_3,
-        properties: CharacteristicProperty::Read
-            | CharacteristicProperty::Write
-            | CharacteristicProperty::Notify,
+        properties: (&[
+            CharacteristicProperty::Read,
+            CharacteristicProperty::Write,
+            CharacteristicProperty::Notify,
+        ])
+            .into(),
         permissions: AttributePermissions {
             read: Some(SecurityLevels::default()),
             write: Some(SecurityLevels::default()),
@@ -383,7 +371,7 @@
     def.add_characteristic(Characteristic {
         handle: Handle(4),
         uuid: TEST_UUID_3,
-        properties: CharacteristicProperty::Read | CharacteristicProperty::Indicate,
+        properties: (&[CharacteristicProperty::Read, CharacteristicProperty::Indicate]).into(),
         permissions: AttributePermissions {
             read: Some(SecurityLevels::default()),
             write: None,
diff --git a/rust/bt-gatt/src/types.rs b/rust/bt-gatt/src/types.rs
index e8beec7..a4a9514 100644
--- a/rust/bt-gatt/src/types.rs
+++ b/rust/bt-gatt/src/types.rs
@@ -230,65 +230,94 @@
     WritableAuxiliaries = 0x200,
 }
 
-impl CharacteristicProperty {
+/// Set of [`CharacteristicProperty`] flags associated with a GATT
+/// Characteristic.
+#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct CharacteristicProperties(&'static [CharacteristicProperty]);
+
+impl CharacteristicProperties {
     /// Characteristic is readable and can send notifications.
-    pub const READ_NOTIFY: &'static [Self] = &[Self::Read, Self::Notify];
+    pub const READ_NOTIFY: Self =
+        Self(&[CharacteristicProperty::Read, CharacteristicProperty::Notify]);
+
+    /// Characteristic can be broadcast and send notifications.
+    pub const BROADCAST_NOTIFY: Self =
+        Self(&[CharacteristicProperty::Broadcast, CharacteristicProperty::Notify]);
 
     /// Control Point property set: writable (with and without response) and can
     /// send notifications.
-    pub const WRITE_NOTIFY: &'static [Self] =
-        &[Self::Write, Self::WriteWithoutResponse, Self::Notify];
+    pub const WRITE_NOTIFY: Self = Self(&[
+        CharacteristicProperty::Write,
+        CharacteristicProperty::WriteWithoutResponse,
+        CharacteristicProperty::Notify,
+    ]);
 
     /// Readable, writable (with and without response), and can send
     /// notifications.
-    pub const READ_WRITE_NOTIFY: &'static [Self] =
-        &[Self::Read, Self::Write, Self::WriteWithoutResponse, Self::Notify];
-}
+    pub const READ_WRITE_NOTIFY: Self = Self(&[
+        CharacteristicProperty::Read,
+        CharacteristicProperty::Write,
+        CharacteristicProperty::WriteWithoutResponse,
+        CharacteristicProperty::Notify,
+    ]);
 
-impl std::ops::BitOr for CharacteristicProperty {
-    type Output = CharacteristicProperties;
+    /// Characteristic can send notifications or indications.
+    pub const NOTIFY_INDICATE: Self =
+        Self(&[CharacteristicProperty::Notify, CharacteristicProperty::Indicate]);
 
-    fn bitor(self, rhs: Self) -> Self::Output {
-        CharacteristicProperties(vec![self, rhs])
+    pub fn contains(&self, property: CharacteristicProperty) -> bool {
+        self.0.contains(&property)
     }
-}
 
-#[derive(Default, Debug, Clone)]
-pub struct CharacteristicProperties(pub Vec<CharacteristicProperty>);
-
-impl From<CharacteristicProperty> for CharacteristicProperties {
-    fn from(value: CharacteristicProperty) -> Self {
-        Self(vec![value])
-    }
-}
-
-impl FromIterator<CharacteristicProperty> for CharacteristicProperties {
-    fn from_iter<T: IntoIterator<Item = CharacteristicProperty>>(iter: T) -> Self {
-        Self(iter.into_iter().collect())
-    }
-}
-
-impl std::ops::BitOr<CharacteristicProperty> for CharacteristicProperties {
-    type Output = Self;
-
-    fn bitor(mut self, rhs: CharacteristicProperty) -> Self::Output {
-        self.0.push(rhs);
-        self
-    }
-}
-
-impl CharacteristicProperties {
     pub fn is_disjoint(&self, other: &Self) -> bool {
         for property in self.0.iter() {
-            if other.0.contains(&property) {
+            if other.0.contains(property) {
                 return false;
             }
         }
         true
     }
+}
 
-    pub fn contains(&self, property: CharacteristicProperty) -> bool {
-        self.0.contains(&property)
+impl std::ops::Deref for CharacteristicProperties {
+    type Target = [CharacteristicProperty];
+
+    fn deref(&self) -> &Self::Target {
+        self.0
+    }
+}
+
+impl From<CharacteristicProperty> for CharacteristicProperties {
+    fn from(value: CharacteristicProperty) -> Self {
+        match value {
+            CharacteristicProperty::Broadcast => Self(&[CharacteristicProperty::Broadcast]),
+            CharacteristicProperty::Read => Self(&[CharacteristicProperty::Read]),
+            CharacteristicProperty::WriteWithoutResponse => {
+                Self(&[CharacteristicProperty::WriteWithoutResponse])
+            }
+            CharacteristicProperty::Write => Self(&[CharacteristicProperty::Write]),
+            CharacteristicProperty::Notify => Self(&[CharacteristicProperty::Notify]),
+            CharacteristicProperty::Indicate => Self(&[CharacteristicProperty::Indicate]),
+            CharacteristicProperty::AuthenticatedSignedWrites => {
+                Self(&[CharacteristicProperty::AuthenticatedSignedWrites])
+            }
+            CharacteristicProperty::ReliableWrite => Self(&[CharacteristicProperty::ReliableWrite]),
+            CharacteristicProperty::WritableAuxiliaries => {
+                Self(&[CharacteristicProperty::WritableAuxiliaries])
+            }
+        }
+    }
+}
+
+impl From<&'static [CharacteristicProperty]> for CharacteristicProperties {
+    fn from(value: &'static [CharacteristicProperty]) -> Self {
+        Self(value)
+    }
+}
+
+impl<const N: usize> From<&'static [CharacteristicProperty; N]> for CharacteristicProperties {
+    fn from(value: &'static [CharacteristicProperty; N]) -> Self {
+        Self(value)
     }
 }
 
@@ -329,11 +358,11 @@
 
 impl AttributePermissions {
     pub fn with_levels(properties: &CharacteristicProperties, levels: &SecurityLevels) -> Self {
-        let notify_properties = CharacteristicProperty::Notify | CharacteristicProperty::Indicate;
         Self {
             read: properties.contains(CharacteristicProperty::Read).then_some(levels.clone()),
             write: properties.contains(CharacteristicProperty::Write).then_some(levels.clone()),
-            update: (!properties.is_disjoint(&notify_properties)).then_some(levels.clone()),
+            update: (!properties.is_disjoint(&CharacteristicProperties::NOTIFY_INDICATE))
+                .then_some(levels.clone()),
         }
     }
 }
@@ -405,7 +434,7 @@
     }
 
     pub fn supports_property(&self, property: &CharacteristicProperty) -> bool {
-        self.properties.0.contains(property)
+        self.properties.contains(*property)
     }
 
     pub fn descriptors(&self) -> impl Iterator<Item = &Descriptor> {
diff --git a/rust/bt-mcs/src/server.rs b/rust/bt-mcs/src/server.rs
index f4b43d0..5abb3fa 100644
--- a/rust/bt-mcs/src/server.rs
+++ b/rust/bt-mcs/src/server.rs
@@ -46,91 +46,91 @@
 /// Handle assigned to the Content Control ID (CCID) characteristic.
 const CONTENT_CONTROL_ID_HANDLE: Handle = Handle(13);
 
-/// Specification of a mandatory GATT characteristic for MCS.
-struct CharacteristicSpec {
-    handle: Handle,
-    uuid: Uuid,
-    properties: &'static [CharacteristicProperty],
+/// All 13 mandatory characteristics defined in MCS v1.0.1 Section 3.
+fn mandatory_characteristics() -> [Characteristic; 13] {
+    [
+        build_mandatory_characteristic(
+            MEDIA_PLAYER_NAME_HANDLE,
+            MEDIA_PLAYER_NAME_UUID,
+            CharacteristicProperties::READ_NOTIFY,
+        ),
+        build_mandatory_characteristic(
+            TRACK_CHANGED_HANDLE,
+            TRACK_CHANGED_UUID,
+            CharacteristicProperty::Notify,
+        ),
+        build_mandatory_characteristic(
+            TRACK_TITLE_HANDLE,
+            TRACK_TITLE_UUID,
+            CharacteristicProperties::READ_NOTIFY,
+        ),
+        build_mandatory_characteristic(
+            TRACK_DURATION_HANDLE,
+            TRACK_DURATION_UUID,
+            CharacteristicProperties::READ_NOTIFY,
+        ),
+        build_mandatory_characteristic(
+            TRACK_POSITION_HANDLE,
+            TRACK_POSITION_UUID,
+            CharacteristicProperties::READ_WRITE_NOTIFY,
+        ),
+        build_mandatory_characteristic(
+            PLAYBACK_SPEED_HANDLE,
+            PLAYBACK_SPEED_UUID,
+            CharacteristicProperties::READ_WRITE_NOTIFY,
+        ),
+        build_mandatory_characteristic(
+            SEEKING_SPEED_HANDLE,
+            SEEKING_SPEED_UUID,
+            CharacteristicProperties::READ_NOTIFY,
+        ),
+        build_mandatory_characteristic(
+            PLAYING_ORDER_HANDLE,
+            PLAYING_ORDER_UUID,
+            CharacteristicProperties::READ_WRITE_NOTIFY,
+        ),
+        build_mandatory_characteristic(
+            PLAYING_ORDERS_SUPPORTED_HANDLE,
+            PLAYING_ORDERS_SUPPORTED_UUID,
+            CharacteristicProperty::Read,
+        ),
+        build_mandatory_characteristic(
+            MEDIA_STATE_HANDLE,
+            MEDIA_STATE_UUID,
+            CharacteristicProperties::READ_NOTIFY,
+        ),
+        build_mandatory_characteristic(
+            MEDIA_CONTROL_POINT_HANDLE,
+            MEDIA_CONTROL_POINT_UUID,
+            CharacteristicProperties::WRITE_NOTIFY,
+        ),
+        build_mandatory_characteristic(
+            MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_HANDLE,
+            MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_UUID,
+            CharacteristicProperties::READ_NOTIFY,
+        ),
+        build_mandatory_characteristic(
+            CONTENT_CONTROL_ID_HANDLE,
+            CONTENT_CONTROL_ID_UUID,
+            CharacteristicProperty::Read,
+        ),
+    ]
 }
 
-/// All the mandatory characteristics defined in MCS v1.0.1 Section 3.
-const MANDATORY_CHARACTERISTICS: [CharacteristicSpec; 13] = [
-    CharacteristicSpec {
-        handle: MEDIA_PLAYER_NAME_HANDLE,
-        uuid: MEDIA_PLAYER_NAME_UUID,
-        properties: CharacteristicProperty::READ_NOTIFY,
-    },
-    CharacteristicSpec {
-        handle: TRACK_CHANGED_HANDLE,
-        uuid: TRACK_CHANGED_UUID,
-        properties: &[CharacteristicProperty::Notify],
-    },
-    CharacteristicSpec {
-        handle: TRACK_TITLE_HANDLE,
-        uuid: TRACK_TITLE_UUID,
-        properties: CharacteristicProperty::READ_NOTIFY,
-    },
-    CharacteristicSpec {
-        handle: TRACK_DURATION_HANDLE,
-        uuid: TRACK_DURATION_UUID,
-        properties: CharacteristicProperty::READ_NOTIFY,
-    },
-    CharacteristicSpec {
-        handle: TRACK_POSITION_HANDLE,
-        uuid: TRACK_POSITION_UUID,
-        properties: CharacteristicProperty::READ_WRITE_NOTIFY,
-    },
-    CharacteristicSpec {
-        handle: PLAYBACK_SPEED_HANDLE,
-        uuid: PLAYBACK_SPEED_UUID,
-        properties: CharacteristicProperty::READ_WRITE_NOTIFY,
-    },
-    CharacteristicSpec {
-        handle: SEEKING_SPEED_HANDLE,
-        uuid: SEEKING_SPEED_UUID,
-        properties: CharacteristicProperty::READ_NOTIFY,
-    },
-    CharacteristicSpec {
-        handle: PLAYING_ORDER_HANDLE,
-        uuid: PLAYING_ORDER_UUID,
-        properties: CharacteristicProperty::READ_WRITE_NOTIFY,
-    },
-    CharacteristicSpec {
-        handle: PLAYING_ORDERS_SUPPORTED_HANDLE,
-        uuid: PLAYING_ORDERS_SUPPORTED_UUID,
-        properties: &[CharacteristicProperty::Read],
-    },
-    CharacteristicSpec {
-        handle: MEDIA_STATE_HANDLE,
-        uuid: MEDIA_STATE_UUID,
-        properties: CharacteristicProperty::READ_NOTIFY,
-    },
-    CharacteristicSpec {
-        handle: MEDIA_CONTROL_POINT_HANDLE,
-        uuid: MEDIA_CONTROL_POINT_UUID,
-        properties: CharacteristicProperty::WRITE_NOTIFY,
-    },
-    CharacteristicSpec {
-        handle: MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_HANDLE,
-        uuid: MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_UUID,
-        properties: CharacteristicProperty::READ_NOTIFY,
-    },
-    CharacteristicSpec {
-        handle: CONTENT_CONTROL_ID_HANDLE,
-        uuid: CONTENT_CONTROL_ID_UUID,
-        properties: &[CharacteristicProperty::Read],
-    },
-];
-
+/// Specification of a mandatory GATT characteristic for MCS.
 /// Constructs a characteristic definition with the specified handle, UUID,
 /// properties, and encryption-required permissions conforming to MCS v1.0.1
 /// Section 3.
-fn build_mandatory_characteristic(spec: &CharacteristicSpec) -> Characteristic {
-    let properties: CharacteristicProperties = spec.properties.iter().copied().collect();
+fn build_mandatory_characteristic(
+    handle: Handle,
+    uuid: Uuid,
+    properties: impl Into<CharacteristicProperties>,
+) -> Characteristic {
+    let properties = properties.into();
     Characteristic {
-        handle: spec.handle,
-        uuid: spec.uuid,
-        properties: properties.clone(),
+        handle,
+        uuid,
+        properties,
         permissions: AttributePermissions::with_levels(
             &properties,
             &SecurityLevels::encryption_required(),
@@ -179,8 +179,8 @@
             ServiceKind::Primary,
         );
 
-        for spec in &MANDATORY_CHARACTERISTICS {
-            service_def.add_characteristic(build_mandatory_characteristic(spec))?;
+        for chrc in mandatory_characteristics() {
+            service_def.add_characteristic(chrc)?;
         }
 
         Ok(service_def)
@@ -222,31 +222,26 @@
             builder.build_service_definition().expect("service definition builds successfully");
         let characteristics: Vec<&Characteristic> = service_def.characteristics().collect();
 
-        assert_eq!(characteristics.len(), MANDATORY_CHARACTERISTICS.len());
+        let expected_mandatory = mandatory_characteristics();
+        assert_eq!(characteristics.len(), expected_mandatory.len());
 
-        for (i, spec) in MANDATORY_CHARACTERISTICS.iter().enumerate() {
+        for (i, expected) in expected_mandatory.iter().enumerate() {
             let chrc = characteristics[i];
-            assert_eq!(chrc.handle, spec.handle);
-            assert_eq!(chrc.uuid, spec.uuid);
-            assert_eq!(chrc.properties.0.as_slice(), spec.properties);
+            assert_eq!(chrc.handle, expected.handle);
+            assert_eq!(chrc.uuid, expected.uuid);
+            assert_eq!(chrc.properties, expected.properties);
 
-            let expected_properties: CharacteristicProperties =
-                spec.properties.iter().copied().collect();
-            let expected_permissions = AttributePermissions::with_levels(
-                &expected_properties,
-                &SecurityLevels::encryption_required(),
-            );
             assert_eq!(
                 chrc.permissions.read.map(|s| s.encryption),
-                expected_permissions.read.map(|s| s.encryption)
+                expected.permissions.read.map(|s| s.encryption)
             );
             assert_eq!(
                 chrc.permissions.write.map(|s| s.encryption),
-                expected_permissions.write.map(|s| s.encryption)
+                expected.permissions.write.map(|s| s.encryption)
             );
             assert_eq!(
                 chrc.permissions.update.map(|s| s.encryption),
-                expected_permissions.update.map(|s| s.encryption)
+                expected.permissions.update.map(|s| s.encryption)
             );
         }
     }
diff --git a/rust/bt-pacs/src/lib.rs b/rust/bt-pacs/src/lib.rs
index 7d65629..d27ad26 100644
--- a/rust/bt-pacs/src/lib.rs
+++ b/rust/bt-pacs/src/lib.rs
@@ -482,7 +482,7 @@
         Uuid,
     };
     use bt_gatt::{
-        types::{AttributePermissions, Handle},
+        types::{AttributePermissions, CharacteristicProperty, Handle},
         Characteristic,
     };
 
@@ -537,9 +537,7 @@
             Characteristic {
                 handle: Handle(1),
                 uuid: Uuid::from_u16(0x2BC9),
-                properties: bt_gatt::types::CharacteristicProperties(vec![
-                    bt_gatt::types::CharacteristicProperty::Read,
-                ]),
+                properties: CharacteristicProperty::Read.into(),
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -571,9 +569,7 @@
             Characteristic {
                 handle: Handle(1),
                 uuid: Uuid::from_u16(0x2BC9),
-                properties: bt_gatt::types::CharacteristicProperties(vec![
-                    bt_gatt::types::CharacteristicProperty::Read,
-                ]),
+                properties: CharacteristicProperty::Read.into(),
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -605,9 +601,7 @@
             Characteristic {
                 handle: Handle(1),
                 uuid: Uuid::from_u16(0x2BC9),
-                properties: bt_gatt::types::CharacteristicProperties(vec![
-                    bt_gatt::types::CharacteristicProperty::Read,
-                ]),
+                properties: CharacteristicProperty::Read.into(),
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -658,9 +652,7 @@
             Characteristic {
                 handle: Handle(1),
                 uuid: Uuid::from_u16(0x28CD),
-                properties: bt_gatt::types::CharacteristicProperties(vec![
-                    bt_gatt::types::CharacteristicProperty::Read,
-                ]),
+                properties: CharacteristicProperty::Read.into(),
                 permissions: AttributePermissions::default(),
                 descriptors: vec![],
             },
@@ -680,9 +672,7 @@
         let chr = Characteristic {
             handle: Handle(1),
             uuid: Uuid::from_u16(0x28CD),
-            properties: bt_gatt::types::CharacteristicProperties(vec![
-                bt_gatt::types::CharacteristicProperty::Read,
-            ]),
+            properties: CharacteristicProperty::Read.into(),
             permissions: AttributePermissions::default(),
             descriptors: vec![],
         };
@@ -705,9 +695,7 @@
         let chr = Characteristic {
             handle: Handle(1),
             uuid: Uuid::from_u16(0x28CE),
-            properties: bt_gatt::types::CharacteristicProperties(vec![
-                bt_gatt::types::CharacteristicProperty::Read,
-            ]),
+            properties: CharacteristicProperty::Read.into(),
             permissions: AttributePermissions::default(),
             descriptors: vec![],
         };
diff --git a/rust/bt-pacs/src/server/types.rs b/rust/bt-pacs/src/server/types.rs
index 70096af..1bc9afb 100644
--- a/rust/bt-pacs/src/server/types.rs
+++ b/rust/bt-pacs/src/server/types.rs
@@ -21,12 +21,12 @@
 impl From<&SupportedAudioContexts> for Characteristic {
     fn from(_value: &SupportedAudioContexts) -> Self {
         // TODO(b/309015071): implement optional properties.
-        let properties: CharacteristicProperties = CharacteristicProperty::Read.into();
+        let properties = CharacteristicProperty::Read.into();
 
         Characteristic {
             handle: SUPPORTED_AUDIO_CONTEXTS_HANDLE,
             uuid: <SupportedAudioContexts as FromCharacteristic>::UUID,
-            properties: properties.clone(),
+            properties,
             permissions: AttributePermissions::with_levels(
                 &properties,
                 &SecurityLevels::encryption_required(),
@@ -38,12 +38,12 @@
 
 impl From<&AvailableAudioContexts> for Characteristic {
     fn from(_value: &AvailableAudioContexts) -> Self {
-        let properties = CharacteristicProperty::Read | CharacteristicProperty::Notify;
+        let properties = CharacteristicProperties::READ_NOTIFY;
 
         Characteristic {
             handle: AVAILABLE_AUDIO_CONTEXTS_HANDLE,
             uuid: <AvailableAudioContexts as FromCharacteristic>::UUID,
-            properties: properties.clone(),
+            properties,
             permissions: AttributePermissions::with_levels(
                 &properties,
                 &SecurityLevels::encryption_required(),
@@ -56,12 +56,12 @@
 impl From<&SourcePac> for Characteristic {
     fn from(value: &SourcePac) -> Self {
         // TODO(b/309015071): implement optional properties.
-        let properties: CharacteristicProperties = CharacteristicProperty::Read.into();
+        let properties = CharacteristicProperty::Read.into();
 
         Characteristic {
             handle: value.handle,
             uuid: <SourcePac as FromCharacteristic>::UUID,
-            properties: properties.clone(),
+            properties,
             permissions: AttributePermissions::with_levels(
                 &properties,
                 &SecurityLevels::encryption_required(),
@@ -74,12 +74,12 @@
 impl From<&SinkPac> for Characteristic {
     fn from(value: &SinkPac) -> Self {
         // TODO(b/309015071): implement optional properties.
-        let properties: CharacteristicProperties = CharacteristicProperty::Read.into();
+        let properties = CharacteristicProperty::Read.into();
 
         Characteristic {
             handle: value.handle,
             uuid: <SinkPac as FromCharacteristic>::UUID,
-            properties: properties.clone(),
+            properties,
             permissions: AttributePermissions::with_levels(
                 &properties,
                 &SecurityLevels::encryption_required(),
@@ -92,12 +92,12 @@
 impl From<&SourceAudioLocations> for Characteristic {
     fn from(value: &SourceAudioLocations) -> Self {
         // TODO(b/309015071): implement optional properties.
-        let properties: CharacteristicProperties = CharacteristicProperty::Read.into();
+        let properties = CharacteristicProperty::Read.into();
 
         Characteristic {
             handle: value.handle,
             uuid: <SourceAudioLocations as FromCharacteristic>::UUID,
-            properties: properties.clone(),
+            properties,
             permissions: AttributePermissions::with_levels(
                 &properties,
                 &SecurityLevels::encryption_required(),
@@ -110,12 +110,12 @@
 impl From<&SinkAudioLocations> for Characteristic {
     fn from(value: &SinkAudioLocations) -> Self {
         // TODO(b/309015071): implement optional properties.
-        let properties: CharacteristicProperties = CharacteristicProperty::Read.into();
+        let properties = CharacteristicProperty::Read.into();
 
         Characteristic {
             handle: value.handle,
             uuid: <SinkAudioLocations as FromCharacteristic>::UUID,
-            properties: properties.clone(),
+            properties,
             permissions: AttributePermissions::with_levels(
                 &properties,
                 &SecurityLevels::encryption_required(),
diff --git a/rust/bt-vcs/src/lib.rs b/rust/bt-vcs/src/lib.rs
index 12532e2..7300b3c 100644
--- a/rust/bt-vcs/src/lib.rs
+++ b/rust/bt-vcs/src/lib.rs
@@ -438,7 +438,7 @@
         Characteristic {
             handle: STATE_HANDLE,
             uuid: VolumeState::UUID,
-            properties: CharacteristicProperty::Read | CharacteristicProperty::Notify,
+            properties: CharacteristicProperties::READ_NOTIFY,
             permissions: AttributePermissions {
                 read: Some(SecurityLevels::default()),
                 update: Some(SecurityLevels::default()),