Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions crates/ziggurat-driver/src/zigbee_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,44 @@ pub enum NwkSecurityMode {
}

/// How the MAC next hop for an outgoing unicast is chosen.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SendMode {
/// The destination is its own next hop: transmit straight to it with no routing
/// lookup (and route discovery suppressed). Used for frames to a one-hop neighbor,
/// e.g. delivering the network key to a joining device.
Direct,
/// Resolve the next hop through the routing layer: the route table or an applicable
/// source route, discovering a route first if none is known.
Route,
/// Resolve the next hop through the routing layer, subject to a host [`RouteDirective`]:
/// the route table or an applicable source route, discovering a route first if none is
/// known.
Route(RouteDirective),
}

/// How the host wants an originated unicast routed.
///
/// Hints and forces are per-frame: they steer a single send and are never written into
/// the routing tables (so they never echo back through the route-change notification
/// stream).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RouteDirective {
/// The stack resolves the route entirely on its own (source route, route table, or
/// discovery). What all stack-originated traffic passes.
StackDecides,
/// Use the supplied route only when the stack has no route of its own; it stands in
/// for route discovery rather than triggering it. A known route still wins.
Hint(HostRoute),
/// Use the supplied route unconditionally, overriding even a known route and
/// suppressing discovery.
Force(HostRoute),
}

/// A route the host supplies alongside a send.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HostRoute {
/// Hand the frame to this next hop and let it route onward.
NextHop(Nwk),
/// The ordered relay path to embed as a source route; the MAC destination is the
/// last relay. An empty list means direct delivery to the destination.
SourceRoute(Vec<Nwk>),
}

/// Whether a unicast APS data frame requests an end-to-end acknowledgement. When it
Expand Down
15 changes: 11 additions & 4 deletions crates/ziggurat-driver/src/zigbee_stack/aps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ use ziggurat_zigbee::Instant as CoreInstant;
use ziggurat_zigbee::flat_map::Entry;

use super::{
ApsAck, ApsAckData, ApsAckResult, NwkSecurityMode, PendingApsAck, RequestId, SendMode,
TxOutcome, TxPolicy, TxPriority, ZigbeeNotification, ZigbeeStack, ZigbeeStackError,
ApsAck, ApsAckData, ApsAckResult, NwkSecurityMode, PendingApsAck, RequestId, RouteDirective,
SendMode, TxOutcome, TxPolicy, TxPriority, ZigbeeNotification, ZigbeeStack, ZigbeeStackError,
};
use crate::frame_token::TrafficClass;

Expand Down Expand Up @@ -173,7 +173,11 @@ impl<P: RadioPhy, R: Runtime> ZigbeeStack<P, R> {
.nwk_data_frame(nwk_frame.nwk_header.source, payload)
.with_discover_route(NwkRouteDiscovery::Enable);

self.background_send_nwk_frame(aps_ack_frame, NwkSecurityMode::NetworkKey, SendMode::Route);
self.background_send_nwk_frame(
aps_ack_frame,
NwkSecurityMode::NetworkKey,
SendMode::Route(RouteDirective::StackDecides),
);
}

