diff --git a/backend/src/lib.rs b/backend/src/lib.rs index 89490be9..600811a5 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -149,11 +149,12 @@ impl Client { pub async fn join_req( &self, + receiver_id: Vec, pl: &mut JoinReqPayload, async_resp: Option>>, ) -> Result { pl.base.sender_id = self.config.sender_id.clone(); - pl.base.receiver_id = self.config.receiver_id.clone(); + pl.base.receiver_id = receiver_id; pl.base.message_type = MessageType::JoinReq; let mut ans: JoinAnsPayload = Default::default(); @@ -231,11 +232,12 @@ impl Client { pub async fn home_ns_req( &self, + receiver_id: Vec, pl: &mut HomeNSReqPayload, async_resp: Option>>, ) -> Result { pl.base.sender_id = self.config.sender_id.clone(); - pl.base.receiver_id = self.config.receiver_id.clone(); + pl.base.receiver_id = receiver_id; pl.base.message_type = MessageType::HomeNSReq; let mut ans: HomeNSAnsPayload = Default::default(); @@ -1176,8 +1178,7 @@ pub mod test { let server = MockServer::start(); let c = Client::new(ClientConfig { - sender_id: "010203".into(), - receiver_id: "0102030405060708".into(), + sender_id: vec![1, 2, 3], server: server.url("/"), async_timeout: Duration::from_secs(1), ..Default::default() @@ -1186,8 +1187,8 @@ pub mod test { let mut req = HomeNSReqPayload { base: BasePayload { - sender_id: "010203".into(), - receiver_id: "0102030405060708".into(), + sender_id: vec![1, 2, 3], + receiver_id: vec![1, 2, 3, 4, 5, 6, 7, 8], message_type: MessageType::HomeNSReq, transaction_id: 1234, ..Default::default() @@ -1198,8 +1199,8 @@ pub mod test { let ans = HomeNSAnsPayload { base: BasePayloadResult { base: BasePayload { - sender_id: "0102030405060708".into(), - receiver_id: "010203".into(), + sender_id: vec![1, 2, 3, 4, 5, 6, 7, 8], + receiver_id: vec![1, 2, 3], message_type: MessageType::HomeNSAns, transaction_id: 1234, ..Default::default() @@ -1222,14 +1223,19 @@ pub mod test { // OK let (tx, rx) = oneshot::channel(); tx.send(serde_json::to_vec(&ans).unwrap()).unwrap(); - let resp = c.home_ns_req(&mut req, Some(rx)).await.unwrap(); + let resp = c + .home_ns_req(vec![1, 2, 3, 4, 5, 6, 7, 8], &mut req, Some(rx)) + .await + .unwrap(); mock.assert(); mock.delete(); assert_eq!(resp, ans); // Timeout let (_tx, rx) = oneshot::channel(); - let resp = c.home_ns_req(&mut req, Some(rx)).await; + let resp = c + .home_ns_req(vec![1, 2, 3, 4, 5, 6, 7, 8], &mut req, Some(rx)) + .await; assert!(resp.is_err()); } @@ -1238,8 +1244,7 @@ pub mod test { let server = MockServer::start(); let c = Client::new(ClientConfig { - sender_id: "010203".into(), - receiver_id: "0102030405060708".into(), + sender_id: vec![1, 2, 3], server: server.url("/"), async_timeout: Duration::from_secs(1), ..Default::default() @@ -1248,8 +1253,8 @@ pub mod test { let mut req = HomeNSReqPayload { base: BasePayload { - sender_id: "010203".into(), - receiver_id: "0102030405060708".into(), + sender_id: vec![1, 2, 3], + receiver_id: vec![1, 2, 3, 4, 5, 6, 7, 8], message_type: MessageType::HomeNSReq, transaction_id: 1234, ..Default::default() @@ -1260,8 +1265,8 @@ pub mod test { let ans = HomeNSAnsPayload { base: BasePayloadResult { base: BasePayload { - sender_id: "0102030405060708".into(), - receiver_id: "010203".into(), + sender_id: vec![1, 2, 3, 4, 5, 6, 7, 8], + receiver_id: vec![1, 2, 3], message_type: MessageType::HomeNSAns, transaction_id: 1234, ..Default::default() @@ -1284,14 +1289,19 @@ pub mod test { // OK let (tx, rx) = oneshot::channel(); tx.send(serde_json::to_vec(&ans).unwrap()).unwrap(); - let resp = c.home_ns_req(&mut req, Some(rx)).await.unwrap(); + let resp = c + .home_ns_req(vec![1, 2, 3, 4, 5, 6, 7, 8], &mut req, Some(rx)) + .await + .unwrap(); mock.assert(); mock.delete(); assert_eq!(resp, ans); // Timeout let (_tx, rx) = oneshot::channel(); - let resp = c.home_ns_req(&mut req, Some(rx)).await; + let resp = c + .home_ns_req(vec![1, 2, 3, 4, 5, 6, 7, 8], &mut req, Some(rx)) + .await; assert!(resp.is_err()); } @@ -1300,8 +1310,7 @@ pub mod test { let server = MockServer::start(); let c = Client::new(ClientConfig { - sender_id: "010203".into(), - receiver_id: "0102030405060708".into(), + sender_id: vec![1, 2, 3], server: server.url("/"), ..Default::default() }) @@ -1309,8 +1318,8 @@ pub mod test { let mut req = HomeNSReqPayload { base: BasePayload { - sender_id: "010203".into(), - receiver_id: "0102030405060708".into(), + sender_id: vec![1, 2, 3], + receiver_id: vec![1, 2, 3, 4, 5, 6, 7, 8], message_type: MessageType::HomeNSReq, transaction_id: 1234, ..Default::default() @@ -1321,8 +1330,8 @@ pub mod test { let ans = HomeNSAnsPayload { base: BasePayloadResult { base: BasePayload { - sender_id: "0102030405060708".into(), - receiver_id: "010203".into(), + sender_id: vec![1, 2, 3, 4, 5, 6, 7, 8], + receiver_id: vec![1, 2, 3], message_type: MessageType::HomeNSAns, transaction_id: 1234, ..Default::default() @@ -1342,7 +1351,10 @@ pub mod test { .body(serde_json::to_string(&req).unwrap()); then.body(serde_json::to_vec(&ans).unwrap()).status(200); }); - let resp = c.home_ns_req(&mut req, None).await.unwrap(); + let resp = c + .home_ns_req(vec![1, 2, 3, 4, 5, 6, 7, 8], &mut req, None) + .await + .unwrap(); mock.assert(); mock.delete(); assert_eq!(resp, ans); @@ -1354,7 +1366,9 @@ pub mod test { .body(serde_json::to_string(&req).unwrap()); then.status(500); }); - let resp = c.home_ns_req(&mut req, None).await; + let resp = c + .home_ns_req(vec![1, 2, 3, 4, 5, 6, 7, 8], &mut req, None) + .await; mock.assert(); mock.delete(); assert!(resp.is_err()); diff --git a/chirpstack/src/api/backend/mod.rs b/chirpstack/src/api/backend/mod.rs index f1e2f580..2d950bd7 100644 --- a/chirpstack/src/api/backend/mod.rs +++ b/chirpstack/src/api/backend/mod.rs @@ -101,7 +101,7 @@ pub async fn _handle_request(bp: BasePayload, b: Vec) -> http::Response v, Err(_) => { warn!("Unknown SenderID"); diff --git a/chirpstack/src/backend/joinserver.rs b/chirpstack/src/backend/joinserver.rs index d51af917..93e972f5 100644 --- a/chirpstack/src/backend/joinserver.rs +++ b/chirpstack/src/backend/joinserver.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::sync::{Arc, RwLock}; use anyhow::Result; @@ -6,22 +5,24 @@ use tracing::info; use crate::config; use backend::{Client, ClientConfig}; -use lrwn::EUI64; +use lrwn::{EUI64Prefix, EUI64}; lazy_static! { - static ref CLIENTS: RwLock>> = RwLock::new(HashMap::new()); + static ref CLIENTS: RwLock)>> = RwLock::new(vec![]); } pub fn setup() -> Result<()> { info!("Setting up Join Server clients"); let conf = config::get(); + let mut clients_w = CLIENTS.write().unwrap(); + *clients_w = vec![]; + for js in &conf.join_server.servers { - info!(join_eui = %js.join_eui, "Configuring Join Server"); + info!(join_eui_prefix = %js.join_eui_prefix, "Configuring Join Server"); let c = Client::new(ClientConfig { sender_id: conf.network.net_id.to_vec(), - receiver_id: js.join_eui.to_vec(), server: js.server.clone(), ca_cert: js.ca_cert.clone(), tls_cert: js.tls_cert.clone(), @@ -30,32 +31,28 @@ pub fn setup() -> Result<()> { ..Default::default() })?; - set(&js.join_eui, c); + clients_w.push((js.join_eui_prefix, Arc::new(c))); } Ok(()) } -pub fn set(join_eui: &EUI64, c: Client) { - let mut clients_w = CLIENTS.write().unwrap(); - clients_w.insert(*join_eui, Arc::new(c)); -} - -pub fn get(join_eui: &EUI64) -> Result> { +pub fn get(join_eui: EUI64) -> Result> { let clients_r = CLIENTS.read().unwrap(); - Ok(clients_r - .get(join_eui) - .ok_or_else(|| { - anyhow!( - "Join Server client for join_eui {} does not exist", - join_eui - ) - })? - .clone()) + for client in clients_r.iter() { + if client.0.matches(join_eui) { + return Ok(client.1.clone()); + } + } + + Err(anyhow!( + "Join Server client for join_eui {} does not exist", + join_eui + )) } #[cfg(test)] pub fn reset() { let mut clients_w = CLIENTS.write().unwrap(); - *clients_w = HashMap::new(); + *clients_w = vec![]; } diff --git a/chirpstack/src/cmd/configfile.rs b/chirpstack/src/cmd/configfile.rs index 88e5fc4f..e2c0a664 100644 --- a/chirpstack/src/cmd/configfile.rs +++ b/chirpstack/src/cmd/configfile.rs @@ -600,11 +600,24 @@ pub fn run() { [join_server] # Per Join Server configuration (this can be repeated). + # + # ChirpStack will try to match the Join-Request JoinEUI against each + # join_eui_prefix in the same order as they appear in the configuration. + # + # If you configure a 'catch-all' Join Server, then this entry must appear + # as the last item in the list. + # # Example: # [[join_server.servers]] # - # # JoinEUI of the Join Server. - # join_eui="0102030405060708" + # # JoinEUI prefix that must be routed to the Join Server. + # # + # # Example '0102030405060700/56` means that the 56MSB of the + # # join_eui_prefix will be used to match against the JoinEUI. + # # Thus the following JoinEUI range will be forwarded to the + # # configured Join Server: + # # 0102030405060700 - 01020304050607ff + # join_eui_prefix="0102030405060708/64" # # # Server endpoint. # server="https://example.com:1234/join/endpoint" @@ -633,7 +646,7 @@ pub fn run() { {{#each join_server.servers}} [[join_server.servers]] - join_eui="{{ this.join_eui }}" + join_eui_prefix="{{ this.join_eui_prefix }}" server="{{ this.server }}" async_interface={{ this.async_interface }} async_interface_timeout="{{ this.async_interface_timeout }}" diff --git a/chirpstack/src/config.rs b/chirpstack/src/config.rs index 5151ea9c..9e695d37 100644 --- a/chirpstack/src/config.rs +++ b/chirpstack/src/config.rs @@ -7,7 +7,7 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use lrwn::region::CommonName; -use lrwn::{AES128Key, DevAddrPrefix, NetID, EUI64}; +use lrwn::{AES128Key, DevAddrPrefix, EUI64Prefix, NetID}; lazy_static! { static ref CONFIG: Mutex> = Mutex::new(Arc::new(Default::default())); @@ -416,7 +416,8 @@ pub struct JoinServer { #[derive(Serialize, Deserialize, Default, Clone)] #[serde(default)] pub struct JoinServerServer { - pub join_eui: EUI64, + #[serde(alias = "join_eui")] + pub join_eui_prefix: EUI64Prefix, pub server: String, #[serde(with = "humantime_serde")] pub async_timeout: Duration, diff --git a/chirpstack/src/test/otaa_js_test.rs b/chirpstack/src/test/otaa_js_test.rs index 3dbf1d11..f375a108 100644 --- a/chirpstack/src/test/otaa_js_test.rs +++ b/chirpstack/src/test/otaa_js_test.rs @@ -8,7 +8,7 @@ use crate::{ uplink, }; use chirpstack_api::{common, gw, integration as integration_pb, internal}; -use lrwn::{DevAddr, EUI64}; +use lrwn::{DevAddr, EUI64Prefix, EUI64}; struct Test { name: String, @@ -361,11 +361,11 @@ async fn run_test(t: &Test) { }); let mut conf: config::Configuration = (*config::get()).clone(); - conf.join_server.servers.push(config::JoinServerServer { - join_eui: EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), + conf.join_server.servers = vec![config::JoinServerServer { + join_eui_prefix: EUI64Prefix::new([1, 2, 3, 4, 5, 6, 7, 8], 64), server: server.url("/"), ..Default::default() - }); + }]; config::set(conf); region::setup().unwrap(); joinserver::setup().unwrap(); diff --git a/chirpstack/src/test/otaa_pr_test.rs b/chirpstack/src/test/otaa_pr_test.rs index 6c67b908..13827832 100644 --- a/chirpstack/src/test/otaa_pr_test.rs +++ b/chirpstack/src/test/otaa_pr_test.rs @@ -17,7 +17,7 @@ use crate::storage::{ }; use crate::{config, test, uplink}; use chirpstack_api::gw; -use lrwn::{AES128Key, NetID, EUI64}; +use lrwn::{AES128Key, EUI64Prefix, NetID, EUI64}; #[tokio::test] async fn test_fns() { @@ -32,18 +32,18 @@ async fn test_fns() { conf.network.net_id = NetID::from_str("010203").unwrap(); // Set Join Server. - conf.join_server.servers.push(config::JoinServerServer { - join_eui: EUI64::from_str("0102030405060708").unwrap(), + conf.join_server.servers = vec![config::JoinServerServer { + join_eui_prefix: EUI64Prefix::new([1, 2, 3, 4, 5, 6, 7, 8], 64), server: js_mock.url("/"), ..Default::default() - }); + }]; // Set roaming agreement. - conf.roaming.servers.push(config::RoamingServer { + conf.roaming.servers = vec![config::RoamingServer { net_id: NetID::from_str("030201").unwrap(), server: sns_mock.url("/"), ..Default::default() - }); + }]; config::set(conf); joinserver::setup().unwrap(); diff --git a/chirpstack/src/uplink/join.rs b/chirpstack/src/uplink/join.rs index 9eed721c..eee87de5 100644 --- a/chirpstack/src/uplink/join.rs +++ b/chirpstack/src/uplink/join.rs @@ -264,7 +264,7 @@ impl JoinRequest { if self.device_keys.is_none() { trace!(join_eui = %jr.join_eui, "Getting Join Server client"); - self.js_client = Some(joinserver::get(&jr.join_eui)?); + self.js_client = Some(joinserver::get(jr.join_eui)?); } Ok(()) @@ -537,6 +537,7 @@ impl JoinRequest { trace!("Getting join-accept from Join Server"); let js_client = self.js_client.as_ref().unwrap(); + let jr = self.join_request.as_ref().unwrap(); let region_network = config::get_region_network(&self.uplink_frame_set.region_config_id)?; let region_conf = region::get(&self.uplink_frame_set.region_config_id)?; @@ -584,7 +585,9 @@ impl JoinRequest { ), }; - let join_ans_pl = js_client.join_req(&mut join_req_pl, async_receiver).await?; + let join_ans_pl = js_client + .join_req(jr.join_eui.to_vec(), &mut join_req_pl, async_receiver) + .await?; if let Some(v) = &join_ans_pl.app_s_key { self.app_s_key = Some(common::KeyEnvelope { diff --git a/chirpstack/src/uplink/join_fns.rs b/chirpstack/src/uplink/join_fns.rs index d259ecd0..fe40145a 100644 --- a/chirpstack/src/uplink/join_fns.rs +++ b/chirpstack/src/uplink/join_fns.rs @@ -58,7 +58,7 @@ impl JoinRequest { trace!("Getting home netid"); trace!(join_eui = %self.join_request.join_eui, "Trying to get join-server client"); - let js_client = joinserver::get(&self.join_request.join_eui)?; + let js_client = joinserver::get(self.join_request.join_eui)?; let mut home_ns_req = backend::HomeNSReqPayload { dev_eui: self.join_request.dev_eui.to_vec(), @@ -83,7 +83,11 @@ impl JoinRequest { trace!("Requesting home netid"); let home_ns_ans = js_client - .home_ns_req(&mut home_ns_req, async_receiver) + .home_ns_req( + self.join_request.join_eui.to_vec(), + &mut home_ns_req, + async_receiver, + ) .await?; self.home_net_id = Some(NetID::from_slice(&home_ns_ans.h_net_id)?); diff --git a/chirpstack/src/uplink/join_sns.rs b/chirpstack/src/uplink/join_sns.rs index b95bbaba..4bc7c5d4 100644 --- a/chirpstack/src/uplink/join_sns.rs +++ b/chirpstack/src/uplink/join_sns.rs @@ -147,7 +147,7 @@ impl JoinRequest { if self.device_keys.is_none() { trace!(join_eui = %jr.join_eui, "Getting Join Server client"); - self.js_client = Some(joinserver::get(&jr.join_eui)?); + self.js_client = Some(joinserver::get(jr.join_eui)?); } Ok(()) @@ -226,6 +226,7 @@ impl JoinRequest { trace!("Getting join-accept from Join Server"); let js_client = self.js_client.as_ref().unwrap(); + let jr = self.join_request.as_ref().unwrap(); let region_network = config::get_region_network(&self.uplink_frame_set.region_config_id)?; let region_conf = region::get(&self.uplink_frame_set.region_config_id)?; @@ -262,7 +263,9 @@ impl JoinRequest { ..Default::default() }; - let join_ans_pl = js_client.join_req(&mut join_req_pl, None).await?; + let join_ans_pl = js_client + .join_req(jr.join_eui.to_vec(), &mut join_req_pl, None) + .await?; if let Some(v) = &join_ans_pl.app_s_key { self.app_s_key = Some(common::KeyEnvelope { diff --git a/lrwn/src/devaddr.rs b/lrwn/src/devaddr.rs index 1b0ada5e..fef1e79a 100644 --- a/lrwn/src/devaddr.rs +++ b/lrwn/src/devaddr.rs @@ -46,8 +46,12 @@ impl FromStr for DevAddrPrefix { type Err = Error; fn from_str(s: &str) -> Result { let s = s.to_string(); + let mut size: u32 = 32; let parts: Vec<&str> = s.split('/').collect(); - if parts.len() != 2 { + if parts.len() == 2 { + size = parts[1].parse().map_err(|_| Error::DevAddrPrefixFormat)?; + } + if parts.len() > 2 { return Err(Error::DevAddrPrefixFormat); } @@ -57,7 +61,6 @@ impl FromStr for DevAddrPrefix { let mut mask: [u8; 4] = [0; 4]; hex::decode_to_slice(parts[0], &mut mask)?; - let size: u32 = parts[1].parse().map_err(|_| Error::DevAddrPrefixFormat)?; Ok(DevAddrPrefix(mask, size)) } @@ -311,6 +314,10 @@ mod tests { let p = DevAddrPrefix::from_str("01000000/8").unwrap(); assert_eq!(DevAddrPrefix::new([1, 0, 0, 0], 8), p); assert_eq!("01000000/8", p.to_string()); + + let p = DevAddrPrefix::from_str("01020304").unwrap(); + assert_eq!(DevAddrPrefix::new([1, 2, 3, 4], 32), p); + assert_eq!("01020304/32", p.to_string()); } #[test] diff --git a/lrwn/src/error.rs b/lrwn/src/error.rs index 5d53cefa..eef81246 100644 --- a/lrwn/src/error.rs +++ b/lrwn/src/error.rs @@ -17,6 +17,12 @@ pub enum Error { #[error("DevAddrPrefix must be in the form 00000000/0")] DevAddrPrefixFormat, + #[error("EUI64Prefix must be in the form 0000000000000000/0")] + EUI64PrefixFormat, + #[error(transparent)] FromHexError(#[from] hex::FromHexError), + + #[error(transparent)] + Anyhow(#[from] anyhow::Error), } diff --git a/lrwn/src/eui64.rs b/lrwn/src/eui64.rs index 4aa5b5cf..97dde8ee 100644 --- a/lrwn/src/eui64.rs +++ b/lrwn/src/eui64.rs @@ -1,7 +1,7 @@ use std::fmt; use std::str::FromStr; -use anyhow::Result; +use anyhow::{Context, Result}; #[cfg(feature = "diesel")] use diesel::{backend::Backend, deserialize, serialize, sql_types::Binary}; #[cfg(feature = "serde")] @@ -151,12 +151,115 @@ impl diesel::sql_types::SqlType for EUI64 { type IsNull = diesel::sql_types::is_nullable::NotNull; } +#[derive(PartialEq, Eq, Copy, Clone, Default)] +pub struct EUI64Prefix([u8; 8], u64); + +impl EUI64Prefix { + pub fn new(prefix: [u8; 8], size: u64) -> Self { + EUI64Prefix(prefix, size) + } + + pub fn matches(&self, eui: EUI64) -> bool { + if self.size() == 0 { + return true; + } + + let eui = u64::from_be_bytes(eui.to_be_bytes()); + let prefix = u64::from_be_bytes(self.prefix()); + let shift = 64 - self.size(); + + (prefix >> shift) == (eui >> shift) + } + + fn prefix(&self) -> [u8; 8] { + self.0 + } + + fn size(&self) -> u64 { + self.1 + } +} + +impl fmt::Display for EUI64Prefix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}/{}", hex::encode(self.prefix()), self.size()) + } +} + +impl fmt::Debug for EUI64Prefix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}/{}", hex::encode(self.prefix()), self.size()) + } +} + +impl FromStr for EUI64Prefix { + type Err = Error; + fn from_str(s: &str) -> Result { + let s = s.to_string(); + let mut size: u64 = 64; + let parts: Vec<&str> = s.split("/").collect(); + if parts.len() == 2 { + size = parts[1].parse().map_err(|_| Error::EUI64PrefixFormat)?; + } + if parts.len() > 2 { + return Err(Error::EUI64PrefixFormat); + } + if parts[0].len() != 16 { + return Err(Error::EUI64PrefixFormat); + } + + let mut mask: [u8; 8] = [0; 8]; + hex::decode_to_slice(parts[0], &mut mask).context("Decode EUI64Prefix")?; + + Ok(EUI64Prefix(mask, size)) + } +} + +#[cfg(feature = "serde")] +impl Serialize for EUI64Prefix { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for EUI64Prefix { + fn deserialize(deserialize: D) -> Result + where + D: Deserializer<'de>, + { + deserialize.deserialize_str(EUI64PrefixVisitor) + } +} + +#[cfg(feature = "serde")] +struct EUI64PrefixVisitor; + +#[cfg(feature = "serde")] +impl<'de> Visitor<'de> for EUI64PrefixVisitor { + type Value = EUI64Prefix; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("A EUI64Prefix in the format 0000000000000000/0 is expected") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + EUI64Prefix::from_str(value).map_err(|e| E::custom(format!("{}", e))) + } +} + #[cfg(test)] mod tests { use super::*; #[test] - fn test_to_le_bytes() { + fn test_eui64_to_le_bytes() { let eui = EUI64::from_be_bytes([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); assert_eq!( @@ -166,7 +269,7 @@ mod tests { } #[test] - fn test_from_le_bytes() { + fn test_eui64_from_le_bytes() { let eui64_from_le = EUI64::from_le_bytes([0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]); let eui64_from_be = EUI64::from_be_bytes([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); @@ -174,14 +277,61 @@ mod tests { } #[test] - fn test_to_string() { + fn test_eui64_to_string() { let eui = EUI64::from_be_bytes([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); assert_eq!(eui.to_string(), "0102030405060708"); } #[test] - fn test_from_str() { + fn test_eui64_from_str() { let eui = EUI64::from_be_bytes([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); assert_eq!(eui, EUI64::from_str(&"0102030405060708").unwrap()); } + + #[test] + fn test_eui64_prefix() { + let p = EUI64Prefix::from_str("0102030405060708").unwrap(); + assert_eq!(EUI64Prefix::new([1, 2, 3, 4, 5, 6, 7, 8], 64), p); + assert_eq!("0102030405060708/64", p.to_string()); + + let p = EUI64Prefix::from_str("0100000000000000/8").unwrap(); + assert_eq!(EUI64Prefix::new([1, 0, 0, 0, 0, 0, 0, 0], 8), p); + assert_eq!("0100000000000000/8", p.to_string()); + } + + #[test] + fn test_eui64_prefix_is_eui64() { + struct Test { + prefix: EUI64Prefix, + eui: EUI64, + matches: bool, + } + + let tests = vec![ + Test { + prefix: EUI64Prefix::from_str("0000000000000000/0").unwrap(), + eui: EUI64::from_str("0000000000000000").unwrap(), + matches: true, + }, + Test { + prefix: EUI64Prefix::from_str("0000000000000000/0").unwrap(), + eui: EUI64::from_str("ffffffffffffffff").unwrap(), + matches: true, + }, + Test { + eui: EUI64::from_str("ffffffff00000000").unwrap(), + prefix: EUI64Prefix::from_str("ff00000000000000/8").unwrap(), + matches: true, + }, + Test { + eui: EUI64::from_str("ffffffff00000000").unwrap(), + prefix: EUI64Prefix::from_str("ff00000000000000/9").unwrap(), + matches: false, + }, + ]; + + for tst in &tests { + assert_eq!(tst.matches, tst.prefix.matches(tst.eui)); + } + } }