[lowpan][lowpan-spinel-driver] Get Neighbor Table

Get neighbor table from ot-stack.

Test: fx test lowpan-spinel-driver-test
Bug: 44678
Change-Id: I9ef4dd88d91d733eef25afb69fe83be111ce318a
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/473358
Fuchsia-Auto-Submit: Jiaming (Charlie) Wang <jiamingw@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
Reviewed-by: Robert Quattlebaum <rquattle@google.com>
diff --git a/src/connectivity/lowpan/drivers/lowpan-spinel-driver/src/driver/api.rs b/src/connectivity/lowpan/drivers/lowpan-spinel-driver/src/driver/api.rs
index a8b6576..e72e786 100644
--- a/src/connectivity/lowpan/drivers/lowpan-spinel-driver/src/driver/api.rs
+++ b/src/connectivity/lowpan/drivers/lowpan-spinel-driver/src/driver/api.rs
@@ -964,8 +964,30 @@
     }
 
     async fn get_neighbor_table(&self) -> ZxResult<Vec<NeighborInfo>> {
-        // TODO: Implement.
-        return Ok(vec![]);
+        // Wait until we are ready.
+        self.wait_for_state(DriverState::is_initialized).await;
+
+        // Wait for our turn.
+        let _lock = self.wait_for_api_task_lock("get_neighbor_table").await?;
+
+        Ok(self
+            .get_property_simple::<NeighborTable, _>(PropThread::NeighborTable)
+            .await?
+            .into_iter()
+            .map(|item| NeighborInfo {
+                mac_address: Some(item.extended_addr.0.to_vec()),
+                short_address: Some(item.short_addr),
+                age: Some(fuchsia_zircon::Duration::from_seconds(item.age.into()).into_nanos()),
+                is_child: Some(item.is_child),
+                link_frame_count: Some(item.link_frame_cnt),
+                mgmt_frame_count: Some(item.mle_frame_cnt),
+                last_rssi_in: Some(item.last_rssi.into()),
+                avg_rssi_in: Some(item.avg_rssi),
+                lqi_in: Some(item.link_quality),
+                thread_mode: Some(item.mode),
+                ..NeighborInfo::EMPTY
+            })
+            .collect::<Vec<_>>())
     }
 
     async fn get_counters(&self) -> ZxResult<Counters> {
diff --git a/src/connectivity/lowpan/drivers/lowpan-spinel-driver/src/driver/tests.rs b/src/connectivity/lowpan/drivers/lowpan-spinel-driver/src/driver/tests.rs
index 5c08cc9..d862581 100644
--- a/src/connectivity/lowpan/drivers/lowpan-spinel-driver/src/driver/tests.rs
+++ b/src/connectivity/lowpan/drivers/lowpan-spinel-driver/src/driver/tests.rs
@@ -10,6 +10,7 @@
 
 use crate::spinel::mock::PROP_DEBUG_LOGGING_TEST;
 use fidl_fuchsia_lowpan::{Credential, Identity, ProvisioningParams, NET_TYPE_THREAD_1_X};
+use fidl_fuchsia_lowpan_test::NeighborInfo;
 use lowpan_driver_common::Driver as _;
 
 impl<DS, NI> SpinelDriver<DS, NI> {
@@ -87,6 +88,42 @@
             traceln!("app_task: thread_rloc16: {:?}", thread_rloc16);
             assert_eq!(thread_rloc16.map(|_| ()), Ok(()));
 
+            let thread_neighbor_table = driver.get_neighbor_table().await;
+            traceln!("app_task: thread_neighbor_table: {:?}", thread_neighbor_table);
+            let thread_neighbor_entry_vec = thread_neighbor_table.unwrap();
+            assert_eq!(
+                thread_neighbor_entry_vec[0],
+                NeighborInfo {
+                    mac_address: Some([0, 1, 2, 3, 4, 5, 6, 7].to_vec()),
+                    short_address: Some(0x123),
+                    age: Some(fuchsia_zircon::Duration::from_seconds(11).into_nanos()),
+                    is_child: Some(true),
+                    link_frame_count: Some(1),
+                    mgmt_frame_count: Some(1),
+                    last_rssi_in: Some(-20),
+                    avg_rssi_in: Some(-20),
+                    lqi_in: Some(3),
+                    thread_mode: Some(0x0b),
+                    ..NeighborInfo::EMPTY
+                }
+            );
+            assert_eq!(
+                thread_neighbor_entry_vec[1],
+                NeighborInfo {
+                    mac_address: Some([1, 2, 3, 4, 5, 6, 7, 8].to_vec()),
+                    short_address: Some(0x1234),
+                    age: Some(fuchsia_zircon::Duration::from_seconds(22).into_nanos()),
+                    is_child: Some(true),
+                    link_frame_count: Some(1),
+                    mgmt_frame_count: Some(1),
+                    last_rssi_in: Some(-30),
+                    avg_rssi_in: Some(-30),
+                    lqi_in: Some(4),
+                    thread_mode: Some(0x0b),
+                    ..NeighborInfo::EMPTY
+                }
+            );
+
             traceln!("app_task: Attempting a reset...");
             assert_eq!(driver.reset().await, Ok(()));
             traceln!("app_task: Did reset!");
diff --git a/src/connectivity/lowpan/drivers/lowpan-spinel-driver/src/spinel/mock/fake_device_client.rs b/src/connectivity/lowpan/drivers/lowpan-spinel-driver/src/spinel/mock/fake_device_client.rs
index 1eb51d3..925b5b5d 100644
--- a/src/connectivity/lowpan/drivers/lowpan-spinel-driver/src/spinel/mock/fake_device_client.rs
+++ b/src/connectivity/lowpan/drivers/lowpan-spinel-driver/src/spinel/mock/fake_device_client.rs
@@ -546,6 +546,40 @@
                 spinel_write!(&mut response, "CiiS", frame.header, Cmd::PropValueIs, prop, 0x0000,)
                     .unwrap();
             }
+            Prop::Thread(PropThread::NeighborTable) => {
+                spinel_write!(
+                    &mut response,
+                    "Ciidd",
+                    frame.header,
+                    Cmd::PropValueIs,
+                    prop,
+                    NeighborTableEntry {
+                        extended_addr: EUI64([0, 1, 2, 3, 4, 5, 6, 7]),
+                        short_addr: 0x123,
+                        age: 11,
+                        is_child: true,
+                        link_frame_cnt: 1,
+                        mle_frame_cnt: 1,
+                        last_rssi: -20,
+                        avg_rssi: -20,
+                        link_quality: 3,
+                        mode: 0x0b,
+                    },
+                    NeighborTableEntry {
+                        extended_addr: EUI64([1, 2, 3, 4, 5, 6, 7, 8]),
+                        short_addr: 0x1234,
+                        age: 22,
+                        is_child: true,
+                        link_frame_cnt: 1,
+                        mle_frame_cnt: 1,
+                        last_rssi: -30,
+                        avg_rssi: -30,
+                        link_quality: 4,
+                        mode: 0x0b,
+                    },
+                )
+                .unwrap();
+            }
             prop => {
                 let properties = self.properties.lock();
                 if let Some(value) = properties.get(&prop) {
diff --git a/src/connectivity/lowpan/drivers/lowpan-spinel-driver/src/spinel/types.rs b/src/connectivity/lowpan/drivers/lowpan-spinel-driver/src/spinel/types.rs
index 2234b0b..d55f55e 100644
--- a/src/connectivity/lowpan/drivers/lowpan-spinel-driver/src/spinel/types.rs
+++ b/src/connectivity/lowpan/drivers/lowpan-spinel-driver/src/spinel/types.rs
@@ -278,6 +278,23 @@
 
 pub type DenyList = HashSet<DenyListEntry>;
 
+#[spinel_packed("ESLCcCbLLc")]
+#[derive(Debug, Hash, Clone, Eq, PartialEq)]
+pub struct NeighborTableEntry {
+    pub extended_addr: EUI64,
+    pub short_addr: u16,
+    pub age: u32,
+    pub link_quality: u8,
+    pub avg_rssi: i8,
+    pub mode: u8,
+    pub is_child: bool,
+    pub link_frame_cnt: u32,
+    pub mle_frame_cnt: u32,
+    pub last_rssi: i8,
+}
+
+pub type NeighborTable = Vec<NeighborTableEntry>;
+
 #[cfg(test)]
 mod tests {
     use super::*;