From 883e73e1bdf58e3c5bda4582fb92cf8659057ebe Mon Sep 17 00:00:00 2001 From: Rain Date: Sat, 18 Apr 2026 22:00:44 -0700 Subject: [PATCH 1/4] [spr] initial version Created using spr 1.3.6-beta.1 --- Cargo.lock | 19 +- Cargo.toml | 2 + dpd-api/Cargo.toml | 5 +- dpd-api/src/lib.rs | 2052 +++++------------ dpd-api/src/v1.rs | 413 ---- dpd-api/src/v2.rs | 41 - dpd-api/src/v7.rs | 104 - dpd-types/Cargo.toml | 7 +- dpd-types/src/arp.rs | 7 + dpd-types/src/counters.rs | 7 + dpd-types/src/fault.rs | 15 +- dpd-types/src/lib.rs | 12 +- dpd-types/src/link.rs | 150 +- dpd-types/src/loopback.rs | 7 + dpd-types/src/mcast.rs | 259 +-- dpd-types/src/misc.rs | 7 + dpd-types/src/nat.rs | 7 + dpd-types/src/oxstats.rs | 2 +- dpd-types/src/port.rs | 7 + dpd-types/src/port_map.rs | 174 +- dpd-types/src/route.rs | 116 +- dpd-types/src/serdes.rs | 7 + dpd-types/src/snapshot.rs | 7 + dpd-types/src/switch_identifiers.rs | 42 +- dpd-types/src/switch_port.rs | 89 +- dpd-types/src/table.rs | 7 + dpd-types/src/transceivers.rs | 122 +- dpd-types/src/views.rs | 184 -- dpd-types/versions/Cargo.toml | 16 + dpd-types/versions/src/asic_details/mod.rs | 11 + .../src/asic_details/switch_identifiers.rs | 61 + .../versions/src/attached_subnets/mod.rs | 12 + .../versions/src/attached_subnets/route.rs | 24 + .../src/consolidated_v4_routes/mod.rs | 13 + .../src/consolidated_v4_routes/route.rs | 75 + dpd-types/versions/src/impls/link.rs | 77 + dpd-types/versions/src/impls/mcast.rs | 138 ++ dpd-types/versions/src/impls/mod.rs | 15 + dpd-types/versions/src/impls/port_map.rs | 55 + dpd-types/versions/src/impls/route.rs | 130 ++ dpd-types/versions/src/impls/serdes.rs | 16 + dpd-types/versions/src/impls/table.rs | 26 + dpd-types/versions/src/impls/transceivers.rs | 31 + dpd-types/versions/src/initial/arp.rs | 60 + dpd-types/versions/src/initial/counters.rs | 70 + dpd-types/versions/src/initial/fault.rs | 26 + dpd-types/versions/src/initial/link.rs | 253 ++ dpd-types/versions/src/initial/loopback.rs | 20 + dpd-types/versions/src/initial/mcast.rs | 235 ++ dpd-types/versions/src/initial/misc.rs | 31 + dpd-types/versions/src/initial/mod.rs | 27 + dpd-types/versions/src/initial/nat.rs | 54 + dpd-types/versions/src/initial/port.rs | 108 + dpd-types/versions/src/initial/port_map.rs | 130 ++ dpd-types/versions/src/initial/route.rs | 159 ++ dpd-types/versions/src/initial/serdes.rs | 170 ++ .../src/initial/switch_identifiers.rs | 43 + dpd-types/versions/src/initial/switch_port.rs | 109 + dpd-types/versions/src/initial/table.rs | 94 + .../versions/src/initial/transceivers.rs | 108 + dpd-types/versions/src/latest.rs | 210 ++ dpd-types/versions/src/lib.rs | 55 + .../src/mcast_source_filter_any/mcast.rs | 167 ++ .../src/mcast_source_filter_any/mod.rs | 12 + .../src/mcast_strict_underlay/mcast.rs | 253 ++ .../versions/src/mcast_strict_underlay/mod.rs | 14 + .../versions/src/prbs_error_tracking/link.rs | 74 +- .../versions/src/prbs_error_tracking/mod.rs | 14 + .../versions/src/prbs_error_tracking/port.rs | 46 + dpd-types/versions/src/snapshot/mod.rs | 13 + dpd-types/versions/src/snapshot/snapshot.rs | 165 ++ .../versions/src/v4_over_v6_routes/mod.rs | 12 + .../versions/src/v4_over_v6_routes/route.rs | 62 + .../versions/src/wallclock_history/link.rs | 15 +- .../versions/src/wallclock_history/mod.rs | 12 + dpd/Cargo.toml | 1 + dpd/src/api_server.rs | 115 +- dpd/src/arp.rs | 8 +- dpd/src/counters.rs | 6 +- dpd/src/link.rs | 40 +- dpd/src/macaddrs.rs | 2 +- dpd/src/main.rs | 12 +- dpd/src/mcast/validate.rs | 4 +- dpd/src/port_settings.rs | 4 +- dpd/src/ports.rs | 4 +- dpd/src/route.rs | 8 +- dpd/src/snapshot.rs | 4 +- dpd/src/switch_port.rs | 4 +- dpd/src/table/arp_ipv4.rs | 5 +- dpd/src/table/attached_subnet_v4.rs | 5 +- dpd/src/table/attached_subnet_v6.rs | 5 +- dpd/src/table/mac.rs | 10 +- dpd/src/table/mcast/mcast_egress.rs | 9 +- dpd/src/table/mcast/mcast_nat.rs | 9 +- dpd/src/table/mcast/mcast_replication.rs | 5 +- dpd/src/table/mcast/mcast_route.rs | 9 +- dpd/src/table/mcast/mcast_src_filter.rs | 9 +- dpd/src/table/mod.rs | 6 +- dpd/src/table/nat.rs | 9 +- dpd/src/table/neighbor_ipv6.rs | 5 +- dpd/src/table/port_ip.rs | 9 +- dpd/src/table/route_ipv4.rs | 9 +- dpd/src/table/route_ipv6.rs | 9 +- dpd/src/table/uplink.rs | 9 +- 104 files changed, 4269 insertions(+), 3457 deletions(-) delete mode 100644 dpd-api/src/v1.rs delete mode 100644 dpd-api/src/v2.rs delete mode 100644 dpd-api/src/v7.rs create mode 100644 dpd-types/src/arp.rs create mode 100644 dpd-types/src/counters.rs create mode 100644 dpd-types/src/loopback.rs create mode 100644 dpd-types/src/misc.rs create mode 100644 dpd-types/src/nat.rs create mode 100644 dpd-types/src/port.rs create mode 100644 dpd-types/src/serdes.rs create mode 100644 dpd-types/src/snapshot.rs create mode 100644 dpd-types/src/table.rs delete mode 100644 dpd-types/src/views.rs create mode 100644 dpd-types/versions/Cargo.toml create mode 100644 dpd-types/versions/src/asic_details/mod.rs create mode 100644 dpd-types/versions/src/asic_details/switch_identifiers.rs create mode 100644 dpd-types/versions/src/attached_subnets/mod.rs create mode 100644 dpd-types/versions/src/attached_subnets/route.rs create mode 100644 dpd-types/versions/src/consolidated_v4_routes/mod.rs create mode 100644 dpd-types/versions/src/consolidated_v4_routes/route.rs create mode 100644 dpd-types/versions/src/impls/link.rs create mode 100644 dpd-types/versions/src/impls/mcast.rs create mode 100644 dpd-types/versions/src/impls/mod.rs create mode 100644 dpd-types/versions/src/impls/port_map.rs create mode 100644 dpd-types/versions/src/impls/route.rs create mode 100644 dpd-types/versions/src/impls/serdes.rs create mode 100644 dpd-types/versions/src/impls/table.rs create mode 100644 dpd-types/versions/src/impls/transceivers.rs create mode 100644 dpd-types/versions/src/initial/arp.rs create mode 100644 dpd-types/versions/src/initial/counters.rs create mode 100644 dpd-types/versions/src/initial/fault.rs create mode 100644 dpd-types/versions/src/initial/link.rs create mode 100644 dpd-types/versions/src/initial/loopback.rs create mode 100644 dpd-types/versions/src/initial/mcast.rs create mode 100644 dpd-types/versions/src/initial/misc.rs create mode 100644 dpd-types/versions/src/initial/mod.rs create mode 100644 dpd-types/versions/src/initial/nat.rs create mode 100644 dpd-types/versions/src/initial/port.rs create mode 100644 dpd-types/versions/src/initial/port_map.rs create mode 100644 dpd-types/versions/src/initial/route.rs create mode 100644 dpd-types/versions/src/initial/serdes.rs create mode 100644 dpd-types/versions/src/initial/switch_identifiers.rs create mode 100644 dpd-types/versions/src/initial/switch_port.rs create mode 100644 dpd-types/versions/src/initial/table.rs create mode 100644 dpd-types/versions/src/initial/transceivers.rs create mode 100644 dpd-types/versions/src/latest.rs create mode 100644 dpd-types/versions/src/lib.rs create mode 100644 dpd-types/versions/src/mcast_source_filter_any/mcast.rs create mode 100644 dpd-types/versions/src/mcast_source_filter_any/mod.rs create mode 100644 dpd-types/versions/src/mcast_strict_underlay/mcast.rs create mode 100644 dpd-types/versions/src/mcast_strict_underlay/mod.rs rename dpd-api/src/v12.rs => dpd-types/versions/src/prbs_error_tracking/link.rs (54%) create mode 100644 dpd-types/versions/src/prbs_error_tracking/mod.rs create mode 100644 dpd-types/versions/src/prbs_error_tracking/port.rs create mode 100644 dpd-types/versions/src/snapshot/mod.rs create mode 100644 dpd-types/versions/src/snapshot/snapshot.rs create mode 100644 dpd-types/versions/src/v4_over_v6_routes/mod.rs create mode 100644 dpd-types/versions/src/v4_over_v6_routes/route.rs rename dpd-api/src/v11.rs => dpd-types/versions/src/wallclock_history/link.rs (56%) create mode 100644 dpd-types/versions/src/wallclock_history/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 196f7b15..d6642f01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1597,6 +1597,7 @@ dependencies = [ "dpd-api", "dpd-client 0.1.0", "dpd-types", + "dpd-types-versions", "dropshot", "expectorate", "futures", @@ -1644,13 +1645,10 @@ version = "0.1.0" dependencies = [ "common 0.1.0", "dpd-types", + "dpd-types-versions", "dropshot", "dropshot-api-manager-types", - "oxnet", - "schemars 0.8.22", - "serde", "transceiver-controller", - "uuid", ] [[package]] @@ -1716,9 +1714,20 @@ dependencies = [ name = "dpd-types" version = "0.1.0" dependencies = [ - "aal", "chrono", + "dpd-types-versions", + "omicron-common", + "schemars 0.8.22", + "serde", +] + +[[package]] +name = "dpd-types-versions" +version = "0.1.0" +dependencies = [ + "aal", "common 0.1.0", + "dropshot", "omicron-common", "oxnet", "schemars 0.8.22", diff --git a/Cargo.toml b/Cargo.toml index efbf670a..51a04a23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "dpd-api", "dpd-client", "dpd-types", + "dpd-types/versions", "dropshot-apis", "packet", "pcap", @@ -35,6 +36,7 @@ asic = { path = "asic" } dpd-api = { path = "dpd-api" } dpd-client = { path = "dpd-client" } dpd-types = { path = "dpd-types" } +dpd-types-versions = { path = "dpd-types/versions" } common = { path = "common" } packet = { path = "packet" } pcap = { path = "pcap" } diff --git a/dpd-api/Cargo.toml b/dpd-api/Cargo.toml index be395f98..49b1bdd6 100644 --- a/dpd-api/Cargo.toml +++ b/dpd-api/Cargo.toml @@ -6,10 +6,7 @@ edition = "2024" [dependencies] common.workspace = true dpd-types.workspace = true +dpd-types-versions.workspace = true dropshot.workspace = true dropshot-api-manager-types.workspace = true -oxnet.workspace = true -schemars.workspace = true -serde.workspace = true transceiver-controller.workspace = true -uuid.workspace = true diff --git a/dpd-api/src/lib.rs b/dpd-api/src/lib.rs index c1ea5656..97cc7a2e 100644 --- a/dpd-api/src/lib.rs +++ b/dpd-api/src/lib.rs @@ -6,49 +6,23 @@ //! DPD endpoint definitions. -pub mod v1; -pub mod v11; -pub mod v12; -pub mod v2; -pub mod v7; - -use std::{ - collections::{BTreeMap, HashMap, HashSet}, - fmt, - net::{IpAddr, Ipv4Addr, Ipv6Addr}, - str::FromStr, -}; +use std::collections::BTreeMap; +use std::net::{Ipv4Addr, Ipv6Addr}; use common::{ attached_subnet::AttachedSubnetEntry, - counters::{FecRSCounters, PcsCounters, RMonCounters, RMonCountersAll}, nat::{Ipv4Nat, Ipv6Nat}, network::{InstanceTarget, MacAddr, NatTarget}, - ports::{ - Ipv4Entry, Ipv6Entry, PortFec, PortId, PortPrbsMode, PortSpeed, TxEq, - TxEqSwHw, - }, -}; -use dpd_types::{ - fault::Fault, - link::{LinkFsmCounters, LinkId, LinkUpCounter}, - mcast, oxstats, - port_map::BackplaneLink, - route::{Ipv4Route, Ipv6Route, Route}, - switch_identifiers::SwitchIdentifiers, - switch_port::{Led, ManagementMode}, - transceivers::Transceiver, - views, + ports::{Ipv4Entry, Ipv6Entry, PortId, PortPrbsMode, TxEq, TxEqSwHw}, }; +use dpd_types::oxstats; +use dpd_types_versions::{latest, v1, v4, v7}; use dropshot::{ EmptyScanParams, HttpError, HttpResponseCreated, HttpResponseDeleted, HttpResponseOk, HttpResponseUpdatedNoContent, PaginationParams, Path, Query, RequestContext, ResultsPage, TypedBody, }; use dropshot_api_manager_types::api_versions; -use oxnet::{IpNet, Ipv4Net, Ipv6Net}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use transceiver_controller::{ Datapath, Monitors, PowerState, message::LedState, }; @@ -107,8 +81,8 @@ pub trait DpdApi { }] async fn ndp_list( rqctx: RequestContext, - query: Query>, - ) -> Result>, HttpError>; + query: Query>, + ) -> Result>, HttpError>; /** * Remove all entries in the the IPv6 NDP tables. @@ -130,8 +104,8 @@ pub trait DpdApi { }] async fn ndp_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /** * Add an IPv6 NDP entry, mapping an IPv6 address to a MAC address. @@ -142,7 +116,7 @@ pub trait DpdApi { }] async fn ndp_create( rqctx: RequestContext, - update: TypedBody, + update: TypedBody, ) -> Result; /** @@ -154,7 +128,7 @@ pub trait DpdApi { }] async fn ndp_delete( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /** @@ -166,8 +140,8 @@ pub trait DpdApi { }] async fn arp_list( rqctx: RequestContext, - query: Query>, - ) -> Result>, HttpError>; + query: Query>, + ) -> Result>, HttpError>; /** * Remove all entries in the IPv4 ARP tables. @@ -189,8 +163,8 @@ pub trait DpdApi { }] async fn arp_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /** * Add an IPv4 ARP table entry, mapping an IPv4 address to a MAC address. @@ -201,7 +175,7 @@ pub trait DpdApi { }] async fn arp_create( rqctx: RequestContext, - update: TypedBody, + update: TypedBody, ) -> Result; /** @@ -213,7 +187,7 @@ pub trait DpdApi { }] async fn arp_delete( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /** @@ -226,8 +200,10 @@ pub trait DpdApi { }] async fn route_ipv6_list( rqctx: RequestContext, - query: Query>, - ) -> Result>, HttpError>; + query: Query< + PaginationParams, + >, + ) -> Result>, HttpError>; /** * Get a single IPv6 route, by its IPv6 CIDR block. @@ -238,8 +214,8 @@ pub trait DpdApi { }] async fn route_ipv6_get( rqctx: RequestContext, - path: Path, - ) -> Result>, HttpError>; + path: Path, + ) -> Result>, HttpError>; /** * Route an IPv6 subnet to a link and a nexthop gateway. @@ -253,7 +229,7 @@ pub trait DpdApi { }] async fn route_ipv6_add( rqctx: RequestContext, - update: TypedBody, + update: TypedBody, ) -> Result; /** @@ -268,7 +244,7 @@ pub trait DpdApi { }] async fn route_ipv6_set( rqctx: RequestContext, - update: TypedBody, + update: TypedBody, ) -> Result; /** @@ -280,7 +256,7 @@ pub trait DpdApi { }] async fn route_ipv6_delete( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /** @@ -292,7 +268,7 @@ pub trait DpdApi { }] async fn route_ipv6_delete_target( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /** @@ -302,37 +278,14 @@ pub trait DpdApi { #[endpoint { method = GET, path = "/route/ipv4", - versions = ..VERSION_V4_OVER_V6_ROUTES + versions = VERSION_V4_OVER_V6_ROUTES.. }] - async fn route_ipv4_list_v1( + async fn route_ipv4_list( rqctx: RequestContext, - query: Query>, - ) -> Result>, HttpError> { - let result = Self::route_ipv4_list(rqctx, query).await?.0; - - let mut v2_result = Vec::default(); - for x in result.items { - let mut v2_routes = - v1::Ipv4Routes { cidr: x.cidr, targets: Vec::default() }; - for t in x.targets { - if let Route::V4(r) = &t { - v2_routes.targets.push(Ipv4Route { - tag: r.tag.clone(), - port_id: r.port_id, - link_id: r.link_id, - tgt_ip: r.tgt_ip, - vlan_id: r.vlan_id, - }); - } - } - v2_result.push(v2_routes); - } - - Ok(HttpResponseOk(ResultsPage { - next_page: result.next_page, - items: v2_result, - })) - } + query: Query< + PaginationParams, + >, + ) -> Result>, HttpError>; /** * Fetch the configured IPv4 routes, mapping IPv4 CIDR blocks to the switch port @@ -341,12 +294,21 @@ pub trait DpdApi { #[endpoint { method = GET, path = "/route/ipv4", - versions = VERSION_V4_OVER_V6_ROUTES.. + versions = ..VERSION_V4_OVER_V6_ROUTES }] - async fn route_ipv4_list( + async fn route_ipv4_list_v1( rqctx: RequestContext, - query: Query>, - ) -> Result>, HttpError>; + query: Query< + PaginationParams, + >, + ) -> Result>, HttpError> + { + let page = Self::route_ipv4_list(rqctx, query).await?.0; + Ok(HttpResponseOk(ResultsPage { + next_page: page.next_page, + items: page.items.into_iter().map(Into::into).collect(), + })) + } /** * Get the configured route for the given IPv4 subnet. @@ -358,8 +320,8 @@ pub trait DpdApi { }] async fn route_ipv4_get( rqctx: RequestContext, - path: Path, - ) -> Result>, HttpError>; + path: Path, + ) -> Result>, HttpError>; #[endpoint { method = GET, @@ -368,48 +330,20 @@ pub trait DpdApi { }] async fn route_ipv4_get_v1( rqctx: RequestContext, - path: Path, - ) -> Result>, HttpError> { + path: Path, + ) -> Result>, HttpError> { let result = Self::route_ipv4_get(rqctx, path).await?.0; Ok(HttpResponseOk( result .into_iter() - .flat_map(|r| match r { - Route::V4(r) => Some(r), - _ => None, + .filter_map(|r| match r { + latest::route::Route::V4(r) => Some(r), + latest::route::Route::V6(_) => None, }) .collect(), )) } - /** - * Route an IPv4 subnet to a link and a nexthop gateway. - * - * This call can be used to create a new single-path route or to add new targets - * to a multipath route. - */ - #[endpoint { - method = POST, - path = "/route/ipv4", - versions = ..VERSION_CONSOLIDATED_V4_ROUTES, - operation_id = "route_ipv4_add", - }] - async fn route_ipv4_add_v1( - rqctx: RequestContext, - update: TypedBody, - ) -> Result { - let route = update.into_inner(); - Self::route_ipv4_add( - rqctx, - TypedBody::from(Ipv4RouteUpdate { - cidr: route.cidr, - target: RouteTarget::V4(route.target), - replace: route.replace, - }), - ) - .await - } - /** * Route an IPv4 subnet to a link and a nexthop gateway (IPv4 or IPv6). * @@ -423,7 +357,7 @@ pub trait DpdApi { }] async fn route_ipv4_add( rqctx: RequestContext, - update: TypedBody, + update: TypedBody, ) -> Result; /** @@ -436,49 +370,32 @@ pub trait DpdApi { method = POST, path = "/route/ipv4-over-ipv6", versions = VERSION_V4_OVER_V6_ROUTES..VERSION_CONSOLIDATED_V4_ROUTES, + operation_id = "route_ipv4_over_ipv6_add", }] - async fn route_ipv4_over_ipv6_add( + async fn route_ipv4_over_ipv6_add_v4( rqctx: RequestContext, - update: TypedBody, + update: TypedBody, ) -> Result { - let route = update.into_inner(); - Self::route_ipv4_add( - rqctx, - TypedBody::from(Ipv4RouteUpdate { - cidr: route.cidr, - target: RouteTarget::V6(route.target), - replace: route.replace, - }), - ) - .await + Self::route_ipv4_add(rqctx, update.map(Into::into)).await } /** * Route an IPv4 subnet to a link and a nexthop gateway. * - * This call can be used to create a new single-path route or to replace any - * existing routes with a new single-path route. + * This call can be used to create a new single-path route or to add new targets + * to a multipath route. */ #[endpoint { - method = PUT, + method = POST, path = "/route/ipv4", versions = ..VERSION_CONSOLIDATED_V4_ROUTES, - operation_id = "route_ipv4_set", + operation_id = "route_ipv4_add", }] - async fn route_ipv4_set_v1( + async fn route_ipv4_add_v1( rqctx: RequestContext, - update: TypedBody, + update: TypedBody, ) -> Result { - let route = update.into_inner(); - Self::route_ipv4_set( - rqctx, - TypedBody::from(Ipv4RouteUpdate { - cidr: route.cidr, - target: RouteTarget::V4(route.target), - replace: route.replace, - }), - ) - .await + Self::route_ipv4_add(rqctx, update.map(Into::into)).await } /** @@ -494,7 +411,7 @@ pub trait DpdApi { }] async fn route_ipv4_set( rqctx: RequestContext, - update: TypedBody, + update: TypedBody, ) -> Result; /** @@ -507,21 +424,32 @@ pub trait DpdApi { method = PUT, path = "/route/ipv4-over-ipv6", versions = VERSION_V4_OVER_V6_ROUTES..VERSION_CONSOLIDATED_V4_ROUTES, + operation_id = "route_ipv4_over_ipv6_set", }] - async fn route_ipv4_over_ipv6_set( + async fn route_ipv4_over_ipv6_set_v4( rqctx: RequestContext, - update: TypedBody, + update: TypedBody, ) -> Result { - let route = update.into_inner(); - Self::route_ipv4_set( - rqctx, - TypedBody::from(Ipv4RouteUpdate { - cidr: route.cidr, - target: RouteTarget::V6(route.target), - replace: route.replace, - }), - ) - .await + Self::route_ipv4_set(rqctx, update.map(Into::into)).await + } + + /** + * Route an IPv4 subnet to a link and a nexthop gateway. + * + * This call can be used to create a new single-path route or to replace any + * existing routes with a new single-path route. + */ + #[endpoint { + method = PUT, + path = "/route/ipv4", + versions = ..VERSION_CONSOLIDATED_V4_ROUTES, + operation_id = "route_ipv4_set", + }] + async fn route_ipv4_set_v1( + rqctx: RequestContext, + update: TypedBody, + ) -> Result { + Self::route_ipv4_set(rqctx, update.map(Into::into)).await } /** @@ -533,47 +461,37 @@ pub trait DpdApi { }] async fn route_ipv4_delete( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /** - * Remove a single target for the given IPv4 subnet + * Remove a single target for the given IPv4 subnet (IPv4 or IPv6 next hop) */ #[endpoint { method = DELETE, path = "/route/ipv4/{cidr}/{port_id}/{link_id}/{tgt_ip}", - versions = ..VERSION_CONSOLIDATED_V4_ROUTES, - operation_id = "route_ipv4_delete_target", + versions = VERSION_CONSOLIDATED_V4_ROUTES.. }] - async fn route_ipv4_delete_target_v1( + async fn route_ipv4_delete_target( rqctx: RequestContext, - path: Path, - ) -> Result { - let p = path.into_inner(); - Self::route_ipv4_delete_target( - rqctx, - Path::from(RouteTargetIpv4Path { - cidr: p.cidr, - port_id: p.port_id, - link_id: p.link_id, - tgt_ip: IpAddr::V4(p.tgt_ip), - }), - ) - .await - } + path: Path, + ) -> Result; /** - * Remove a single target for the given IPv4 subnet (IPv4 or IPv6 next hop) + * Remove a single target for the given IPv4 subnet */ #[endpoint { method = DELETE, path = "/route/ipv4/{cidr}/{port_id}/{link_id}/{tgt_ip}", - versions = VERSION_CONSOLIDATED_V4_ROUTES.. + versions = ..VERSION_CONSOLIDATED_V4_ROUTES, + operation_id = "route_ipv4_delete_target", }] - async fn route_ipv4_delete_target( + async fn route_ipv4_delete_target_v1( rqctx: RequestContext, - path: Path, - ) -> Result; + path: Path, + ) -> Result { + Self::route_ipv4_delete_target(rqctx, path.map(Into::into)).await + } /// List all switch ports on the system. #[endpoint { @@ -596,13 +514,19 @@ pub trait DpdApi { }] async fn channels_list( rqctx: RequestContext, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /// Get the set of available channels for all ports. /// /// This returns the unused MAC channels for each physical switch port. This can /// be used to determine how many additional links can be crated on a physical /// switch port. + // + // TODO: `FreeChannels` is unchanged across versions, so this split may be + // unnecessary — the rationale for gating the endpoint at + // `VERSION_MCAST_STRICT_UNDERLAY` is not obvious from the code alone. + // Revisit in a follow-up to either document the reason or collapse back to + // a single `channels_list` endpoint. #[endpoint { method = GET, path = "/channels", @@ -611,7 +535,7 @@ pub trait DpdApi { }] async fn channels_list_v1( rqctx: RequestContext, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> { Self::channels_list(rqctx).await } @@ -622,8 +546,8 @@ pub trait DpdApi { }] async fn port_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /// Return the current management mode of a QSFP switch port. #[endpoint { @@ -632,8 +556,8 @@ pub trait DpdApi { }] async fn management_mode_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /// Set the current management mode of a QSFP switch port. #[endpoint { @@ -642,8 +566,8 @@ pub trait DpdApi { }] async fn management_mode_set( rqctx: RequestContext, - path: Path, - body: TypedBody, + path: Path, + body: TypedBody, ) -> Result; /// Return the current state of the attention LED on a front-facing QSFP port. @@ -653,8 +577,8 @@ pub trait DpdApi { }] async fn led_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /// Override the current state of the attention LED on a front-facing QSFP port. /// @@ -672,7 +596,7 @@ pub trait DpdApi { }] async fn led_set( rqctx: RequestContext, - path: Path, + path: Path, body: TypedBody, ) -> Result; @@ -687,7 +611,10 @@ pub trait DpdApi { }] async fn backplane_map( rqctx: RequestContext, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Return the backplane mapping for a single switch port. #[endpoint { @@ -696,8 +623,8 @@ pub trait DpdApi { }] async fn port_backplane_link( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /// Return the state of all attention LEDs on the Sidecar QSFP ports. #[endpoint { @@ -706,7 +633,10 @@ pub trait DpdApi { }] async fn leds_list( rqctx: RequestContext, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Set the LED policy to automatic. /// @@ -718,7 +648,7 @@ pub trait DpdApi { }] async fn led_set_auto( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /// Return information about all QSFP transceivers. @@ -728,7 +658,10 @@ pub trait DpdApi { }] async fn transceivers_list( rqctx: RequestContext, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Return the information about a port's transceiver. /// @@ -744,8 +677,8 @@ pub trait DpdApi { }] async fn transceiver_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /// Effect a module-level reset of a QSFP transceiver. /// @@ -757,7 +690,7 @@ pub trait DpdApi { }] async fn transceiver_reset( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /// Control the power state of a transceiver. @@ -767,7 +700,7 @@ pub trait DpdApi { }] async fn transceiver_power_set( rqctx: RequestContext, - path: Path, + path: Path, state: TypedBody, ) -> Result; @@ -778,7 +711,7 @@ pub trait DpdApi { }] async fn transceiver_power_get( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result, HttpError>; /// Fetch the monitored environmental information for the provided transceiver. @@ -788,7 +721,7 @@ pub trait DpdApi { }] async fn transceiver_monitors_get( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result, HttpError>; /// Fetch the state of the datapath for the provided transceiver. @@ -798,7 +731,7 @@ pub trait DpdApi { }] async fn transceiver_datapath_get( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result, HttpError>; /// Create a link on a switch port. @@ -812,32 +745,34 @@ pub trait DpdApi { }] async fn link_create( rqctx: RequestContext, - path: Path, - params: TypedBody, - ) -> Result, HttpError>; + path: Path, + params: TypedBody, + ) -> Result, HttpError>; /// Get an existing link by ID. #[endpoint { method = GET, - versions = ..VERSION_PRBS_ERROR_TRACKING, + versions = VERSION_PRBS_ERROR_TRACKING.., path = "/ports/{port_id}/links/{link_id}" }] - async fn link_get_v12( + async fn link_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError> { - Self::link_get(rqctx, path).await.map(|resp| resp.map(Into::into)) - } + path: Path, + ) -> Result, HttpError>; + /// Get an existing link by ID. #[endpoint { method = GET, - versions = VERSION_PRBS_ERROR_TRACKING.., - path = "/ports/{port_id}/links/{link_id}" + versions = ..VERSION_PRBS_ERROR_TRACKING, + path = "/ports/{port_id}/links/{link_id}", + operation_id = "link_get", }] - async fn link_get( + async fn link_get_v1( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError> { + Self::link_get(rqctx, path).await.map(|resp| resp.map(Into::into)) + } /// Delete a link from a switch port. #[endpoint { @@ -846,63 +781,70 @@ pub trait DpdApi { }] async fn link_delete( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; + /// List the links within a single switch port. + #[endpoint { + method = GET, + versions = VERSION_PRBS_ERROR_TRACKING.., + path = "/ports/{port_id}/links", + }] + async fn link_list( + rqctx: RequestContext, + path: Path, + ) -> Result>, HttpError>; + /// List the links within a single switch port. #[endpoint { method = GET, versions = ..VERSION_PRBS_ERROR_TRACKING, path = "/ports/{port_id}/links", + operation_id = "link_list", }] - async fn link_list_v12( + async fn link_list_v1( rqctx: RequestContext, - path: Path, - ) -> Result>, HttpError> { + path: Path, + ) -> Result>, HttpError> { Self::link_list(rqctx, path).await.map(|resp| { resp.map(|list| { - list.into_iter().map(Into::into).collect::>() + list.into_iter() + .map(Into::into) + .collect::>() }) }) } - /// List the links within a single switch port. + /// List all links, on all switch ports. #[endpoint { method = GET, versions = VERSION_PRBS_ERROR_TRACKING.., - path = "/ports/{port_id}/links", + path = "/links", }] - async fn link_list( + async fn link_list_all( rqctx: RequestContext, - path: Path, - ) -> Result>, HttpError>; + query: Query, + ) -> Result>, HttpError>; /// List all links, on all switch ports. #[endpoint { method = GET, versions = ..VERSION_PRBS_ERROR_TRACKING, path = "/links", + operation_id = "link_list_all", }] - async fn link_list_all_v12( + async fn link_list_all_v1( rqctx: RequestContext, - query: Query, - ) -> Result>, HttpError> { + query: Query, + ) -> Result>, HttpError> { Self::link_list_all(rqctx, query).await.map(|resp| { resp.map(|list| { - list.into_iter().map(Into::into).collect::>() + list.into_iter() + .map(Into::into) + .collect::>() }) }) } - /// List all links, on all switch ports. - #[endpoint { - method = GET, - versions = VERSION_PRBS_ERROR_TRACKING.., - path = "/links", - }] - async fn link_list_all( - rqctx: RequestContext, - query: Query, - ) -> Result>, HttpError>; /// Return whether the link is enabled. #[endpoint { @@ -911,7 +853,7 @@ pub trait DpdApi { }] async fn link_enabled_get( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result, HttpError>; /// Enable or disable a link. @@ -921,7 +863,7 @@ pub trait DpdApi { }] async fn link_enabled_set( rqctx: RequestContext, - path: Path, + path: Path, body: TypedBody, ) -> Result; @@ -932,7 +874,7 @@ pub trait DpdApi { }] async fn link_ipv6_enabled_get( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result, HttpError>; /// Set whether a port is configured to act as an IPv6 endpoint @@ -942,7 +884,7 @@ pub trait DpdApi { }] async fn link_ipv6_enabled_set( rqctx: RequestContext, - path: Path, + path: Path, body: TypedBody, ) -> Result; @@ -960,7 +902,7 @@ pub trait DpdApi { }] async fn link_kr_get( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result, HttpError>; /// Enable or disable a link. @@ -970,7 +912,7 @@ pub trait DpdApi { }] async fn link_kr_set( rqctx: RequestContext, - path: Path, + path: Path, body: TypedBody, ) -> Result; @@ -982,7 +924,7 @@ pub trait DpdApi { }] async fn link_autoneg_get( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result, HttpError>; /// Set whether a port is configured to use autonegotation with its peer link. @@ -992,20 +934,33 @@ pub trait DpdApi { }] async fn link_autoneg_set( rqctx: RequestContext, - path: Path, + path: Path, body: TypedBody, ) -> Result; + /// Set a link's PRBS speed and mode. + #[endpoint { + method = PUT, + versions = VERSION_PRBS_ERROR_TRACKING.., + path = "/ports/{port_id}/links/{link_id}/prbs", + }] + async fn link_prbs_set( + rqctx: RequestContext, + path: Path, + body: TypedBody, + ) -> Result; + /// Set a link's PRBS speed and mode. #[endpoint { method = PUT, versions = ..VERSION_PRBS_ERROR_TRACKING, path = "/ports/{port_id}/links/{link_id}/prbs", + operation_id = "link_prbs_set", }] - async fn link_prbs_set_v12( + async fn link_prbs_set_v1( rqctx: RequestContext, - path: Path, - body: TypedBody, + path: Path, + body: TypedBody, ) -> Result { let mode = PortPrbsMode::try_from(body.into_inner()).map_err(|e| { HttpError::for_bad_request(None, format!("bad PRBS mode: {e}")) @@ -1014,18 +969,6 @@ pub trait DpdApi { Self::link_prbs_set(rqctx, path, TypedBody::from(mode)).await } - /// Set a link's PRBS speed and mode. - #[endpoint { - method = PUT, - versions = VERSION_PRBS_ERROR_TRACKING.., - path = "/ports/{port_id}/links/{link_id}/prbs", - }] - async fn link_prbs_set( - rqctx: RequestContext, - path: Path, - body: TypedBody, - ) -> Result; - /// Return the link's PRBS speed and mode. /// /// During link training, a pseudorandom bit sequence (PRBS) is used to allow @@ -1033,15 +976,13 @@ pub trait DpdApi { /// underlying circuitry (such as filter gains). #[endpoint { method = GET, - versions = ..VERSION_PRBS_ERROR_TRACKING, + versions = VERSION_PRBS_ERROR_TRACKING.., path = "/ports/{port_id}/links/{link_id}/prbs", }] - async fn link_prbs_get_v12( + async fn link_prbs_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError> { - Self::link_prbs_get(rqctx, path).await.map(|resp| resp.map(Into::into)) - } + path: Path, + ) -> Result, HttpError>; /// Return the link's PRBS speed and mode. /// @@ -1050,13 +991,16 @@ pub trait DpdApi { /// underlying circuitry (such as filter gains). #[endpoint { method = GET, - versions = VERSION_PRBS_ERROR_TRACKING.., + versions = ..VERSION_PRBS_ERROR_TRACKING, path = "/ports/{port_id}/links/{link_id}/prbs", + operation_id = "link_prbs_get", }] - async fn link_prbs_get( + async fn link_prbs_get_v1( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError> { + Self::link_prbs_get(rqctx, path).await.map(|resp| resp.map(Into::into)) + } /// Return whether a link is up. #[endpoint { @@ -1065,7 +1009,7 @@ pub trait DpdApi { }] async fn link_linkup_get( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result, HttpError>; /// Return any fault currently set on this link @@ -1075,8 +1019,8 @@ pub trait DpdApi { }] async fn link_fault_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /// Clear any fault currently set on this link #[endpoint { @@ -1085,7 +1029,7 @@ pub trait DpdApi { }] async fn link_fault_clear( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /// Inject a fault on this link @@ -1095,7 +1039,7 @@ pub trait DpdApi { }] async fn link_fault_inject( rqctx: RequestContext, - path: Path, + path: Path, entry: TypedBody, ) -> Result; @@ -1106,8 +1050,8 @@ pub trait DpdApi { }] async fn link_ipv4_list( rqctx: RequestContext, - path: Path, - query: Query>, + path: Path, + query: Query>, ) -> Result>, HttpError>; /// Add an IPv4 address to a link. @@ -1117,7 +1061,7 @@ pub trait DpdApi { }] async fn link_ipv4_create( rqctx: RequestContext, - path: Path, + path: Path, entry: TypedBody, ) -> Result; @@ -1128,7 +1072,7 @@ pub trait DpdApi { }] async fn link_ipv4_reset( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /// Remove an IPv4 address from a link. @@ -1138,7 +1082,7 @@ pub trait DpdApi { }] async fn link_ipv4_delete( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /// List the IPv6 addresses associated with a link. @@ -1148,8 +1092,8 @@ pub trait DpdApi { }] async fn link_ipv6_list( rqctx: RequestContext, - path: Path, - query: Query>, + path: Path, + query: Query>, ) -> Result>, HttpError>; /// Add an IPv6 address to a link. @@ -1159,7 +1103,7 @@ pub trait DpdApi { }] async fn link_ipv6_create( rqctx: RequestContext, - path: Path, + path: Path, entry: TypedBody, ) -> Result; @@ -1170,7 +1114,7 @@ pub trait DpdApi { }] async fn link_ipv6_reset( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /// Remove an IPv6 address from a link. @@ -1180,7 +1124,7 @@ pub trait DpdApi { }] async fn link_ipv6_delete( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /// Get a link's MAC address. @@ -1190,7 +1134,7 @@ pub trait DpdApi { }] async fn link_mac_get( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result, HttpError>; /// Set a link's MAC address. @@ -1204,7 +1148,7 @@ pub trait DpdApi { }] async fn link_mac_set( rqctx: RequestContext, - path: Path, + path: Path, body: TypedBody, ) -> Result; @@ -1212,11 +1156,12 @@ pub trait DpdApi { #[endpoint { method = GET, path = "/ports/{port_id}/links/{link_id}/nat_only", - versions = ..VERSION_UPLINK_PORTS + versions = ..VERSION_UPLINK_PORTS, + operation_id = "link_nat_only_get", }] - async fn link_nat_only_get( + async fn link_nat_only_get_v1( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result, HttpError> { Self::link_uplink_get(rqctx, path).await } @@ -1225,22 +1170,23 @@ pub trait DpdApi { #[endpoint { method = GET, path = "/ports/{port_id}/links/{link_id}/uplink", - versions = VERSION_UPLINK_PORTS.. + versions = VERSION_UPLINK_PORTS.., }] async fn link_uplink_get( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result, HttpError>; /// Set whether a port is configured to use drop non-nat traffic #[endpoint { method = PUT, path = "/ports/{port_id}/links/{link_id}/nat_only", - versions = ..VERSION_UPLINK_PORTS + versions = ..VERSION_UPLINK_PORTS, + operation_id = "link_nat_only_set", }] - async fn link_nat_only_set( + async fn link_nat_only_set_v1( rqctx: RequestContext, - path: Path, + path: Path, body: TypedBody, ) -> Result { Self::link_uplink_set(rqctx, path, body).await @@ -1250,11 +1196,11 @@ pub trait DpdApi { #[endpoint { method = PUT, path = "/ports/{port_id}/links/{link_id}/uplink", - versions = VERSION_UPLINK_PORTS.. + versions = VERSION_UPLINK_PORTS.., }] async fn link_uplink_set( rqctx: RequestContext, - path: Path, + path: Path, body: TypedBody, ) -> Result; @@ -1262,29 +1208,28 @@ pub trait DpdApi { #[endpoint { method = GET, path = "/ports/{port_id}/links/{link_id}/history", - versions = ..VERSION_WALLCLOCK_HISTORY + versions = VERSION_WALLCLOCK_HISTORY.., }] - async fn link_history_get_v11( + async fn link_history_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError> { - let history = Self::link_history_get(rqctx, path).await?.0; - Ok(HttpResponseOk(v11::LinkHistory { - timestamp: history.relative, - events: history.events, - })) - } + path: Path, + ) -> Result, HttpError>; /// Get the event history for the given link. #[endpoint { method = GET, path = "/ports/{port_id}/links/{link_id}/history", - versions = VERSION_WALLCLOCK_HISTORY.. + versions = ..VERSION_WALLCLOCK_HISTORY, + operation_id = "link_history_get", }] - async fn link_history_get( + async fn link_history_get_v1( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError> { + Self::link_history_get(rqctx, path) + .await + .map(|resp| resp.map(Into::into)) + } /** * Get loopback IPv4 addresses. @@ -1318,7 +1263,7 @@ pub trait DpdApi { }] async fn loopback_ipv4_delete( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /** @@ -1353,7 +1298,7 @@ pub trait DpdApi { }] async fn loopback_ipv6_delete( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /** @@ -1365,7 +1310,7 @@ pub trait DpdApi { }] async fn nat_ipv6_addresses_list( rqctx: RequestContext, - query: Query>, + query: Query>, ) -> Result>, HttpError>; /** @@ -1377,8 +1322,8 @@ pub trait DpdApi { }] async fn nat_ipv6_list( rqctx: RequestContext, - path: Path, - query: Query>, + path: Path, + query: Query>, ) -> Result>, HttpError>; /** @@ -1391,7 +1336,7 @@ pub trait DpdApi { }] async fn nat_ipv6_get( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result, HttpError>; /** @@ -1413,7 +1358,7 @@ pub trait DpdApi { }] async fn nat_ipv6_create( rqctx: RequestContext, - path: Path, + path: Path, target: TypedBody, ) -> Result; @@ -1426,7 +1371,7 @@ pub trait DpdApi { }] async fn nat_ipv6_delete( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /** @@ -1449,7 +1394,7 @@ pub trait DpdApi { }] async fn nat_ipv4_addresses_list( rqctx: RequestContext, - query: Query>, + query: Query>, ) -> Result>, HttpError>; /** @@ -1461,8 +1406,8 @@ pub trait DpdApi { }] async fn nat_ipv4_list( rqctx: RequestContext, - path: Path, - query: Query>, + path: Path, + query: Query>, ) -> Result>, HttpError>; /** @@ -1474,7 +1419,7 @@ pub trait DpdApi { }] async fn nat_ipv4_get( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result, HttpError>; /** @@ -1496,7 +1441,7 @@ pub trait DpdApi { }] async fn nat_ipv4_create( rqctx: RequestContext, - path: Path, + path: Path, target: TypedBody, ) -> Result; @@ -1509,7 +1454,7 @@ pub trait DpdApi { }] async fn nat_ipv4_delete( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /** @@ -1529,11 +1474,16 @@ pub trait DpdApi { #[endpoint { method = GET, path = "/attached_subnet", - versions = VERSION_ATTACHED_SUBNETS.., + versions = VERSION_ATTACHED_SUBNETS.., }] async fn attached_subnet_list( rqctx: RequestContext, - query: Query>, + query: Query< + PaginationParams< + EmptyScanParams, + latest::route::AttachedSubnetToken, + >, + >, ) -> Result>, HttpError>; /** @@ -1542,11 +1492,11 @@ pub trait DpdApi { #[endpoint { method = GET, path = "/attached_subnet/{subnet}", - versions = VERSION_ATTACHED_SUBNETS.., + versions = VERSION_ATTACHED_SUBNETS.., }] async fn attached_subnet_get( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result, HttpError>; /** @@ -1559,11 +1509,11 @@ pub trait DpdApi { #[endpoint { method = PUT, path = "/attached_subnet/{subnet}", - versions = VERSION_ATTACHED_SUBNETS.., + versions = VERSION_ATTACHED_SUBNETS.., }] async fn attached_subnet_create( rqctx: RequestContext, - path: Path, + path: Path, target: TypedBody, ) -> Result; @@ -1573,11 +1523,11 @@ pub trait DpdApi { #[endpoint { method = DELETE, path = "/attached_subnet/{subnet}", - versions = VERSION_ATTACHED_SUBNETS.., + versions = VERSION_ATTACHED_SUBNETS.., }] async fn attached_subnet_delete( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /** @@ -1586,7 +1536,7 @@ pub trait DpdApi { #[endpoint { method = DELETE, path = "/attached_subnet", - versions = VERSION_ATTACHED_SUBNETS.., + versions = VERSION_ATTACHED_SUBNETS.., }] async fn attached_subnet_reset( rqctx: RequestContext, @@ -1607,7 +1557,7 @@ pub trait DpdApi { }] async fn reset_all_tagged( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /// Clear all settings. @@ -1630,7 +1580,7 @@ pub trait DpdApi { }] async fn link_up_counters_list( rqctx: RequestContext, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /// Get the LinkUp counters for the given link. #[endpoint { @@ -1639,8 +1589,8 @@ pub trait DpdApi { }] async fn link_up_counters_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /// Get the autonegotiation FSM counters for the given link. #[endpoint { @@ -1649,8 +1599,8 @@ pub trait DpdApi { }] async fn link_fsm_counters_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /// Return detailed build information about the `dpd` server itself. #[endpoint { @@ -1659,7 +1609,7 @@ pub trait DpdApi { }] async fn build_info( _rqctx: RequestContext, - ) -> Result, HttpError>; + ) -> Result, HttpError>; /** * Return the version of the `dpd` server itself. @@ -1709,10 +1659,10 @@ pub trait DpdApi { }] async fn port_settings_apply( rqctx: RequestContext, - path: Path, - query: Query, - body: TypedBody, - ) -> Result, HttpError>; + path: Path, + query: Query, + body: TypedBody, + ) -> Result, HttpError>; /** * Clear port settings atomically. @@ -1723,9 +1673,9 @@ pub trait DpdApi { }] async fn port_settings_clear( rqctx: RequestContext, - path: Path, - query: Query, - ) -> Result, HttpError>; + path: Path, + query: Query, + ) -> Result, HttpError>; /** * Get port settings atomically. @@ -1736,9 +1686,9 @@ pub trait DpdApi { }] async fn port_settings_get( rqctx: RequestContext, - path: Path, - query: Query, - ) -> Result, HttpError>; + path: Path, + query: Query, + ) -> Result, HttpError>; /// Get switch identifiers. /// @@ -1751,7 +1701,10 @@ pub trait DpdApi { }] async fn switch_identifiers( rqctx: RequestContext, - ) -> Result, HttpError>; + ) -> Result< + HttpResponseOk, + HttpError, + >; /// Get switch identifiers. /// @@ -1765,7 +1718,10 @@ pub trait DpdApi { }] async fn switch_identifiers_v1( rqctx: RequestContext, - ) -> Result, HttpError> { + ) -> Result< + HttpResponseOk, + HttpError, + > { let result = Self::switch_identifiers(rqctx).await?.0; Ok(HttpResponseOk(result.into())) } @@ -1780,19 +1736,7 @@ pub trait DpdApi { }] async fn tfport_data( rqctx: RequestContext, - ) -> Result>, HttpError>; - - /** - * Get NATv4 generation number - */ - #[endpoint { - method = GET, - path = "/rpw/nat/ipv4/gen", - versions = ..VERSION_DUAL_STACK_NAT_WORKFLOW - }] - async fn ipv4_nat_generation( - rqctx: RequestContext, - ) -> Result, HttpError>; + ) -> Result>, HttpError>; /** * Get NAT generation number @@ -1807,16 +1751,19 @@ pub trait DpdApi { ) -> Result, HttpError>; /** - * Trigger NATv4 Reconciliation + * Get NATv4 generation number */ #[endpoint { - method = POST, - path = "/rpw/nat/ipv4/trigger", - versions = ..VERSION_DUAL_STACK_NAT_WORKFLOW + method = GET, + path = "/rpw/nat/ipv4/gen", + versions = ..VERSION_DUAL_STACK_NAT_WORKFLOW, + operation_id = "ipv4_nat_generation", }] - async fn ipv4_nat_trigger_update( + async fn ipv4_nat_generation_v1( rqctx: RequestContext, - ) -> Result, HttpError>; + ) -> Result, HttpError> { + Self::nat_generation(rqctx).await + } /** * Trigger NAT Reconciliation @@ -1830,6 +1777,21 @@ pub trait DpdApi { rqctx: RequestContext, ) -> Result, HttpError>; + /** + * Trigger NATv4 Reconciliation + */ + #[endpoint { + method = POST, + path = "/rpw/nat/ipv4/trigger", + versions = ..VERSION_DUAL_STACK_NAT_WORKFLOW, + operation_id = "ipv4_nat_trigger_update", + }] + async fn ipv4_nat_trigger_update_v1( + rqctx: RequestContext, + ) -> Result, HttpError> { + Self::nat_trigger_update(rqctx).await + } + /** * Get the list of P4 tables */ @@ -1853,9 +1815,9 @@ pub trait DpdApi { }] async fn table_dump( rqctx: RequestContext, - query: Query, - path: Path, - ) -> Result, HttpError>; + query: Query, + path: Path, + ) -> Result, HttpError>; /** * Get the contents of a single P4 table. @@ -1870,11 +1832,13 @@ pub trait DpdApi { }] async fn table_dump_v1( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError> { + path: Path, + ) -> Result, HttpError> { Self::table_dump( rqctx, - Query::from(TableDumpOptions { from_hardware: false }), + Query::from(latest::snapshot::TableDumpOptions { + from_hardware: false, + }), path, ) .await @@ -1891,9 +1855,9 @@ pub trait DpdApi { }] async fn table_counters( rqctx: RequestContext, - query: Query, - path: Path, - ) -> Result>, HttpError>; + query: Query, + path: Path, + ) -> Result>, HttpError>; /** * Get a list of all the available p4-defined counters. @@ -1917,7 +1881,7 @@ pub trait DpdApi { }] async fn counter_reset( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /** @@ -1931,9 +1895,9 @@ pub trait DpdApi { }] async fn counter_get( rqctx: RequestContext, - query: Query, - path: Path, - ) -> Result>, HttpError>; + query: Query, + path: Path, + ) -> Result>, HttpError>; /** * Create an external-only multicast group configuration. @@ -1949,9 +1913,9 @@ pub trait DpdApi { }] async fn multicast_group_create_external( rqctx: RequestContext, - group: TypedBody, + group: TypedBody, ) -> Result< - HttpResponseCreated, + HttpResponseCreated, HttpError, >; @@ -1964,9 +1928,9 @@ pub trait DpdApi { }] async fn multicast_group_create_external_v7( rqctx: RequestContext, - group: TypedBody, + group: TypedBody, ) -> Result< - HttpResponseCreated, + HttpResponseCreated, HttpError, > { Self::multicast_group_create_external(rqctx, group) @@ -1989,14 +1953,17 @@ pub trait DpdApi { }] async fn multicast_group_create_external_v1( rqctx: RequestContext, - group: TypedBody, + group: TypedBody, ) -> Result< - HttpResponseCreated, + HttpResponseCreated, HttpError, > { - Self::multicast_group_create_external(rqctx, group.map(Into::into)) - .await - .map(|resp| resp.map(Into::into)) + Self::multicast_group_create_external_v7( + rqctx, + group.map(Into::into), + ) + .await + .map(|resp| resp.map(Into::into)) } /// Create an underlay (internal) multicast group configuration. @@ -2007,9 +1974,9 @@ pub trait DpdApi { }] async fn multicast_group_create_underlay( rqctx: RequestContext, - group: TypedBody, + group: TypedBody, ) -> Result< - HttpResponseCreated, + HttpResponseCreated, HttpError, >; @@ -2028,16 +1995,17 @@ pub trait DpdApi { }] async fn multicast_group_create_underlay_v1( rqctx: RequestContext, - group: TypedBody, + group: TypedBody, ) -> Result< - HttpResponseCreated, + HttpResponseCreated, HttpError, > { let v4_body = group .try_map(|entry| { - let group_ip = - mcast::UnderlayMulticastIpv6::try_from(entry.group_ip)?; - Ok(mcast::MulticastGroupCreateUnderlayEntry { + let group_ip = latest::mcast::UnderlayMulticastIpv6::try_from( + entry.group_ip, + )?; + Ok(latest::mcast::MulticastGroupCreateUnderlayEntry { group_ip, tag: entry.tag, members: entry.members, @@ -2062,13 +2030,17 @@ pub trait DpdApi { }] async fn multicast_group_delete( rqctx: RequestContext, - path: Path, - query: Query, + path: Path, + query: Query, ) -> Result; /** * Delete a multicast group configuration by IP address. */ + // This is a required method because the latest version requires a + // `MulticastGroupTagQuery` query parameter for tag validation, which + // the v1 endpoint does not have. There is no sensible default tag to + // synthesize, so the implementation must handle this case directly. #[endpoint { method = DELETE, path = "/multicast/groups/{group_ip}", @@ -2077,7 +2049,7 @@ pub trait DpdApi { }] async fn multicast_group_delete_v1( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /** @@ -2101,8 +2073,8 @@ pub trait DpdApi { }] async fn multicast_group_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /// Get the multicast group configuration for a given group IP address. #[endpoint { @@ -2113,8 +2085,9 @@ pub trait DpdApi { }] async fn multicast_group_get_v7( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError> { + path: Path, + ) -> Result, HttpError> + { Self::multicast_group_get(rqctx, path) .await .map(|resp| resp.map(Into::into)) @@ -2131,9 +2104,10 @@ pub trait DpdApi { }] async fn multicast_group_get_v1( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError> { - Self::multicast_group_get(rqctx, path) + path: Path, + ) -> Result, HttpError> + { + Self::multicast_group_get_v7(rqctx, path) .await .map(|resp| resp.map(Into::into)) } @@ -2146,8 +2120,11 @@ pub trait DpdApi { }] async fn multicast_group_get_underlay( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result< + HttpResponseOk, + HttpError, + >; /** * Get an underlay (internal) multicast group configuration by admin-scoped @@ -2164,12 +2141,16 @@ pub trait DpdApi { }] async fn multicast_group_get_underlay_v1( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError> - { + path: Path, + ) -> Result< + HttpResponseOk, + HttpError, + > { let v4_path = path.try_map(|p| { - mcast::UnderlayMulticastIpv6::try_from(p.group_ip) - .map(|group_ip| MulticastUnderlayGroupIpParam { group_ip }) + latest::mcast::UnderlayMulticastIpv6::try_from(p.group_ip) + .map(|group_ip| latest::mcast::MulticastUnderlayGroupIpParam { + group_ip, + }) .map_err(|e| { HttpError::for_bad_request( None, @@ -2193,10 +2174,13 @@ pub trait DpdApi { }] async fn multicast_group_update_underlay( rqctx: RequestContext, - path: Path, - query: Query, - group: TypedBody, - ) -> Result, HttpError>; + path: Path, + query: Query, + group: TypedBody, + ) -> Result< + HttpResponseOk, + HttpError, + >; /** * Update an underlay (internal) multicast group configuration for a given @@ -2205,6 +2189,10 @@ pub trait DpdApi { * Underlay groups are used for admin-scoped IPv6 multicast traffic that * requires replication infrastructure with external and underlay members. */ + // Required method: the latest version requires a `MulticastGroupTagQuery` + // query parameter that v1 does not have. When the tag is absent from the + // request body, the implementation must look up the existing group's tag + // via `Self::Context`, which is not available in a provided method. #[endpoint { method = PUT, path = "/multicast/underlay-groups/{group_ip}", @@ -2213,9 +2201,12 @@ pub trait DpdApi { }] async fn multicast_group_update_underlay_v1( rqctx: RequestContext, - path: Path, - group: TypedBody, - ) -> Result, HttpError>; + path: Path, + group: TypedBody, + ) -> Result< + HttpResponseOk, + HttpError, + >; /** * Update an external-only multicast group configuration for a given group IP address. @@ -2232,16 +2223,23 @@ pub trait DpdApi { }] async fn multicast_group_update_external( rqctx: RequestContext, - path: Path, - query: Query, - group: TypedBody, - ) -> Result, HttpError>; + path: Path, + query: Query, + group: TypedBody, + ) -> Result< + HttpResponseOk, + HttpError, + >; /** * Update an external-only multicast group configuration. * * Tags are optional for backward compatibility. */ + // Required method: the latest version requires a `MulticastGroupTagQuery` + // query parameter that v7 does not have. When the tag is absent from the + // request body, the implementation must look up the existing group's tag + // via `Self::Context`, which is not available in a provided method. #[endpoint { method = PUT, path = "/multicast/external-groups/{group_ip}", @@ -2250,10 +2248,10 @@ pub trait DpdApi { }] async fn multicast_group_update_external_v7( rqctx: RequestContext, - path: Path, - group: TypedBody, + path: Path, + group: TypedBody, ) -> Result< - HttpResponseCreated, + HttpResponseCreated, HttpError, >; @@ -2263,6 +2261,7 @@ pub trait DpdApi { * External-only groups are used for IPv4 and non-admin-scoped IPv6 * multicast traffic that doesn't require replication infrastructure. */ + // Required method: same reason as `multicast_group_update_external_v7`. #[endpoint { method = PUT, path = "/multicast/external-groups/{group_ip}", @@ -2271,10 +2270,10 @@ pub trait DpdApi { }] async fn multicast_group_update_external_v1( rqctx: RequestContext, - path: Path, - group: TypedBody, + path: Path, + group: TypedBody, ) -> Result< - HttpResponseCreated, + HttpResponseCreated, HttpError, >; @@ -2289,10 +2288,13 @@ pub trait DpdApi { async fn multicast_groups_list( rqctx: RequestContext, query_params: Query< - PaginationParams, + PaginationParams< + EmptyScanParams, + latest::mcast::MulticastGroupIpParam, + >, >, ) -> Result< - HttpResponseOk>, + HttpResponseOk>, HttpError, >; @@ -2306,10 +2308,10 @@ pub trait DpdApi { async fn multicast_groups_list_v7( rqctx: RequestContext, query_params: Query< - PaginationParams, + PaginationParams, >, ) -> Result< - HttpResponseOk>, + HttpResponseOk>, HttpError, > { let HttpResponseOk(page) = @@ -2332,14 +2334,14 @@ pub trait DpdApi { async fn multicast_groups_list_v1( rqctx: RequestContext, query_params: Query< - PaginationParams, + PaginationParams, >, ) -> Result< - HttpResponseOk>, + HttpResponseOk>, HttpError, > { let HttpResponseOk(page) = - Self::multicast_groups_list(rqctx, query_params).await?; + Self::multicast_groups_list_v7(rqctx, query_params).await?; Ok(HttpResponseOk(ResultsPage { items: page.items.into_iter().map(Into::into).collect(), next_page: page.next_page, @@ -2360,12 +2362,15 @@ pub trait DpdApi { }] async fn multicast_groups_list_by_tag( rqctx: RequestContext, - path: Path, + path: Path, query_params: Query< - PaginationParams, + PaginationParams< + EmptyScanParams, + latest::mcast::MulticastGroupIpParam, + >, >, ) -> Result< - HttpResponseOk>, + HttpResponseOk>, HttpError, >; @@ -2378,12 +2383,12 @@ pub trait DpdApi { }] async fn multicast_groups_list_by_tag_v7( rqctx: RequestContext, - path: Path, + path: Path, query_params: Query< - PaginationParams, + PaginationParams, >, ) -> Result< - HttpResponseOk>, + HttpResponseOk>, HttpError, > { let HttpResponseOk(page) = Self::multicast_groups_list_by_tag( @@ -2409,17 +2414,17 @@ pub trait DpdApi { }] async fn multicast_groups_list_by_tag_v1( rqctx: RequestContext, - path: Path, + path: Path, query_params: Query< - PaginationParams, + PaginationParams, >, ) -> Result< - HttpResponseOk>, + HttpResponseOk>, HttpError, > { - let HttpResponseOk(page) = Self::multicast_groups_list_by_tag( + let HttpResponseOk(page) = Self::multicast_groups_list_by_tag_v7( rqctx, - path.map(Into::into), + path, query_params, ) .await?; @@ -2444,7 +2449,7 @@ pub trait DpdApi { }] async fn multicast_reset_by_tag( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /** @@ -2458,7 +2463,7 @@ pub trait DpdApi { }] async fn multicast_reset_by_tag_v1( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result { Self::multicast_reset_by_tag(rqctx, path.map(Into::into)).await } @@ -2482,6 +2487,8 @@ pub trait DpdApi { /** * Delete all multicast groups (and associated routes) without a tag. */ + // Required method: the latest version always returns 410 Gone, while v1 + // actually deletes untagged groups. The semantic behavior is different. #[endpoint { method = DELETE, path = "/multicast/untagged", @@ -2501,7 +2508,7 @@ pub trait DpdApi { }] async fn pcs_counters_list( rqctx: RequestContext, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /** * Get the Physical Coding Sublayer (PCS) counters for the given link. @@ -2512,8 +2519,8 @@ pub trait DpdApi { }] async fn pcs_counters_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /** * Get the FEC RS counters for all links. @@ -2524,7 +2531,10 @@ pub trait DpdApi { }] async fn fec_rs_counters_list( rqctx: RequestContext, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /** * Get the FEC RS counters for the given link. @@ -2535,8 +2545,8 @@ pub trait DpdApi { }] async fn fec_rs_counters_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /** * Get the most relevant subset of traffic counters for the given link. @@ -2547,8 +2557,8 @@ pub trait DpdApi { }] async fn rmon_counters_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /** * Get the full set of traffic counters for the given link. @@ -2559,8 +2569,8 @@ pub trait DpdApi { }] async fn rmon_counters_get_all( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /** * Get the logical->physical mappings for each lane in this port @@ -2571,8 +2581,8 @@ pub trait DpdApi { }] async fn lane_map_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /** * Get the per-lane tx eq settings for each lane on this link @@ -2583,7 +2593,7 @@ pub trait DpdApi { }] async fn link_tx_eq_get( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result>, HttpError>; /** @@ -2595,7 +2605,7 @@ pub trait DpdApi { }] async fn link_tx_eq_set( rqctx: RequestContext, - path: Path, + path: Path, args: TypedBody, ) -> Result; @@ -2608,8 +2618,8 @@ pub trait DpdApi { }] async fn link_rx_sig_info_get( rqctx: RequestContext, - path: Path, - ) -> Result>, HttpError>; + path: Path, + ) -> Result>, HttpError>; /** * Get the per-lane adaptation counts for each lane on this link @@ -2620,8 +2630,11 @@ pub trait DpdApi { }] async fn link_rx_adapt_count_get( rqctx: RequestContext, - path: Path, - ) -> Result>, HttpError>; + path: Path, + ) -> Result< + HttpResponseOk>, + HttpError, + >; /** * Get the per-lane eye measurements for each lane on this link @@ -2632,8 +2645,8 @@ pub trait DpdApi { }] async fn link_eye_get( rqctx: RequestContext, - path: Path, - ) -> Result>, HttpError>; + path: Path, + ) -> Result>, HttpError>; /** * Get the per-lane speed and encoding for each lane on this link @@ -2644,8 +2657,8 @@ pub trait DpdApi { }] async fn link_enc_speed_get( rqctx: RequestContext, - path: Path, - ) -> Result>, HttpError>; + path: Path, + ) -> Result>, HttpError>; /** * Get the per-lane AN/LT status for each lane on this link @@ -2656,8 +2669,8 @@ pub trait DpdApi { }] async fn link_an_lt_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /** * Return the estimated bit-error rate (BER) for a link. @@ -2668,8 +2681,8 @@ pub trait DpdApi { }] async fn link_ber_get( rqctx: RequestContext, - path: Path, - ) -> Result, HttpError>; + path: Path, + ) -> Result, HttpError>; /** * Return the measured bit-error rate for a link with an active PRBS @@ -2682,8 +2695,8 @@ pub trait DpdApi { }] async fn link_prbs_get_err( rqctx: RequestContext, - path: Path, - body: TypedBody, + path: Path, + body: TypedBody, ) -> Result>, HttpError>; /// Capture a PHV snapshot: create snapshot, set triggers, arm, wait @@ -2695,8 +2708,8 @@ pub trait DpdApi { }] async fn snapshot_capture( rqctx: RequestContext, - body: TypedBody, - ) -> Result, HttpError>; + body: TypedBody, + ) -> Result, HttpError>; /// Check which fields are in scope at a given stage. #[endpoint { @@ -2706,1048 +2719,9 @@ pub trait DpdApi { }] async fn snapshot_scope( rqctx: RequestContext, - body: TypedBody, - ) -> Result>, HttpError>; -} - -/// Duration in milliseconds -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct MsDuration { - /// Duration in milliseconds - pub ms: u32, -} - -/// Parameter used to create a port. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct PortCreateParams { - /// The name of the port. This should be a string like `"3:0"`. - pub name: String, - /// The speed at which to configure the port. - pub speed: PortSpeed, - /// The forward error-correction scheme for the port. - pub fec: PortFec, -} - -/// Represents the free MAC channels on a single physical port. -#[derive(Deserialize, Serialize, JsonSchema, Debug)] -pub struct FreeChannels { - /// The switch port. - pub port_id: PortId, - /// The Tofino connector for this port. - /// - /// This describes the set of electrical connections representing this port - /// object, which are defined by the pinout and board design of the Sidecar. - pub connector: String, - /// The set of available channels (lanes) on this connector. - pub channels: Vec, -} - -/// Represents the mapping of an IP address to a MAC address. -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct ArpEntry { - /// A tag used to associate this entry with a client. - pub tag: String, - /// The IP address for the entry. - pub ip: IpAddr, - /// The MAC address to which `ip` maps. - pub mac: MacAddr, - /// The time the entry was updated - pub update: String, -} - -/// Represents a specific egress port and nexthop target. -#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] -pub enum RouteTarget { - V4(Ipv4Route), - V6(Ipv6Route), -} - -impl From<&Ipv4Route> for RouteTarget { - fn from(route: &Ipv4Route) -> RouteTarget { - RouteTarget::V4(route.clone()) - } -} - -impl From for RouteTarget { - fn from(route: Ipv4Route) -> RouteTarget { - RouteTarget::V4(route) - } -} - -impl From<&Ipv6Route> for RouteTarget { - fn from(route: &Ipv6Route) -> RouteTarget { - RouteTarget::V6(route.clone()) - } -} - -impl From for RouteTarget { - fn from(route: Ipv6Route) -> RouteTarget { - RouteTarget::V6(route) - } -} - -impl TryFrom for Ipv4Route { - type Error = HttpError; - - fn try_from(target: RouteTarget) -> Result { - match target { - RouteTarget::V4(route) => Ok(route), - _ => Err(dropshot::HttpError::for_bad_request( - None, - "expected an IPv4 route target".to_string(), - )), - } - } -} - -impl TryFrom for Ipv6Route { - type Error = HttpError; - - fn try_from(target: RouteTarget) -> Result { - match target { - RouteTarget::V6(route) => Ok(route), - _ => Err(dropshot::HttpError::for_bad_request( - None, - "expected an IPv6 route target".to_string(), - )), - } - } -} - -/// Represents a new or replacement mapping of an IPv4 subnet to a single -/// RouteTarget nexthop target, which may be either IPv4 or IPv6. -#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] -pub struct Ipv4RouteUpdate { - /// Traffic destined for any address within the CIDR block is routed using - /// this information. - pub cidr: Ipv4Net, - /// A single Route associated with this CIDR - pub target: RouteTarget, - /// Should this route replace any existing route? If a route exists and - /// this parameter is false, then the call will fail. - pub replace: bool, -} - -#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] -pub struct Ipv4OverIpv6RouteUpdate { - /// Traffic destined for any address within the CIDR block is routed using - /// this information. - pub cidr: Ipv4Net, - /// A single Route associated with this CIDR - pub target: Ipv6Route, - /// Should this route replace any existing route? If a route exists and - /// this parameter is false, then the call will fail. - pub replace: bool, -} - -/// Represents a new or replacement mapping of a subnet to a single IPv6 -/// RouteTarget nexthop target. -#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] -pub struct Ipv6RouteUpdate { - /// Traffic destined for any address within the CIDR block is routed using - /// this information. - pub cidr: Ipv6Net, - /// A single RouteTarget associated with this CIDR - pub target: Ipv6Route, - /// Should this route replace any existing route? If a route exists and - /// this parameter is false, then the call will fail. - pub replace: bool, -} - -#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] -pub struct Ipv4Routes { - /// Traffic destined for any address within the CIDR block is routed using - /// this information. - pub cidr: Ipv4Net, - /// All RouteTargets associated with this CIDR - pub targets: Vec, -} - -/// Represents all mappings of an IPv6 subnet to a its nexthop target(s). -#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] -pub struct Ipv6Routes { - /// Traffic destined for any address within the CIDR block is routed using - /// this information. - pub cidr: Ipv6Net, - /// All RouteTargets associated with this CIDR - pub targets: Vec, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct Ipv6ArpParam { - pub ip: Ipv6Addr, -} - -/** - * Represents a cursor into a paginated request for the contents of an ARP table - */ -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct ArpToken { - pub ip: IpAddr, -} - -/** - * Represents a potential fault condtion on a link - */ -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct FaultCondition { - pub fault: Option, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct Ipv4ArpParam { - pub ip: Ipv4Addr, -} - -/** - * Represents a cursor into a paginated request for the contents of an - * Ipv4-indexed table. - */ -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct Ipv4Token { - pub ip: Ipv4Addr, -} - -/** - * Represents a cursor into a paginated request for the contents of an - * IPv6-indexed table. - */ -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct Ipv6Token { - pub ip: Ipv6Addr, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct RoutePathV4 { - /// The IPv4 subnet in CIDR notation whose route entry is returned. - pub cidr: Ipv4Net, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct SubnetPath { - /// The external subnet in CIDR notation being managed - pub subnet: IpNet, -} - -/// Represents a single subnet->target route entry with an IPv4 or IPv6 -/// next hop. -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct RouteTargetIpv4Path { - /// The subnet being routed - pub cidr: Ipv4Net, - /// The switch port to which packets should be sent - pub port_id: PortId, - /// The link to which packets should be sent - pub link_id: LinkId, - /// The next hop in the route (IPv4 or IPv6) - pub tgt_ip: IpAddr, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct RoutePathV6 { - /// The IPv6 subnet in CIDR notation whose route entry is returned. - pub cidr: Ipv6Net, -} - -/// Represents a single subnet->target route entry -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct RouteTargetIpv6Path { - /// The subnet being routed - pub cidr: Ipv6Net, - /// The switch port to which packets should be sent - pub port_id: PortId, - /// The link to which packets should be sent - pub link_id: LinkId, - /// The next hop in the IPv4 route - pub tgt_ip: Ipv6Addr, -} - -/** - * Represents a cursor into a paginated request for the contents of the - * subnet routing table. Because we don't (yet) support filtering or arbitrary - * sorting, it is sufficient to track the last mac address reported. - */ -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct Ipv4RouteToken { - pub cidr: Ipv4Net, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct Ipv6RouteToken { - pub cidr: Ipv6Net, -} - -/** - * Represents a cursor into a paginated request for the contents of the - * external subnets table. - */ -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct AttachedSubnetToken { - pub cidr: IpNet, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct PortIpv4Path { - pub port: String, - pub ipv4: Ipv4Addr, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct PortIpv6Path { - pub port: String, - pub ipv6: Ipv6Addr, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct LoopbackIpv4Path { - pub ipv4: Ipv4Addr, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct LoopbackIpv6Path { - pub ipv6: Ipv6Addr, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct NatIpv6Path { - pub ipv6: Ipv6Addr, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct NatIpv6PortPath { - pub ipv6: Ipv6Addr, - pub low: u16, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct NatIpv6RangePath { - pub ipv6: Ipv6Addr, - pub low: u16, - pub high: u16, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct NatIpv4Path { - pub ipv4: Ipv4Addr, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct NatIpv4PortPath { - pub ipv4: Ipv4Addr, - pub low: u16, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct NatIpv4RangePath { - pub ipv4: Ipv4Addr, - pub low: u16, - pub high: u16, -} - -/** - * Represents a cursor into a paginated request for all NAT data. - */ -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct NatToken { - pub port: u16, -} - -/** - * Represents a cursor into a paginated request for all port data. Because we - * don't (yet) support filtering or arbitrary sorting, it is sufficient to - * track the last port returned. - */ -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct PortToken { - pub port: u16, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct PortIdPathParams { - /// The switch port on which to operate. - pub port_id: PortId, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct PortSettingsTag { - /// Restrict operations on this port to the provided tag. - pub tag: Option, -} - -/// Identifies a logical link on a physical port. -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct LinkPath { - /// The switch port on which to operate. - pub port_id: PortId, - /// The link in the switch port on which to operate. - pub link_id: LinkId, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct LinkIpv4Path { - /// The switch port on which to operate. - pub port_id: PortId, - /// The link in the switch port on which to operate. - pub link_id: LinkId, - /// The IPv4 address on which to operate. - pub address: Ipv4Addr, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct LinkIpv6Path { - /// The switch port on which to operate. - pub port_id: PortId, - /// The link in the switch port on which to operate. - pub link_id: LinkId, - /// The IPv6 address on which to operate. - pub address: Ipv6Addr, -} - -/// Parameters used to create a link on a switch port. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct LinkCreate { - /// The first lane of the port to use for the new link - pub lane: Option, - /// The requested speed of the link. - pub speed: PortSpeed, - /// The requested forward-error correction method. If this is None, the - /// standard FEC for the underlying media will be applied if it can be - /// determined. - pub fec: Option, - /// Whether the link is configured to autonegotiate with its peer during - /// link training. - /// - /// This is generally only true for backplane links, and defaults to - /// `false`. - #[serde(default)] - pub autoneg: bool, - /// Whether the link is configured in KR mode, an electrical specification - /// generally only true for backplane link. - /// - /// This defaults to `false`. - #[serde(default)] - pub kr: bool, - - /// Transceiver equalization adjustment parameters. - /// This defaults to `None`. - #[serde(default)] - pub tx_eq: Option, -} - -#[derive(Clone, Debug, Deserialize, JsonSchema)] -pub struct LinkFilter { - /// Filter links to those whose name contains the provided string. - /// - /// If not provided, then all links are returned. - pub filter: Option, -} - -/// Path parameter for tag-based operations. -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct TagPath { - pub tag: String, -} - -/// Detailed build information about `dpd`. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct BuildInfo { - pub version: String, - pub git_sha: String, - pub git_commit_timestamp: String, - pub git_branch: String, - pub rustc_semver: String, - pub rustc_channel: String, - pub rustc_host_triple: String, - pub rustc_commit_sha: String, - pub cargo_triple: String, - pub debug: bool, - pub opt_level: u8, - pub sde_commit_sha: String, -} - -/// A port settings transaction object. When posted to the -/// `/port-settings/{port_id}` API endpoint, these settings will be applied -/// holistically, and to the extent possible atomically to a given port. -#[derive(Default, Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct PortSettings { - /// The link settings to apply to the port on a per-link basis. Any links - /// not in this map that are resident on the switch port will be removed. - /// Any links that are in this map that are not resident on the switch port - /// will be added. Any links that are resident on the switch port and in - /// this map, and are different, will be modified. Links are indexed by - /// spatial index within the port. - pub links: HashMap, -} - -/// An object with link settings used in concert with [`PortSettings`]. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct LinkSettings { - pub params: LinkCreate, - pub addrs: HashSet, -} - -/// An object with IPv4 route settings used in concert with [`PortSettings`]. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct RouteSettingsV4 { - pub link_id: u8, - pub nexthop: Ipv4Addr, -} - -/// An object with IPV6 route settings used in concert with [`PortSettings`]. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct RouteSettingsV6 { - pub link_id: u8, - pub nexthop: Ipv6Addr, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct CounterSync { - /// Force a sync of the counters from the ASIC to memory, even if the - /// default refresh timeout hasn't been reached. - pub force_sync: bool, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct TableParam { - pub table: String, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct TableDumpOptions { - pub from_hardware: bool, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct CounterPath { - pub counter: String, -} - -/// Used to identify a multicast group by IP address, the main -/// identifier for a multicast group. -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupIpParam { - pub group_ip: IpAddr, -} - -/// Tag for identifying and authorizing multicast group operations. -/// -/// Tag format: 1 to 80 ASCII bytes containing alphanumeric characters, -/// hyphens, underscores, colons, or periods. Default format is -/// `{uuid}:{group_ip}`. -#[derive( - Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema, -)] -pub struct MulticastTag( - #[schemars( - length(min = 1, max = 80), - regex(pattern = r"^[a-zA-Z0-9_.:-]+$") - )] - pub String, -); - -impl AsRef for MulticastTag { - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl From for String { - fn from(tag: MulticastTag) -> Self { - tag.0 - } -} - -impl From for MulticastTag { - fn from(tag: String) -> Self { - MulticastTag(tag) - } -} - -/// Maximum length for multicast tags. -pub const MAX_TAG_LENGTH: usize = 80; - -/// Error parsing a multicast tag from a string. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct MulticastTagParseError(String); - -impl fmt::Display for MulticastTagParseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl std::error::Error for MulticastTagParseError {} - -impl FromStr for MulticastTag { - type Err = MulticastTagParseError; - - fn from_str(s: &str) -> Result { - if s.is_empty() { - return Err(MulticastTagParseError( - "tag cannot be empty".to_string(), - )); - } - if s.len() > MAX_TAG_LENGTH { - return Err(MulticastTagParseError(format!( - "tag cannot exceed {MAX_TAG_LENGTH} bytes" - ))); - } - if !s.bytes().all(|b| { - b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b':' | b'.') - }) { - return Err(MulticastTagParseError( - "tag must contain only ASCII alphanumeric characters, hyphens, \ - underscores, colons, or periods" - .to_string(), - )); - } - Ok(MulticastTag(s.to_string())) - } -} - -/// Path parameter for multicast tag-based operations. -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct MulticastTagPath { - pub tag: MulticastTag, -} - -impl From for MulticastTagPath { - fn from(path: TagPath) -> Self { - Self { tag: path.tag.into() } - } -} - -/// Tag for multicast group validation. -/// -/// All groups have tags (auto-generated at creation if not provided). -/// The provided tag must match the group's existing tag. -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupTagQuery { - /// Tag that must match the group's existing tag. - pub tag: MulticastTag, -} - -/// Used to identify an underlay multicast group by IPv6 address within -/// the underlay multicast subnet (ff04::/64). -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct MulticastUnderlayGroupIpParam { - pub group_ip: mcast::UnderlayMulticastIpv6, -} - -/// Used to identify a multicast group by ID. -/// -/// If not provided, it will return all multicast groups. -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupIdParam { - pub group_id: Option, -} - -/// The Physical Coding Sublayer (PCS) counters for a specific link. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct LinkPcsCounters { - /// The switch port ID. - pub port_id: PortId, - /// The link ID. - pub link_id: LinkId, - /// The PCS counter data. - pub counters: PcsCounters, -} - -/// The FEC counters for a specific link, including its link ID. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct LinkFecRSCounters { - /// The switch port ID. - pub port_id: PortId, - /// The link ID. - pub link_id: LinkId, - /// The FEC counter data. - pub counters: FecRSCounters, -} - -/// The RMON counters (traffic counters) for a specific link. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct LinkRMonCounters { - /// The switch port ID. - pub port_id: PortId, - /// The link ID. - pub link_id: LinkId, - /// The RMON counter data. - pub counters: RMonCounters, -} - -/// The complete RMON counters (traffic counters) for a specific link. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct LinkRMonCountersAll { - /// The switch port ID. - pub port_id: PortId, - /// The link ID. - pub link_id: LinkId, - /// The RMON counter data. - pub counters: RMonCountersAll, -} - -/// Mapping of the logical lanes in a link to their physical instantiation in -/// the MAC/serdes interface. -// -// For each lane assigned to the port, this captures the mac block, the logical -// lane within the mac block, the physical rx and tx lanes, and the polarity of -// each. All of these values are determined by the physical layout of the -// board, should be identical across all sidecars with the same board revision, -// and shouldn't change from run to run. -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct LaneMap { - /// MAC block in the tofino ASIC - pub mac_block: u32, - /// logical lane within the mac block for each lane - pub logical_lane: Vec, - /// Rx logical->physical mapping - pub rx_phys: Vec, - /// Tx logical->physical mapping - pub tx_phys: Vec, - /// Rx polarity - pub rx_polarity: Vec, - /// Tx polarity - pub tx_polarity: Vec, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub enum Polarity { - Normal, - Inverted, -} - -impl From for Polarity { - fn from(p: bool) -> Self { - match p { - true => Polarity::Inverted, - false => Polarity::Normal, - } - } -} - -/// Per-lane Rx signal information -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct RxSigInfo { - /// Rx signal detected - pub sig_detect: bool, - /// CDR lock achieved - pub phy_ready: bool, - /// Apparent PPM difference between local and remote - pub ppm: i32, -} - -/// Rx DFE adaptation information -#[derive(Default, Deserialize, Serialize, JsonSchema)] -pub struct DfeAdaptationState { - /// DFE complete - pub adapt_done: bool, - /// Total DFE attempts - pub adapt_cnt: u32, - /// DFE attempts since the last read - pub readapt_cnt: u32, - /// Times the signal was lost since the last read - pub link_lost_cnt: u32, -} - -/// Eye height(s) for a single lane in mv -#[derive(Deserialize, Serialize, JsonSchema)] -pub enum SerdesEye { - Nrz(f32), - Pam4 { eye1: f32, eye2: f32, eye3: f32 }, -} - -/// Signal encoding -#[derive(PartialEq, Deserialize, Serialize, JsonSchema)] -pub enum LaneEncoding { - /// Pulse Amplitude Modulation 4-level - Pam4, - /// Non-Return-to-Zero encoding - Nrz, - /// No encoding selected - None, -} - -/// Signal speed and encoding for a single lane -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct EncSpeed { - pub encoding: LaneEncoding, - pub gigabits: u32, -} - -/// State of a single lane during autonegotiation -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct AnStatus { - /// Can the link partner perform AN? - pub lp_an_ability: bool, - /// Allegedly: is the link up? In practice, this always seems to be false? - /// TODO: investigate this - pub link_status: bool, - /// Are we capable of AN? - pub an_ability: bool, - /// Remote fault detected - pub remote_fault: bool, - /// Is autonegotiation complete? - pub an_complete: bool, - /// has a base page been received? - pub page_rcvd: bool, - /// Is extended page format supported? - pub ext_np_status: bool, - /// A fault has been detected via the parallel detection function - pub parallel_detect_fault: bool, -} - -/// Link-training status for a single lane -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct LtStatus { - /// Readout for frame lock state - pub readout_state: u32, - /// Frame lock state - pub frame_lock: bool, - /// Local training finished - pub rx_trained: bool, - /// Training state readout - pub readout_training_state: u32, - /// Link training failed - pub training_failure: bool, - /// TX control to send training pattern - pub tx_training_data_en: bool, - /// Signal detect for PCS - pub sig_det: bool, - /// State machine readout for training arbiter - pub readout_txstate: u32, -} - -/// A collection of the data involved in the autonegiation/link-training process -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct AnLtStatus { - /// The base and extended pages received from the link partner - pub lp_pages: LpPages, - /// The per-lane status - pub lanes: Vec, -} - -/// Set of AN pages sent by our link partner -#[derive(Default, Deserialize, Serialize, JsonSchema)] -pub struct LpPages { - pub base_page: u64, - pub next_page1: u64, - pub next_page2: u64, -} - -/// The combined status of a lane, with respect to the autonegotiation / -/// link-training process. -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct LaneStatus { - /// Has a lane successfully completed autoneg and link training? - pub lane_done: bool, - /// Detailed autonegotiation status - pub lane_an_status: AnStatus, - /// Detailed link-training status - pub lane_lt_status: LtStatus, -} - -/// Reports the bit-error rate (BER) for a link. -#[derive(Clone, Deserialize, JsonSchema, PartialEq, Serialize)] -pub struct Ber { - /// Counters of symbol errors per-lane. - pub symbol_errors: Vec, - /// Estimated BER per-lane. - pub ber: Vec, - /// Aggregate BER on the link. - pub total_ber: f32, -} - -// --- Snapshot types --- - -/// Direction of a PHV snapshot. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub enum SnapshotDirection { - /// Take snapshot of ingress pipeline - Ingress, - /// Take snapshot of egress pipeline - Egress, -} - -/// A trigger field for a snapshot, with hex-encoded value and mask. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct SnapshotTrigger { - /// Name of the field to capture. - /// - /// Must match what's in the phv ingress or phv egress section of - /// /opt/oxide/dendrite/sidecar/pipe/sidecar.bfa. - pub field: String, - /// Hex-encoded value (e.g. "0x112233445566") - pub value: String, - /// Hex-encoded mask (e.g. "0xffffffffffff") - pub mask: String, -} - -/// Request body for creating and capturing a PHV snapshot. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct SnapshotCreate { - /// Index of the pipeline to capture. Typically this will be 0 through 3. - /// Different ports map to different pipelines. - pub pipe: u32, - /// Tofino hardware stage to start capturing at. - /// - /// See /opt/oxide/dendrite/sidecar/pipe/sidecar.bfa to get a sense of - /// stage layout. - pub start_stage: u8, - /// Tofino hardware stage to stop capturing at. - /// - /// See /opt/oxide/dendrite/sidecar/pipe/sidecar.bfa to get a sense of - /// stage layout. - pub end_stage: u8, - /// Whether to capture on the ingress or egress pipeline. - pub dir: SnapshotDirection, - /// Fields and masks to use as snapshot trigger. Triggers are combined as - /// a logical `and`. - pub triggers: Vec, - /// Field names to decode from the capture. - pub fields: Vec, - /// Timeout in seconds to wait for trigger. - pub timeout_secs: u64, -} - -/// Table hit/miss result from a snapshot capture. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct SnapshotTableResult { - /// Name of the table - pub name: String, - /// Whether the match lookup found a matching entry. - /// - /// Only meaningful when `executed` is true and `inhibited` is false. - /// The absence of `hit` does not necessarily mean that a lookup was - /// attempted. It simply means there was no hit, which could mean no lookup - /// was attempted or that the table's gateway inhibited it. - pub hit: bool, - /// Whether the table's gateway inhibited the match lookup from - /// proceeding. Gateways are conditional guards attached to tables that - /// can skip the lookup entirely. When inhibited, `hit` and - /// `match_hit_address` will be 0. Only applicable to tables that have - /// an attached gateway. - pub inhibited: bool, - /// Whether the table was active in this stage. This is the primary - /// gate: if a table was not executed, `hit`, `inhibited`, and - /// `match_hit_address` are all meaningless (zeroed by the SDE). - pub executed: bool, - /// The physical address of the entry that matched, sourced from the - /// exact-match or TCAM hit-address register depending on table type. - /// Zero when the table was not executed or was inhibited. - pub match_hit_address: u32, -} - -/// Per-stage result from a snapshot capture. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct SnapshotStageResult { - /// The index of the stage this result came from. - pub stage_id: u8, - /// Whether this stage's own PHV match criteria fired the snapshot. - /// This is the primary trigger: the PHV contents at this stage matched - /// the key/mask programmed via the snapshot trigger configuration. - pub local_stage_trigger: bool, - /// Whether the snapshot was triggered because the previous stage was - /// already triggered and propagated its trigger signal forward. A - /// `prev_stage_trigger` with no `local_stage_trigger` means this stage - /// did not match the trigger criteria itself -- it was captured solely - /// because an adjacent stage matched. - pub prev_stage_trigger: bool, - /// Whether the snapshot was triggered by the timer mechanism rather - /// than a PHV field match. Useful for capturing pipeline state at a - /// specific time regardless of packet contents. - pub timer_trigger: bool, - /// The P4 table name that the MAU pipeline selected for execution in - /// the following stage after processing this one. - pub next_table: String, - /// Datapath error detected in the ingress pipeline at capture time. - /// Only reported on Tofino 2+; always false on Tofino 1. - pub ingress_dp_error: bool, - /// Datapath error detected in the egress pipeline at capture time. - /// Only reported on Tofino 2+; always false on Tofino 1. - pub egress_dp_error: bool, - /// Tables captured in the result. - pub tables: Vec, - /// Fields captured in the result. - pub fields: Vec, -} - -/// A decoded field value from a snapshot capture. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct SnapshotFieldValue { - /// Name of the field. - pub name: String, - /// None if the field is not valid at this stage. - pub value: Option, -} - -/// Result of a snapshot capture operation. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct SnapshotResult { - /// Stages captured in the result. - pub stages: Vec, -} - -/// Request body for checking field scope at a given stage. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct SnapshotScopeRequest { - /// Pipeline index to check. - pub pipe: u32, - /// Stage index. - pub stage: u8, - /// Whether to check the ingress or egress pipeline. - pub dir: SnapshotDirection, - /// Fields to check. - pub fields: Vec, - /// If true, check trigger scope; otherwise check capture scope. - pub trigger: bool, -} - -/// Whether a field is in scope at a given stage. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct SnapshotFieldScope { - /// Field name - pub field: String, - /// Whether or not the field is in scope. - pub in_scope: bool, -} - -/// Request body for dumping table entries. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct TableDumpRequest { - /// Fully-qualified P4 table name (e.g. "Ingress.services.service"). - pub table_name: String, - /// If true, read entries from ASIC hardware via indirect register - /// reads instead of the SDE's software shadow. - pub from_hw: bool, -} - -/// A key field from a table entry dump. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct TableDumpKeyField { - /// Name of the field - pub name: String, - /// Key value - pub value: String, - /// For ternary fields: the mask. For LPM: the prefix length. - pub mask: Option, -} - -/// A single entry from a table dump. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct TableDumpEntry { - /// Action associated with an entry. - pub action: String, - /// Match fields for an entry. - pub match_fields: Vec, -} - -/// Result of a table dump operation. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct TableDumpResult { - /// Name of the table. - pub table_name: String, - /// Number of entries in the table. - pub num_entries: usize, - /// Table entries. - pub entries: Vec, + body: TypedBody, + ) -> Result< + HttpResponseOk>, + HttpError, + >; } diff --git a/dpd-api/src/v1.rs b/dpd-api/src/v1.rs deleted file mode 100644 index a543d9f1..00000000 --- a/dpd-api/src/v1.rs +++ /dev/null @@ -1,413 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/ -// -// Copyright 2026 Oxide Computer Company - -//! Types from API version 1 (INITIAL) that changed in later versions. -//! -//! - `IpSrc` was changed in v7 (MCAST_SOURCE_FILTER_ANY): `Subnet` -> `Any` -//! - `AdminScopedIpv6` was changed in v8 (MCAST_STRICT_UNDERLAY): ff04::/16 -> ff04::/64 -//! - Response `tag` fields were changed in v8: `Option` -> `String` - -use std::{ - fmt, - net::{IpAddr, Ipv6Addr}, - str::FromStr, -}; - -use dpd_types::mcast::{ - ExternalForwarding, InternalForwarding, MulticastGroupId, - MulticastGroupMember, UnderlayMulticastIpv6, -}; -use dpd_types::route::Ipv4Route; -use oxnet::{Ipv4Net, Ipv6Net}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -/// Represents all mappings of an IPv4 subnet to its nexthop target(s). -#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] -pub struct Ipv4Routes { - /// Traffic destined for any address within the CIDR block is routed using - /// this information. - pub cidr: Ipv4Net, - /// All RouteTargets associated with this CIDR. - pub targets: Vec, -} - -/// Identifiers for a switch. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct SwitchIdentifiers { - /// Unique identifier for the chip. - pub sidecar_id: Uuid, - /// Asic backend (compiler target) responsible for these identifiers. - pub asic_backend: String, - /// Fabrication plant identifier. - pub fab: Option, - /// Lot identifier. - pub lot: Option, - /// Wafer number within the lot. - pub wafer: Option, - /// The wafer location as (x, y) coordinates on the wafer, represented as - /// an array due to the lack of tuple support in OpenAPI. - pub wafer_loc: Option<[i16; 2]>, - /// The model number of the switch being managed. - pub model: String, - /// The revision number of the switch being managed. - pub revision: u32, - /// The serial number of the switch being managed. - pub serial: String, - /// The slot number of the switch being managed. - /// - /// MGS uses u16 for this internally. - pub slot: u16, -} - -impl From - for SwitchIdentifiers -{ - fn from(latest: dpd_types::switch_identifiers::SwitchIdentifiers) -> Self { - Self { - sidecar_id: latest.sidecar_id, - asic_backend: latest.asic_backend, - fab: latest.fab, - lot: latest.lot, - wafer: latest.wafer, - wafer_loc: latest.wafer_loc, - model: latest.model, - revision: latest.revision, - serial: latest.serial, - slot: latest.slot, - } - } -} - -// Multicast types introduced in v1 - -/// Source filter match key for multicast traffic. -#[derive( - Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema, -)] -pub enum IpSrc { - /// Exact match for the source IP address. - Exact(IpAddr), - /// Subnet match for the source IP address. - Subnet(Ipv4Net), -} - -impl fmt::Display for IpSrc { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - IpSrc::Exact(ip) => write!(f, "{ip}"), - IpSrc::Subnet(net) => write!(f, "{net}"), - } - } -} - -/// Convert from latest IpSrc to v1 IpSrc. -impl From for IpSrc { - fn from(src: dpd_types::mcast::IpSrc) -> Self { - match src { - dpd_types::mcast::IpSrc::Exact(ip) => IpSrc::Exact(ip), - dpd_types::mcast::IpSrc::Any => { - // v1-v4 API only supported IPv4 subnet matching. - IpSrc::Subnet( - Ipv4Net::new(std::net::Ipv4Addr::UNSPECIFIED, 0).unwrap(), - ) - } - } - } -} - -/// Convert from v1 IpSrc to latest IpSrc. -impl From for dpd_types::mcast::IpSrc { - fn from(src: IpSrc) -> Self { - match src { - IpSrc::Exact(ip) => dpd_types::mcast::IpSrc::Exact(ip), - IpSrc::Subnet(net) if net.width() == 0 => { - dpd_types::mcast::IpSrc::Any - } - IpSrc::Subnet(net) => { - dpd_types::mcast::IpSrc::Exact(IpAddr::V4(net.addr())) - } - } - } -} - -/// A validated admin-scoped IPv6 multicast address. -/// -/// Admin-scoped addresses are ff04::/16, ff05::/16, or ff08::/16. These are -/// used for internal/underlay multicast groups. -#[derive( - Clone, - Copy, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Deserialize, - Serialize, - JsonSchema, -)] -#[serde(try_from = "Ipv6Addr", into = "Ipv6Addr")] -pub struct AdminScopedIpv6(Ipv6Addr); - -impl AdminScopedIpv6 { - /// Create a new AdminScopedIpv6 if the address is admin-local (ff04::/16). - pub fn new(addr: Ipv6Addr) -> Result { - if !Ipv6Net::new_unchecked(addr, 128).is_admin_local_multicast() { - return Err(format!( - "Address {} is not admin-local (must be ff04::/16)", - addr - )); - } - Ok(Self(addr)) - } -} - -impl TryFrom for AdminScopedIpv6 { - type Error = String; - - fn try_from(addr: Ipv6Addr) -> Result { - Self::new(addr) - } -} - -impl From for Ipv6Addr { - fn from(admin: AdminScopedIpv6) -> Self { - admin.0 - } -} - -impl From for IpAddr { - fn from(admin: AdminScopedIpv6) -> Self { - IpAddr::V6(admin.0) - } -} - -impl From for AdminScopedIpv6 { - fn from(underlay: UnderlayMulticastIpv6) -> Self { - // UnderlayMulticastIpv6 is a subset of AdminScopedIpv6, so this is safe - Self(underlay.into()) - } -} - -impl TryFrom for UnderlayMulticastIpv6 { - type Error = String; - - fn try_from(admin: AdminScopedIpv6) -> Result { - UnderlayMulticastIpv6::new(admin.0).map_err(|e| e.to_string()) - } -} - -impl fmt::Display for AdminScopedIpv6 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl FromStr for AdminScopedIpv6 { - type Err = String; - - fn from_str(s: &str) -> Result { - let addr: Ipv6Addr = - s.parse().map_err(|e| format!("invalid IPv6: {e}"))?; - Self::new(addr) - } -} - -// External multicast types (v1-v6, before IpSrc changed in v7) - -/// A multicast group configuration for POST requests for external (to the -/// rack) groups. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupCreateExternalEntry { - pub group_ip: IpAddr, - pub tag: Option, - pub internal_forwarding: InternalForwarding, - pub external_forwarding: ExternalForwarding, - pub sources: Option>, -} - -impl From - for dpd_types::mcast::MulticastGroupCreateExternalEntry -{ - fn from(entry: MulticastGroupCreateExternalEntry) -> Self { - Self { - group_ip: entry.group_ip, - tag: entry.tag, - internal_forwarding: entry.internal_forwarding, - external_forwarding: entry.external_forwarding, - sources: entry - .sources - .map(|s| s.into_iter().map(Into::into).collect()), - } - } -} - -/// A multicast group update entry for PUT requests for external (to the rack) -/// groups. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupUpdateExternalEntry { - pub tag: Option, - pub internal_forwarding: InternalForwarding, - pub external_forwarding: ExternalForwarding, - pub sources: Option>, -} - -impl From - for dpd_types::mcast::MulticastGroupUpdateExternalEntry -{ - fn from(entry: MulticastGroupUpdateExternalEntry) -> Self { - Self { - internal_forwarding: entry.internal_forwarding, - external_forwarding: entry.external_forwarding, - sources: entry - .sources - .map(|s| s.into_iter().map(Into::into).collect()), - } - } -} - -/// Response structure for external multicast group operations. These groups -/// handle IPv4 and non-admin IPv6 multicast via NAT targets. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupExternalResponse { - pub group_ip: IpAddr, - pub external_group_id: MulticastGroupId, - pub tag: Option, - pub internal_forwarding: InternalForwarding, - pub external_forwarding: ExternalForwarding, - pub sources: Option>, -} - -/// Convert from v7 response to v1 response. -impl From - for MulticastGroupExternalResponse -{ - fn from(resp: crate::v7::MulticastGroupExternalResponse) -> Self { - Self { - group_ip: resp.group_ip, - external_group_id: resp.external_group_id, - tag: resp.tag, - internal_forwarding: resp.internal_forwarding, - external_forwarding: resp.external_forwarding, - sources: resp - .sources - .map(|sources| sources.into_iter().map(IpSrc::from).collect()), - } - } -} - -/// Convert from latest response to v1 response (chains through v7). -impl From - for MulticastGroupExternalResponse -{ - fn from(resp: dpd_types::mcast::MulticastGroupExternalResponse) -> Self { - crate::v7::MulticastGroupExternalResponse::from(resp).into() - } -} - -// Underlay multicast types (v1-v7, before AdminScopedIpv6 changed in v8) - -/// Path parameter for underlay multicast group endpoints. -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct MulticastUnderlayGroupIpParam { - pub group_ip: AdminScopedIpv6, -} - -/// A multicast group configuration for POST requests for internal (to the -/// rack) groups. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupCreateUnderlayEntry { - pub group_ip: AdminScopedIpv6, - pub tag: Option, - pub members: Vec, -} - -/// Represents a multicast replication entry for PUT requests for internal -/// (to the rack) groups. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupUpdateUnderlayEntry { - pub tag: Option, - pub members: Vec, -} - -impl From - for dpd_types::mcast::MulticastGroupUpdateUnderlayEntry -{ - fn from(entry: MulticastGroupUpdateUnderlayEntry) -> Self { - Self { members: entry.members } - } -} - -/// Response structure for underlay/internal multicast group operations. These -/// groups handle admin-scoped IPv6 multicast with full replication. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupUnderlayResponse { - pub group_ip: AdminScopedIpv6, - pub external_group_id: MulticastGroupId, - pub underlay_group_id: MulticastGroupId, - pub tag: Option, - pub members: Vec, -} - -/// Convert from latest response to v1 response. -impl From - for MulticastGroupUnderlayResponse -{ - fn from(resp: dpd_types::mcast::MulticastGroupUnderlayResponse) -> Self { - Self { - group_ip: resp.group_ip.into(), - external_group_id: resp.external_group_id, - underlay_group_id: resp.underlay_group_id, - tag: Some(resp.tag), - members: resp.members, - } - } -} - -// Multicast unified response types - -/// Unified response type for operations that return mixed group types. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "kind", rename_all = "snake_case")] -pub enum MulticastGroupResponse { - Underlay(MulticastGroupUnderlayResponse), - External(MulticastGroupExternalResponse), -} - -impl MulticastGroupResponse { - /// Get the multicast group IP address. - pub fn ip(&self) -> IpAddr { - match self { - Self::Underlay(resp) => resp.group_ip.into(), - Self::External(resp) => resp.group_ip, - } - } -} - -/// Convert from v7 response to v1 response. -impl From for MulticastGroupResponse { - fn from(resp: crate::v7::MulticastGroupResponse) -> Self { - match resp { - crate::v7::MulticastGroupResponse::Underlay(u) => { - // v7 underlay is re-exported from v1, so it's the same type - Self::Underlay(u) - } - crate::v7::MulticastGroupResponse::External(e) => { - Self::External(e.into()) - } - } - } -} - -/// Convert from latest response to v1 response (chains through v7). -impl From for MulticastGroupResponse { - fn from(resp: dpd_types::mcast::MulticastGroupResponse) -> Self { - crate::v7::MulticastGroupResponse::from(resp).into() - } -} diff --git a/dpd-api/src/v2.rs b/dpd-api/src/v2.rs deleted file mode 100644 index 73f99fce..00000000 --- a/dpd-api/src/v2.rs +++ /dev/null @@ -1,41 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/ -// -// Copyright 2026 Oxide Computer Company - -use std::net::Ipv4Addr; - -use common::ports::PortId; -use dpd_types::link::LinkId; -use dpd_types::route::Ipv4Route; -use oxnet::Ipv4Net; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -/// Represents a new or replacement mapping of a subnet to a single IPv4 -/// RouteTarget nexthop target. -#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] -pub struct Ipv4RouteUpdate { - /// Traffic destined for any address within the CIDR block is routed using - /// this information. - pub cidr: Ipv4Net, - /// A single Route associated with this CIDR - pub target: Ipv4Route, - /// Should this route replace any existing route? If a route exists and - /// this parameter is false, then the call will fail. - pub replace: bool, -} - -/// Represents a single subnet->target route entry -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct RouteTargetIpv4Path { - /// The subnet being routed - pub cidr: Ipv4Net, - /// The switch port to which packets should be sent - pub port_id: PortId, - /// The link to which packets should be sent - pub link_id: LinkId, - /// The next hop in the IPv4 route - pub tgt_ip: Ipv4Addr, -} diff --git a/dpd-api/src/v7.rs b/dpd-api/src/v7.rs deleted file mode 100644 index 3162c669..00000000 --- a/dpd-api/src/v7.rs +++ /dev/null @@ -1,104 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/ -// -// Copyright 2026 Oxide Computer Company - -//! Types from API version 7 (MCAST_SOURCE_FILTER_ANY) that changed in -//! version 8 (MCAST_STRICT_UNDERLAY). -//! -//! Changed `IpSrc` from `{Exact, Subnet}` to `{Exact, Any}`. -//! External multicast types use the new `IpSrc`. - -use std::net::IpAddr; - -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use dpd_types::mcast::{ - ExternalForwarding, InternalForwarding, IpSrc, MulticastGroupId, -}; - -// External multicast types changed in v7 (use new IpSrc with Any variant) - -/// Response structure for external multicast group operations. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupExternalResponse { - pub group_ip: IpAddr, - pub external_group_id: MulticastGroupId, - pub tag: Option, - pub internal_forwarding: InternalForwarding, - pub external_forwarding: ExternalForwarding, - pub sources: Option>, -} - -/// Convert from latest response to v7 response. -impl From - for MulticastGroupExternalResponse -{ - fn from(resp: dpd_types::mcast::MulticastGroupExternalResponse) -> Self { - Self { - group_ip: resp.group_ip, - external_group_id: resp.external_group_id, - tag: Some(resp.tag), - internal_forwarding: resp.internal_forwarding, - external_forwarding: resp.external_forwarding, - sources: resp.sources, - } - } -} - -/// A multicast group update entry for PUT requests for external groups. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupUpdateExternalEntry { - pub tag: Option, - pub internal_forwarding: InternalForwarding, - pub external_forwarding: ExternalForwarding, - pub sources: Option>, -} - -impl From - for dpd_types::mcast::MulticastGroupUpdateExternalEntry -{ - fn from(entry: MulticastGroupUpdateExternalEntry) -> Self { - Self { - internal_forwarding: entry.internal_forwarding, - external_forwarding: entry.external_forwarding, - sources: entry.sources, - } - } -} - -// Multicast unified response types - -/// Unified response type for operations that return mixed group types. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "kind", rename_all = "snake_case")] -pub enum MulticastGroupResponse { - Underlay(crate::v1::MulticastGroupUnderlayResponse), - External(MulticastGroupExternalResponse), -} - -impl MulticastGroupResponse { - /// Get the multicast group IP address. - pub fn ip(&self) -> IpAddr { - match self { - Self::Underlay(resp) => resp.group_ip.into(), - Self::External(resp) => resp.group_ip, - } - } -} - -/// Convert from latest response to v7 response. -impl From for MulticastGroupResponse { - fn from(resp: dpd_types::mcast::MulticastGroupResponse) -> Self { - match resp { - dpd_types::mcast::MulticastGroupResponse::Underlay(u) => { - Self::Underlay(u.into()) - } - dpd_types::mcast::MulticastGroupResponse::External(e) => { - Self::External(e.into()) - } - } - } -} diff --git a/dpd-types/Cargo.toml b/dpd-types/Cargo.toml index 6349a92f..d663dd7f 100644 --- a/dpd-types/Cargo.toml +++ b/dpd-types/Cargo.toml @@ -4,13 +4,8 @@ version = "0.1.0" edition = "2024" [dependencies] -aal.workspace = true chrono.workspace = true -common.workspace = true +dpd-types-versions.workspace = true omicron-common.workspace = true -oxnet.workspace = true schemars.workspace = true serde.workspace = true -thiserror.workspace = true -transceiver-controller = { workspace = true, features = ["api-traits"] } -uuid.workspace = true diff --git a/dpd-types/src/arp.rs b/dpd-types/src/arp.rs new file mode 100644 index 00000000..b97e1dae --- /dev/null +++ b/dpd-types/src/arp.rs @@ -0,0 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +pub use dpd_types_versions::latest::arp::*; diff --git a/dpd-types/src/counters.rs b/dpd-types/src/counters.rs new file mode 100644 index 00000000..1435775e --- /dev/null +++ b/dpd-types/src/counters.rs @@ -0,0 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +pub use dpd_types_versions::latest::counters::*; diff --git a/dpd-types/src/fault.rs b/dpd-types/src/fault.rs index 40267157..8b7cfca5 100644 --- a/dpd-types/src/fault.rs +++ b/dpd-types/src/fault.rs @@ -2,17 +2,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -/// A Fault represents a specific kind of failure, and carries some additional -/// context. Currently Faults are only used to describe Link failures, but -/// there is no reason they couldn't be used elsewhere. -#[derive(Clone, Debug, PartialEq, Deserialize, JsonSchema, Serialize)] -pub enum Fault { - LinkFlap(String), - Autoneg(String), - Injected(String), -} +pub use dpd_types_versions::latest::fault::*; diff --git a/dpd-types/src/lib.rs b/dpd-types/src/lib.rs index 79e6809a..055ab969 100644 --- a/dpd-types/src/lib.rs +++ b/dpd-types/src/lib.rs @@ -2,15 +2,23 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company +pub mod arp; +pub mod counters; pub mod fault; pub mod link; +pub mod loopback; pub mod mcast; +pub mod misc; +pub mod nat; pub mod oxstats; +pub mod port; pub mod port_map; pub mod route; +pub mod serdes; +pub mod snapshot; pub mod switch_identifiers; pub mod switch_port; +pub mod table; pub mod transceivers; -pub mod views; diff --git a/dpd-types/src/link.rs b/dpd-types/src/link.rs index 5efccf4a..601a37d1 100644 --- a/dpd-types/src/link.rs +++ b/dpd-types/src/link.rs @@ -4,152 +4,4 @@ // // Copyright 2026 Oxide Computer Company -use std::{fmt, str::FromStr}; - -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::fault::Fault; - -/// An identifier for a link within a switch port. -/// -/// A switch port identified by a [`PortId`] may have multiple links within it, -/// each identified by a `LinkId`. These are unique within a switch port only. -#[derive( - Clone, - Copy, - Debug, - Deserialize, - Eq, - Hash, - JsonSchema, - Ord, - PartialEq, - PartialOrd, - Serialize, -)] -pub struct LinkId(pub u8); - -impl From for u8 { - fn from(l: LinkId) -> Self { - l.0 - } -} - -impl From for u16 { - fn from(l: LinkId) -> Self { - l.0 as u16 - } -} - -impl From for LinkId { - fn from(x: u8) -> Self { - Self(x) - } -} - -impl fmt::Display for LinkId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl FromStr for LinkId { - type Err = std::num::ParseIntError; - - fn from_str(s: &str) -> Result { - s.parse::().map(LinkId) - } -} - -/// The state of a data link with a peer. -#[derive(Clone, Deserialize, JsonSchema, PartialEq, Serialize)] -#[serde(rename_all = "snake_case")] -pub enum LinkState { - /// An error was encountered while trying to configure the link in the - /// switch hardware. - ConfigError(String), - /// The link is up. - Up, - /// The link is down. - Down, - /// The Link is offline due to a fault - Faulted(Fault), - /// The link's state is not known. - Unknown, -} - -impl LinkState { - /// A shortcut to tell whether a LinkState is Faulted or not, allowing for - /// cleaner code in the callers. - pub fn is_fault(&self) -> bool { - matches!(self, LinkState::Faulted(_)) - } - - /// If the link is in a faulted state, return the Fault. If not, return - /// None. - pub fn get_fault(&self) -> Option { - match self { - LinkState::Faulted(f) => Some(f.clone()), - _ => None, - } - } -} - -impl std::fmt::Display for LinkState { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - LinkState::Up => write!(f, "Up"), - LinkState::Down => write!(f, "Down"), - LinkState::ConfigError(_) => write!(f, "ConfigError"), - LinkState::Faulted(_) => write!(f, "Faulted"), - LinkState::Unknown => write!(f, "Unknown"), - } - } -} - -impl std::fmt::Debug for LinkState { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - LinkState::Up => write!(f, "Up"), - LinkState::Down => write!(f, "Down"), - LinkState::ConfigError(detail) => { - write!(f, "ConfigError - {detail:?}") - } - LinkState::Faulted(reason) => write!(f, "Faulted - {reason:?}"), - LinkState::Unknown => write!(f, "Unknown"), - } - } -} - -/// Reports how many times a link has transitioned from Down to Up. -#[derive(Clone, Deserialize, JsonSchema, PartialEq, Serialize)] -pub struct LinkUpCounter { - /// Link being reported - pub link_path: String, - /// LinkUp transitions since the link was last enabled - pub current: u32, - /// LinkUp transitions since the link was created - pub total: u32, -} - -/// Reports how many times a given autoneg/link-training state has been entered -#[derive(Clone, Deserialize, JsonSchema, PartialEq, Serialize)] -pub struct LinkFsmCounter { - /// FSM state being counted - pub state_name: String, - /// Times entered since the link was last enabled - pub current: u32, - /// Times entered since the link was created - pub total: u32, -} - -/// Reports all the autoneg/link-training states a link has transitioned into. -#[derive(Clone, Deserialize, JsonSchema, PartialEq, Serialize)] -pub struct LinkFsmCounters { - /// Link being reported - pub link_path: String, - /// All the states this link has entered, along with counts of how many - /// times each state was entered. - pub counters: Vec, -} +pub use dpd_types_versions::latest::link::*; diff --git a/dpd-types/src/loopback.rs b/dpd-types/src/loopback.rs new file mode 100644 index 00000000..e6c46306 --- /dev/null +++ b/dpd-types/src/loopback.rs @@ -0,0 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +pub use dpd_types_versions::latest::loopback::*; diff --git a/dpd-types/src/mcast.rs b/dpd-types/src/mcast.rs index 36e5c462..57b25191 100644 --- a/dpd-types/src/mcast.rs +++ b/dpd-types/src/mcast.rs @@ -6,261 +6,4 @@ //! Public types for multicast group management. -use std::{ - fmt, - net::{IpAddr, Ipv6Addr}, - str::FromStr, -}; - -use common::{network::NatTarget, ports::PortId}; -use omicron_common::address::UNDERLAY_MULTICAST_SUBNET; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::link::LinkId; - -/// Type alias for multicast group IDs. -pub type MulticastGroupId = u16; - -/// A validated underlay multicast IPv6 address. -/// -/// Underlay multicast addresses must be within the subnet allocated by Omicron -/// for rack-internal multicast traffic (ff04::/64). This is a subset of the -/// admin-local scope (ff04::/16) defined in RFC 4291. -#[derive( - Clone, - Copy, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Deserialize, - Serialize, - JsonSchema, -)] -#[serde(try_from = "Ipv6Addr", into = "Ipv6Addr")] -pub struct UnderlayMulticastIpv6(Ipv6Addr); - -impl UnderlayMulticastIpv6 { - /// Create a new UnderlayMulticastIpv6 if the address is within the - /// underlay multicast subnet (ff04::/64). - pub fn new(addr: Ipv6Addr) -> Result { - if !UNDERLAY_MULTICAST_SUBNET.contains(addr) { - return Err(Error::InvalidUnderlayMulticastIp(addr)); - } - Ok(Self(addr)) - } -} - -impl TryFrom for UnderlayMulticastIpv6 { - type Error = Error; - - fn try_from(addr: Ipv6Addr) -> Result { - Self::new(addr) - } -} - -impl From for Ipv6Addr { - fn from(addr: UnderlayMulticastIpv6) -> Self { - addr.0 - } -} - -impl From for IpAddr { - fn from(addr: UnderlayMulticastIpv6) -> Self { - IpAddr::V6(addr.0) - } -} - -impl fmt::Display for UnderlayMulticastIpv6 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl FromStr for UnderlayMulticastIpv6 { - type Err = Error; - - fn from_str(s: &str) -> Result { - let addr: Ipv6Addr = s - .parse() - .map_err(|e| Error::InvalidIpv6Address(s.to_string(), e))?; - Self::new(addr) - } -} - -/// Source filter match key for multicast traffic. -/// -/// For SSM groups, use `Exact` with specific source addresses. -/// For ASM groups with any-source filtering, use `Any`. -#[derive( - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Deserialize, - Serialize, - JsonSchema, -)] -pub enum IpSrc { - /// Exact match for the source IP address. - Exact(IpAddr), - /// Match any source address (0.0.0.0/0 or ::/0 depending on group IP version). - Any, -} - -impl fmt::Display for IpSrc { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - IpSrc::Exact(ip) => write!(f, "{ip}"), - IpSrc::Any => write!(f, "any"), - } - } -} - -/// A multicast group configuration for POST requests for internal (to the rack) -/// groups. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupCreateUnderlayEntry { - pub group_ip: UnderlayMulticastIpv6, - /// Tag for validating update/delete requests. If a tag is not provided, - /// one is auto-generated as `{uuid}:{group_ip}`. - pub tag: Option, - pub members: Vec, -} - -/// A multicast group configuration for POST requests for external (to the rack) -/// groups. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupCreateExternalEntry { - pub group_ip: IpAddr, - /// Tag for validating update/delete requests. If a tag is not provided, - /// one is auto-generated as `{uuid}:{group_ip}`. - pub tag: Option, - pub internal_forwarding: InternalForwarding, - pub external_forwarding: ExternalForwarding, - pub sources: Option>, -} - -/// Represents a multicast replication entry for PUT requests for internal -/// (to the rack) groups. -/// -/// Tag validation is performed via the `tag` query parameter. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupUpdateUnderlayEntry { - pub members: Vec, -} - -/// A multicast group update entry for PUT requests for external (to the rack) -/// groups. -/// -/// Tag validation is performed via the `tag` query parameter. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupUpdateExternalEntry { - pub internal_forwarding: InternalForwarding, - pub external_forwarding: ExternalForwarding, - pub sources: Option>, -} - -/// Response structure for underlay/internal multicast group operations. -/// These groups handle admin-local IPv6 multicast with full replication. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupUnderlayResponse { - pub group_ip: UnderlayMulticastIpv6, - pub external_group_id: MulticastGroupId, - pub underlay_group_id: MulticastGroupId, - /// Tag for validating update/delete requests. Always present and generated - /// as `{uuid}:{group_ip}` if not provided at creation time. - pub tag: String, - pub members: Vec, -} - -/// Response structure for external multicast group operations. -/// These groups handle IPv4 and non-admin-local IPv6 multicast via NAT targets. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupExternalResponse { - pub group_ip: IpAddr, - pub external_group_id: MulticastGroupId, - /// Tag for validating update/delete requests. Always present and generated - /// as `{uuid}:{group_ip}` if not provided at creation time. - pub tag: String, - pub internal_forwarding: InternalForwarding, - pub external_forwarding: ExternalForwarding, - pub sources: Option>, -} - -/// Unified response type for operations that return mixed group types. -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "kind", rename_all = "snake_case")] -pub enum MulticastGroupResponse { - Underlay(MulticastGroupUnderlayResponse), - External(MulticastGroupExternalResponse), -} - -impl MulticastGroupResponse { - /// Get the multicast group IP address. - pub fn ip(&self) -> IpAddr { - match self { - Self::Underlay(resp) => resp.group_ip.into(), - Self::External(resp) => resp.group_ip, - } - } - - /// Get the tag. - pub fn tag(&self) -> &str { - match self { - Self::Underlay(resp) => &resp.tag, - Self::External(resp) => &resp.tag, - } - } -} - -/// Represents the NAT target for multicast traffic for internal/underlay -/// forwarding. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] -pub struct InternalForwarding { - pub nat_target: Option, -} - -/// Represents the forwarding configuration for external multicast traffic. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] -pub struct ExternalForwarding { - pub vlan_id: Option, -} - -/// Represents a member of a multicast group. -#[derive( - Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema, -)] -pub struct MulticastGroupMember { - pub port_id: PortId, - pub link_id: LinkId, - pub direction: Direction, -} - -/// Direction a multicast group member is reached by. -/// -/// `External` group members must have any packet encapsulation removed -/// before packet delivery. -#[derive( - Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema, -)] -pub enum Direction { - Underlay, - External, -} - -#[derive(Clone, Debug, thiserror::Error)] -pub enum Error { - #[error( - "Address {0} is not in underlay multicast subnet (must be ff04::/64)" - )] - InvalidUnderlayMulticastIp(Ipv6Addr), - #[error("Invalid IPv6 address '{0}': {1}")] - InvalidIpv6Address(String, std::net::AddrParseError), -} +pub use dpd_types_versions::latest::mcast::*; diff --git a/dpd-types/src/misc.rs b/dpd-types/src/misc.rs new file mode 100644 index 00000000..9e226dc7 --- /dev/null +++ b/dpd-types/src/misc.rs @@ -0,0 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +pub use dpd_types_versions::latest::misc::*; diff --git a/dpd-types/src/nat.rs b/dpd-types/src/nat.rs new file mode 100644 index 00000000..183f516c --- /dev/null +++ b/dpd-types/src/nat.rs @@ -0,0 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +pub use dpd_types_versions::latest::nat::*; diff --git a/dpd-types/src/oxstats.rs b/dpd-types/src/oxstats.rs index e898a994..e33ae62d 100644 --- a/dpd-types/src/oxstats.rs +++ b/dpd-types/src/oxstats.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::net::Ipv6Addr; diff --git a/dpd-types/src/port.rs b/dpd-types/src/port.rs new file mode 100644 index 00000000..2cfae8fb --- /dev/null +++ b/dpd-types/src/port.rs @@ -0,0 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +pub use dpd_types_versions::latest::port::*; diff --git a/dpd-types/src/port_map.rs b/dpd-types/src/port_map.rs index 3d2dac3c..7386e862 100644 --- a/dpd-types/src/port_map.rs +++ b/dpd-types/src/port_map.rs @@ -2,176 +2,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company -use common::ports::RearPort; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -/// The Sidecar chassis connector mating the backplane and internal cabling. -/// -/// This describes the "group" of backplane links that all terminate in one -/// connector on the Sidecar itself. This is the connection point between a -/// cable on the backplane itself and the Sidecar chassis. -#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] -pub struct SidecarConnector(u8); - -impl From for u8 { - fn from(g: SidecarConnector) -> u8 { - g.as_u8() - } -} - -impl TryFrom for SidecarConnector { - type Error = Error; - - fn try_from(x: u8) -> Result { - if x > 7 { - return Err(Error::SidecarConnector(x)); - } - Ok(Self(x)) - } -} - -impl SidecarConnector { - /// Create a new backplane group. - pub fn new(x: u8) -> Result { - Self::try_from(x) - } - - /// Return the index of this group as an integer. - pub const fn as_u8(&self) -> u8 { - self.0 - } -} - -/// A single point-to-point connection on the cabled backplane. -/// -/// This describes a single link from the Sidecar switch to a cubby, via the -/// cabled backplane. It ultimately maps the Tofino ASIC pins to the cubby at -/// which that link terminates. This path follows the Sidecar internal cable; -/// the Sidecar chassis connector; and the backplane cable itself. This is used -/// to map the Tofino driver's "connector" number (an index in its possible -/// pinouts) through the backplane to our logical cubby numbering. -#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] -pub struct BackplaneLink { - // The internal Tofino driver connector number. - pub tofino_connector: u8, - // The leg label on the Sidecar-internal cable. - pub sidecar_leg: SidecarCableLeg, - // The Sidecar chassis connector. - pub sidecar_connector: SidecarConnector, - // The leg label on the cabled backplane. - pub backplane_leg: BackplaneCableLeg, - // The cubby at which the cable terminates. - pub cubby: u8, -} - -impl From for BackplaneLink { - fn from(p: RearPort) -> Self { - Self::from_cubby(p.as_u8()).unwrap() - } -} - -/// The leg of the backplane cable. -/// -/// This describes the leg on the actual backplane cable that connects the -/// Sidecar chassis connector to a cubby endpoint. -// NOTE: This is the connector on the cubby chassis end of the part -// HDR-222627-xx-EBCM. The `xx` describes the length of the cable. -#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] -pub enum BackplaneCableLeg { - A, - B, - C, - D, -} - -// Helper macro to make a backplane map entry. -macro_rules! bp_entry { - ( - $connector:literal, - $sidecar_leg:expr, - $sidecar_connector:literal, - $backplane_leg:expr, - $cubby:literal - ) => { - BackplaneLink { - tofino_connector: $connector, - sidecar_leg: $sidecar_leg, - sidecar_connector: SidecarConnector($sidecar_connector), - backplane_leg: $backplane_leg, - cubby: $cubby, - } - }; -} - -/// The leg of the Sidecar-internal cable. -/// -/// This describes the leg on the cabling that connects the pins on the Tofino -/// ASIC to the Sidecar chassis connector. -// NOTE: This is the connector on the Sidecar main board side of the part -// HDR-222623-01-EBCF. -#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] -pub enum SidecarCableLeg { - A, - C, -} - -pub const SIDECAR_REV_AB_BACKPLANE_MAP: [BackplaneLink; 32] = [ - bp_entry!(1, SidecarCableLeg::C, 0, BackplaneCableLeg::C, 29), - bp_entry!(2, SidecarCableLeg::C, 0, BackplaneCableLeg::D, 31), - bp_entry!(3, SidecarCableLeg::A, 0, BackplaneCableLeg::A, 25), - bp_entry!(4, SidecarCableLeg::A, 0, BackplaneCableLeg::B, 27), - bp_entry!(5, SidecarCableLeg::C, 1, BackplaneCableLeg::C, 21), - bp_entry!(6, SidecarCableLeg::C, 1, BackplaneCableLeg::D, 23), - bp_entry!(7, SidecarCableLeg::A, 1, BackplaneCableLeg::A, 17), - bp_entry!(8, SidecarCableLeg::A, 1, BackplaneCableLeg::B, 19), - bp_entry!(9, SidecarCableLeg::A, 2, BackplaneCableLeg::B, 11), - bp_entry!(10, SidecarCableLeg::A, 2, BackplaneCableLeg::A, 9), - bp_entry!(11, SidecarCableLeg::C, 2, BackplaneCableLeg::D, 15), - bp_entry!(12, SidecarCableLeg::C, 2, BackplaneCableLeg::C, 13), - bp_entry!(13, SidecarCableLeg::A, 3, BackplaneCableLeg::B, 3), - bp_entry!(14, SidecarCableLeg::A, 3, BackplaneCableLeg::A, 1), - bp_entry!(15, SidecarCableLeg::C, 3, BackplaneCableLeg::D, 7), - bp_entry!(16, SidecarCableLeg::C, 3, BackplaneCableLeg::C, 5), - bp_entry!(17, SidecarCableLeg::C, 4, BackplaneCableLeg::C, 28), - bp_entry!(18, SidecarCableLeg::C, 4, BackplaneCableLeg::D, 30), - bp_entry!(19, SidecarCableLeg::A, 4, BackplaneCableLeg::A, 24), - bp_entry!(20, SidecarCableLeg::A, 4, BackplaneCableLeg::B, 26), - bp_entry!(21, SidecarCableLeg::C, 5, BackplaneCableLeg::C, 20), - bp_entry!(22, SidecarCableLeg::C, 5, BackplaneCableLeg::D, 22), - bp_entry!(23, SidecarCableLeg::A, 5, BackplaneCableLeg::A, 16), - bp_entry!(24, SidecarCableLeg::A, 5, BackplaneCableLeg::B, 18), - bp_entry!(25, SidecarCableLeg::A, 6, BackplaneCableLeg::B, 10), - bp_entry!(26, SidecarCableLeg::A, 6, BackplaneCableLeg::A, 8), - bp_entry!(27, SidecarCableLeg::C, 6, BackplaneCableLeg::D, 14), - bp_entry!(28, SidecarCableLeg::C, 6, BackplaneCableLeg::C, 12), - bp_entry!(29, SidecarCableLeg::A, 7, BackplaneCableLeg::B, 2), - bp_entry!(30, SidecarCableLeg::A, 7, BackplaneCableLeg::A, 0), - bp_entry!(31, SidecarCableLeg::C, 7, BackplaneCableLeg::D, 6), - bp_entry!(32, SidecarCableLeg::C, 7, BackplaneCableLeg::C, 4), -]; - -impl BackplaneLink { - /// Construct a link from the cubby number. - pub fn from_cubby(cubby: u8) -> Result { - SIDECAR_REV_AB_BACKPLANE_MAP - .iter() - .find(|entry| entry.cubby == cubby) - .copied() - .ok_or(Error::Cubby(cubby)) - } -} - -#[derive(Clone, Debug, thiserror::Error)] -pub enum Error { - #[error("Invalid backplane group {0}, must be in [0, 7]")] - SidecarConnector(u8), - - #[error("Invalid cubby {0}, must be in [0, 31]")] - Cubby(u8), - - #[error("Invalid SoftNPU revision '{found}', expected '{expected}'")] - SoftNpuRevision { expected: String, found: String }, -} +pub use dpd_types_versions::latest::port_map::*; diff --git a/dpd-types/src/route.rs b/dpd-types/src/route.rs index f402b227..e4d402a0 100644 --- a/dpd-types/src/route.rs +++ b/dpd-types/src/route.rs @@ -4,118 +4,4 @@ // // Copyright 2026 Oxide Computer Company -use std::{ - fmt, - net::{Ipv4Addr, Ipv6Addr}, -}; - -use common::ports::PortId; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::link::LinkId; - -#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] -pub enum Route { - V4(Ipv4Route), - V6(Ipv6Route), -} - -/// A route for an IPv4 subnet. -#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] -pub struct Ipv4Route { - // The client-specific tag for this route. - pub tag: String, - // The switch port out which routed traffic is sent. - pub port_id: PortId, - // The link out which routed traffic is sent. - pub link_id: LinkId, - // Route traffic matching the subnet via this IP. - pub tgt_ip: Ipv4Addr, - // Tag traffic on this route with this vlan ID. - pub vlan_id: Option, -} - -// We implement PartialEq for Ipv4Route because we want to exclude the tag and -// vlan_id from any comparisons. We do this because the tag is a comment -// identifying the originator rather than a semantically meaningful part of the -// route. The vlan_id is used to modify the traffic on a specific route, rather -// then being part of the route itself. -impl PartialEq for Ipv4Route { - fn eq(&self, other: &Self) -> bool { - self.port_id == other.port_id - && self.link_id == other.link_id - && self.tgt_ip == other.tgt_ip - } -} - -// See the comment above PartialEq to understand why we implement Hash rather -// then Deriving it. -impl std::hash::Hash for Ipv4Route { - fn hash(&self, state: &mut H) - where - H: std::hash::Hasher, - { - self.port_id.hash(state); - self.link_id.hash(state); - self.tgt_ip.hash(state); - } -} - -impl fmt::Display for Ipv4Route { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "port: {} link: {} gw: {} vlan: {:?}", - self.port_id, self.link_id, self.tgt_ip, self.vlan_id - )?; - Ok(()) - } -} - -/// A route for an IPv6 subnet. -#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] -pub struct Ipv6Route { - // The client-specific tag for this route. - pub tag: String, - // The switch port out which routed traffic is sent. - pub port_id: PortId, - // The link out which routed traffic is sent. - pub link_id: LinkId, - // Route traffic matching the subnet to this IP. - pub tgt_ip: Ipv6Addr, - // Tag traffic on this route with this vlan ID. - pub vlan_id: Option, -} - -// See the comment above the PartialEq for IPv4Route -impl PartialEq for Ipv6Route { - fn eq(&self, other: &Self) -> bool { - self.port_id == other.port_id - && self.link_id == other.link_id - && self.tgt_ip == other.tgt_ip - } -} - -// See the comment above PartialEq for IPv4Route -impl std::hash::Hash for Ipv6Route { - fn hash(&self, state: &mut H) - where - H: std::hash::Hasher, - { - self.port_id.hash(state); - self.link_id.hash(state); - self.tgt_ip.hash(state); - } -} - -impl fmt::Display for Ipv6Route { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "port: {} link: {} gw: {} vlan: {:?}", - self.port_id, self.link_id, self.tgt_ip, self.vlan_id - )?; - Ok(()) - } -} +pub use dpd_types_versions::latest::route::*; diff --git a/dpd-types/src/serdes.rs b/dpd-types/src/serdes.rs new file mode 100644 index 00000000..d7f4e52c --- /dev/null +++ b/dpd-types/src/serdes.rs @@ -0,0 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +pub use dpd_types_versions::latest::serdes::*; diff --git a/dpd-types/src/snapshot.rs b/dpd-types/src/snapshot.rs new file mode 100644 index 00000000..20a10bf7 --- /dev/null +++ b/dpd-types/src/snapshot.rs @@ -0,0 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +pub use dpd_types_versions::latest::snapshot::*; diff --git a/dpd-types/src/switch_identifiers.rs b/dpd-types/src/switch_identifiers.rs index b448bf82..37bbee4c 100644 --- a/dpd-types/src/switch_identifiers.rs +++ b/dpd-types/src/switch_identifiers.rs @@ -4,44 +4,4 @@ // // Copyright 2026 Oxide Computer Company -use schemars::JsonSchema; -use serde::Serialize; -use uuid::Uuid; - -// Re-export fuse types from aal. -pub use aal::{ - ChipRevision, DisabledFeatures, FrequencySettings, FuseData, - ManufacturingData, PartInfo, -}; - -/// Identifiers for a switch. -#[derive(Clone, Debug, JsonSchema, Serialize)] -pub struct SwitchIdentifiers { - /// Unique identifier for the chip. - pub sidecar_id: Uuid, - /// Asic backend (compiler target) responsible for these identifiers. - pub asic_backend: String, - /// Fabrication plant identifier. - pub fab: Option, - /// Lot identifier. - pub lot: Option, - /// Lot number (4-character identifier within the lot). - pub lotnum: Option<[char; 4]>, - /// Wafer number within the lot. - pub wafer: Option, - /// The wafer location as (x, y) coordinates on the wafer, represented as - /// an array due to the lack of tuple support in OpenAPI. - pub wafer_loc: Option<[i16; 2]>, - /// The model number of the switch being managed. - pub model: String, - /// The revision number of the switch being managed. - pub revision: u32, - /// The serial number of the switch being managed. - pub serial: String, - /// The slot number of the switch being managed. - /// - /// MGS uses u16 for this internally. - pub slot: u16, - /// Fuse data from the ASIC, if available. - pub fuse: Option, -} +pub use dpd_types_versions::latest::switch_identifiers::*; diff --git a/dpd-types/src/switch_port.rs b/dpd-types/src/switch_port.rs index 09c7ab46..afecea66 100644 --- a/dpd-types/src/switch_port.rs +++ b/dpd-types/src/switch_port.rs @@ -2,91 +2,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use transceiver_controller::message::LedState; - -/// How a switch port is managed. -/// -/// The free-side devices in QSFP ports are complex devices, whose operation -/// usually involves coordinated steps through one or more state machines. For -/// example, when bringing up an optical link, a signal from the peer link must -/// be detected; then a signal recovered; equalizer gains set; etc. In -/// `Automatic` mode, all these kinds of steps are managed autonomously by -/// switch driver software. In `Manual` mode, none of these will occur -- a -/// switch port will only change in response to explicit requests from the -/// operator or Oxide control plane. -// -// NOTE: This is the parameter which marks a switch port _visible_ to the BF -// SDE. `Manual` means under our control, `Automatic` means visible to the SDE -// and under its control. -#[derive( - Clone, - Copy, - Debug, - Deserialize, - Eq, - Hash, - JsonSchema, - Ord, - PartialEq, - PartialOrd, - Serialize, -)] -#[serde(rename_all = "snake_case")] -pub enum ManagementMode { - /// A port is managed manually, by either the Oxide control plane or an - /// operator. - Manual, - /// A port is managed automatically by the switch software. - Automatic, -} - -/// The policy by which a port's LED is controlled. -#[derive( - Clone, - Copy, - Debug, - Deserialize, - Eq, - Hash, - JsonSchema, - Ord, - PartialEq, - PartialOrd, - Serialize, -)] -#[serde(rename_all = "snake_case")] -pub enum LedPolicy { - /// The default policy is for the LED to reflect the port's state itself. - /// - /// If the port is operating normally, the LED will be solid on. Without a - /// transceiver, the LED will be solid off. A blinking LED is used to - /// indicate an unsupported module or other failure on that port. - Automatic, - /// The LED is explicitly overridden by client requests. - Override, -} - -/// Information about a QSFP port's LED. -#[derive( - Clone, - Copy, - Debug, - Deserialize, - Eq, - Hash, - JsonSchema, - Ord, - PartialEq, - PartialOrd, - Serialize, -)] -pub struct Led { - /// The policy by which the LED is controlled. - pub policy: LedPolicy, - /// The state of the LED. - pub state: LedState, -} +pub use dpd_types_versions::latest::switch_port::*; diff --git a/dpd-types/src/table.rs b/dpd-types/src/table.rs new file mode 100644 index 00000000..d71362c9 --- /dev/null +++ b/dpd-types/src/table.rs @@ -0,0 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +pub use dpd_types_versions::latest::table::*; diff --git a/dpd-types/src/transceivers.rs b/dpd-types/src/transceivers.rs index 8060f7d2..b0995c6e 100644 --- a/dpd-types/src/transceivers.rs +++ b/dpd-types/src/transceivers.rs @@ -4,124 +4,4 @@ // // Copyright 2026 Oxide Computer Company -use std::time::Instant; - -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use transceiver_controller::{PowerMode, VendorInfo}; - -use crate::switch_port::ManagementMode; - -/// A QSFP switch port. -/// -/// This includes the hardware controls and information relevant to QSFP ports -/// specifically. For example, these ports are on the front IO panel of the -/// switch, and have LEDs used for status and attention. This includes the state -/// and controls for those LEDs. It also includes information about the -/// free-side QSFP module, should one be plugged in. -#[derive(Clone, Debug, JsonSchema, Serialize)] -pub struct QsfpDevice { - /// Details about a transceiver module inserted into the switch port. - /// - /// If there is no transceiver at all, this will be `None`. - pub transceiver: Option, - /// How the QSFP device is managed. - /// - /// See `ManagementMode` for details. - pub management_mode: ManagementMode, -} - -impl Default for QsfpDevice { - fn default() -> Self { - Self { transceiver: None, management_mode: ManagementMode::Automatic } - } -} - -/// The cause of a fault on a transceiver. -#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, Serialize)] -#[serde(rename_all = "snake_case")] -pub enum FaultReason { - /// An error occurred accessing the transceiver. - Failed, - /// Power was enabled, but did not come up in the requisite time. - PowerTimeout, - /// Power was enabled and later lost. - PowerLost, - /// The service processor disabled the transceiver. - /// - /// The SP is responsible for monitoring the thermal data from the - /// transceivers, and controlling the fans to compensate. If a module's - /// thermal data cannot be read, the SP may completely disable the - /// transceiver to ensure it cannot overheat the Sidecar. - DisabledBySp, -} - -/// The state of a transceiver in a QSFP switch port. -#[derive(Clone, Debug, JsonSchema, Serialize)] -#[serde(rename_all = "snake_case", tag = "state", content = "info")] -pub enum Transceiver { - /// The transceiver could not be managed due to a power fault. - Faulted(FaultReason), - /// A transceiver was present, but unsupported and automatically disabled. - Unsupported, - /// A transceiver is present and supported. - Supported(TransceiverInfo), -} - -/// Information about a QSFP transceiver. -/// -/// This stores the most relevant information about a transceiver module, such -/// as vendor info or power. Each field may be missing, indicating it could not -/// be determined. -#[derive(Clone, Debug, JsonSchema, Serialize)] -pub struct TransceiverInfo { - /// Vendor and part identifying information. - /// - /// The information will not be populated if it could not be read. - pub vendor_info: Option, - /// True if the module is currently in reset. - pub in_reset: Option, - /// True if there is a pending interrupt on the module. - pub interrupt_pending: Option, - /// The power mode of the transceiver. - pub power_mode: Option, - /// The electrical mode of the transceiver. - /// - /// See [`ElectricalMode`] for details. - pub electrical_mode: ElectricalMode, - // The instant at which we first saw this transceiver. - // - // This is only used to support initially blinking the transceiver to - // acknowledge insertion. - #[serde(skip)] - pub first_seen: Instant, -} - -impl Default for TransceiverInfo { - fn default() -> Self { - Self { - vendor_info: None, - in_reset: None, - interrupt_pending: None, - power_mode: None, - electrical_mode: ElectricalMode::Single, - first_seen: Instant::now(), - } - } -} - -/// The electrical mode of a QSFP-capable port. -/// -/// QSFP ports can be broken out into one of several different electrical -/// configurations or modes. This describes how the transmit/receive lanes are -/// grouped into a single, logical link. -/// -/// Note that the electrical mode may only be changed if there are no links -/// within the port, _and_ if the inserted QSFP module actually supports this -/// mode. -#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema, Serialize)] -pub enum ElectricalMode { - /// All transmit/receive lanes are used for a single link. - #[default] - Single, -} +pub use dpd_types_versions::latest::transceivers::*; diff --git a/dpd-types/src/views.rs b/dpd-types/src/views.rs deleted file mode 100644 index 03987109..00000000 --- a/dpd-types/src/views.rs +++ /dev/null @@ -1,184 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/ -// -// Copyright 2026 Oxide Computer Company - -//! Public API view types, exposing the internal Dendrite data in a manner -//! suitable for API clients. - -use std::{collections::BTreeMap, net::Ipv6Addr}; - -use common::{ - network::MacAddr, - ports::{PortFec, PortId, PortMedia, PortPrbsMode, PortSpeed}, -}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::{ - link::{LinkId, LinkState}, - transceivers::QsfpDevice, -}; - -/// A physical port on the Sidecar switch. -// -// NOTE: This is the public API view onto `types::SwitchPort`. -#[derive(Clone, Debug, JsonSchema, Serialize)] -pub struct SwitchPort { - /// The identifier for the switch port. - pub port_id: PortId, - /// Information for QSFP port functionality. This will be empty for non-QSFP - /// switch ports. - #[serde(flatten, skip_serializing_if = "Option::is_none")] - pub qsfp_device: Option, -} - -/// An Ethernet-capable link within a switch port. -// -// NOTE: This is a view onto `crate::link::Link`. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct Link { - /// The switch port on which this link exists. - pub port_id: PortId, - /// The `LinkId` within the switch port for this link. - pub link_id: LinkId, - /// The Tofino connector number associated with this link. - pub tofino_connector: u16, - /// The lower-level ASIC ID used to refer to this object in the switch - /// driver software. - pub asic_id: u16, - /// True if the transceiver module has detected a media presence. - pub presence: bool, - /// True if this link is in KR mode, i.e., is on a cabled backplane. - pub kr: bool, - /// True if this link is configured to autonegotiate with its peer. - pub autoneg: bool, - /// Current state in the autonegotiation/link-training finite state machine - pub fsm_state: String, - /// The speed of the link. - pub speed: PortSpeed, - /// The error-correction scheme for this link. - pub fec: Option, - /// The physical media underlying this link. - pub media: PortMedia, - /// True if this link is enabled. - pub enabled: bool, - /// The PRBS mode. - pub prbs: PortPrbsMode, - /// The state of the Ethernet link. - pub link_state: LinkState, - /// The MAC address for the link. - pub address: MacAddr, - /// The link is configured for IPv6 use - pub ipv6_enabled: bool, -} - -impl std::fmt::Display for Link { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}/{}", self.port_id, self.link_id) - } -} - -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct LinkEvent { - /// Time the event occurred. The time is represented in milliseconds, - /// starting at an undefined time in the past. This means that timestamps - /// can be used to measure the time between events, but not to determine the - /// wall-clock time at which the event occurred. - pub timestamp: i64, - /// Channel ID for sub-link-level events - pub channel: Option, - /// Event class - pub class: String, - /// Event subclass - pub subclass: String, - /// Optionally, additional details about the event - pub details: Option, -} - -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct LinkHistory { - /// The wallclock time in milliseconds at which this history was collected. - pub timestamp: i64, - /// The timestamp in milliseconds at which this history was collected, - /// relative to the time the switch management daemon started. - pub relative: i64, - /// The set of historical events recorded - pub events: Vec, -} - -/// The per-link data consumed by tfportd -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct TfportData { - /// The switch port ID for this link. - pub port_id: PortId, - /// The link ID for this link. - pub link_id: LinkId, - /// The lower-level ASIC ID used to refer to this object in the switch - /// driver software. - pub asic_id: u16, - /// The MAC address for the link. - pub mac: MacAddr, - /// Is ipv6 enabled for this link - pub ipv6_enabled: bool, - /// The IPv6 link-local address of the link, if it exists. - pub link_local: Option, -} - -/// Each entry in a P4 table is addressed by matching against a set of key -/// values. If an entry is found, an action is taken with an action-specific -/// set of arguments. -/// -/// Note: each entry will have the same key fields and each instance of any -/// given action will have the same argument names, so a vector of TableEntry -/// structs will contain a signficant amount of redundant data. We could -/// consider tightening this up by including a schema of sorts in the "struct -/// Table". -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct TableEntry { - /// Names and values of each of the key fields. - pub keys: BTreeMap, - /// Name of the action to take on a match - pub action: String, - /// Names and values for the arguments to the action implementation. - pub action_args: BTreeMap, -} - -impl TableEntry { - pub fn new( - key: impl aal::MatchParse, - action: impl aal::ActionParse, - ) -> Self { - TableEntry { - keys: key.key_values(), - action: action.action_name(), - action_args: action.action_args(), - } - } -} - -/// Represents the contents of a P4 table -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct Table { - /// A user-friendly name for the table - pub name: String, - /// The maximum number of entries the table can hold - pub size: usize, - /// There will be an entry for each populated slot in the table - pub entries: Vec, -} - -#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] -pub struct TableCounterEntry { - /// Names and values of each of the key fields. - pub keys: BTreeMap, - /// Counter values - pub data: aal::CounterData, -} - -impl TableCounterEntry { - pub fn new(key: impl aal::MatchParse, data: aal::CounterData) -> Self { - TableCounterEntry { keys: key.key_values(), data } - } -} diff --git a/dpd-types/versions/Cargo.toml b/dpd-types/versions/Cargo.toml new file mode 100644 index 00000000..c87edc53 --- /dev/null +++ b/dpd-types/versions/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "dpd-types-versions" +version = "0.1.0" +edition = "2024" + +[dependencies] +aal.workspace = true +common.workspace = true +dropshot.workspace = true +omicron-common.workspace = true +oxnet.workspace = true +schemars.workspace = true +serde.workspace = true +thiserror.workspace = true +transceiver-controller = { workspace = true, features = ["api-traits"] } +uuid.workspace = true diff --git a/dpd-types/versions/src/asic_details/mod.rs b/dpd-types/versions/src/asic_details/mod.rs new file mode 100644 index 00000000..a5f40459 --- /dev/null +++ b/dpd-types/versions/src/asic_details/mod.rs @@ -0,0 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Version `ASIC_DETAILS` of the DPD API. +//! +//! Added `lotnum` and `fuse` fields to `SwitchIdentifiers`. + +pub mod switch_identifiers; diff --git a/dpd-types/versions/src/asic_details/switch_identifiers.rs b/dpd-types/versions/src/asic_details/switch_identifiers.rs new file mode 100644 index 00000000..b3e53b88 --- /dev/null +++ b/dpd-types/versions/src/asic_details/switch_identifiers.rs @@ -0,0 +1,61 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use schemars::JsonSchema; +use serde::Serialize; +use uuid::Uuid; + +use crate::v1; +use crate::v1::switch_identifiers::FuseData; + +/// Identifiers for a switch. +#[derive(Clone, Debug, JsonSchema, Serialize)] +pub struct SwitchIdentifiers { + /// Unique identifier for the chip. + pub sidecar_id: Uuid, + /// Asic backend (compiler target) responsible for these identifiers. + pub asic_backend: String, + /// Fabrication plant identifier. + pub fab: Option, + /// Lot identifier. + pub lot: Option, + /// Lot number (4-character identifier within the lot). + pub lotnum: Option<[char; 4]>, + /// Wafer number within the lot. + pub wafer: Option, + /// The wafer location as (x, y) coordinates on the wafer, represented as + /// an array due to the lack of tuple support in OpenAPI. + pub wafer_loc: Option<[i16; 2]>, + /// The model number of the switch being managed. + pub model: String, + /// The revision number of the switch being managed. + pub revision: u32, + /// The serial number of the switch being managed. + pub serial: String, + /// The slot number of the switch being managed. + /// + /// MGS uses u16 for this internally. + pub slot: u16, + /// Fuse data from the ASIC, if available. + pub fuse: Option, +} + +impl From for v1::switch_identifiers::SwitchIdentifiers { + fn from(latest: SwitchIdentifiers) -> Self { + Self { + sidecar_id: latest.sidecar_id, + asic_backend: latest.asic_backend, + fab: latest.fab, + lot: latest.lot, + wafer: latest.wafer, + wafer_loc: latest.wafer_loc, + model: latest.model, + revision: latest.revision, + serial: latest.serial, + slot: latest.slot, + } + } +} diff --git a/dpd-types/versions/src/attached_subnets/mod.rs b/dpd-types/versions/src/attached_subnets/mod.rs new file mode 100644 index 00000000..3b32f9a4 --- /dev/null +++ b/dpd-types/versions/src/attached_subnets/mod.rs @@ -0,0 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Version `ATTACHED_SUBNETS` of the DPD API. +//! +//! Added attached subnet management endpoints with `SubnetPath` and +//! `AttachedSubnetToken`. + +pub mod route; diff --git a/dpd-types/versions/src/attached_subnets/route.rs b/dpd-types/versions/src/attached_subnets/route.rs new file mode 100644 index 00000000..33748c10 --- /dev/null +++ b/dpd-types/versions/src/attached_subnets/route.rs @@ -0,0 +1,24 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use oxnet::IpNet; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct SubnetPath { + /// The external subnet in CIDR notation being managed + pub subnet: IpNet, +} + +/** + * Represents a cursor into a paginated request for the contents of the + * external subnets table. + */ +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct AttachedSubnetToken { + pub cidr: IpNet, +} diff --git a/dpd-types/versions/src/consolidated_v4_routes/mod.rs b/dpd-types/versions/src/consolidated_v4_routes/mod.rs new file mode 100644 index 00000000..3caf9bf5 --- /dev/null +++ b/dpd-types/versions/src/consolidated_v4_routes/mod.rs @@ -0,0 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Version `CONSOLIDATED_V4_ROUTES` of the DPD API. +//! +//! Changed `Ipv4RouteUpdate` to accept `RouteTarget` (IPv4 or IPv6) instead of +//! just `Ipv4Route`, and changed `RouteTargetIpv4Path` to use `IpAddr` for +//! `tgt_ip` instead of `Ipv4Addr`. + +pub mod route; diff --git a/dpd-types/versions/src/consolidated_v4_routes/route.rs b/dpd-types/versions/src/consolidated_v4_routes/route.rs new file mode 100644 index 00000000..55dabec9 --- /dev/null +++ b/dpd-types/versions/src/consolidated_v4_routes/route.rs @@ -0,0 +1,75 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use std::net::IpAddr; + +use crate::v1; +use crate::v1::link::LinkId; +use crate::v4; +use crate::v4::route::RouteTarget; +use common::ports::PortId; +use oxnet::Ipv4Net; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Represents a new or replacement mapping of an IPv4 subnet to a single +/// RouteTarget nexthop target, which may be either IPv4 or IPv6. +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +pub struct Ipv4RouteUpdate { + /// Traffic destined for any address within the CIDR block is routed using + /// this information. + pub cidr: Ipv4Net, + /// A single Route associated with this CIDR + pub target: RouteTarget, + /// Should this route replace any existing route? If a route exists and + /// this parameter is false, then the call will fail. + pub replace: bool, +} + +impl From for Ipv4RouteUpdate { + fn from(old: v1::route::Ipv4RouteUpdate) -> Self { + Self { + cidr: old.cidr, + target: RouteTarget::V4(old.target), + replace: old.replace, + } + } +} + +impl From for Ipv4RouteUpdate { + fn from(old: v4::route::Ipv4OverIpv6RouteUpdate) -> Self { + Self { + cidr: old.cidr, + target: RouteTarget::V6(old.target), + replace: old.replace, + } + } +} + +/// Represents a single subnet->target route entry with an IPv4 or IPv6 +/// next hop. +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct RouteTargetIpv4Path { + /// The subnet being routed + pub cidr: Ipv4Net, + /// The switch port to which packets should be sent + pub port_id: PortId, + /// The link to which packets should be sent + pub link_id: LinkId, + /// The next hop in the route (IPv4 or IPv6) + pub tgt_ip: IpAddr, +} + +impl From for RouteTargetIpv4Path { + fn from(old: v1::route::RouteTargetIpv4Path) -> Self { + Self { + cidr: old.cidr, + port_id: old.port_id, + link_id: old.link_id, + tgt_ip: IpAddr::V4(old.tgt_ip), + } + } +} diff --git a/dpd-types/versions/src/impls/link.rs b/dpd-types/versions/src/impls/link.rs new file mode 100644 index 00000000..cac943f2 --- /dev/null +++ b/dpd-types/versions/src/impls/link.rs @@ -0,0 +1,77 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use std::{fmt, str::FromStr}; + +use crate::latest::fault::Fault; +use crate::latest::link::{LinkId, LinkState, LinkView}; + +impl From for u8 { + fn from(l: LinkId) -> Self { + l.0 + } +} + +impl From for u16 { + fn from(l: LinkId) -> Self { + l.0 as u16 + } +} + +impl From for LinkId { + fn from(x: u8) -> Self { + Self(x) + } +} + +impl fmt::Display for LinkId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl FromStr for LinkId { + type Err = std::num::ParseIntError; + + fn from_str(s: &str) -> Result { + s.parse::().map(LinkId) + } +} + +impl LinkState { + /// A shortcut to tell whether a LinkState is Faulted or not, allowing for + /// cleaner code in the callers. + pub fn is_fault(&self) -> bool { + matches!(self, LinkState::Faulted(_)) + } + + /// If the link is in a faulted state, return the Fault. If not, return + /// None. + pub fn get_fault(&self) -> Option { + match self { + LinkState::Faulted(f) => Some(f.clone()), + _ => None, + } + } +} + +impl fmt::Display for LinkState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + LinkState::Up => write!(f, "Up"), + LinkState::Down => write!(f, "Down"), + LinkState::ConfigError(_) => write!(f, "ConfigError"), + LinkState::Faulted(_) => write!(f, "Faulted"), + LinkState::Unknown => write!(f, "Unknown"), + } + } +} + +impl fmt::Display for LinkView { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}/{}", self.port_id, self.link_id) + } +} diff --git a/dpd-types/versions/src/impls/mcast.rs b/dpd-types/versions/src/impls/mcast.rs new file mode 100644 index 00000000..a067c211 --- /dev/null +++ b/dpd-types/versions/src/impls/mcast.rs @@ -0,0 +1,138 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use std::{ + fmt, + net::{IpAddr, Ipv6Addr}, + str::FromStr, +}; + +use omicron_common::address::UNDERLAY_MULTICAST_SUBNET; + +use crate::latest::mcast::{ + Error, IpSrc, MulticastGroupResponse, MulticastTag, UnderlayMulticastIpv6, +}; + +/// Maximum length for multicast tags. +pub const MAX_TAG_LENGTH: usize = 80; + +/// Error parsing a multicast tag from a string. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MulticastTagParseError(pub(crate) String); + +impl UnderlayMulticastIpv6 { + /// Create a new UnderlayMulticastIpv6 if the address is within the + /// underlay multicast subnet (ff04::/64). + pub fn new(addr: Ipv6Addr) -> Result { + if !UNDERLAY_MULTICAST_SUBNET.contains(addr) { + return Err(Error::InvalidUnderlayMulticastIp(addr)); + } + Ok(Self(addr)) + } +} + +impl From for IpAddr { + fn from(addr: UnderlayMulticastIpv6) -> Self { + IpAddr::V6(addr.0) + } +} + +impl fmt::Display for UnderlayMulticastIpv6 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl FromStr for UnderlayMulticastIpv6 { + type Err = Error; + + fn from_str(s: &str) -> Result { + let addr: Ipv6Addr = s + .parse() + .map_err(|e| Error::InvalidIpv6Address(s.to_string(), e))?; + Self::new(addr) + } +} + +impl AsRef for MulticastTag { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl From for String { + fn from(tag: MulticastTag) -> Self { + tag.0 + } +} + +impl From for MulticastTag { + fn from(tag: String) -> Self { + MulticastTag(tag) + } +} + +impl fmt::Display for MulticastTagParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl std::error::Error for MulticastTagParseError {} + +impl FromStr for MulticastTag { + type Err = MulticastTagParseError; + + fn from_str(s: &str) -> Result { + if s.is_empty() { + return Err(MulticastTagParseError( + "tag cannot be empty".to_string(), + )); + } + if s.len() > MAX_TAG_LENGTH { + return Err(MulticastTagParseError(format!( + "tag cannot exceed {MAX_TAG_LENGTH} bytes" + ))); + } + if !s.bytes().all(|b| { + b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b':' | b'.') + }) { + return Err(MulticastTagParseError( + "tag must contain only ASCII alphanumeric characters, hyphens, \ + underscores, colons, or periods" + .to_string(), + )); + } + Ok(MulticastTag(s.to_string())) + } +} + +impl MulticastGroupResponse { + /// Get the multicast group IP address. + pub fn ip(&self) -> IpAddr { + match self { + Self::Underlay(resp) => resp.group_ip.into(), + Self::External(resp) => resp.group_ip, + } + } + + /// Get the tag. + pub fn tag(&self) -> &str { + match self { + Self::Underlay(resp) => &resp.tag, + Self::External(resp) => &resp.tag, + } + } +} + +impl fmt::Display for IpSrc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + IpSrc::Exact(ip) => write!(f, "{ip}"), + IpSrc::Any => write!(f, "any"), + } + } +} diff --git a/dpd-types/versions/src/impls/mod.rs b/dpd-types/versions/src/impls/mod.rs new file mode 100644 index 00000000..0793200a --- /dev/null +++ b/dpd-types/versions/src/impls/mod.rs @@ -0,0 +1,15 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Functional code for the latest versions of types. + +mod link; +pub(crate) mod mcast; +mod port_map; +mod route; +mod serdes; +mod table; +mod transceivers; diff --git a/dpd-types/versions/src/impls/port_map.rs b/dpd-types/versions/src/impls/port_map.rs new file mode 100644 index 00000000..8e2b2ba6 --- /dev/null +++ b/dpd-types/versions/src/impls/port_map.rs @@ -0,0 +1,55 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use common::ports::RearPort; + +use crate::latest::port_map::{BackplaneLink, Error, SidecarConnector}; + +impl From for u8 { + fn from(g: SidecarConnector) -> u8 { + g.as_u8() + } +} + +impl TryFrom for SidecarConnector { + type Error = Error; + + fn try_from(x: u8) -> Result { + if x > 7 { + return Err(Error::SidecarConnector(x)); + } + Ok(Self(x)) + } +} + +impl SidecarConnector { + /// Create a new backplane group. + pub fn new(x: u8) -> Result { + Self::try_from(x) + } + + /// Return the index of this group as an integer. + pub const fn as_u8(&self) -> u8 { + self.0 + } +} + +impl From for BackplaneLink { + fn from(p: RearPort) -> Self { + Self::from_cubby(p.as_u8()).unwrap() + } +} + +impl BackplaneLink { + /// Construct a link from the cubby number. + pub fn from_cubby(cubby: u8) -> Result { + crate::latest::port_map::SIDECAR_REV_AB_BACKPLANE_MAP + .iter() + .find(|entry| entry.cubby == cubby) + .copied() + .ok_or(Error::Cubby(cubby)) + } +} diff --git a/dpd-types/versions/src/impls/route.rs b/dpd-types/versions/src/impls/route.rs new file mode 100644 index 00000000..b2d6d65e --- /dev/null +++ b/dpd-types/versions/src/impls/route.rs @@ -0,0 +1,130 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use std::fmt; + +use crate::latest::route::{Ipv4Route, Ipv6Route, RouteTarget}; + +// We implement PartialEq for Ipv4Route because we want to exclude the tag and +// vlan_id from any comparisons. We do this because the tag is a comment +// identifying the originator rather than a semantically meaningful part of the +// route. The vlan_id is used to modify the traffic on a specific route, rather +// then being part of the route itself. +impl PartialEq for Ipv4Route { + fn eq(&self, other: &Self) -> bool { + self.port_id == other.port_id + && self.link_id == other.link_id + && self.tgt_ip == other.tgt_ip + } +} + +// See the comment above PartialEq to understand why we implement Hash rather +// then Deriving it. +impl std::hash::Hash for Ipv4Route { + fn hash(&self, state: &mut H) + where + H: std::hash::Hasher, + { + self.port_id.hash(state); + self.link_id.hash(state); + self.tgt_ip.hash(state); + } +} + +impl fmt::Display for Ipv4Route { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "port: {} link: {} gw: {} vlan: {:?}", + self.port_id, self.link_id, self.tgt_ip, self.vlan_id + )?; + Ok(()) + } +} + +// See the comment above the PartialEq for IPv4Route +impl PartialEq for Ipv6Route { + fn eq(&self, other: &Self) -> bool { + self.port_id == other.port_id + && self.link_id == other.link_id + && self.tgt_ip == other.tgt_ip + } +} + +// See the comment above PartialEq for IPv4Route +impl std::hash::Hash for Ipv6Route { + fn hash(&self, state: &mut H) + where + H: std::hash::Hasher, + { + self.port_id.hash(state); + self.link_id.hash(state); + self.tgt_ip.hash(state); + } +} + +impl fmt::Display for Ipv6Route { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "port: {} link: {} gw: {} vlan: {:?}", + self.port_id, self.link_id, self.tgt_ip, self.vlan_id + )?; + Ok(()) + } +} + +impl From<&Ipv4Route> for RouteTarget { + fn from(route: &Ipv4Route) -> RouteTarget { + RouteTarget::V4(route.clone()) + } +} + +impl From for RouteTarget { + fn from(route: Ipv4Route) -> RouteTarget { + RouteTarget::V4(route) + } +} + +impl From<&Ipv6Route> for RouteTarget { + fn from(route: &Ipv6Route) -> RouteTarget { + RouteTarget::V6(route.clone()) + } +} + +impl From for RouteTarget { + fn from(route: Ipv6Route) -> RouteTarget { + RouteTarget::V6(route) + } +} + +impl TryFrom for Ipv4Route { + type Error = dropshot::HttpError; + + fn try_from(target: RouteTarget) -> Result { + match target { + RouteTarget::V4(route) => Ok(route), + _ => Err(dropshot::HttpError::for_bad_request( + None, + "expected an IPv4 route target".to_string(), + )), + } + } +} + +impl TryFrom for Ipv6Route { + type Error = dropshot::HttpError; + + fn try_from(target: RouteTarget) -> Result { + match target { + RouteTarget::V6(route) => Ok(route), + _ => Err(dropshot::HttpError::for_bad_request( + None, + "expected an IPv6 route target".to_string(), + )), + } + } +} diff --git a/dpd-types/versions/src/impls/serdes.rs b/dpd-types/versions/src/impls/serdes.rs new file mode 100644 index 00000000..46c50d92 --- /dev/null +++ b/dpd-types/versions/src/impls/serdes.rs @@ -0,0 +1,16 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use crate::latest::serdes::Polarity; + +impl From for Polarity { + fn from(p: bool) -> Self { + match p { + true => Polarity::Inverted, + false => Polarity::Normal, + } + } +} diff --git a/dpd-types/versions/src/impls/table.rs b/dpd-types/versions/src/impls/table.rs new file mode 100644 index 00000000..335de320 --- /dev/null +++ b/dpd-types/versions/src/impls/table.rs @@ -0,0 +1,26 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use crate::latest::table::{TableCounterEntry, TableEntry}; + +impl TableEntry { + pub fn new( + key: impl aal::MatchParse, + action: impl aal::ActionParse, + ) -> Self { + TableEntry { + keys: key.key_values(), + action: action.action_name(), + action_args: action.action_args(), + } + } +} + +impl TableCounterEntry { + pub fn new(key: impl aal::MatchParse, data: aal::CounterData) -> Self { + TableCounterEntry { keys: key.key_values(), data } + } +} diff --git a/dpd-types/versions/src/impls/transceivers.rs b/dpd-types/versions/src/impls/transceivers.rs new file mode 100644 index 00000000..e0f5cb67 --- /dev/null +++ b/dpd-types/versions/src/impls/transceivers.rs @@ -0,0 +1,31 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use std::time::Instant; + +use crate::latest::switch_port::ManagementMode; +use crate::latest::transceivers::{ + ElectricalMode, QsfpDevice, TransceiverInfo, +}; + +impl Default for QsfpDevice { + fn default() -> Self { + Self { transceiver: None, management_mode: ManagementMode::Automatic } + } +} + +impl Default for TransceiverInfo { + fn default() -> Self { + Self { + vendor_info: None, + in_reset: None, + interrupt_pending: None, + power_mode: None, + electrical_mode: ElectricalMode::Single, + first_seen: Instant::now(), + } + } +} diff --git a/dpd-types/versions/src/initial/arp.rs b/dpd-types/versions/src/initial/arp.rs new file mode 100644 index 00000000..43e4e110 --- /dev/null +++ b/dpd-types/versions/src/initial/arp.rs @@ -0,0 +1,60 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + +use common::network::MacAddr; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Represents the mapping of an IP address to a MAC address. +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct ArpEntry { + /// A tag used to associate this entry with a client. + pub tag: String, + /// The IP address for the entry. + pub ip: IpAddr, + /// The MAC address to which `ip` maps. + pub mac: MacAddr, + /// The time the entry was updated + pub update: String, +} + +/** + * Represents a cursor into a paginated request for the contents of an ARP table + */ +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct ArpToken { + pub ip: IpAddr, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct Ipv4ArpParam { + pub ip: Ipv4Addr, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct Ipv6ArpParam { + pub ip: Ipv6Addr, +} + +/** + * Represents a cursor into a paginated request for the contents of an + * Ipv4-indexed table. + */ +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct Ipv4Token { + pub ip: Ipv4Addr, +} + +/** + * Represents a cursor into a paginated request for the contents of an + * IPv6-indexed table. + */ +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct Ipv6Token { + pub ip: Ipv6Addr, +} diff --git a/dpd-types/versions/src/initial/counters.rs b/dpd-types/versions/src/initial/counters.rs new file mode 100644 index 00000000..3a92957a --- /dev/null +++ b/dpd-types/versions/src/initial/counters.rs @@ -0,0 +1,70 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use common::counters::{ + FecRSCounters, PcsCounters, RMonCounters, RMonCountersAll, +}; +use common::ports::PortId; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use super::link::LinkId; + +/// The Physical Coding Sublayer (PCS) counters for a specific link. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct LinkPcsCounters { + /// The switch port ID. + pub port_id: PortId, + /// The link ID. + pub link_id: LinkId, + /// The PCS counter data. + pub counters: PcsCounters, +} + +/// The FEC counters for a specific link, including its link ID. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct LinkFecRSCounters { + /// The switch port ID. + pub port_id: PortId, + /// The link ID. + pub link_id: LinkId, + /// The FEC counter data. + pub counters: FecRSCounters, +} + +/// The RMON counters (traffic counters) for a specific link. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct LinkRMonCounters { + /// The switch port ID. + pub port_id: PortId, + /// The link ID. + pub link_id: LinkId, + /// The RMON counter data. + pub counters: RMonCounters, +} + +/// The complete RMON counters (traffic counters) for a specific link. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct LinkRMonCountersAll { + /// The switch port ID. + pub port_id: PortId, + /// The link ID. + pub link_id: LinkId, + /// The RMON counter data. + pub counters: RMonCountersAll, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct CounterSync { + /// Force a sync of the counters from the ASIC to memory, even if the + /// default refresh timeout hasn't been reached. + pub force_sync: bool, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct CounterPath { + pub counter: String, +} diff --git a/dpd-types/versions/src/initial/fault.rs b/dpd-types/versions/src/initial/fault.rs new file mode 100644 index 00000000..61a15db0 --- /dev/null +++ b/dpd-types/versions/src/initial/fault.rs @@ -0,0 +1,26 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// A Fault represents a specific kind of failure, and carries some additional +/// context. Currently Faults are only used to describe Link failures, but +/// there is no reason they couldn't be used elsewhere. +#[derive(Clone, Debug, PartialEq, Deserialize, JsonSchema, Serialize)] +pub enum Fault { + LinkFlap(String), + Autoneg(String), + Injected(String), +} + +/** + * Represents a potential fault condtion on a link + */ +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct FaultCondition { + pub fault: Option, +} diff --git a/dpd-types/versions/src/initial/link.rs b/dpd-types/versions/src/initial/link.rs new file mode 100644 index 00000000..262f4581 --- /dev/null +++ b/dpd-types/versions/src/initial/link.rs @@ -0,0 +1,253 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use std::net::{Ipv4Addr, Ipv6Addr}; + +use common::{ + network::MacAddr, + ports::{PortFec, PortId, PortMedia, PortSpeed, TxEq}, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use super::fault::Fault; +use super::port::PortPrbsMode; + +/// An identifier for a link within a switch port. +/// +/// A switch port identified by a [`PortId`] may have multiple links within it, +/// each identified by a `LinkId`. These are unique within a switch port only. +#[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + JsonSchema, + Ord, + PartialEq, + PartialOrd, + Serialize, +)] +pub struct LinkId(pub u8); + +/// The state of a data link with a peer. +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum LinkState { + /// An error was encountered while trying to configure the link in the + /// switch hardware. + ConfigError(String), + /// The link is up. + Up, + /// The link is down. + Down, + /// The Link is offline due to a fault + Faulted(Fault), + /// The link's state is not known. + Unknown, +} + +impl std::fmt::Debug for LinkState { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + LinkState::Up => write!(f, "Up"), + LinkState::Down => write!(f, "Down"), + LinkState::ConfigError(detail) => { + write!(f, "ConfigError - {detail:?}") + } + LinkState::Faulted(reason) => write!(f, "Faulted - {reason:?}"), + LinkState::Unknown => write!(f, "Unknown"), + } + } +} + +/// Reports how many times a link has transitioned from Down to Up. +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Serialize)] +pub struct LinkUpCounter { + /// Link being reported + pub link_path: String, + /// LinkUp transitions since the link was last enabled + pub current: u32, + /// LinkUp transitions since the link was created + pub total: u32, +} + +/// Reports how many times a given autoneg/link-training state has been entered +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Serialize)] +pub struct LinkFsmCounter { + /// FSM state being counted + pub state_name: String, + /// Times entered since the link was last enabled + pub current: u32, + /// Times entered since the link was created + pub total: u32, +} + +/// Reports all the autoneg/link-training states a link has transitioned into. +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Serialize)] +pub struct LinkFsmCounters { + /// Link being reported + pub link_path: String, + /// All the states this link has entered, along with counts of how many + /// times each state was entered. + pub counters: Vec, +} + +/// Identifies a logical link on a physical port. +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct LinkPath { + /// The switch port on which to operate. + pub port_id: PortId, + /// The link in the switch port on which to operate. + pub link_id: LinkId, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct LinkIpv4Path { + /// The switch port on which to operate. + pub port_id: PortId, + /// The link in the switch port on which to operate. + pub link_id: LinkId, + /// The IPv4 address on which to operate. + pub address: Ipv4Addr, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct LinkIpv6Path { + /// The switch port on which to operate. + pub port_id: PortId, + /// The link in the switch port on which to operate. + pub link_id: LinkId, + /// The IPv6 address on which to operate. + pub address: Ipv6Addr, +} + +/// Parameters used to create a link on a switch port. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct LinkCreate { + /// The first lane of the port to use for the new link + pub lane: Option, + /// The requested speed of the link. + pub speed: PortSpeed, + /// The requested forward-error correction method. If this is None, the + /// standard FEC for the underlying media will be applied if it can be + /// determined. + pub fec: Option, + /// Whether the link is configured to autonegotiate with its peer during + /// link training. + /// + /// This is generally only true for backplane links, and defaults to + /// `false`. + #[serde(default)] + pub autoneg: bool, + /// Whether the link is configured in KR mode, an electrical specification + /// generally only true for backplane link. + /// + /// This defaults to `false`. + #[serde(default)] + pub kr: bool, + + /// Transceiver equalization adjustment parameters. + /// This defaults to `None`. + #[serde(default)] + pub tx_eq: Option, +} + +#[derive(Clone, Debug, Deserialize, JsonSchema)] +pub struct LinkFilter { + /// Filter links to those whose name contains the provided string. + /// + /// If not provided, then all links are returned. + pub filter: Option, +} + +// View types: public API representations of internal Dendrite data. + +/// An Ethernet-capable link within a switch port. +// +// NOTE: This is a view onto `dpd::link::Link`. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +#[serde(rename = "Link")] +pub struct LinkView { + /// The switch port on which this link exists. + pub port_id: PortId, + /// The `LinkId` within the switch port for this link. + pub link_id: LinkId, + /// The Tofino connector number associated with this link. + pub tofino_connector: u16, + /// The lower-level ASIC ID used to refer to this object in the switch + /// driver software. + pub asic_id: u16, + /// True if the transceiver module has detected a media presence. + pub presence: bool, + /// True if this link is in KR mode, i.e., is on a cabled backplane. + pub kr: bool, + /// True if this link is configured to autonegotiate with its peer. + pub autoneg: bool, + /// Current state in the autonegotiation/link-training finite state machine + pub fsm_state: String, + /// The speed of the link. + pub speed: PortSpeed, + /// The error-correction scheme for this link. + pub fec: Option, + /// The physical media underlying this link. + pub media: PortMedia, + /// True if this link is enabled. + pub enabled: bool, + /// The PRBS mode. + pub prbs: PortPrbsMode, + /// The state of the Ethernet link. + pub link_state: LinkState, + /// The MAC address for the link. + pub address: MacAddr, + /// The link is configured for IPv6 use + pub ipv6_enabled: bool, +} + +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct LinkEvent { + /// Time the event occurred. The time is represented in milliseconds, + /// starting at an undefined time in the past. This means that timestamps + /// can be used to measure the time between events, but not to determine the + /// wall-clock time at which the event occurred. + pub timestamp: i64, + /// Channel ID for sub-link-level events + pub channel: Option, + /// Event class + pub class: String, + /// Event subclass + pub subclass: String, + /// Optionally, additional details about the event + pub details: Option, +} + +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct LinkHistory { + /// The timestamp in milliseconds at which this history was collected. + pub timestamp: i64, + /// The set of historical events recorded + pub events: Vec, +} + +/// The per-link data consumed by tfportd +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct TfportData { + /// The switch port ID for this link. + pub port_id: PortId, + /// The link ID for this link. + pub link_id: LinkId, + /// The lower-level ASIC ID used to refer to this object in the switch + /// driver software. + pub asic_id: u16, + /// The MAC address for the link. + pub mac: MacAddr, + /// Is ipv6 enabled for this link + pub ipv6_enabled: bool, + /// The IPv6 link-local address of the link, if it exists. + pub link_local: Option, +} diff --git a/dpd-types/versions/src/initial/loopback.rs b/dpd-types/versions/src/initial/loopback.rs new file mode 100644 index 00000000..da84891e --- /dev/null +++ b/dpd-types/versions/src/initial/loopback.rs @@ -0,0 +1,20 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use std::net::{Ipv4Addr, Ipv6Addr}; + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct LoopbackIpv4Path { + pub ipv4: Ipv4Addr, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct LoopbackIpv6Path { + pub ipv6: Ipv6Addr, +} diff --git a/dpd-types/versions/src/initial/mcast.rs b/dpd-types/versions/src/initial/mcast.rs new file mode 100644 index 00000000..a954f23a --- /dev/null +++ b/dpd-types/versions/src/initial/mcast.rs @@ -0,0 +1,235 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Public types for multicast group management. + +use std::fmt; +use std::net::{IpAddr, Ipv6Addr}; +use std::str::FromStr; + +use common::{network::NatTarget, ports::PortId}; +use oxnet::{Ipv4Net, Ipv6Net}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use super::link::LinkId; + +/// Type alias for multicast group IDs. +pub type MulticastGroupId = u16; + +/// Represents the NAT target for multicast traffic for internal/underlay +/// forwarding. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +pub struct InternalForwarding { + pub nat_target: Option, +} + +/// Represents the forwarding configuration for external multicast traffic. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +pub struct ExternalForwarding { + pub vlan_id: Option, +} + +/// Represents a member of a multicast group. +#[derive( + Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema, +)] +pub struct MulticastGroupMember { + pub port_id: PortId, + pub link_id: LinkId, + pub direction: Direction, +} + +/// Direction a multicast group member is reached by. +/// +/// `External` group members must have any packet encapsulation removed +/// before packet delivery. +#[derive( + Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema, +)] +pub enum Direction { + Underlay, + External, +} + +/// Used to identify a multicast group by IP address, the main +/// identifier for a multicast group. +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupIpParam { + pub group_ip: IpAddr, +} + +/// Used to identify a multicast group by ID. +/// +/// If not provided, it will return all multicast groups. +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupIdParam { + pub group_id: Option, +} + +/// Source filter match key for multicast traffic. +#[derive( + Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema, +)] +pub enum IpSrc { + /// Exact match for the source IP address. + Exact(IpAddr), + /// Subnet match for the source IP address. + Subnet(Ipv4Net), +} + +/// A validated admin-scoped IPv6 multicast address. +/// +/// Admin-scoped addresses are ff04::/16, ff05::/16, or ff08::/16. These are +/// used for internal/underlay multicast groups. +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Deserialize, + Serialize, + JsonSchema, +)] +#[serde(try_from = "Ipv6Addr", into = "Ipv6Addr")] +pub struct AdminScopedIpv6(pub(crate) Ipv6Addr); + +impl AdminScopedIpv6 { + /// Create a new AdminScopedIpv6 if the address is admin-local (ff04::/16). + pub fn new(addr: Ipv6Addr) -> Result { + if !Ipv6Net::new_unchecked(addr, 128).is_admin_local_multicast() { + return Err(format!( + "Address {} is not admin-local (must be ff04::/16)", + addr + )); + } + Ok(Self(addr)) + } +} + +impl TryFrom for AdminScopedIpv6 { + type Error = String; + + fn try_from(addr: Ipv6Addr) -> Result { + Self::new(addr) + } +} + +impl From for Ipv6Addr { + fn from(admin: AdminScopedIpv6) -> Self { + admin.0 + } +} + +impl From for IpAddr { + fn from(admin: AdminScopedIpv6) -> Self { + IpAddr::V6(admin.0) + } +} + +impl fmt::Display for AdminScopedIpv6 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl FromStr for AdminScopedIpv6 { + type Err = String; + + fn from_str(s: &str) -> Result { + let addr: Ipv6Addr = + s.parse().map_err(|e| format!("invalid IPv6: {e}"))?; + Self::new(addr) + } +} + +impl fmt::Display for IpSrc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + IpSrc::Exact(ip) => write!(f, "{ip}"), + IpSrc::Subnet(net) => write!(f, "{net}"), + } + } +} + +/// A multicast group configuration for POST requests for external (to the +/// rack) groups. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupCreateExternalEntry { + pub group_ip: IpAddr, + pub tag: Option, + pub internal_forwarding: InternalForwarding, + pub external_forwarding: ExternalForwarding, + pub sources: Option>, +} + +/// A multicast group update entry for PUT requests for external (to the rack) +/// groups. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupUpdateExternalEntry { + pub tag: Option, + pub internal_forwarding: InternalForwarding, + pub external_forwarding: ExternalForwarding, + pub sources: Option>, +} + +/// Response structure for external multicast group operations. These groups +/// handle IPv4 and non-admin IPv6 multicast via NAT targets. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupExternalResponse { + pub group_ip: IpAddr, + pub external_group_id: MulticastGroupId, + pub tag: Option, + pub internal_forwarding: InternalForwarding, + pub external_forwarding: ExternalForwarding, + pub sources: Option>, +} + +/// Path parameter for underlay multicast group endpoints. +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct MulticastUnderlayGroupIpParam { + pub group_ip: AdminScopedIpv6, +} + +/// A multicast group configuration for POST requests for internal (to the +/// rack) groups. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupCreateUnderlayEntry { + pub group_ip: AdminScopedIpv6, + pub tag: Option, + pub members: Vec, +} + +/// Represents a multicast replication entry for PUT requests for internal +/// (to the rack) groups. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupUpdateUnderlayEntry { + pub tag: Option, + pub members: Vec, +} + +/// Response structure for underlay/internal multicast group operations. These +/// groups handle admin-scoped IPv6 multicast with full replication. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupUnderlayResponse { + pub group_ip: AdminScopedIpv6, + pub external_group_id: MulticastGroupId, + pub underlay_group_id: MulticastGroupId, + pub tag: Option, + pub members: Vec, +} + +/// Unified response type for operations that return mixed group types. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "kind", rename_all = "snake_case")] +pub enum MulticastGroupResponse { + Underlay(MulticastGroupUnderlayResponse), + External(MulticastGroupExternalResponse), +} diff --git a/dpd-types/versions/src/initial/misc.rs b/dpd-types/versions/src/initial/misc.rs new file mode 100644 index 00000000..da6a6cb7 --- /dev/null +++ b/dpd-types/versions/src/initial/misc.rs @@ -0,0 +1,31 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Path parameter for tag-based operations. +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct TagPath { + pub tag: String, +} + +/// Detailed build information about `dpd`. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct BuildInfo { + pub version: String, + pub git_sha: String, + pub git_commit_timestamp: String, + pub git_branch: String, + pub rustc_semver: String, + pub rustc_channel: String, + pub rustc_host_triple: String, + pub rustc_commit_sha: String, + pub cargo_triple: String, + pub debug: bool, + pub opt_level: u8, + pub sde_commit_sha: String, +} diff --git a/dpd-types/versions/src/initial/mod.rs b/dpd-types/versions/src/initial/mod.rs new file mode 100644 index 00000000..3f9f952a --- /dev/null +++ b/dpd-types/versions/src/initial/mod.rs @@ -0,0 +1,27 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Version `INITIAL` of the DPD API. +//! +//! This is the first version of the API. All types first published in v1 live +//! here. + +pub mod arp; +pub mod counters; +pub mod fault; +pub mod link; +pub mod loopback; +pub mod mcast; +pub mod misc; +pub mod nat; +pub mod port; +pub mod port_map; +pub mod route; +pub mod serdes; +pub mod switch_identifiers; +pub mod switch_port; +pub mod table; +pub mod transceivers; diff --git a/dpd-types/versions/src/initial/nat.rs b/dpd-types/versions/src/initial/nat.rs new file mode 100644 index 00000000..a92b516e --- /dev/null +++ b/dpd-types/versions/src/initial/nat.rs @@ -0,0 +1,54 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use std::net::{Ipv4Addr, Ipv6Addr}; + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct NatIpv4Path { + pub ipv4: Ipv4Addr, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct NatIpv4PortPath { + pub ipv4: Ipv4Addr, + pub low: u16, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct NatIpv4RangePath { + pub ipv4: Ipv4Addr, + pub low: u16, + pub high: u16, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct NatIpv6Path { + pub ipv6: Ipv6Addr, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct NatIpv6PortPath { + pub ipv6: Ipv6Addr, + pub low: u16, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct NatIpv6RangePath { + pub ipv6: Ipv6Addr, + pub low: u16, + pub high: u16, +} + +/** + * Represents a cursor into a paginated request for all NAT data. + */ +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct NatToken { + pub port: u16, +} diff --git a/dpd-types/versions/src/initial/port.rs b/dpd-types/versions/src/initial/port.rs new file mode 100644 index 00000000..69d364b5 --- /dev/null +++ b/dpd-types/versions/src/initial/port.rs @@ -0,0 +1,108 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use std::collections::{HashMap, HashSet}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + +use common::ports::{PortFec, PortId, PortSpeed}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use super::link::LinkCreate; + +#[derive( + Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize, JsonSchema, +)] +pub enum PortPrbsMode { + Mode31, + Mode23, + Mode15, + Mode13, + Mode11, + Mode9, + Mode7, + Mission, // i.e. PRBS disabled +} + +/// Parameter used to create a port. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct PortCreateParams { + /// The name of the port. This should be a string like `"3:0"`. + pub name: String, + /// The speed at which to configure the port. + pub speed: PortSpeed, + /// The forward error-correction scheme for the port. + pub fec: PortFec, +} + +/// Represents the free MAC channels on a single physical port. +#[derive(Deserialize, Serialize, JsonSchema, Debug)] +pub struct FreeChannels { + /// The switch port. + pub port_id: PortId, + /// The Tofino connector for this port. + /// + /// This describes the set of electrical connections representing this port + /// object, which are defined by the pinout and board design of the Sidecar. + pub connector: String, + /// The set of available channels (lanes) on this connector. + pub channels: Vec, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct PortIdPathParams { + /// The switch port on which to operate. + pub port_id: PortId, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct PortSettingsTag { + /// Restrict operations on this port to the provided tag. + pub tag: Option, +} + +/** + * Represents a cursor into a paginated request for all port data. Because we + * don't (yet) support filtering or arbitrary sorting, it is sufficient to + * track the last port returned. + */ +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct PortToken { + pub port: u16, +} + +/// A port settings transaction object. When posted to the +/// `/port-settings/{port_id}` API endpoint, these settings will be applied +/// holistically, and to the extent possible atomically to a given port. +#[derive(Default, Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct PortSettings { + /// The link settings to apply to the port on a per-link basis. Any links + /// not in this map that are resident on the switch port will be removed. + /// Any links that are in this map that are not resident on the switch port + /// will be added. Any links that are resident on the switch port and in + /// this map, and are different, will be modified. Links are indexed by + /// spatial index within the port. + pub links: HashMap, +} + +/// An object with link settings used in concert with [`PortSettings`]. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct LinkSettings { + pub params: LinkCreate, + pub addrs: HashSet, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct PortIpv4Path { + pub port: String, + pub ipv4: Ipv4Addr, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct PortIpv6Path { + pub port: String, + pub ipv6: Ipv6Addr, +} diff --git a/dpd-types/versions/src/initial/port_map.rs b/dpd-types/versions/src/initial/port_map.rs new file mode 100644 index 00000000..1c11f7ed --- /dev/null +++ b/dpd-types/versions/src/initial/port_map.rs @@ -0,0 +1,130 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// The Sidecar chassis connector mating the backplane and internal cabling. +/// +/// This describes the "group" of backplane links that all terminate in one +/// connector on the Sidecar itself. This is the connection point between a +/// cable on the backplane itself and the Sidecar chassis. +#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] +pub struct SidecarConnector(pub(crate) u8); + +/// A single point-to-point connection on the cabled backplane. +/// +/// This describes a single link from the Sidecar switch to a cubby, via the +/// cabled backplane. It ultimately maps the Tofino ASIC pins to the cubby at +/// which that link terminates. This path follows the Sidecar internal cable; +/// the Sidecar chassis connector; and the backplane cable itself. This is used +/// to map the Tofino driver's "connector" number (an index in its possible +/// pinouts) through the backplane to our logical cubby numbering. +#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] +pub struct BackplaneLink { + // The internal Tofino driver connector number. + pub tofino_connector: u8, + // The leg label on the Sidecar-internal cable. + pub sidecar_leg: SidecarCableLeg, + // The Sidecar chassis connector. + pub sidecar_connector: SidecarConnector, + // The leg label on the cabled backplane. + pub backplane_leg: BackplaneCableLeg, + // The cubby at which the cable terminates. + pub cubby: u8, +} + +/// The leg of the backplane cable. +/// +/// This describes the leg on the actual backplane cable that connects the +/// Sidecar chassis connector to a cubby endpoint. +// NOTE: This is the connector on the cubby chassis end of the part +// HDR-222627-xx-EBCM. The `xx` describes the length of the cable. +#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] +pub enum BackplaneCableLeg { + A, + B, + C, + D, +} + +// Helper macro to make a backplane map entry. +macro_rules! bp_entry { + ( + $connector:literal, + $sidecar_leg:expr, + $sidecar_connector:literal, + $backplane_leg:expr, + $cubby:literal + ) => { + BackplaneLink { + tofino_connector: $connector, + sidecar_leg: $sidecar_leg, + sidecar_connector: SidecarConnector($sidecar_connector), + backplane_leg: $backplane_leg, + cubby: $cubby, + } + }; +} + +/// The leg of the Sidecar-internal cable. +/// +/// This describes the leg on the cabling that connects the pins on the Tofino +/// ASIC to the Sidecar chassis connector. +// NOTE: This is the connector on the Sidecar main board side of the part +// HDR-222623-01-EBCF. +#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] +pub enum SidecarCableLeg { + A, + C, +} + +pub const SIDECAR_REV_AB_BACKPLANE_MAP: [BackplaneLink; 32] = [ + bp_entry!(1, SidecarCableLeg::C, 0, BackplaneCableLeg::C, 29), + bp_entry!(2, SidecarCableLeg::C, 0, BackplaneCableLeg::D, 31), + bp_entry!(3, SidecarCableLeg::A, 0, BackplaneCableLeg::A, 25), + bp_entry!(4, SidecarCableLeg::A, 0, BackplaneCableLeg::B, 27), + bp_entry!(5, SidecarCableLeg::C, 1, BackplaneCableLeg::C, 21), + bp_entry!(6, SidecarCableLeg::C, 1, BackplaneCableLeg::D, 23), + bp_entry!(7, SidecarCableLeg::A, 1, BackplaneCableLeg::A, 17), + bp_entry!(8, SidecarCableLeg::A, 1, BackplaneCableLeg::B, 19), + bp_entry!(9, SidecarCableLeg::A, 2, BackplaneCableLeg::B, 11), + bp_entry!(10, SidecarCableLeg::A, 2, BackplaneCableLeg::A, 9), + bp_entry!(11, SidecarCableLeg::C, 2, BackplaneCableLeg::D, 15), + bp_entry!(12, SidecarCableLeg::C, 2, BackplaneCableLeg::C, 13), + bp_entry!(13, SidecarCableLeg::A, 3, BackplaneCableLeg::B, 3), + bp_entry!(14, SidecarCableLeg::A, 3, BackplaneCableLeg::A, 1), + bp_entry!(15, SidecarCableLeg::C, 3, BackplaneCableLeg::D, 7), + bp_entry!(16, SidecarCableLeg::C, 3, BackplaneCableLeg::C, 5), + bp_entry!(17, SidecarCableLeg::C, 4, BackplaneCableLeg::C, 28), + bp_entry!(18, SidecarCableLeg::C, 4, BackplaneCableLeg::D, 30), + bp_entry!(19, SidecarCableLeg::A, 4, BackplaneCableLeg::A, 24), + bp_entry!(20, SidecarCableLeg::A, 4, BackplaneCableLeg::B, 26), + bp_entry!(21, SidecarCableLeg::C, 5, BackplaneCableLeg::C, 20), + bp_entry!(22, SidecarCableLeg::C, 5, BackplaneCableLeg::D, 22), + bp_entry!(23, SidecarCableLeg::A, 5, BackplaneCableLeg::A, 16), + bp_entry!(24, SidecarCableLeg::A, 5, BackplaneCableLeg::B, 18), + bp_entry!(25, SidecarCableLeg::A, 6, BackplaneCableLeg::B, 10), + bp_entry!(26, SidecarCableLeg::A, 6, BackplaneCableLeg::A, 8), + bp_entry!(27, SidecarCableLeg::C, 6, BackplaneCableLeg::D, 14), + bp_entry!(28, SidecarCableLeg::C, 6, BackplaneCableLeg::C, 12), + bp_entry!(29, SidecarCableLeg::A, 7, BackplaneCableLeg::B, 2), + bp_entry!(30, SidecarCableLeg::A, 7, BackplaneCableLeg::A, 0), + bp_entry!(31, SidecarCableLeg::C, 7, BackplaneCableLeg::D, 6), + bp_entry!(32, SidecarCableLeg::C, 7, BackplaneCableLeg::C, 4), +]; + +#[derive(Clone, Debug, thiserror::Error)] +pub enum Error { + #[error("Invalid backplane group {0}, must be in [0, 7]")] + SidecarConnector(u8), + + #[error("Invalid cubby {0}, must be in [0, 31]")] + Cubby(u8), + + #[error("Invalid SoftNPU revision '{found}', expected '{expected}'")] + SoftNpuRevision { expected: String, found: String }, +} diff --git a/dpd-types/versions/src/initial/route.rs b/dpd-types/versions/src/initial/route.rs new file mode 100644 index 00000000..7caa0e04 --- /dev/null +++ b/dpd-types/versions/src/initial/route.rs @@ -0,0 +1,159 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use std::net::{Ipv4Addr, Ipv6Addr}; + +use common::ports::PortId; +use oxnet::{Ipv4Net, Ipv6Net}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use super::link::LinkId; + +/// A route for an IPv4 subnet. +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +pub struct Ipv4Route { + // The client-specific tag for this route. + pub tag: String, + // The switch port out which routed traffic is sent. + pub port_id: PortId, + // The link out which routed traffic is sent. + pub link_id: LinkId, + // Route traffic matching the subnet via this IP. + pub tgt_ip: Ipv4Addr, + // Tag traffic on this route with this vlan ID. + pub vlan_id: Option, +} + +/// A route for an IPv6 subnet. +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +pub struct Ipv6Route { + // The client-specific tag for this route. + pub tag: String, + // The switch port out which routed traffic is sent. + pub port_id: PortId, + // The link out which routed traffic is sent. + pub link_id: LinkId, + // Route traffic matching the subnet to this IP. + pub tgt_ip: Ipv6Addr, + // Tag traffic on this route with this vlan ID. + pub vlan_id: Option, +} + +/// Represents a new or replacement mapping of a subnet to a single IPv6 +/// RouteTarget nexthop target. +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +pub struct Ipv6RouteUpdate { + /// Traffic destined for any address within the CIDR block is routed using + /// this information. + pub cidr: Ipv6Net, + /// A single RouteTarget associated with this CIDR + pub target: Ipv6Route, + /// Should this route replace any existing route? If a route exists and + /// this parameter is false, then the call will fail. + pub replace: bool, +} + +/// Represents all mappings of an IPv6 subnet to a its nexthop target(s). +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +pub struct Ipv6Routes { + /// Traffic destined for any address within the CIDR block is routed using + /// this information. + pub cidr: Ipv6Net, + /// All RouteTargets associated with this CIDR + pub targets: Vec, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct RoutePathV4 { + /// The IPv4 subnet in CIDR notation whose route entry is returned. + pub cidr: Ipv4Net, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct RoutePathV6 { + /// The IPv6 subnet in CIDR notation whose route entry is returned. + pub cidr: Ipv6Net, +} + +/// Represents a single subnet->target route entry +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct RouteTargetIpv6Path { + /// The subnet being routed + pub cidr: Ipv6Net, + /// The switch port to which packets should be sent + pub port_id: PortId, + /// The link to which packets should be sent + pub link_id: LinkId, + /// The next hop in the IPv4 route + pub tgt_ip: Ipv6Addr, +} + +/** + * Represents a cursor into a paginated request for the contents of the + * subnet routing table. Because we don't (yet) support filtering or arbitrary + * sorting, it is sufficient to track the last mac address reported. + */ +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct Ipv4RouteToken { + pub cidr: Ipv4Net, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct Ipv6RouteToken { + pub cidr: Ipv6Net, +} + +/// An object with IPv4 route settings used in concert with [`PortSettings`]. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct RouteSettingsV4 { + pub link_id: u8, + pub nexthop: Ipv4Addr, +} + +/// An object with IPV6 route settings used in concert with [`PortSettings`]. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct RouteSettingsV6 { + pub link_id: u8, + pub nexthop: Ipv6Addr, +} + +/// Represents all mappings of an IPv4 subnet to its nexthop target(s). +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +pub struct Ipv4Routes { + /// Traffic destined for any address within the CIDR block is routed using + /// this information. + pub cidr: Ipv4Net, + /// All RouteTargets associated with this CIDR. + pub targets: Vec, +} + +/// Represents a new or replacement mapping of a subnet to a single IPv4 +/// RouteTarget nexthop target. +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +pub struct Ipv4RouteUpdate { + /// Traffic destined for any address within the CIDR block is routed using + /// this information. + pub cidr: Ipv4Net, + /// A single Route associated with this CIDR + pub target: Ipv4Route, + /// Should this route replace any existing route? If a route exists and + /// this parameter is false, then the call will fail. + pub replace: bool, +} + +/// Represents a single subnet->target route entry +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct RouteTargetIpv4Path { + /// The subnet being routed + pub cidr: Ipv4Net, + /// The switch port to which packets should be sent + pub port_id: PortId, + /// The link to which packets should be sent + pub link_id: LinkId, + /// The next hop in the IPv4 route + pub tgt_ip: Ipv4Addr, +} diff --git a/dpd-types/versions/src/initial/serdes.rs b/dpd-types/versions/src/initial/serdes.rs new file mode 100644 index 00000000..8b046397 --- /dev/null +++ b/dpd-types/versions/src/initial/serdes.rs @@ -0,0 +1,170 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Mapping of the logical lanes in a link to their physical instantiation in +/// the MAC/serdes interface. +// +// For each lane assigned to the port, this captures the mac block, the logical +// lane within the mac block, the physical rx and tx lanes, and the polarity of +// each. All of these values are determined by the physical layout of the +// board, should be identical across all sidecars with the same board revision, +// and shouldn't change from run to run. +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct LaneMap { + /// MAC block in the tofino ASIC + pub mac_block: u32, + /// logical lane within the mac block for each lane + pub logical_lane: Vec, + /// Rx logical->physical mapping + pub rx_phys: Vec, + /// Tx logical->physical mapping + pub tx_phys: Vec, + /// Rx polarity + pub rx_polarity: Vec, + /// Tx polarity + pub tx_polarity: Vec, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub enum Polarity { + Normal, + Inverted, +} + +/// Per-lane Rx signal information +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct RxSigInfo { + /// Rx signal detected + pub sig_detect: bool, + /// CDR lock achieved + pub phy_ready: bool, + /// Apparent PPM difference between local and remote + pub ppm: i32, +} + +/// Rx DFE adaptation information +#[derive(Default, Deserialize, Serialize, JsonSchema)] +pub struct DfeAdaptationState { + /// DFE complete + pub adapt_done: bool, + /// Total DFE attempts + pub adapt_cnt: u32, + /// DFE attempts since the last read + pub readapt_cnt: u32, + /// Times the signal was lost since the last read + pub link_lost_cnt: u32, +} + +/// Eye height(s) for a single lane in mv +#[derive(Deserialize, Serialize, JsonSchema)] +pub enum SerdesEye { + Nrz(f32), + Pam4 { eye1: f32, eye2: f32, eye3: f32 }, +} + +/// Signal encoding +#[derive(PartialEq, Deserialize, Serialize, JsonSchema)] +pub enum LaneEncoding { + /// Pulse Amplitude Modulation 4-level + Pam4, + /// Non-Return-to-Zero encoding + Nrz, + /// No encoding selected + None, +} + +/// Signal speed and encoding for a single lane +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct EncSpeed { + pub encoding: LaneEncoding, + pub gigabits: u32, +} + +/// State of a single lane during autonegotiation +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct AnStatus { + /// Can the link partner perform AN? + pub lp_an_ability: bool, + /// Allegedly: is the link up? In practice, this always seems to be false? + /// TODO: investigate this + pub link_status: bool, + /// Are we capable of AN? + pub an_ability: bool, + /// Remote fault detected + pub remote_fault: bool, + /// Is autonegotiation complete? + pub an_complete: bool, + /// has a base page been received? + pub page_rcvd: bool, + /// Is extended page format supported? + pub ext_np_status: bool, + /// A fault has been detected via the parallel detection function + pub parallel_detect_fault: bool, +} + +/// Link-training status for a single lane +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct LtStatus { + /// Readout for frame lock state + pub readout_state: u32, + /// Frame lock state + pub frame_lock: bool, + /// Local training finished + pub rx_trained: bool, + /// Training state readout + pub readout_training_state: u32, + /// Link training failed + pub training_failure: bool, + /// TX control to send training pattern + pub tx_training_data_en: bool, + /// Signal detect for PCS + pub sig_det: bool, + /// State machine readout for training arbiter + pub readout_txstate: u32, +} + +/// A collection of the data involved in the autonegiation/link-training process +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct AnLtStatus { + /// The base and extended pages received from the link partner + pub lp_pages: LpPages, + /// The per-lane status + pub lanes: Vec, +} + +/// Set of AN pages sent by our link partner +#[derive(Default, Deserialize, Serialize, JsonSchema)] +pub struct LpPages { + pub base_page: u64, + pub next_page1: u64, + pub next_page2: u64, +} + +/// The combined status of a lane, with respect to the autonegotiation / +/// link-training process. +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct LaneStatus { + /// Has a lane successfully completed autoneg and link training? + pub lane_done: bool, + /// Detailed autonegotiation status + pub lane_an_status: AnStatus, + /// Detailed link-training status + pub lane_lt_status: LtStatus, +} + +/// Reports the bit-error rate (BER) for a link. +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Serialize)] +pub struct Ber { + /// Counters of symbol errors per-lane. + pub symbol_errors: Vec, + /// Estimated BER per-lane. + pub ber: Vec, + /// Aggregate BER on the link. + pub total_ber: f32, +} diff --git a/dpd-types/versions/src/initial/switch_identifiers.rs b/dpd-types/versions/src/initial/switch_identifiers.rs new file mode 100644 index 00000000..af972b5d --- /dev/null +++ b/dpd-types/versions/src/initial/switch_identifiers.rs @@ -0,0 +1,43 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +// Re-export fuse types from aal. +pub use aal::{ + ChipRevision, DisabledFeatures, FrequencySettings, FuseData, + ManufacturingData, PartInfo, +}; + +/// Identifiers for a switch. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct SwitchIdentifiers { + /// Unique identifier for the chip. + pub sidecar_id: Uuid, + /// Asic backend (compiler target) responsible for these identifiers. + pub asic_backend: String, + /// Fabrication plant identifier. + pub fab: Option, + /// Lot identifier. + pub lot: Option, + /// Wafer number within the lot. + pub wafer: Option, + /// The wafer location as (x, y) coordinates on the wafer, represented as + /// an array due to the lack of tuple support in OpenAPI. + pub wafer_loc: Option<[i16; 2]>, + /// The model number of the switch being managed. + pub model: String, + /// The revision number of the switch being managed. + pub revision: u32, + /// The serial number of the switch being managed. + pub serial: String, + /// The slot number of the switch being managed. + /// + /// MGS uses u16 for this internally. + pub slot: u16, +} diff --git a/dpd-types/versions/src/initial/switch_port.rs b/dpd-types/versions/src/initial/switch_port.rs new file mode 100644 index 00000000..34c02194 --- /dev/null +++ b/dpd-types/versions/src/initial/switch_port.rs @@ -0,0 +1,109 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use common::ports::PortId; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use transceiver_controller::message::LedState; + +use super::transceivers::QsfpDevice; + +/// How a switch port is managed. +/// +/// The free-side devices in QSFP ports are complex devices, whose operation +/// usually involves coordinated steps through one or more state machines. For +/// example, when bringing up an optical link, a signal from the peer link must +/// be detected; then a signal recovered; equalizer gains set; etc. In +/// `Automatic` mode, all these kinds of steps are managed autonomously by +/// switch driver software. In `Manual` mode, none of these will occur -- a +/// switch port will only change in response to explicit requests from the +/// operator or Oxide control plane. +// +// NOTE: This is the parameter which marks a switch port _visible_ to the BF +// SDE. `Manual` means under our control, `Automatic` means visible to the SDE +// and under its control. +#[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + JsonSchema, + Ord, + PartialEq, + PartialOrd, + Serialize, +)] +#[serde(rename_all = "snake_case")] +pub enum ManagementMode { + /// A port is managed manually, by either the Oxide control plane or an + /// operator. + Manual, + /// A port is managed automatically by the switch software. + Automatic, +} + +/// The policy by which a port's LED is controlled. +#[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + JsonSchema, + Ord, + PartialEq, + PartialOrd, + Serialize, +)] +#[serde(rename_all = "snake_case")] +pub enum LedPolicy { + /// The default policy is for the LED to reflect the port's state itself. + /// + /// If the port is operating normally, the LED will be solid on. Without a + /// transceiver, the LED will be solid off. A blinking LED is used to + /// indicate an unsupported module or other failure on that port. + Automatic, + /// The LED is explicitly overridden by client requests. + Override, +} + +/// Information about a QSFP port's LED. +#[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + JsonSchema, + Ord, + PartialEq, + PartialOrd, + Serialize, +)] +pub struct Led { + /// The policy by which the LED is controlled. + pub policy: LedPolicy, + /// The state of the LED. + pub state: LedState, +} + +/// A physical port on the Sidecar switch. +// +// NOTE: This is the public API view onto `types::SwitchPort`. +#[derive(Clone, Debug, JsonSchema, Serialize)] +#[serde(rename = "SwitchPort")] +pub struct SwitchPortView { + /// The identifier for the switch port. + pub port_id: PortId, + /// Information for QSFP port functionality. This will be empty for non-QSFP + /// switch ports. + #[serde(flatten, skip_serializing_if = "Option::is_none")] + pub qsfp_device: Option, +} diff --git a/dpd-types/versions/src/initial/table.rs b/dpd-types/versions/src/initial/table.rs new file mode 100644 index 00000000..b1aa785f --- /dev/null +++ b/dpd-types/versions/src/initial/table.rs @@ -0,0 +1,94 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use std::collections::BTreeMap; + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Each entry in a P4 table is addressed by matching against a set of key +/// values. If an entry is found, an action is taken with an action-specific +/// set of arguments. +/// +/// Note: each entry will have the same key fields and each instance of any +/// given action will have the same argument names, so a vector of TableEntry +/// structs will contain a signficant amount of redundant data. We could +/// consider tightening this up by including a schema of sorts in the "struct +/// Table". +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct TableEntry { + /// Names and values of each of the key fields. + pub keys: BTreeMap, + /// Name of the action to take on a match + pub action: String, + /// Names and values for the arguments to the action implementation. + pub action_args: BTreeMap, +} + +/// Represents the contents of a P4 table +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct Table { + /// A user-friendly name for the table + pub name: String, + /// The maximum number of entries the table can hold + pub size: usize, + /// There will be an entry for each populated slot in the table + pub entries: Vec, +} + +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +pub struct TableCounterEntry { + /// Names and values of each of the key fields. + pub keys: BTreeMap, + /// Counter values + pub data: aal::CounterData, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct TableParam { + pub table: String, +} + +/// Request body for dumping table entries. +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +pub struct TableDumpRequest { + /// Fully-qualified P4 table name (e.g. "Ingress.services.service"). + pub table_name: String, + /// If true, read entries from ASIC hardware via indirect register + /// reads instead of the SDE's software shadow. + pub from_hw: bool, +} + +/// A key field from a table entry dump. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct TableDumpKeyField { + /// Name of the field + pub name: String, + /// Key value + pub value: String, + /// For ternary fields: the mask. For LPM: the prefix length. + pub mask: Option, +} + +/// A single entry from a table dump. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct TableDumpEntry { + /// Action associated with an entry. + pub action: String, + /// Match fields for an entry. + pub match_fields: Vec, +} + +/// Result of a table dump operation. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct TableDumpResult { + /// Name of the table. + pub table_name: String, + /// Number of entries in the table. + pub num_entries: usize, + /// Table entries. + pub entries: Vec, +} diff --git a/dpd-types/versions/src/initial/transceivers.rs b/dpd-types/versions/src/initial/transceivers.rs new file mode 100644 index 00000000..d70ccf5d --- /dev/null +++ b/dpd-types/versions/src/initial/transceivers.rs @@ -0,0 +1,108 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use std::time::Instant; + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use transceiver_controller::{PowerMode, VendorInfo}; + +use super::switch_port::ManagementMode; + +/// A QSFP switch port. +/// +/// This includes the hardware controls and information relevant to QSFP ports +/// specifically. For example, these ports are on the front IO panel of the +/// switch, and have LEDs used for status and attention. This includes the state +/// and controls for those LEDs. It also includes information about the +/// free-side QSFP module, should one be plugged in. +#[derive(Clone, Debug, JsonSchema, Serialize)] +pub struct QsfpDevice { + /// Details about a transceiver module inserted into the switch port. + /// + /// If there is no transceiver at all, this will be `None`. + pub transceiver: Option, + /// How the QSFP device is managed. + /// + /// See `ManagementMode` for details. + pub management_mode: ManagementMode, +} + +/// The cause of a fault on a transceiver. +#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum FaultReason { + /// An error occurred accessing the transceiver. + Failed, + /// Power was enabled, but did not come up in the requisite time. + PowerTimeout, + /// Power was enabled and later lost. + PowerLost, + /// The service processor disabled the transceiver. + /// + /// The SP is responsible for monitoring the thermal data from the + /// transceivers, and controlling the fans to compensate. If a module's + /// thermal data cannot be read, the SP may completely disable the + /// transceiver to ensure it cannot overheat the Sidecar. + DisabledBySp, +} + +/// The state of a transceiver in a QSFP switch port. +#[derive(Clone, Debug, JsonSchema, Serialize)] +#[serde(rename_all = "snake_case", tag = "state", content = "info")] +pub enum Transceiver { + /// The transceiver could not be managed due to a power fault. + Faulted(FaultReason), + /// A transceiver was present, but unsupported and automatically disabled. + Unsupported, + /// A transceiver is present and supported. + Supported(TransceiverInfo), +} + +/// Information about a QSFP transceiver. +/// +/// This stores the most relevant information about a transceiver module, such +/// as vendor info or power. Each field may be missing, indicating it could not +/// be determined. +#[derive(Clone, Debug, JsonSchema, Serialize)] +pub struct TransceiverInfo { + /// Vendor and part identifying information. + /// + /// The information will not be populated if it could not be read. + pub vendor_info: Option, + /// True if the module is currently in reset. + pub in_reset: Option, + /// True if there is a pending interrupt on the module. + pub interrupt_pending: Option, + /// The power mode of the transceiver. + pub power_mode: Option, + /// The electrical mode of the transceiver. + /// + /// See [`ElectricalMode`] for details. + pub electrical_mode: ElectricalMode, + // The instant at which we first saw this transceiver. + // + // This is only used to support initially blinking the transceiver to + // acknowledge insertion. + #[serde(skip)] + pub first_seen: Instant, +} + +/// The electrical mode of a QSFP-capable port. +/// +/// QSFP ports can be broken out into one of several different electrical +/// configurations or modes. This describes how the transmit/receive lanes are +/// grouped into a single, logical link. +/// +/// Note that the electrical mode may only be changed if there are no links +/// within the port, _and_ if the inserted QSFP module actually supports this +/// mode. +#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema, Serialize)] +pub enum ElectricalMode { + /// All transmit/receive lanes are used for a single link. + #[default] + Single, +} diff --git a/dpd-types/versions/src/latest.rs b/dpd-types/versions/src/latest.rs new file mode 100644 index 00000000..3350c606 --- /dev/null +++ b/dpd-types/versions/src/latest.rs @@ -0,0 +1,210 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Re-exports of the latest versions of all published types. + +pub mod arp { + pub use crate::v1::arp::ArpEntry; + pub use crate::v1::arp::ArpToken; + pub use crate::v1::arp::Ipv4ArpParam; + pub use crate::v1::arp::Ipv4Token; + pub use crate::v1::arp::Ipv6ArpParam; + pub use crate::v1::arp::Ipv6Token; +} + +pub mod counters { + pub use crate::v1::counters::CounterPath; + pub use crate::v1::counters::CounterSync; + pub use crate::v1::counters::LinkFecRSCounters; + pub use crate::v1::counters::LinkPcsCounters; + pub use crate::v1::counters::LinkRMonCounters; + pub use crate::v1::counters::LinkRMonCountersAll; +} + +pub mod fault { + pub use crate::v1::fault::Fault; + pub use crate::v1::fault::FaultCondition; +} + +pub mod link { + pub use crate::v1::link::LinkCreate; + pub use crate::v1::link::LinkEvent; + pub use crate::v1::link::LinkFilter; + pub use crate::v1::link::LinkFsmCounter; + pub use crate::v1::link::LinkFsmCounters; + pub use crate::v1::link::LinkId; + pub use crate::v1::link::LinkIpv4Path; + pub use crate::v1::link::LinkIpv6Path; + pub use crate::v1::link::LinkPath; + pub use crate::v1::link::LinkState; + pub use crate::v1::link::LinkUpCounter; + pub use crate::v1::link::TfportData; + + pub use crate::v11::link::LinkHistory; + + pub use crate::v12::link::LinkView; + pub use crate::v12::link::MsDuration; +} + +pub mod loopback { + pub use crate::v1::loopback::LoopbackIpv4Path; + pub use crate::v1::loopback::LoopbackIpv6Path; +} + +pub mod mcast { + pub use crate::v1::mcast::Direction; + pub use crate::v1::mcast::ExternalForwarding; + pub use crate::v1::mcast::InternalForwarding; + pub use crate::v1::mcast::MulticastGroupId; + pub use crate::v1::mcast::MulticastGroupIdParam; + pub use crate::v1::mcast::MulticastGroupIpParam; + pub use crate::v1::mcast::MulticastGroupMember; + + pub use crate::v7::mcast::IpSrc; + pub use crate::v7::mcast::MulticastGroupCreateExternalEntry; + + pub use crate::v8::mcast::Error; + pub use crate::v8::mcast::MulticastGroupCreateUnderlayEntry; + pub use crate::v8::mcast::MulticastGroupExternalResponse; + pub use crate::v8::mcast::MulticastGroupResponse; + pub use crate::v8::mcast::MulticastGroupTagQuery; + pub use crate::v8::mcast::MulticastGroupUnderlayResponse; + pub use crate::v8::mcast::MulticastGroupUpdateExternalEntry; + pub use crate::v8::mcast::MulticastGroupUpdateUnderlayEntry; + pub use crate::v8::mcast::MulticastTag; + pub use crate::v8::mcast::MulticastTagPath; + pub use crate::v8::mcast::MulticastUnderlayGroupIpParam; + pub use crate::v8::mcast::UnderlayMulticastIpv6; + + pub use crate::impls::mcast::MAX_TAG_LENGTH; + pub use crate::impls::mcast::MulticastTagParseError; +} + +pub mod misc { + pub use crate::v1::misc::BuildInfo; + pub use crate::v1::misc::TagPath; +} + +pub mod nat { + pub use crate::v1::nat::NatIpv4Path; + pub use crate::v1::nat::NatIpv4PortPath; + pub use crate::v1::nat::NatIpv4RangePath; + pub use crate::v1::nat::NatIpv6Path; + pub use crate::v1::nat::NatIpv6PortPath; + pub use crate::v1::nat::NatIpv6RangePath; + pub use crate::v1::nat::NatToken; +} + +pub mod port { + pub use crate::v1::port::FreeChannels; + pub use crate::v1::port::LinkSettings; + pub use crate::v1::port::PortCreateParams; + pub use crate::v1::port::PortIdPathParams; + pub use crate::v1::port::PortIpv4Path; + pub use crate::v1::port::PortIpv6Path; + pub use crate::v1::port::PortSettings; + pub use crate::v1::port::PortSettingsTag; + pub use crate::v1::port::PortToken; +} + +pub mod port_map { + pub use crate::v1::port_map::BackplaneCableLeg; + pub use crate::v1::port_map::BackplaneLink; + pub use crate::v1::port_map::Error; + pub use crate::v1::port_map::SIDECAR_REV_AB_BACKPLANE_MAP; + pub use crate::v1::port_map::SidecarCableLeg; + pub use crate::v1::port_map::SidecarConnector; +} + +pub mod route { + pub use crate::v1::route::Ipv4Route; + pub use crate::v1::route::Ipv4RouteToken; + pub use crate::v1::route::Ipv6Route; + pub use crate::v1::route::Ipv6RouteToken; + pub use crate::v1::route::Ipv6RouteUpdate; + pub use crate::v1::route::Ipv6Routes; + pub use crate::v1::route::RoutePathV4; + pub use crate::v1::route::RoutePathV6; + pub use crate::v1::route::RouteSettingsV4; + pub use crate::v1::route::RouteSettingsV6; + pub use crate::v1::route::RouteTargetIpv6Path; + + pub use crate::v3::route::AttachedSubnetToken; + pub use crate::v3::route::SubnetPath; + + pub use crate::v4::route::Ipv4Routes; + pub use crate::v4::route::Route; + pub use crate::v4::route::RouteTarget; + + pub use crate::v6::route::Ipv4RouteUpdate; + pub use crate::v6::route::RouteTargetIpv4Path; +} + +pub mod serdes { + pub use crate::v1::serdes::AnLtStatus; + pub use crate::v1::serdes::AnStatus; + pub use crate::v1::serdes::Ber; + pub use crate::v1::serdes::DfeAdaptationState; + pub use crate::v1::serdes::EncSpeed; + pub use crate::v1::serdes::LaneEncoding; + pub use crate::v1::serdes::LaneMap; + pub use crate::v1::serdes::LaneStatus; + pub use crate::v1::serdes::LpPages; + pub use crate::v1::serdes::LtStatus; + pub use crate::v1::serdes::Polarity; + pub use crate::v1::serdes::RxSigInfo; + pub use crate::v1::serdes::SerdesEye; +} + +pub mod snapshot { + pub use crate::v9::snapshot::SnapshotCreate; + pub use crate::v9::snapshot::SnapshotDirection; + pub use crate::v9::snapshot::SnapshotFieldScope; + pub use crate::v9::snapshot::SnapshotFieldValue; + pub use crate::v9::snapshot::SnapshotResult; + pub use crate::v9::snapshot::SnapshotScopeRequest; + pub use crate::v9::snapshot::SnapshotStageResult; + pub use crate::v9::snapshot::SnapshotTableResult; + pub use crate::v9::snapshot::SnapshotTrigger; + pub use crate::v9::snapshot::TableDumpOptions; +} + +pub mod switch_identifiers { + pub use crate::v1::switch_identifiers::ChipRevision; + pub use crate::v1::switch_identifiers::DisabledFeatures; + pub use crate::v1::switch_identifiers::FrequencySettings; + pub use crate::v1::switch_identifiers::FuseData; + pub use crate::v1::switch_identifiers::ManufacturingData; + pub use crate::v1::switch_identifiers::PartInfo; + + pub use crate::v10::switch_identifiers::SwitchIdentifiers; +} + +pub mod switch_port { + pub use crate::v1::switch_port::Led; + pub use crate::v1::switch_port::LedPolicy; + pub use crate::v1::switch_port::ManagementMode; + pub use crate::v1::switch_port::SwitchPortView; +} + +pub mod table { + pub use crate::v1::table::Table; + pub use crate::v1::table::TableCounterEntry; + pub use crate::v1::table::TableDumpEntry; + pub use crate::v1::table::TableDumpKeyField; + pub use crate::v1::table::TableDumpRequest; + pub use crate::v1::table::TableDumpResult; + pub use crate::v1::table::TableEntry; + pub use crate::v1::table::TableParam; +} + +pub mod transceivers { + pub use crate::v1::transceivers::ElectricalMode; + pub use crate::v1::transceivers::FaultReason; + pub use crate::v1::transceivers::QsfpDevice; + pub use crate::v1::transceivers::Transceiver; + pub use crate::v1::transceivers::TransceiverInfo; +} diff --git a/dpd-types/versions/src/lib.rs b/dpd-types/versions/src/lib.rs new file mode 100644 index 00000000..5f99f707 --- /dev/null +++ b/dpd-types/versions/src/lib.rs @@ -0,0 +1,55 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Versioned types for the DPD API. +//! +//! # Adding a new API version +//! +//! When adding a new API version N with added or changed types: +//! +//! 1. Create `/mod.rs`, where `` is the lowercase +//! form of the new version's identifier, as defined in the API trait's +//! `api_versions!` macro. +//! +//! 2. Add to the end of this list: +//! +//! ```rust,ignore +//! #[path = "/mod.rs"] +//! pub mod vN; +//! ``` +//! +//! 3. Add your types to the new module, mirroring the module structure from +//! earlier versions. +//! +//! 4. Update `latest.rs` with new and updated types from the new version. +//! +//! For more information, see the [detailed guide] and [RFD 619]. +//! +//! [detailed guide]: https://github.com/oxidecomputer/dropshot-api-manager/blob/main/guides/new-version.md +//! [RFD 619]: https://rfd.shared.oxide.computer/rfd/619 + +mod impls; +pub mod latest; +#[path = "initial/mod.rs"] +pub mod v1; +#[path = "asic_details/mod.rs"] +pub mod v10; +#[path = "wallclock_history/mod.rs"] +pub mod v11; +#[path = "prbs_error_tracking/mod.rs"] +pub mod v12; +#[path = "attached_subnets/mod.rs"] +pub mod v3; +#[path = "v4_over_v6_routes/mod.rs"] +pub mod v4; +#[path = "consolidated_v4_routes/mod.rs"] +pub mod v6; +#[path = "mcast_source_filter_any/mod.rs"] +pub mod v7; +#[path = "mcast_strict_underlay/mod.rs"] +pub mod v8; +#[path = "snapshot/mod.rs"] +pub mod v9; diff --git a/dpd-types/versions/src/mcast_source_filter_any/mcast.rs b/dpd-types/versions/src/mcast_source_filter_any/mcast.rs new file mode 100644 index 00000000..c2856a32 --- /dev/null +++ b/dpd-types/versions/src/mcast_source_filter_any/mcast.rs @@ -0,0 +1,167 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use std::net::IpAddr; + +use oxnet::Ipv4Net; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::v1; +use crate::v1::mcast::{ + ExternalForwarding, InternalForwarding, MulticastGroupId, + MulticastGroupUnderlayResponse, +}; + +/// Source filter match key for multicast traffic. +/// +/// For SSM groups, use `Exact` with specific source addresses. +/// For ASM groups with any-source filtering, use `Any`. +#[derive( + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Deserialize, + Serialize, + JsonSchema, +)] +pub enum IpSrc { + /// Exact match for the source IP address. + Exact(IpAddr), + /// Match any source address (0.0.0.0/0 or ::/0 depending on group IP version). + Any, +} + +impl From for IpSrc { + fn from(src: v1::mcast::IpSrc) -> Self { + match src { + v1::mcast::IpSrc::Exact(ip) => IpSrc::Exact(ip), + v1::mcast::IpSrc::Subnet(net) if net.width() == 0 => IpSrc::Any, + v1::mcast::IpSrc::Subnet(net) => { + IpSrc::Exact(IpAddr::V4(net.addr())) + } + } + } +} + +impl From for v1::mcast::IpSrc { + fn from(src: IpSrc) -> Self { + match src { + IpSrc::Exact(ip) => v1::mcast::IpSrc::Exact(ip), + IpSrc::Any => { + // v1-v4 API only supported IPv4 subnet matching. + v1::mcast::IpSrc::Subnet( + Ipv4Net::new(std::net::Ipv4Addr::UNSPECIFIED, 0).unwrap(), + ) + } + } + } +} + +/// A multicast group configuration for POST requests for external (to the +/// rack) groups. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupCreateExternalEntry { + pub group_ip: IpAddr, + /// Tag for validating update/delete requests. If a tag is not provided, + /// one is auto-generated as `{uuid}:{group_ip}`. + pub tag: Option, + pub internal_forwarding: InternalForwarding, + pub external_forwarding: ExternalForwarding, + pub sources: Option>, +} + +impl From + for MulticastGroupCreateExternalEntry +{ + fn from(entry: v1::mcast::MulticastGroupCreateExternalEntry) -> Self { + Self { + group_ip: entry.group_ip, + tag: entry.tag, + internal_forwarding: entry.internal_forwarding, + external_forwarding: entry.external_forwarding, + sources: entry + .sources + .map(|s| s.into_iter().map(Into::into).collect()), + } + } +} + +/// Response structure for external multicast group operations. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupExternalResponse { + pub group_ip: IpAddr, + pub external_group_id: MulticastGroupId, + pub tag: Option, + pub internal_forwarding: InternalForwarding, + pub external_forwarding: ExternalForwarding, + pub sources: Option>, +} + +impl From + for v1::mcast::MulticastGroupExternalResponse +{ + fn from(resp: MulticastGroupExternalResponse) -> Self { + Self { + group_ip: resp.group_ip, + external_group_id: resp.external_group_id, + tag: resp.tag, + internal_forwarding: resp.internal_forwarding, + external_forwarding: resp.external_forwarding, + sources: resp + .sources + .map(|sources| sources.into_iter().map(Into::into).collect()), + } + } +} + +/// A multicast group update entry for PUT requests for external groups. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupUpdateExternalEntry { + pub tag: Option, + pub internal_forwarding: InternalForwarding, + pub external_forwarding: ExternalForwarding, + pub sources: Option>, +} + +impl From + for MulticastGroupUpdateExternalEntry +{ + fn from(entry: v1::mcast::MulticastGroupUpdateExternalEntry) -> Self { + Self { + tag: entry.tag, + internal_forwarding: entry.internal_forwarding, + external_forwarding: entry.external_forwarding, + sources: entry + .sources + .map(|s| s.into_iter().map(Into::into).collect()), + } + } +} + +/// Unified response type for operations that return mixed group types. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "kind", rename_all = "snake_case")] +pub enum MulticastGroupResponse { + Underlay(MulticastGroupUnderlayResponse), + External(MulticastGroupExternalResponse), +} + +impl From for v1::mcast::MulticastGroupResponse { + fn from(resp: MulticastGroupResponse) -> Self { + match resp { + MulticastGroupResponse::Underlay(u) => { + // v7 underlay is re-exported from v1, so it's the same type. + Self::Underlay(u) + } + MulticastGroupResponse::External(e) => Self::External(e.into()), + } + } +} diff --git a/dpd-types/versions/src/mcast_source_filter_any/mod.rs b/dpd-types/versions/src/mcast_source_filter_any/mod.rs new file mode 100644 index 00000000..903aeac1 --- /dev/null +++ b/dpd-types/versions/src/mcast_source_filter_any/mod.rs @@ -0,0 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Version `MCAST_SOURCE_FILTER_ANY` of the DPD API. +//! +//! Changed `IpSrc` from `{Exact, Subnet}` to `{Exact, Any}`. External +//! multicast types updated to use the new `IpSrc`. + +pub mod mcast; diff --git a/dpd-types/versions/src/mcast_strict_underlay/mcast.rs b/dpd-types/versions/src/mcast_strict_underlay/mcast.rs new file mode 100644 index 00000000..0a1e246b --- /dev/null +++ b/dpd-types/versions/src/mcast_strict_underlay/mcast.rs @@ -0,0 +1,253 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Public types for multicast group management introduced in the +//! `MCAST_STRICT_UNDERLAY` version. + +use std::net::{IpAddr, Ipv6Addr}; + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::v1; +use crate::v1::mcast::{ + ExternalForwarding, InternalForwarding, MulticastGroupId, + MulticastGroupMember, +}; +use crate::v7; +use crate::v7::mcast::IpSrc; + +/// A validated underlay multicast IPv6 address. +/// +/// Underlay multicast addresses must be within the subnet allocated by Omicron +/// for rack-internal multicast traffic (ff04::/64). This is a subset of the +/// admin-local scope (ff04::/16) defined in RFC 4291. +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Deserialize, + Serialize, + JsonSchema, +)] +#[serde(try_from = "Ipv6Addr", into = "Ipv6Addr")] +pub struct UnderlayMulticastIpv6(pub(crate) Ipv6Addr); + +impl TryFrom for UnderlayMulticastIpv6 { + type Error = Error; + + fn try_from(addr: Ipv6Addr) -> Result { + Self::new(addr) + } +} + +impl From for Ipv6Addr { + fn from(addr: UnderlayMulticastIpv6) -> Self { + addr.0 + } +} + +impl From for v1::mcast::AdminScopedIpv6 { + fn from(underlay: UnderlayMulticastIpv6) -> Self { + // UnderlayMulticastIpv6 is a subset of AdminScopedIpv6, so this is safe. + Self::new(underlay.into()) + .expect("UnderlayMulticastIpv6 is within AdminScopedIpv6 range") + } +} + +impl TryFrom for UnderlayMulticastIpv6 { + type Error = String; + + fn try_from( + admin: v1::mcast::AdminScopedIpv6, + ) -> Result { + UnderlayMulticastIpv6::new(admin.into()).map_err(|e| e.to_string()) + } +} + +#[derive(Clone, Debug, thiserror::Error)] +pub enum Error { + #[error( + "Address {0} is not in underlay multicast subnet (must be ff04::/64)" + )] + InvalidUnderlayMulticastIp(Ipv6Addr), + #[error("Invalid IPv6 address '{0}': {1}")] + InvalidIpv6Address(String, std::net::AddrParseError), +} + +/// A multicast group configuration for POST requests for internal (to the rack) +/// groups. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupCreateUnderlayEntry { + pub group_ip: UnderlayMulticastIpv6, + /// Tag for validating update/delete requests. If a tag is not provided, + /// one is auto-generated as `{uuid}:{group_ip}`. + pub tag: Option, + pub members: Vec, +} + +/// Represents a multicast replication entry for PUT requests for internal +/// (to the rack) groups. +/// +/// Tag validation is performed via the `tag` query parameter. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupUpdateUnderlayEntry { + pub members: Vec, +} + +impl From + for MulticastGroupUpdateUnderlayEntry +{ + fn from(entry: v1::mcast::MulticastGroupUpdateUnderlayEntry) -> Self { + Self { members: entry.members } + } +} + +/// A multicast group update entry for PUT requests for external (to the rack) +/// groups. +/// +/// Tag validation is performed via the `tag` query parameter. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupUpdateExternalEntry { + pub internal_forwarding: InternalForwarding, + pub external_forwarding: ExternalForwarding, + pub sources: Option>, +} + +impl From + for MulticastGroupUpdateExternalEntry +{ + fn from(entry: v7::mcast::MulticastGroupUpdateExternalEntry) -> Self { + Self { + internal_forwarding: entry.internal_forwarding, + external_forwarding: entry.external_forwarding, + sources: entry.sources, + } + } +} + +/// Response structure for underlay/internal multicast group operations. +/// These groups handle admin-local IPv6 multicast with full replication. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupUnderlayResponse { + pub group_ip: UnderlayMulticastIpv6, + pub external_group_id: MulticastGroupId, + pub underlay_group_id: MulticastGroupId, + /// Tag for validating update/delete requests. Always present and generated + /// as `{uuid}:{group_ip}` if not provided at creation time. + pub tag: String, + pub members: Vec, +} + +impl From + for v1::mcast::MulticastGroupUnderlayResponse +{ + fn from(resp: MulticastGroupUnderlayResponse) -> Self { + Self { + group_ip: resp.group_ip.into(), + external_group_id: resp.external_group_id, + underlay_group_id: resp.underlay_group_id, + tag: Some(resp.tag), + members: resp.members, + } + } +} + +/// Response structure for external multicast group operations. +/// These groups handle IPv4 and non-admin-local IPv6 multicast via NAT targets. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupExternalResponse { + pub group_ip: IpAddr, + pub external_group_id: MulticastGroupId, + /// Tag for validating update/delete requests. Always present and generated + /// as `{uuid}:{group_ip}` if not provided at creation time. + pub tag: String, + pub internal_forwarding: InternalForwarding, + pub external_forwarding: ExternalForwarding, + pub sources: Option>, +} + +impl From + for v7::mcast::MulticastGroupExternalResponse +{ + fn from(resp: MulticastGroupExternalResponse) -> Self { + Self { + group_ip: resp.group_ip, + external_group_id: resp.external_group_id, + tag: Some(resp.tag), + internal_forwarding: resp.internal_forwarding, + external_forwarding: resp.external_forwarding, + sources: resp.sources, + } + } +} + +/// Unified response type for operations that return mixed group types. +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "kind", rename_all = "snake_case")] +pub enum MulticastGroupResponse { + Underlay(MulticastGroupUnderlayResponse), + External(MulticastGroupExternalResponse), +} + +impl From for v7::mcast::MulticastGroupResponse { + fn from(resp: MulticastGroupResponse) -> Self { + match resp { + MulticastGroupResponse::Underlay(u) => Self::Underlay(u.into()), + MulticastGroupResponse::External(e) => Self::External(e.into()), + } + } +} + +/// Tag for identifying and authorizing multicast group operations. +/// +/// Tag format: 1 to 80 ASCII bytes containing alphanumeric characters, +/// hyphens, underscores, colons, or periods. Default format is +/// `{uuid}:{group_ip}`. +#[derive( + Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema, +)] +pub struct MulticastTag( + #[schemars( + length(min = 1, max = 80), + regex(pattern = r"^[a-zA-Z0-9_.:-]+$") + )] + pub String, +); + +/// Path parameter for multicast tag-based operations. +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct MulticastTagPath { + pub tag: MulticastTag, +} + +impl From for MulticastTagPath { + fn from(path: v1::misc::TagPath) -> Self { + Self { tag: path.tag.into() } + } +} + +/// Tag for multicast group validation. +/// +/// All groups have tags (auto-generated at creation if not provided). +/// The provided tag must match the group's existing tag. +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupTagQuery { + /// Tag that must match the group's existing tag. + pub tag: MulticastTag, +} + +/// Used to identify an underlay multicast group by IPv6 address within +/// the underlay multicast subnet (ff04::/64). +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct MulticastUnderlayGroupIpParam { + pub group_ip: UnderlayMulticastIpv6, +} diff --git a/dpd-types/versions/src/mcast_strict_underlay/mod.rs b/dpd-types/versions/src/mcast_strict_underlay/mod.rs new file mode 100644 index 00000000..686dc2cb --- /dev/null +++ b/dpd-types/versions/src/mcast_strict_underlay/mod.rs @@ -0,0 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Version `MCAST_STRICT_UNDERLAY` of the DPD API. +//! +//! Introduced `UnderlayMulticastIpv6` (ff04::/64 validation), `MulticastTag`, +//! `MulticastGroupTagQuery`, and `MulticastTagPath`. Changed underlay multicast +//! types to use `UnderlayMulticastIpv6` instead of `AdminScopedIpv6`, and +//! changed response `tag` fields from `Option` to `String`. + +pub mod mcast; diff --git a/dpd-api/src/v12.rs b/dpd-types/versions/src/prbs_error_tracking/link.rs similarity index 54% rename from dpd-api/src/v12.rs rename to dpd-types/versions/src/prbs_error_tracking/link.rs index 0bacfd41..aec3f8b2 100644 --- a/dpd-api/src/v12.rs +++ b/dpd-types/versions/src/prbs_error_tracking/link.rs @@ -4,71 +4,35 @@ // // Copyright 2026 Oxide Computer Company -//! Types from API version 10 (ASIC_DETAILS) that changed in -//! version 11 (PRBS_IMPROVEMENT). -//! -//! Dropped API support for PRBS modes not supported by the Tofino ASIC. - -use std::convert::TryFrom; - +use common::{ + network::MacAddr, + ports::{PortFec, PortId, PortMedia, PortPrbsMode, PortSpeed}, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use common::network::MacAddr; -use common::ports::{PortFec, PortId, PortMedia, PortSpeed}; -use dpd_types::link::{LinkId, LinkState}; - -/// Legal PRBS modes -#[derive( - Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize, JsonSchema, -)] -pub enum PortPrbsMode { - Mode31, - Mode23, - Mode15, - Mode13, - Mode11, - Mode9, - Mode7, - Mission, // i.e. PRBS disabled -} - -impl TryFrom for common::ports::PortPrbsMode { - type Error = String; +use crate::v1; - fn try_from(x: PortPrbsMode) -> Result { - match x { - PortPrbsMode::Mode9 => Ok(common::ports::PortPrbsMode::Mode9), - PortPrbsMode::Mode13 => Ok(common::ports::PortPrbsMode::Mode13), - PortPrbsMode::Mode15 => Ok(common::ports::PortPrbsMode::Mode15), - PortPrbsMode::Mode31 => Ok(common::ports::PortPrbsMode::Mode31), - PortPrbsMode::Mission => Ok(common::ports::PortPrbsMode::Mission), - x => Err(format!("{x:?} is not a supported PRBS mode")), - } - } -} +// `MsDuration` lives alongside `LinkView` because it is the body type for the +// link-scoped PRBS bit-error measurement endpoint introduced in this version. -impl From for PortPrbsMode { - fn from(x: common::ports::PortPrbsMode) -> Self { - match x { - common::ports::PortPrbsMode::Mode9 => PortPrbsMode::Mode9, - common::ports::PortPrbsMode::Mode13 => PortPrbsMode::Mode13, - common::ports::PortPrbsMode::Mode15 => PortPrbsMode::Mode15, - common::ports::PortPrbsMode::Mode31 => PortPrbsMode::Mode31, - common::ports::PortPrbsMode::Mission => PortPrbsMode::Mission, - } - } +/// Duration in milliseconds +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct MsDuration { + /// Duration in milliseconds + pub ms: u32, } /// An Ethernet-capable link within a switch port. // -// NOTE: This is a view onto `crate::link::Link`. +// NOTE: This is a view onto `dpd::link::Link`. #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct Link { +#[serde(rename = "Link")] +pub struct LinkView { /// The switch port on which this link exists. pub port_id: PortId, /// The `LinkId` within the switch port for this link. - pub link_id: LinkId, + pub link_id: v1::link::LinkId, /// The Tofino connector number associated with this link. pub tofino_connector: u16, /// The lower-level ASIC ID used to refer to this object in the switch @@ -93,15 +57,15 @@ pub struct Link { /// The PRBS mode. pub prbs: PortPrbsMode, /// The state of the Ethernet link. - pub link_state: LinkState, + pub link_state: v1::link::LinkState, /// The MAC address for the link. pub address: MacAddr, /// The link is configured for IPv6 use pub ipv6_enabled: bool, } -impl From for Link { - fn from(value: dpd_types::views::Link) -> Self { +impl From for v1::link::LinkView { + fn from(value: LinkView) -> Self { Self { port_id: value.port_id, link_id: value.link_id, diff --git a/dpd-types/versions/src/prbs_error_tracking/mod.rs b/dpd-types/versions/src/prbs_error_tracking/mod.rs new file mode 100644 index 00000000..aa879fc8 --- /dev/null +++ b/dpd-types/versions/src/prbs_error_tracking/mod.rs @@ -0,0 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Version `PRBS_ERROR_TRACKING` of the DPD API. +//! +//! Dropped support for PRBS modes not producible by the Tofino ASIC, which +//! changes the published `LinkView` type; added an endpoint for measuring the +//! PRBS bit-error rate with a `MsDuration` body. + +pub mod link; +pub mod port; diff --git a/dpd-types/versions/src/prbs_error_tracking/port.rs b/dpd-types/versions/src/prbs_error_tracking/port.rs new file mode 100644 index 00000000..28f5c68a --- /dev/null +++ b/dpd-types/versions/src/prbs_error_tracking/port.rs @@ -0,0 +1,46 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Conversions between the v1-versioned `PortPrbsMode` and the unversioned +//! `PortPrbsMode` used from this version onward. +//! +//! From `PRBS_ERROR_TRACKING` onward, the PRBS mode published by the API is +//! `common::ports::PortPrbsMode`: it is no longer carried as a versioned type +//! in this crate, because it now matches the set of modes the Tofino ASIC can +//! actually produce. The module below defines the bidirectional conversion +//! with the prior `v1::port::PortPrbsMode`, which still appears in the v1 +//! endpoint surface. + +use common::ports::PortPrbsMode; + +use crate::v1; + +impl From for v1::port::PortPrbsMode { + fn from(value: PortPrbsMode) -> Self { + match value { + PortPrbsMode::Mode31 => v1::port::PortPrbsMode::Mode31, + PortPrbsMode::Mode15 => v1::port::PortPrbsMode::Mode15, + PortPrbsMode::Mode13 => v1::port::PortPrbsMode::Mode13, + PortPrbsMode::Mode9 => v1::port::PortPrbsMode::Mode9, + PortPrbsMode::Mission => v1::port::PortPrbsMode::Mission, + } + } +} + +impl TryFrom for PortPrbsMode { + type Error = String; + + fn try_from(value: v1::port::PortPrbsMode) -> Result { + match value { + v1::port::PortPrbsMode::Mode9 => Ok(PortPrbsMode::Mode9), + v1::port::PortPrbsMode::Mode13 => Ok(PortPrbsMode::Mode13), + v1::port::PortPrbsMode::Mode15 => Ok(PortPrbsMode::Mode15), + v1::port::PortPrbsMode::Mode31 => Ok(PortPrbsMode::Mode31), + v1::port::PortPrbsMode::Mission => Ok(PortPrbsMode::Mission), + x => Err(format!("{x:?} is not a supported PRBS mode")), + } + } +} diff --git a/dpd-types/versions/src/snapshot/mod.rs b/dpd-types/versions/src/snapshot/mod.rs new file mode 100644 index 00000000..9d4f6c82 --- /dev/null +++ b/dpd-types/versions/src/snapshot/mod.rs @@ -0,0 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Version `SNAPSHOT` of the DPD API. +//! +//! Added PHV snapshot capture and scope-checking endpoints with +//! `SnapshotDirection`, `SnapshotTrigger`, `SnapshotCreate`, `SnapshotResult`, +//! and related types. Also added `TableDumpOptions`. + +pub mod snapshot; diff --git a/dpd-types/versions/src/snapshot/snapshot.rs b/dpd-types/versions/src/snapshot/snapshot.rs new file mode 100644 index 00000000..e30847d4 --- /dev/null +++ b/dpd-types/versions/src/snapshot/snapshot.rs @@ -0,0 +1,165 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Direction of a PHV snapshot. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub enum SnapshotDirection { + /// Take snapshot of ingress pipeline + Ingress, + /// Take snapshot of egress pipeline + Egress, +} + +/// A trigger field for a snapshot, with hex-encoded value and mask. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SnapshotTrigger { + /// Name of the field to capture. + /// + /// Must match what's in the phv ingress or phv egress section of + /// /opt/oxide/dendrite/sidecar/pipe/sidecar.bfa. + pub field: String, + /// Hex-encoded value (e.g. "0x112233445566") + pub value: String, + /// Hex-encoded mask (e.g. "0xffffffffffff") + pub mask: String, +} + +/// Request body for creating and capturing a PHV snapshot. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SnapshotCreate { + /// Index of the pipeline to capture. Typically this will be 0 through 3. + /// Different ports map to different pipelines. + pub pipe: u32, + /// Tofino hardware stage to start capturing at. + /// + /// See /opt/oxide/dendrite/sidecar/pipe/sidecar.bfa to get a sense of + /// stage layout. + pub start_stage: u8, + /// Tofino hardware stage to stop capturing at. + /// + /// See /opt/oxide/dendrite/sidecar/pipe/sidecar.bfa to get a sense of + /// stage layout. + pub end_stage: u8, + /// Whether to capture on the ingress or egress pipeline. + pub dir: SnapshotDirection, + /// Fields and masks to use as snapshot trigger. Triggers are combined as + /// a logical `and`. + pub triggers: Vec, + /// Field names to decode from the capture. + pub fields: Vec, + /// Timeout in seconds to wait for trigger. + pub timeout_secs: u64, +} + +/// Table hit/miss result from a snapshot capture. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SnapshotTableResult { + /// Name of the table + pub name: String, + /// Whether the match lookup found a matching entry. + /// + /// Only meaningful when `executed` is true and `inhibited` is false. + /// The absence of `hit` does not necessarily mean that a lookup was + /// attempted. It simply means there was no hit, which could mean no lookup + /// was attempted or that the table's gateway inhibited it. + pub hit: bool, + /// Whether the table's gateway inhibited the match lookup from + /// proceeding. Gateways are conditional guards attached to tables that + /// can skip the lookup entirely. When inhibited, `hit` and + /// `match_hit_address` will be 0. Only applicable to tables that have + /// an attached gateway. + pub inhibited: bool, + /// Whether the table was active in this stage. This is the primary + /// gate: if a table was not executed, `hit`, `inhibited`, and + /// `match_hit_address` are all meaningless (zeroed by the SDE). + pub executed: bool, + /// The physical address of the entry that matched, sourced from the + /// exact-match or TCAM hit-address register depending on table type. + /// Zero when the table was not executed or was inhibited. + pub match_hit_address: u32, +} + +/// Per-stage result from a snapshot capture. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SnapshotStageResult { + /// The index of the stage this result came from. + pub stage_id: u8, + /// Whether this stage's own PHV match criteria fired the snapshot. + /// This is the primary trigger: the PHV contents at this stage matched + /// the key/mask programmed via the snapshot trigger configuration. + pub local_stage_trigger: bool, + /// Whether the snapshot was triggered because the previous stage was + /// already triggered and propagated its trigger signal forward. A + /// `prev_stage_trigger` with no `local_stage_trigger` means this stage + /// did not match the trigger criteria itself -- it was captured solely + /// because an adjacent stage matched. + pub prev_stage_trigger: bool, + /// Whether the snapshot was triggered by the timer mechanism rather + /// than a PHV field match. Useful for capturing pipeline state at a + /// specific time regardless of packet contents. + pub timer_trigger: bool, + /// The P4 table name that the MAU pipeline selected for execution in + /// the following stage after processing this one. + pub next_table: String, + /// Datapath error detected in the ingress pipeline at capture time. + /// Only reported on Tofino 2+; always false on Tofino 1. + pub ingress_dp_error: bool, + /// Datapath error detected in the egress pipeline at capture time. + /// Only reported on Tofino 2+; always false on Tofino 1. + pub egress_dp_error: bool, + /// Tables captured in the result. + pub tables: Vec, + /// Fields captured in the result. + pub fields: Vec, +} + +/// A decoded field value from a snapshot capture. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SnapshotFieldValue { + /// Name of the field. + pub name: String, + /// None if the field is not valid at this stage. + pub value: Option, +} + +/// Result of a snapshot capture operation. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SnapshotResult { + /// Stages captured in the result. + pub stages: Vec, +} + +/// Request body for checking field scope at a given stage. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SnapshotScopeRequest { + /// Pipeline index to check. + pub pipe: u32, + /// Stage index. + pub stage: u8, + /// Whether to check the ingress or egress pipeline. + pub dir: SnapshotDirection, + /// Fields to check. + pub fields: Vec, + /// If true, check trigger scope; otherwise check capture scope. + pub trigger: bool, +} + +/// Whether a field is in scope at a given stage. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SnapshotFieldScope { + /// Field name + pub field: String, + /// Whether or not the field is in scope. + pub in_scope: bool, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct TableDumpOptions { + pub from_hardware: bool, +} diff --git a/dpd-types/versions/src/v4_over_v6_routes/mod.rs b/dpd-types/versions/src/v4_over_v6_routes/mod.rs new file mode 100644 index 00000000..13a8c053 --- /dev/null +++ b/dpd-types/versions/src/v4_over_v6_routes/mod.rs @@ -0,0 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Version `V4_OVER_V6_ROUTES` of the DPD API. +//! +//! Added `Route` enum (V4/V6) and `RouteTarget`, changed `Ipv4Routes` to use +//! `Vec` instead of `Vec`, and added `Ipv4OverIpv6RouteUpdate`. + +pub mod route; diff --git a/dpd-types/versions/src/v4_over_v6_routes/route.rs b/dpd-types/versions/src/v4_over_v6_routes/route.rs new file mode 100644 index 00000000..ad2817a3 --- /dev/null +++ b/dpd-types/versions/src/v4_over_v6_routes/route.rs @@ -0,0 +1,62 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use crate::v1; +use crate::v1::route::{Ipv4Route, Ipv6Route}; +use oxnet::Ipv4Net; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +pub enum Route { + V4(Ipv4Route), + V6(Ipv6Route), +} + +/// Represents a specific egress port and nexthop target. +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +pub enum RouteTarget { + V4(Ipv4Route), + V6(Ipv6Route), +} + +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +pub struct Ipv4Routes { + /// Traffic destined for any address within the CIDR block is routed using + /// this information. + pub cidr: Ipv4Net, + /// All RouteTargets associated with this CIDR + pub targets: Vec, +} + +// v1 only understood IPv4 next hops, so drop any V6 targets on the way back. +impl From for v1::route::Ipv4Routes { + fn from(new: Ipv4Routes) -> Self { + Self { + cidr: new.cidr, + targets: new + .targets + .into_iter() + .filter_map(|r| match r { + Route::V4(r) => Some(r), + Route::V6(_) => None, + }) + .collect(), + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +pub struct Ipv4OverIpv6RouteUpdate { + /// Traffic destined for any address within the CIDR block is routed using + /// this information. + pub cidr: Ipv4Net, + /// A single Route associated with this CIDR + pub target: Ipv6Route, + /// Should this route replace any existing route? If a route exists and + /// this parameter is false, then the call will fail. + pub replace: bool, +} diff --git a/dpd-api/src/v11.rs b/dpd-types/versions/src/wallclock_history/link.rs similarity index 56% rename from dpd-api/src/v11.rs rename to dpd-types/versions/src/wallclock_history/link.rs index 62be8d7f..9991865c 100644 --- a/dpd-api/src/v11.rs +++ b/dpd-types/versions/src/wallclock_history/link.rs @@ -7,10 +7,21 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::v1; + #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] pub struct LinkHistory { - /// The timestamp in milliseconds at which this history was collected + /// The wallclock time in milliseconds at which this history was collected. pub timestamp: i64, + /// The timestamp in milliseconds at which this history was collected, + /// relative to the time the switch management daemon started. + pub relative: i64, /// The set of historical events recorded - pub events: Vec, + pub events: Vec, +} + +impl From for v1::link::LinkHistory { + fn from(history: LinkHistory) -> Self { + Self { timestamp: history.relative, events: history.events } + } } diff --git a/dpd-types/versions/src/wallclock_history/mod.rs b/dpd-types/versions/src/wallclock_history/mod.rs new file mode 100644 index 00000000..015b7643 --- /dev/null +++ b/dpd-types/versions/src/wallclock_history/mod.rs @@ -0,0 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Version `WALLCLOCK_HISTORY` of the DPD API. +//! +//! Added a wallclock timestamp to link history results, alongside the existing +//! relative timestamp. + +pub mod link; diff --git a/dpd/Cargo.toml b/dpd/Cargo.toml index 6f1f0366..7a15905f 100644 --- a/dpd/Cargo.toml +++ b/dpd/Cargo.toml @@ -28,6 +28,7 @@ asic.workspace = true common.workspace = true dpd-api.workspace = true dpd-types.workspace = true +dpd-types-versions.workspace = true anyhow.workspace = true cfg-if.workspace = true diff --git a/dpd/src/api_server.rs b/dpd/src/api_server.rs index 2dd9feaf..0312b4e9 100644 --- a/dpd/src/api_server.rs +++ b/dpd/src/api_server.rs @@ -13,19 +13,59 @@ use std::convert::TryFrom; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::Arc; -use dpd_types::fault::Fault; -use dpd_types::link::LinkFsmCounters; -use dpd_types::link::LinkId; -use dpd_types::link::LinkUpCounter; -use dpd_types::mcast::*; +use dpd_types::arp::{ + ArpEntry, ArpToken, Ipv4ArpParam, Ipv4Token, Ipv6ArpParam, Ipv6Token, +}; +use dpd_types::counters::{ + CounterPath, CounterSync, LinkFecRSCounters, LinkPcsCounters, + LinkRMonCounters, LinkRMonCountersAll, +}; +use dpd_types::fault::{Fault, FaultCondition}; +use dpd_types::link::{ + LinkCreate, LinkFilter, LinkFsmCounters, LinkHistory, LinkId, LinkIpv4Path, + LinkIpv6Path, LinkPath, LinkUpCounter, LinkView, MsDuration, TfportData, +}; +use dpd_types::loopback::{LoopbackIpv4Path, LoopbackIpv6Path}; +#[cfg(feature = "multicast")] +use dpd_types::mcast::UnderlayMulticastIpv6; +use dpd_types::mcast::{ + MulticastGroupCreateExternalEntry, MulticastGroupCreateUnderlayEntry, + MulticastGroupExternalResponse, MulticastGroupIpParam, + MulticastGroupResponse, MulticastGroupTagQuery, + MulticastGroupUnderlayResponse, MulticastGroupUpdateExternalEntry, + MulticastGroupUpdateUnderlayEntry, MulticastTagPath, + MulticastUnderlayGroupIpParam, +}; +use dpd_types::misc::{BuildInfo, TagPath}; +use dpd_types::nat::{ + NatIpv4Path, NatIpv4PortPath, NatIpv4RangePath, NatIpv6Path, + NatIpv6PortPath, NatIpv6RangePath, NatToken, +}; use dpd_types::oxstats::OximeterMetadata; +use dpd_types::port::{ + FreeChannels, LinkSettings, PortIdPathParams, PortSettings, PortSettingsTag, +}; use dpd_types::port_map::BackplaneLink; -use dpd_types::route::Ipv6Route; -use dpd_types::route::Route; +use dpd_types::route::{ + AttachedSubnetToken, Ipv4RouteToken, Ipv4RouteUpdate, Ipv4Routes, + Ipv6Route, Ipv6RouteToken, Ipv6RouteUpdate, Ipv6Routes, Route, RoutePathV4, + RoutePathV6, RouteTarget, RouteTargetIpv4Path, RouteTargetIpv6Path, + SubnetPath, +}; +use dpd_types::serdes::{ + AnLtStatus, Ber, DfeAdaptationState, EncSpeed, LaneMap, RxSigInfo, + SerdesEye, +}; +use dpd_types::snapshot::{ + SnapshotCreate, SnapshotFieldScope, SnapshotResult, SnapshotScopeRequest, + TableDumpOptions, +}; use dpd_types::switch_identifiers::SwitchIdentifiers; -use dpd_types::switch_port::Led; -use dpd_types::switch_port::ManagementMode; +use dpd_types::switch_port::{Led, ManagementMode, SwitchPortView}; +use dpd_types::table; +use dpd_types::table::TableParam; use dpd_types::transceivers::Transceiver; +use dpd_types_versions::{v1, v7}; use dropshot::ClientErrorStatusCode; use dropshot::ClientSpecifiesVersionInHeader; use dropshot::EmptyScanParams; @@ -77,7 +117,6 @@ use common::ports::PortId; use common::ports::QsfpPort; use common::ports::{Ipv4Entry, Ipv6Entry, PortPrbsMode}; use dpd_api::*; -use dpd_types::views; type ApiServer = dropshot::HttpServer>; @@ -534,10 +573,10 @@ impl DpdApi for DpdApiImpl { async fn port_get( rqctx: RequestContext>, path: Path, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let switch = rqctx.context(); let port_id = path.into_inner().port_id; - Ok(HttpResponseOk(views::SwitchPort::from( + Ok(HttpResponseOk(SwitchPortView::from( &*switch .switch_ports .ports @@ -820,7 +859,7 @@ impl DpdApi for DpdApiImpl { async fn link_get( rqctx: RequestContext>, path: Path, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let switch: &Switch = rqctx.context(); let path = path.into_inner(); switch @@ -844,7 +883,7 @@ impl DpdApi for DpdApiImpl { async fn link_list( rqctx: RequestContext>, path: Path, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> { let switch = &rqctx.context(); let port_id = path.into_inner().port_id; switch.list_links(port_id).map(HttpResponseOk).map_err(HttpError::from) @@ -853,7 +892,7 @@ impl DpdApi for DpdApiImpl { async fn link_list_all( rqctx: RequestContext>, query: Query, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> { let switch = &rqctx.context(); let filter = query.into_inner().filter; Ok(HttpResponseOk(switch.list_all_links(filter.as_deref()))) @@ -1285,7 +1324,7 @@ impl DpdApi for DpdApiImpl { async fn link_history_get( rqctx: RequestContext>, path: Path, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let switch: &Switch = rqctx.context(); let path = path.into_inner(); let port_id = path.port_id; @@ -1862,17 +1901,11 @@ impl DpdApi for DpdApiImpl { async fn tfport_data( rqctx: RequestContext>, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> { let switch = &rqctx.context(); Ok(HttpResponseOk(switch.all_tfport_data())) } - async fn ipv4_nat_generation( - rqctx: RequestContext>, - ) -> Result, HttpError> { - Self::nat_generation(rqctx).await - } - async fn nat_generation( rqctx: RequestContext>, ) -> Result, HttpError> { @@ -1881,12 +1914,6 @@ impl DpdApi for DpdApiImpl { Ok(HttpResponseOk(nat::get_nat_generation(switch))) } - async fn ipv4_nat_trigger_update( - rqctx: RequestContext>, - ) -> Result, HttpError> { - Self::nat_trigger_update(rqctx).await - } - async fn nat_trigger_update( rqctx: RequestContext>, ) -> Result, HttpError> { @@ -1912,7 +1939,7 @@ impl DpdApi for DpdApiImpl { rqctx: RequestContext>, query: Query, path: Path, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let switch: &Switch = rqctx.context(); let table = path.into_inner().table; crate::table::get_entries( @@ -1928,7 +1955,7 @@ impl DpdApi for DpdApiImpl { rqctx: RequestContext>, query: Query, path: Path, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> { let switch: &Switch = rqctx.context(); let force_sync = query.into_inner().force_sync; let table = path.into_inner().table; @@ -1965,7 +1992,7 @@ impl DpdApi for DpdApiImpl { rqctx: RequestContext>, query: Query, path: Path, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> { let switch: &Arc = rqctx.context(); let counter = path.into_inner().counter; let force_sync = query.into_inner().force_sync; @@ -2028,7 +2055,7 @@ impl DpdApi for DpdApiImpl { #[allow(unused_variables)] async fn multicast_group_delete_v1( rqctx: RequestContext>, - path: Path, + path: Path, ) -> Result { require_multicast!({ let switch: &Switch = rqctx.context(); @@ -2100,10 +2127,10 @@ impl DpdApi for DpdApiImpl { #[allow(unused_variables)] async fn multicast_group_update_underlay_v1( rqctx: RequestContext>, - path: Path, - group: TypedBody, + path: Path, + group: TypedBody, ) -> Result< - HttpResponseOk, + HttpResponseOk, HttpError, > { require_multicast!({ @@ -2170,10 +2197,10 @@ impl DpdApi for DpdApiImpl { #[allow(unused_variables)] async fn multicast_group_update_external_v7( rqctx: RequestContext>, - path: Path, - group: TypedBody, + path: Path, + group: TypedBody, ) -> Result< - HttpResponseCreated, + HttpResponseCreated, HttpError, > { require_multicast!({ @@ -2198,10 +2225,10 @@ impl DpdApi for DpdApiImpl { #[allow(unused_variables)] async fn multicast_group_update_external_v1( rqctx: RequestContext>, - path: Path, - group: TypedBody, + path: Path, + group: TypedBody, ) -> Result< - HttpResponseCreated, + HttpResponseCreated, HttpError, > { require_multicast!({ @@ -2217,7 +2244,9 @@ impl DpdApi for DpdApiImpl { .to_string(), }; - mcast::modify_group_external(switch, ip, &tag, entry.into()) + let v7_entry = + v7::mcast::MulticastGroupUpdateExternalEntry::from(entry); + mcast::modify_group_external(switch, ip, &tag, v7_entry.into()) .map(|resp| HttpResponseCreated(resp.into())) .map_err(HttpError::from) }) diff --git a/dpd/src/arp.rs b/dpd/src/arp.rs index 58500999..cad0b1cc 100644 --- a/dpd/src/arp.rs +++ b/dpd/src/arp.rs @@ -144,7 +144,7 @@ pub fn get_range_ipv4( switch: &Switch, last: Option<&Ipv4Addr>, mut max: u32, -) -> DpdResult> { +) -> DpdResult> { if max > 32 { max = 32 }; @@ -159,7 +159,7 @@ pub fn get_range_ipv4( .v4 .range((lower_bound, Bound::Unbounded)) .take(usize::try_from(max).expect("invalid usize")) - .map(|(ip, entry)| dpd_api::ArpEntry { + .map(|(ip, entry)| dpd_types::arp::ArpEntry { tag: entry.tag.clone(), ip: IpAddr::V4((*ip).into()), mac: entry.mac, @@ -174,7 +174,7 @@ pub fn get_range_ipv6( switch: &Switch, last: Option<&Ipv6Addr>, mut max: u32, -) -> DpdResult> { +) -> DpdResult> { max = std::cmp::max(max, 32); let lower_bound = match last { @@ -187,7 +187,7 @@ pub fn get_range_ipv6( .v6 .range((lower_bound, Bound::Unbounded)) .take(usize::try_from(max).expect("invalid usize")) - .map(|(ip, entry)| dpd_api::ArpEntry { + .map(|(ip, entry)| dpd_types::arp::ArpEntry { tag: entry.tag.clone(), ip: IpAddr::V6((*ip).into()), mac: entry.mac, diff --git a/dpd/src/counters.rs b/dpd/src/counters.rs index 71ffc0a5..90df4314 100644 --- a/dpd/src/counters.rs +++ b/dpd/src/counters.rs @@ -28,7 +28,7 @@ use common::counters::MulticastCounterId; use common::table::TableType; use anyhow::Context; -use dpd_types::views; +use dpd_types::table::TableCounterEntry; // Counters in an indirect table are accessed by their index number rather than // a key. Still, we define a key anyway to allow us to use the direct counter @@ -297,7 +297,7 @@ pub async fn get_values( switch: &Arc, force_sync: bool, counter_name: String, -) -> DpdResult> { +) -> DpdResult> { let counter_id = counter_name.parse::()?; let counters = { @@ -356,7 +356,7 @@ pub async fn get_values( // table. We've already translated the index into a label, so // that's what we're calling the key now. keys.insert("label".to_string(), key); - entries.push(views::TableCounterEntry { keys, data }); + entries.push(TableCounterEntry { keys, data }); } } diff --git a/dpd/src/link.rs b/dpd/src/link.rs index dc831839..3834a69d 100644 --- a/dpd/src/link.rs +++ b/dpd/src/link.rs @@ -34,12 +34,15 @@ use common::ports::PortMedia; use common::ports::PortPrbsMode; use common::ports::PortSpeed; use common::ports::TxEq; -use dpd_api::{Ber, LinkCreate}; +use dpd_types::link::LinkCreate; use dpd_types::link::LinkFsmCounters; +use dpd_types::link::LinkHistory; use dpd_types::link::LinkId; use dpd_types::link::LinkState; use dpd_types::link::LinkUpCounter; -use dpd_types::views; +use dpd_types::link::LinkView; +use dpd_types::link::TfportData; +use dpd_types::serdes::Ber; use slog::debug; use slog::error; use slog::info; @@ -204,7 +207,7 @@ impl Switch { /// A `Link` is a configured Ethernet link. /// /// This object exists to capture all the Tofino-facing information required to -/// keep track of the logical link. It is converted to a `crate::views::Link` for +/// keep track of the logical link. It is converted to a `crate::LinkView` for /// exposure in the public API. #[derive(Debug)] pub struct Link { @@ -251,7 +254,7 @@ pub struct Link { pub(crate) plumbed: LinkPlumbed, } -impl From<&Link> for views::Link { +impl From<&Link> for LinkView { fn from(m: &Link) -> Self { Self { port_id: m.port_id, @@ -274,13 +277,13 @@ impl From<&Link> for views::Link { } } -impl From for views::Link { +impl From for LinkView { fn from(m: crate::link::Link) -> Self { Self::from(&m) } } -impl From<&Link> for views::TfportData { +impl From<&Link> for TfportData { fn from(m: &Link) -> Self { Self { port_id: m.port_id, @@ -293,7 +296,7 @@ impl From<&Link> for views::TfportData { } } -impl From for views::TfportData { +impl From for TfportData { fn from(m: Link) -> Self { Self::from(&m) } @@ -610,21 +613,21 @@ impl Switch { &self, port_id: PortId, link_id: LinkId, - ) -> DpdResult { + ) -> DpdResult { let link_lock = self.get_link_lock(port_id, link_id)?; - let link = views::Link::from(&*link_lock.lock().unwrap()); + let link = LinkView::from(&*link_lock.lock().unwrap()); Ok(link) } /// List all links on the given switch port. - pub fn list_links(&self, port_id: PortId) -> DpdResult> { + pub fn list_links(&self, port_id: PortId) -> DpdResult> { self.switch_ports.verify_exists(port_id)?; let links = self.links.lock().unwrap(); let mut all = Vec::new(); for ((p_id, _), link_lock) in links.0.iter() { if *p_id == port_id { - all.push(views::Link::from(&*link_lock.lock().unwrap())) + all.push(LinkView::from(&*link_lock.lock().unwrap())) } } Ok(all) @@ -634,7 +637,7 @@ impl Switch { /// /// If provided, the `filter` argument can be used to limit the output to /// those links whose name contains `filter` as a substring. - pub fn list_all_links(&self, filter: Option<&str>) -> Vec { + pub fn list_all_links(&self, filter: Option<&str>) -> Vec { let mut links = Vec::with_capacity(self.switch_ports.ports.len()); for port_id in self.switch_ports.ports.keys() { let port_links = self.list_links(*port_id).unwrap_or_default(); @@ -651,10 +654,7 @@ impl Switch { } // Fetch the tfport-relevant data for all links on this port - fn port_tfport_data( - &self, - port_id: PortId, - ) -> DpdResult> { + fn port_tfport_data(&self, port_id: PortId) -> DpdResult> { self.switch_ports.verify_exists(port_id)?; let links = self.links.lock().unwrap(); @@ -664,7 +664,7 @@ impl Switch { .filter_map(|link_lock| { let link = link_lock.lock().unwrap(); if link.port_id == port_id { - Some(views::TfportData::from(&*link)) + Some(TfportData::from(&*link)) } else { None } @@ -673,7 +673,7 @@ impl Switch { } /// Fetch the tfport-relevant data for all links on all switch ports - pub fn all_tfport_data(&self) -> Vec { + pub fn all_tfport_data(&self) -> Vec { self.switch_ports .ports .keys() @@ -966,7 +966,7 @@ impl Switch { &self, port_id: PortId, link_id: LinkId, - ) -> DpdResult { + ) -> DpdResult { let asic_ids: Vec = { let link_lock = self.get_link_lock(port_id, link_id)?; // If the link hasn't been configured yet, we only collect the @@ -998,7 +998,7 @@ impl Switch { // caller. let mut events = self.fetch_history(asic_ids); events.sort_by_key(|a| a.timestamp); - Ok(views::LinkHistory { + Ok(LinkHistory { timestamp: common::wallclock_ms(), relative: common::timestamp_ms(), events: events.iter().map(|er| er.into()).collect(), diff --git a/dpd/src/macaddrs.rs b/dpd/src/macaddrs.rs index b095c6ac..7acff4e6 100644 --- a/dpd/src/macaddrs.rs +++ b/dpd/src/macaddrs.rs @@ -29,7 +29,7 @@ cfg_if::cfg_if! { use common::ports::PortFec; use common::ports::PortSpeed; use common::ports::InternalPort; - use dpd_api::LinkCreate; + use dpd_types::link::LinkCreate; use transceiver_controller::Error as TransceiverError; } } diff --git a/dpd/src/main.rs b/dpd/src/main.rs index 9cd7c346..a2b37ddc 100644 --- a/dpd/src/main.rs +++ b/dpd/src/main.rs @@ -17,7 +17,7 @@ use std::sync::MutexGuard; use anyhow::Context; use clap::{Parser, Subcommand}; -use dpd_api::LinkCreate; +use dpd_types::link::LinkCreate; use dpd_types::link::LinkId; use dpd_types::oxstats::OximeterMetadata; use futures::stream::StreamExt; @@ -402,10 +402,10 @@ impl Switch { &self, t: TableType, from_hardware: bool, - ) -> DpdResult { + ) -> DpdResult { let t = self.table_get(t)?; - Ok(dpd_types::views::Table { + Ok(dpd_types::table::Table { name: t.type_.to_string(), size: t.usage.size as usize, entries: t @@ -419,7 +419,7 @@ impl Switch { .map(|vec| { vec.into_iter() .map(|(key, action): (M, A)| { - dpd_types::views::TableEntry::new(key, action) + dpd_types::table::TableEntry::new(key, action) }) .collect() })?, @@ -431,7 +431,7 @@ impl Switch { &self, force_sync: bool, t: TableType, - ) -> DpdResult> { + ) -> DpdResult> { let t = self.table_get(t)?; t.get_counters::(&self.asic_hdl, force_sync) @@ -444,7 +444,7 @@ impl Switch { .map(|vec| { vec.into_iter() .map(|(key, data): (M, aal::CounterData)| { - dpd_types::views::TableCounterEntry::new(key, data) + dpd_types::table::TableCounterEntry::new(key, data) }) .collect() }) diff --git a/dpd/src/mcast/validate.rs b/dpd/src/mcast/validate.rs index 9fab2d17..428cafb9 100644 --- a/dpd/src/mcast/validate.rs +++ b/dpd/src/mcast/validate.rs @@ -14,7 +14,7 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use super::IpSrc; use crate::types::{DpdError, DpdResult}; use common::network::NatTarget; -use dpd_api::MulticastTag; +use dpd_types::mcast::MulticastTag; use omicron_common::address::{ IPV4_LINK_LOCAL_MULTICAST_SUBNET, IPV4_SSM_SUBNET, IPV6_INTERFACE_LOCAL_MULTICAST_SUBNET, IPV6_LINK_LOCAL_MULTICAST_SUBNET, @@ -281,7 +281,7 @@ pub(crate) fn validate_tag( mod tests { use super::*; use common::network::{MacAddr, Vni}; - use dpd_api::MAX_TAG_LENGTH; + use dpd_types::mcast::MAX_TAG_LENGTH; /// Admin-local IPv6 multicast prefix (ff04::/16, scope 4). const ADMIN_LOCAL_PREFIX: u16 = 0xff04; diff --git a/dpd/src/port_settings.rs b/dpd/src/port_settings.rs index 40c6bc9e..a60eda51 100644 --- a/dpd/src/port_settings.rs +++ b/dpd/src/port_settings.rs @@ -16,9 +16,9 @@ use common::ports::PortFec; use common::ports::PortId; use common::ports::PortSpeed; use common::ports::TxEq; -use dpd_api::LinkSettings; -use dpd_api::PortSettings; use dpd_types::link::LinkId; +use dpd_types::port::LinkSettings; +use dpd_types::port::PortSettings; use slog::Logger; use slog::debug; use slog::error; diff --git a/dpd/src/ports.rs b/dpd/src/ports.rs index a4861e14..2265fe2f 100644 --- a/dpd/src/ports.rs +++ b/dpd/src/ports.rs @@ -7,9 +7,9 @@ use std::collections::HashMap; use std::collections::VecDeque; -use dpd_api::LinkCreate; +use dpd_types::link::LinkCreate; +use dpd_types::link::LinkEvent; use dpd_types::link::LinkId; -use dpd_types::views::LinkEvent; use slog::debug; use slog::error; use slog::info; diff --git a/dpd/src/route.rs b/dpd/src/route.rs index e6421ca2..df50344a 100644 --- a/dpd/src/route.rs +++ b/dpd/src/route.rs @@ -791,7 +791,7 @@ pub async fn get_range_ipv4( switch: &Switch, last: Option, max: u32, -) -> DpdResult> { +) -> DpdResult> { let route_data = switch.routes.lock().await; let lower = match last { None => Bound::Unbounded, @@ -804,7 +804,7 @@ pub async fn get_range_ipv4( .range((lower, Bound::Unbounded)) .take(usize::try_from(max).expect("invalid usize")) { - routes.push(dpd_api::Ipv4Routes { + routes.push(dpd_types::route::Ipv4Routes { cidr: match subnet { IpNet::V4(n) => *n, IpNet::V6(_) => { @@ -824,7 +824,7 @@ pub async fn get_range_ipv6( switch: &Switch, last: Option, max: u32, -) -> DpdResult> { +) -> DpdResult> { let route_data = switch.routes.lock().await; let lower = match last { None => Bound::Unbounded, @@ -837,7 +837,7 @@ pub async fn get_range_ipv6( .range((lower, Bound::Unbounded)) .take(usize::try_from(max).expect("invalid usize")) { - routes.push(dpd_api::Ipv6Routes { + routes.push(dpd_types::route::Ipv6Routes { cidr: match subnet { IpNet::V6(n) => *n, IpNet::V4(_) => { diff --git a/dpd/src/snapshot.rs b/dpd/src/snapshot.rs index 48b50a66..026641d1 100644 --- a/dpd/src/snapshot.rs +++ b/dpd/src/snapshot.rs @@ -16,7 +16,7 @@ pub(crate) async fn capture( switch: &Switch, req: SnapshotCreate, ) -> Result { - use dpd_api::SnapshotDirection; + use dpd_types::snapshot::SnapshotDirection; let hdl = &switch.asic_hdl; @@ -108,7 +108,7 @@ pub(crate) async fn capture( let mut fields = Vec::new(); for field_name in &req.fields { - use dpd_api::SnapshotFieldValue; + use dpd_types::snapshot::SnapshotFieldValue; let val = snapshot::snapshot_decode_field( hdl, diff --git a/dpd/src/switch_port.rs b/dpd/src/switch_port.rs index 80bcae19..3aa9ddc0 100644 --- a/dpd/src/switch_port.rs +++ b/dpd/src/switch_port.rs @@ -11,8 +11,8 @@ use dpd_types::port_map::BackplaneLink; use dpd_types::switch_port::Led; use dpd_types::switch_port::LedPolicy; use dpd_types::switch_port::ManagementMode; +use dpd_types::switch_port::SwitchPortView; use dpd_types::transceivers::QsfpDevice; -use dpd_types::views; use serde::Deserialize; use tokio::sync::Mutex; pub use transceiver_controller::message::LedState; @@ -271,7 +271,7 @@ impl SwitchPort { } } -impl From<&SwitchPort> for views::SwitchPort { +impl From<&SwitchPort> for SwitchPortView { fn from(p: &SwitchPort) -> Self { let qsfp_device = match &p.fixed_side { FixedSideDevice::Qsfp { device, .. } => Some(device.clone()), diff --git a/dpd/src/table/arp_ipv4.rs b/dpd/src/table/arp_ipv4.rs index 2824014e..91ca2beb 100644 --- a/dpd/src/table/arp_ipv4.rs +++ b/dpd/src/table/arp_ipv4.rs @@ -4,6 +4,7 @@ // // Copyright 2026 Oxide Computer Company +use dpd_types::table; use std::convert::TryInto; use std::net::Ipv4Addr; @@ -63,14 +64,14 @@ pub fn delete_entry(s: &Switch, tgt_ip: Ipv4Addr) -> DpdResult<()> { s.table_entry_del(TableType::ArpIpv4, &match_key) } -pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { +pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { s.table_dump::(TableType::ArpIpv4, from_hardware) } pub fn counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::ArpIpv4) } diff --git a/dpd/src/table/attached_subnet_v4.rs b/dpd/src/table/attached_subnet_v4.rs index dedc2e98..fb37ec8a 100644 --- a/dpd/src/table/attached_subnet_v4.rs +++ b/dpd/src/table/attached_subnet_v4.rs @@ -4,6 +4,7 @@ // // Copyright 2026 Oxide Computer Company +use dpd_types::table; use std::convert::TryInto; use std::net::Ipv6Addr; @@ -56,7 +57,7 @@ pub fn delete_entry(s: &Switch, subnet: Ipv4Net) -> DpdResult<()> { s.table_entry_del(TableType::AttachedSubnetIpv4, &match_key) } -pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { +pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { s.table_dump::( TableType::AttachedSubnetIpv4, from_hardware, @@ -66,7 +67,7 @@ pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { pub fn counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::( force_sync, TableType::AttachedSubnetIpv4, diff --git a/dpd/src/table/attached_subnet_v6.rs b/dpd/src/table/attached_subnet_v6.rs index 743f4895..16fafdcc 100644 --- a/dpd/src/table/attached_subnet_v6.rs +++ b/dpd/src/table/attached_subnet_v6.rs @@ -3,6 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/ // // Copyright 2026 Oxide Computer Company +use dpd_types::table; use std::convert::TryInto; use std::net::Ipv6Addr; @@ -55,7 +56,7 @@ pub fn delete_entry(s: &Switch, subnet: Ipv6Net) -> DpdResult<()> { s.table_entry_del(TableType::AttachedSubnetIpv6, &match_key) } -pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { +pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { s.table_dump::( TableType::AttachedSubnetIpv6, from_hardware, @@ -65,7 +66,7 @@ pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { pub fn counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::( force_sync, TableType::AttachedSubnetIpv6, diff --git a/dpd/src/table/mac.rs b/dpd/src/table/mac.rs index 0627f037..533577f5 100644 --- a/dpd/src/table/mac.rs +++ b/dpd/src/table/mac.rs @@ -15,7 +15,7 @@ use aal_macros::ActionParse; use aal_macros::MatchParse; use common::network::MacAddr; use common::table::TableType; -use dpd_types::views; +use dpd_types::table; #[derive(MatchParse, Debug, Hash)] struct MacMatchKey { @@ -79,7 +79,7 @@ pub fn mac_clear(s: &Switch, port: u16) -> DpdResult<()> { mac_clear_common(s, TableType::PortMacAddress, port) } -pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { +pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { s.table_dump::( TableType::PortMacAddress, from_hardware, @@ -89,7 +89,7 @@ pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { pub fn counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::PortMacAddress) } @@ -117,7 +117,7 @@ pub fn mcast_mac_clear(s: &Switch, port: u16) -> DpdResult<()> { pub fn mcast_table_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::PortMacAddressMcast, from_hardware, @@ -128,7 +128,7 @@ pub fn mcast_table_dump( pub fn mcast_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::PortMacAddressMcast) } diff --git a/dpd/src/table/mcast/mcast_egress.rs b/dpd/src/table/mcast/mcast_egress.rs index 8a432846..10eed2e8 100644 --- a/dpd/src/table/mcast/mcast_egress.rs +++ b/dpd/src/table/mcast/mcast_egress.rs @@ -6,6 +6,7 @@ //! Table operations for multicast egress entries. +use dpd_types::table; use std::fmt; use crate::{Switch, table::*}; @@ -162,7 +163,7 @@ pub(crate) fn del_bitmap_entry( pub(crate) fn bitmap_table_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::McastEgressDecapPorts, from_hardware, @@ -173,7 +174,7 @@ pub(crate) fn bitmap_table_dump( pub(crate) fn bitmap_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::( force_sync, TableType::McastEgressDecapPorts, @@ -245,7 +246,7 @@ pub(crate) fn del_port_mapping_entry( pub(crate) fn port_mapping_table_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::McastEgressPortMapping, from_hardware, @@ -256,7 +257,7 @@ pub(crate) fn port_mapping_table_dump( pub(crate) fn port_mapping_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::( force_sync, TableType::McastEgressPortMapping, diff --git a/dpd/src/table/mcast/mcast_nat.rs b/dpd/src/table/mcast/mcast_nat.rs index b9e307b6..961f1644 100644 --- a/dpd/src/table/mcast/mcast_nat.rs +++ b/dpd/src/table/mcast/mcast_nat.rs @@ -11,6 +11,7 @@ //! check in the P4 pipeline. Decapsulated Geneve packets never reach these //! tables, so each group only needs a single entry with an exact VLAN match. +use dpd_types::table; use std::net::{Ipv4Addr, Ipv6Addr}; use crate::{Switch, table::*}; @@ -143,7 +144,7 @@ pub(crate) fn del_ipv4_entry_with_tgt( pub(crate) fn ipv4_table_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::NatIngressIpv4Mcast, from_hardware, @@ -154,7 +155,7 @@ pub(crate) fn ipv4_table_dump( pub(crate) fn ipv4_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::( force_sync, TableType::NatIngressIpv4Mcast, @@ -276,7 +277,7 @@ pub(crate) fn del_ipv6_entry_with_tgt( pub(crate) fn ipv6_table_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::NatIngressIpv6Mcast, from_hardware, @@ -287,7 +288,7 @@ pub(crate) fn ipv6_table_dump( pub(crate) fn ipv6_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::( force_sync, TableType::NatIngressIpv6Mcast, diff --git a/dpd/src/table/mcast/mcast_replication.rs b/dpd/src/table/mcast/mcast_replication.rs index 50815c8d..15274aec 100644 --- a/dpd/src/table/mcast/mcast_replication.rs +++ b/dpd/src/table/mcast/mcast_replication.rs @@ -6,6 +6,7 @@ //! Table operations for multicast replication information. +use dpd_types::table; use std::net::Ipv6Addr; use crate::{Switch, table::*}; @@ -119,7 +120,7 @@ pub(crate) fn del_ipv6_entry(s: &Switch, dst_addr: Ipv6Addr) -> DpdResult<()> { pub(crate) fn ipv6_table_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::McastIpv6, from_hardware, @@ -130,7 +131,7 @@ pub(crate) fn ipv6_table_dump( pub(crate) fn ipv6_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::McastIpv6) } diff --git a/dpd/src/table/mcast/mcast_route.rs b/dpd/src/table/mcast/mcast_route.rs index e383f459..cfa925d3 100644 --- a/dpd/src/table/mcast/mcast_route.rs +++ b/dpd/src/table/mcast/mcast_route.rs @@ -13,6 +13,7 @@ //! VLAN-based access control (preventing VLAN translation) is handled by NAT //! ingress tables before encapsulation, not by route tables. +use dpd_types::table; use std::net::{Ipv4Addr, Ipv6Addr}; use aal::ActionParse; @@ -100,7 +101,7 @@ pub(crate) fn del_ipv4_entry(s: &Switch, route: Ipv4Addr) -> DpdResult<()> { pub(crate) fn ipv4_table_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::RouteIpv4Mcast, from_hardware, @@ -111,7 +112,7 @@ pub(crate) fn ipv4_table_dump( pub(crate) fn ipv4_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::RouteIpv4Mcast) } @@ -199,7 +200,7 @@ pub(crate) fn del_ipv6_entry(s: &Switch, route: Ipv6Addr) -> DpdResult<()> { pub(crate) fn ipv6_table_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::RouteIpv6Mcast, from_hardware, @@ -210,7 +211,7 @@ pub(crate) fn ipv6_table_dump( pub(crate) fn ipv6_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::RouteIpv6Mcast) } diff --git a/dpd/src/table/mcast/mcast_src_filter.rs b/dpd/src/table/mcast/mcast_src_filter.rs index 775f17c0..c9e4ca38 100644 --- a/dpd/src/table/mcast/mcast_src_filter.rs +++ b/dpd/src/table/mcast/mcast_src_filter.rs @@ -6,6 +6,7 @@ //! Table operations for multicast source filter entries. +use dpd_types::table; use std::{ fmt, net::{Ipv4Addr, Ipv6Addr}, @@ -101,7 +102,7 @@ pub(crate) fn del_ipv4_entry( pub(crate) fn ipv4_table_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::McastIpv4SrcFilter, from_hardware, @@ -112,7 +113,7 @@ pub(crate) fn ipv4_table_dump( pub(crate) fn ipv4_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::McastIpv4SrcFilter) } @@ -154,7 +155,7 @@ pub(crate) fn del_ipv6_entry( pub(crate) fn ipv6_table_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::McastIpv6SrcFilter, from_hardware, @@ -165,7 +166,7 @@ pub(crate) fn ipv6_table_dump( pub(crate) fn ipv6_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::McastIpv6SrcFilter) } diff --git a/dpd/src/table/mod.rs b/dpd/src/table/mod.rs index 8abcd957..3afbd8cc 100644 --- a/dpd/src/table/mod.rs +++ b/dpd/src/table/mod.rs @@ -16,7 +16,7 @@ use aal::ActionParse; use aal::MatchParse; use aal::TableOps; use common::table::TableType; -use dpd_types::views; +use dpd_types::table as dpd_table; pub mod arp_ipv4; pub mod attached_subnet_v4; @@ -214,7 +214,7 @@ pub fn get_entries( switch: &Switch, name: String, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { match TableType::try_from(name.as_str())? { TableType::RouteIdxIpv4 => { route_ipv4::index_dump(switch, from_hardware) @@ -309,7 +309,7 @@ pub fn get_counters( switch: &Switch, force_sync: bool, name: String, -) -> DpdResult> { +) -> DpdResult> { match TableType::try_from(name.as_str())? { TableType::RouteIdxIpv4 => { route_ipv4::index_counter_fetch(switch, force_sync) diff --git a/dpd/src/table/nat.rs b/dpd/src/table/nat.rs index cb652e2d..5d15f3ca 100644 --- a/dpd/src/table/nat.rs +++ b/dpd/src/table/nat.rs @@ -4,6 +4,7 @@ // // Copyright 2026 Oxide Computer Company +use dpd_types::table; use std::convert::TryInto; use std::fmt; use std::net::{Ipv4Addr, Ipv6Addr}; @@ -148,7 +149,7 @@ pub fn delete_ipv4_entry( pub fn ipv4_table_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::NatIngressIpv4, from_hardware, @@ -158,7 +159,7 @@ pub fn ipv4_table_dump( pub fn ipv6_table_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::NatIngressIpv6, from_hardware, @@ -168,14 +169,14 @@ pub fn ipv6_table_dump( pub fn ipv4_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::NatIngressIpv4) } pub fn ipv6_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::NatIngressIpv6) } diff --git a/dpd/src/table/neighbor_ipv6.rs b/dpd/src/table/neighbor_ipv6.rs index 0d89d5f5..595553dc 100644 --- a/dpd/src/table/neighbor_ipv6.rs +++ b/dpd/src/table/neighbor_ipv6.rs @@ -4,6 +4,7 @@ // // Copyright 2026 Oxide Computer Company +use dpd_types::table; use std::convert::TryInto; use std::net::Ipv6Addr; @@ -68,14 +69,14 @@ pub fn delete_entry(s: &Switch, tgt_ip: Ipv6Addr) -> DpdResult<()> { s.table_entry_del(TableType::NeighborIpv6, &match_key) } -pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { +pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { s.table_dump::(TableType::NeighborIpv6, from_hardware) } pub fn counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::NeighborIpv6) } diff --git a/dpd/src/table/port_ip.rs b/dpd/src/table/port_ip.rs index 04a004fe..322493a5 100644 --- a/dpd/src/table/port_ip.rs +++ b/dpd/src/table/port_ip.rs @@ -4,6 +4,7 @@ // // Copyright 2026 Oxide Computer Company +use dpd_types::table; use std::convert::TryInto; use std::net::Ipv4Addr; use std::net::Ipv6Addr; @@ -317,7 +318,7 @@ pub fn ipv6_delete(s: &Switch, port: u16, ipv6: Ipv6Addr) -> DpdResult<()> { pub fn ipv4_table_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::PortAddrIpv4, from_hardware, @@ -327,7 +328,7 @@ pub fn ipv4_table_dump( pub fn ipv6_table_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::PortAddrIpv6, from_hardware, @@ -345,14 +346,14 @@ pub fn ipv6_table_clear(s: &Switch) -> DpdResult<()> { pub fn ipv4_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::PortAddrIpv4) } pub fn ipv6_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::PortAddrIpv6) } diff --git a/dpd/src/table/route_ipv4.rs b/dpd/src/table/route_ipv4.rs index 60564ede..e5b9d79e 100644 --- a/dpd/src/table/route_ipv4.rs +++ b/dpd/src/table/route_ipv4.rs @@ -4,6 +4,7 @@ // // Copyright 2026 Oxide Computer Company +use dpd_types::table; use std::convert::TryInto; use std::net::Ipv4Addr; use std::net::Ipv6Addr; @@ -186,14 +187,14 @@ pub fn delete_route_target(s: &Switch, idx: u16) -> DpdResult<()> { pub fn forward_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::RouteFwdIpv4, from_hardware, ) } -pub fn index_dump(s: &Switch, from_hardware: bool) -> DpdResult { +pub fn index_dump(s: &Switch, from_hardware: bool) -> DpdResult { s.table_dump::( TableType::RouteIdxIpv4, from_hardware, @@ -203,14 +204,14 @@ pub fn index_dump(s: &Switch, from_hardware: bool) -> DpdResult { pub fn forward_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::RouteFwdIpv4) } pub fn index_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::RouteIdxIpv4) } diff --git a/dpd/src/table/route_ipv6.rs b/dpd/src/table/route_ipv6.rs index 28c6bb3a..81dc2ec3 100644 --- a/dpd/src/table/route_ipv6.rs +++ b/dpd/src/table/route_ipv6.rs @@ -4,6 +4,7 @@ // // Copyright 2026 Oxide Computer Company +use dpd_types::table; use std::convert::TryInto; use std::net::Ipv6Addr; @@ -144,14 +145,14 @@ pub fn delete_route_target(s: &Switch, idx: u16) -> DpdResult<()> { pub fn forward_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::RouteFwdIpv6, from_hardware, ) } -pub fn index_dump(s: &Switch, from_hardware: bool) -> DpdResult { +pub fn index_dump(s: &Switch, from_hardware: bool) -> DpdResult { s.table_dump::( TableType::RouteIdxIpv6, from_hardware, @@ -161,14 +162,14 @@ pub fn index_dump(s: &Switch, from_hardware: bool) -> DpdResult { pub fn forward_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::RouteFwdIpv6) } pub fn index_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::RouteIdxIpv6) } diff --git a/dpd/src/table/uplink.rs b/dpd/src/table/uplink.rs index f858d228..d8f04abd 100644 --- a/dpd/src/table/uplink.rs +++ b/dpd/src/table/uplink.rs @@ -4,6 +4,7 @@ // // Copyright 2026 Oxide Computer Company +use dpd_types::table; use std::convert::TryInto; use slog::{error, info}; @@ -116,7 +117,7 @@ pub fn uplink_clear(s: &Switch, port: u16) -> DpdResult<()> { pub fn egress_table_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::UplinkEgress, from_hardware, @@ -126,7 +127,7 @@ pub fn egress_table_dump( pub fn ingress_table_dump( s: &Switch, from_hardware: bool, -) -> DpdResult { +) -> DpdResult { s.table_dump::( TableType::UplinkIngress, from_hardware, @@ -136,13 +137,13 @@ pub fn ingress_table_dump( pub fn egress_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::UplinkEgress) } pub fn ingress_counter_fetch( s: &Switch, force_sync: bool, -) -> DpdResult> { +) -> DpdResult> { s.counter_fetch::(force_sync, TableType::UplinkIngress) } From e3616a0b5f6ceca2042c91c064501fed66631e25 Mon Sep 17 00:00:00 2001 From: Rain Date: Sat, 18 Apr 2026 22:25:33 -0700 Subject: [PATCH 2/4] attempt to fix build Created using spr 1.3.6-beta.1 --- Cargo.lock | 2 +- asic/Cargo.toml | 2 +- asic/src/tofino_asic/serdes.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d6642f01..75952de7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,7 +174,7 @@ dependencies = [ "cfg-if", "chrono", "common 0.1.0", - "dpd-api", + "dpd-types", "lazy_static", "libc", "oximeter", diff --git a/asic/Cargo.toml b/asic/Cargo.toml index a87204bd..96d42164 100644 --- a/asic/Cargo.toml +++ b/asic/Cargo.toml @@ -23,7 +23,7 @@ doctest = false [dependencies] aal.workspace = true common.workspace = true -dpd-api.workspace = true +dpd-types.workspace = true propolis = { workspace = true, optional = true , features = ["falcon"] } tofino = { workspace = true, optional = true } diff --git a/asic/src/tofino_asic/serdes.rs b/asic/src/tofino_asic/serdes.rs index 499ebad2..32fbc42f 100644 --- a/asic/src/tofino_asic/serdes.rs +++ b/asic/src/tofino_asic/serdes.rs @@ -16,7 +16,7 @@ use crate::tofino_asic::genpd::*; use crate::tofino_asic::ports; use crate::tofino_asic::{CheckError, Handle}; use aal::{AsicError, AsicResult, PortHdl}; -use dpd_api::{ +use dpd_types::serdes::{ AnLtStatus, AnStatus, DfeAdaptationState, EncSpeed, LaneEncoding, LaneMap, LaneStatus, LpPages, LtStatus, RxSigInfo, SerdesEye, }; From 6370d83cc5442261ea918b35a3be390d166d8ffd Mon Sep 17 00:00:00 2001 From: Rain Date: Sat, 18 Apr 2026 22:43:17 -0700 Subject: [PATCH 3/4] more failures Created using spr 1.3.6-beta.1 --- dpd/src/api_server.rs | 5 ++++- dpd/src/snapshot.rs | 12 ++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/dpd/src/api_server.rs b/dpd/src/api_server.rs index 0312b4e9..665ca0d9 100644 --- a/dpd/src/api_server.rs +++ b/dpd/src/api_server.rs @@ -2247,7 +2247,10 @@ impl DpdApi for DpdApiImpl { let v7_entry = v7::mcast::MulticastGroupUpdateExternalEntry::from(entry); mcast::modify_group_external(switch, ip, &tag, v7_entry.into()) - .map(|resp| HttpResponseCreated(resp.into())) + .map(|resp| { + let v7_resp = v7::mcast::MulticastGroupExternalResponse::from(resp); + HttpResponseCreated(v7_resp.into()) + }) .map_err(HttpError::from) }) } diff --git a/dpd/src/snapshot.rs b/dpd/src/snapshot.rs index 026641d1..d82cd6c8 100644 --- a/dpd/src/snapshot.rs +++ b/dpd/src/snapshot.rs @@ -9,15 +9,15 @@ use crate::Switch; use crate::types::DpdError; use asic::tofino_asic::snapshot; -use dpd_api::{SnapshotCreate, SnapshotResult}; -use dpd_api::{SnapshotDirection, SnapshotFieldScope, SnapshotScopeRequest}; +use dpd_types::snapshot::{ + SnapshotCreate, SnapshotDirection, SnapshotFieldScope, SnapshotResult, + SnapshotScopeRequest, +}; pub(crate) async fn capture( switch: &Switch, req: SnapshotCreate, ) -> Result { - use dpd_types::snapshot::SnapshotDirection; - let hdl = &switch.asic_hdl; let dir = match req.dir { @@ -92,7 +92,7 @@ pub(crate) async fn capture( // Build stage results with decoded fields. let mut stages = Vec::new(); for ctrl in &cap.ctrls { - use dpd_api::{SnapshotStageResult, SnapshotTableResult}; + use dpd_types::snapshot::{SnapshotStageResult, SnapshotTableResult}; let tables = ctrl .tables @@ -122,7 +122,7 @@ pub(crate) async fn capture( .map_err(DpdError::from)?; fields.push(SnapshotFieldValue { - name: field_name.clone(), + name: field_name.to_string(), value: val.map(|v| { let hex: String = v.iter().map(|b| format!("{b:02x}")).collect(); From bbea18edad9b4615c1ba13a557b5c3c41f16c806 Mon Sep 17 00:00:00 2001 From: Rain Date: Sat, 18 Apr 2026 23:01:02 -0700 Subject: [PATCH 4/4] rustfmt Created using spr 1.3.6-beta.1 --- dpd-api/src/lib.rs | 18 ++++++------------ dpd/src/api_server.rs | 3 ++- dpd/src/snapshot.rs | 4 +++- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/dpd-api/src/lib.rs b/dpd-api/src/lib.rs index 97cc7a2e..84b65b61 100644 --- a/dpd-api/src/lib.rs +++ b/dpd-api/src/lib.rs @@ -1958,12 +1958,9 @@ pub trait DpdApi { HttpResponseCreated, HttpError, > { - Self::multicast_group_create_external_v7( - rqctx, - group.map(Into::into), - ) - .await - .map(|resp| resp.map(Into::into)) + Self::multicast_group_create_external_v7(rqctx, group.map(Into::into)) + .await + .map(|resp| resp.map(Into::into)) } /// Create an underlay (internal) multicast group configuration. @@ -2422,12 +2419,9 @@ pub trait DpdApi { HttpResponseOk>, HttpError, > { - let HttpResponseOk(page) = Self::multicast_groups_list_by_tag_v7( - rqctx, - path, - query_params, - ) - .await?; + let HttpResponseOk(page) = + Self::multicast_groups_list_by_tag_v7(rqctx, path, query_params) + .await?; Ok(HttpResponseOk(ResultsPage { items: page.items.into_iter().map(Into::into).collect(), next_page: page.next_page, diff --git a/dpd/src/api_server.rs b/dpd/src/api_server.rs index 665ca0d9..3025d3cc 100644 --- a/dpd/src/api_server.rs +++ b/dpd/src/api_server.rs @@ -2248,7 +2248,8 @@ impl DpdApi for DpdApiImpl { v7::mcast::MulticastGroupUpdateExternalEntry::from(entry); mcast::modify_group_external(switch, ip, &tag, v7_entry.into()) .map(|resp| { - let v7_resp = v7::mcast::MulticastGroupExternalResponse::from(resp); + let v7_resp = + v7::mcast::MulticastGroupExternalResponse::from(resp); HttpResponseCreated(v7_resp.into()) }) .map_err(HttpError::from) diff --git a/dpd/src/snapshot.rs b/dpd/src/snapshot.rs index d82cd6c8..be466ef8 100644 --- a/dpd/src/snapshot.rs +++ b/dpd/src/snapshot.rs @@ -92,7 +92,9 @@ pub(crate) async fn capture( // Build stage results with decoded fields. let mut stages = Vec::new(); for ctrl in &cap.ctrls { - use dpd_types::snapshot::{SnapshotStageResult, SnapshotTableResult}; + use dpd_types::snapshot::{ + SnapshotStageResult, SnapshotTableResult, + }; let tables = ctrl .tables