Skip to content

Commit 1bcfcfd

Browse files
avrabeclaude
andauthored
feat(trace-topology): IPv4 + IPv6 in Spar_Identity (v0.10.x B-6) (#213)
Extends the Spar_Identity property set with IPv4_Address and IPv6_Address (aadlstring; applies to device, processor, system, bus, abstract) so the v0.11.0 reconciler can check L3 identity against runtime-observed ARP / NDP / DHCP state alongside the existing L2 MAC, L2.5 VLAN, and LLDP identities. - standard_properties: SPAR_IDENTITY 6 -> 8 entries; total 137 -> 139. - spar-trace-topology::identity: get_ipv4_address / get_ipv6_address follow the typed-first / string-fallback pattern of the existing six accessors. - Tests: 2 new identity_accessors tests (ipv4 typed, ipv6 raw); the spar-hir-def standard_properties tests are updated to the new count. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent bad85e6 commit 1bcfcfd

6 files changed

Lines changed: 127 additions & 4 deletions

File tree

artifacts/requirements.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1895,4 +1895,18 @@ artifacts:
18951895
status: implemented
18961896
tags: [trace-topology, ingest, gptp, sync, v0100]
18971897

1898+
- id: REQ-TRACE-TOPOLOGY-007
1899+
type: requirement
1900+
title: IPv4 + IPv6 address properties in Spar_Identity
1901+
description: >
1902+
System shall extend the Spar_Identity property set with
1903+
IPv4_Address and IPv6_Address (aadlstring; applies to device,
1904+
processor, system, bus, abstract). Typed accessors live in
1905+
spar-trace-topology::identity following the same typed-first /
1906+
string-fallback pattern as the existing six identity
1907+
properties. Enables the v0.11.0 reconciler to check L3
1908+
identity against runtime-observed ARP / NDP / DHCP state.
1909+
status: implemented
1910+
tags: [trace-topology, properties, identity, ip, v0100]
1911+
18981912
# Research findings tracked separately in research/findings.yaml

artifacts/verification.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2468,3 +2468,21 @@ artifacts:
24682468
links:
24692469
- type: satisfies
24702470
target: REQ-TRACE-TOPOLOGY-006
2471+
2472+
- id: TEST-TRACE-TOPOLOGY-IP-IDENTITY
2473+
type: feature
2474+
title: IPv4/IPv6 accessors honour typed and raw paths
2475+
description: >
2476+
Tests in crates/spar-trace-topology/src/identity.rs verify that
2477+
get_ipv4_address / get_ipv6_address return the typed value
2478+
first and fall back to the raw quoted form, mirroring the
2479+
existing 6 Spar_Identity accessors.
2480+
fields:
2481+
method: automated-test
2482+
steps:
2483+
- run: cargo test -p spar-trace-topology --lib -- identity::tests
2484+
status: passing
2485+
tags: [trace-topology, properties, identity, ip, v0100]
2486+
links:
2487+
- type: satisfies
2488+
target: REQ-TRACE-TOPOLOGY-007

crates/spar-hir-def/src/standard_properties.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,14 @@ const SPAR_IDENTITY: &[(&str, &str)] = &[
573573
// LLDP port-id of a bus access feature as reported by the runtime
574574
// LLDP snapshot. Applies to bus access (feature-level).
575575
("LLDP_Port_Id", "aadlstring"),
576+
// L3 IPv4 address of an addressable AADL component as reported by
577+
// runtime ARP / DHCP snapshots. Applies to device, processor,
578+
// system, bus, abstract.
579+
("IPv4_Address", "aadlstring"),
580+
// L3 IPv6 address of an addressable AADL component as reported by
581+
// runtime NDP / DHCPv6 snapshots. Applies to device, processor,
582+
// system, bus, abstract.
583+
("IPv6_Address", "aadlstring"),
576584
];
577585

578586
/// Helper: collect properties from a table into the result vector.
@@ -1262,13 +1270,15 @@ mod tests {
12621270
assert!(is_standard_property_set("Spar_Identity"));
12631271

12641272
let props = standard_properties_in_set("Spar_Identity");
1265-
assert_eq!(props.len(), 6);
1273+
assert_eq!(props.len(), 8);
12661274
assert!(props.contains(&"MAC_Address"));
12671275
assert!(props.contains(&"VLAN_ID"));
12681276
assert!(props.contains(&"Stream_Handle"));
12691277
assert!(props.contains(&"Multicast_Group"));
12701278
assert!(props.contains(&"LLDP_Chassis_Id"));
12711279
assert!(props.contains(&"LLDP_Port_Id"));
1280+
assert!(props.contains(&"IPv4_Address"));
1281+
assert!(props.contains(&"IPv6_Address"));
12721282

12731283
// Each property resolves to its expected type.
12741284
assert_eq!(
@@ -1295,6 +1305,14 @@ mod tests {
12951305
standard_property_type("Spar_Identity", "LLDP_Port_Id"),
12961306
Some("aadlstring")
12971307
);
1308+
assert_eq!(
1309+
standard_property_type("Spar_Identity", "IPv4_Address"),
1310+
Some("aadlstring")
1311+
);
1312+
assert_eq!(
1313+
standard_property_type("Spar_Identity", "IPv6_Address"),
1314+
Some("aadlstring")
1315+
);
12981316

12991317
// Deliberately-wrong name returns None.
13001318
assert_eq!(standard_property_type("Spar_Identity", "Nonexistent"), None);
@@ -1325,6 +1343,8 @@ mod tests {
13251343
"Multicast_Group",
13261344
"LLDP_Chassis_Id",
13271345
"LLDP_Port_Id",
1346+
"IPv4_Address",
1347+
"IPv6_Address",
13281348
] {
13291349
let result = scope.resolve_property(&Name::new("Spar_Identity"), &Name::new(prop_name));
13301350
assert!(
@@ -1368,7 +1388,7 @@ mod tests {
13681388
#[test]
13691389
fn test_all_standard_properties_total_count() {
13701390
let all = all_standard_properties();
1371-
// 12 + 13 + 14 + 14 + 8 + 25 + 4 + 13 + 5 + 4 + 5 + 4 + 1 + 9 + 6 = 137
1391+
// 12 + 13 + 14 + 14 + 8 + 25 + 4 + 13 + 5 + 4 + 5 + 4 + 1 + 9 + 8 = 139
13721392
// (Timing + Communication + Memory + Deployment + Thread + Programming
13731393
// + Modeling + AADL_Project + Spar_Timing + Spar_Trace + Spar_Network
13741394
// + Spar_Migration + Spar_Power + Spar_TSN + Spar_Identity)
@@ -1383,7 +1403,9 @@ mod tests {
13831403
// Spar_Identity: +6 for MAC_Address, VLAN_ID, Stream_Handle,
13841404
// Multicast_Group, LLDP_Chassis_Id, LLDP_Port_Id (v0.10.0
13851405
// trace-topology foundation, Track G).
1386-
assert_eq!(all.len(), 137);
1406+
// +2 for IPv4_Address, IPv6_Address (v0.10.x B-6, L3
1407+
// identity for the reconciler).
1408+
assert_eq!(all.len(), 139);
13871409
}
13881410

13891411
#[test]

crates/spar-trace-topology/src/identity.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,29 @@ pub fn get_lldp_port_id(props: &PropertyMap) -> Option<String> {
109109
}
110110
get_raw(props, "LLDP_Port_Id").map(unquote)
111111
}
112+
113+
/// Read [`Spar_Identity::IPv4_Address`] — the canonical IPv4 of a
114+
/// device, processor, system, or bus.
115+
///
116+
/// Returns the raw declared string (e.g. `"192.0.2.42"`); no
117+
/// canonicalisation here — the v0.11.0 reconciliation engine
118+
/// normalises before comparison.
119+
pub fn get_ipv4_address(props: &PropertyMap) -> Option<String> {
120+
if let Some(PropertyExpr::StringLit(s)) = get_typed(props, "IPv4_Address") {
121+
return Some(s.clone());
122+
}
123+
get_raw(props, "IPv4_Address").map(unquote)
124+
}
125+
126+
/// Read [`Spar_Identity::IPv6_Address`] — the canonical IPv6 of a
127+
/// device, processor, system, or bus.
128+
///
129+
/// Returns the raw declared string (e.g. `"2001:db8::1"`); no
130+
/// canonicalisation here — the v0.11.0 reconciliation engine
131+
/// normalises before comparison.
132+
pub fn get_ipv6_address(props: &PropertyMap) -> Option<String> {
133+
if let Some(PropertyExpr::StringLit(s)) = get_typed(props, "IPv6_Address") {
134+
return Some(s.clone());
135+
}
136+
get_raw(props, "IPv6_Address").map(unquote)
137+
}

crates/spar-trace-topology/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
//! - The [`identity`] module exposes typed accessors for the new
2222
//! `Spar_Identity::*` property surface (`MAC_Address`, `VLAN_ID`,
2323
//! `Stream_Handle`, `Multicast_Group`, `LLDP_Chassis_Id`,
24-
//! `LLDP_Port_Id`).
24+
//! `LLDP_Port_Id`, `IPv4_Address`, `IPv6_Address`).
2525
//! - The [`ingest`] module declares trait skeletons for the four
2626
//! parsers — frame source (PCAPNG), topology source (LLDP), switch
2727
//! config source (Qcc YANG), and PTP-time source (gPTP). Real

crates/spar-trace-topology/tests/identity_accessors.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,46 @@ fn lldp_port_id_typed_and_string_fallback() {
189189
let empty = PropertyMap::new();
190190
assert_eq!(identity::get_lldp_port_id(&empty), None);
191191
}
192+
193+
#[test]
194+
fn ipv4_address_typed_path() {
195+
// Typed PropertyExpr::StringLit path returns the unquoted value
196+
// directly.
197+
let typed = make_props(
198+
"Spar_Identity",
199+
"IPv4_Address",
200+
"\"192.0.2.42\"",
201+
Some(PropertyExpr::StringLit("192.0.2.42".to_string())),
202+
);
203+
assert_eq!(
204+
identity::get_ipv4_address(&typed),
205+
Some("192.0.2.42".to_string())
206+
);
207+
208+
// Absent => None.
209+
let empty = PropertyMap::new();
210+
assert_eq!(identity::get_ipv4_address(&empty), None);
211+
}
212+
213+
#[test]
214+
fn ipv6_address_raw_fallback() {
215+
// String-fallback path strips surrounding quotes from the raw
216+
// source-text form.
217+
let raw = make_props("Spar_Identity", "IPv6_Address", "\"2001:db8::1\"", None);
218+
assert_eq!(
219+
identity::get_ipv6_address(&raw),
220+
Some("2001:db8::1".to_string())
221+
);
222+
223+
// Typed path also works end-to-end.
224+
let typed = make_props(
225+
"Spar_Identity",
226+
"IPv6_Address",
227+
"\"fe80::1\"",
228+
Some(PropertyExpr::StringLit("fe80::1".to_string())),
229+
);
230+
assert_eq!(
231+
identity::get_ipv6_address(&typed),
232+
Some("fe80::1".to_string())
233+
);
234+
}

0 commit comments

Comments
 (0)