/// Build the NWK frame carrying an APS data frame, plus the ack-correlation data when
Expand Down Expand Up @@ -327,6 +331,7 @@ impl<P: RadioPhy, R: Runtime> ZigbeeStack<P, R> {
aps_security: Option<Eui64>,
sleepy_destination: bool,
priority: TxPriority,
route: RouteDirective,
request_id: RequestId,
) -> Result<(), ZigbeeStackError> {
let (nwk_frame, ack_data) = self.prepare_aps_send(
Expand Down Expand Up @@ -369,6 +374,7 @@ impl<P: RadioPhy, R: Runtime> ZigbeeStack<P, R> {
request_id,
aps_ack: ack_data,
},
SendMode::Route(route),
);
Ok(())
}
Expand All @@ -382,6 +388,7 @@ impl<P: RadioPhy, R: Runtime> ZigbeeStack<P, R> {
nwk_frame: NwkFrame,
policy: TxPolicy,
outcome: TxOutcome,
mode: SendMode,
) {
if nwk_frame.nwk_header.destination.as_u16() >= BROADCAST_LOW_POWER_ROUTERS.as_u16() {
let request_id = match outcome {
Expand All @@ -402,7 +409,7 @@ impl<P: RadioPhy, R: Runtime> ZigbeeStack<P, R> {
self.originate_unicast(
nwk_frame,
NwkSecurityMode::NetworkKey,
SendMode::Route,
mode,
policy,
outcome,
);
Expand Down
6 changes: 3 additions & 3 deletions crates/ziggurat-driver/src/zigbee_stack/joining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ use ziggurat_zigbee::nwk::commands::{

use super::{
AddrConflictSource, DeviceLeaveReason, IndirectFrame, IndirectPayload, JoinKind, NwkDeviceType,
NwkSecurityMode, RadioPhy, SendMode, TxOutcome, TxPolicy, TxPriority, ZigbeeNotification,
ZigbeeStack, neighbors,
NwkSecurityMode, RadioPhy, RouteDirective, SendMode, TxOutcome, TxPolicy, TxPriority,
ZigbeeNotification, ZigbeeStack, neighbors,
};

impl<P: RadioPhy, R: Runtime> ZigbeeStack<P, R> {
Expand Down Expand Up @@ -628,7 +628,7 @@ impl<P: RadioPhy, R: Runtime> ZigbeeStack<P, R> {
if self.is_neighbor(destination) {
SendMode::Direct
} else {
SendMode::Route
SendMode::Route(RouteDirective::StackDecides)
},
);
}
Expand Down
69 changes: 48 additions & 21 deletions crates/ziggurat-driver/src/zigbee_stack/nwk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ use ziggurat_zigbee::nwk::frame::{

use super::routing::{Route, RouteUpdate, Status as RouteStatus};
use super::{
AddrConflictSource, BroadcastSchedule, IndirectFrame, IndirectPayload, JoinKind, MAX_DEPTH,
NwkSecurityMode, PROTOCOL_VERSION, PendingBroadcast, PendingFrame, PendingRoute,
PendingUnicastRetry, RequestId, SendKind, SendMode, SendRequest, SendResult, TxOutcome,
TxPolicy, TxPriority, ZigbeeNotification, ZigbeeStack, ZigbeeStackError,
AddrConflictSource, BroadcastSchedule, HostRoute, IndirectFrame, IndirectPayload, JoinKind,
MAX_DEPTH, NwkSecurityMode, PROTOCOL_VERSION, PendingBroadcast, PendingFrame, PendingRoute,
PendingUnicastRetry, RequestId, RouteDirective, SendKind, SendMode, SendRequest, SendResult,
TxOutcome, TxPolicy, TxPriority, ZigbeeNotification, ZigbeeStack, ZigbeeStackError,
};

/// The outcome of resolving a unicast's MAC next hop without blocking (see
Expand Down Expand Up @@ -603,7 +603,7 @@ impl<P: RadioPhy, R: Runtime> ZigbeeStack<P, R> {
let destination = nwk_frame.nwk_header.destination;
nwk_frame.nwk_header.sequence_number = self.next_nwk_sequence_number();

match self.resolve_next_hop(&mut nwk_frame, mode) {
match self.resolve_next_hop(&mut nwk_frame, &mode) {
NextHop::Resolved(next_hop) => {
self.enqueue_unicast(
nwk_frame,
Expand Down Expand Up @@ -633,11 +633,17 @@ impl<P: RadioPhy, R: Runtime> ZigbeeStack<P, R> {
/// Resolve the MAC next hop for a unicast without ever blocking. A source-routed
/// result rewrites `nwk_frame`'s header in place (spec 3.6.4.3.1). When no route is
/// known the frame's `discover_route` flag decides between discovery and discard.
fn resolve_next_hop(&self, nwk_frame: &mut NwkFrame, mode: SendMode) -> NextHop {
fn resolve_next_hop(&self, nwk_frame: &mut NwkFrame, mode: &SendMode) -> NextHop {
let destination = nwk_frame.nwk_header.destination;

if mode == SendMode::Direct {
return NextHop::Resolved(destination);
let directive = match mode {
SendMode::Direct => return NextHop::Resolved(destination),
SendMode::Route(directive) => directive,
};

// A forced host route overrides everything, including a known route.
if let RouteDirective::Force(route) = directive {
return self.apply_host_route(nwk_frame, route);
}

// End device children never route-discover; their parent delivers directly.
Expand All @@ -649,16 +655,7 @@ impl<P: RadioPhy, R: Runtime> ZigbeeStack<P, R> {
match self.outbound_route(destination) {
Some(Route::NextHop(next_hop)) => return NextHop::Resolved(next_hop),
Some(Route::SourceRouted(relays)) => {
// Spec 3.6.4.3.1: the MAC destination is the relay closest to us, listed
// last; the relay index starts one below the relay count.
let next_hop = *relays.last().unwrap();
nwk_frame.nwk_header.frame_control.source_route = true;
nwk_frame.nwk_header.frame_control.discover_route = NwkRouteDiscovery::Suppress;
nwk_frame.nwk_header.source_route = Some(NwkSourceRoute {
relay_index: relays.len() as u8 - 1,
relays,
});
return NextHop::Resolved(next_hop);
return self.apply_source_route(nwk_frame, relays);
}
None => {}
}
Expand All @@ -673,14 +670,41 @@ impl<P: RadioPhy, R: Runtime> ZigbeeStack<P, R> {
}
}

// No usable route. Spec 3.6.3.3: only initiate discovery if the frame allows it.
// No usable route. A host hint stands in for discovery rather than triggering it.
if let RouteDirective::Hint(route) = directive {
return self.apply_host_route(nwk_frame, route);
}

// Spec 3.6.3.3: only initiate discovery if the frame allows it.
if nwk_frame.nwk_header.frame_control.discover_route == NwkRouteDiscovery::Suppress {
NextHop::Discard
} else {
NextHop::NeedDiscovery
}
}

/// Apply a host-supplied route to a frame we originate, resolving its MAC next hop.
fn apply_host_route(&self, nwk_frame: &mut NwkFrame, route: &HostRoute) -> NextHop {
match route {
// The next hop routes onward itself, so `discover_route` is left as built:
// an intermediate hop still needs it to reach the final destination.
HostRoute::NextHop(next_hop) => NextHop::Resolved(*next_hop),
HostRoute::SourceRoute(relays) => self.apply_source_route(nwk_frame, relays.clone()),
}
}

/// Rewrite `nwk_frame` to carry a source route.
fn apply_source_route(&self, nwk_frame: &mut NwkFrame, relays: Vec<Nwk>) -> NextHop {
let next_hop = *relays.last().unwrap();
nwk_frame.nwk_header.frame_control.source_route = true;
nwk_frame.nwk_header.frame_control.discover_route = NwkRouteDiscovery::Suppress;
nwk_frame.nwk_header.source_route = Some(NwkSourceRoute {
relay_index: relays.len() as u8 - 1,
relays,
});
NextHop::Resolved(next_hop)
}

pub async fn send_nwk_frame(
&self,
nwk_frame: NwkFrame,
Expand Down Expand Up @@ -1085,7 +1109,10 @@ impl<P: RadioPhy, R: Runtime> ZigbeeStack<P, R> {
token,
} = queued;

match self.resolve_next_hop(&mut nwk_frame, SendMode::Route) {
match self.resolve_next_hop(
&mut nwk_frame,
&SendMode::Route(RouteDirective::StackDecides),
) {
NextHop::Resolved(next_hop) => {
self.enqueue_unicast(nwk_frame, next_hop, security, priority, outcome, token);
}
Expand Down Expand Up @@ -1928,7 +1955,7 @@ impl<P: RadioPhy, R: Runtime> ZigbeeStack<P, R> {
self.background_send_nwk_frame(
network_status_frame,
NwkSecurityMode::NetworkKey,
SendMode::Route,
SendMode::Route(RouteDirective::StackDecides),
);
}

Expand Down
5 changes: 3 additions & 2 deletions crates/ziggurat-driver/src/zigbee_stack/zdp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use ziggurat_zigbee::zdp::{
};

use super::{
ApsAck, MAX_DEPTH, NwkDeviceType, TxOutcome, TxPolicy, TxPriority, ZigbeeStack,
ZigbeeStackError, neighbors, routing,
ApsAck, MAX_DEPTH, NwkDeviceType, RouteDirective, SendMode, TxOutcome, TxPolicy, TxPriority,
ZigbeeStack, ZigbeeStackError, neighbors, routing,
};
use crate::frame_token::TrafficClass;

Expand Down Expand Up @@ -291,6 +291,7 @@ impl<P: RadioPhy, R: Runtime> ZigbeeStack<P, R> {
class: TrafficClass::Host,
},
TxOutcome::Discard,
SendMode::Route(RouteDirective::StackDecides),
);
Ok(())
}
Expand Down
36 changes: 34 additions & 2 deletions crates/ziggurat-protocol/src/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ use core::time::Duration;
use ziggurat_driver::runtime::Runtime;
use ziggurat_driver::zigbee_stack::aps_security::TclkFlavor;
use ziggurat_driver::zigbee_stack::{
ApsAck, ApsAckResult, DeviceLeaveReason, NetworkBeacon, NetworkConfig, NwkDeviceType,
RequestId as StackRequestId, SendResult, TclkSeed, TxPriority, ZigbeeNotification, ZigbeeStack,
ApsAck, ApsAckResult, DeviceLeaveReason, HostRoute, NetworkBeacon, NetworkConfig,
NwkDeviceType, RequestId as StackRequestId, RouteDirective, SendResult, TclkSeed, TxPriority,
ZigbeeNotification, ZigbeeStack,
};
use ziggurat_ieee_802154::types::{Eui64, Key, Nwk};
use ziggurat_phy::RadioPhy;
Expand Down Expand Up @@ -277,6 +278,8 @@ pub fn send_aps<P: RadioPhy, R: Runtime>(
ApsAck::None
};

let route = route_directive(payload.route, payload.next_hop, payload.relays)?;

stack
.send_aps(
payload.flags.delivery_mode,
Expand All @@ -292,11 +295,40 @@ pub fn send_aps<P: RadioPhy, R: Runtime>(
aps_security,
payload.flags.sleepy_destination,
TxPriority::from_host(payload.priority as i8),
route,
StackRequestId::from(request_id),
)
.map_err(|e| Error::new(Status::TransmitFailed, &e.to_string()))
}

/// Build the driver's [`RouteDirective`] from the wire route control.
fn route_directive(
control: RouteControl,
next_hop: Option<Nwk>,
relays: Option<SourceRouteRelays>,
) -> Result<RouteDirective, Error> {
let host_source_route = |relays: SourceRouteRelays| -> Result<HostRoute, Error> {
if relays.relays.is_empty() {
Err(Error::new(
Status::InvalidRequest,
"a source route must contain at least one relay",
))
} else {
Ok(HostRoute::SourceRoute(relays.relays))
}
};

Ok(match control {
RouteControl::StackDecides => RouteDirective::StackDecides,
RouteControl::HintNextHop => RouteDirective::Hint(HostRoute::NextHop(next_hop.unwrap())),
RouteControl::ForceNextHop => RouteDirective::Force(HostRoute::NextHop(next_hop.unwrap())),
RouteControl::HintSourceRoute => RouteDirective::Hint(host_source_route(relays.unwrap())?),
RouteControl::ForceSourceRoute => {
RouteDirective::Force(host_source_route(relays.unwrap())?)
}
})
}

/// Cancel an in-flight send by the `request_id` it was issued under. Best-effort: the
/// reply reports whether a still-cancellable (pre-delivery) send was found and removed.
pub fn cancel_request<P: RadioPhy, R: Runtime>(
Expand Down
35 changes: 35 additions & 0 deletions crates/ziggurat-protocol/src/wire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,32 @@ pub struct SendApsFlags {
pub reserved: u2,
}

/// How the host wants a unicast routed.
#[abstract_bits(bits = 8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
#[repr(u8)]
pub enum RouteControl {
/// Leave routing entirely to the stack. No route data follows.
StackDecides = 0,
/// Use `next_hop` only if the stack has no route of its own (stands in for discovery).
HintNextHop = 1,
/// Use `next_hop` unconditionally, overriding a known route and suppressing discovery.
ForceNextHop = 2,
/// Use `relays` as a source route only if the stack has no route of its own.
HintSourceRoute = 3,
/// Use `relays` as a source route unconditionally, overriding a known route.
ForceSourceRoute = 4,
}

/// The ordered relay path of a host-supplied source route (excluding the destination).
#[abstract_bits]
#[derive(Debug, Clone)]
pub struct SourceRouteRelays {
pub relay_count: u8,
#[abstract_bits(length_from = relay_count)]
pub relays: Vec<Nwk>,
}

#[abstract_bits]
#[derive(Debug, Clone)]
pub struct SendApsPayload {
Expand All @@ -373,6 +399,15 @@ pub struct SendApsPayload {
pub aps_seq: u8,
pub radius: u8,
pub priority: u8, // i8 two's complement
pub route: RouteControl,
#[abstract_bits(
presence_from = matches!(route, RouteControl::HintNextHop | RouteControl::ForceNextHop)
)]
pub next_hop: Option<Nwk>,
#[abstract_bits(
presence_from = matches!(route, RouteControl::HintSourceRoute | RouteControl::ForceSourceRoute)
)]
pub relays: Option<SourceRouteRelays>,
pub asdu_len: u16,
#[abstract_bits(length_from = asdu_len)]
pub asdu: Vec<u8>,
Expand Down