rust/bt-battery: Initial commit of the battery client

- Define a `BatteryMonitor` which can be used to scan for compatible LE
peers that support the BT Battery Service (BAS).
- Provide a mechanism for connecting to a compatible peer.
- Read and save the current battery level once a GATT connection is made.
- Add test utilities for setting up a mock peer battery service.

Test: cargo test

Change-Id: I643af01012c0487e11b5fa09afd49b96616f045c
Reviewed-on: https://bluetooth-review.git.corp.google.com/c/bluetooth/+/1660
Reviewed-by: Dayeong Lee <dayeonglee@google.com>
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 8b01f6f..8aaf960 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -13,6 +13,7 @@
 ## Local path dependencies (keep sorted)
 bt-bap = { path = "bt-bap" }
 bt-bass = { path = "bt-bass" }
+bt-battery = { path = "bt-battery" }
 bt-broadcast-assistant = { path = "bt-broadcast-assistant" }
 bt-common = { path = "bt-common" }
 bt-gatt = { path = "bt-gatt" }
diff --git a/rust/bt-battery/.gitignore b/rust/bt-battery/.gitignore
new file mode 100644
index 0000000..438e2b1
--- /dev/null
+++ b/rust/bt-battery/.gitignore
@@ -0,0 +1,17 @@
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+*.pdb
+
+# Vim swap files.
+*.swp
diff --git a/rust/bt-battery/Cargo.toml b/rust/bt-battery/Cargo.toml
new file mode 100644
index 0000000..b91ea77
--- /dev/null
+++ b/rust/bt-battery/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "bt-battery"
+description = "Client and server library for the Battery Service"
+version = "0.1.0"
+edition.workspace = true
+license.workspace = true
+
+[dependencies]
+bt-common.workspace = true
+bt-gatt = { workspace = true , features = ["test-utils"] }
+futures.workspace = true
+parking_lot.workspace = true
+thiserror.workspace = true
diff --git a/rust/bt-battery/src/error.rs b/rust/bt-battery/src/error.rs
new file mode 100644
index 0000000..0db99af
--- /dev/null
+++ b/rust/bt-battery/src/error.rs
@@ -0,0 +1,41 @@
+// Copyright 2024 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use bt_common::packet_encoding::Error as PacketError;
+use bt_gatt::types::{Error as GattLibraryError, GattError};
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+pub enum Error {
+    #[error("LE Scan for battery services has already started.")]
+    ScanAlreadyStarted,
+
+    #[error("GATT operation error: {0:?}")]
+    Gatt(#[from] GattError),
+
+    #[error("GATT library error: {0:?}")]
+    GattLibrary(#[from] GattLibraryError),
+
+    #[error("No compatible battery service found")]
+    ServiceNotFound,
+
+    #[error("Packet serialization/deserialization error: {0}")]
+    Packet(#[from] PacketError),
+
+    #[error("Malformed service on peer: {0}")]
+    Service(ServiceError),
+
+    #[error("Generic failure: {0}")]
+    Generic(String),
+}
+
+/// Errors found when interacting with the remote peer's battery service.
+#[derive(Debug, Error, PartialEq)]
+pub enum ServiceError {
+    #[error("Missing a required service characteristic")]
+    MissingCharacteristic,
+
+    #[error("Notification streams unexpectedly terminated")]
+    NotificationStreamClosed,
+}
diff --git a/rust/bt-battery/src/lib.rs b/rust/bt-battery/src/lib.rs
new file mode 100644
index 0000000..ac20b57
--- /dev/null
+++ b/rust/bt-battery/src/lib.rs
@@ -0,0 +1,13 @@
+// Copyright 2024 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// An error type for the battery service crate.
+mod error;
+pub use error::Error;
+
+/// Implements the Battery Service monitor (client) role.
+pub mod monitor;
+
+/// Common types used throughout the battery service crate.
+pub mod types;
diff --git a/rust/bt-battery/src/monitor.rs b/rust/bt-battery/src/monitor.rs
new file mode 100644
index 0000000..cbe5a64
--- /dev/null
+++ b/rust/bt-battery/src/monitor.rs
@@ -0,0 +1,192 @@
+// Copyright 2024 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Implements the Bluetooth Battery Service (BAS) client role.
+//!
+//! Use the toplevel `BatteryMonitor` to construct a new battery monitoring
+//! client. The client will scan for compatible peers that support the Battery
+//! Service. When a compatible peer is found, use `BatteryMonitor::connect` to
+//! establish a GATT connection to the peer's service. The battery level and
+//! other characteristics are available in the returned `BatteryMonitorClient`.
+//!
+//! For example:
+//!
+//! // Set up a GATT Central and construct the battery monitor.
+//! let central = ...;
+//! let monitor = BatteryMonitor::new(central);
+//!
+//! // Start scanning for compatible peers and find the next available one.
+//! let mut scan_results = monitor.start()?;
+//! let compatible_peer_id = scan_results.next().await?;
+//!
+//! // Connect to the peer's Battery Service.
+//! let connected_service = monitor.connect(compatible_peer_id).await?;
+//!
+//! // Process battery events (notifications/indications) from the peer.
+//! let battery_events = connected_service.take_event_stream()?;
+//! while let Some(battery_event) = battery_events.next().await? {
+//!     // Do something with `battery_event`
+//! }
+
+use bt_common::PeerId;
+use bt_gatt::central::{Filter, ScanFilter};
+use bt_gatt::client::PeerServiceHandle;
+use bt_gatt::{Central, Client, GattTypes};
+
+use crate::error::Error;
+use crate::types::BATTERY_SERVICE_UUID;
+
+mod client;
+pub use client::{BatteryMonitorClient, BatteryMonitorEventStream};
+
+/// Monitors the battery properties on a remote peer's Battery Service (BAS).
+///
+/// Use `BatteryMonitor::start` to scan for compatible peers that provide the
+/// battery service. Once a suitable peer has been found, use
+/// `BatteryMonitor::connect` to initiate an outbound connection to the peer's
+/// Battery GATT service. Use the returned `BatteryMonitorClient` to
+/// interact with the GATT service (e.g. read battery level, receive battery
+/// level updates, etc.).
+pub struct BatteryMonitor<T: GattTypes> {
+    central: T::Central,
+    scan_stream: Option<T::ScanResultStream>,
+}
+
+impl<T: GattTypes> BatteryMonitor<T> {
+    pub fn new(central: T::Central) -> Self {
+        let scan_stream = central.scan(&Self::scan_filter());
+        Self { central, scan_stream: Some(scan_stream) }
+    }
+
+    fn scan_filter() -> Vec<ScanFilter> {
+        vec![ScanFilter {
+            filters: vec![Filter::ServiceUuid(BATTERY_SERVICE_UUID), Filter::IsConnectable],
+        }]
+    }
+
+    /// Start scanning for compatible Battery peers.
+    /// Returns a stream of scan results on success, Error if the scan couldn't
+    /// complete for any reason.
+    /// Can only be called once, returns `Error::ScanAlreadyStarted` on
+    /// subsequent attempts.
+    pub fn start(&mut self) -> Result<T::ScanResultStream, Error>
+    where
+        <T as bt_gatt::GattTypes>::ScanResultStream: std::marker::Send,
+    {
+        let Some(scan_stream) = self.scan_stream.take() else {
+            return Err(Error::ScanAlreadyStarted);
+        };
+
+        Ok(scan_stream)
+    }
+
+    /// Attempts to connect to the remote peer's Battery service.
+    /// Returns a battery monitor which can be used to interact with the peer's
+    /// battery service on success, Error if the connection couldn't be made
+    /// or if the peer's Battery service is invalid.
+    pub async fn connect(&mut self, id: PeerId) -> Result<BatteryMonitorClient<T>, Error>
+    where
+        <T as bt_gatt::GattTypes>::NotificationStream: std::marker::Send,
+    {
+        let client = self.central.connect(id).await.map_err(Error::GattLibrary)?;
+        let peer_service_handles =
+            client.find_service(BATTERY_SERVICE_UUID).await.map_err(Error::GattLibrary)?;
+
+        for handle in peer_service_handles {
+            if handle.uuid() != BATTERY_SERVICE_UUID || !handle.is_primary() {
+                return Err(Error::ServiceNotFound);
+            }
+            let service = handle.connect().await.map_err(Error::GattLibrary)?;
+            let monitor = BatteryMonitorClient::<T>::create(client, service).await?;
+            // TODO(b/335246946): This short circuits after the first valid service is
+            // found. Expand this to read all of the battery services to provide
+            // an aggregated view of the peer.
+            return Ok(monitor);
+        }
+        Err(Error::ServiceNotFound)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use bt_common::Uuid;
+    use bt_gatt::test_utils::{FakeCentral, FakeClient, FakeTypes};
+    use futures::{pin_mut, FutureExt};
+    use std::task::Poll;
+
+    use crate::monitor::client::tests::fake_battery_service;
+
+    #[test]
+    fn battery_monitor_start_scan() {
+        let fake_central = FakeCentral::new();
+        let mut monitor = BatteryMonitor::<FakeTypes>::new(fake_central);
+        let _monitor_scan_stream = monitor.start().expect("can start scanning");
+
+        // The scan stream can only be acquired once.
+        assert!(monitor.start().is_err());
+    }
+
+    #[test]
+    fn battery_monitor_connect_success() {
+        // Instantiate a fake client with a battery service.
+        let id = PeerId(1);
+        let mut fake_central = FakeCentral::new();
+        let mut fake_client = FakeClient::new();
+        fake_central.add_client(id, fake_client.clone());
+        let fake_battery_service = fake_battery_service(50);
+        fake_client.add_service(BATTERY_SERVICE_UUID, true, fake_battery_service);
+
+        let mut monitor = BatteryMonitor::<FakeTypes>::new(fake_central);
+
+        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
+        let connect_fut = monitor.connect(id);
+        pin_mut!(connect_fut);
+        let polled = connect_fut.poll_unpin(&mut noop_cx);
+        let Poll::Ready(Ok(_monitor_service)) = polled else {
+            panic!("Expected connect success");
+        };
+    }
+
+    #[test]
+    fn connect_no_services_is_error() {
+        let id = PeerId(1);
+        let mut fake_central = FakeCentral::new();
+        let fake_client = FakeClient::new();
+        fake_central.add_client(id, fake_client.clone());
+        // No battery service.
+
+        let mut monitor = BatteryMonitor::<FakeTypes>::new(fake_central);
+
+        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
+        let connect_fut = monitor.connect(id);
+        pin_mut!(connect_fut);
+        let polled = connect_fut.poll_unpin(&mut noop_cx);
+        let Poll::Ready(Err(Error::ServiceNotFound)) = polled else {
+            panic!("Expected connect failure");
+        };
+    }
+
+    #[test]
+    fn connect_invalid_battery_service_is_error() {
+        let id = PeerId(1);
+        let mut fake_central = FakeCentral::new();
+        let mut fake_client = FakeClient::new();
+        fake_central.add_client(id, fake_client.clone());
+        let fake_battery_service = fake_battery_service(50);
+        let random_uuid = Uuid::from_u16(0x1234);
+        fake_client.add_service(random_uuid, true, fake_battery_service);
+
+        let mut monitor = BatteryMonitor::<FakeTypes>::new(fake_central);
+
+        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
+        let connect_fut = monitor.connect(id);
+        pin_mut!(connect_fut);
+        let polled = connect_fut.poll_unpin(&mut noop_cx);
+        let Poll::Ready(Err(Error::ServiceNotFound)) = polled else {
+            panic!("Expected connect failure");
+        };
+    }
+}
diff --git a/rust/bt-battery/src/monitor/client.rs b/rust/bt-battery/src/monitor/client.rs
new file mode 100644
index 0000000..70ce184
--- /dev/null
+++ b/rust/bt-battery/src/monitor/client.rs
@@ -0,0 +1,207 @@
+// Copyright 2024 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use bt_common::packet_encoding::Decodable;
+use bt_gatt::client::ServiceCharacteristic;
+use bt_gatt::types::Handle;
+use bt_gatt::GattTypes;
+
+use crate::error::{Error, ServiceError};
+use crate::types::{BatteryLevel, BATTERY_LEVEL_UUID, READ_CHARACTERISTIC_BUFFER_SIZE};
+
+// TODO(aniramakri): Implement this.
+pub struct BatteryMonitorEventStream {}
+
+/// Implements the Battery Service client role.
+pub struct BatteryMonitorClient<T: GattTypes> {
+    /// Represents the underlying GATT LE connection. Kept alive to maintain the
+    /// connection to the peer.
+    _client: T::Client,
+    /// GATT client interface for interacting with the peer's battery service.
+    gatt_client: T::PeerService,
+    /// GATT Handles associated with the peer's one or more Battery Level
+    /// characteristics. The first `Handle` in this list is expected to be
+    /// the "primary" one.
+    battery_level_handles: Vec<Handle>,
+    // TODO(b/335259516): Save Handles for additional characteristics that are discovered.
+    /// The current battery level reported by the peer's battery server.
+    battery_level: BatteryLevel,
+}
+
+impl<T: GattTypes> BatteryMonitorClient<T> {
+    pub(crate) async fn create(
+        _client: T::Client,
+        gatt_client: T::PeerService,
+    ) -> Result<Self, Error>
+    where
+        <T as bt_gatt::GattTypes>::NotificationStream: std::marker::Send,
+    {
+        // All battery services must contain at least one battery level characteristic.
+        let battery_level_characteristics =
+            ServiceCharacteristic::<T>::find(&gatt_client, BATTERY_LEVEL_UUID).await?;
+
+        if battery_level_characteristics.is_empty() {
+            return Err(Error::Service(ServiceError::MissingCharacteristic));
+        }
+        // It is valid to have multiple Battery Level Characteristics. If multiple
+        // exist, the primary (main) characteristic has a Description field of
+        // "main". For now, we assume the first such characteristic is the
+        // primary. See BAS 1.1 Section 3.1.2.1. TODO(b/335246946): Check for
+        // Characteristic Presentation Format descriptor if
+        // multiple characteristics are present. Use this to infer the "primary".
+        let battery_level_handles: Vec<Handle> =
+            battery_level_characteristics.iter().map(|c| *c.handle()).collect();
+        // Get the current battery level of the primary characteristic.
+        let (battery_level, _decoded_bytes) = {
+            let mut buf = vec![0; READ_CHARACTERISTIC_BUFFER_SIZE];
+            let read_bytes =
+                battery_level_characteristics.first().unwrap().read(&mut buf[..]).await?;
+            BatteryLevel::decode(&buf[0..read_bytes])?
+        };
+
+        // TODO(aniramakri): Subscribe to notifications on the battery level
+        // characteristic and save as a stream of events.
+
+        Ok(Self { _client, gatt_client, battery_level_handles, battery_level })
+    }
+
+    /// Returns a stream of battery events.
+    /// The returned Stream _must_ be polled in order to receive the relevant
+    /// notification and indications on the battery service.
+    /// This method should only be called once.
+    /// Returns Some<T> if the battery stream is available, None otherwise.
+    pub fn take_event_stream(&mut self) -> Option<BatteryMonitorEventStream> {
+        todo!("Implement battery event stream")
+    }
+
+    #[cfg(test)]
+    fn battery_level(&self) -> BatteryLevel {
+        self.battery_level
+    }
+}
+
+#[cfg(test)]
+pub(crate) mod tests {
+    use super::*;
+
+    use bt_common::packet_encoding::Error as PacketError;
+    use bt_common::Uuid;
+    use bt_gatt::test_utils::{FakeClient, FakePeerService, FakeTypes};
+    use bt_gatt::types::{
+        AttributePermissions, Characteristic, CharacteristicProperties, CharacteristicProperty,
+    };
+    use futures::{pin_mut, FutureExt};
+    use std::task::Poll;
+
+    pub(crate) const BATTERY_LEVEL_HANDLE: Handle = Handle(0x1);
+    pub(crate) fn fake_battery_service(battery_level: u8) -> FakePeerService {
+        let mut peer_service = FakePeerService::new();
+        peer_service.add_characteristic(
+            Characteristic {
+                handle: BATTERY_LEVEL_HANDLE,
+                uuid: BATTERY_LEVEL_UUID,
+                properties: CharacteristicProperties(vec![
+                    CharacteristicProperty::Read,
+                    CharacteristicProperty::Notify,
+                ]),
+                permissions: AttributePermissions::default(),
+                descriptors: vec![],
+            },
+            vec![battery_level],
+        );
+        peer_service
+    }
+
+    /// Builds a `BatteryMonitorClient` that is connected to a fake GATT service
+    /// with a Battery Service.
+    fn setup_client(battery_level: u8) -> (BatteryMonitorClient<FakeTypes>, FakePeerService) {
+        // Constructs a FakePeerService with a battery level characteristic.
+        let fake_peer_service = fake_battery_service(battery_level);
+        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
+        let create_result =
+            BatteryMonitorClient::<FakeTypes>::create(FakeClient::new(), fake_peer_service.clone());
+        pin_mut!(create_result);
+        let polled = create_result.poll_unpin(&mut noop_cx);
+        let Poll::Ready(Ok(client)) = polled else {
+            panic!("Expected BatteryMonitorClient to be successfully created");
+        };
+
+        (client, fake_peer_service)
+    }
+
+    #[test]
+    fn create_client_and_read_battery_level_success() {
+        let battery_level = 20;
+        let (monitor, _fake_peer_service) = setup_client(battery_level);
+        assert_eq!(monitor.battery_level(), BatteryLevel(battery_level));
+    }
+
+    #[test]
+    fn empty_battery_service_is_error() {
+        let fake_peer_service = FakePeerService::new();
+        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
+        let create_result =
+            BatteryMonitorClient::<FakeTypes>::create(FakeClient::new(), fake_peer_service);
+        pin_mut!(create_result);
+        let polled = create_result.poll_unpin(&mut noop_cx);
+        let Poll::Ready(Err(Error::Service(ServiceError::MissingCharacteristic))) = polled else {
+            panic!("Expected BatteryMonitorClient failure");
+        };
+    }
+
+    #[test]
+    fn service_missing_battery_level_characteristic_is_error() {
+        let mut fake_peer_service = FakePeerService::new();
+        // Battery Level characteristic is invalidly formatted.
+        fake_peer_service.add_characteristic(
+            Characteristic {
+                handle: BATTERY_LEVEL_HANDLE,
+                uuid: Uuid::from_u16(0x1234), // Random UUID, not Battery Level
+                properties: CharacteristicProperties(vec![
+                    CharacteristicProperty::Read,
+                    CharacteristicProperty::Notify,
+                ]),
+                permissions: AttributePermissions::default(),
+                descriptors: vec![],
+            },
+            vec![],
+        );
+
+        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
+        let create_result =
+            BatteryMonitorClient::<FakeTypes>::create(FakeClient::new(), fake_peer_service);
+        pin_mut!(create_result);
+        let polled = create_result.poll_unpin(&mut noop_cx);
+        let Poll::Ready(Err(Error::Service(ServiceError::MissingCharacteristic))) = polled else {
+            panic!("Expected BatteryMonitorClient failure");
+        };
+    }
+
+    #[test]
+    fn invalid_battery_level_value_is_error() {
+        // Battery Level characteristic has an empty battery level value.
+        let mut fake_peer_service = FakePeerService::new();
+        fake_peer_service.add_characteristic(
+            Characteristic {
+                handle: BATTERY_LEVEL_HANDLE,
+                uuid: BATTERY_LEVEL_UUID,
+                properties: CharacteristicProperties(vec![
+                    CharacteristicProperty::Read,
+                    CharacteristicProperty::Notify,
+                ]),
+                permissions: AttributePermissions::default(),
+                descriptors: vec![],
+            },
+            vec![],
+        );
+        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
+        let create_result =
+            BatteryMonitorClient::<FakeTypes>::create(FakeClient::new(), fake_peer_service);
+        pin_mut!(create_result);
+        let polled = create_result.poll_unpin(&mut noop_cx);
+        let Poll::Ready(Err(Error::Packet(PacketError::UnexpectedDataLength))) = polled else {
+            panic!("Expected BatteryMonitorClient to be successfully created");
+        };
+    }
+}
diff --git a/rust/bt-battery/src/types.rs b/rust/bt-battery/src/types.rs
new file mode 100644
index 0000000..b7980c5
--- /dev/null
+++ b/rust/bt-battery/src/types.rs
@@ -0,0 +1,68 @@
+// Copyright 2024 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use bt_common::packet_encoding::{Decodable, Error as PacketError};
+use bt_common::Uuid;
+
+/// The UUID of the GATT battery service.
+/// Defined in Assigned Numbers Section 3.4.2.
+pub const BATTERY_SERVICE_UUID: Uuid = Uuid::from_u16(0x180f);
+
+/// The UUID of the GATT Battery level characteristic.
+/// Defined in Assigned Numbers Section 3.8.1.
+pub const BATTERY_LEVEL_UUID: Uuid = Uuid::from_u16(0x2a19);
+
+pub(crate) const READ_CHARACTERISTIC_BUFFER_SIZE: usize = 255;
+
+#[derive(Clone, Copy, Debug, PartialEq, Default)]
+pub struct BatteryLevel(pub u8);
+
+impl Decodable for BatteryLevel {
+    type Error = PacketError;
+
+    fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+        if buf.len() < 1 {
+            return Err(PacketError::UnexpectedDataLength);
+        }
+
+        let level_percent = buf[0].clamp(0, 100);
+        Ok((BatteryLevel(level_percent), 1))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn decode_battery_level_success() {
+        let buf = [55];
+        let (parsed, parsed_size) = BatteryLevel::decode(&buf).expect("valid battery level");
+        assert_eq!(parsed, BatteryLevel(55));
+        assert_eq!(parsed_size, 1);
+    }
+
+    #[test]
+    fn decode_large_battery_level_clamped() {
+        let buf = [125]; // Too large, expected to be a percentage value.
+        let (parsed, parsed_size) = BatteryLevel::decode(&buf).expect("valid battery level");
+        assert_eq!(parsed, BatteryLevel(100));
+        assert_eq!(parsed_size, 1);
+    }
+
+    #[test]
+    fn decode_large_buf_success() {
+        let large_buf = [19, 0]; // Only expect a single u8 for the level.
+        let (parsed, parsed_size) = BatteryLevel::decode(&large_buf).expect("valid battery level");
+        assert_eq!(parsed, BatteryLevel(19)); // Only the first byte should be read.
+        assert_eq!(parsed_size, 1);
+    }
+
+    #[test]
+    fn decode_invalid_battery_level_buf_is_error() {
+        let buf = [];
+        let result = BatteryLevel::decode(&buf);
+        assert_eq!(result, Err(PacketError::UnexpectedDataLength));
+    }
+}