Skip to main content

secured_cam_sender_receiver/
secured_cam_sender_receiver.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2// Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT)
3
4//! Secured CAM Sender and Receiver Example
5//!
6//! This example mirrors `FlexStack/examples/secured_cam_sender_and_receiver.py`
7//! and demonstrates sending and receiving ETSI TS 103 097-secured CAMs using
8//! the `rustflexstack` security layer.
9//!
10//! Two instances are expected to run simultaneously, one with `--at 1` and the
11//! other with `--at 2`.  Each loads its own Authorization Ticket from the
12//! `certs/` directory (generated by `generate_certificate_chain`), signs
13//! outgoing CAMs, and verifies incoming ones.
14//!
15//! Security is applied as a middleware layer between the GN router and the
16//! link layer — outgoing packets are signed and incoming secured packets are
17//! verified before being forwarded to the GN router.
18//!
19//! # Prerequisites
20//! ```text
21//! cargo run --example generate_certificate_chain --target x86_64-unknown-linux-gnu
22//! ```
23//!
24//! # Running
25//! ```text
26//! # Terminal 1:
27//! sudo cargo run --example secured_cam_sender_receiver --target x86_64-unknown-linux-gnu -- --at 1
28//! # Terminal 2:
29//! sudo cargo run --example secured_cam_sender_receiver --target x86_64-unknown-linux-gnu -- --at 2
30//! ```
31//!
32//! If no interface is given the example falls back to `lo` (loopback), which
33//! is sufficient to test send→receive on a single machine.
34
35use std::env;
36use std::f64::consts::PI;
37use std::fs;
38use std::path::Path;
39use std::sync::{mpsc, Arc, Mutex};
40use std::thread;
41use std::time::Duration;
42
43use rustflexstack::btp::router::Router as BTPRouter;
44use rustflexstack::facilities::ca_basic_service::{CooperativeAwarenessBasicService, VehicleData};
45use rustflexstack::facilities::local_dynamic_map::{
46    ldm_constants::ITS_AID_CAM,
47    ldm_storage::ItsDataObject,
48    ldm_types::{RegisterDataConsumerReq, RegisterDataProviderReq, RequestDataObjectsReq},
49    LdmFacility,
50};
51use rustflexstack::facilities::location_service::{GpsFix, LocationService};
52use rustflexstack::geonet::basic_header::{BasicHeader, BasicNH};
53use rustflexstack::geonet::gn_address::{GNAddress, M, MID, ST};
54use rustflexstack::geonet::mib::Mib;
55use rustflexstack::geonet::position_vector::LongPositionVector;
56use rustflexstack::geonet::router::Router as GNRouter;
57use rustflexstack::link_layer::raw_link_layer::RawLinkLayer;
58
59use rustflexstack::security::certificate::{Certificate, OwnCertificate};
60use rustflexstack::security::certificate_library::CertificateLibrary;
61use rustflexstack::security::ecdsa_backend::EcdsaBackend;
62use rustflexstack::security::sign_service::SignService;
63use rustflexstack::security::sn_sap::{ReportVerify, SNSignRequest, SNVerifyRequest};
64use rustflexstack::security::verify_service::{verify_message, VerifyEvent};
65
66const ITS_AID_CAM_VAL: u64 = 36;
67
68/// Build the security stack: load certificates, create backend, sign & verify
69/// services.  Returns a `SignService` ready for use.
70fn build_security_stack(at_index: usize) -> SignService {
71    let cert_dir = Path::new("certs");
72
73    // ── Load certificates ────────────────────────────────────────────────
74    let root_bytes = fs::read(cert_dir.join("root_ca.cert"))
75        .expect("root_ca.cert not found — run generate_certificate_chain first");
76    let aa_bytes = fs::read(cert_dir.join("aa.cert"))
77        .expect("aa.cert not found — run generate_certificate_chain first");
78
79    let root_ca = Certificate::from_bytes(&root_bytes, None);
80    let aa = Certificate::from_bytes(&aa_bytes, Some(root_ca.clone()));
81
82    // Load both ATs — one is "ours", the other is a known peer
83    let at1_cert_bytes = fs::read(cert_dir.join("at1.cert")).expect("at1.cert not found");
84    let at2_cert_bytes = fs::read(cert_dir.join("at2.cert")).expect("at2.cert not found");
85
86    let at1 = Certificate::from_bytes(&at1_cert_bytes, Some(aa.clone()));
87    let at2 = Certificate::from_bytes(&at2_cert_bytes, Some(aa.clone()));
88
89    // Load our private key
90    let own_key_file = if at_index == 1 { "at1.key" } else { "at2.key" };
91    let key_bytes = fs::read(cert_dir.join(own_key_file))
92        .unwrap_or_else(|_| panic!("{} not found", own_key_file));
93
94    // ── Create backend and import key ────────────────────────────────────
95    let mut backend = EcdsaBackend::new();
96    let key_id = backend.import_signing_key(&key_bytes);
97
98    let own_cert = if at_index == 1 {
99        at1.clone()
100    } else {
101        at2.clone()
102    };
103    let peer_cert = if at_index == 1 {
104        at2.clone()
105    } else {
106        at1.clone()
107    };
108
109    // ── Build certificate library ────────────────────────────────────────
110    let cert_library = CertificateLibrary::new(
111        &backend,
112        vec![root_ca],
113        vec![aa],
114        vec![own_cert.clone(), peer_cert],
115    );
116
117    // ── Create sign service and add own certificate ──────────────────────
118    let mut sign_service = SignService::new(backend, cert_library);
119    let own = OwnCertificate::new(own_cert, key_id);
120    sign_service.add_own_certificate(own);
121
122    sign_service
123}
124
125fn main() {
126    // ── Parse arguments ──────────────────────────────────────────────────
127    let args: Vec<String> = env::args().collect();
128    let mut at_index: usize = 1;
129    let mut iface = "lo".to_string();
130
131    let mut i = 1;
132    while i < args.len() {
133        match args[i].as_str() {
134            "--at" => {
135                i += 1;
136                at_index = args[i].parse::<usize>().expect("--at must be 1 or 2");
137                assert!(at_index == 1 || at_index == 2, "--at must be 1 or 2");
138            }
139            "--iface" | "-i" => {
140                i += 1;
141                iface = args[i].clone();
142            }
143            other => {
144                // Positional argument: interface name
145                iface = other.to_string();
146            }
147        }
148        i += 1;
149    }
150
151    println!("=== Secured CAM Sender/Receiver ===");
152    println!("AT index: {}", at_index);
153    println!("Interface: {}", iface);
154
155    // ── Build security stack ─────────────────────────────────────────────
156    let sign_service = build_security_stack(at_index);
157    println!(
158        "Security stack loaded. Own AT HashedId8: {:02x?}",
159        sign_service
160            .cert_library
161            .own_certificates
162            .keys()
163            .next()
164            .unwrap()
165    );
166
167    // Wrap in Arc<Mutex> so it can be shared between threads
168    let sign_service = Arc::new(Mutex::new(sign_service));
169
170    // ── Generate a random locally-administered MAC ───────────────────────
171    let mac = {
172        use std::time::{SystemTime, UNIX_EPOCH};
173        let seed = SystemTime::now()
174            .duration_since(UNIX_EPOCH)
175            .unwrap()
176            .subsec_nanos()
177            ^ (at_index as u32 * 0x1234_5678); // different seed per AT
178        [
179            0x02u8,
180            (seed >> 24) as u8,
181            (seed >> 16) as u8,
182            (seed >> 8) as u8,
183            seed as u8,
184            at_index as u8,
185        ]
186    };
187    println!(
188        "MAC: {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
189        mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
190    );
191
192    // ── MIB ──────────────────────────────────────────────────────────────
193    let mut mib: Mib = Mib::new();
194    mib.itsGnLocalGnAddr = GNAddress::new(M::GnMulticast, ST::PassengerCar, MID::new(mac));
195    mib.itsGnBeaconServiceRetransmitTimer = 0;
196
197    // ── Location Service ─────────────────────────────────────────────────
198    let mut loc_svc = LocationService::new();
199    let gn_gps_rx = loc_svc.subscribe();
200    let ca_gps_rx = loc_svc.subscribe();
201
202    // ── Spawn GN router and BTP router ───────────────────────────────────
203    let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib, None, None, None);
204    let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib);
205
206    // ── Wire RawLinkLayer ────────────────────────────────────────────────
207    let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::<Vec<u8>>();
208
209    // ── Security middleware: TX path ─────────────────────────────────────
210    //
211    // Intercept packets from GN router → link layer.
212    // Sign the payload (CommonHeader + extended header + data) and wrap
213    // in a secured GN packet with BasicNH::SecuredPacket.
214    let (secured_ll_tx, secured_ll_rx) = mpsc::channel::<Vec<u8>>();
215    let sign_svc_tx = Arc::clone(&sign_service);
216    thread::spawn(move || {
217        while let Ok(packet) = gn_to_ll_rx.recv() {
218            if packet.len() < 4 {
219                let _ = secured_ll_tx.send(packet);
220                continue;
221            }
222            // Parse BasicHeader to check if it's a CommonHeader packet
223            let bh_bytes: [u8; 4] = packet[0..4].try_into().unwrap();
224            let bh = BasicHeader::decode(bh_bytes);
225
226            match bh.nh {
227                BasicNH::CommonHeader if packet.len() > 4 => {
228                    // The payload to sign = everything after BasicHeader
229                    let inner_payload = &packet[4..];
230
231                    let request = SNSignRequest {
232                        tbs_message: inner_payload.to_vec(),
233                        its_aid: ITS_AID_CAM_VAL,
234                        permissions: vec![],
235                        generation_location: None,
236                    };
237
238                    let sec_message = {
239                        let mut svc = sign_svc_tx.lock().unwrap();
240                        svc.sign_request(&request).sec_message
241                    };
242
243                    // Build new packet: BasicHeader(nh=SecuredPacket) + sec_message
244                    let mut new_bh = bh;
245                    new_bh.nh = BasicNH::SecuredPacket;
246                    let secured_packet: Vec<u8> = new_bh
247                        .encode()
248                        .iter()
249                        .copied()
250                        .chain(sec_message.iter().copied())
251                        .collect();
252                    let _ = secured_ll_tx.send(secured_packet);
253                }
254                _ => {
255                    // Pass through (e.g. beacons)
256                    let _ = secured_ll_tx.send(packet);
257                }
258            }
259        }
260    });
261
262    // The link layer reads from secured_ll_rx (post-signing)
263    let raw_ll = RawLinkLayer::new(ll_to_gn_tx, secured_ll_rx, &iface, mac);
264    raw_ll.start();
265
266    // ── Security middleware: RX path ─────────────────────────────────────
267    //
268    // Intercept packets from link layer → GN router.
269    // If BasicNH::SecuredPacket, verify and extract, then forward
270    // with BasicNH::CommonHeader.
271    let gn_h_rx = gn_handle.clone();
272    let sign_svc_rx = Arc::clone(&sign_service);
273    thread::spawn(move || {
274        while let Ok(packet) = ll_to_gn_rx.recv() {
275            if packet.len() < 4 {
276                gn_h_rx.send_incoming_packet(packet);
277                continue;
278            }
279            let bh_bytes: [u8; 4] = packet[0..4].try_into().unwrap();
280            let bh = BasicHeader::decode(bh_bytes);
281
282            match bh.nh {
283                BasicNH::SecuredPacket if packet.len() > 4 => {
284                    let sec_message = &packet[4..];
285                    let request = SNVerifyRequest {
286                        message: sec_message.to_vec(),
287                    };
288
289                    let (confirm, _events) = {
290                        let mut svc = sign_svc_rx.lock().unwrap();
291                        let svc = &mut *svc;
292                        let result = verify_message(&request, &svc.backend, &mut svc.cert_library);
293                        // Process VerifyEvents for P2PCD
294                        for event in &result.1 {
295                            match event {
296                                VerifyEvent::UnknownAt(h8) => {
297                                    svc.notify_unknown_at(h8);
298                                }
299                                VerifyEvent::InlineP2pcdRequest(h3s) => {
300                                    svc.notify_inline_p2pcd_request(h3s);
301                                }
302                                VerifyEvent::ReceivedCaCertificate(cert) => {
303                                    svc.notify_received_ca_certificate(cert.as_ref().clone());
304                                }
305                            }
306                        }
307                        result
308                    };
309
310                    if confirm.report == ReportVerify::Success {
311                        println!(
312                            "[SEC RX] Verified OK — ITS-AID={}, cert={:02x?}",
313                            confirm.its_aid,
314                            &confirm.certificate_id[..],
315                        );
316                        // Rebuild the packet: BasicHeader(nh=CommonHeader) + plain_message
317                        let mut new_bh = bh;
318                        new_bh.nh = BasicNH::CommonHeader;
319                        let plain_packet: Vec<u8> = new_bh
320                            .encode()
321                            .iter()
322                            .copied()
323                            .chain(confirm.plain_message.iter().copied())
324                            .collect();
325                        gn_h_rx.send_incoming_packet(plain_packet);
326                    } else {
327                        eprintln!("[SEC RX] Verification failed: {:?}", confirm.report);
328                    }
329                }
330                _ => {
331                    // Non-secured packet — forward directly
332                    gn_h_rx.send_incoming_packet(packet);
333                }
334            }
335        }
336    });
337
338    // ── GN → BTP ─────────────────────────────────────────────────────────
339    let btp_h1 = btp_handle.clone();
340    thread::spawn(move || {
341        while let Ok(ind) = gn_to_btp_rx.recv() {
342            btp_h1.send_gn_data_indication(ind);
343        }
344    });
345
346    // ── BTP → GN ─────────────────────────────────────────────────────────
347    let gn_h2 = gn_handle.clone();
348    thread::spawn(move || {
349        while let Ok(req) = btp_to_gn_rx.recv() {
350            gn_h2.send_gn_data_request(req);
351        }
352    });
353
354    // ── LocationService → GN position vector ─────────────────────────────
355    let gn_h3 = gn_handle.clone();
356    thread::spawn(move || {
357        while let Ok(fix) = gn_gps_rx.recv() {
358            let mut epv = LongPositionVector::decode([0u8; 24]);
359            epv.update_from_gps(
360                fix.latitude,
361                fix.longitude,
362                fix.speed_mps,
363                fix.heading_deg,
364                fix.pai,
365            );
366            gn_h3.update_position_vector(epv);
367        }
368    });
369
370    // ── Local Dynamic Map ────────────────────────────────────────────────
371    let ldm = LdmFacility::new(415_520_000, 21_340_000, 5_000.0);
372    ldm.if_ldm_3
373        .register_data_provider(RegisterDataProviderReq {
374            application_id: ITS_AID_CAM,
375        });
376    ldm.if_ldm_4
377        .register_data_consumer(RegisterDataConsumerReq {
378            application_id: ITS_AID_CAM,
379        });
380
381    // ── CA Basic Service ─────────────────────────────────────────────────
382    let station_id = u32::from_be_bytes([mac[2], mac[3], mac[4], mac[5]]);
383    let vehicle_data = VehicleData {
384        station_id,
385        ..VehicleData::default()
386    };
387    let (ca_svc, _cam_rx) =
388        CooperativeAwarenessBasicService::new(btp_handle.clone(), vehicle_data, Some(ldm.clone()));
389    ca_svc.start(ca_gps_rx);
390
391    // ── LDM query printer ────────────────────────────────────────────────
392    let ldm_reader = ldm.clone();
393    thread::spawn(move || loop {
394        thread::sleep(Duration::from_secs(1));
395        let resp = ldm_reader
396            .if_ldm_4
397            .request_data_objects(RequestDataObjectsReq {
398                application_id: ITS_AID_CAM,
399                data_object_types: vec![ITS_AID_CAM],
400                filter: None,
401                order: None,
402                max_results: None,
403            });
404        if resp.data_objects.is_empty() {
405            println!("[LDM] No CAM records in store");
406        } else {
407            println!("[LDM] {} CAM record(s):", resp.data_objects.len());
408            for entry in &resp.data_objects {
409                if let ItsDataObject::Cam(cam) = &entry.data_object {
410                    let lat = cam
411                        .cam
412                        .cam_parameters
413                        .basic_container
414                        .reference_position
415                        .latitude
416                        .0 as f64
417                        / 1e7;
418                    let lon = cam
419                        .cam
420                        .cam_parameters
421                        .basic_container
422                        .reference_position
423                        .longitude
424                        .0 as f64
425                        / 1e7;
426                    println!(
427                        "  [LDM CAM] record={:>5} station={:>10}  lat={:.5}  lon={:.5}",
428                        entry.record_id, cam.header.station_id.0, lat, lon,
429                    );
430                }
431            }
432        }
433    });
434
435    // ── GPS publisher (random trajectory — mirrors Python _RandomTrajectoryLocationService) ──
436    //
437    // Updates every 80 ms (below T_CheckCamGen = 100 ms) with a randomly
438    // changing heading (±5–15° per step, always exceeding the 4° condition-1
439    // threshold) and speed random-walking within [5, 20] m/s.  This sustains
440    // 10 Hz CAM generation via the dynamics triggering conditions.
441    thread::sleep(Duration::from_millis(100));
442    println!("Publishing GPS fixes @ 12.5 Hz (80 ms) with random trajectory — Ctrl+C to stop\n");
443
444    use rand::Rng;
445    let mut rng = rand::thread_rng();
446
447    const PERIOD_S: f64 = 0.08; // 80 ms — below T_CheckCamGen
448    const EARTH_R: f64 = 6_371_000.0;
449
450    let mut lat: f64 = 41.386931;
451    let mut lon: f64 = 2.112104;
452    let mut heading: f64 = rng.gen_range(0.0..360.0);
453    let mut speed: f64 = 10.0; // ~36 km/h
454
455    loop {
456        thread::sleep(Duration::from_millis(80));
457
458        // Heading: random signed change guaranteed to exceed the 4° threshold
459        let delta: f64 = rng.gen_range(5.0..15.0) * if rng.gen_bool(0.5) { 1.0 } else { -1.0 };
460        heading = (heading + delta).rem_euclid(360.0);
461
462        // Speed: small random walk within [5, 20] m/s
463        speed = (speed + rng.gen_range(-0.5..0.5)).clamp(5.0, 20.0);
464
465        // Position update (flat-Earth approximation; step sizes < 2 m)
466        let d = speed * PERIOD_S;
467        let heading_r = heading * PI / 180.0;
468        let lat_r = lat * PI / 180.0;
469        lat += (d * heading_r.cos() / EARTH_R) * (180.0 / PI);
470        lon += (d * heading_r.sin() / (EARTH_R * lat_r.cos())) * (180.0 / PI);
471
472        loc_svc.publish(GpsFix {
473            latitude: lat,
474            longitude: lon,
475            altitude_m: 120.0,
476            speed_mps: speed,
477            heading_deg: heading,
478            pai: true,
479        });
480    }
481}