From 9f774b1a0f5c930df0653b95a4fc77677c94b2df Mon Sep 17 00:00:00 2001 From: Jordi Marias I Parella Date: Mon, 6 Apr 2026 17:20:52 +0200 Subject: [PATCH 1/6] security Layer and a few extra changes --- Cargo.lock | 535 ++ Cargo.toml | 13 + examples/bench_cam_rx.rs | 116 +- examples/bench_cam_tx.rs | 174 +- examples/bench_congestion.rs | 427 +- examples/cam_sender_receiver.rs | 96 +- examples/denm_sender_receiver.rs | 40 +- examples/generate_certificate_chain.rs | 238 + examples/secured_cam_sender_receiver.rs | 448 + examples/vam_sender_receiver.rs | 40 +- src/btp/btp_header.rs | 10 +- src/btp/mod.rs | 4 +- src/btp/router.rs | 101 +- src/btp/service_access_point.rs | 211 +- src/facilities/ca_basic_service/cam_coder.rs | 12 +- .../ca_basic_service/cam_reception.rs | 51 +- .../ca_basic_service/cam_transmission.rs | 772 +- src/facilities/ca_basic_service/mod.rs | 26 +- .../denm_coder.rs | 22 +- .../denm_reception.rs | 6 +- .../denm_transmission.rs | 148 +- .../mod.rs | 20 +- src/facilities/local_dynamic_map/if_ldm_3.rs | 8 +- src/facilities/local_dynamic_map/if_ldm_4.rs | 11 +- .../local_dynamic_map/ldm_constants.rs | 27 +- .../local_dynamic_map/ldm_maintenance.rs | 2 +- .../local_dynamic_map/ldm_service.rs | 205 +- .../local_dynamic_map/ldm_storage.rs | 86 +- src/facilities/local_dynamic_map/ldm_types.rs | 64 +- src/facilities/local_dynamic_map/mod.rs | 2 +- src/facilities/location_service.rs | 14 +- src/facilities/vru_awareness_service/mod.rs | 17 +- .../vru_awareness_service/vam_bindings.rs | 9 +- .../vru_awareness_service/vam_coder.rs | 20 +- .../vru_awareness_service/vam_reception.rs | 26 +- .../vru_awareness_service/vam_transmission.rs | 327 +- src/geonet/basic_header.rs | 188 +- src/geonet/common_header.rs | 122 +- src/geonet/gbc_extended_header.rs | 112 +- src/geonet/gn_address.rs | 150 +- src/geonet/guc_extended_header.rs | 68 + src/geonet/location_table.rs | 345 +- src/geonet/ls_extended_header.rs | 120 + src/geonet/mib.rs | 274 +- src/geonet/mod.rs | 5 +- src/geonet/position_vector.rs | 154 +- src/geonet/router.rs | 1840 +++- src/geonet/service_access_point.rs | 224 +- src/geonet/tsb_extended_header.rs | 51 + src/lib.rs | 1 + src/link_layer/mod.rs | 2 +- src/link_layer/packet_consts.rs | 2 +- src/link_layer/raw_link_layer.rs | 36 +- src/security/certificate.rs | 508 + src/security/certificate_library.rs | 188 + src/security/ecdsa_backend.rs | 147 + src/security/mod.rs | 29 + src/security/security_asn.rs | 8408 +++++++++++++++++ src/security/sign_service.rs | 302 + src/security/sn_sap.rs | 71 + src/security/time_service.rs | 33 + src/security/verify_service.rs | 346 + 62 files changed, 15940 insertions(+), 2114 deletions(-) create mode 100644 examples/generate_certificate_chain.rs create mode 100644 examples/secured_cam_sender_receiver.rs create mode 100644 src/geonet/guc_extended_header.rs create mode 100644 src/geonet/ls_extended_header.rs create mode 100644 src/geonet/tsb_extended_header.rs create mode 100644 src/security/certificate.rs create mode 100644 src/security/certificate_library.rs create mode 100644 src/security/ecdsa_backend.rs create mode 100644 src/security/mod.rs create mode 100644 src/security/security_asn.rs create mode 100644 src/security/sign_service.rs create mode 100644 src/security/sn_sap.rs create mode 100644 src/security/time_service.rs create mode 100644 src/security/verify_service.rs diff --git a/Cargo.lock b/Cargo.lock index 8cd8c87..fe0d093 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,12 +11,33 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bitvec" version = "1.0.1" @@ -39,12 +60,37 @@ dependencies = [ "nom", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "cc" +version = "1.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -57,7 +103,91 @@ version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ + "iana-time-zone", + "js-sys", "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", ] [[package]] @@ -66,12 +196,59 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -89,12 +266,56 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ipnetwork" version = "0.20.0" @@ -119,12 +340,28 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "js-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + [[package]] name = "memchr" version = "2.8.0" @@ -153,6 +390,19 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -163,6 +413,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -172,6 +431,27 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -187,6 +467,37 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pnet" version = "0.34.0" @@ -287,6 +598,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -363,6 +683,21 @@ dependencies = [ "xml-no-std", ] +[[package]] +name = "rasn-compiler" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8091cf7b6525a6880d6f220cdaf830d99262468bb05b929b985a9320078a06a7" +dependencies = [ + "chrono", + "nom", + "num", + "proc-macro2", + "quote", + "regex", + "wasm-bindgen", +] + [[package]] name = "rasn-derive" version = "0.26.6" @@ -417,14 +752,49 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rustflexstack" version = "0.1.1" dependencies = [ + "ecdsa", + "elliptic-curve", "libc", + "p256", "pnet", "rand", "rasn", + "rasn-compiler", + "sha2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", ] [[package]] @@ -469,6 +839,33 @@ dependencies = [ "zmij", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "snafu" version = "0.8.9" @@ -490,6 +887,22 @@ dependencies = [ "syn", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.117" @@ -507,6 +920,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -522,12 +941,63 @@ dependencies = [ "getrandom", ] +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasm-bindgen" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +dependencies = [ + "unicode-ident", +] + [[package]] name = "winapi" version = "0.3.9" @@ -550,6 +1020,65 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "wyz" version = "0.5.1" @@ -585,6 +1114,12 @@ dependencies = [ "syn", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index d183482..f5f8a8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,21 @@ pnet = "0.34.0" rand = "0.8" libc = "0.2" rasn = "0.26" +rasn-compiler = { version = "0.9", optional = true } +sha2 = "0.10" +p256 = { version = "0.13", features = ["ecdsa", "ecdsa-core"] } +ecdsa = { version = "0.16", features = ["signing", "verifying"] } +elliptic-curve = { version = "0.13", features = ["sec1"] } [[example]] name = "cam_sender_receiver" path = "examples/cam_sender_receiver.rs" +[[example]] +name = "generate_certificate_chain" +path = "examples/generate_certificate_chain.rs" + +[[example]] +name = "secured_cam_sender_receiver" +path = "examples/secured_cam_sender_receiver.rs" + diff --git a/examples/bench_cam_rx.rs b/examples/bench_cam_rx.rs index 602c002..fe59a4b 100644 --- a/examples/bench_cam_rx.rs +++ b/examples/bench_cam_rx.rs @@ -31,8 +31,11 @@ use std::thread; use std::time::{Duration, Instant}; fn main() { - let iface = env::args().nth(1).unwrap_or_else(|| "lo".to_string()); - let duration_s = env::args().nth(2).and_then(|s| s.parse::().ok()).unwrap_or(30); + let iface = env::args().nth(1).unwrap_or_else(|| "lo".to_string()); + let duration_s = env::args() + .nth(2) + .and_then(|s| s.parse::().ok()) + .unwrap_or(30); println!("=== Benchmark: Maximum CAM RX throughput ==="); println!("Interface : {iface}"); @@ -45,12 +48,18 @@ fn main() { mib.itsGnBeaconServiceRetransmitTimer = 0; // ── Routers + link layer ────────────────────────────────────────────────── - let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone()); - let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); + let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone(), None, None, None); + let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::>(); RawLinkLayer::new(ll_to_gn_tx, gn_to_ll_rx, &iface, mac).start(); - wire_routers(&gn_handle, &btp_handle, ll_to_gn_rx, gn_to_btp_rx, btp_to_gn_rx); + wire_routers( + &gn_handle, + &btp_handle, + ll_to_gn_rx, + gn_to_btp_rx, + btp_to_gn_rx, + ); // Seed position vector. let mut epv = LongPositionVector::decode([0u8; 24]); @@ -64,34 +73,41 @@ fn main() { // ── Benchmark loop ──────────────────────────────────────────────────────── println!("Waiting for CAMs on port 2001…\n"); - println!("{:>7} {:>10} {:>12} {:>12} {:>10}", "time(s)", "total_recv", - "rate(pkt/s)", "avg_dec(µs)", "errors"); + println!( + "{:>7} {:>10} {:>12} {:>12} {:>10}", + "time(s)", "total_recv", "rate(pkt/s)", "avg_dec(µs)", "errors" + ); let coder = CamCoder::new(); - let mut total_recv: u64 = 0; - let mut total_errors: u64 = 0; + let mut total_recv: u64 = 0; + let mut total_errors: u64 = 0; let mut total_dec_us: u128 = 0; - let bench_start = Instant::now(); - let bench_end = bench_start + Duration::from_secs(duration_s); + let bench_start = Instant::now(); + let bench_end = bench_start + Duration::from_secs(duration_s); let mut win_start = Instant::now(); let mut win_recv: u64 = 0; loop { let now = Instant::now(); - if now >= bench_end { break; } + if now >= bench_end { + break; + } let timeout = (bench_end - now).min(Duration::from_millis(500)); match ind_rx.recv_timeout(timeout) { Ok(ind) => { let t0 = Instant::now(); match coder.decode(&ind.data) { - Ok(_) => {} - Err(e) => { total_errors += 1; eprintln!("[RX] Decode error: {e}"); } + Ok(_) => {} + Err(e) => { + total_errors += 1; + eprintln!("[RX] Decode error: {e}"); + } } let dec_us = t0.elapsed().as_micros(); - total_recv += 1; + total_recv += 1; total_dec_us += dec_us; - win_recv += 1; + win_recv += 1; } Err(mpsc::RecvTimeoutError::Timeout) => {} Err(mpsc::RecvTimeoutError::Disconnected) => break, @@ -99,19 +115,33 @@ fn main() { let win_elapsed = win_start.elapsed(); if win_elapsed >= Duration::from_secs(1) { - let pps = win_recv as f64 / win_elapsed.as_secs_f64(); - let avg_dec = if total_recv > 0 { total_dec_us / total_recv as u128 } else { 0 }; - println!("{:>7.1} {:>10} {:>12.1} {:>12} {:>10}", - bench_start.elapsed().as_secs_f64(), total_recv, pps, avg_dec, total_errors); + let pps = win_recv as f64 / win_elapsed.as_secs_f64(); + let avg_dec = if total_recv > 0 { + total_dec_us / total_recv as u128 + } else { + 0 + }; + println!( + "{:>7.1} {:>10} {:>12.1} {:>12} {:>10}", + bench_start.elapsed().as_secs_f64(), + total_recv, + pps, + avg_dec, + total_errors + ); win_start = Instant::now(); - win_recv = 0; + win_recv = 0; } } // ── Summary ─────────────────────────────────────────────────────────────── - let elapsed = bench_start.elapsed().as_secs_f64(); - let avg_rate = total_recv as f64 / elapsed; - let avg_decode = if total_recv > 0 { total_dec_us / total_recv as u128 } else { 0 }; + let elapsed = bench_start.elapsed().as_secs_f64(); + let avg_rate = total_recv as f64 / elapsed; + let avg_decode = if total_recv > 0 { + total_dec_us / total_recv as u128 + } else { + 0 + }; println!(); println!("=== CAM RX Results ==="); @@ -126,21 +156,43 @@ fn main() { fn random_mac() -> [u8; 6] { use std::time::{SystemTime, UNIX_EPOCH}; - let s = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_nanos(); - [0x02, (s >> 24) as u8, (s >> 16) as u8, (s >> 8) as u8, s as u8, 0xCC] + let s = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .subsec_nanos(); + [ + 0x02, + (s >> 24) as u8, + (s >> 16) as u8, + (s >> 8) as u8, + s as u8, + 0xCC, + ] } fn wire_routers( - gn: &RouterHandle, - btp: &BTPRouterHandle, - ll_rx: mpsc::Receiver>, + gn: &RouterHandle, + btp: &BTPRouterHandle, + ll_rx: mpsc::Receiver>, gn_btp_rx: mpsc::Receiver, btp_gn_rx: mpsc::Receiver, ) { let g1 = gn.clone(); - thread::spawn(move || { while let Ok(p) = ll_rx.recv() { g1.send_incoming_packet(p); } }); + thread::spawn(move || { + while let Ok(p) = ll_rx.recv() { + g1.send_incoming_packet(p); + } + }); let b1 = btp.clone(); - thread::spawn(move || { while let Ok(i) = gn_btp_rx.recv() { b1.send_gn_data_indication(i); } }); + thread::spawn(move || { + while let Ok(i) = gn_btp_rx.recv() { + b1.send_gn_data_indication(i); + } + }); let g2 = gn.clone(); - thread::spawn(move || { while let Ok(r) = btp_gn_rx.recv() { g2.send_gn_data_request(r); } }); + thread::spawn(move || { + while let Ok(r) = btp_gn_rx.recv() { + g2.send_gn_data_request(r); + } + }); } diff --git a/examples/bench_cam_tx.rs b/examples/bench_cam_tx.rs index 7cba19a..49638d8 100644 --- a/examples/bench_cam_tx.rs +++ b/examples/bench_cam_tx.rs @@ -15,27 +15,26 @@ use rustflexstack::btp::router::{BTPRouterHandle, Router as BTPRouter}; use rustflexstack::btp::service_access_point::BTPDataRequest; use rustflexstack::facilities::ca_basic_service::cam_coder::{ - cam_header, generation_delta_time_now, - AccelerationComponent, AccelerationConfidence, AccelerationValue, - Altitude, AltitudeConfidence, AltitudeValue, BasicContainer, - BasicVehicleContainerHighFrequency, Cam, CamCoder, CamParameters, CamPayload, - Curvature, CurvatureCalculationMode, CurvatureConfidence, CurvatureValue, - DriveDirection, Heading, HeadingConfidence, HeadingValue, - HighFrequencyContainer, Latitude, Longitude, PositionConfidenceEllipse, - ReferencePositionWithConfidence, SemiAxisLength, Speed, SpeedConfidence, - SpeedValue, TrafficParticipantType, VehicleLength, - VehicleLengthConfidenceIndication, VehicleLengthValue, VehicleWidth, - Wgs84AngleValue, YawRate, YawRateConfidence, YawRateValue, + cam_header, generation_delta_time_now, AccelerationComponent, AccelerationConfidence, + AccelerationValue, Altitude, AltitudeConfidence, AltitudeValue, BasicContainer, + BasicVehicleContainerHighFrequency, Cam, CamCoder, CamParameters, CamPayload, Curvature, + CurvatureCalculationMode, CurvatureConfidence, CurvatureValue, DriveDirection, Heading, + HeadingConfidence, HeadingValue, HighFrequencyContainer, Latitude, Longitude, + PositionConfidenceEllipse, ReferencePositionWithConfidence, SemiAxisLength, Speed, + SpeedConfidence, SpeedValue, TrafficParticipantType, VehicleLength, + VehicleLengthConfidenceIndication, VehicleLengthValue, VehicleWidth, Wgs84AngleValue, YawRate, + YawRateConfidence, YawRateValue, }; use rustflexstack::geonet::gn_address::{GNAddress, M, MID, ST}; use rustflexstack::geonet::mib::Mib; use rustflexstack::geonet::position_vector::LongPositionVector; use rustflexstack::geonet::router::{Router as GNRouter, RouterHandle}; use rustflexstack::geonet::service_access_point::{ - Area, CommunicationProfile, CommonNH, GNDataIndication, GNDataRequest, - HeaderSubType, HeaderType, PacketTransportType, TopoBroadcastHST, TrafficClass, + Area, CommonNH, CommunicationProfile, GNDataIndication, GNDataRequest, HeaderSubType, + HeaderType, PacketTransportType, TopoBroadcastHST, TrafficClass, }; use rustflexstack::link_layer::raw_link_layer::RawLinkLayer; +use rustflexstack::security::sn_sap::SecurityProfile; use std::env; use std::sync::mpsc; @@ -43,8 +42,11 @@ use std::thread; use std::time::{Duration, Instant}; fn main() { - let iface = env::args().nth(1).unwrap_or_else(|| "lo".to_string()); - let duration_s = env::args().nth(2).and_then(|s| s.parse::().ok()).unwrap_or(10); + let iface = env::args().nth(1).unwrap_or_else(|| "lo".to_string()); + let duration_s = env::args() + .nth(2) + .and_then(|s| s.parse::().ok()) + .unwrap_or(10); println!("=== Benchmark: Maximum CAM TX throughput ==="); println!("Interface : {iface}"); @@ -57,12 +59,18 @@ fn main() { mib.itsGnBeaconServiceRetransmitTimer = 0; // ── Routers + link layer ────────────────────────────────────────────────── - let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone()); - let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); + let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone(), None, None, None); + let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::>(); RawLinkLayer::new(ll_to_gn_tx, gn_to_ll_rx, &iface, mac).start(); - wire_routers(&gn_handle, &btp_handle, ll_to_gn_rx, gn_to_btp_rx, btp_to_gn_rx); + wire_routers( + &gn_handle, + &btp_handle, + ll_to_gn_rx, + gn_to_btp_rx, + btp_to_gn_rx, + ); // Seed GN position vector so packets are accepted downstream. let mut epv = LongPositionVector::decode([0u8; 24]); @@ -72,48 +80,59 @@ fn main() { // ── Coder + template CAM ────────────────────────────────────────────────── let station_id = u32::from_be_bytes([mac[2], mac[3], mac[4], mac[5]]); - let coder = CamCoder::new(); - let template = make_cam(station_id); + let coder = CamCoder::new(); + let template = make_cam(station_id); // ── Benchmark loop ──────────────────────────────────────────────────────── println!("Sending CAMs as fast as possible…\n"); - println!("{:>7} {:>10} {:>12} {:>12}", "time(s)", "total_sent", "rate(pkt/s)", "avg_enc(µs)"); + println!( + "{:>7} {:>10} {:>12} {:>12}", + "time(s)", "total_sent", "rate(pkt/s)", "avg_enc(µs)" + ); - let mut total_sent: u64 = 0; + let mut total_sent: u64 = 0; let mut total_enc_us: u128 = 0; - let bench_start = Instant::now(); - let bench_end = bench_start + Duration::from_secs(duration_s); + let bench_start = Instant::now(); + let bench_end = bench_start + Duration::from_secs(duration_s); let mut win_start = Instant::now(); let mut win_sent: u64 = 0; while Instant::now() < bench_end { let t0 = Instant::now(); let data = match coder.encode(&template) { - Ok(d) => d, - Err(e) => { eprintln!("[TX] Encode error: {e}"); continue; } + Ok(d) => d, + Err(e) => { + eprintln!("[TX] Encode error: {e}"); + continue; + } }; let enc_us = t0.elapsed().as_micros(); btp_handle.send_btp_data_request(cam_btp_request(data)); - total_sent += 1; + total_sent += 1; total_enc_us += enc_us; - win_sent += 1; + win_sent += 1; let win_elapsed = win_start.elapsed(); if win_elapsed >= Duration::from_secs(1) { - let pps = win_sent as f64 / win_elapsed.as_secs_f64(); + let pps = win_sent as f64 / win_elapsed.as_secs_f64(); let avg_enc = total_enc_us / total_sent.max(1) as u128; - println!("{:>7.1} {:>10} {:>12.1} {:>12}", - bench_start.elapsed().as_secs_f64(), total_sent, pps, avg_enc); + println!( + "{:>7.1} {:>10} {:>12.1} {:>12}", + bench_start.elapsed().as_secs_f64(), + total_sent, + pps, + avg_enc + ); win_start = Instant::now(); - win_sent = 0; + win_sent = 0; } } // ── Summary ─────────────────────────────────────────────────────────────── - let elapsed = bench_start.elapsed().as_secs_f64(); - let avg_rate = total_sent as f64 / elapsed; + let elapsed = bench_start.elapsed().as_secs_f64(); + let avg_rate = total_sent as f64 / elapsed; let avg_encode = total_enc_us / total_sent.max(1) as u128; println!(); @@ -131,13 +150,22 @@ fn make_cam(station_id: u32) -> Cam { Heading::new(HeadingValue(900), HeadingConfidence(127)), Speed::new(SpeedValue(0), SpeedConfidence(127)), DriveDirection::unavailable, - VehicleLength::new(VehicleLengthValue(1023), VehicleLengthConfidenceIndication::unavailable), + VehicleLength::new( + VehicleLengthValue(1023), + VehicleLengthConfidenceIndication::unavailable, + ), VehicleWidth(62), AccelerationComponent::new(AccelerationValue(161), AccelerationConfidence(102)), Curvature::new(CurvatureValue(1023), CurvatureConfidence::unavailable), CurvatureCalculationMode::unavailable, YawRate::new(YawRateValue(32767), YawRateConfidence::unavailable), - None, None, None, None, None, None, None, + None, + None, + None, + None, + None, + None, + None, ); Cam::new( cam_header(station_id), @@ -150,13 +178,17 @@ fn make_cam(station_id: u32) -> Cam { Latitude(415_520_000), Longitude(21_340_000), PositionConfidenceEllipse::new( - SemiAxisLength(4095), SemiAxisLength(4095), Wgs84AngleValue(3601), + SemiAxisLength(4095), + SemiAxisLength(4095), + Wgs84AngleValue(3601), ), Altitude::new(AltitudeValue(12000), AltitudeConfidence::unavailable), ), ), HighFrequencyContainer::basicVehicleContainerHighFrequency(hf), - None, None, None, + None, + None, + None, ), ), ) @@ -169,15 +201,35 @@ fn cam_btp_request(data: Vec) -> BTPDataRequest { destination_port: 2001, destination_port_info: 0, gn_packet_transport_type: PacketTransportType { - header_type: HeaderType::Tsb, + header_type: HeaderType::Tsb, header_sub_type: HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop), }, gn_destination_address: GNAddress { - m: M::GnMulticast, st: ST::Unknown, mid: MID::new([0xFF; 6]), + m: M::GnMulticast, + st: ST::Unknown, + mid: MID::new([0xFF; 6]), }, communication_profile: CommunicationProfile::Unspecified, - gn_area: Area { latitude: 0, longitude: 0, a: 0, b: 0, angle: 0 }, - traffic_class: TrafficClass { scf: false, channel_offload: false, tc_id: 0 }, + gn_area: Area { + latitude: 0, + longitude: 0, + a: 0, + b: 0, + angle: 0, + }, + traffic_class: TrafficClass { + scf: false, + channel_offload: false, + tc_id: 0, + }, + security_profile: SecurityProfile::NoSecurity, + its_aid: 36, + security_permissions: vec![], + gn_max_hop_limit: 1, + gn_max_packet_lifetime: None, + gn_repetition_interval: None, + gn_max_repetition_time: None, + destination: None, length: data.len() as u16, data, } @@ -185,21 +237,43 @@ fn cam_btp_request(data: Vec) -> BTPDataRequest { fn random_mac() -> [u8; 6] { use std::time::{SystemTime, UNIX_EPOCH}; - let s = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_nanos(); - [0x02, (s >> 24) as u8, (s >> 16) as u8, (s >> 8) as u8, s as u8, 0xBB] + let s = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .subsec_nanos(); + [ + 0x02, + (s >> 24) as u8, + (s >> 16) as u8, + (s >> 8) as u8, + s as u8, + 0xBB, + ] } fn wire_routers( - gn: &RouterHandle, - btp: &BTPRouterHandle, - ll_rx: mpsc::Receiver>, + gn: &RouterHandle, + btp: &BTPRouterHandle, + ll_rx: mpsc::Receiver>, gn_btp_rx: mpsc::Receiver, btp_gn_rx: mpsc::Receiver, ) { let g1 = gn.clone(); - thread::spawn(move || { while let Ok(p) = ll_rx.recv() { g1.send_incoming_packet(p); } }); + thread::spawn(move || { + while let Ok(p) = ll_rx.recv() { + g1.send_incoming_packet(p); + } + }); let b1 = btp.clone(); - thread::spawn(move || { while let Ok(i) = gn_btp_rx.recv() { b1.send_gn_data_indication(i); } }); + thread::spawn(move || { + while let Ok(i) = gn_btp_rx.recv() { + b1.send_gn_data_indication(i); + } + }); let g2 = gn.clone(); - thread::spawn(move || { while let Ok(r) = btp_gn_rx.recv() { g2.send_gn_data_request(r); } }); + thread::spawn(move || { + while let Ok(r) = btp_gn_rx.recv() { + g2.send_gn_data_request(r); + } + }); } diff --git a/examples/bench_congestion.rs b/examples/bench_congestion.rs index 23a0a6e..6090042 100644 --- a/examples/bench_congestion.rs +++ b/examples/bench_congestion.rs @@ -20,51 +20,42 @@ use rustflexstack::btp::router::{BTPRouterHandle, Router as BTPRouter}; use rustflexstack::btp::service_access_point::{BTPDataIndication, BTPDataRequest}; use rustflexstack::facilities::ca_basic_service::cam_coder::{ - cam_header, generation_delta_time_now as cam_gdt, - AccelerationComponent, AccelerationConfidence, AccelerationValue, - Altitude, AltitudeConfidence, AltitudeValue, BasicContainer, - BasicVehicleContainerHighFrequency, Cam, CamCoder, CamParameters, CamPayload, - Curvature, CurvatureCalculationMode, CurvatureConfidence, CurvatureValue, - DriveDirection, Heading, HeadingConfidence, HeadingValue, - HighFrequencyContainer, Latitude, Longitude, PositionConfidenceEllipse, - ReferencePositionWithConfidence, SemiAxisLength, Speed, SpeedConfidence, - SpeedValue, TrafficParticipantType, VehicleLength, - VehicleLengthConfidenceIndication, VehicleLengthValue, VehicleWidth, - Wgs84AngleValue, YawRate, YawRateConfidence, YawRateValue, + cam_header, generation_delta_time_now as cam_gdt, AccelerationComponent, + AccelerationConfidence, AccelerationValue, Altitude, AltitudeConfidence, AltitudeValue, + BasicContainer, BasicVehicleContainerHighFrequency, Cam, CamCoder, CamParameters, CamPayload, + Curvature, CurvatureCalculationMode, CurvatureConfidence, CurvatureValue, DriveDirection, + Heading, HeadingConfidence, HeadingValue, HighFrequencyContainer, Latitude, Longitude, + PositionConfidenceEllipse, ReferencePositionWithConfidence, SemiAxisLength, Speed, + SpeedConfidence, SpeedValue, TrafficParticipantType, VehicleLength, + VehicleLengthConfidenceIndication, VehicleLengthValue, VehicleWidth, Wgs84AngleValue, YawRate, + YawRateConfidence, YawRateValue, }; +use rustflexstack::facilities::decentralized_environmental_notification_service::denm_coder::CauseCodeChoice; use rustflexstack::facilities::decentralized_environmental_notification_service::{ - denm_coder::AccidentSubCauseCode, - DecentralizedEnvironmentalNotificationService, DENRequest, DenmCoder, - VehicleData as DenVehicleData, + denm_coder::AccidentSubCauseCode, DENRequest, DecentralizedEnvironmentalNotificationService, + DenmCoder, VehicleData as DenVehicleData, }; -use rustflexstack::facilities::decentralized_environmental_notification_service::denm_coder::CauseCodeChoice; -use rustflexstack::facilities::vru_awareness_service::{ - vam_coder::{ - generation_delta_time_now as vam_gdt, vam_header, - AccelerationConfidence as VamAccelConf, - Altitude as VamAlt, AltitudeConfidence as VamAltConf, - AltitudeValue as VamAltVal, BasicContainer as VamBasicContainer, Latitude as VamLat, - Longitude as VamLon, - LongitudinalAcceleration as VamLongAccel, - LongitudinalAccelerationValue as VamLongAccelVal, - PositionConfidenceEllipse as VamPCE, - ReferencePositionWithConfidence as VamRefPos, SemiAxisLength as VamSAL, - Speed as VamSpeed, SpeedConfidence as VamSpeedConf, SpeedValue as VamSpeedVal, - TrafficParticipantType as VamTPT, Vam, VamCoder, VamParameters, - VruAwareness, VruHighFrequencyContainer, - Wgs84Angle as VamWgs84Angle, Wgs84AngleConfidence as VamWgs84AnglConf, - Wgs84AngleValue as VamWgs84AngleVal, - }, +use rustflexstack::facilities::vru_awareness_service::vam_coder::{ + generation_delta_time_now as vam_gdt, vam_header, AccelerationConfidence as VamAccelConf, + Altitude as VamAlt, AltitudeConfidence as VamAltConf, AltitudeValue as VamAltVal, + BasicContainer as VamBasicContainer, Latitude as VamLat, Longitude as VamLon, + LongitudinalAcceleration as VamLongAccel, LongitudinalAccelerationValue as VamLongAccelVal, + PositionConfidenceEllipse as VamPCE, ReferencePositionWithConfidence as VamRefPos, + SemiAxisLength as VamSAL, Speed as VamSpeed, SpeedConfidence as VamSpeedConf, + SpeedValue as VamSpeedVal, TrafficParticipantType as VamTPT, Vam, VamCoder, VamParameters, + VruAwareness, VruHighFrequencyContainer, Wgs84Angle as VamWgs84Angle, + Wgs84AngleConfidence as VamWgs84AnglConf, Wgs84AngleValue as VamWgs84AngleVal, }; use rustflexstack::geonet::gn_address::{GNAddress, M, MID, ST}; use rustflexstack::geonet::mib::Mib; use rustflexstack::geonet::position_vector::LongPositionVector; use rustflexstack::geonet::router::{Router as GNRouter, RouterHandle}; use rustflexstack::geonet::service_access_point::{ - Area, CommunicationProfile, CommonNH, GNDataIndication, GNDataRequest, - HeaderSubType, HeaderType, PacketTransportType, TopoBroadcastHST, TrafficClass, + Area, CommonNH, CommunicationProfile, GNDataIndication, GNDataRequest, HeaderSubType, + HeaderType, PacketTransportType, TopoBroadcastHST, TrafficClass, }; use rustflexstack::link_layer::raw_link_layer::RawLinkLayer; +use rustflexstack::security::sn_sap::SecurityProfile; use std::env; use std::sync::{ @@ -75,8 +66,11 @@ use std::thread; use std::time::{Duration, Instant}; fn main() { - let iface = env::args().nth(1).unwrap_or_else(|| "lo".to_string()); - let duration_s = env::args().nth(2).and_then(|s| s.parse::().ok()).unwrap_or(30); + let iface = env::args().nth(1).unwrap_or_else(|| "lo".to_string()); + let duration_s = env::args() + .nth(2) + .and_then(|s| s.parse::().ok()) + .unwrap_or(30); println!("=== Benchmark: Congested scenario (CAM + VAM + DENM) ==="); println!("Interface : {iface}"); @@ -92,12 +86,18 @@ fn main() { let station_id = u32::from_be_bytes([mac[2], mac[3], mac[4], mac[5]]); // ── Routers + link layer ────────────────────────────────────────────────── - let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone()); - let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); + let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone(), None, None, None); + let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::>(); RawLinkLayer::new(ll_to_gn_tx, gn_to_ll_rx, &iface, mac).start(); - wire_routers(&gn_handle, &btp_handle, ll_to_gn_rx, gn_to_btp_rx, btp_to_gn_rx); + wire_routers( + &gn_handle, + &btp_handle, + ll_to_gn_rx, + gn_to_btp_rx, + btp_to_gn_rx, + ); // Seed position vector. let mut epv = LongPositionVector::decode([0u8; 24]); @@ -106,32 +106,33 @@ fn main() { thread::sleep(Duration::from_millis(50)); // ── DEN service (single instance for both TX and RX) ───────────────────── - let vd = DenVehicleData { station_id, station_type: 5 }; - let (den_svc, denm_rx) = DecentralizedEnvironmentalNotificationService::new( - btp_handle.clone(), - vd, - ); + let vd = DenVehicleData { + station_id, + station_type: 5, + }; + let (den_svc, denm_rx) = + DecentralizedEnvironmentalNotificationService::new(btp_handle.clone(), vd); // ── Shared atomic counters ──────────────────────────────────────────────── - let cam_tx = Arc::new(AtomicU64::new(0)); - let vam_tx = Arc::new(AtomicU64::new(0)); + let cam_tx = Arc::new(AtomicU64::new(0)); + let vam_tx = Arc::new(AtomicU64::new(0)); let denm_tx = Arc::new(AtomicU64::new(0)); - let cam_rx = Arc::new(AtomicU64::new(0)); - let vam_rx = Arc::new(AtomicU64::new(0)); + let cam_rx = Arc::new(AtomicU64::new(0)); + let vam_rx = Arc::new(AtomicU64::new(0)); let denm_rx_cnt = Arc::new(AtomicU64::new(0)); - let rx_err = Arc::new(AtomicU64::new(0)); - let cam_dec_us_total = Arc::new(AtomicU64::new(0)); - let vam_dec_us_total = Arc::new(AtomicU64::new(0)); + let rx_err = Arc::new(AtomicU64::new(0)); + let cam_dec_us_total = Arc::new(AtomicU64::new(0)); + let vam_dec_us_total = Arc::new(AtomicU64::new(0)); let bench_end = Instant::now() + Duration::from_secs(duration_s); // ── CAM TX thread (100 ms interval → 10 Hz) ─────────────────────────────── { - let btp = btp_handle.clone(); - let cnt = cam_tx.clone(); - let end = bench_end; + let btp = btp_handle.clone(); + let cnt = cam_tx.clone(); + let end = bench_end; let coder = CamCoder::new(); - let tmpl = make_cam(station_id); + let tmpl = make_cam(station_id); thread::spawn(move || { while Instant::now() < end { let t0 = Instant::now(); @@ -149,11 +150,11 @@ fn main() { // ── VAM TX thread (100 ms interval → 10 Hz) ─────────────────────────────── { - let btp = btp_handle.clone(); - let cnt = vam_tx.clone(); - let end = bench_end; + let btp = btp_handle.clone(); + let cnt = vam_tx.clone(); + let end = bench_end; let coder = VamCoder::new(); - let tmpl = make_vam(station_id); + let tmpl = make_vam(station_id); thread::spawn(move || { while Instant::now() < end { let t0 = Instant::now(); @@ -179,15 +180,15 @@ fn main() { // separate thread counting 200 ms ticks. thread::spawn(move || { den.trigger_denm(DENRequest { - event_latitude: 41.552, - event_longitude: 2.134, - event_altitude_m: 50.0, - cause_code: CauseCodeChoice::accident2(AccidentSubCauseCode(0)), + event_latitude: 41.552, + event_longitude: 2.134, + event_altitude_m: 50.0, + cause_code: CauseCodeChoice::accident2(AccidentSubCauseCode(0)), information_quality: 3, - event_speed_raw: 16383, + event_speed_raw: 16383, event_heading_raw: 3601, - denm_interval_ms: 200, // 5 Hz - time_period_ms: total_ms, + denm_interval_ms: 200, // 5 Hz + time_period_ms: total_ms, relevance_radius_m: 1000, }); // Approximate TX count: 1 DENM every 200 ms. @@ -203,22 +204,28 @@ fn main() { { let (cam_ind_tx, cam_ind_rx) = mpsc::channel::(); btp_handle.register_port(2001, cam_ind_tx); - let cnt = cam_rx.clone(); - let err = rx_err.clone(); + let cnt = cam_rx.clone(); + let err = rx_err.clone(); let dec_sum = cam_dec_us_total.clone(); - let end = bench_end; + let end = bench_end; thread::spawn(move || { let coder = CamCoder::new(); loop { let now = Instant::now(); - if now >= end { break; } + if now >= end { + break; + } let timeout = (end - now).min(Duration::from_millis(500)); match cam_ind_rx.recv_timeout(timeout) { Ok(ind) => { let t0 = Instant::now(); match coder.decode(&ind.data) { - Ok(_) => { cnt.fetch_add(1, Ordering::Relaxed); } - Err(_) => { err.fetch_add(1, Ordering::Relaxed); } + Ok(_) => { + cnt.fetch_add(1, Ordering::Relaxed); + } + Err(_) => { + err.fetch_add(1, Ordering::Relaxed); + } } dec_sum.fetch_add(t0.elapsed().as_micros() as u64, Ordering::Relaxed); } @@ -233,22 +240,28 @@ fn main() { { let (vam_ind_tx, vam_ind_rx) = mpsc::channel::(); btp_handle.register_port(2018, vam_ind_tx); - let cnt = vam_rx.clone(); - let err = rx_err.clone(); + let cnt = vam_rx.clone(); + let err = rx_err.clone(); let dec_sum = vam_dec_us_total.clone(); - let end = bench_end; + let end = bench_end; thread::spawn(move || { let coder = VamCoder::new(); loop { let now = Instant::now(); - if now >= end { break; } + if now >= end { + break; + } let timeout = (end - now).min(Duration::from_millis(500)); match vam_ind_rx.recv_timeout(timeout) { Ok(ind) => { let t0 = Instant::now(); match coder.decode(&ind.data) { - Ok(_) => { cnt.fetch_add(1, Ordering::Relaxed); } - Err(_) => { err.fetch_add(1, Ordering::Relaxed); } + Ok(_) => { + cnt.fetch_add(1, Ordering::Relaxed); + } + Err(_) => { + err.fetch_add(1, Ordering::Relaxed); + } } dec_sum.fetch_add(t0.elapsed().as_micros() as u64, Ordering::Relaxed); } @@ -263,26 +276,34 @@ fn main() { { let cnt = denm_rx_cnt.clone(); let end = bench_end; - thread::spawn(move || { - loop { - let now = Instant::now(); - if now >= end { break; } - let timeout = (end - now).min(Duration::from_millis(500)); - match denm_rx.recv_timeout(timeout) { - Ok(_) => { cnt.fetch_add(1, Ordering::Relaxed); } - Err(mpsc::RecvTimeoutError::Timeout) => {} - Err(mpsc::RecvTimeoutError::Disconnected) => break, + thread::spawn(move || loop { + let now = Instant::now(); + if now >= end { + break; + } + let timeout = (end - now).min(Duration::from_millis(500)); + match denm_rx.recv_timeout(timeout) { + Ok(_) => { + cnt.fetch_add(1, Ordering::Relaxed); } + Err(mpsc::RecvTimeoutError::Timeout) => {} + Err(mpsc::RecvTimeoutError::Disconnected) => break, } }); } // ── 1 Hz stats printer (main thread) ───────────────────────────────────── - println!("{:>6} {:>8} {:>8} {:>8} {:>8} {:>8} {:>8} {:>8} {:>8} {:>8}", + println!( + "{:>6} {:>8} {:>8} {:>8} {:>8} {:>8} {:>8} {:>8} {:>8} {:>8}", "t(s)", - "cam_tx", "cam_rx", "cam_dec_µs", - "vam_tx", "vam_rx", "vam_dec_µs", - "den_tx", "den_rx", + "cam_tx", + "cam_rx", + "cam_dec_µs", + "vam_tx", + "vam_rx", + "vam_dec_µs", + "den_tx", + "den_rx", "rx_err", ); @@ -294,64 +315,90 @@ fn main() { loop { thread::sleep(Duration::from_secs(1)); let t = bench_start.elapsed().as_secs_f64(); - if t >= duration_s as f64 + 1.5 { break; } - - let ctx = cam_tx.load(Ordering::Relaxed); - let crx = cam_rx.load(Ordering::Relaxed); - let vtx = vam_tx.load(Ordering::Relaxed); - let vrx = vam_rx.load(Ordering::Relaxed); - let dtx = denm_tx.load(Ordering::Relaxed); - let drx = denm_rx_cnt.load(Ordering::Relaxed); + if t >= duration_s as f64 + 1.5 { + break; + } + + let ctx = cam_tx.load(Ordering::Relaxed); + let crx = cam_rx.load(Ordering::Relaxed); + let vtx = vam_tx.load(Ordering::Relaxed); + let vrx = vam_rx.load(Ordering::Relaxed); + let dtx = denm_tx.load(Ordering::Relaxed); + let drx = denm_rx_cnt.load(Ordering::Relaxed); let errs = rx_err.load(Ordering::Relaxed); - let cdu = cam_dec_us_total.load(Ordering::Relaxed); - let vdu = vam_dec_us_total.load(Ordering::Relaxed); + let cdu = cam_dec_us_total.load(Ordering::Relaxed); + let vdu = vam_dec_us_total.load(Ordering::Relaxed); let cam_dec_avg = if crx > 0 { cdu / crx } else { 0 }; let vam_dec_avg = if vrx > 0 { vdu / vrx } else { 0 }; - let cam_rx_rate = (crx - prev_cam_rx) as f64; - let vam_rx_rate = (vrx - prev_vam_rx) as f64; - let denm_rx_rate = (drx - prev_denm_rx) as f64; - prev_cam_rx = crx; - prev_vam_rx = vrx; + let cam_rx_rate = (crx - prev_cam_rx) as f64; + let vam_rx_rate = (vrx - prev_vam_rx) as f64; + let denm_rx_rate = (drx - prev_denm_rx) as f64; + prev_cam_rx = crx; + prev_vam_rx = vrx; prev_denm_rx = drx; - println!("{:>6.1} {:>8} {:>8.1} {:>10} {:>8} {:>8.1} {:>10} {:>8} {:>8.1} {:>8}", + println!( + "{:>6.1} {:>8} {:>8.1} {:>10} {:>8} {:>8.1} {:>10} {:>8} {:>8.1} {:>8}", t, - ctx, cam_rx_rate, cam_dec_avg, - vtx, vam_rx_rate, vam_dec_avg, - dtx, denm_rx_rate, + ctx, + cam_rx_rate, + cam_dec_avg, + vtx, + vam_rx_rate, + vam_dec_avg, + dtx, + denm_rx_rate, errs, ); - if t > duration_s as f64 { break; } + if t > duration_s as f64 { + break; + } } // ── Final summary ───────────────────────────────────────────────────────── let elapsed = bench_start.elapsed().as_secs_f64(); - let ctx = cam_tx.load(Ordering::Relaxed); - let crx = cam_rx.load(Ordering::Relaxed); - let vtx = vam_tx.load(Ordering::Relaxed); - let vrx = vam_rx.load(Ordering::Relaxed); - let dtx = denm_tx.load(Ordering::Relaxed); - let drx = denm_rx_cnt.load(Ordering::Relaxed); + let ctx = cam_tx.load(Ordering::Relaxed); + let crx = cam_rx.load(Ordering::Relaxed); + let vtx = vam_tx.load(Ordering::Relaxed); + let vrx = vam_rx.load(Ordering::Relaxed); + let dtx = denm_tx.load(Ordering::Relaxed); + let drx = denm_rx_cnt.load(Ordering::Relaxed); let errs = rx_err.load(Ordering::Relaxed); - let cdu = cam_dec_us_total.load(Ordering::Relaxed); - let vdu = vam_dec_us_total.load(Ordering::Relaxed); + let cdu = cam_dec_us_total.load(Ordering::Relaxed); + let vdu = vam_dec_us_total.load(Ordering::Relaxed); let cam_dec_avg = if crx > 0 { cdu / crx } else { 0 }; let vam_dec_avg = if vrx > 0 { vdu / vrx } else { 0 }; - let cam_ratio = if ctx > 0 { crx as f64 / ctx as f64 * 100.0 } else { 0.0 }; - let vam_ratio = if vtx > 0 { vrx as f64 / vtx as f64 * 100.0 } else { 0.0 }; - let denm_ratio = if dtx > 0 { drx as f64 / dtx as f64 * 100.0 } else { 0.0 }; + let cam_ratio = if ctx > 0 { + crx as f64 / ctx as f64 * 100.0 + } else { + 0.0 + }; + let vam_ratio = if vtx > 0 { + vrx as f64 / vtx as f64 * 100.0 + } else { + 0.0 + }; + let denm_ratio = if dtx > 0 { + drx as f64 / dtx as f64 * 100.0 + } else { + 0.0 + }; println!(); println!("=== Congestion Benchmark Results ({elapsed:.1} s) ==="); println!(); - println!(" CAM TX: {ctx:>8} RX: {crx:>8} ratio: {cam_ratio:>6.1}% avg_dec: {cam_dec_avg} µs"); - println!(" VAM TX: {vtx:>8} RX: {vrx:>8} ratio: {vam_ratio:>6.1}% avg_dec: {vam_dec_avg} µs"); + println!( + " CAM TX: {ctx:>8} RX: {crx:>8} ratio: {cam_ratio:>6.1}% avg_dec: {cam_dec_avg} µs" + ); + println!( + " VAM TX: {vtx:>8} RX: {vrx:>8} ratio: {vam_ratio:>6.1}% avg_dec: {vam_dec_avg} µs" + ); println!(" DENM TX: {dtx:>8} RX: {drx:>8} ratio: {denm_ratio:>6.1}%"); println!(" RX errors: {errs}"); } @@ -363,13 +410,22 @@ fn make_cam(station_id: u32) -> Cam { Heading::new(HeadingValue(900), HeadingConfidence(127)), Speed::new(SpeedValue(0), SpeedConfidence(127)), DriveDirection::unavailable, - VehicleLength::new(VehicleLengthValue(1023), VehicleLengthConfidenceIndication::unavailable), + VehicleLength::new( + VehicleLengthValue(1023), + VehicleLengthConfidenceIndication::unavailable, + ), VehicleWidth(62), AccelerationComponent::new(AccelerationValue(161), AccelerationConfidence(102)), Curvature::new(CurvatureValue(1023), CurvatureConfidence::unavailable), CurvatureCalculationMode::unavailable, YawRate::new(YawRateValue(32767), YawRateConfidence::unavailable), - None, None, None, None, None, None, None, + None, + None, + None, + None, + None, + None, + None, ); Cam::new( cam_header(station_id), @@ -382,13 +438,17 @@ fn make_cam(station_id: u32) -> Cam { Latitude(415_520_000), Longitude(21_340_000), PositionConfidenceEllipse::new( - SemiAxisLength(4095), SemiAxisLength(4095), Wgs84AngleValue(3601), + SemiAxisLength(4095), + SemiAxisLength(4095), + Wgs84AngleValue(3601), ), Altitude::new(AltitudeValue(12000), AltitudeConfidence::unavailable), ), ), HighFrequencyContainer::basicVehicleContainerHighFrequency(hf), - None, None, None, + None, + None, + None, ), ), ) @@ -401,15 +461,35 @@ fn cam_btp_request(data: Vec) -> BTPDataRequest { destination_port: 2001, destination_port_info: 0, gn_packet_transport_type: PacketTransportType { - header_type: HeaderType::Tsb, + header_type: HeaderType::Tsb, header_sub_type: HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop), }, gn_destination_address: GNAddress { - m: M::GnMulticast, st: ST::Unknown, mid: MID::new([0xFF; 6]), + m: M::GnMulticast, + st: ST::Unknown, + mid: MID::new([0xFF; 6]), }, communication_profile: CommunicationProfile::Unspecified, - gn_area: Area { latitude: 0, longitude: 0, a: 0, b: 0, angle: 0 }, - traffic_class: TrafficClass { scf: false, channel_offload: false, tc_id: 0 }, + gn_area: Area { + latitude: 0, + longitude: 0, + a: 0, + b: 0, + angle: 0, + }, + traffic_class: TrafficClass { + scf: false, + channel_offload: false, + tc_id: 0, + }, + security_profile: SecurityProfile::NoSecurity, + its_aid: 36, + security_permissions: vec![], + gn_max_hop_limit: 1, + gn_max_packet_lifetime: None, + gn_repetition_interval: None, + gn_max_repetition_time: None, + destination: None, length: data.len() as u16, data, } @@ -428,7 +508,17 @@ fn make_vam(station_id: u32) -> Vam { VamWgs84Angle::new(VamWgs84AngleVal(3601), VamWgs84AnglConf(127)), // heading unavailable VamSpeed::new(VamSpeedVal(0), VamSpeedConf(127)), // speed 0 VamLongAccel::new(VamLongAccelVal(161), VamAccelConf(102)), // accel unavailable - None, None, None, None, None, None, None, None, None, None, None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, ); Vam::new( vam_header(station_id), @@ -445,7 +535,10 @@ fn make_vam(station_id: u32) -> Vam { ), ), hf, - None, None, None, None, // lf, cluster_info, cluster_op, motion_pred + None, + None, + None, + None, // lf, cluster_info, cluster_op, motion_pred ), ), ) @@ -458,15 +551,35 @@ fn vam_btp_request(data: Vec) -> BTPDataRequest { destination_port: 2018, destination_port_info: 0, gn_packet_transport_type: PacketTransportType { - header_type: HeaderType::Tsb, + header_type: HeaderType::Tsb, header_sub_type: HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop), }, gn_destination_address: GNAddress { - m: M::GnMulticast, st: ST::Unknown, mid: MID::new([0xFF; 6]), + m: M::GnMulticast, + st: ST::Unknown, + mid: MID::new([0xFF; 6]), }, communication_profile: CommunicationProfile::Unspecified, - gn_area: Area { latitude: 0, longitude: 0, a: 0, b: 0, angle: 0 }, - traffic_class: TrafficClass { scf: false, channel_offload: false, tc_id: 0 }, + gn_area: Area { + latitude: 0, + longitude: 0, + a: 0, + b: 0, + angle: 0, + }, + traffic_class: TrafficClass { + scf: false, + channel_offload: false, + tc_id: 0, + }, + security_profile: SecurityProfile::NoSecurity, + its_aid: 16513, + security_permissions: vec![], + gn_max_hop_limit: 1, + gn_max_packet_lifetime: None, + gn_repetition_interval: None, + gn_max_repetition_time: None, + destination: None, length: data.len() as u16, data, } @@ -476,21 +589,43 @@ fn vam_btp_request(data: Vec) -> BTPDataRequest { fn random_mac() -> [u8; 6] { use std::time::{SystemTime, UNIX_EPOCH}; - let s = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_nanos(); - [0x02, (s >> 24) as u8, (s >> 16) as u8, (s >> 8) as u8, s as u8, 0xDD] + let s = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .subsec_nanos(); + [ + 0x02, + (s >> 24) as u8, + (s >> 16) as u8, + (s >> 8) as u8, + s as u8, + 0xDD, + ] } fn wire_routers( - gn: &RouterHandle, - btp: &BTPRouterHandle, - ll_rx: mpsc::Receiver>, + gn: &RouterHandle, + btp: &BTPRouterHandle, + ll_rx: mpsc::Receiver>, gn_btp_rx: mpsc::Receiver, btp_gn_rx: mpsc::Receiver, ) { let g1 = gn.clone(); - thread::spawn(move || { while let Ok(p) = ll_rx.recv() { g1.send_incoming_packet(p); } }); + thread::spawn(move || { + while let Ok(p) = ll_rx.recv() { + g1.send_incoming_packet(p); + } + }); let b1 = btp.clone(); - thread::spawn(move || { while let Ok(i) = gn_btp_rx.recv() { b1.send_gn_data_indication(i); } }); + thread::spawn(move || { + while let Ok(i) = gn_btp_rx.recv() { + b1.send_gn_data_indication(i); + } + }); let g2 = gn.clone(); - thread::spawn(move || { while let Ok(r) = btp_gn_rx.recv() { g2.send_gn_data_request(r); } }); + thread::spawn(move || { + while let Ok(r) = btp_gn_rx.recv() { + g2.send_gn_data_request(r); + } + }); } diff --git a/examples/cam_sender_receiver.rs b/examples/cam_sender_receiver.rs index 99471ff..d165cc0 100644 --- a/examples/cam_sender_receiver.rs +++ b/examples/cam_sender_receiver.rs @@ -83,11 +83,11 @@ fn main() { // We subscribe twice: once for the GN router's position vector, and once // for the CA Basic Service CAM generation. let mut loc_svc = LocationService::new(); - let gn_gps_rx = loc_svc.subscribe(); // → GN position vector updates - let ca_gps_rx = loc_svc.subscribe(); // → CAM transmission + let gn_gps_rx = loc_svc.subscribe(); // → GN position vector updates + let ca_gps_rx = loc_svc.subscribe(); // → CAM transmission // ── Spawn GeoNetworking router ──────────────────────────────────────────── - let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone()); + let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone(), None, None, None); // ── Spawn BTP router ───────────────────────────────────────────────────── let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); @@ -131,7 +131,13 @@ fn main() { thread::spawn(move || { while let Ok(fix) = gn_gps_rx.recv() { let mut epv = LongPositionVector::decode([0u8; 24]); - epv.update_from_gps(fix.latitude, fix.longitude, fix.speed_mps, fix.heading_deg, fix.pai); + epv.update_from_gps( + fix.latitude, + fix.longitude, + fix.speed_mps, + fix.heading_deg, + fix.pai, + ); gn_h3.update_position_vector(epv); } }); @@ -143,12 +149,14 @@ fn main() { let ldm = LdmFacility::new(415_520_000, 21_340_000, 5_000.0); // Register this node as a CAM data provider and consumer. - ldm.if_ldm_3.register_data_provider(RegisterDataProviderReq { - application_id: ITS_AID_CAM, - }); - ldm.if_ldm_4.register_data_consumer(RegisterDataConsumerReq { - application_id: ITS_AID_CAM, - }); + ldm.if_ldm_3 + .register_data_provider(RegisterDataProviderReq { + application_id: ITS_AID_CAM, + }); + ldm.if_ldm_4 + .register_data_consumer(RegisterDataConsumerReq { + application_id: ITS_AID_CAM, + }); // ── CA Basic Service ───────────────────────────────────────────────────── // @@ -175,31 +183,43 @@ fn main() { // This uses the ETSI IF.LDM.4 interface and confirms that received CAMs // are correctly stored and retrievable. let ldm_reader = ldm.clone(); - thread::spawn(move || { - loop { - thread::sleep(Duration::from_secs(1)); - let resp = ldm_reader.if_ldm_4.request_data_objects(RequestDataObjectsReq { - application_id: ITS_AID_CAM, + thread::spawn(move || loop { + thread::sleep(Duration::from_secs(1)); + let resp = ldm_reader + .if_ldm_4 + .request_data_objects(RequestDataObjectsReq { + application_id: ITS_AID_CAM, data_object_types: vec![ITS_AID_CAM], - filter: None, - order: None, - max_results: None, + filter: None, + order: None, + max_results: None, }); - if resp.data_objects.is_empty() { - println!("[LDM] No CAM records in store"); - } else { - println!("[LDM] {} CAM record(s):", resp.data_objects.len()); - for entry in &resp.data_objects { - if let ItsDataObject::Cam(cam) = &entry.data_object { - let lat = cam.cam.cam_parameters.basic_container - .reference_position.latitude.0 as f64 / 1e7; - let lon = cam.cam.cam_parameters.basic_container - .reference_position.longitude.0 as f64 / 1e7; - println!( - " [LDM CAM] record={:>5} station={:>10} lat={:.5} lon={:.5}", - entry.record_id, cam.header.station_id.0, lat, lon, - ); - } + if resp.data_objects.is_empty() { + println!("[LDM] No CAM records in store"); + } else { + println!("[LDM] {} CAM record(s):", resp.data_objects.len()); + for entry in &resp.data_objects { + if let ItsDataObject::Cam(cam) = &entry.data_object { + let lat = cam + .cam + .cam_parameters + .basic_container + .reference_position + .latitude + .0 as f64 + / 1e7; + let lon = cam + .cam + .cam_parameters + .basic_container + .reference_position + .longitude + .0 as f64 + / 1e7; + println!( + " [LDM CAM] record={:>5} station={:>10} lat={:.5} lon={:.5}", + entry.record_id, cam.header.station_id.0, lat, lon, + ); } } } @@ -216,12 +236,12 @@ fn main() { thread::sleep(Duration::from_millis(100)); // 41.552°N 2.134°E — Parc Tecnològic del Vallès loc_svc.publish(GpsFix { - latitude: 41.552, - longitude: 2.134, - altitude_m: 120.0, - speed_mps: 0.0, + latitude: 41.552, + longitude: 2.134, + altitude_m: 120.0, + speed_mps: 0.0, heading_deg: 0.0, - pai: true, + pai: true, }); } } diff --git a/examples/denm_sender_receiver.rs b/examples/denm_sender_receiver.rs index 07e7ace..1163d04 100644 --- a/examples/denm_sender_receiver.rs +++ b/examples/denm_sender_receiver.rs @@ -82,11 +82,11 @@ fn main() { // // A single LocationService publishes GPS fixes. We subscribe once for the // GN router's position vector updates. - let mut loc_svc = LocationService::new(); - let gn_gps_rx = loc_svc.subscribe(); + let mut loc_svc = LocationService::new(); + let gn_gps_rx = loc_svc.subscribe(); // ── Spawn GeoNetworking router ──────────────────────────────────────────── - let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone()); + let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone(), None, None, None); // ── Spawn BTP router ────────────────────────────────────────────────────── let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); @@ -127,7 +127,13 @@ fn main() { thread::spawn(move || { while let Ok(fix) = gn_gps_rx.recv() { let mut epv = LongPositionVector::decode([0u8; 24]); - epv.update_from_gps(fix.latitude, fix.longitude, fix.speed_mps, fix.heading_deg, fix.pai); + epv.update_from_gps( + fix.latitude, + fix.longitude, + fix.speed_mps, + fix.heading_deg, + fix.pai, + ); gn_h3.update_position_vector(epv); } }); @@ -163,12 +169,12 @@ fn main() { // Publish position fixes to keep the GN position vector up to date. let gps_fix = GpsFix { - latitude: 41.552, - longitude: 2.134, - altitude_m: 120.0, - speed_mps: 14.0, // ~50 km/h + latitude: 41.552, + longitude: 2.134, + altitude_m: 120.0, + speed_mps: 14.0, // ~50 km/h heading_deg: 90.0, - pai: true, + pai: true, }; loc_svc.publish(gps_fix.clone()); @@ -177,15 +183,15 @@ fn main() { // Send 1 DENM/s for 30 s, 1 000 m geo-broadcast circle. println!("Triggering road-hazard DENM (accident) for 30 s @ 1 Hz"); den_svc.trigger_denm(DENRequest { - event_latitude: gps_fix.latitude, - event_longitude: gps_fix.longitude, - event_altitude_m: gps_fix.altitude_m, - cause_code: CauseCodeChoice::accident2(AccidentSubCauseCode(0)), + event_latitude: gps_fix.latitude, + event_longitude: gps_fix.longitude, + event_altitude_m: gps_fix.altitude_m, + cause_code: CauseCodeChoice::accident2(AccidentSubCauseCode(0)), information_quality: 4, - event_speed_raw: (gps_fix.speed_mps * 100.0) as u16, - event_heading_raw: (gps_fix.heading_deg * 10.0) as u16, - denm_interval_ms: 1_000, - time_period_ms: 30_000, + event_speed_raw: (gps_fix.speed_mps * 100.0) as u16, + event_heading_raw: (gps_fix.heading_deg * 10.0) as u16, + denm_interval_ms: 1_000, + time_period_ms: 30_000, relevance_radius_m: 1_000, }); diff --git a/examples/generate_certificate_chain.rs b/examples/generate_certificate_chain.rs new file mode 100644 index 0000000..7e59865 --- /dev/null +++ b/examples/generate_certificate_chain.rs @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) + +//! Generate a certificate chain: Root CA → AA → AT1 + AT2. +//! +//! The generated certificates and private keys are written to the `certs/` +//! directory, which is created if it does not exist. +//! +//! # Running +//! ```text +//! cargo run --example generate_certificate_chain --target x86_64-unknown-linux-gnu +//! ``` + +use std::fs; +use std::path::Path; +use std::time::{SystemTime, UNIX_EPOCH}; + +use rasn::prelude::*; + +use rustflexstack::security::certificate::{encode_certificate, OwnCertificate}; +use rustflexstack::security::ecdsa_backend::EcdsaBackend; +use rustflexstack::security::security_asn::ieee1609_dot2::{ + CertificateId, EndEntityType, PsidGroupPermissions, SequenceOfAppExtensions, + SequenceOfCertIssueExtensions, SequenceOfCertRequestExtensions, + SequenceOfPsidGroupPermissions, SubjectPermissions, ToBeSignedCertificate, + VerificationKeyIndicator, +}; +use rustflexstack::security::security_asn::ieee1609_dot2_base_types::{ + CrlSeries, Duration, HashedId3, Hostname, Psid, PsidSsp, PsidSspRange, + SequenceOfPsidSsp, SequenceOfPsidSspRange, Time32, Uint16, Uint32, ValidityPeriod, +}; + +// ITS-AID values +const ITS_AID_CAM: u64 = 36; +const ITS_AID_DENM: u64 = 37; +const ITS_AID_VAM: u64 = 638; + +/// ITS epoch: 2004-01-01 00:00:00 UTC as a UNIX timestamp. +const ITS_EPOCH_UNIX: u64 = 1_072_915_200; + +/// Current time as seconds since ITS epoch. +fn its_time32_now() -> Time32 { + let unix_secs = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + let its_secs = unix_secs.saturating_sub(ITS_EPOCH_UNIX) as u32; + Time32(Uint32(its_secs)) +} + +/// Default EndEntityType: app only (bit 0 = true, bit 1 = false). +fn default_ee_type() -> EndEntityType { + let mut bits = FixedBitString::<8>::default(); + bits.set(0, true); + EndEntityType(bits) +} + +/// Helper: create a `SequenceOfPsidSsp` with the given PSIDs (no SSP). +fn app_permissions(psids: &[u64]) -> SequenceOfPsidSsp { + let items: Vec = psids + .iter() + .map(|&p| PsidSsp::new(Psid(Integer::from(p as i128)), None)) + .collect(); + SequenceOfPsidSsp(items.into()) +} + +/// Helper: build a `SequenceOfPsidGroupPermissions` with +/// `SubjectPermissions::explicit` for the given PSIDs. +fn cert_issue_permissions_explicit( + psids: &[u64], + min_chain_length: i128, +) -> SequenceOfPsidGroupPermissions { + let ranges: Vec = psids + .iter() + .map(|&p| PsidSspRange::new(Psid(Integer::from(p as i128)), None)) + .collect(); + let perm = PsidGroupPermissions::new( + SubjectPermissions::explicit(SequenceOfPsidSspRange(ranges.into())), + Integer::from(min_chain_length), + Integer::from(0i128), + default_ee_type(), + ); + SequenceOfPsidGroupPermissions(vec![perm].into()) +} + +/// Helper: build a `SequenceOfPsidGroupPermissions` with +/// `SubjectPermissions::all`. +fn cert_issue_permissions_all(min_chain_length: i128) -> SequenceOfPsidGroupPermissions { + let perm = PsidGroupPermissions::new( + SubjectPermissions::all(()), + Integer::from(min_chain_length), + Integer::from(0i128), + default_ee_type(), + ); + SequenceOfPsidGroupPermissions(vec![perm].into()) +} + +fn main() { + let cert_dir = Path::new("certs"); + fs::create_dir_all(cert_dir).expect("failed to create certs/ directory"); + + let mut backend = EcdsaBackend::new(); + + // ─── Root CA ───────────────────────────────────────────────────────── + let root_tbs = ToBeSignedCertificate::new( + CertificateId::name(Hostname("RootCA".to_string().into())), + HashedId3(FixedOctetString::from([0u8; 3])), + CrlSeries(Uint16(0)), + ValidityPeriod::new(its_time32_now(), Duration::years(Uint16(20))), + None, // region + None, // assuranceLevel + Some(app_permissions(&[ITS_AID_CAM, ITS_AID_DENM, ITS_AID_VAM])), // appPermissions + Some(cert_issue_permissions_all(2)), // certIssuePermissions + None, // certRequestPermissions + None, // canRequestRollover + None, // encryptionKey + // placeholder — overwritten by initialize_self_signed + VerificationKeyIndicator::verificationKey( + rustflexstack::security::security_asn::ieee1609_dot2_base_types + ::PublicVerificationKey::ecdsaNistP256( + rustflexstack::security::security_asn::ieee1609_dot2_base_types + ::EccP256CurvePoint::x_only(vec![0u8; 32].into()), + ), + ), + None, + SequenceOfAppExtensions(Default::default()), + SequenceOfCertIssueExtensions(Default::default()), + SequenceOfCertRequestExtensions(Default::default()), + ); + let root_ca = OwnCertificate::initialize_self_signed(&mut backend, root_tbs); + println!( + "Root CA HashedId8: {:02x?}", + root_ca.as_hashedid8() + ); + + // ─── Authorization Authority ───────────────────────────────────────── + let aa_tbs = ToBeSignedCertificate::new( + CertificateId::name(Hostname("AA".to_string().into())), + HashedId3(FixedOctetString::from([0u8; 3])), + CrlSeries(Uint16(0)), + ValidityPeriod::new(its_time32_now(), Duration::years(Uint16(10))), + None, + None, + Some(app_permissions(&[ITS_AID_CAM, ITS_AID_DENM, ITS_AID_VAM])), + Some(cert_issue_permissions_explicit( + &[ITS_AID_CAM, ITS_AID_DENM, ITS_AID_VAM], + 1, + )), + None, + None, + None, + VerificationKeyIndicator::verificationKey( + rustflexstack::security::security_asn::ieee1609_dot2_base_types + ::PublicVerificationKey::ecdsaNistP256( + rustflexstack::security::security_asn::ieee1609_dot2_base_types + ::EccP256CurvePoint::x_only(vec![0u8; 32].into()), + ), + ), + None, + SequenceOfAppExtensions(Default::default()), + SequenceOfCertIssueExtensions(Default::default()), + SequenceOfCertRequestExtensions(Default::default()), + ); + let aa = OwnCertificate::initialize_issued(&mut backend, aa_tbs, &root_ca); + println!( + "AA HashedId8: {:02x?}", + aa.as_hashedid8() + ); + + // ─── Authorization Tickets ─────────────────────────────────────────── + let at_names = ["AT1", "AT2"]; + let mut at_certs = Vec::new(); + for name in &at_names { + let at_tbs = ToBeSignedCertificate::new( + CertificateId::none(()), + HashedId3(FixedOctetString::from([0u8; 3])), + CrlSeries(Uint16(0)), + ValidityPeriod::new(its_time32_now(), Duration::years(Uint16(5))), + None, + None, + Some(app_permissions(&[ITS_AID_CAM, ITS_AID_DENM, ITS_AID_VAM])), + None, // no certIssuePermissions for AT + None, + None, + None, + VerificationKeyIndicator::verificationKey( + rustflexstack::security::security_asn::ieee1609_dot2_base_types + ::PublicVerificationKey::ecdsaNistP256( + rustflexstack::security::security_asn::ieee1609_dot2_base_types + ::EccP256CurvePoint::x_only(vec![0u8; 32].into()), + ), + ), + None, + SequenceOfAppExtensions(Default::default()), + SequenceOfCertIssueExtensions(Default::default()), + SequenceOfCertRequestExtensions(Default::default()), + ); + let at = OwnCertificate::initialize_issued(&mut backend, at_tbs, &aa); + println!( + "{:<8} HashedId8: {:02x?}", + name, + at.as_hashedid8() + ); + at_certs.push((name, at)); + } + + // ─── Write files ───────────────────────────────────────────────────── + let write_cert = |filename: &str, own: &OwnCertificate| { + let cert_bytes = encode_certificate(&own.cert.inner); + let key_bytes = backend.export_signing_key(own.key_id); + fs::write(cert_dir.join(format!("{}.cert", filename)), &cert_bytes) + .unwrap_or_else(|e| panic!("write {}.cert: {}", filename, e)); + fs::write(cert_dir.join(format!("{}.key", filename)), &key_bytes) + .unwrap_or_else(|e| panic!("write {}.key: {}", filename, e)); + println!( + " Wrote {}.cert ({} bytes) + {}.key ({} bytes)", + filename, + cert_bytes.len(), + filename, + key_bytes.len() + ); + }; + + write_cert("root_ca", &root_ca); + write_cert("aa", &aa); + for (name, at) in &at_certs { + write_cert(&name.to_lowercase(), at); + } + + // ─── Verify the chain ──────────────────────────────────────────────── + println!("\n=== Verification ==="); + println!("Root CA self-signed: {}", root_ca.verify(&backend)); + println!("AA issued by Root: {}", aa.verify(&backend)); + for (name, at) in &at_certs { + println!("{} issued by AA: {}", name, at.verify(&backend)); + } + println!("\nDone. Certificate files in certs/"); +} diff --git a/examples/secured_cam_sender_receiver.rs b/examples/secured_cam_sender_receiver.rs new file mode 100644 index 0000000..a7ba15c --- /dev/null +++ b/examples/secured_cam_sender_receiver.rs @@ -0,0 +1,448 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) + +//! Secured CAM Sender and Receiver Example +//! +//! This example mirrors `FlexStack/examples/secured_cam_sender_and_receiver.py` +//! and demonstrates sending and receiving ETSI TS 103 097-secured CAMs using +//! the `rustflexstack` security layer. +//! +//! Two instances are expected to run simultaneously, one with `--at 1` and the +//! other with `--at 2`. Each loads its own Authorization Ticket from the +//! `certs/` directory (generated by `generate_certificate_chain`), signs +//! outgoing CAMs, and verifies incoming ones. +//! +//! Security is applied as a middleware layer between the GN router and the +//! link layer — outgoing packets are signed and incoming secured packets are +//! verified before being forwarded to the GN router. +//! +//! # Prerequisites +//! ```text +//! cargo run --example generate_certificate_chain --target x86_64-unknown-linux-gnu +//! ``` +//! +//! # Running +//! ```text +//! # Terminal 1: +//! sudo cargo run --example secured_cam_sender_receiver --target x86_64-unknown-linux-gnu -- --at 1 +//! # Terminal 2: +//! sudo cargo run --example secured_cam_sender_receiver --target x86_64-unknown-linux-gnu -- --at 2 +//! ``` +//! +//! If no interface is given the example falls back to `lo` (loopback), which +//! is sufficient to test send→receive on a single machine. + +use std::env; +use std::fs; +use std::path::Path; +use std::sync::{mpsc, Arc, Mutex}; +use std::thread; +use std::time::Duration; + +use rustflexstack::btp::router::Router as BTPRouter; +use rustflexstack::facilities::ca_basic_service::{CooperativeAwarenessBasicService, VehicleData}; +use rustflexstack::facilities::local_dynamic_map::{ + ldm_constants::ITS_AID_CAM, + ldm_storage::ItsDataObject, + ldm_types::{RegisterDataConsumerReq, RegisterDataProviderReq, RequestDataObjectsReq}, + LdmFacility, +}; +use rustflexstack::facilities::location_service::{GpsFix, LocationService}; +use rustflexstack::geonet::basic_header::{BasicHeader, BasicNH}; +use rustflexstack::geonet::gn_address::{GNAddress, M, MID, ST}; +use rustflexstack::geonet::mib::Mib; +use rustflexstack::geonet::position_vector::LongPositionVector; +use rustflexstack::geonet::router::Router as GNRouter; +use rustflexstack::link_layer::raw_link_layer::RawLinkLayer; + +use rustflexstack::security::certificate::{Certificate, OwnCertificate}; +use rustflexstack::security::certificate_library::CertificateLibrary; +use rustflexstack::security::ecdsa_backend::EcdsaBackend; +use rustflexstack::security::sign_service::SignService; +use rustflexstack::security::sn_sap::{ReportVerify, SNSignRequest, SNVerifyRequest}; +use rustflexstack::security::verify_service::{verify_message, VerifyEvent}; + +const ITS_AID_CAM_VAL: u64 = 36; + +/// Build the security stack: load certificates, create backend, sign & verify +/// services. Returns a `SignService` ready for use. +fn build_security_stack(at_index: usize) -> SignService { + let cert_dir = Path::new("certs"); + + // ── Load certificates ──────────────────────────────────────────────── + let root_bytes = fs::read(cert_dir.join("root_ca.cert")) + .expect("root_ca.cert not found — run generate_certificate_chain first"); + let aa_bytes = fs::read(cert_dir.join("aa.cert")) + .expect("aa.cert not found — run generate_certificate_chain first"); + + let root_ca = Certificate::from_bytes(&root_bytes, None); + let aa = Certificate::from_bytes(&aa_bytes, Some(root_ca.clone())); + + // Load both ATs — one is "ours", the other is a known peer + let at1_cert_bytes = fs::read(cert_dir.join("at1.cert")).expect("at1.cert not found"); + let at2_cert_bytes = fs::read(cert_dir.join("at2.cert")).expect("at2.cert not found"); + + let at1 = Certificate::from_bytes(&at1_cert_bytes, Some(aa.clone())); + let at2 = Certificate::from_bytes(&at2_cert_bytes, Some(aa.clone())); + + // Load our private key + let own_key_file = if at_index == 1 { "at1.key" } else { "at2.key" }; + let key_bytes = fs::read(cert_dir.join(own_key_file)) + .unwrap_or_else(|_| panic!("{} not found", own_key_file)); + + // ── Create backend and import key ──────────────────────────────────── + let mut backend = EcdsaBackend::new(); + let key_id = backend.import_signing_key(&key_bytes); + + let own_cert = if at_index == 1 { at1.clone() } else { at2.clone() }; + let peer_cert = if at_index == 1 { at2.clone() } else { at1.clone() }; + + // ── Build certificate library ──────────────────────────────────────── + let cert_library = CertificateLibrary::new( + &backend, + vec![root_ca], + vec![aa], + vec![own_cert.clone(), peer_cert], + ); + + // ── Create sign service and add own certificate ────────────────────── + let mut sign_service = SignService::new(backend, cert_library); + let own = OwnCertificate::new(own_cert, key_id); + sign_service.add_own_certificate(own); + + sign_service +} + +fn main() { + // ── Parse arguments ────────────────────────────────────────────────── + let args: Vec = env::args().collect(); + let mut at_index: usize = 1; + let mut iface = "lo".to_string(); + + let mut i = 1; + while i < args.len() { + match args[i].as_str() { + "--at" => { + i += 1; + at_index = args[i].parse::().expect("--at must be 1 or 2"); + assert!(at_index == 1 || at_index == 2, "--at must be 1 or 2"); + } + "--iface" | "-i" => { + i += 1; + iface = args[i].clone(); + } + other => { + // Positional argument: interface name + iface = other.to_string(); + } + } + i += 1; + } + + println!("=== Secured CAM Sender/Receiver ==="); + println!("AT index: {}", at_index); + println!("Interface: {}", iface); + + // ── Build security stack ───────────────────────────────────────────── + let sign_service = build_security_stack(at_index); + println!( + "Security stack loaded. Own AT HashedId8: {:02x?}", + sign_service + .cert_library + .own_certificates + .keys() + .next() + .unwrap() + ); + + // Wrap in Arc so it can be shared between threads + let sign_service = Arc::new(Mutex::new(sign_service)); + + // ── Generate a random locally-administered MAC ─────────────────────── + let mac = { + use std::time::{SystemTime, UNIX_EPOCH}; + let seed = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .subsec_nanos() + ^ (at_index as u32 * 0x1234_5678); // different seed per AT + [ + 0x02u8, + (seed >> 24) as u8, + (seed >> 16) as u8, + (seed >> 8) as u8, + seed as u8, + at_index as u8, + ] + }; + println!( + "MAC: {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] + ); + + // ── MIB ────────────────────────────────────────────────────────────── + let mut mib = Mib::new(); + mib.itsGnLocalGnAddr = GNAddress::new(M::GnMulticast, ST::PassengerCar, MID::new(mac)); + mib.itsGnBeaconServiceRetransmitTimer = 0; + + // ── Location Service ───────────────────────────────────────────────── + let mut loc_svc = LocationService::new(); + let gn_gps_rx = loc_svc.subscribe(); + let ca_gps_rx = loc_svc.subscribe(); + + // ── Spawn GN router and BTP router ─────────────────────────────────── + let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone(), None, None, None); + let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); + + // ── Wire RawLinkLayer ──────────────────────────────────────────────── + let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::>(); + + // ── Security middleware: TX path ───────────────────────────────────── + // + // Intercept packets from GN router → link layer. + // Sign the payload (CommonHeader + extended header + data) and wrap + // in a secured GN packet with BasicNH::SecuredPacket. + let (secured_ll_tx, secured_ll_rx) = mpsc::channel::>(); + let sign_svc_tx = Arc::clone(&sign_service); + thread::spawn(move || { + while let Ok(packet) = gn_to_ll_rx.recv() { + if packet.len() < 4 { + let _ = secured_ll_tx.send(packet); + continue; + } + // Parse BasicHeader to check if it's a CommonHeader packet + let bh_bytes: [u8; 4] = packet[0..4].try_into().unwrap(); + let bh = BasicHeader::decode(bh_bytes); + + match bh.nh { + BasicNH::CommonHeader if packet.len() > 4 => { + // The payload to sign = everything after BasicHeader + let inner_payload = &packet[4..]; + + let request = SNSignRequest { + tbs_message: inner_payload.to_vec(), + its_aid: ITS_AID_CAM_VAL, + permissions: vec![], + generation_location: None, + }; + + let sec_message = { + let mut svc = sign_svc_tx.lock().unwrap(); + svc.sign_request(&request).sec_message + }; + + // Build new packet: BasicHeader(nh=SecuredPacket) + sec_message + let mut new_bh = bh; + new_bh.nh = BasicNH::SecuredPacket; + let secured_packet: Vec = new_bh + .encode() + .iter() + .copied() + .chain(sec_message.iter().copied()) + .collect(); + let _ = secured_ll_tx.send(secured_packet); + } + _ => { + // Pass through (e.g. beacons) + let _ = secured_ll_tx.send(packet); + } + } + } + }); + + // The link layer reads from secured_ll_rx (post-signing) + let raw_ll = RawLinkLayer::new(ll_to_gn_tx, secured_ll_rx, &iface, mac); + raw_ll.start(); + + // ── Security middleware: RX path ───────────────────────────────────── + // + // Intercept packets from link layer → GN router. + // If BasicNH::SecuredPacket, verify and extract, then forward + // with BasicNH::CommonHeader. + let gn_h_rx = gn_handle.clone(); + let sign_svc_rx = Arc::clone(&sign_service); + thread::spawn(move || { + while let Ok(packet) = ll_to_gn_rx.recv() { + if packet.len() < 4 { + gn_h_rx.send_incoming_packet(packet); + continue; + } + let bh_bytes: [u8; 4] = packet[0..4].try_into().unwrap(); + let bh = BasicHeader::decode(bh_bytes); + + match bh.nh { + BasicNH::SecuredPacket if packet.len() > 4 => { + let sec_message = &packet[4..]; + let request = SNVerifyRequest { + message: sec_message.to_vec(), + }; + + let (confirm, events) = { + let mut svc = sign_svc_rx.lock().unwrap(); + let svc = &mut *svc; + let result = verify_message( + &request, + &svc.backend, + &mut svc.cert_library, + ); + // Process VerifyEvents for P2PCD + for event in &result.1 { + match event { + VerifyEvent::UnknownAt(h8) => { + svc.notify_unknown_at(h8); + } + VerifyEvent::InlineP2pcdRequest(h3s) => { + svc.notify_inline_p2pcd_request(h3s); + } + VerifyEvent::ReceivedCaCertificate(cert) => { + svc.notify_received_ca_certificate(cert.clone()); + } + } + } + result + }; + + if confirm.report == ReportVerify::Success { + println!( + "[SEC RX] Verified OK — ITS-AID={}, cert={:02x?}", + confirm.its_aid, + &confirm.certificate_id[..], + ); + // Rebuild the packet: BasicHeader(nh=CommonHeader) + plain_message + let mut new_bh = bh; + new_bh.nh = BasicNH::CommonHeader; + let plain_packet: Vec = new_bh + .encode() + .iter() + .copied() + .chain(confirm.plain_message.iter().copied()) + .collect(); + gn_h_rx.send_incoming_packet(plain_packet); + } else { + eprintln!( + "[SEC RX] Verification failed: {:?}", + confirm.report + ); + } + } + _ => { + // Non-secured packet — forward directly + gn_h_rx.send_incoming_packet(packet); + } + } + } + }); + + // ── GN → BTP ───────────────────────────────────────────────────────── + let btp_h1 = btp_handle.clone(); + thread::spawn(move || { + while let Ok(ind) = gn_to_btp_rx.recv() { + btp_h1.send_gn_data_indication(ind); + } + }); + + // ── BTP → GN ───────────────────────────────────────────────────────── + let gn_h2 = gn_handle.clone(); + thread::spawn(move || { + while let Ok(req) = btp_to_gn_rx.recv() { + gn_h2.send_gn_data_request(req); + } + }); + + // ── LocationService → GN position vector ───────────────────────────── + let gn_h3 = gn_handle.clone(); + thread::spawn(move || { + while let Ok(fix) = gn_gps_rx.recv() { + let mut epv = LongPositionVector::decode([0u8; 24]); + epv.update_from_gps( + fix.latitude, + fix.longitude, + fix.speed_mps, + fix.heading_deg, + fix.pai, + ); + gn_h3.update_position_vector(epv); + } + }); + + // ── Local Dynamic Map ──────────────────────────────────────────────── + let ldm = LdmFacility::new(415_520_000, 21_340_000, 5_000.0); + ldm.if_ldm_3 + .register_data_provider(RegisterDataProviderReq { + application_id: ITS_AID_CAM, + }); + ldm.if_ldm_4 + .register_data_consumer(RegisterDataConsumerReq { + application_id: ITS_AID_CAM, + }); + + // ── CA Basic Service ───────────────────────────────────────────────── + let station_id = u32::from_be_bytes([mac[2], mac[3], mac[4], mac[5]]); + let vehicle_data = VehicleData { + station_id, + ..VehicleData::default() + }; + let (ca_svc, _cam_rx) = + CooperativeAwarenessBasicService::new(btp_handle.clone(), vehicle_data, Some(ldm.clone())); + ca_svc.start(ca_gps_rx); + + // ── LDM query printer ──────────────────────────────────────────────── + let ldm_reader = ldm.clone(); + thread::spawn(move || loop { + thread::sleep(Duration::from_secs(1)); + let resp = ldm_reader + .if_ldm_4 + .request_data_objects(RequestDataObjectsReq { + application_id: ITS_AID_CAM, + data_object_types: vec![ITS_AID_CAM], + filter: None, + order: None, + max_results: None, + }); + if resp.data_objects.is_empty() { + println!("[LDM] No CAM records in store"); + } else { + println!("[LDM] {} CAM record(s):", resp.data_objects.len()); + for entry in &resp.data_objects { + if let ItsDataObject::Cam(cam) = &entry.data_object { + let lat = cam + .cam + .cam_parameters + .basic_container + .reference_position + .latitude + .0 as f64 + / 1e7; + let lon = cam + .cam + .cam_parameters + .basic_container + .reference_position + .longitude + .0 as f64 + / 1e7; + println!( + " [LDM CAM] record={:>5} station={:>10} lat={:.5} lon={:.5}", + entry.record_id, cam.header.station_id.0, lat, lon, + ); + } + } + } + }); + + // ── GPS publisher ──────────────────────────────────────────────────── + thread::sleep(Duration::from_millis(100)); + println!("Publishing GPS fixes @ 10 Hz — Ctrl+C to stop\n"); + + loop { + thread::sleep(Duration::from_millis(100)); + loc_svc.publish(GpsFix { + latitude: 41.552, + longitude: 2.134, + altitude_m: 120.0, + speed_mps: 0.0, + heading_deg: 0.0, + pai: true, + }); + } +} diff --git a/examples/vam_sender_receiver.rs b/examples/vam_sender_receiver.rs index b3f8a7d..631176b 100644 --- a/examples/vam_sender_receiver.rs +++ b/examples/vam_sender_receiver.rs @@ -79,11 +79,11 @@ fn main() { // We subscribe twice: once for the GN router's position vector, and once // for the VRU Awareness Service VAM generation. let mut loc_svc = LocationService::new(); - let gn_gps_rx = loc_svc.subscribe(); // → GN position vector updates + let gn_gps_rx = loc_svc.subscribe(); // → GN position vector updates let vru_gps_rx = loc_svc.subscribe(); // → VAM transmission // ── Spawn GeoNetworking router ──────────────────────────────────────────── - let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone()); + let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone(), None, None, None); // ── Spawn BTP router ────────────────────────────────────────────────────── let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); @@ -127,7 +127,13 @@ fn main() { thread::spawn(move || { while let Ok(fix) = gn_gps_rx.recv() { let mut epv = LongPositionVector::decode([0u8; 24]); - epv.update_from_gps(fix.latitude, fix.longitude, fix.speed_mps, fix.heading_deg, fix.pai); + epv.update_from_gps( + fix.latitude, + fix.longitude, + fix.speed_mps, + fix.heading_deg, + fix.pai, + ); gn_h3.update_position_vector(epv); } }); @@ -152,8 +158,22 @@ fn main() { // ── Decoded VAM printer ─────────────────────────────────────────────────── thread::spawn(move || { while let Ok(vam) = vam_rx.recv() { - let lat = vam.vam.vam_parameters.basic_container.reference_position.latitude.0 as f64 / 1e7; - let lon = vam.vam.vam_parameters.basic_container.reference_position.longitude.0 as f64 / 1e7; + let lat = vam + .vam + .vam_parameters + .basic_container + .reference_position + .latitude + .0 as f64 + / 1e7; + let lon = vam + .vam + .vam_parameters + .basic_container + .reference_position + .longitude + .0 as f64 + / 1e7; println!( "[VAM RX] station={:>10} lat={:.5} lon={:.5}", vam.header.0.station_id.0, lat, lon, @@ -172,12 +192,12 @@ fn main() { thread::sleep(Duration::from_millis(100)); // 41.552°N 2.134°E — Parc Tecnològic del Vallès loc_svc.publish(GpsFix { - latitude: 41.552, - longitude: 2.134, - altitude_m: 120.0, - speed_mps: 1.5, + latitude: 41.552, + longitude: 2.134, + altitude_m: 120.0, + speed_mps: 1.5, heading_deg: 90.0, - pai: true, + pai: true, }); } } diff --git a/src/btp/btp_header.rs b/src/btp/btp_header.rs index d66876c..b59d3d1 100644 --- a/src/btp/btp_header.rs +++ b/src/btp/btp_header.rs @@ -30,7 +30,10 @@ pub struct BTPAHeader { impl BTPAHeader { pub fn new() -> Self { - BTPAHeader { destination_port: 0, source_port: 0 } + BTPAHeader { + destination_port: 0, + source_port: 0, + } } /// Build a BTP-A header from a [`BTPDataRequest`]. @@ -87,7 +90,10 @@ pub struct BTPBHeader { impl BTPBHeader { pub fn new() -> Self { - BTPBHeader { destination_port: 0, destination_port_info: 0 } + BTPBHeader { + destination_port: 0, + destination_port_info: 0, + } } /// Build a BTP-B header from a [`BTPDataRequest`]. diff --git a/src/btp/mod.rs b/src/btp/mod.rs index 43f3c0e..e3a0ba6 100644 --- a/src/btp/mod.rs +++ b/src/btp/mod.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) -pub mod service_access_point; pub mod btp_header; -pub mod router; \ No newline at end of file +pub mod router; +pub mod service_access_point; diff --git a/src/btp/router.rs b/src/btp/router.rs index 2afbc00..da7ff78 100644 --- a/src/btp/router.rs +++ b/src/btp/router.rs @@ -16,11 +16,13 @@ //! - **BTP-B** (`CommonNH::BtpB`): carries a *destination port info* field //! instead of a source port. Used for connectionless broadcast (CAM, DENM). -use std::sync::mpsc::{self, Sender, Receiver}; -use std::thread; use std::collections::HashMap; +use std::convert::TryInto; +use std::sync::mpsc::{self, Receiver, Sender}; +use std::thread; + use super::btp_header::{BTPAHeader, BTPBHeader}; -use super::service_access_point::{BTPDataRequest, BTPDataIndication}; +use super::service_access_point::{BTPDataIndication, BTPDataRequest}; use crate::geonet::mib::Mib; use crate::geonet::service_access_point::CommonNH; use crate::geonet::service_access_point::{GNDataIndication, GNDataRequest}; @@ -29,17 +31,11 @@ use crate::geonet::service_access_point::{GNDataIndication, GNDataRequest}; // Message types // ------------------------------------------------------------------ -/// Messages that the BTP router processes from its input queue. pub enum BTPRouterInput { - /// A Facilities-layer send request. BTPDataRequest(BTPDataRequest), - /// An indication from the GN router (received packet). GNDataIndication(GNDataIndication), - /// Register a port sink. Packets arriving on `port` will be forwarded to `callback_tx`. RegisterPort(u16, Sender), - /// Remove the sink registered for `port`. UnregisterPort(u16), - /// Shut down the router thread. Shutdown, } @@ -47,43 +43,38 @@ pub enum BTPRouterInput { // Router and handle // ------------------------------------------------------------------ -/// Internal BTP router state. Use [`BTPRouterHandle`] for all interaction. pub struct Router { mib: Mib, - /// Port → Facilities-layer channel map. port_callbacks: HashMap>, - /// Channel for sending GN data requests to the GeoNetworking layer. gn_request_tx: Sender, } -/// A clonable handle to the running BTP router. #[derive(Clone)] pub struct BTPRouterHandle { input_tx: Sender, } impl BTPRouterHandle { - /// Submit a Facilities-layer BTP data request (transmit path). pub fn send_btp_data_request(&self, request: BTPDataRequest) { let _ = self.input_tx.send(BTPRouterInput::BTPDataRequest(request)); } - /// Inject a GN data indication into the BTP router (receive path). pub fn send_gn_data_indication(&self, indication: GNDataIndication) { - let _ = self.input_tx.send(BTPRouterInput::GNDataIndication(indication)); + let _ = self + .input_tx + .send(BTPRouterInput::GNDataIndication(indication)); } - /// Register `callback_tx` to receive indications for `port`. pub fn register_port(&self, port: u16, callback_tx: Sender) { - let _ = self.input_tx.send(BTPRouterInput::RegisterPort(port, callback_tx)); + let _ = self + .input_tx + .send(BTPRouterInput::RegisterPort(port, callback_tx)); } - /// Unregister the sink for `port`. pub fn unregister_port(&self, port: u16) { let _ = self.input_tx.send(BTPRouterInput::UnregisterPort(port)); } - /// Shut down the router thread. pub fn shutdown(self) { let _ = self.input_tx.send(BTPRouterInput::Shutdown); } @@ -94,7 +85,6 @@ impl BTPRouterHandle { // ------------------------------------------------------------------ impl Router { - /// Create a new BTP router. Prefer [`Router::spawn`]. pub fn new(mib: Mib, gn_request_tx: Sender) -> Self { Router { mib, @@ -103,11 +93,6 @@ impl Router { } } - /// Spawn the BTP router actor thread. - /// - /// Returns: - /// - A [`BTPRouterHandle`] for sending messages. - /// - A [`Receiver`] for the GN router to consume. pub fn spawn(mib: Mib) -> (BTPRouterHandle, Receiver) { let (input_tx, input_rx) = mpsc::channel::(); let (gn_request_tx, gn_request_rx) = mpsc::channel::(); @@ -145,7 +130,6 @@ impl Router { // Transmit path // ------------------------------------------------------------------ - /// Encode a BTP-A or BTP-B header and forward the packet to the GN layer. fn btp_data_request(&mut self, request: BTPDataRequest) { let header_bytes: [u8; 4] = match request.btp_type { CommonNH::BtpA => BTPAHeader::initialize_with_request(&request).encode(), @@ -166,9 +150,15 @@ impl Router { upper_protocol_entity: request.btp_type.clone(), packet_transport_type: request.gn_packet_transport_type.clone(), communication_profile: request.communication_profile.clone(), - traffic_class: request.traffic_class.clone(), + traffic_class: request.traffic_class, + security_profile: request.security_profile, + its_aid: request.its_aid, + security_permissions: request.security_permissions.clone(), + max_hop_limit: request.gn_max_hop_limit, + max_packet_lifetime: request.gn_max_packet_lifetime, + destination: request.destination, length: payload.len() as u16, - area: request.gn_area.clone(), + area: request.gn_area, data: payload, }; let _ = self.gn_request_tx.send(gn_request); @@ -178,7 +168,6 @@ impl Router { // Receive path // ------------------------------------------------------------------ - /// Strip a BTP-B header and dispatch the payload to the registered port sink. fn btpb_data_indication(&mut self, gn_ind: GNDataIndication) { if gn_ind.data.len() < 4 { eprintln!("[BTP] BTP-B payload too short"); @@ -187,30 +176,26 @@ impl Router { let btp_bytes: [u8; 4] = gn_ind.data[0..4].try_into().unwrap(); let header = BTPBHeader::decode(btp_bytes); - let indication = BTPDataIndication { - source_port: 0, // BTP-B carries no source port - destination_port: header.destination_port, - destination_port_info: header.destination_port_info, - gn_packet_transport_type: gn_ind.packet_transport_type, - // Use the actual GN destination from the indication - gn_destination_address: self.mib.itsGnLocalGnAddr, - gn_source_position_vector: gn_ind.source_position_vector, - gn_traffic_class: gn_ind.traffic_class, - length: gn_ind.data.len().saturating_sub(4) as u16, - data: gn_ind.data[4..].to_vec(), - }; + let indication = + BTPDataIndication::initialize_with_gn_data_indication(&gn_ind) + .set_destination_port_and_info( + header.destination_port, + header.destination_port_info, + ); - match self.port_callbacks.get(&header.destination_port) { + match self.port_callbacks.get(&indication.destination_port) { Some(tx) => { let _ = tx.send(indication); } None => { - eprintln!("[BTP] No sink registered for port {}", header.destination_port); + eprintln!( + "[BTP] No sink registered for port {}", + indication.destination_port + ); } } } - /// Strip a BTP-A header and dispatch the payload to the registered port sink. fn btpa_data_indication(&mut self, gn_ind: GNDataIndication) { if gn_ind.data.len() < 4 { eprintln!("[BTP] BTP-A payload too short"); @@ -219,24 +204,20 @@ impl Router { let btp_bytes: [u8; 4] = gn_ind.data[0..4].try_into().unwrap(); let header = BTPAHeader::decode(btp_bytes); - let indication = BTPDataIndication { - source_port: header.source_port(), - destination_port: header.destination_port(), - destination_port_info: 0, // BTP-A has no port-info field - gn_packet_transport_type: gn_ind.packet_transport_type, - gn_destination_address: self.mib.itsGnLocalGnAddr, - gn_source_position_vector: gn_ind.source_position_vector, - gn_traffic_class: gn_ind.traffic_class, - length: gn_ind.data.len().saturating_sub(4) as u16, - data: gn_ind.data[4..].to_vec(), - }; + let mut indication = + BTPDataIndication::initialize_with_gn_data_indication(&gn_ind) + .set_destination_port_and_info(header.destination_port(), 0); + indication.source_port = header.source_port(); - match self.port_callbacks.get(&header.destination_port()) { + match self.port_callbacks.get(&indication.destination_port) { Some(tx) => { let _ = tx.send(indication); } None => { - eprintln!("[BTP] No sink registered for port {}", header.destination_port()); + eprintln!( + "[BTP] No sink registered for port {}", + indication.destination_port + ); } } } @@ -249,7 +230,3 @@ impl Router { } } } - -use std::convert::TryInto; - - diff --git a/src/btp/service_access_point.rs b/src/btp/service_access_point.rs index 63bb2bd..6278d44 100644 --- a/src/btp/service_access_point.rs +++ b/src/btp/service_access_point.rs @@ -1,103 +1,170 @@ // SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) -use crate::geonet::gn_address::{GNAddress, M, ST, MID}; -use crate::geonet::service_access_point::{Area, GNDataIndication, PacketTransportType, CommunicationProfile, TrafficClass, CommonNH, HeaderType, HeaderSubType, UnspecifiedHST}; +use crate::geonet::gn_address::{GNAddress, M, MID, ST}; use crate::geonet::position_vector::LongPositionVector; +use crate::geonet::service_access_point::{ + Area, CommonNH, CommunicationProfile, GNDataIndication, HeaderSubType, HeaderType, + PacketTransportType, TrafficClass, UnspecifiedHST, +}; +use crate::security::sn_sap::SecurityProfile; -pub struct BTPDataRequest{ - pub btp_type : CommonNH, - pub source_port : u16, - pub destination_port : u16, - pub destination_port_info : u16, - pub gn_packet_transport_type : PacketTransportType, - pub gn_destination_address : GNAddress, - pub gn_area : Area, - pub communication_profile : CommunicationProfile, - pub traffic_class : TrafficClass, - pub length : u16, - pub data : Vec, +/// BTP Data Request — ETSI EN 302 636-5-1 V2.2.1 Annex A.2. +pub struct BTPDataRequest { + pub btp_type: CommonNH, + pub source_port: u16, + pub destination_port: u16, + pub destination_port_info: u16, + pub gn_packet_transport_type: PacketTransportType, + pub gn_destination_address: GNAddress, + pub gn_area: Area, + pub gn_max_hop_limit: u8, + pub gn_max_packet_lifetime: Option, + pub gn_repetition_interval: Option, + pub gn_max_repetition_time: Option, + pub communication_profile: CommunicationProfile, + pub traffic_class: TrafficClass, + pub security_profile: SecurityProfile, + pub its_aid: u64, + pub security_permissions: Vec, + pub destination: Option, + pub length: u16, + pub data: Vec, } impl BTPDataRequest { - pub fn new() -> Self{ - BTPDataRequest{ - btp_type : CommonNH::Any, - source_port : 0, - destination_port : 0, - destination_port_info : 0, - gn_packet_transport_type : PacketTransportType{ - header_type : HeaderType::Any, - header_sub_type : HeaderSubType::Unspecified(UnspecifiedHST::Unspecified), + pub fn new() -> Self { + BTPDataRequest { + btp_type: CommonNH::Any, + source_port: 0, + destination_port: 0, + destination_port_info: 0, + gn_packet_transport_type: PacketTransportType { + header_type: HeaderType::Any, + header_sub_type: HeaderSubType::Unspecified(UnspecifiedHST::Unspecified), }, - gn_destination_address : GNAddress{ - m : M::GnUnicast, - st : ST::Unknown, - mid : MID::new([0; 6]), + gn_destination_address: GNAddress { + m: M::GnUnicast, + st: ST::Unknown, + mid: MID::new([0; 6]), }, - gn_area : Area{ + gn_area: Area { latitude: 0, longitude: 0, a: 0, b: 0, angle: 0, }, - communication_profile : CommunicationProfile::Unspecified, - traffic_class : TrafficClass{ - scf : false, - channel_offload : false, - tc_id : 0, + gn_max_hop_limit: 1, + gn_max_packet_lifetime: None, + gn_repetition_interval: None, + gn_max_repetition_time: None, + communication_profile: CommunicationProfile::Unspecified, + traffic_class: TrafficClass { + scf: false, + channel_offload: false, + tc_id: 0, }, - length : 0, - data : vec![0,0], + security_profile: SecurityProfile::NoSecurity, + its_aid: 0, + security_permissions: vec![], + destination: None, + length: 0, + data: vec![], } } } -pub struct BTPDataIndication{ - pub source_port : u16, - pub destination_port : u16, - pub destination_port_info : u16, - pub gn_packet_transport_type : PacketTransportType, - pub gn_destination_address : GNAddress, - pub gn_source_position_vector : LongPositionVector, - pub gn_traffic_class : TrafficClass, - pub length : u16, - pub data : Vec, +/// BTP Data Indication — ETSI EN 302 636-5-1 V2.2.1 Annex A.3. +pub struct BTPDataIndication { + pub source_port: u16, + pub destination_port: u16, + pub destination_port_info: u16, + pub gn_packet_transport_type: PacketTransportType, + pub gn_destination_address: GNAddress, + pub gn_source_position_vector: LongPositionVector, + pub gn_security_report: Option>, + pub gn_certificate_id: Option>, + pub gn_permissions: Option>, + pub gn_traffic_class: TrafficClass, + pub gn_remaining_packet_lifetime: Option, + pub length: u16, + pub data: Vec, } -impl BTPDataIndication{ - - pub fn new() -> Self{ - BTPDataIndication{ - source_port : 0, - destination_port : 0, - destination_port_info : 0, - gn_packet_transport_type : PacketTransportType{ - header_type : HeaderType::Any, - header_sub_type : HeaderSubType::Unspecified(UnspecifiedHST::Unspecified), +impl BTPDataIndication { + pub fn new() -> Self { + BTPDataIndication { + source_port: 0, + destination_port: 0, + destination_port_info: 0, + gn_packet_transport_type: PacketTransportType { + header_type: HeaderType::Any, + header_sub_type: HeaderSubType::Unspecified(UnspecifiedHST::Unspecified), + }, + gn_destination_address: GNAddress { + m: M::GnUnicast, + st: ST::Unknown, + mid: MID::new([0; 6]), }, - gn_destination_address : GNAddress{ - m : M::GnUnicast, - st : ST::Unknown, - mid : MID::new([0; 6]), + gn_source_position_vector: LongPositionVector::decode([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]), + gn_security_report: None, + gn_certificate_id: None, + gn_permissions: None, + gn_traffic_class: TrafficClass { + scf: false, + channel_offload: false, + tc_id: 0, }, - gn_source_position_vector : LongPositionVector::decode([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]), - gn_traffic_class : TrafficClass{ - scf : false, - channel_offload : false, - tc_id : 0, + gn_remaining_packet_lifetime: None, + length: 0, + data: vec![], + } + } + + /// Construct a BTPDataIndication from a GNDataIndication. + /// Strips the first 4 bytes (BTP header) from the payload. + pub fn initialize_with_gn_data_indication( + gn_data_indication: &GNDataIndication, + ) -> Self { + let payload = if gn_data_indication.data.len() > 4 { + gn_data_indication.data[4..].to_vec() + } else { + vec![] + }; + BTPDataIndication { + source_port: 0, + destination_port: 0, + destination_port_info: 0, + gn_packet_transport_type: gn_data_indication.packet_transport_type.clone(), + gn_destination_address: GNAddress { + m: M::GnUnicast, + st: ST::Unknown, + mid: MID::new([0; 6]), }, - length : 0, - data : vec![0,0], + gn_source_position_vector: gn_data_indication.source_position_vector, + gn_security_report: None, + gn_certificate_id: None, + gn_permissions: None, + gn_traffic_class: gn_data_indication.traffic_class, + gn_remaining_packet_lifetime: gn_data_indication.remaining_packet_lifetime, + length: payload.len() as u16, + data: payload, } } - pub fn initialize_with_gn_data_indication(&mut self, gn_data_indication: &GNDataIndication){ - self.gn_packet_transport_type = gn_data_indication.packet_transport_type.clone(); - self.gn_source_position_vector = gn_data_indication.source_position_vector.clone(); - self.gn_traffic_class = gn_data_indication.traffic_class.clone(); - self.data = gn_data_indication.data[4..].to_vec().clone(); - self.length = self.data.len() as u16; + /// Return a new indication with the destination port and port info set. + pub fn set_destination_port_and_info( + self, + destination_port: u16, + destination_port_info: u16, + ) -> Self { + BTPDataIndication { + destination_port, + destination_port_info, + ..self + } } } diff --git a/src/facilities/ca_basic_service/cam_coder.rs b/src/facilities/ca_basic_service/cam_coder.rs index cda5fb8..1c9233a 100644 --- a/src/facilities/ca_basic_service/cam_coder.rs +++ b/src/facilities/ca_basic_service/cam_coder.rs @@ -12,17 +12,16 @@ // ─── Re-exports from compiled ASN.1 bindings ──────────────────────────────── pub use super::cam_bindings::cam_pdu_descriptions::{ - BasicVehicleContainerHighFrequency, CAM, CamParameters, CamPayload, HighFrequencyContainer, + BasicVehicleContainerHighFrequency, CamParameters, CamPayload, HighFrequencyContainer, CAM, }; pub use super::cam_bindings::etsi_its_cdd::{ AccelerationComponent, AccelerationConfidence, AccelerationValue, Altitude, AltitudeConfidence, AltitudeValue, BasicContainer, Curvature, CurvatureCalculationMode, CurvatureConfidence, CurvatureValue, DriveDirection, GenerationDeltaTime, Heading, HeadingConfidence, HeadingValue, ItsPduHeader, Latitude, Longitude, MessageId, OrdinalNumber1B, PositionConfidenceEllipse, - ReferencePositionWithConfidence, SemiAxisLength, Speed, - SpeedConfidence, SpeedValue, StationId, StationType, TrafficParticipantType, VehicleLength, - VehicleLengthConfidenceIndication, VehicleLengthValue, VehicleWidth, Wgs84AngleValue, YawRate, - YawRateConfidence, YawRateValue, + ReferencePositionWithConfidence, SemiAxisLength, Speed, SpeedConfidence, SpeedValue, StationId, + StationType, TrafficParticipantType, VehicleLength, VehicleLengthConfidenceIndication, + VehicleLengthValue, VehicleWidth, Wgs84AngleValue, YawRate, YawRateConfidence, YawRateValue, }; // ─── GenerationDeltaTime helpers ───────────────────────────────────────────── @@ -84,7 +83,6 @@ impl CamCoder { /// UPER-decode a [`Cam`] PDU from bytes. pub fn decode(&self, bytes: &[u8]) -> Result { - rasn::uper::decode::(bytes) - .map_err(|e| format!("CAM UPER decode error: {e}")) + rasn::uper::decode::(bytes).map_err(|e| format!("CAM UPER decode error: {e}")) } } diff --git a/src/facilities/ca_basic_service/cam_reception.rs b/src/facilities/ca_basic_service/cam_reception.rs index a833530..fb40e9a 100644 --- a/src/facilities/ca_basic_service/cam_reception.rs +++ b/src/facilities/ca_basic_service/cam_reception.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) -//! CAM Reception Management. +//! CAM Reception Management — ETSI TS 103 900 V2.2.1 §6.2 / Annex B.3. //! //! Mirrors `CAMReceptionManagement` in //! `flexstack/facilities/ca_basic_service/cam_reception_management.py`. @@ -10,8 +10,10 @@ //! 1. Registers BTP port 2001 with the BTP router. //! 2. Waits for [`BTPDataIndication`] messages from the router. //! 3. Decodes the UPER payload via [`CamCoder`]. -//! 4. Optionally stores the decoded CAM in the LDM (if a handle is provided). -//! 5. Forwards the decoded [`Cam`] to the caller via an MPSC channel. +//! 4. Decoding exceptions are caught and logged; the LDM/applications are NOT +//! updated with corrupt data (Annex B.3.3.1). +//! 5. Optionally stores the decoded CAM in the LDM (if a handle is provided). +//! 6. Forwards the decoded [`Cam`] to the caller via an MPSC channel. use super::cam_coder::{Cam, CamCoder}; use crate::btp::router::BTPRouterHandle; @@ -25,7 +27,7 @@ use crate::facilities::local_dynamic_map::{ use std::sync::mpsc::{self, Sender}; use std::thread; -/// CAM Reception Management. +/// CAM Reception Management — ETSI TS 103 900 V2.2.1 §6.2 / Annex B.3. /// /// Spawned as a background thread via [`CAMReceptionManagement::spawn`]. pub struct CAMReceptionManagement; @@ -44,9 +46,9 @@ impl CAMReceptionManagement { /// returned from [`CooperativeAwarenessBasicService::new`]. pub fn spawn( btp_handle: BTPRouterHandle, - coder: CamCoder, - cam_tx: Sender, - ldm: Option, + coder: CamCoder, + cam_tx: Sender, + ldm: Option, ) { // Create an internal BTPDataIndication channel and register it // on BTP port 2001 (CAM destination port per ETSI EN 302 637-2). @@ -61,24 +63,37 @@ impl CAMReceptionManagement { "[CAM RX] station={} gen_dt={} lat={:.5} lon={:.5}", cam.header.station_id.0, cam.cam.generation_delta_time.0, - cam.cam.cam_parameters.basic_container.reference_position.latitude.0 as f64 / 1e7, - cam.cam.cam_parameters.basic_container.reference_position.longitude.0 as f64 / 1e7, + cam.cam + .cam_parameters + .basic_container + .reference_position + .latitude + .0 as f64 + / 1e7, + cam.cam + .cam_parameters + .basic_container + .reference_position + .longitude + .0 as f64 + / 1e7, ); // Insert into LDM if a handle was provided. if let Some(ref ldm_handle) = ldm { - let ref_pos = &cam.cam.cam_parameters.basic_container.reference_position; + let ref_pos = + &cam.cam.cam_parameters.basic_container.reference_position; let lat = ref_pos.latitude.0; let lon = ref_pos.longitude.0; let alt = ref_pos.altitude.altitude_value.0; ldm_handle.if_ldm_3.add_provider_data(AddDataProviderReq { - application_id: ITS_AID_CAM, - timestamp_its: now_its_ms(), - lat_etsi: lat, - lon_etsi: lon, - altitude_cm: (alt / 10) as i32, // AltitudeValue in 0.01 m → cm - time_validity_s: 1, // CAM validity: 1 s - data_object: ItsDataObject::Cam(Box::new(cam.clone())), + application_id: ITS_AID_CAM, + timestamp_its: now_its_ms(), + lat_etsi: lat, + lon_etsi: lon, + altitude_cm: (alt / 10) as i32, // AltitudeValue in 0.01 m → cm + time_validity_s: 1, // CAM validity: 1 s + data_object: ItsDataObject::Cam(Box::new(cam.clone())), }); } @@ -87,7 +102,7 @@ impl CAMReceptionManagement { break; } } - Err(e) => eprintln!("[CAM RX] Decode error: {}", e), + Err(e) => eprintln!("[CAM RX] Decode error (Annex B.3.3.1 — discarding): {}", e), } } eprintln!("[CAM RX] Thread exiting"); diff --git a/src/facilities/ca_basic_service/cam_transmission.rs b/src/facilities/ca_basic_service/cam_transmission.rs index dc3b2ef..f288be6 100644 --- a/src/facilities/ca_basic_service/cam_transmission.rs +++ b/src/facilities/ca_basic_service/cam_transmission.rs @@ -1,55 +1,120 @@ // SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) -//! CAM Transmission Management. +//! CAM Transmission Management — ETSI TS 103 900 V2.2.1 (2025-02). //! //! Mirrors `CAMTransmissionManagement` in //! `flexstack/facilities/ca_basic_service/cam_transmission_management.py`. //! -//! [`CAMTransmissionManagement::spawn`] starts a background thread that: -//! 1. Waits for GPS fixes from a [`LocationService`] subscriber channel. -//! 2. Builds a CAM from the fix and the static vehicle data. -//! 3. UPER-encodes the CAM via [`CamCoder`]. -//! 4. Sends a [`BTPDataRequest`] on port 2001 via the BTP router handle. -//! -//! The transmission rate is capped at [`T_GEN_CAM_MIN`] = 100 ms (10 Hz) and -//! floored at [`T_GEN_CAM_MAX`] = 1 000 ms (1 Hz) per ETSI EN 302 637-2 §6.1. +//! Key behavioural properties: +//! - Timer-based (T_CheckCamGen) instead of GPS-callback-reactive (§6.1.3, Annex B). +//! - T_GenCam is initialised to T_GenCamMax (not T_GenCamMin) as mandated by §6.1.3. +//! - Condition 1 (dynamics: heading/position/speed) and Condition 2 (time) are both +//! evaluated on every T_CheckCamGen tick. +//! - N_GenCam counter resets T_GenCam to T_GenCamMax after N_GenCam consecutive +//! condition-1 CAMs. +//! - Low-Frequency, Special-Vehicle, Very-Low-Frequency and Two-Wheeler extension +//! containers are included according to §6.1.3. +//! - GN max packet lifetime set to 1000 ms per §5.3.4.1. +use super::cam_bindings::cam_pdu_descriptions::{ + BasicVehicleContainerLowFrequency, ExtensionContainerId, LowFrequencyContainer, + SpecialVehicleContainer, TwoWheelerContainer, VeryLowFrequencyContainer, + WrappedExtensionContainer, WrappedExtensionContainers, +}; +use super::cam_bindings::etsi_its_cdd::{ + DeltaAltitude, DeltaLatitude, DeltaLongitude, DeltaReferencePosition, ExteriorLights, Path, + PathDeltaTime, PathPoint, VehicleRole, +}; use super::cam_coder::{ cam_header, generation_delta_time_now, AccelerationComponent, AccelerationConfidence, AccelerationValue, Altitude, AltitudeConfidence, AltitudeValue, BasicContainer, BasicVehicleContainerHighFrequency, Cam, CamCoder, CamParameters, CamPayload, Curvature, - CurvatureCalculationMode, CurvatureConfidence, CurvatureValue, DriveDirection, - Heading, HeadingConfidence, HeadingValue, HighFrequencyContainer, - Latitude, Longitude, PositionConfidenceEllipse, ReferencePositionWithConfidence, - SemiAxisLength, Speed, SpeedConfidence, SpeedValue, - TrafficParticipantType, VehicleLength, VehicleLengthConfidenceIndication, - VehicleLengthValue, VehicleWidth, Wgs84AngleValue, YawRate, YawRateConfidence, YawRateValue, + CurvatureCalculationMode, CurvatureConfidence, CurvatureValue, DriveDirection, Heading, + HeadingConfidence, HeadingValue, HighFrequencyContainer, Latitude, Longitude, + PositionConfidenceEllipse, ReferencePositionWithConfidence, SemiAxisLength, Speed, + SpeedConfidence, SpeedValue, TrafficParticipantType, VehicleLength, + VehicleLengthConfidenceIndication, VehicleLengthValue, VehicleWidth, Wgs84AngleValue, YawRate, + YawRateConfidence, YawRateValue, }; use crate::btp::router::BTPRouterHandle; use crate::btp::service_access_point::BTPDataRequest; use crate::facilities::location_service::GpsFix; use crate::geonet::gn_address::{GNAddress, M, MID, ST}; use crate::geonet::service_access_point::{ - Area, CommunicationProfile, CommonNH, HeaderSubType, HeaderType, PacketTransportType, + Area, CommonNH, CommunicationProfile, HeaderSubType, HeaderType, PacketTransportType, TopoBroadcastHST, TrafficClass, }; -use std::sync::mpsc::Receiver; +use crate::security::sn_sap::SecurityProfile; +use rand::Rng; +use rasn::prelude::*; +use std::sync::mpsc::{Receiver, RecvTimeoutError}; use std::thread; use std::time::{Duration, Instant}; -// ─── Timing constants (ETSI EN 302 637-2 §6.1) ─────────────────────────────── +// ─── Timing constants (ETSI TS 103 900 V2.2.1 §6.1.3) ─────────────────────── + +/// T_GenCamMin [ms]: minimum CAM generation interval (10 Hz maximum rate). +pub const T_GEN_CAM_MIN_MS: u64 = 100; +/// T_GenCamMax [ms]: maximum CAM generation interval (1 Hz minimum rate). +pub const T_GEN_CAM_MAX_MS: u64 = 1_000; +/// T_CheckCamGen [ms]: timer period for condition evaluation (≤ T_GenCamMin). +pub const T_CHECK_CAM_GEN_MS: u64 = T_GEN_CAM_MIN_MS; +/// T_GenCam_DCC [ms]: DCC-imposed minimum interval ∈ [T_GenCamMin, T_GenCamMax]. +pub const T_GEN_CAM_DCC_MS: u64 = T_GEN_CAM_MIN_MS; + +// ─── Optional-container intervals (§6.1.3) ─────────────────────────────────── + +/// N_GenCam: max consecutive condition-1-triggered CAMs before resetting T_GenCam. +const N_GEN_CAM_DEFAULT: u32 = 3; +/// Low-frequency container minimum interval [ms]. +const T_GEN_CAM_LF_MS: u64 = 500; +/// Special-vehicle container minimum interval [ms]. +const T_GEN_CAM_SPECIAL_MS: u64 = 500; +/// Very-low-frequency container minimum interval [ms]. +const T_GEN_CAM_VLF_MS: u64 = 10_000; + +/// Station types that must include the Two-Wheeler extension container (§6.1.3): +/// cyclist(2), moped(3), motorcycle(4). +const TWO_WHEELER_STATION_TYPES: [u8; 3] = [2, 3, 4]; + +/// VehicleRole enum names indexed by integer value (§6.1.3 / CDD). +const VEHICLE_ROLE_NAMES: [VehicleRole; 16] = [ + VehicleRole::default, + VehicleRole::publicTransport, + VehicleRole::specialTransport, + VehicleRole::dangerousGoods, + VehicleRole::roadWork, + VehicleRole::rescue, + VehicleRole::emergency, + VehicleRole::safetyCar, + VehicleRole::agriculture, + VehicleRole::commercial, + VehicleRole::military, + VehicleRole::roadOperator, + VehicleRole::taxi, + VehicleRole::uvar, + VehicleRole::rfu1, + VehicleRole::rfu2, +]; -/// Minimum CAM generation interval: 100 ms (10 Hz maximum rate). -pub const T_GEN_CAM_MIN: Duration = Duration::from_millis(100); -/// Maximum CAM generation interval: 1 000 ms (1 Hz minimum rate). -pub const T_GEN_CAM_MAX: Duration = Duration::from_millis(1_000); +// ─── Haversine ──────────────────────────────────────────────────────────────── + +fn haversine_m(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 { + const R: f64 = 6_371_000.0; + let dlat = (lat2 - lat1).to_radians(); + let dlon = (lon2 - lon1).to_radians(); + let a = (dlat / 2.0).sin().powi(2) + + lat1.to_radians().cos() * lat2.to_radians().cos() * (dlon / 2.0).sin().powi(2); + R * 2.0 * a.sqrt().atan2((1.0 - a).max(0.0).sqrt()) +} // ─── VehicleData ───────────────────────────────────────────────────────────── /// Static vehicle data used to populate every CAM. /// -/// Mirrors the `VehicleData` frozen dataclass in the Python implementation. +/// Mirrors the `VehicleData` frozen dataclass in the Python implementation +/// (ETSI TS 103 900 V2.2.1). #[derive(Debug, Clone)] pub struct VehicleData { /// ITS station ID (0–4 294 967 295). @@ -63,94 +128,88 @@ pub struct VehicleData { pub vehicle_length_value: u16, /// Vehicle width in 0.1 m units (1–61 valid; 62 = unavailable). pub vehicle_width: u8, + /// VehicleRole (0=default). Used in the Low-Frequency container and to + /// decide whether a Special-Vehicle container is required (§6.1.3). + pub vehicle_role: u8, + /// ExteriorLights BIT STRING (SIZE(8)). One byte; bits ordered MSB→LSB + /// correspond to lowBeam(0)…parkingLights(7). Default = all off. + pub exterior_lights: [u8; 1], + /// Special vehicle container data (CHOICE variant), e.g. + /// `SpecialVehicleContainer::emergencyContainer(...)`. + /// `None` if not applicable. + pub special_vehicle_data: Option, } impl Default for VehicleData { /// Sensible defaults — PassengerCar, all kinematic fields unavailable. fn default() -> Self { VehicleData { - station_id: 0, - station_type: 5, // passengerCar - drive_direction: DriveDirection::unavailable, + station_id: 0, + station_type: 5, // passengerCar + drive_direction: DriveDirection::unavailable, vehicle_length_value: 1023, - vehicle_width: 62, + vehicle_width: 62, + vehicle_role: 0, + exterior_lights: [0x00], + special_vehicle_data: None, } } } // ─── CAM builder ───────────────────────────────────────────────────────────── -/// Build a complete [`Cam`] from a GPS fix and static vehicle data. -fn build_cam(fix: &GpsFix, vd: &VehicleData) -> Cam { +/// Build the BasicContainer + HighFrequencyContainer portions of a CAM from +/// a GPS fix and static vehicle data. +fn build_cam( + fix: &GpsFix, + vd: &VehicleData, + lf: Option, + special: Option, + extensions: Option, +) -> Cam { let gen_dt = generation_delta_time_now(); // ── BasicContainer ────────────────────────────────────────────────────── let ref_pos = ReferencePositionWithConfidence::new( - // Latitude: 1/10 µdeg, −900 000 000 .. 900 000 001 (unavail) Latitude(((fix.latitude * 1e7).round() as i32).clamp(-900_000_000, 900_000_000)), - // Longitude: 1/10 µdeg, −1 800 000 000 .. 1 800 000 001 (unavail) Longitude(((fix.longitude * 1e7).round() as i32).clamp(-1_800_000_000, 1_800_000_000)), - // PositionConfidenceEllipse — all axes unavailable PositionConfidenceEllipse::new( - SemiAxisLength(4095), // semiMajorAxisLength unavailable - SemiAxisLength(4095), // semiMinorAxisLength unavailable - Wgs84AngleValue(3601), // semiMajorAxisOrientation unavailable + SemiAxisLength(4095), + SemiAxisLength(4095), + Wgs84AngleValue(3601), ), - // Altitude — value in 0.01 m, 800 001 = unavailable Altitude::new( - AltitudeValue( - ((fix.altitude_m * 100.0).round() as i32).clamp(-100_000, 800_000), - ), + AltitudeValue(((fix.altitude_m * 100.0).round() as i32).clamp(-100_000, 800_000)), AltitudeConfidence::unavailable, ), ); - let basic_container = BasicContainer::new( - TrafficParticipantType(vd.station_type), - ref_pos, - ); + let basic_container = BasicContainer::new(TrafficParticipantType(vd.station_type), ref_pos); // ── BasicVehicleContainerHighFrequency ───────────────────────────────── - // HeadingValue: 0.1°, 0–3 600 valid, 3 601 = unavailable - let heading_value = HeadingValue( - ((fix.heading_deg * 10.0).round() as u16).clamp(0, 3600), - ); - // HeadingConfidence: 0.1°, 1–125 valid, 126 = outOfRange, 127 = unavailable + let heading_value = HeadingValue(((fix.heading_deg * 10.0).round() as u16).clamp(0, 3600)); let heading = Heading::new(heading_value, HeadingConfidence(127)); - // SpeedValue: 0.01 m/s, 0–16 382 valid, 16 383 = unavailable let speed_value = SpeedValue(((fix.speed_mps * 100.0).round() as u16).min(16_382)); - // SpeedConfidence: 0.01 m/s, 1–125 valid, 126 = outOfRange, 127 = unavailable let speed = Speed::new(speed_value, SpeedConfidence(127)); - // VehicleLength: 0.1 m, 1–1 022 valid, 1 023 = unavailable let vehicle_length = VehicleLength::new( VehicleLengthValue(vd.vehicle_length_value.clamp(1, 1023)), VehicleLengthConfidenceIndication::unavailable, ); - - // VehicleWidth: 0.1 m, 1–61 valid, 62 = unavailable let vehicle_width = VehicleWidth(vd.vehicle_width.clamp(1, 62)); - // LongitudinalAcceleration → AccelerationComponent (CDD v2) - // AccelerationValue: 0.1 m/s², −160..160 valid, 161 = unavailable - // AccelerationConfidence: 0..100 valid (in 0.1 m/s²), 101 = outOfRange, 102 = unavailable let longitudinal_acceleration = AccelerationComponent::new( - AccelerationValue(161), // unavailable - AccelerationConfidence(102), // unavailable + AccelerationValue(161), + AccelerationConfidence(102), ); - - // Curvature: 0.0001 m⁻¹, −1 022..1 022 valid, 1 023 = unavailable let curvature = Curvature::new( - CurvatureValue(1023), // unavailable - CurvatureConfidence::unavailable, // = 8 + CurvatureValue(1023), + CurvatureConfidence::unavailable, ); - - // YawRate: 0.01 °/s, −32 766..32 766 valid, 32 767 = unavailable - // YawRateConfidence: enum, 8 = unavailable let yaw_rate = YawRate::new( - YawRateValue(32767), // unavailable - YawRateConfidence::unavailable, // = 8 + YawRateValue(32767), + YawRateConfidence::unavailable, ); let hf = BasicVehicleContainerHighFrequency::new( @@ -163,13 +222,13 @@ fn build_cam(fix: &GpsFix, vd: &VehicleData) -> Cam { curvature, CurvatureCalculationMode::unavailable, yaw_rate, - None, // accelerationControl - None, // lanePosition - None, // steeringWheelAngle - None, // lateralAcceleration - None, // verticalAcceleration - None, // performanceClass - None, // cenDsrcTollingZone + None, + None, + None, + None, + None, + None, + None, ); Cam::new( @@ -179,89 +238,534 @@ fn build_cam(fix: &GpsFix, vd: &VehicleData) -> Cam { CamParameters::new( basic_container, HighFrequencyContainer::basicVehicleContainerHighFrequency(hf), - None, // lowFrequencyContainer - None, // specialVehicleContainer - None, // extensionContainers + lf, + special, + extensions, ), ), ) } +// ─── Path history entry ────────────────────────────────────────────────────── + +struct PathEntry { + lat: f64, + lon: f64, + time_ms: u64, +} + // ─── CAMTransmissionManagement ─────────────────────────────────────────────── -/// CAM Transmission Management. +/// CAM Transmission Management — ETSI TS 103 900 V2.2.1 §6.1. +/// +/// Timer-based (T_CheckCamGen) architecture. Evaluates Condition 1 (dynamics) +/// and Condition 2 (time) on every tick. pub struct CAMTransmissionManagement; impl CAMTransmissionManagement { /// Spawn the transmission management thread. /// - /// # Arguments - /// * `btp_handle` — handle to the BTP router for sending CAMs. - /// * `coder` — shared [`CamCoder`] instance. - /// * `vehicle_data` — static vehicle parameters. - /// * `gps_rx` — GPS fix channel (from [`LocationService::subscribe`]). + /// Implements the T_CheckCamGen timer loop per Annex B.2.4. The thread + /// drains GPS fixes from `gps_rx`, caching the latest, and evaluates CAM + /// generation conditions every `T_CHECK_CAM_GEN_MS` milliseconds. pub fn spawn( - btp_handle: BTPRouterHandle, - coder: CamCoder, + btp_handle: BTPRouterHandle, + coder: CamCoder, vehicle_data: VehicleData, - gps_rx: Receiver, + gps_rx: Receiver, ) { thread::spawn(move || { - let mut last_sent: Option = None; + // §6.1.3 — T_GenCam starts at T_GenCamMax + let mut t_gen_cam_ms: u64 = T_GEN_CAM_MAX_MS; + let mut n_gen_cam_counter: u32 = 0; - while let Ok(fix) = gps_rx.recv() { - let now = Instant::now(); + // Dynamics state of last transmitted CAM + let mut last_cam_time: Option = None; + let mut last_cam_heading: Option = None; + let mut last_cam_lat: Option = None; + let mut last_cam_lon: Option = None; + let mut last_cam_speed: Option = None; + + // Container timing state + let mut cam_count: u64 = 0; + let mut last_lf_time: Option = None; + let mut last_vlf_time: Option = None; + let mut last_special_time: Option = None; + + // Path history (lat, lon, time_ms) oldest→newest; max 40 + let mut path_history: Vec = Vec::new(); - // Enforce T_GEN_CAM_MIN — skip if called too quickly. - if let Some(last) = last_sent { - if now.duration_since(last) < T_GEN_CAM_MIN { - continue; + // Cached GPS fix + let mut current_fix: Option = None; + + // Annex B.2.4 step 1 — non-clock-synchronised start (random initial delay) + let initial_delay = Duration::from_millis( + rand::thread_rng().gen_range(0..T_CHECK_CAM_GEN_MS), + ); + thread::sleep(initial_delay); + + loop { + // Drain GPS fixes for T_CHECK_CAM_GEN_MS, then evaluate conditions + let deadline = Instant::now() + Duration::from_millis(T_CHECK_CAM_GEN_MS); + loop { + let remaining = deadline.saturating_duration_since(Instant::now()); + if remaining.is_zero() { + break; + } + match gps_rx.recv_timeout(remaining) { + Ok(fix) => current_fix = Some(fix), + Err(RecvTimeoutError::Timeout) => break, + Err(RecvTimeoutError::Disconnected) => { + eprintln!("[CAM TX] GPS channel closed, thread exiting"); + return; + } } } - let cam = build_cam(&fix, &vehicle_data); - - match coder.encode(&cam) { - Ok(data) => { - let req = BTPDataRequest { - btp_type: CommonNH::BtpB, - source_port: 0, - destination_port: 2001, - destination_port_info: 0, - gn_packet_transport_type: PacketTransportType { - header_type: HeaderType::Tsb, - header_sub_type: HeaderSubType::TopoBroadcast( - TopoBroadcastHST::SingleHop, - ), - }, - gn_destination_address: GNAddress { - m: M::GnMulticast, - st: ST::Unknown, - mid: MID::new([0xFF; 6]), - }, - communication_profile: CommunicationProfile::Unspecified, - gn_area: Area { - latitude: 0, - longitude: 0, - a: 0, - b: 0, - angle: 0, - }, - traffic_class: TrafficClass { - scf: false, - channel_offload: false, - tc_id: 0, - }, - length: data.len() as u16, - data, - }; - btp_handle.send_btp_data_request(req); - last_sent = Some(Instant::now()); - } - Err(e) => eprintln!("[CAM TX] Encode error: {}", e), + // Timer tick — evaluate CAM conditions (Annex B.2.4 step 2) + let fix = match current_fix { + Some(ref f) => f.clone(), + None => continue, + }; + + let now = Instant::now(); + + // First CAM after activation — send immediately + if last_cam_time.is_none() { + generate_and_send( + &fix, + &vehicle_data, + &coder, + &btp_handle, + now, + 0, + 1, // condition 1 + &mut t_gen_cam_ms, + &mut n_gen_cam_counter, + &mut last_cam_time, + &mut last_cam_heading, + &mut last_cam_lat, + &mut last_cam_lon, + &mut last_cam_speed, + &mut cam_count, + &mut last_lf_time, + &mut last_vlf_time, + &mut last_special_time, + &mut path_history, + ); + continue; + } + + let elapsed_ms = now.duration_since(last_cam_time.unwrap()).as_millis() as u64; + + // Condition 1 (§6.1.3): elapsed ≥ T_GenCam_DCC AND dynamics changed + if elapsed_ms >= T_GEN_CAM_DCC_MS + && check_dynamics( + &fix, + last_cam_heading, + last_cam_lat, + last_cam_lon, + last_cam_speed, + ) + { + generate_and_send( + &fix, + &vehicle_data, + &coder, + &btp_handle, + now, + elapsed_ms, + 1, + &mut t_gen_cam_ms, + &mut n_gen_cam_counter, + &mut last_cam_time, + &mut last_cam_heading, + &mut last_cam_lat, + &mut last_cam_lon, + &mut last_cam_speed, + &mut cam_count, + &mut last_lf_time, + &mut last_vlf_time, + &mut last_special_time, + &mut path_history, + ); + continue; + } + + // Condition 2 (§6.1.3): elapsed ≥ T_GenCam AND elapsed ≥ T_GenCam_DCC + if elapsed_ms >= t_gen_cam_ms && elapsed_ms >= T_GEN_CAM_DCC_MS { + generate_and_send( + &fix, + &vehicle_data, + &coder, + &btp_handle, + now, + elapsed_ms, + 2, + &mut t_gen_cam_ms, + &mut n_gen_cam_counter, + &mut last_cam_time, + &mut last_cam_heading, + &mut last_cam_lat, + &mut last_cam_lon, + &mut last_cam_speed, + &mut cam_count, + &mut last_lf_time, + &mut last_vlf_time, + &mut last_special_time, + &mut path_history, + ); } } - eprintln!("[CAM TX] GPS channel closed, thread exiting"); }); } } + +// ─── Dynamics check — §6.1.3 Condition 1 ──────────────────────────────────── + +/// Return `true` if at least one dynamics threshold is exceeded. +/// +/// Thresholds (§6.1.3): +/// * |Δheading| > 4° +/// * |Δposition| > 4 m (haversine) +/// * |Δspeed| > 0.5 m/s +fn check_dynamics( + fix: &GpsFix, + last_heading: Option, + last_lat: Option, + last_lon: Option, + last_speed: Option, +) -> bool { + // No reference — treat as changed + if last_heading.is_none() { + return true; + } + + // Heading + if let Some(prev) = last_heading { + let mut diff = (fix.heading_deg - prev).abs(); + if diff > 180.0 { + diff = 360.0 - diff; + } + if diff > 4.0 { + return true; + } + } + + // Position + if let (Some(prev_lat), Some(prev_lon)) = (last_lat, last_lon) { + if haversine_m(prev_lat, prev_lon, fix.latitude, fix.longitude) > 4.0 { + return true; + } + } + + // Speed + if let Some(prev_speed) = last_speed { + if (fix.speed_mps - prev_speed).abs() > 0.5 { + return true; + } + } + + false +} + +// ─── Optional container inclusion rules (§6.1.3) ──────────────────────────── + +fn should_include_lf(cam_count: u64, last_lf_time: Option, now: Instant) -> bool { + if cam_count == 0 { + return true; + } + match last_lf_time { + None => true, + Some(t) => now.duration_since(t).as_millis() as u64 >= T_GEN_CAM_LF_MS, + } +} + +fn should_include_special_vehicle( + vehicle_role: u8, + cam_count: u64, + last_special_time: Option, + now: Instant, +) -> bool { + if vehicle_role == 0 { + return false; + } + if cam_count == 0 { + return true; + } + match last_special_time { + None => true, + Some(t) => now.duration_since(t).as_millis() as u64 >= T_GEN_CAM_SPECIAL_MS, + } +} + +fn should_include_vlf( + cam_count: u64, + last_vlf_time: Option, + now: Instant, + include_lf: bool, + include_special: bool, +) -> bool { + // Second CAM after activation (cam_count == 1) + if cam_count == 1 { + return true; + } + match last_vlf_time { + None => false, + Some(t) => { + now.duration_since(t).as_millis() as u64 >= T_GEN_CAM_VLF_MS + && !include_lf + && !include_special + } + } +} + +fn should_include_two_wheeler(station_type: u8) -> bool { + TWO_WHEELER_STATION_TYPES.contains(&station_type) +} + +// ─── Low-Frequency container builder ───────────────────────────────────────── + +fn build_lf_container( + vd: &VehicleData, + fix: &GpsFix, + path_history: &[PathEntry], + now_ms: u64, +) -> LowFrequencyContainer { + let role_idx = vd.vehicle_role as usize; + let role = if role_idx < VEHICLE_ROLE_NAMES.len() { + VEHICLE_ROLE_NAMES[role_idx] + } else { + VehicleRole::default + }; + + let mut ext_bits = rasn::types::FixedBitString::<8>::default(); + // Set bits from the byte: bit 0 (MSB) = lowBeam, etc. + for i in 0..8 { + if vd.exterior_lights[0] & (1 << (7 - i)) != 0 { + ext_bits.set(i, true); + } + } + let ext_lights = ExteriorLights(ext_bits); + + let path_points = build_path_points(fix, path_history, now_ms); + let path = Path(path_points); + + LowFrequencyContainer::basicVehicleContainerLowFrequency( + BasicVehicleContainerLowFrequency::new(role, ext_lights, path), + ) +} + +fn build_path_points( + current_fix: &GpsFix, + history: &[PathEntry], + now_ms: u64, +) -> Vec { + let mut result = Vec::new(); + for entry in history.iter().rev() { + let delta_lat = ((entry.lat - current_fix.latitude) * 1e7).round() as i32; + let delta_lon = ((entry.lon - current_fix.longitude) * 1e7).round() as i32; + if !(-131_071..=131_072).contains(&delta_lat) { + break; + } + if !(-131_071..=131_072).contains(&delta_lon) { + break; + } + let delta_time_10ms = ((now_ms.saturating_sub(entry.time_ms)) / 10).clamp(1, 65_534); + result.push(PathPoint::new( + DeltaReferencePosition::new( + DeltaLatitude(delta_lat), + DeltaLongitude(delta_lon), + DeltaAltitude(12800), // unavailable + ), + Some(PathDeltaTime(Integer::from(delta_time_10ms as i128))), + )); + if result.len() >= 23 { + break; + } + } + result +} + +// ─── Extension container builder ───────────────────────────────────────────── + +fn build_extension_containers( + include_two_wheeler: bool, + include_vlf: bool, +) -> Option { + let mut containers = Vec::new(); + + if include_two_wheeler { + let tw = TwoWheelerContainer::new(None, None, None, None); + if let Ok(tw_bytes) = rasn::uper::encode(&tw) { + containers.push(WrappedExtensionContainer::new( + ExtensionContainerId(Integer::from(1i128)), + Any::new(tw_bytes.to_vec()), + )); + } + } + + if include_vlf { + let vlf = VeryLowFrequencyContainer::new(None, None, None); + if let Ok(vlf_bytes) = rasn::uper::encode(&vlf) { + containers.push(WrappedExtensionContainer::new( + ExtensionContainerId(Integer::from(3i128)), + Any::new(vlf_bytes.to_vec()), + )); + } + } + + if containers.is_empty() { + None + } else { + Some(WrappedExtensionContainers(containers)) + } +} + +// ─── Generate and send ─────────────────────────────────────────────────────── + +#[allow(clippy::too_many_arguments)] +fn generate_and_send( + fix: &GpsFix, + vd: &VehicleData, + coder: &CamCoder, + btp_handle: &BTPRouterHandle, + now: Instant, + elapsed_ms: u64, + condition: u8, + t_gen_cam_ms: &mut u64, + n_gen_cam_counter: &mut u32, + last_cam_time: &mut Option, + last_cam_heading: &mut Option, + last_cam_lat: &mut Option, + last_cam_lon: &mut Option, + last_cam_speed: &mut Option, + cam_count: &mut u64, + last_lf_time: &mut Option, + last_vlf_time: &mut Option, + last_special_time: &mut Option, + path_history: &mut Vec, +) { + let include_lf = should_include_lf(*cam_count, *last_lf_time, now); + let include_special = should_include_special_vehicle(vd.vehicle_role, *cam_count, *last_special_time, now); + let include_vlf = should_include_vlf(*cam_count, *last_vlf_time, now, include_lf, include_special); + let include_tw = should_include_two_wheeler(vd.station_type); + + // Approximate now_ms for path history delta-time calculations + let now_ms = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64; + + // Build optional containers + let lf = if include_lf { + Some(build_lf_container(vd, fix, path_history, now_ms)) + } else { + None + }; + + let special = if include_special { + vd.special_vehicle_data.clone() + } else { + None + }; + + let extensions = build_extension_containers(include_tw, include_vlf); + + // Build and encode CAM (Annex B.2.5 — skip on failure) + let cam = build_cam(fix, vd, lf, special, extensions); + + match coder.encode(&cam) { + Ok(data) => { + let req = BTPDataRequest { + btp_type: CommonNH::BtpB, + source_port: 0, + destination_port: 2001, + destination_port_info: 0, + gn_packet_transport_type: PacketTransportType { + header_type: HeaderType::Tsb, + header_sub_type: HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop), + }, + gn_destination_address: GNAddress { + m: M::GnMulticast, + st: ST::Unknown, + mid: MID::new([0xFF; 6]), + }, + communication_profile: CommunicationProfile::Unspecified, + gn_area: Area { + latitude: 0, + longitude: 0, + a: 0, + b: 0, + angle: 0, + }, + traffic_class: TrafficClass { + scf: false, + channel_offload: false, + tc_id: 0, + }, + security_profile: SecurityProfile::CooperativeAwarenessMessage, + its_aid: 36, + security_permissions: vec![], + gn_max_hop_limit: 1, + gn_max_packet_lifetime: Some(1.0), // §5.3.4.1: 1000 ms + gn_repetition_interval: None, + gn_max_repetition_time: None, + destination: None, + length: data.len() as u16, + data, + }; + btp_handle.send_btp_data_request(req); + + eprintln!( + "[CAM TX] Sent CAM: station={} cond={}", + vd.station_id, condition, + ); + + // ── Update state after successful transmission (§6.1.3) ────── + if condition == 1 { + *t_gen_cam_ms = elapsed_ms.clamp(T_GEN_CAM_MIN_MS, T_GEN_CAM_MAX_MS); + *n_gen_cam_counter += 1; + if *n_gen_cam_counter >= N_GEN_CAM_DEFAULT { + *t_gen_cam_ms = T_GEN_CAM_MAX_MS; + *n_gen_cam_counter = 0; + } + } else { + *n_gen_cam_counter = 0; + *t_gen_cam_ms = T_GEN_CAM_MAX_MS; + } + + *last_cam_time = Some(now); + *last_cam_heading = Some(fix.heading_deg); + *last_cam_lat = Some(fix.latitude); + *last_cam_lon = Some(fix.longitude); + *last_cam_speed = Some(fix.speed_mps); + + // Update path history + path_history.push(PathEntry { + lat: fix.latitude, + lon: fix.longitude, + time_ms: now_ms, + }); + if path_history.len() > 40 { + path_history.remove(0); + } + + // Update container timing + if include_lf { + *last_lf_time = Some(now); + } + if include_special { + *last_special_time = Some(now); + } + if include_vlf { + *last_vlf_time = Some(now); + } + + *cam_count += 1; + } + Err(e) => { + eprintln!("[CAM TX] Encode error (Annex B.2.5 — skipping): {}", e); + } + } +} diff --git a/src/facilities/ca_basic_service/mod.rs b/src/facilities/ca_basic_service/mod.rs index e6eb7fa..c56de30 100644 --- a/src/facilities/ca_basic_service/mod.rs +++ b/src/facilities/ca_basic_service/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) -//! CA Basic Service — Cooperative Awareness Basic Service (ETSI EN 302 637-2). +//! CA Basic Service — Cooperative Awareness Basic Service (ETSI TS 103 900 V2.2.1). //! //! Implements CAM generation and reception on top of the BTP and //! GeoNetworking layers already present in this crate. @@ -59,12 +59,12 @@ use std::sync::mpsc::{self, Receiver, Sender}; /// Create with [`new`](Self::new), then call [`start`](Self::start) once the /// GPS channel wiring is ready. pub struct CooperativeAwarenessBasicService { - btp_handle: BTPRouterHandle, + btp_handle: BTPRouterHandle, vehicle_data: VehicleData, /// Reception management writes decoded CAMs into this sender. - cam_tx: Sender, + cam_tx: Sender, /// Optional LDM handle — if provided, received CAMs are stored in the LDM. - ldm: Option, + ldm: Option, } impl CooperativeAwarenessBasicService { @@ -77,12 +77,17 @@ impl CooperativeAwarenessBasicService { /// Pass `Some(ldm_handle)` to have every received CAM inserted into the /// LDM automatically before it is delivered to the `cam_receiver`. pub fn new( - btp_handle: BTPRouterHandle, + btp_handle: BTPRouterHandle, vehicle_data: VehicleData, - ldm: Option, + ldm: Option, ) -> (Self, Receiver) { let (cam_tx, cam_rx) = mpsc::channel::(); - let svc = CooperativeAwarenessBasicService { btp_handle, vehicle_data, cam_tx, ldm }; + let svc = CooperativeAwarenessBasicService { + btp_handle, + vehicle_data, + cam_tx, + ldm, + }; (svc, cam_rx) } @@ -94,7 +99,12 @@ impl CooperativeAwarenessBasicService { /// The transmission thread starts producing CAMs as soon as fixes arrive. pub fn start(self, gps_rx: Receiver) { let coder = CamCoder::new(); - CAMReceptionManagement::spawn(self.btp_handle.clone(), coder.clone(), self.cam_tx, self.ldm); + CAMReceptionManagement::spawn( + self.btp_handle.clone(), + coder.clone(), + self.cam_tx, + self.ldm, + ); CAMTransmissionManagement::spawn(self.btp_handle, coder, self.vehicle_data, gps_rx); } } diff --git a/src/facilities/decentralized_environmental_notification_service/denm_coder.rs b/src/facilities/decentralized_environmental_notification_service/denm_coder.rs index 090ae5d..6ca211c 100644 --- a/src/facilities/decentralized_environmental_notification_service/denm_coder.rs +++ b/src/facilities/decentralized_environmental_notification_service/denm_coder.rs @@ -12,18 +12,17 @@ // ─── Re-exports from compiled ASN.1 bindings ───────────────────────────────── pub use super::denm_bindings::denm_pdu_description::{ - AlacarteContainer, DenmPayload, LocationContainer, ManagementContainer, - SituationContainer, Termination, DENM, + AlacarteContainer, DenmPayload, LocationContainer, ManagementContainer, SituationContainer, + Termination, DENM, }; pub use super::denm_bindings::etsi_its_cdd::{ - AccidentSubCauseCode, ActionId, Altitude, AltitudeConfidence, AltitudeValue, - CauseCodeChoice, CauseCodeV2, DeltaAltitude, DeltaLatitude, DeltaLongitude, - DeltaReferencePosition, DeltaTimeMilliSecondPositive, DeltaTimeSecond, - HeadingValue, InformationQuality, ItsPduHeader, Latitude, Longitude, - MessageId, OrdinalNumber1B, Path, PathPoint, PosConfidenceEllipse, - ReferencePosition, SemiAxisLength, SequenceNumber, Speed, SpeedConfidence, - SpeedValue, StationId, StationType, SubCauseCodeType, TimestampIts, Traces, - TrafficParticipantType, Wgs84Angle, Wgs84AngleConfidence, Wgs84AngleValue, + AccidentSubCauseCode, ActionId, Altitude, AltitudeConfidence, AltitudeValue, CauseCodeChoice, + CauseCodeV2, DeltaAltitude, DeltaLatitude, DeltaLongitude, DeltaReferencePosition, + DeltaTimeMilliSecondPositive, DeltaTimeSecond, HeadingValue, InformationQuality, ItsPduHeader, + Latitude, Longitude, MessageId, OrdinalNumber1B, Path, PathPoint, PosConfidenceEllipse, + ReferencePosition, SemiAxisLength, SequenceNumber, Speed, SpeedConfidence, SpeedValue, + StationId, StationType, SubCauseCodeType, TimestampIts, Traces, TrafficParticipantType, + Wgs84Angle, Wgs84AngleConfidence, Wgs84AngleValue, }; // ─── ITS epoch constant ─────────────────────────────────────────────────────── @@ -83,7 +82,6 @@ impl DenmCoder { /// UPER-decode a [`Denm`] PDU from bytes. pub fn decode(&self, bytes: &[u8]) -> Result { - rasn::uper::decode::(bytes) - .map_err(|e| format!("DENM UPER decode error: {e}")) + rasn::uper::decode::(bytes).map_err(|e| format!("DENM UPER decode error: {e}")) } } diff --git a/src/facilities/decentralized_environmental_notification_service/denm_reception.rs b/src/facilities/decentralized_environmental_notification_service/denm_reception.rs index 6fa9fa6..3fe3898 100644 --- a/src/facilities/decentralized_environmental_notification_service/denm_reception.rs +++ b/src/facilities/decentralized_environmental_notification_service/denm_reception.rs @@ -33,11 +33,7 @@ impl DENMReceptionManagement { /// /// The caller should hold the corresponding `Receiver` — typically /// returned from [`DecentralizedEnvironmentalNotificationService::new`]. - pub fn spawn( - btp_handle: BTPRouterHandle, - coder: DenmCoder, - denm_tx: Sender, - ) { + pub fn spawn(btp_handle: BTPRouterHandle, coder: DenmCoder, denm_tx: Sender) { // Create an internal BTPDataIndication channel and register it // on BTP port 2002 (DENM destination port per ETSI EN 302 637-3). let (ind_tx, ind_rx) = mpsc::channel::(); diff --git a/src/facilities/decentralized_environmental_notification_service/denm_transmission.rs b/src/facilities/decentralized_environmental_notification_service/denm_transmission.rs index 99c8f08..f6aacbb 100644 --- a/src/facilities/decentralized_environmental_notification_service/denm_transmission.rs +++ b/src/facilities/decentralized_environmental_notification_service/denm_transmission.rs @@ -17,22 +17,22 @@ //! the configured `time_period_ms` has elapsed. use super::denm_coder::{ - denm_header, timestamp_its_now, ActionId, Altitude, AltitudeConfidence, - AltitudeValue, CauseCodeChoice, CauseCodeV2, DeltaAltitude, DeltaLatitude, - DeltaLongitude, DeltaReferencePosition, DeltaTimeMilliSecondPositive, - DeltaTimeSecond, Denm, DenmCoder, DenmPayload, InformationQuality, Latitude, - Longitude, ManagementContainer, Path, PathPoint, PosConfidenceEllipse, - ReferencePosition, SemiAxisLength, SequenceNumber, SituationContainer, - Speed, SpeedConfidence, SpeedValue, StationId, StationType, SubCauseCodeType, - Traces, TrafficParticipantType, Wgs84Angle, Wgs84AngleConfidence, - Wgs84AngleValue, LocationContainer, HeadingValue, + denm_header, timestamp_its_now, ActionId, Altitude, AltitudeConfidence, AltitudeValue, + CauseCodeChoice, CauseCodeV2, DeltaAltitude, DeltaLatitude, DeltaLongitude, + DeltaReferencePosition, DeltaTimeMilliSecondPositive, DeltaTimeSecond, Denm, DenmCoder, + DenmPayload, HeadingValue, InformationQuality, Latitude, LocationContainer, Longitude, + ManagementContainer, Path, PathPoint, PosConfidenceEllipse, ReferencePosition, SemiAxisLength, + SequenceNumber, SituationContainer, Speed, SpeedConfidence, SpeedValue, StationId, StationType, + SubCauseCodeType, Traces, TrafficParticipantType, Wgs84Angle, Wgs84AngleConfidence, + Wgs84AngleValue, }; use crate::btp::router::BTPRouterHandle; +use crate::security::sn_sap::SecurityProfile; use crate::btp::service_access_point::BTPDataRequest; use crate::geonet::gn_address::{GNAddress, M, MID, ST}; use crate::geonet::service_access_point::{ - Area, CommunicationProfile, CommonNH, GeoBroadcastHST, HeaderSubType, - HeaderType, PacketTransportType, TrafficClass, + Area, CommonNH, CommunicationProfile, GeoBroadcastHST, HeaderSubType, HeaderType, + PacketTransportType, TrafficClass, }; use std::sync::atomic::{AtomicU16, Ordering}; use std::sync::Arc; @@ -57,7 +57,7 @@ pub struct VehicleData { impl Default for VehicleData { fn default() -> Self { VehicleData { - station_id: 0, + station_id: 0, station_type: 5, // passengerCar } } @@ -73,7 +73,7 @@ impl Default for VehicleData { pub struct DENRequest { // ── Event position ─────────────────────────────────────────────────────── /// Event latitude in decimal degrees (WGS-84). - pub event_latitude: f64, + pub event_latitude: f64, /// Event longitude in decimal degrees (WGS-84). pub event_longitude: f64, /// Event altitude in metres above WGS-84 ellipsoid. @@ -105,17 +105,15 @@ pub struct DENRequest { impl Default for DENRequest { fn default() -> Self { DENRequest { - event_latitude: 0.0, - event_longitude: 0.0, - event_altitude_m: 0.0, - cause_code: CauseCodeChoice::accident2( - super::denm_coder::AccidentSubCauseCode(0), - ), + event_latitude: 0.0, + event_longitude: 0.0, + event_altitude_m: 0.0, + cause_code: CauseCodeChoice::accident2(super::denm_coder::AccidentSubCauseCode(0)), information_quality: 0, - event_speed_raw: 16383, // unavailable - event_heading_raw: 3601, // unavailable - denm_interval_ms: 1000, - time_period_ms: 5000, + event_speed_raw: 16383, // unavailable + event_heading_raw: 3601, // unavailable + denm_interval_ms: 1000, + time_period_ms: 5000, relevance_radius_m: 1000, } } @@ -138,42 +136,34 @@ pub fn build_denm(request: &DENRequest, vd: &VehicleData, seq_nr: u16) -> Denm { // ── ManagementContainer ─────────────────────────────────────────────────── let event_pos = ReferencePosition::new( - Latitude( - ((request.event_latitude * 1e7).round() as i32) - .clamp(-900_000_000, 900_000_000), - ), + Latitude(((request.event_latitude * 1e7).round() as i32).clamp(-900_000_000, 900_000_000)), Longitude( - ((request.event_longitude * 1e7).round() as i32) - .clamp(-1_800_000_000, 1_800_000_000), + ((request.event_longitude * 1e7).round() as i32).clamp(-1_800_000_000, 1_800_000_000), ), PosConfidenceEllipse::new( - SemiAxisLength(4095), // unavailable - SemiAxisLength(4095), // unavailable - HeadingValue(3601), // unavailable (uses HeadingValue in DENM) + SemiAxisLength(4095), // unavailable + SemiAxisLength(4095), // unavailable + HeadingValue(3601), // unavailable (uses HeadingValue in DENM) ), Altitude::new( AltitudeValue( - ((request.event_altitude_m * 100.0).round() as i32) - .clamp(-100_000, 800_000), + ((request.event_altitude_m * 100.0).round() as i32).clamp(-100_000, 800_000), ), AltitudeConfidence::unavailable, ), ); - let action_id = ActionId::new( - StationId(vd.station_id), - SequenceNumber(seq_nr), - ); + let action_id = ActionId::new(StationId(vd.station_id), SequenceNumber(seq_nr)); let management = ManagementContainer::new( action_id, - now.clone(), // detectionTime - now.clone(), // referenceTime - None, // termination — active DENM + now.clone(), // detectionTime + now.clone(), // referenceTime + None, // termination — active DENM event_pos, - None, // awarenessDistance - None, // trafficDirection - DeltaTimeSecond(600), // validityDuration: 600 s default + None, // awarenessDistance + None, // trafficDirection + DeltaTimeSecond(600), // validityDuration: 600 s default Some(DeltaTimeMilliSecondPositive( request.denm_interval_ms.min(10_000) as u16, )), @@ -195,9 +185,9 @@ pub fn build_denm(request: &DENRequest, vd: &VehicleData, seq_nr: u16) -> Denm { // delta (zero offset — the event is at the reference position). let path_point = PathPoint::new( DeltaReferencePosition::new( - DeltaLatitude(131072), // 131072 = unavailable - DeltaLongitude(131072), // 131072 = unavailable - DeltaAltitude(12800), // 12800 = unavailable + DeltaLatitude(131072), // 131072 = unavailable + DeltaLongitude(131072), // 131072 = unavailable + DeltaAltitude(12800), // 12800 = unavailable ), None, // pathDeltaTime ); @@ -240,18 +230,18 @@ pub fn build_denm(request: &DENRequest, vd: &VehicleData, seq_nr: u16) -> Denm { /// Mirrors `DENMTransmissionManagement` in /// `flexstack/facilities/decentralized_environmental_notification_service/denm_transmission_management.py`. pub struct DENMTransmissionManagement { - btp_handle: BTPRouterHandle, - coder: DenmCoder, + btp_handle: BTPRouterHandle, + coder: DenmCoder, vehicle_data: VehicleData, } impl DENMTransmissionManagement { - pub fn new( - btp_handle: BTPRouterHandle, - coder: DenmCoder, - vehicle_data: VehicleData, - ) -> Self { - DENMTransmissionManagement { btp_handle, coder, vehicle_data } + pub fn new(btp_handle: BTPRouterHandle, coder: DenmCoder, vehicle_data: VehicleData) -> Self { + DENMTransmissionManagement { + btp_handle, + coder, + vehicle_data, + } } /// Trigger repeated DENM transmission in a new background thread. @@ -259,18 +249,18 @@ impl DENMTransmissionManagement { /// DENMs are sent every `request.denm_interval_ms` milliseconds for /// `request.time_period_ms` total milliseconds. pub fn trigger_denm_sending(&self, request: DENRequest) { - let btp_handle = self.btp_handle.clone(); - let coder = self.coder.clone(); + let btp_handle = self.btp_handle.clone(); + let coder = self.coder.clone(); let vehicle_data = self.vehicle_data.clone(); thread::spawn(move || { - let interval = Duration::from_millis(request.denm_interval_ms); - let end_time = std::time::Instant::now() - + Duration::from_millis(request.time_period_ms); + let interval = Duration::from_millis(request.denm_interval_ms); + let end_time = + std::time::Instant::now() + Duration::from_millis(request.time_period_ms); while std::time::Instant::now() < end_time { let seq_nr = next_sequence_number(); - let denm = build_denm(&request, &vehicle_data, seq_nr); + let denm = build_denm(&request, &vehicle_data, seq_nr); transmit_denm(&btp_handle, &coder, &denm, &request); thread::sleep(interval); } @@ -281,7 +271,7 @@ impl DENMTransmissionManagement { /// Transmit a single pre-built DENM immediately (fire-and-forget). pub fn send_single_denm(&self, request: &DENRequest) { let seq_nr = next_sequence_number(); - let denm = build_denm(request, &self.vehicle_data, seq_nr); + let denm = build_denm(request, &self.vehicle_data, seq_nr); transmit_denm(&self.btp_handle, &self.coder, &denm, request); } } @@ -290,19 +280,17 @@ impl DENMTransmissionManagement { fn transmit_denm( btp_handle: &BTPRouterHandle, - coder: &DenmCoder, - denm: &Denm, - request: &DENRequest, + coder: &DenmCoder, + denm: &Denm, + request: &DENRequest, ) { match coder.encode(denm) { Ok(data) => { // Event position in 1/10 µdeg for the GN area centre (Area uses u32). - let area_lat = - ((request.event_latitude * 1e7).round() as i32) - .clamp(-900_000_000, 900_000_000) as u32; - let area_lon = - ((request.event_longitude * 1e7).round() as i32) - .clamp(-1_800_000_000, 1_800_000_000) as u32; + let area_lat = ((request.event_latitude * 1e7).round() as i32) + .clamp(-900_000_000, 900_000_000) as u32; + let area_lon = ((request.event_longitude * 1e7).round() as i32) + .clamp(-1_800_000_000, 1_800_000_000) as u32; let req = BTPDataRequest { btp_type: CommonNH::BtpB, @@ -316,23 +304,31 @@ fn transmit_denm( ), }, gn_destination_address: GNAddress { - m: M::GnMulticast, - st: ST::Unknown, + m: M::GnMulticast, + st: ST::Unknown, mid: MID::new([0xFF; 6]), }, communication_profile: CommunicationProfile::Unspecified, gn_area: Area { - latitude: area_lat, + latitude: area_lat, longitude: area_lon, a: request.relevance_radius_m as u16, // semi-major axis (m) b: 0, angle: 0, }, traffic_class: TrafficClass { - scf: false, + scf: false, channel_offload: false, - tc_id: 0, + tc_id: 0, }, + security_profile: SecurityProfile::DecentralizedEnvironmentalNotificationMessage, + its_aid: 37, + security_permissions: vec![], + gn_max_hop_limit: 10, + gn_max_packet_lifetime: None, + gn_repetition_interval: None, + gn_max_repetition_time: None, + destination: None, length: data.len() as u16, data, }; diff --git a/src/facilities/decentralized_environmental_notification_service/mod.rs b/src/facilities/decentralized_environmental_notification_service/mod.rs index 35cc735..eccad19 100644 --- a/src/facilities/decentralized_environmental_notification_service/mod.rs +++ b/src/facilities/decentralized_environmental_notification_service/mod.rs @@ -48,7 +48,7 @@ pub mod denm_transmission; pub use denm_coder::{Denm, DenmCoder}; pub use denm_reception::DENMReceptionManagement; -pub use denm_transmission::{DENRequest, DENMTransmissionManagement, VehicleData}; +pub use denm_transmission::{DENMTransmissionManagement, DENRequest, VehicleData}; use crate::btp::router::BTPRouterHandle; use std::sync::mpsc::{self, Receiver, Sender}; @@ -64,7 +64,7 @@ use std::sync::mpsc::{self, Receiver, Sender}; pub struct DecentralizedEnvironmentalNotificationService { tx_management: DENMTransmissionManagement, /// Reception management writes decoded DENMs into this sender. - denm_tx: Sender, + denm_tx: Sender, } impl DecentralizedEnvironmentalNotificationService { @@ -73,23 +73,19 @@ impl DecentralizedEnvironmentalNotificationService { /// Returns `(service, denm_receiver)`. Hold `denm_receiver` to consume /// decoded incoming DENMs. The reception thread is started immediately /// (it registers BTP port 2002 right away). - pub fn new( - btp_handle: BTPRouterHandle, - vehicle_data: VehicleData, - ) -> (Self, Receiver) { + pub fn new(btp_handle: BTPRouterHandle, vehicle_data: VehicleData) -> (Self, Receiver) { let coder = DenmCoder::new(); let (denm_tx, denm_rx) = mpsc::channel::(); // Start reception immediately. DENMReceptionManagement::spawn(btp_handle.clone(), coder.clone(), denm_tx.clone()); - let tx_management = DENMTransmissionManagement::new( - btp_handle, - coder, - vehicle_data, - ); + let tx_management = DENMTransmissionManagement::new(btp_handle, coder, vehicle_data); - let svc = DecentralizedEnvironmentalNotificationService { tx_management, denm_tx }; + let svc = DecentralizedEnvironmentalNotificationService { + tx_management, + denm_tx, + }; (svc, denm_rx) } diff --git a/src/facilities/local_dynamic_map/if_ldm_3.rs b/src/facilities/local_dynamic_map/if_ldm_3.rs index f1a3e0d..93ef6b8 100644 --- a/src/facilities/local_dynamic_map/if_ldm_3.rs +++ b/src/facilities/local_dynamic_map/if_ldm_3.rs @@ -20,11 +20,9 @@ use std::sync::Arc; use crate::facilities::local_dynamic_map::ldm_service::LdmService; use crate::facilities::local_dynamic_map::ldm_types::{ - AddDataProviderReq, AddDataProviderResp, - DeleteDataProviderReq, DeleteDataProviderResp, - DeregisterDataProviderReq, DeregisterDataProviderResp, - RegisterDataProviderReq, RegisterDataProviderResp, - UpdateDataProviderReq, UpdateDataProviderResp, + AddDataProviderReq, AddDataProviderResp, DeleteDataProviderReq, DeleteDataProviderResp, + DeregisterDataProviderReq, DeregisterDataProviderResp, RegisterDataProviderReq, + RegisterDataProviderResp, UpdateDataProviderReq, UpdateDataProviderResp, }; /// ETSI IF.LDM.3 — Data Provider interface. diff --git a/src/facilities/local_dynamic_map/if_ldm_4.rs b/src/facilities/local_dynamic_map/if_ldm_4.rs index 7768652..09d592b 100644 --- a/src/facilities/local_dynamic_map/if_ldm_4.rs +++ b/src/facilities/local_dynamic_map/if_ldm_4.rs @@ -16,16 +16,15 @@ //! //! All methods are thin delegations to `LdmService`. -use std::sync::Arc; use std::sync::mpsc::{self, Receiver}; +use std::sync::Arc; use crate::facilities::local_dynamic_map::ldm_service::LdmService; use crate::facilities::local_dynamic_map::ldm_types::{ - DeregisterDataConsumerReq, DeregisterDataConsumerResp, - RegisterDataConsumerReq, RegisterDataConsumerResp, - RequestDataObjectsReq, RequestDataObjectsResp, - SubscribeDataObjectsReq, SubscribeDataObjectsResp, - UnsubscribeDataConsumerReq, UnsubscribeDataConsumerResp, + DeregisterDataConsumerReq, DeregisterDataConsumerResp, RegisterDataConsumerReq, + RegisterDataConsumerResp, RequestDataObjectsReq, RequestDataObjectsResp, + SubscribeDataObjectsReq, SubscribeDataObjectsResp, UnsubscribeDataConsumerReq, + UnsubscribeDataConsumerResp, }; /// ETSI IF.LDM.4 — Data Consumer interface. diff --git a/src/facilities/local_dynamic_map/ldm_constants.rs b/src/facilities/local_dynamic_map/ldm_constants.rs index 8a9a6f3..7feda98 100644 --- a/src/facilities/local_dynamic_map/ldm_constants.rs +++ b/src/facilities/local_dynamic_map/ldm_constants.rs @@ -16,22 +16,22 @@ /// ITS-S application identifiers (ETSI TS 102 965). pub const ITS_AID_DENM: u32 = 1; -pub const ITS_AID_CAM: u32 = 2; -pub const ITS_AID_POI: u32 = 3; +pub const ITS_AID_CAM: u32 = 2; +pub const ITS_AID_POI: u32 = 3; pub const ITS_AID_SAEM: u32 = 4; pub const ITS_AID_EEBL: u32 = 5; -pub const ITS_AID_IVI: u32 = 6; -pub const ITS_AID_TLC: u32 = 7; -pub const ITS_AID_GPC: u32 = 9; +pub const ITS_AID_IVI: u32 = 6; +pub const ITS_AID_TLC: u32 = 7; +pub const ITS_AID_GPC: u32 = 9; pub const ITS_AID_GNSS: u32 = 10; -pub const ITS_AID_TPG: u32 = 11; -pub const ITS_AID_CRL: u32 = 12; -pub const ITS_AID_CRT: u32 = 13; -pub const ITS_AID_SRM: u32 = 14; -pub const ITS_AID_SSM: u32 = 15; -pub const ITS_AID_VAM: u32 = 16; +pub const ITS_AID_TPG: u32 = 11; +pub const ITS_AID_CRL: u32 = 12; +pub const ITS_AID_CRT: u32 = 13; +pub const ITS_AID_SRM: u32 = 14; +pub const ITS_AID_SSM: u32 = 15; +pub const ITS_AID_VAM: u32 = 16; pub const ITS_AID_IMZM: u32 = 17; -pub const ITS_AID_PAM: u32 = 21; +pub const ITS_AID_PAM: u32 = 21; /// Milliseconds between UNIX epoch (1970-01-01) and the ITS epoch (2004-01-01). /// Used to convert `std::time::SystemTime` to ITS milliseconds. @@ -85,8 +85,7 @@ pub fn haversine_m(lat1_etsi: i32, lon1_etsi: i32, lat2_etsi: i32, lon2_etsi: i3 let dlat = lat2 - lat1; let dlon = ((lon2_etsi - lon1_etsi) as f64 * 1e-7).to_radians(); - let a = (dlat / 2.0).sin().powi(2) - + lat1.cos() * lat2.cos() * (dlon / 2.0).sin().powi(2); + let a = (dlat / 2.0).sin().powi(2) + lat1.cos() * lat2.cos() * (dlon / 2.0).sin().powi(2); let c = 2.0 * a.sqrt().asin(); EARTH_RADIUS_M * c diff --git a/src/facilities/local_dynamic_map/ldm_maintenance.rs b/src/facilities/local_dynamic_map/ldm_maintenance.rs index 4bc418e..9f79962 100644 --- a/src/facilities/local_dynamic_map/ldm_maintenance.rs +++ b/src/facilities/local_dynamic_map/ldm_maintenance.rs @@ -37,7 +37,7 @@ impl LdmMaintenance { /// each maintenance cycle. A zero or negative `area_radius_m` disables /// the spatial GC (only expiry-based GC runs). pub fn spawn( - service: Arc, + service: Arc, area_lat_etsi: i32, area_lon_etsi: i32, area_radius_m: f64, diff --git a/src/facilities/local_dynamic_map/ldm_service.rs b/src/facilities/local_dynamic_map/ldm_service.rs index 8eca74e..6272151 100644 --- a/src/facilities/local_dynamic_map/ldm_service.rs +++ b/src/facilities/local_dynamic_map/ldm_service.rs @@ -15,36 +15,34 @@ //! channel. use std::collections::HashSet; +use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex, RwLock}; use std::time::Instant; -use std::sync::mpsc::Sender; use crate::facilities::local_dynamic_map::ldm_constants::now_its_ms; use crate::facilities::local_dynamic_map::ldm_storage::{ItsDataObject, LdmStore, StoredRecord}; use crate::facilities::local_dynamic_map::ldm_types::{ - AddDataProviderReq, AddDataProviderResp, AddDataProviderResult, - ComparisonOperator, DataObjectEntry, - DeleteDataProviderReq, DeleteDataProviderResp, DeleteDataProviderResult, + AddDataProviderReq, AddDataProviderResp, AddDataProviderResult, ComparisonOperator, + DataObjectEntry, DeleteDataProviderReq, DeleteDataProviderResp, DeleteDataProviderResult, DeregisterDataConsumerAck, DeregisterDataConsumerReq, DeregisterDataConsumerResp, - DeregisterDataProviderAck, DeregisterDataProviderReq, DeregisterDataProviderResp, - Filter, FilterAttribute, - LogicalOperator, - RegisterDataConsumerReq, RegisterDataConsumerResp, RegisterDataConsumerResult, - RegisterDataProviderReq, RegisterDataProviderResp, RegisterDataProviderResult, - RequestDataObjectsReq, RequestDataObjectsResp, RequestedDataObjectsResult, - SubscribeDataObjectsReq, SubscribeDataObjectsResp, SubscribeDataObjectsResult, - UnsubscribeDataConsumerAck, UnsubscribeDataConsumerReq, UnsubscribeDataConsumerResp, - UpdateDataProviderReq, UpdateDataProviderResp, UpdateDataProviderResult, + DeregisterDataProviderAck, DeregisterDataProviderReq, DeregisterDataProviderResp, Filter, + FilterAttribute, LogicalOperator, RegisterDataConsumerReq, RegisterDataConsumerResp, + RegisterDataConsumerResult, RegisterDataProviderReq, RegisterDataProviderResp, + RegisterDataProviderResult, RequestDataObjectsReq, RequestDataObjectsResp, + RequestedDataObjectsResult, SubscribeDataObjectsReq, SubscribeDataObjectsResp, + SubscribeDataObjectsResult, UnsubscribeDataConsumerAck, UnsubscribeDataConsumerReq, + UnsubscribeDataConsumerResp, UpdateDataProviderReq, UpdateDataProviderResp, + UpdateDataProviderResult, }; // ─── Subscription entry (Option B — Sender<>) ──────────────────────────────── /// Internal state for one active subscription. struct SubscriptionEntry { - id: u64, - request: SubscribeDataObjectsReq, - tx: Sender, - last_notified: Instant, + id: u64, + request: SubscribeDataObjectsReq, + tx: Sender, + last_notified: Instant, } // ─── LdmService ────────────────────────────────────────────────────────────── @@ -72,10 +70,10 @@ impl LdmService { pub fn new(store: Arc>) -> Arc { Arc::new(LdmService { store, - providers: Mutex::new(HashSet::new()), - consumers: Mutex::new(HashSet::new()), + providers: Mutex::new(HashSet::new()), + consumers: Mutex::new(HashSet::new()), subscriptions: Mutex::new(Vec::new()), - next_sub_id: Mutex::new(1), + next_sub_id: Mutex::new(1), }) } @@ -83,10 +81,15 @@ impl LdmService { pub fn register_data_provider(&self, req: RegisterDataProviderReq) -> RegisterDataProviderResp { self.providers.lock().unwrap().insert(req.application_id); - RegisterDataProviderResp { result: RegisterDataProviderResult::Accepted } + RegisterDataProviderResp { + result: RegisterDataProviderResult::Accepted, + } } - pub fn deregister_data_provider(&self, req: DeregisterDataProviderReq) -> DeregisterDataProviderResp { + pub fn deregister_data_provider( + &self, + req: DeregisterDataProviderReq, + ) -> DeregisterDataProviderResp { let removed = self.providers.lock().unwrap().remove(&req.application_id); DeregisterDataProviderResp { ack: if removed { @@ -113,7 +116,7 @@ impl LdmService { req.data_object, ); AddDataProviderResp { - result: AddDataProviderResult::Succeed, + result: AddDataProviderResult::Succeed, record_id: Some(id), } } @@ -157,10 +160,15 @@ impl LdmService { pub fn register_data_consumer(&self, req: RegisterDataConsumerReq) -> RegisterDataConsumerResp { self.consumers.lock().unwrap().insert(req.application_id); - RegisterDataConsumerResp { result: RegisterDataConsumerResult::Accepted } + RegisterDataConsumerResp { + result: RegisterDataConsumerResult::Accepted, + } } - pub fn deregister_data_consumer(&self, req: DeregisterDataConsumerReq) -> DeregisterDataConsumerResp { + pub fn deregister_data_consumer( + &self, + req: DeregisterDataConsumerReq, + ) -> DeregisterDataConsumerResp { let removed = self.consumers.lock().unwrap().remove(&req.application_id); DeregisterDataConsumerResp { ack: if removed { @@ -202,7 +210,7 @@ impl LdmService { } RequestDataObjectsResp { - result: RequestedDataObjectsResult::Succeed, + result: RequestedDataObjectsResult::Succeed, data_objects: entries, } } @@ -212,7 +220,7 @@ impl LdmService { pub fn subscribe_data_consumer( &self, req: SubscribeDataObjectsReq, - tx: Sender, + tx: Sender, ) -> SubscribeDataObjectsResp { let id = { let mut counter = self.next_sub_id.lock().unwrap(); @@ -227,13 +235,16 @@ impl LdmService { last_notified: Instant::now(), }); SubscribeDataObjectsResp { - result: SubscribeDataObjectsResult::Successful, + result: SubscribeDataObjectsResult::Successful, subscription_id: Some(id), } } /// Cancel a subscription. - pub fn unsubscribe_data_consumer(&self, req: UnsubscribeDataConsumerReq) -> UnsubscribeDataConsumerResp { + pub fn unsubscribe_data_consumer( + &self, + req: UnsubscribeDataConsumerReq, + ) -> UnsubscribeDataConsumerResp { let mut subs = self.subscriptions.lock().unwrap(); let before = subs.len(); subs.retain(|s| s.id != req.subscription_id); @@ -277,7 +288,7 @@ impl LdmService { } let resp = RequestDataObjectsResp { - result: RequestedDataObjectsResult::Succeed, + result: RequestedDataObjectsResult::Succeed, data_objects: entries, }; @@ -302,21 +313,24 @@ fn filter_matches(record: &StoredRecord, filter: &Option) -> bool { let r1 = eval_statement(record, &f.stmt1); match (&f.logical, &f.stmt2) { (Some(LogicalOperator::And), Some(s2)) => r1 && eval_statement(record, s2), - (Some(LogicalOperator::Or), Some(s2)) => r1 || eval_statement(record, s2), - _ => r1, + (Some(LogicalOperator::Or), Some(s2)) => r1 || eval_statement(record, s2), + _ => r1, } } -fn eval_statement(record: &StoredRecord, stmt: &crate::facilities::local_dynamic_map::ldm_types::FilterStatement) -> bool { +fn eval_statement( + record: &StoredRecord, + stmt: &crate::facilities::local_dynamic_map::ldm_types::FilterStatement, +) -> bool { let val = attribute_value_record(record, &stmt.attribute); let ref_v = stmt.ref_value; match stmt.operator { - ComparisonOperator::Equal => val == ref_v, - ComparisonOperator::NotEqual => val != ref_v, - ComparisonOperator::GreaterThan => val > ref_v, - ComparisonOperator::LessThan => val < ref_v, + ComparisonOperator::Equal => val == ref_v, + ComparisonOperator::NotEqual => val != ref_v, + ComparisonOperator::GreaterThan => val > ref_v, + ComparisonOperator::LessThan => val < ref_v, ComparisonOperator::GreaterThanOrEqual => val >= ref_v, - ComparisonOperator::LessThanOrEqual => val <= ref_v, + ComparisonOperator::LessThanOrEqual => val <= ref_v, } } @@ -324,14 +338,14 @@ fn eval_statement(record: &StoredRecord, stmt: &crate::facilities::local_dynamic fn attribute_value_record(record: &StoredRecord, attr: &FilterAttribute) -> i64 { match attr { FilterAttribute::ApplicationId => record.application_id as i64, - FilterAttribute::Latitude => record.lat_etsi as i64, - FilterAttribute::Longitude => record.lon_etsi as i64, - FilterAttribute::Altitude => record.altitude_cm as i64, + FilterAttribute::Latitude => record.lat_etsi as i64, + FilterAttribute::Longitude => record.lon_etsi as i64, + FilterAttribute::Altitude => record.altitude_cm as i64, // Fields that require PDU inspection — extract best-effort. - FilterAttribute::StationType => extract_station_type(record), - FilterAttribute::StationId => extract_station_id(record), - FilterAttribute::Speed => 0, // not available at StoredRecord level - FilterAttribute::Heading => 0, + FilterAttribute::StationType => extract_station_type(record), + FilterAttribute::StationId => extract_station_id(record), + FilterAttribute::Speed => 0, // not available at StoredRecord level + FilterAttribute::Heading => 0, } } @@ -339,30 +353,26 @@ fn attribute_value_record(record: &StoredRecord, attr: &FilterAttribute) -> i64 fn attribute_value_entry(entry: &DataObjectEntry, attr: &FilterAttribute) -> i64 { match attr { FilterAttribute::ApplicationId => entry.application_id as i64, - FilterAttribute::Latitude => entry.lat_etsi as i64, - FilterAttribute::Longitude => entry.lon_etsi as i64, - FilterAttribute::Altitude => entry.altitude_cm as i64, - _ => 0, + FilterAttribute::Latitude => entry.lat_etsi as i64, + FilterAttribute::Longitude => entry.lon_etsi as i64, + FilterAttribute::Altitude => entry.altitude_cm as i64, + _ => 0, } } fn extract_station_id(record: &StoredRecord) -> i64 { match &record.data_object { - ItsDataObject::Cam(cam) => cam.header.station_id.0 as i64, + ItsDataObject::Cam(cam) => cam.header.station_id.0 as i64, ItsDataObject::Denm(denm) => denm.header.station_id.0 as i64, - ItsDataObject::Vam(vam) => vam.header.0.station_id.0 as i64, + ItsDataObject::Vam(vam) => vam.header.0.station_id.0 as i64, ItsDataObject::Unknown { .. } => 0, } } fn extract_station_type(record: &StoredRecord) -> i64 { match &record.data_object { - ItsDataObject::Cam(cam) => { - cam.cam.cam_parameters.basic_container.station_type.0 as i64 - } - ItsDataObject::Vam(vam) => { - vam.vam.vam_parameters.basic_container.station_type.0 as i64 - } + ItsDataObject::Cam(cam) => cam.cam.cam_parameters.basic_container.station_type.0 as i64, + ItsDataObject::Vam(vam) => vam.vam.vam_parameters.basic_container.station_type.0 as i64, _ => 0, } } @@ -390,51 +400,66 @@ fn extract_station_type(record: &StoredRecord) -> i64 { /// each `DataObjectEntry` owns an independent copy. This is done lazily /// only when a consumer issues a query. fn record_to_entry(record: &StoredRecord) -> DataObjectEntry { - use crate::facilities::local_dynamic_map::ldm_constants::{ITS_AID_CAM, ITS_AID_DENM, ITS_AID_VAM}; use crate::facilities::ca_basic_service::cam_coder::CamCoder; use crate::facilities::decentralized_environmental_notification_service::denm_coder::DenmCoder; + use crate::facilities::local_dynamic_map::ldm_constants::{ + ITS_AID_CAM, ITS_AID_DENM, ITS_AID_VAM, + }; use crate::facilities::vru_awareness_service::vam_coder::VamCoder; let data_object = match &record.data_object { - ItsDataObject::Cam(cam) => { - match CamCoder.encode(cam) { - Ok(bytes) => match CamCoder.decode(&bytes) { - Ok(c) => ItsDataObject::Cam(Box::new(c)), - Err(_) => ItsDataObject::Unknown { its_aid: ITS_AID_CAM, raw: bytes }, + ItsDataObject::Cam(cam) => match CamCoder.encode(cam) { + Ok(bytes) => match CamCoder.decode(&bytes) { + Ok(c) => ItsDataObject::Cam(Box::new(c)), + Err(_) => ItsDataObject::Unknown { + its_aid: ITS_AID_CAM, + raw: bytes, }, - Err(_) => ItsDataObject::Unknown { its_aid: ITS_AID_CAM, raw: vec![] }, - } - } - ItsDataObject::Denm(denm) => { - match DenmCoder.encode(denm) { - Ok(bytes) => match DenmCoder.decode(&bytes) { - Ok(d) => ItsDataObject::Denm(Box::new(d)), - Err(_) => ItsDataObject::Unknown { its_aid: ITS_AID_DENM, raw: bytes }, + }, + Err(_) => ItsDataObject::Unknown { + its_aid: ITS_AID_CAM, + raw: vec![], + }, + }, + ItsDataObject::Denm(denm) => match DenmCoder.encode(denm) { + Ok(bytes) => match DenmCoder.decode(&bytes) { + Ok(d) => ItsDataObject::Denm(Box::new(d)), + Err(_) => ItsDataObject::Unknown { + its_aid: ITS_AID_DENM, + raw: bytes, }, - Err(_) => ItsDataObject::Unknown { its_aid: ITS_AID_DENM, raw: vec![] }, - } - } - ItsDataObject::Vam(vam) => { - match VamCoder.encode(vam) { - Ok(bytes) => match VamCoder.decode(&bytes) { - Ok(v) => ItsDataObject::Vam(Box::new(v)), - Err(_) => ItsDataObject::Unknown { its_aid: ITS_AID_VAM, raw: bytes }, + }, + Err(_) => ItsDataObject::Unknown { + its_aid: ITS_AID_DENM, + raw: vec![], + }, + }, + ItsDataObject::Vam(vam) => match VamCoder.encode(vam) { + Ok(bytes) => match VamCoder.decode(&bytes) { + Ok(v) => ItsDataObject::Vam(Box::new(v)), + Err(_) => ItsDataObject::Unknown { + its_aid: ITS_AID_VAM, + raw: bytes, }, - Err(_) => ItsDataObject::Unknown { its_aid: ITS_AID_VAM, raw: vec![] }, - } - } - ItsDataObject::Unknown { its_aid, raw } => { - ItsDataObject::Unknown { its_aid: *its_aid, raw: raw.clone() } - } + }, + Err(_) => ItsDataObject::Unknown { + its_aid: ITS_AID_VAM, + raw: vec![], + }, + }, + ItsDataObject::Unknown { its_aid, raw } => ItsDataObject::Unknown { + its_aid: *its_aid, + raw: raw.clone(), + }, }; DataObjectEntry { - record_id: record.id, + record_id: record.id, application_id: record.application_id, - timestamp_its: record.timestamp_its_ms, - lat_etsi: record.lat_etsi, - lon_etsi: record.lon_etsi, - altitude_cm: record.altitude_cm, + timestamp_its: record.timestamp_its_ms, + lat_etsi: record.lat_etsi, + lon_etsi: record.lon_etsi, + altitude_cm: record.altitude_cm, data_object, } } diff --git a/src/facilities/local_dynamic_map/ldm_storage.rs b/src/facilities/local_dynamic_map/ldm_storage.rs index 21ee956..d813317 100644 --- a/src/facilities/local_dynamic_map/ldm_storage.rs +++ b/src/facilities/local_dynamic_map/ldm_storage.rs @@ -18,8 +18,8 @@ use crate::facilities::ca_basic_service::cam_coder::Cam; use crate::facilities::decentralized_environmental_notification_service::denm_coder::Denm; -use crate::facilities::vru_awareness_service::vam_coder::Vam; use crate::facilities::local_dynamic_map::ldm_constants::now_its_ms; +use crate::facilities::vru_awareness_service::vam_coder::Vam; use std::collections::HashMap; @@ -48,9 +48,9 @@ impl ItsDataObject { ITS_AID_CAM, ITS_AID_DENM, ITS_AID_VAM, }; match self { - ItsDataObject::Cam(_) => ITS_AID_CAM, - ItsDataObject::Denm(_) => ITS_AID_DENM, - ItsDataObject::Vam(_) => ITS_AID_VAM, + ItsDataObject::Cam(_) => ITS_AID_CAM, + ItsDataObject::Denm(_) => ITS_AID_DENM, + ItsDataObject::Vam(_) => ITS_AID_VAM, ItsDataObject::Unknown { its_aid, .. } => *its_aid, } } @@ -65,29 +65,28 @@ impl ItsDataObject { #[derive(Debug)] pub struct StoredRecord { /// LDM-assigned monotonic identifier. - pub id: u64, + pub id: u64, /// ITS-AID of the data provider. - pub application_id: u32, + pub application_id: u32, /// Time this record was inserted / last updated (ms since ITS epoch). pub timestamp_its_ms: u64, /// Validity window in seconds; record expires when /// `timestamp_its_ms + time_validity_s * 1000 < now`. - pub time_validity_s: u32, + pub time_validity_s: u32, /// Position latitude (ETSI × 1e7 integer units). - pub lat_etsi: i32, + pub lat_etsi: i32, /// Position longitude (ETSI × 1e7 integer units). - pub lon_etsi: i32, + pub lon_etsi: i32, /// Altitude in centimetres above WGS-84 ellipsoid. - pub altitude_cm: i32, + pub altitude_cm: i32, /// The ITS data object. - pub data_object: ItsDataObject, + pub data_object: ItsDataObject, } impl StoredRecord { /// Return `true` if this record has passed its validity horizon. pub fn is_expired(&self) -> bool { - let expiry_ms = self.timestamp_its_ms - + (self.time_validity_s as u64) * 1000; + let expiry_ms = self.timestamp_its_ms + (self.time_validity_s as u64) * 1000; now_its_ms() > expiry_ms } } @@ -116,26 +115,29 @@ impl LdmStore { /// Insert a new record and return its assigned ID. pub fn insert( &mut self, - application_id: u32, + application_id: u32, timestamp_its_ms: u64, - time_validity_s: u32, - lat_etsi: i32, - lon_etsi: i32, - altitude_cm: i32, - data_object: ItsDataObject, + time_validity_s: u32, + lat_etsi: i32, + lon_etsi: i32, + altitude_cm: i32, + data_object: ItsDataObject, ) -> u64 { let id = self.next_id; self.next_id += 1; - self.records.insert(id, StoredRecord { + self.records.insert( id, - application_id, - timestamp_its_ms, - time_validity_s, - lat_etsi, - lon_etsi, - altitude_cm, - data_object, - }); + StoredRecord { + id, + application_id, + timestamp_its_ms, + time_validity_s, + lat_etsi, + lon_etsi, + altitude_cm, + data_object, + }, + ); id } @@ -144,21 +146,21 @@ impl LdmStore { /// Returns `false` when `id` is not found. pub fn update( &mut self, - id: u64, + id: u64, timestamp_its_ms: u64, - time_validity_s: u32, - lat_etsi: i32, - lon_etsi: i32, - altitude_cm: i32, - data_object: ItsDataObject, + time_validity_s: u32, + lat_etsi: i32, + lon_etsi: i32, + altitude_cm: i32, + data_object: ItsDataObject, ) -> bool { if let Some(rec) = self.records.get_mut(&id) { rec.timestamp_its_ms = timestamp_its_ms; - rec.time_validity_s = time_validity_s; - rec.lat_etsi = lat_etsi; - rec.lon_etsi = lon_etsi; - rec.altitude_cm = altitude_cm; - rec.data_object = data_object; + rec.time_validity_s = time_validity_s; + rec.lat_etsi = lat_etsi; + rec.lon_etsi = lon_etsi; + rec.altitude_cm = altitude_cm; + rec.data_object = data_object; true } else { false @@ -172,7 +174,8 @@ impl LdmStore { /// Remove all expired records and return the count removed. pub fn remove_expired(&mut self) -> usize { - let expired: Vec = self.records + let expired: Vec = self + .records .values() .filter(|r| r.is_expired()) .map(|r| r.id) @@ -193,7 +196,8 @@ impl LdmStore { area_radius_m: f64, ) -> usize { use crate::facilities::local_dynamic_map::ldm_constants::haversine_m; - let out: Vec = self.records + let out: Vec = self + .records .values() .filter(|r| { haversine_m(r.lat_etsi, r.lon_etsi, area_lat_etsi, area_lon_etsi) > area_radius_m diff --git a/src/facilities/local_dynamic_map/ldm_types.rs b/src/facilities/local_dynamic_map/ldm_types.rs index 2cd1eda..f75dd9e 100644 --- a/src/facilities/local_dynamic_map/ldm_types.rs +++ b/src/facilities/local_dynamic_map/ldm_types.rs @@ -142,7 +142,7 @@ pub enum LogicalOperator { #[derive(Debug, Clone)] pub struct FilterStatement { pub attribute: FilterAttribute, - pub operator: ComparisonOperator, + pub operator: ComparisonOperator, /// Reference value; ETSI coordinates, speeds, etc. are all expressible as /// `i64` without loss. pub ref_value: i64, @@ -153,9 +153,9 @@ pub struct FilterStatement { /// If `logical` and `stmt2` are `None` only `stmt1` is evaluated. #[derive(Debug, Clone)] pub struct Filter { - pub stmt1: FilterStatement, + pub stmt1: FilterStatement, pub logical: Option, - pub stmt2: Option, + pub stmt2: Option, } // ─── Ordering ──────────────────────────────────────────────────────────────── @@ -205,19 +205,19 @@ pub struct DeregisterDataProviderResp { #[derive(Debug)] pub struct AddDataProviderReq { /// ITS-AID of the providing application. - pub application_id: u32, + pub application_id: u32, /// Timestamp in milliseconds since ITS epoch (2004-01-01). - pub timestamp_its: u64, + pub timestamp_its: u64, /// Latitude in ETSI × 1e7 integer units. - pub lat_etsi: i32, + pub lat_etsi: i32, /// Longitude in ETSI × 1e7 integer units. - pub lon_etsi: i32, + pub lon_etsi: i32, /// Altitude in centimetres above WGS-84 ellipsoid. - pub altitude_cm: i32, + pub altitude_cm: i32, /// How long (in whole seconds) this record is considered valid. pub time_validity_s: u32, /// The ITS data object to store. - pub data_object: ItsDataObject, + pub data_object: ItsDataObject, } /// Response to `add_provider_data`. @@ -232,20 +232,20 @@ pub struct AddDataProviderResp { #[derive(Debug)] pub struct UpdateDataProviderReq { /// LDM record identifier returned by a prior `add_provider_data` call. - pub record_id: u64, + pub record_id: u64, /// New timestamp in milliseconds since ITS epoch. - pub timestamp_its: u64, + pub timestamp_its: u64, /// Updated latitude (ETSI × 1e7). - pub lat_etsi: i32, + pub lat_etsi: i32, /// Updated longitude (ETSI × 1e7). - pub lon_etsi: i32, + pub lon_etsi: i32, /// Updated altitude in cm. - pub altitude_cm: i32, + pub altitude_cm: i32, /// Updated validity window in seconds. pub time_validity_s: u32, /// Replacement data object; must have the same `ItsDataObject` variant as /// the record being updated. - pub data_object: ItsDataObject, + pub data_object: ItsDataObject, } /// Response to `update_provider_data`. @@ -297,41 +297,41 @@ pub struct DeregisterDataConsumerResp { #[derive(Debug, Clone)] pub struct RequestDataObjectsReq { /// ITS-AID of the requesting application. - pub application_id: u32, + pub application_id: u32, /// ITS-AID values of the data types to retrieve (e.g. `[ITS_AID_CAM]`). /// An empty vector means "all types". pub data_object_types: Vec, /// Optional filter expression. - pub filter: Option, + pub filter: Option, /// Optional sort order; applied after filtering. - pub order: Option>, + pub order: Option>, /// Maximum number of records to return (`None` = unlimited). - pub max_results: Option, + pub max_results: Option, } /// A single result record returned inside `RequestDataObjectsResp`. #[derive(Debug)] pub struct DataObjectEntry { /// LDM-assigned record identifier. - pub record_id: u64, + pub record_id: u64, /// ITS-AID of the data provider. - pub application_id: u32, + pub application_id: u32, /// Timestamp of the record (ms since ITS epoch). - pub timestamp_its: u64, + pub timestamp_its: u64, /// Latitude of the record (ETSI × 1e7). - pub lat_etsi: i32, + pub lat_etsi: i32, /// Longitude of the record (ETSI × 1e7). - pub lon_etsi: i32, + pub lon_etsi: i32, /// Altitude of the record in cm. - pub altitude_cm: i32, + pub altitude_cm: i32, /// The stored data object. - pub data_object: ItsDataObject, + pub data_object: ItsDataObject, } /// Response to `request_data_objects`. #[derive(Debug)] pub struct RequestDataObjectsResp { - pub result: RequestedDataObjectsResult, + pub result: RequestedDataObjectsResult, pub data_objects: Vec, } @@ -339,21 +339,21 @@ pub struct RequestDataObjectsResp { #[derive(Debug, Clone)] pub struct SubscribeDataObjectsReq { /// ITS-AID of the subscribing application. - pub application_id: u32, + pub application_id: u32, /// ITS-AID values to subscribe to; empty = all types. - pub data_object_types: Vec, + pub data_object_types: Vec, /// Optional filter applied before delivering notifications. - pub filter: Option, + pub filter: Option, /// Minimum interval between notifications in milliseconds. pub notify_interval_ms: u64, /// Maximum number of records per notification. - pub max_results: Option, + pub max_results: Option, } /// Response to `subscribe_data_consumer`. #[derive(Debug)] pub struct SubscribeDataObjectsResp { - pub result: SubscribeDataObjectsResult, + pub result: SubscribeDataObjectsResult, /// Opaque subscription identifier; pass to `unsubscribe_data_consumer`. pub subscription_id: Option, } diff --git a/src/facilities/local_dynamic_map/mod.rs b/src/facilities/local_dynamic_map/mod.rs index e1347fc..2c59b77 100644 --- a/src/facilities/local_dynamic_map/mod.rs +++ b/src/facilities/local_dynamic_map/mod.rs @@ -104,7 +104,7 @@ impl LdmFacility { /// An `LdmHandle` (`Arc`) that can be shared freely across /// threads. pub fn new(area_lat_etsi: i32, area_lon_etsi: i32, area_radius_m: f64) -> LdmHandle { - let store = Arc::new(std::sync::RwLock::new(LdmStore::new())); + let store = Arc::new(std::sync::RwLock::new(LdmStore::new())); let service = LdmService::new(store); let if_ldm_3 = IfLdm3::new(service.clone()); diff --git a/src/facilities/location_service.rs b/src/facilities/location_service.rs index f099ebe..839a3c9 100644 --- a/src/facilities/location_service.rs +++ b/src/facilities/location_service.rs @@ -73,12 +73,12 @@ pub struct GpsFix { impl Default for GpsFix { fn default() -> Self { GpsFix { - latitude: 0.0, - longitude: 0.0, - altitude_m: 0.0, - speed_mps: 0.0, + latitude: 0.0, + longitude: 0.0, + altitude_m: 0.0, + speed_mps: 0.0, heading_deg: 0.0, - pai: false, + pai: false, } } } @@ -97,7 +97,9 @@ pub struct LocationService { impl LocationService { /// Create a new, empty `LocationService` with no subscribers. pub fn new() -> Self { - LocationService { senders: Vec::new() } + LocationService { + senders: Vec::new(), + } } /// Register a new subscriber. diff --git a/src/facilities/vru_awareness_service/mod.rs b/src/facilities/vru_awareness_service/mod.rs index ab14f24..83514cc 100644 --- a/src/facilities/vru_awareness_service/mod.rs +++ b/src/facilities/vru_awareness_service/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) -//! VRU Awareness Service — VRU Awareness Basic Service (ETSI TS 103 300-3). +//! VRU Awareness Service — VRU Awareness Basic Service (ETSI TS 103 300-3 V2.3.1). //! //! Implements VAM generation and reception on top of the BTP and //! GeoNetworking layers already present in this crate. @@ -58,10 +58,10 @@ use std::sync::mpsc::{self, Receiver, Sender}; /// Create with [`new`](Self::new), then call [`start`](Self::start) once the /// GPS channel wiring is ready. pub struct VruAwarenessService { - btp_handle: BTPRouterHandle, + btp_handle: BTPRouterHandle, device_data: DeviceData, /// Reception management writes decoded VAMs into this sender. - vam_tx: Sender, + vam_tx: Sender, } impl VruAwarenessService { @@ -70,12 +70,13 @@ impl VruAwarenessService { /// Returns `(service, vam_receiver)`. Hold `vam_receiver` to consume /// decoded incoming VAMs. Call [`start`](Self::start) with a GPS fix /// receiver to begin transmitting and receiving. - pub fn new( - btp_handle: BTPRouterHandle, - device_data: DeviceData, - ) -> (Self, Receiver) { + pub fn new(btp_handle: BTPRouterHandle, device_data: DeviceData) -> (Self, Receiver) { let (vam_tx, vam_rx) = mpsc::channel::(); - let svc = VruAwarenessService { btp_handle, device_data, vam_tx }; + let svc = VruAwarenessService { + btp_handle, + device_data, + vam_tx, + }; (svc, vam_rx) } diff --git a/src/facilities/vru_awareness_service/vam_bindings.rs b/src/facilities/vru_awareness_service/vam_bindings.rs index b0870bd..83823c8 100644 --- a/src/facilities/vru_awareness_service/vam_bindings.rs +++ b/src/facilities/vru_awareness_service/vam_bindings.rs @@ -7363,11 +7363,10 @@ pub mod vam_pdu_descriptions { DeltaTimeQuarterSecond, GeneralizedLanePosition, GenerationDeltaTime, HeadingChangeIndication, ItsPduHeader, LanePosition, LateralAcceleration, LongitudinalAcceleration, PathHistory, PathPredicted, SequenceOfSafeDistanceIndication, - SequenceOfTrajectoryInterceptionIndication, - Speed, StabilityChangeIndication, StationId, TrajectoryInterceptionIndication, - VerticalAcceleration, VruClusterInformation, VruDeviceUsage, VruEnvironment, - VruExteriorLights, VruMovementControl, VruProfileAndSubprofile, VruSizeClass, Wgs84Angle, - YawRate, + SequenceOfTrajectoryInterceptionIndication, Speed, StabilityChangeIndication, StationId, + TrajectoryInterceptionIndication, VerticalAcceleration, VruClusterInformation, + VruDeviceUsage, VruEnvironment, VruExteriorLights, VruMovementControl, + VruProfileAndSubprofile, VruSizeClass, Wgs84Angle, YawRate, }; use core::borrow::Borrow; use rasn::prelude::*; diff --git a/src/facilities/vru_awareness_service/vam_coder.rs b/src/facilities/vru_awareness_service/vam_coder.rs index 3f1068e..2e1c786 100644 --- a/src/facilities/vru_awareness_service/vam_coder.rs +++ b/src/facilities/vru_awareness_service/vam_coder.rs @@ -11,19 +11,18 @@ // ─── Re-exports from compiled ASN.1 bindings ───────────────────────────────── +pub use super::vam_bindings::etsi_its_cdd::{ + AccelerationConfidence, Altitude, AltitudeConfidence, AltitudeValue, BasicContainer, Curvature, + CurvatureCalculationMode, GenerationDeltaTime, ItsPduHeader, Latitude, Longitude, + LongitudinalAcceleration, LongitudinalAccelerationValue, MessageId, OrdinalNumber1B, + PositionConfidenceEllipse, ReferencePositionWithConfidence, SemiAxisLength, Speed, + SpeedConfidence, SpeedValue, StationId, TrafficParticipantType, Wgs84Angle, + Wgs84AngleConfidence, Wgs84AngleValue, +}; pub use super::vam_bindings::vam_pdu_descriptions::{ ItsPduHeaderVam, VamParameters, VruAwareness, VruHighFrequencyContainer, VruLowFrequencyContainer, VruMotionPredictionContainer, VAM, }; -pub use super::vam_bindings::etsi_its_cdd::{ - AccelerationConfidence, Altitude, AltitudeConfidence, AltitudeValue, - BasicContainer, Curvature, CurvatureCalculationMode, GenerationDeltaTime, - ItsPduHeader, Latitude, Longitude, LongitudinalAcceleration, - LongitudinalAccelerationValue, MessageId, OrdinalNumber1B, - PositionConfidenceEllipse, ReferencePositionWithConfidence, SemiAxisLength, - Speed, SpeedConfidence, SpeedValue, StationId, TrafficParticipantType, - Wgs84Angle, Wgs84AngleConfidence, Wgs84AngleValue, -}; // ─── GenerationDeltaTime helpers ───────────────────────────────────────────── @@ -91,7 +90,6 @@ impl VamCoder { /// UPER-decode a [`Vam`] PDU from bytes. pub fn decode(&self, bytes: &[u8]) -> Result { - rasn::uper::decode::(bytes) - .map_err(|e| format!("VAM UPER decode error: {e}")) + rasn::uper::decode::(bytes).map_err(|e| format!("VAM UPER decode error: {e}")) } } diff --git a/src/facilities/vru_awareness_service/vam_reception.rs b/src/facilities/vru_awareness_service/vam_reception.rs index 29ff481..d8b99ce 100644 --- a/src/facilities/vru_awareness_service/vam_reception.rs +++ b/src/facilities/vru_awareness_service/vam_reception.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) -//! VAM Reception Management. +//! VAM Reception Management — ETSI TS 103 300-3 V2.3.1 (2025-12). //! //! Mirrors `VAMReceptionManagement` in //! `flexstack/facilities/vru_awareness_service/vam_reception_management.py`. @@ -33,11 +33,7 @@ impl VAMReceptionManagement { /// /// The caller should hold the corresponding `Receiver` — typically /// returned from [`VruAwarenessService::new`]. - pub fn spawn( - btp_handle: BTPRouterHandle, - coder: VamCoder, - vam_tx: Sender, - ) { + pub fn spawn(btp_handle: BTPRouterHandle, coder: VamCoder, vam_tx: Sender) { // Create an internal BTPDataIndication channel and register it // on BTP port 2018 (VAM destination port per ETSI TS 103 300-3). let (ind_tx, ind_rx) = mpsc::channel::(); @@ -51,15 +47,27 @@ impl VAMReceptionManagement { "[VAM RX] station={} gen_dt={} lat={:.5} lon={:.5}", vam.header.0.station_id.0, vam.vam.generation_delta_time.0, - vam.vam.vam_parameters.basic_container.reference_position.latitude.0 as f64 / 1e7, - vam.vam.vam_parameters.basic_container.reference_position.longitude.0 as f64 / 1e7, + vam.vam + .vam_parameters + .basic_container + .reference_position + .latitude + .0 as f64 + / 1e7, + vam.vam + .vam_parameters + .basic_container + .reference_position + .longitude + .0 as f64 + / 1e7, ); // Forward to caller; if they dropped the receiver, stop quietly. if vam_tx.send(vam).is_err() { break; } } - Err(e) => eprintln!("[VAM RX] Decode error: {}", e), + Err(e) => eprintln!("[VAM RX] Decode error (clause 7): {}", e), } } eprintln!("[VAM RX] Thread exiting"); diff --git a/src/facilities/vru_awareness_service/vam_transmission.rs b/src/facilities/vru_awareness_service/vam_transmission.rs index c679fc1..5cde233 100644 --- a/src/facilities/vru_awareness_service/vam_transmission.rs +++ b/src/facilities/vru_awareness_service/vam_transmission.rs @@ -1,47 +1,75 @@ // SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) -//! VAM Transmission Management. +//! VAM Transmission Management — ETSI TS 103 300-3 V2.3.1 (2025-12). //! //! Mirrors `VAMTransmissionManagement` in //! `flexstack/facilities/vru_awareness_service/vam_transmission_management.py`. //! -//! [`VAMTransmissionManagement::spawn`] starts a background thread that: -//! 1. Waits for GPS fixes from a [`LocationService`] subscriber channel. -//! 2. Builds a VAM from the fix and the static device data. -//! 3. UPER-encodes the VAM via [`VamCoder`]. -//! 4. Sends a [`BTPDataRequest`] on port 2018 via the BTP router handle. -//! -//! The transmission rate is capped at [`T_GEN_VAM_MIN`] = 100 ms (10 Hz) and -//! floored at [`T_GEN_VAM_MAX`] = 5 000 ms (0.2 Hz) per ETSI TS 103 300-3 §6. +//! Key behavioural properties: +//! - Timer-based (T_CheckVamGen) architecture with condition evaluation (clause 6.4.1). +//! - Triggering conditions: elapsed time (T_GenVamMax), position (4 m), speed (0.5 m/s), +//! heading (4°). +//! - VRU Low-Frequency Container included on first VAM, then every ≥ 2 000 ms (clause 6.2). +//! - Security profile: VRU_AWARENESS_MESSAGE, ITS-AID: 638. +use super::vam_bindings::etsi_its_cdd::{ + VruProfileAndSubprofile, VruSubProfilePedestrian, +}; +use super::vam_bindings::vam_pdu_descriptions::VruLowFrequencyContainer; use super::vam_coder::{ - generation_delta_time_now, vam_header, AccelerationConfidence, Altitude, - AltitudeConfidence, AltitudeValue, BasicContainer, Latitude, Longitude, - LongitudinalAcceleration, LongitudinalAccelerationValue, - PositionConfidenceEllipse, ReferencePositionWithConfidence, SemiAxisLength, - Speed, SpeedConfidence, SpeedValue, TrafficParticipantType, Vam, VamCoder, - VamParameters, VruAwareness, VruHighFrequencyContainer, Wgs84Angle, - Wgs84AngleConfidence, Wgs84AngleValue, + generation_delta_time_now, vam_header, AccelerationConfidence, Altitude, AltitudeConfidence, + AltitudeValue, BasicContainer, Latitude, Longitude, LongitudinalAcceleration, + LongitudinalAccelerationValue, PositionConfidenceEllipse, ReferencePositionWithConfidence, + SemiAxisLength, Speed, SpeedConfidence, SpeedValue, TrafficParticipantType, Vam, VamCoder, + VamParameters, VruAwareness, VruHighFrequencyContainer, Wgs84Angle, Wgs84AngleConfidence, + Wgs84AngleValue, }; use crate::btp::router::BTPRouterHandle; use crate::btp::service_access_point::BTPDataRequest; use crate::facilities::location_service::GpsFix; use crate::geonet::gn_address::{GNAddress, M, MID, ST}; use crate::geonet::service_access_point::{ - Area, CommunicationProfile, CommonNH, HeaderSubType, HeaderType, PacketTransportType, + Area, CommonNH, CommunicationProfile, HeaderSubType, HeaderType, PacketTransportType, TopoBroadcastHST, TrafficClass, }; -use std::sync::mpsc::Receiver; +use crate::security::sn_sap::SecurityProfile; +use std::sync::mpsc::{Receiver, RecvTimeoutError}; use std::thread; use std::time::{Duration, Instant}; -// ─── Timing constants (ETSI TS 103 300-3 §6) ───────────────────────────────── +// ─── Timing constants (ETSI TS 103 300-3 V2.3.1 Table 16, clause 6.2) ─────── + +/// T_GenVamMin [ms]: minimum time between consecutive VAM generation events. +pub const T_GEN_VAM_MIN_MS: u64 = 100; +/// T_GenVamMax [ms]: maximum time between consecutive VAM generation events. +pub const T_GEN_VAM_MAX_MS: u64 = 5_000; +/// T_CheckVamGen [ms]: timer period for condition evaluation (≤ T_GenVamMin). +pub const T_CHECK_VAM_GEN_MS: u64 = T_GEN_VAM_MIN_MS; +/// T_GenVam_DCC [ms]: DCC-imposed minimum interval. +pub const T_GEN_VAM_DCC_MS: u64 = T_GEN_VAM_MIN_MS; +/// T_GenVam_LFMin [ms]: minimum interval between LF container inclusions. +pub const T_GEN_VAM_LF_MIN_MS: u64 = 2_000; -/// Minimum VAM generation interval: 100 ms (10 Hz maximum rate). -pub const T_GEN_VAM_MIN: Duration = Duration::from_millis(100); -/// Maximum VAM generation interval: 5 000 ms (0.2 Hz minimum rate). -pub const T_GEN_VAM_MAX: Duration = Duration::from_millis(5_000); +// ─── Triggering thresholds (Table 17, clause 6.4) ──────────────────────────── + +/// Minimum Euclidean position change to trigger a new VAM [m]. +const MIN_POSITION_CHANGE_M: f64 = 4.0; +/// Minimum ground-speed change to trigger a new VAM [m/s]. +const MIN_SPEED_CHANGE_MPS: f64 = 0.5; +/// Minimum heading-vector orientation change to trigger a new VAM [degrees]. +const MIN_HEADING_CHANGE_DEG: f64 = 4.0; + +// ─── Haversine ──────────────────────────────────────────────────────────────── + +fn haversine_m(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 { + const R: f64 = 6_371_000.0; + let dlat = (lat2 - lat1).to_radians(); + let dlon = (lon2 - lon1).to_radians(); + let a = (dlat / 2.0).sin().powi(2) + + lat1.to_radians().cos() * lat2.to_radians().cos() * (dlon / 2.0).sin().powi(2); + R * 2.0 * a.sqrt().atan2((1.0 - a).max(0.0).sqrt()) +} // ─── DeviceData ─────────────────────────────────────────────────────────────── @@ -68,7 +96,7 @@ impl Default for DeviceData { /// Sensible defaults — cyclist, station_id 0. fn default() -> Self { DeviceData { - station_id: 0, + station_id: 0, station_type: 2, // cyclist } } @@ -77,26 +105,24 @@ impl Default for DeviceData { // ─── VAM builder ────────────────────────────────────────────────────────────── /// Build a complete [`Vam`] from a GPS fix and static device data. -fn build_vam(fix: &GpsFix, dd: &DeviceData) -> Vam { +fn build_vam( + fix: &GpsFix, + dd: &DeviceData, + lf: Option, +) -> Vam { let gen_dt = generation_delta_time_now(); // ── BasicContainer ──────────────────────────────────────────────────────── let ref_pos = ReferencePositionWithConfidence::new( - // Latitude: 1/10 µdeg, −900 000 000 .. 900 000 001 (unavail) Latitude(((fix.latitude * 1e7).round() as i32).clamp(-900_000_000, 900_000_000)), - // Longitude: 1/10 µdeg, −1 800 000 000 .. 1 800 000 001 (unavail) Longitude(((fix.longitude * 1e7).round() as i32).clamp(-1_800_000_000, 1_800_000_000)), - // PositionConfidenceEllipse — all axes unavailable PositionConfidenceEllipse::new( - SemiAxisLength(4095), // semiMajorAxisLength unavailable - SemiAxisLength(4095), // semiMinorAxisLength unavailable - Wgs84AngleValue(3601), // semiMajorAxisOrientation unavailable + SemiAxisLength(4095), + SemiAxisLength(4095), + Wgs84AngleValue(3601), ), - // Altitude — value in 0.01 m, 800 001 = unavailable Altitude::new( - AltitudeValue( - ((fix.altitude_m * 100.0).round() as i32).clamp(-100_000, 800_000), - ), + AltitudeValue(((fix.altitude_m * 100.0).round() as i32).clamp(-100_000, 800_000)), AltitudeConfidence::unavailable, ), ); @@ -104,51 +130,34 @@ fn build_vam(fix: &GpsFix, dd: &DeviceData) -> Vam { let basic_container = BasicContainer::new(TrafficParticipantType(dd.station_type), ref_pos); // ── VruHighFrequencyContainer ───────────────────────────────────────────── - // Wgs84AngleValue: 0.1°, 0–3 600 valid, 3 601 = unavailable - // VAM uses Wgs84Angle for heading (not Heading like CAM) let heading_raw = ((fix.heading_deg * 10.0).round() as u16).clamp(0, 3600); let heading = Wgs84Angle::new( Wgs84AngleValue(heading_raw), - Wgs84AngleConfidence(127), // 127 = unavailable + Wgs84AngleConfidence(127), ); - // SpeedValue: 0.01 m/s, 0–16 382 valid, 16 383 = unavailable - // SpeedConfidence: 0.01 m/s, 1–125 valid, 127 = unavailable let speed = Speed::new( SpeedValue(((fix.speed_mps * 100.0).round() as u16).min(16_382)), - SpeedConfidence(127), // unavailable + SpeedConfidence(127), ); - // LongitudinalAcceleration — OLD CDD style (not AccelerationComponent) - // LongitudinalAccelerationValue: 0.1 m/s², −160..160 valid, 161 = unavailable - // AccelerationConfidence: 0..100 valid, 102 = unavailable let longitudinal_acceleration = LongitudinalAcceleration::new( - LongitudinalAccelerationValue(161), // unavailable - AccelerationConfidence(102), // unavailable + LongitudinalAccelerationValue(161), + AccelerationConfidence(102), ); let hf = VruHighFrequencyContainer::new( heading, speed, longitudinal_acceleration, - None, // curvature - None, // curvatureCalculationMode - None, // yawRate - None, // lateralAcceleration - None, // verticalAcceleration - None, // vruLanePosition - None, // environment - None, // movementControl - None, // orientation - None, // rollAngle - None, // deviceUsage + None, None, None, None, None, None, None, None, None, None, None, ); // ── Assemble VAM ────────────────────────────────────────────────────────── let vam_params = VamParameters::new( basic_container, hf, - None, // vruLowFrequencyContainer + lf, None, // vruClusterInformationContainer None, // vruClusterOperationContainer None, // vruMotionPredictionContainer @@ -160,49 +169,172 @@ fn build_vam(fix: &GpsFix, dd: &DeviceData) -> Vam { ) } +// ─── Dynamics check — clause 6.4.1 ────────────────────────────────────────── + +/// Return `true` if at least one triggering threshold is exceeded. +fn check_triggers( + fix: &GpsFix, + last_lat: Option, + last_lon: Option, + last_speed: Option, + last_heading: Option, +) -> bool { + // No reference — treat as changed + if last_lat.is_none() { + return true; + } + + // Position (Euclidean / haversine) + if let (Some(prev_lat), Some(prev_lon)) = (last_lat, last_lon) { + if haversine_m(prev_lat, prev_lon, fix.latitude, fix.longitude) > MIN_POSITION_CHANGE_M { + return true; + } + } + + // Speed + if let Some(prev_speed) = last_speed { + if (fix.speed_mps - prev_speed).abs() > MIN_SPEED_CHANGE_MPS { + return true; + } + } + + // Heading + if let Some(prev_heading) = last_heading { + let mut diff = (fix.heading_deg - prev_heading).abs(); + if diff > 180.0 { + diff = 360.0 - diff; + } + if diff > MIN_HEADING_CHANGE_DEG { + return true; + } + } + + false +} + +// ─── LF container builder ─────────────────────────────────────────────────── + +fn build_lf_container() -> VruLowFrequencyContainer { + VruLowFrequencyContainer::new( + VruProfileAndSubprofile::pedestrian(VruSubProfilePedestrian::unavailable), + None, // sizeClass + None, // exteriorLights + ) +} + // ─── VAMTransmissionManagement ─────────────────────────────────────────────── -/// VAM Transmission Management. +/// VAM Transmission Management — ETSI TS 103 300-3 V2.3.1 clause 6. /// -/// Mirrors `VAMTransmissionManagement` in -/// `flexstack/facilities/vru_awareness_service/vam_transmission_management.py`. +/// Timer-based (T_CheckVamGen) architecture. Evaluates triggering conditions +/// on every tick per clause 6.4.1. pub struct VAMTransmissionManagement; impl VAMTransmissionManagement { /// Spawn the transmission management thread. /// - /// # Arguments - /// * `btp_handle` — handle to the BTP router for sending VAMs. - /// * `coder` — shared [`VamCoder`] instance. - /// * `device_data` — static device parameters (station ID, type). - /// * `gps_rx` — GPS fix channel (from [`LocationService::subscribe`]). + /// Implements the T_CheckVamGen timer loop. The thread drains GPS fixes + /// from `gps_rx`, caching the latest, and evaluates VAM generation + /// conditions every `T_CHECK_VAM_GEN_MS` milliseconds. pub fn spawn( - btp_handle: BTPRouterHandle, - coder: VamCoder, + btp_handle: BTPRouterHandle, + coder: VamCoder, device_data: DeviceData, - gps_rx: Receiver, + gps_rx: Receiver, ) { thread::spawn(move || { - let mut last_sent: Option = None; + let mut t_gen_vam_ms: u64 = T_GEN_VAM_MIN_MS; + let _ = t_gen_vam_ms; // used for future DCC integration + + // State of last transmitted VAM + let mut last_vam_time: Option = None; + let mut last_vam_lat: Option = None; + let mut last_vam_lon: Option = None; + let mut last_vam_speed: Option = None; + let mut last_vam_heading: Option = None; + + // LF container timing + let mut last_lf_time: Option = None; + let mut is_first_vam = true; + + // Cached GPS fix + let mut current_fix: Option = None; + + loop { + // Drain GPS fixes for T_CHECK_VAM_GEN_MS, then evaluate conditions + let deadline = Instant::now() + Duration::from_millis(T_CHECK_VAM_GEN_MS); + loop { + let remaining = deadline.saturating_duration_since(Instant::now()); + if remaining.is_zero() { + break; + } + match gps_rx.recv_timeout(remaining) { + Ok(fix) => current_fix = Some(fix), + Err(RecvTimeoutError::Timeout) => break, + Err(RecvTimeoutError::Disconnected) => { + eprintln!("[VAM TX] GPS channel closed, thread exiting"); + return; + } + } + } + + let fix = match current_fix { + Some(ref f) => f.clone(), + None => continue, + }; - while let Ok(fix) = gps_rx.recv() { let now = Instant::now(); + let mut should_send = false; + + if last_vam_time.is_none() { + // First VAM after activation — send immediately + should_send = true; + } else { + let elapsed_ms = + now.duration_since(last_vam_time.unwrap()).as_millis() as u64; - // Enforce T_GEN_VAM_MIN — skip if called too quickly. - if let Some(last) = last_sent { - if now.duration_since(last) < T_GEN_VAM_MIN { - continue; + // Condition 1: elapsed ≥ T_GenVamMax + if elapsed_ms >= T_GEN_VAM_MAX_MS { + should_send = true; + } + // Conditions 2-4: dynamics changed AND elapsed ≥ T_GenVam_DCC + else if elapsed_ms >= T_GEN_VAM_DCC_MS + && check_triggers( + &fix, + last_vam_lat, + last_vam_lon, + last_vam_speed, + last_vam_heading, + ) + { + should_send = true; } } - let vam = build_vam(&fix, &device_data); + if !should_send { + continue; + } + + // Determine LF container inclusion (clause 6.2) + let include_lf = is_first_vam + || last_lf_time.is_none() + || now.duration_since(last_lf_time.unwrap()).as_millis() as u64 + >= T_GEN_VAM_LF_MIN_MS; + + let lf = if include_lf { + Some(build_lf_container()) + } else { + None + }; + + let vam = build_vam(&fix, &device_data, lf); match coder.encode(&vam) { Ok(data) => { let req = BTPDataRequest { btp_type: CommonNH::BtpB, source_port: 0, - destination_port: 2018, // BTP port for VAM + destination_port: 2018, destination_port_info: 0, gn_packet_transport_type: PacketTransportType { header_type: HeaderType::Tsb, @@ -211,33 +343,58 @@ impl VAMTransmissionManagement { ), }, gn_destination_address: GNAddress { - m: M::GnMulticast, - st: ST::Unknown, + m: M::GnMulticast, + st: ST::Unknown, mid: MID::new([0xFF; 6]), }, communication_profile: CommunicationProfile::Unspecified, gn_area: Area { - latitude: 0, + latitude: 0, longitude: 0, a: 0, b: 0, angle: 0, }, traffic_class: TrafficClass { - scf: false, + scf: false, channel_offload: false, - tc_id: 0, + tc_id: 0, }, + security_profile: SecurityProfile::VruAwarenessMessage, + its_aid: 638, + security_permissions: vec![], + gn_max_hop_limit: 1, + gn_max_packet_lifetime: None, + gn_repetition_interval: None, + gn_max_repetition_time: None, + destination: None, length: data.len() as u16, data, }; btp_handle.send_btp_data_request(req); - last_sent = Some(Instant::now()); + + eprintln!( + "[VAM TX] Sent VAM: station={}", + device_data.station_id, + ); + + // Update state + last_vam_time = Some(now); + last_vam_lat = Some(fix.latitude); + last_vam_lon = Some(fix.longitude); + last_vam_speed = Some(fix.speed_mps); + last_vam_heading = Some(fix.heading_deg); + + if include_lf { + last_lf_time = Some(now); + } + is_first_vam = false; + } + Err(e) => { + eprintln!("[VAM TX] Encode error: {}", e); } - Err(e) => eprintln!("[VAM TX] Encode error: {}", e), } } - eprintln!("[VAM TX] GPS channel closed, thread exiting"); }); } } diff --git a/src/geonet/basic_header.rs b/src/geonet/basic_header.rs index 7fceeb2..57e43ee 100644 --- a/src/geonet/basic_header.rs +++ b/src/geonet/basic_header.rs @@ -4,22 +4,22 @@ use super::mib::Mib; #[derive(Clone, Copy, Debug)] -pub enum BasicNH{ +pub enum BasicNH { Any, CommonHeader, SecuredPacket, } -impl BasicNH{ - pub fn encode(&self) -> u8{ - match self{ - BasicNH::Any => {0}, - BasicNH::CommonHeader => {1}, - BasicNH::SecuredPacket => {2}, +impl BasicNH { + pub fn encode(&self) -> u8 { + match self { + BasicNH::Any => 0, + BasicNH::CommonHeader => 1, + BasicNH::SecuredPacket => 2, } } - pub fn decode(value : u8) -> Self{ + pub fn decode(value: u8) -> Self { match value { 0 => BasicNH::Any, 1 => BasicNH::CommonHeader, @@ -30,15 +30,15 @@ impl BasicNH{ } #[derive(Clone, Copy, Debug)] -pub enum LTBase{ +pub enum LTBase { FiftyMilliseconds, OneSecond, TenSeconds, OneHundredSeconds, } -impl LTBase{ - pub fn decode(value : u8) -> Self{ +impl LTBase { + pub fn decode(value: u8) -> Self { match value { 0 => LTBase::FiftyMilliseconds, 1 => LTBase::OneSecond, @@ -48,107 +48,141 @@ impl LTBase{ } } - pub fn encode(&self) -> u8{ + pub fn encode(&self) -> u8 { match self { - LTBase::FiftyMilliseconds => {0}, - LTBase::OneSecond => {1}, - LTBase::TenSeconds => {2}, - LTBase::OneHundredSeconds => {3}, + LTBase::FiftyMilliseconds => 0, + LTBase::OneSecond => 1, + LTBase::TenSeconds => 2, + LTBase::OneHundredSeconds => 3, } } } #[derive(Clone, Copy, Debug)] -pub struct LT{ - multiplier : u8, - base : LTBase, +pub struct LT { + multiplier: u8, + base: LTBase, } -impl LT{ - pub fn start_in_milliseconds(value : u32) -> Self{ - if value >= 100000{ +impl LT { + pub fn start_in_milliseconds(value: u32) -> Self { + if value >= 100000 { return LT { - multiplier : (value / 100000) as u8, - base : LTBase::OneHundredSeconds, - } - }else if value >= 10000{ + multiplier: (value / 100000) as u8, + base: LTBase::OneHundredSeconds, + }; + } else if value >= 10000 { return LT { - multiplier : (value / 10000) as u8, - base : LTBase::TenSeconds, - } - }else if value >= 1000{ + multiplier: (value / 10000) as u8, + base: LTBase::TenSeconds, + }; + } else if value >= 1000 { return LT { - multiplier : (value / 1000) as u8, - base : LTBase::OneSecond, - } - }else if value >= 50{ + multiplier: (value / 1000) as u8, + base: LTBase::OneSecond, + }; + } else if value >= 50 { return LT { - multiplier : (value / 50) as u8, - base : LTBase::FiftyMilliseconds, - } - }else{ + multiplier: (value / 50) as u8, + base: LTBase::FiftyMilliseconds, + }; + } else { panic!("Invalid LT Value"); } } - pub fn start_in_seconds(value : u8) -> Self{ + pub fn start_in_seconds(value: u8) -> Self { LT::start_in_milliseconds(value as u32 * 1000) } - pub fn get_value_in_milliseconds(&self) -> u32{ - match self.base{ - LTBase::FiftyMilliseconds => {50 * self.multiplier as u32}, - LTBase::OneSecond => {1000 * self.multiplier as u32}, - LTBase::TenSeconds => {10000 * self.multiplier as u32}, - LTBase::OneHundredSeconds => {100000 * self.multiplier as u32}, + pub fn get_value_in_milliseconds(&self) -> u32 { + match self.base { + LTBase::FiftyMilliseconds => 50 * self.multiplier as u32, + LTBase::OneSecond => 1000 * self.multiplier as u32, + LTBase::TenSeconds => 10000 * self.multiplier as u32, + LTBase::OneHundredSeconds => 100000 * self.multiplier as u32, } } - pub fn get_value_in_seconds(&self) -> u8{ + pub fn get_value_in_seconds(&self) -> u8 { (self.get_value_in_milliseconds() / 1000) as u8 } - pub fn encode(&self) -> u8{ - self.multiplier << 2 | (self.base.encode() & 0x3) + pub fn encode(&self) -> u8 { + self.multiplier << 2 | (self.base.encode() & 0x3) } - pub fn decode(value : u8) -> Self{ - LT{ - multiplier : value >> 2, - base : LTBase::decode(value & 0x3), + pub fn decode(value: u8) -> Self { + LT { + multiplier: value >> 2, + base: LTBase::decode(value & 0x3), } } } #[derive(Clone, Copy, Debug)] -pub struct BasicHeader{ - pub version : u8, - pub nh : BasicNH, - pub reserved : u8, - pub lt : LT, - pub rhl : u8, - +pub struct BasicHeader { + pub version: u8, + pub nh: BasicNH, + pub reserved: u8, + pub lt: LT, + pub rhl: u8, } -impl BasicHeader{ - pub fn decode(bytes : [u8; 4]) -> Self{ - BasicHeader{ - version : bytes[0] >> 4, - nh : BasicNH::decode(bytes[0] & 0xF), - reserved : 0, - lt : LT::decode(bytes[2]), - rhl : bytes[3], +impl BasicHeader { + pub fn decode(bytes: [u8; 4]) -> Self { + BasicHeader { + version: bytes[0] >> 4, + nh: BasicNH::decode(bytes[0] & 0xF), + reserved: 0, + lt: LT::decode(bytes[2]), + rhl: bytes[3], + } + } + + pub fn initialize_with_mib(mib: &Mib) -> Self { + BasicHeader { + version: mib.itsGnProtocolVersion.clone(), + nh: BasicNH::CommonHeader, + rhl: mib.itsGnDefaultHopLimit.clone(), + reserved: 0, + lt: LT::start_in_seconds(mib.itsGnDefaultPacketLifetime.clone()), } } - pub fn initialize_with_mib(mib : &Mib) -> Self{ - BasicHeader{ - version : mib.itsGnProtocolVersion.clone(), - nh : BasicNH::CommonHeader, - rhl : mib.itsGnDefaultHopLimit.clone(), - reserved : 0, - lt : LT::start_in_seconds(mib.itsGnDefaultPacketLifetime.clone()) + /// Initialize from MIB with optional max_packet_lifetime and explicit RHL. + pub fn initialize_with_mib_request_and_rhl( + mib: &Mib, + max_packet_lifetime: Option, + rhl: u8, + ) -> Self { + let lt = if let Some(lifetime_secs) = max_packet_lifetime { + LT::start_in_milliseconds((lifetime_secs * 1000.0) as u32) + } else { + LT::start_in_seconds(mib.itsGnDefaultPacketLifetime) + }; + BasicHeader { + version: mib.itsGnProtocolVersion, + nh: BasicNH::CommonHeader, + reserved: 0, + lt, + rhl, } } - pub fn encode(&self) -> [u8; 4]{ - [self.version << 4 | self.nh.encode(), self.reserved, self.lt.encode(), self.rhl] + /// Return a copy with a different next-header value. + pub fn set_nh(self, nh: BasicNH) -> Self { + BasicHeader { nh, ..self } } -} \ No newline at end of file + + /// Return a copy with a different remaining hop limit. + pub fn set_rhl(self, rhl: u8) -> Self { + BasicHeader { rhl, ..self } + } + + pub fn encode(&self) -> [u8; 4] { + [ + self.version << 4 | self.nh.encode(), + self.reserved, + self.lt.encode(), + self.rhl, + ] + } +} diff --git a/src/geonet/common_header.rs b/src/geonet/common_header.rs index 9ace116..dab6de4 100644 --- a/src/geonet/common_header.rs +++ b/src/geonet/common_header.rs @@ -1,76 +1,98 @@ // SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) -use super::service_access_point::{CommonNH, HeaderType, HeaderSubType, TrafficClass, GNDataRequest}; +use super::mib::Mib; +use super::service_access_point::{ + CommonNH, GNDataRequest, HeaderSubType, HeaderType, TrafficClass, + UnspecifiedHST, +}; use std::cmp::PartialEq; #[derive(Debug)] -pub struct CommonHeader{ - pub nh : CommonNH, - pub reserved : u8, - pub ht : HeaderType, - pub hst : HeaderSubType, - pub tc : TrafficClass, - pub flags : u8, - pub pl : u16, - pub mhl : u8, - pub reserved2 : u8, +pub struct CommonHeader { + pub nh: CommonNH, + pub reserved: u8, + pub ht: HeaderType, + pub hst: HeaderSubType, + pub tc: TrafficClass, + pub flags: u8, + pub pl: u16, + pub mhl: u8, + pub reserved2: u8, } -impl CommonHeader{ - pub fn initialize_with_request(request : &GNDataRequest) -> Self{ - CommonHeader{ - nh : request.upper_protocol_entity.clone(), - reserved : 0, - ht : request.packet_transport_type.header_type.clone(), - hst : request.packet_transport_type.header_sub_type.clone(), - tc : request.traffic_class.clone(), - flags : 0, - pl : request.length.clone(), - mhl : 1, - reserved2 : 0, +impl CommonHeader { + pub fn initialize_with_request(request: &GNDataRequest, mib: &Mib) -> Self { + CommonHeader { + nh: request.upper_protocol_entity.clone(), + reserved: 0, + ht: request.packet_transport_type.header_type.clone(), + hst: request.packet_transport_type.header_sub_type.clone(), + tc: request.traffic_class.clone(), + flags: (mib.itsGnIsMobile.encode()) << 7, + pl: request.length.clone(), + mhl: request.max_hop_limit, + reserved2: 0, + } + } + + pub fn initialize_beacon(mib: &Mib) -> Self { + CommonHeader { + nh: CommonNH::Any, + reserved: 0, + ht: HeaderType::Beacon, + hst: HeaderSubType::Unspecified(UnspecifiedHST::Unspecified), + tc: TrafficClass { + scf: false, + channel_offload: false, + tc_id: 0, + }, + flags: (mib.itsGnIsMobile.encode()) << 7, + pl: 0, + mhl: 1, + reserved2: 0, } } pub fn encode(&self) -> [u8; 8] { - let mut bytes : [u8; 8] = [0; 8]; - bytes[0] = (self.nh.encode() << 4) | (self.reserved&0b0000_1111); - bytes[1] = (self.ht.encode()<<4) | (self.hst.encode()&0b0000_1111); + let mut bytes: [u8; 8] = [0; 8]; + bytes[0] = (self.nh.encode() << 4) | (self.reserved & 0b0000_1111); + bytes[1] = (self.ht.encode() << 4) | (self.hst.encode() & 0b0000_1111); bytes[2] = self.tc.encode(); bytes[3] = self.flags; - bytes[4] = (self.pl >> 8)as u8; + bytes[4] = (self.pl >> 8) as u8; bytes[5] = self.pl as u8; bytes[6] = self.mhl; bytes[7] = self.reserved2; bytes } - pub fn decode(bytes : [u8; 8]) -> Self { + pub fn decode(bytes: [u8; 8]) -> Self { let header_type: HeaderType = HeaderType::decode(bytes[1] >> 4); - CommonHeader{ - nh : CommonNH::decode(bytes[0] >> 4), - reserved : bytes[0] & 0b0000_1111, - ht : header_type.clone(), - hst : HeaderSubType::decode(&header_type, bytes[1] & 0b0000_1111), - tc : TrafficClass::decode(bytes[2]), - flags : bytes[3], - pl : ((bytes[4] as u16) << 8) | (bytes[5] as u16), - mhl : bytes[6], - reserved2 : bytes[7], + CommonHeader { + nh: CommonNH::decode(bytes[0] >> 4), + reserved: bytes[0] & 0b0000_1111, + ht: header_type.clone(), + hst: HeaderSubType::decode(&header_type, bytes[1] & 0b0000_1111), + tc: TrafficClass::decode(bytes[2]), + flags: bytes[3], + pl: ((bytes[4] as u16) << 8) | (bytes[5] as u16), + mhl: bytes[6], + reserved2: bytes[7], } } } -impl PartialEq for CommonHeader{ - fn eq(&self, other: &Self) -> bool{ - self.nh == other.nh && - self.reserved == other.reserved && - self.ht == other.ht && - self.hst == other.hst && - self.tc == other.tc && - self.flags == other.flags && - self.pl == other.pl && - self.mhl == other.mhl && - self.reserved2 == other.reserved2 +impl PartialEq for CommonHeader { + fn eq(&self, other: &Self) -> bool { + self.nh == other.nh + && self.reserved == other.reserved + && self.ht == other.ht + && self.hst == other.hst + && self.tc == other.tc + && self.flags == other.flags + && self.pl == other.pl + && self.mhl == other.mhl + && self.reserved2 == other.reserved2 } -} \ No newline at end of file +} diff --git a/src/geonet/gbc_extended_header.rs b/src/geonet/gbc_extended_header.rs index b1314dc..ee89eeb 100644 --- a/src/geonet/gbc_extended_header.rs +++ b/src/geonet/gbc_extended_header.rs @@ -5,35 +5,55 @@ use super::position_vector::LongPositionVector; use super::service_access_point::GNDataRequest; #[derive(Clone, PartialEq, Debug)] -pub struct GBCExtendedHeader{ - pub sn : u16, - pub reserved : u16, - pub so_pv : LongPositionVector, - pub latitude : u32, - pub longitude : u32, - pub a : u16, - pub b : u16, - pub angle : u16, - pub reserved2 : u16, +pub struct GBCExtendedHeader { + pub sn: u16, + pub reserved: u16, + pub so_pv: LongPositionVector, + pub latitude: u32, + pub longitude: u32, + pub a: u16, + pub b: u16, + pub angle: u16, + pub reserved2: u16, } -impl GBCExtendedHeader{ - pub fn initialize_with_request(request : GNDataRequest) -> Self{ - GBCExtendedHeader{ - sn : 0, - reserved : 0, - so_pv : LongPositionVector::decode([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]), - latitude : request.area.latitude.clone(), - longitude : request.area.longitude.clone(), - a : request.area.a.clone(), - b : request.area.b.clone(), - angle : request.area.angle.clone(), - reserved2 : 0, +impl GBCExtendedHeader { + pub fn initialize_with_request_sequence_number_ego_pv( + request: &GNDataRequest, + sequence_number: u16, + ego_pv: LongPositionVector, + ) -> Self { + GBCExtendedHeader { + sn: sequence_number, + reserved: 0, + so_pv: ego_pv, + latitude: request.area.latitude, + longitude: request.area.longitude, + a: request.area.a, + b: request.area.b, + angle: request.area.angle, + reserved2: 0, + } + } + + pub fn initialize_with_request(request: GNDataRequest) -> Self { + GBCExtendedHeader { + sn: 0, + reserved: 0, + so_pv: LongPositionVector::decode([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]), + latitude: request.area.latitude.clone(), + longitude: request.area.longitude.clone(), + a: request.area.a.clone(), + b: request.area.b.clone(), + angle: request.area.angle.clone(), + reserved2: 0, } } pub fn encode(&self) -> [u8; 44] { - let mut bytes : [u8; 44] = [0; 44]; + let mut bytes: [u8; 44] = [0; 44]; bytes[0] = (self.sn >> 8) as u8; bytes[1] = self.sn as u8; bytes[2] = (self.reserved >> 8) as u8; @@ -48,39 +68,39 @@ impl GBCExtendedHeader{ bytes } - pub fn decode(bytes : [u8; 44]) -> Self { - let mut sn : u16 = 0; + pub fn decode(bytes: [u8; 44]) -> Self { + let mut sn: u16 = 0; sn |= (bytes[0] as u16) << 8; sn |= bytes[1] as u16; - let mut reserved : u16 = 0; + let mut reserved: u16 = 0; reserved |= (bytes[2] as u16) << 8; reserved |= bytes[3] as u16; let so_pv = LongPositionVector::decode(bytes[4..28].try_into().unwrap()); - let mut latitude : u32 = 0; - for i in 0..4{ - latitude |= (bytes[28+i] as u32) << (8*(3-i)); + let mut latitude: u32 = 0; + for i in 0..4 { + latitude |= (bytes[28 + i] as u32) << (8 * (3 - i)); } - let mut longitude : u32 = 0; - for i in 0..4{ - longitude |= (bytes[32+i] as u32) << (8*(3-i)); + let mut longitude: u32 = 0; + for i in 0..4 { + longitude |= (bytes[32 + i] as u32) << (8 * (3 - i)); } - let mut a : u16 = 0; - for i in 0..2{ - a |= (bytes[36+i] as u16) << (8*(1-i)); + let mut a: u16 = 0; + for i in 0..2 { + a |= (bytes[36 + i] as u16) << (8 * (1 - i)); } - let mut b : u16 = 0; - for i in 0..2{ - b |= (bytes[38+i] as u16) << (8*(1-i)); + let mut b: u16 = 0; + for i in 0..2 { + b |= (bytes[38 + i] as u16) << (8 * (1 - i)); } - let mut angle : u16 = 0; - for i in 0..2{ - angle |= (bytes[40+i] as u16) << (8*(1-i)); + let mut angle: u16 = 0; + for i in 0..2 { + angle |= (bytes[40 + i] as u16) << (8 * (1 - i)); } - let mut reserved2 : u16 = 0; - for i in 0..2{ - reserved2 |= (bytes[42+i] as u16) << (8*(1-i)); + let mut reserved2: u16 = 0; + for i in 0..2 { + reserved2 |= (bytes[42 + i] as u16) << (8 * (1 - i)); } - GBCExtendedHeader{ + GBCExtendedHeader { sn, reserved, so_pv, @@ -92,4 +112,4 @@ impl GBCExtendedHeader{ reserved2, } } -} \ No newline at end of file +} diff --git a/src/geonet/gn_address.rs b/src/geonet/gn_address.rs index 275278a..855e7d4 100644 --- a/src/geonet/gn_address.rs +++ b/src/geonet/gn_address.rs @@ -4,24 +4,20 @@ use std::cmp::PartialEq; #[derive(Clone, Copy, Debug)] -pub enum M{ +pub enum M { GnUnicast, - GnMulticast + GnMulticast, } -impl M{ +impl M { pub fn encode_to_address(&self) -> u64 { match self { - M::GnUnicast => { - (1 << 7) << 8*7 - }, - M::GnMulticast => { - (0 << 7) << 8*7 - }, + M::GnUnicast => (1 << 7) << 8 * 7, + M::GnMulticast => (0 << 7) << 8 * 7, } } - pub fn decode_from_address(address: u64) -> Self{ + pub fn decode_from_address(address: u64) -> Self { // Bit 63 is the M (multicast/unicast) flag if (address >> 63) & 1 == 1 { M::GnUnicast @@ -32,7 +28,7 @@ impl M{ } #[derive(Clone, Copy, Debug)] -pub enum ST{ +pub enum ST { Unknown, Pedestrian, Cyclist, @@ -45,55 +41,29 @@ pub enum ST{ Trailer, SpecialVehicle, Tram, - RoadSideUnit + RoadSideUnit, } -impl ST{ - pub fn encode_to_address(&self) -> u64{ - match self{ - ST::Unknown => { - (0 << 3) << 8*7 - }, - ST::Pedestrian => { - (1 << 3) << 8*7 - }, - ST::Cyclist => { - (2 << 3) << 8*7 - }, - ST::Moped => { - (3 << 3) << 8*7 - }, - ST::Motorcycle => { - (4 << 3) << 8*7 - }, - ST::PassengerCar => { - (5 << 3) << 8*7 - }, - ST::Bus => { - (6 << 3) << 8*7 - }, - ST::LightTruck => { - (7 << 3) << 8*7 - }, - ST::HeavyTruck => { - (8 << 3) << 8*7 - }, - ST::Trailer => { - (9 << 3) << 8*7 - }, - ST::SpecialVehicle => { - (10 << 3) << 8*7 - }, - ST::Tram => { - (11 << 3) << 8*7 - }, - ST::RoadSideUnit => { - (12 << 3) << 8*7 - }, +impl ST { + pub fn encode_to_address(&self) -> u64 { + match self { + ST::Unknown => (0 << 3) << 8 * 7, + ST::Pedestrian => (1 << 3) << 8 * 7, + ST::Cyclist => (2 << 3) << 8 * 7, + ST::Moped => (3 << 3) << 8 * 7, + ST::Motorcycle => (4 << 3) << 8 * 7, + ST::PassengerCar => (5 << 3) << 8 * 7, + ST::Bus => (6 << 3) << 8 * 7, + ST::LightTruck => (7 << 3) << 8 * 7, + ST::HeavyTruck => (8 << 3) << 8 * 7, + ST::Trailer => (9 << 3) << 8 * 7, + ST::SpecialVehicle => (10 << 3) << 8 * 7, + ST::Tram => (11 << 3) << 8 * 7, + ST::RoadSideUnit => (12 << 3) << 8 * 7, } } - pub fn decode_from_address(address: u64) -> Self{ + pub fn decode_from_address(address: u64) -> Self { // Station type occupies bits 62-58 (bits 6-2 of the most significant byte) // Bit 63 is M (manual/derived), so ST starts at bit 62 match (address >> (8 * 7 + 2)) & 0x1F { @@ -117,78 +87,66 @@ impl ST{ #[derive(Clone, Copy, Debug)] -pub struct MID{ - mid : [u8; 6] +pub struct MID { + mid: [u8; 6], } -impl MID{ - pub fn new(mid: [u8; 6]) -> Self{ - MID{ - mid - } +impl MID { + pub fn new(mid: [u8; 6]) -> Self { + MID { mid } } - pub fn encode_to_address(&self) -> u64{ + pub fn encode_to_address(&self) -> u64 { let mut address: u64 = 0; - for i in 0..6{ - address |= (self.mid[i] as u64) << (8*(5-i)); + for i in 0..6 { + address |= (self.mid[i] as u64) << (8 * (5 - i)); } address } - pub fn decode_from_address(address: u64) -> Self{ + pub fn decode_from_address(address: u64) -> Self { let mut mid: [u8; 6] = [0; 6]; - for i in 0..6{ - mid[i] = (address >> (8*(5-i))) as u8; - } - MID{ - mid + for i in 0..6 { + mid[i] = (address >> (8 * (5 - i))) as u8; } + MID { mid } } } - #[derive(Clone, Copy, Debug)] -pub struct GNAddress{ +pub struct GNAddress { pub m: M, pub st: ST, pub mid: MID, } -impl GNAddress{ - pub fn new(m: M, st: ST, mid: MID) -> Self{ - GNAddress{ - m, - st, - mid, - } +impl GNAddress { + pub fn new(m: M, st: ST, mid: MID) -> Self { + GNAddress { m, st, mid } } - pub fn encode_to_int(&self) -> u64{ + pub fn encode_to_int(&self) -> u64 { self.m.encode_to_address() | self.st.encode_to_address() | self.mid.encode_to_address() } - pub fn encode(&self) -> [u8; 8]{ + pub fn encode(&self) -> [u8; 8] { self.encode_to_int().to_be_bytes() } - pub fn decode(data: &[u8]) -> Self{ - let as_number : u64 = u64::from_be_bytes([data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]]); - let m : M = M::decode_from_address(as_number); - let st : ST = ST::decode_from_address(as_number); - let mid : MID = MID::decode_from_address(as_number); - GNAddress{ - m, - st, - mid, - } - + pub fn decode(data: &[u8]) -> Self { + let as_number: u64 = u64::from_be_bytes([ + data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], + ]); + let m: M = M::decode_from_address(as_number); + let st: ST = ST::decode_from_address(as_number); + let mid: MID = MID::decode_from_address(as_number); + GNAddress { m, st, mid } } } -impl PartialEq for GNAddress{ - fn eq(&self, other: &Self) -> bool{ +impl PartialEq for GNAddress { + fn eq(&self, other: &Self) -> bool { self.encode_to_int() == other.encode_to_int() } -} \ No newline at end of file +} diff --git a/src/geonet/guc_extended_header.rs b/src/geonet/guc_extended_header.rs new file mode 100644 index 0000000..72dddac --- /dev/null +++ b/src/geonet/guc_extended_header.rs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) + +//! GUC Extended Header — ETSI EN 302 636-4-1 V1.4.1 (2020-01) §9.8.2 (Table 11). +//! +//! Layout (48 bytes): +//! SN 2 octets +//! Reserved 2 octets +//! SO PV 24 octets (Long Position Vector) +//! DE PV 20 octets (Short Position Vector) + +use super::position_vector::{LongPositionVector, ShortPositionVector}; + +#[derive(Clone, PartialEq, Debug)] +pub struct GUCExtendedHeader { + pub sn: u16, + pub reserved: u16, + pub so_pv: LongPositionVector, + pub de_pv: ShortPositionVector, +} + +impl GUCExtendedHeader { + pub fn initialize_with_sequence_number_ego_pv_de_pv( + sequence_number: u16, + ego_pv: LongPositionVector, + de_pv: ShortPositionVector, + ) -> Self { + GUCExtendedHeader { + sn: sequence_number, + reserved: 0, + so_pv: ego_pv, + de_pv, + } + } + + /// Return a copy with an updated DE PV (used by forwarder step 8). + pub fn with_de_pv(&self, de_pv: ShortPositionVector) -> Self { + GUCExtendedHeader { + sn: self.sn, + reserved: self.reserved, + so_pv: self.so_pv, + de_pv, + } + } + + pub fn encode(&self) -> [u8; 48] { + let mut bytes = [0u8; 48]; + bytes[0..2].copy_from_slice(&self.sn.to_be_bytes()); + bytes[2..4].copy_from_slice(&self.reserved.to_be_bytes()); + bytes[4..28].copy_from_slice(&self.so_pv.encode()); + bytes[28..48].copy_from_slice(&self.de_pv.encode()); + bytes + } + + pub fn decode(bytes: &[u8]) -> Self { + assert!(bytes.len() >= 48, "GUC Extended Header too short"); + let sn = u16::from_be_bytes([bytes[0], bytes[1]]); + let reserved = u16::from_be_bytes([bytes[2], bytes[3]]); + let so_pv = LongPositionVector::decode(bytes[4..28].try_into().unwrap()); + let de_pv = ShortPositionVector::decode(bytes[28..48].try_into().unwrap()); + GUCExtendedHeader { + sn, + reserved, + so_pv, + de_pv, + } + } +} diff --git a/src/geonet/location_table.rs b/src/geonet/location_table.rs index a1191ff..1e846a2 100644 --- a/src/geonet/location_table.rs +++ b/src/geonet/location_table.rs @@ -3,131 +3,140 @@ //! Location Table — stores position vectors and PDR for known GeoNetworking peers. //! -//! The [`LocationTable`] is the GN-equivalent of an ARP table: it maps a -//! [`GNAddress`] to a [`LocationTableEntry`] that carries the most recent -//! Long Position Vector (LPV), a neighbour flag, and a Duplicate Packet List -//! (DPL) used for duplicate-packet detection. +//! Implements §8.1 of ETSI EN 302 636-4-1 V1.4.1 (2020-01). +//! +//! Duplicate Packet Detection (DPD) uses **sequence-number-based** ring +//! buffers per source (Annex A.2). SHB/Beacon packets have no SN field +//! and must NOT be duplicate-checked via SN (§A.1). use std::collections::HashMap; +use std::collections::HashSet; use std::collections::VecDeque; use std::time::SystemTime; -use std::convert::TryInto; -use std::hash::{Hash, Hasher}; -use std::collections::hash_map::DefaultHasher; use super::gbc_extended_header::GBCExtendedHeader; use super::gn_address::GNAddress; +use super::guc_extended_header::GUCExtendedHeader; +use super::ls_extended_header::{LSReplyExtendedHeader, LSRequestExtendedHeader}; use super::mib::Mib; use super::position_vector::{LongPositionVector, Tst}; - -fn packet_hash(data: &[u8]) -> u64 { - let mut h = DefaultHasher::new(); - data.hash(&mut h); - h.finish() -} +use super::tsb_extended_header::TSBExtendedHeader; /// A single row in the Location Table. -/// -/// Holds the most recent LPV from a remote station, its neighbour status, -/// a Duplicate Packet List (DPL), and a smoothed Packet Data Rate estimate. -#[derive(Clone, PartialEq)] +#[derive(Clone)] pub struct LocationTableEntry { - /// Copy of the MIB used to read tunable parameters (e.g. DPL length, PDR beta). pub mib: Mib, - /// Most recent Long Position Vector received from this station. pub position_vector: LongPositionVector, - /// Whether a Location Service request is pending for this entry. pub ls_pending: bool, - /// `true` if the last received packet was a SHB (single-hop neighbour). pub is_neighbour: bool, - /// Duplicate Packet List — circular buffer of recently seen packet hashes. - pub dpl: VecDeque, - /// Timestamp of the last packet used for PDR estimation. + /// Duplicate Packet List — ring buffer of recently seen sequence numbers (Annex A.2). + pub dpl_deque: VecDeque, + /// O(1) SN lookup companion to `dpl_deque`. + pub dpl_set: HashSet, + /// Timestamp of the last PDR measurement. pub tst: Tst, - /// Smoothed Packet Data Rate in bytes/s (EMA). - pub pdr: u16, + /// Smoothed Packet Data Rate in bytes/s (EMA, Annex B.2). + pub pdr: f64, } impl LocationTableEntry { - /// Create a new, empty entry seeded from `mib`. pub fn new(mib: Mib) -> Self { LocationTableEntry { mib, position_vector: LongPositionVector::decode([0u8; 24]), ls_pending: false, is_neighbour: false, - dpl: VecDeque::new(), + dpl_deque: VecDeque::new(), + dpl_set: HashSet::new(), tst: Tst::set_in_normal_timestamp_milliseconds(0), - pdr: 0, + pdr: 0.0, } } - /// Update the stored LPV only if `position_vector` is strictly newer - /// (ETSI EN 302 636-4-1 Annex C.2). + /// Update the stored LPV only if strictly newer (§C.2). + /// When the stored TST is zero (initial entry), accepts unconditionally. pub fn update_position_vector(&mut self, position_vector: &LongPositionVector) { - if position_vector.tst > self.position_vector.tst { + if self.position_vector.tst == Tst::set_in_normal_timestamp_milliseconds(0) { + self.position_vector = *position_vector; + } else if position_vector.tst > self.position_vector.tst { self.position_vector = *position_vector; } } - /// Update the smoothed PDR estimate using the EMA formula: - /// - /// `PDR_new = β · PDR_old + (1 − β) · current_rate` - pub fn update_pdr(&mut self, position_vector: &LongPositionVector, packet_size: u16) { + /// Update the smoothed PDR estimate (EMA, Annex B.2). + pub fn update_pdr(&mut self, position_vector: &LongPositionVector, packet_size: u32) { let elapsed_ms = position_vector.tst - self.tst; self.tst = position_vector.tst; if elapsed_ms > 0 { - let current_pdr = (packet_size as u32 * 1000 / elapsed_ms as u32) as u16; - let beta = self.mib.itsGnMaxPacketDataRateEmaBeta as f32 / 100.0; - self.pdr = (beta * self.pdr as f32 + (1.0 - beta) * current_pdr as f32) as u16; + let time_since = elapsed_ms as f64 / 1000.0; + let current_pdr = packet_size as f64 / time_since; + let beta = self.mib.itsGnMaxPacketDataRateEmaBeta as f64 / 100.0; + self.pdr = beta * self.pdr + (1.0 - beta) * current_pdr; } } - /// Process a received SHB (TSB Single-Hop Broadcast) packet. + /// SN-based DPD per Annex A.2. Returns `true` if duplicate. /// - /// The first 24 bytes of `packet` must be the sender's Long Position Vector. - /// Returns `true` if the packet is new (not a duplicate). - pub fn update_with_shb_packet(&mut self, packet: &[u8]) -> bool { - let pv_bytes: [u8; 24] = packet[0..24].try_into().unwrap(); - let position_vector = LongPositionVector::decode(pv_bytes); - let is_new = self.check_duplicate_packet(packet); - self.update_position_vector(&position_vector); - self.update_pdr(&position_vector, (packet.len() + 12) as u16); + /// Only applicable to multi-hop packets (GUC, TSB, GBC, GAC, LS). + /// BEACON and SHB do NOT carry an SN field and must NOT call this. + pub fn check_duplicate_sn(&mut self, sn: u16) -> bool { + if self.dpl_set.contains(&sn) { + return true; + } + let max_len = self.mib.itsGnDPLLength as usize; + if self.dpl_deque.len() >= max_len { + if let Some(oldest) = self.dpl_deque.pop_front() { + self.dpl_set.remove(&oldest); + } + } + self.dpl_deque.push_back(sn); + self.dpl_set.insert(sn); + false + } + + /// Process a received SHB packet (§10.3.10.3 steps 4-6). + /// SHB has no SN — DPD does not apply (§A.1). + pub fn update_with_shb_packet( + &mut self, + position_vector: &LongPositionVector, + packet_size: u32, + ) { + self.update_position_vector(position_vector); + self.update_pdr(position_vector, packet_size); self.is_neighbour = true; - is_new } - /// Process a received GBC (Geo-Broadcast) packet. - /// - /// The LPV is taken from `gbc_extended_header`. - /// The entry is *not* marked as a direct neighbour. - /// Returns `true` if the packet is new (not a duplicate). + /// Process a received GBC packet (§10.3.11.3 steps 3-6). pub fn update_with_gbc_packet( &mut self, - packet: &[u8], gbc_extended_header: &GBCExtendedHeader, + packet_size: u32, ) -> bool { - let position_vector = gbc_extended_header.so_pv; - let is_new = self.check_duplicate_packet(packet); - self.update_position_vector(&position_vector); - self.update_pdr(&position_vector, (packet.len() + 12) as u16); + if self.check_duplicate_sn(gbc_extended_header.sn) { + return true; + } + self.update_position_vector(&gbc_extended_header.so_pv); + self.update_pdr(&gbc_extended_header.so_pv, packet_size); self.is_neighbour = false; - is_new + false } - /// Return `true` if `packet` has not been seen before and record its hash. - /// When the DPL is full the oldest entry is evicted (FIFO circular buffer). - pub fn check_duplicate_packet(&mut self, packet: &[u8]) -> bool { - let hash = packet_hash(packet); - if self.dpl.contains(&hash) { - return false; + /// Process a received TSB packet (§10.3.9.3 steps 3-6). + pub fn update_with_tsb_packet( + &mut self, + tsb_extended_header: &TSBExtendedHeader, + packet_size: u32, + is_new_entry: bool, + ) -> bool { + if self.check_duplicate_sn(tsb_extended_header.sn) { + return true; } - self.dpl.push_back(hash); - let max_len = self.mib.itsGnDPLLength as usize; - while self.dpl.len() > max_len { - self.dpl.pop_front(); + self.update_position_vector(&tsb_extended_header.so_pv); + self.update_pdr(&tsb_extended_header.so_pv, packet_size); + if is_new_entry { + self.is_neighbour = false; } - true + false } } @@ -135,20 +144,12 @@ impl LocationTableEntry { // LocationTable // ------------------------------------------------------------------ -/// The GeoNetworking Location Table. -/// -/// Stores one [`LocationTableEntry`] per remote GN address. Entries are -/// created lazily on first contact and expire after -/// `mib.itsGnLifetimeLocTE` seconds without an update. pub struct LocationTable { - /// MIB used when creating new entries. pub mib: Mib, - /// O(1) address → entry map keyed by the 64-bit encoded GN address. pub entries: HashMap, } impl LocationTable { - /// Create an empty location table. pub fn new(mib: Mib) -> Self { LocationTable { mib, @@ -156,13 +157,24 @@ impl LocationTable { } } - /// Look up a mutable reference to the entry for `gn_address`, or `None`. pub fn get_entry(&mut self, gn_address: &GNAddress) -> Option<&mut LocationTableEntry> { self.entries.get_mut(&gn_address.encode_to_int()) } - /// Remove all entries whose LPV timestamp is older than - /// `itsGnLifetimeLocTE` seconds. Uses `retain` to avoid index bugs. + pub fn get_entry_ref(&self, gn_address: &GNAddress) -> Option<&LocationTableEntry> { + self.entries.get(&gn_address.encode_to_int()) + } + + /// Get or create a LocTE without modifying fields (for Location Service). + pub fn ensure_entry(&mut self, gn_address: &GNAddress) -> &mut LocationTableEntry { + let key = gn_address.encode_to_int(); + let mib = self.mib; + self.entries + .entry(key) + .or_insert_with(|| LocationTableEntry::new(mib)) + } + + /// Remove expired entries (§8.1.3). pub fn refresh_table(&mut self) { let current_time = Tst::set_in_normal_timestamp_seconds( SystemTime::now() @@ -171,35 +183,166 @@ impl LocationTable { .as_secs(), ); let lifetime_ms = (self.mib.itsGnLifetimeLocTE as u32) * 1000; - self.entries.retain(|_, entry| { - (current_time - entry.position_vector.tst) <= lifetime_ms - }); + self.entries + .retain(|_, entry| (current_time - entry.position_vector.tst) <= lifetime_ms); } - /// Insert or update an entry for a newly received SHB packet. pub fn new_shb_packet(&mut self, position_vector: &LongPositionVector, packet: &[u8]) { let key = position_vector.gn_addr.encode_to_int(); let mib = self.mib; - let entry = self.entries.entry(key).or_insert_with(|| LocationTableEntry::new(mib)); - entry.update_with_shb_packet(packet); + let entry = self + .entries + .entry(key) + .or_insert_with(|| LocationTableEntry::new(mib)); + entry.update_with_shb_packet(position_vector, (packet.len() + 12) as u32); + self.refresh_table(); } - /// Insert or update an entry for a newly received GBC packet. - pub fn new_gbc_packet(&mut self, gbc_extended_header: &GBCExtendedHeader, packet: &[u8]) { + /// Returns `true` if the packet is a duplicate. + pub fn new_gbc_packet( + &mut self, + gbc_extended_header: &GBCExtendedHeader, + packet: &[u8], + ) -> bool { let key = gbc_extended_header.so_pv.gn_addr.encode_to_int(); let mib = self.mib; - let entry = self.entries.entry(key).or_insert_with(|| LocationTableEntry::new(mib)); - // Correctly call update_with_gbc_packet (was copy-paste bug calling update_with_shb_packet) - entry.update_with_gbc_packet(packet, gbc_extended_header); + let entry = self + .entries + .entry(key) + .or_insert_with(|| LocationTableEntry::new(mib)); + let dup = entry.update_with_gbc_packet(gbc_extended_header, (packet.len() + 12) as u32); + self.refresh_table(); + dup } - /// Return a snapshot of all current neighbour entries - /// (those for which the last received packet was a SHB). - pub fn get_neighbours(&self) -> Vec { - self.entries - .values() - .filter(|e| e.is_neighbour) - .cloned() - .collect() + /// Returns `true` if the packet is a duplicate. + pub fn new_guc_packet( + &mut self, + guc_extended_header: &GUCExtendedHeader, + packet: &[u8], + ) -> bool { + let so_pv = &guc_extended_header.so_pv; + let key = so_pv.gn_addr.encode_to_int(); + let mib = self.mib; + let is_new_entry = !self.entries.contains_key(&key); + let entry = self + .entries + .entry(key) + .or_insert_with(|| LocationTableEntry::new(mib)); + if entry.check_duplicate_sn(guc_extended_header.sn) { + return true; + } + entry.update_position_vector(so_pv); + entry.update_pdr(so_pv, (packet.len() + 12) as u32); + if is_new_entry { + entry.is_neighbour = false; + } + self.refresh_table(); + false + } + + /// Returns `true` if the packet is a duplicate. + pub fn new_tsb_packet( + &mut self, + tsb_extended_header: &TSBExtendedHeader, + packet: &[u8], + ) -> bool { + let key = tsb_extended_header.so_pv.gn_addr.encode_to_int(); + let mib = self.mib; + let is_new_entry = !self.entries.contains_key(&key); + let entry = self + .entries + .entry(key) + .or_insert_with(|| LocationTableEntry::new(mib)); + let dup = entry.update_with_tsb_packet( + tsb_extended_header, + (packet.len() + 12) as u32, + is_new_entry, + ); + self.refresh_table(); + dup + } + + /// Returns `true` if the packet is a duplicate. + pub fn new_gac_packet( + &mut self, + gbc_extended_header: &GBCExtendedHeader, + packet: &[u8], + ) -> bool { + // GAC and GBC share wire format. + let so_pv = &gbc_extended_header.so_pv; + let key = so_pv.gn_addr.encode_to_int(); + let mib = self.mib; + let is_new_entry = !self.entries.contains_key(&key); + let entry = self + .entries + .entry(key) + .or_insert_with(|| LocationTableEntry::new(mib)); + if entry.check_duplicate_sn(gbc_extended_header.sn) { + return true; + } + entry.update_position_vector(so_pv); + entry.update_pdr(so_pv, (packet.len() + 12) as u32); + if is_new_entry { + entry.is_neighbour = false; + } + self.refresh_table(); + false + } + + /// Returns `true` if the packet is a duplicate. + pub fn new_ls_request_packet( + &mut self, + ls_request_header: &LSRequestExtendedHeader, + packet: &[u8], + ) -> bool { + let so_pv = &ls_request_header.so_pv; + let key = so_pv.gn_addr.encode_to_int(); + let mib = self.mib; + let is_new_entry = !self.entries.contains_key(&key); + let entry = self + .entries + .entry(key) + .or_insert_with(|| LocationTableEntry::new(mib)); + if entry.check_duplicate_sn(ls_request_header.sn) { + return true; + } + entry.update_position_vector(so_pv); + entry.update_pdr(so_pv, (packet.len() + 12) as u32); + if is_new_entry { + entry.is_neighbour = false; + } + self.refresh_table(); + false + } + + /// Returns `true` if the packet is a duplicate. + pub fn new_ls_reply_packet( + &mut self, + ls_reply_header: &LSReplyExtendedHeader, + packet: &[u8], + ) -> bool { + let so_pv = &ls_reply_header.so_pv; + let key = so_pv.gn_addr.encode_to_int(); + let mib = self.mib; + let is_new_entry = !self.entries.contains_key(&key); + let entry = self + .entries + .entry(key) + .or_insert_with(|| LocationTableEntry::new(mib)); + if entry.check_duplicate_sn(ls_reply_header.sn) { + return true; + } + entry.update_position_vector(so_pv); + entry.update_pdr(so_pv, (packet.len() + 12) as u32); + if is_new_entry { + entry.is_neighbour = false; + } + self.refresh_table(); + false + } + + pub fn get_neighbours(&self) -> Vec<&LocationTableEntry> { + self.entries.values().filter(|e| e.is_neighbour).collect() } } diff --git a/src/geonet/ls_extended_header.rs b/src/geonet/ls_extended_header.rs new file mode 100644 index 0000000..efe4586 --- /dev/null +++ b/src/geonet/ls_extended_header.rs @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) + +//! LS Extended Headers — ETSI EN 302 636-4-1 V1.4.1 (2020-01). +//! +//! - [`LSRequestExtendedHeader`] — §9.8.7 Table 16, 36 bytes. +//! - [`LSReplyExtendedHeader`] — §9.8.8 Table 17, 48 bytes. + +use super::gn_address::GNAddress; +use super::position_vector::{LongPositionVector, ShortPositionVector}; + +// ── LS Request Extended Header ────────────────────────────────────────── + +/// LS Request Extended Header (36 bytes). +/// +/// Layout: +/// SN 2 octets +/// Reserved 2 octets +/// SO PV 24 octets (Long Position Vector) +/// Request GN_ADDR 8 octets +#[derive(Clone, PartialEq, Debug)] +pub struct LSRequestExtendedHeader { + pub sn: u16, + pub reserved: u16, + pub so_pv: LongPositionVector, + pub request_gn_addr: GNAddress, +} + +impl LSRequestExtendedHeader { + pub fn initialize( + sequence_number: u16, + ego_pv: LongPositionVector, + request_gn_addr: GNAddress, + ) -> Self { + LSRequestExtendedHeader { + sn: sequence_number, + reserved: 0, + so_pv: ego_pv, + request_gn_addr, + } + } + + pub fn encode(&self) -> [u8; 36] { + let mut bytes = [0u8; 36]; + bytes[0..2].copy_from_slice(&self.sn.to_be_bytes()); + bytes[2..4].copy_from_slice(&self.reserved.to_be_bytes()); + bytes[4..28].copy_from_slice(&self.so_pv.encode()); + bytes[28..36].copy_from_slice(&self.request_gn_addr.encode()); + bytes + } + + pub fn decode(bytes: &[u8]) -> Self { + assert!(bytes.len() >= 36, "LS Request Extended Header too short"); + let sn = u16::from_be_bytes([bytes[0], bytes[1]]); + let reserved = u16::from_be_bytes([bytes[2], bytes[3]]); + let so_pv = LongPositionVector::decode(bytes[4..28].try_into().unwrap()); + let request_gn_addr = GNAddress::decode(&bytes[28..36]); + LSRequestExtendedHeader { + sn, + reserved, + so_pv, + request_gn_addr, + } + } +} + +// ── LS Reply Extended Header ──────────────────────────────────────────── + +/// LS Reply Extended Header (48 bytes). +/// +/// Layout (identical to GUC Extended Header §9.8.2): +/// SN 2 octets +/// Reserved 2 octets +/// SO PV 24 octets (Long Position Vector — replier) +/// DE PV 20 octets (Short Position Vector — requester) +#[derive(Clone, PartialEq, Debug)] +pub struct LSReplyExtendedHeader { + pub sn: u16, + pub reserved: u16, + pub so_pv: LongPositionVector, + pub de_pv: ShortPositionVector, +} + +impl LSReplyExtendedHeader { + pub fn initialize( + sequence_number: u16, + ego_pv: LongPositionVector, + de_pv: ShortPositionVector, + ) -> Self { + LSReplyExtendedHeader { + sn: sequence_number, + reserved: 0, + so_pv: ego_pv, + de_pv, + } + } + + pub fn encode(&self) -> [u8; 48] { + let mut bytes = [0u8; 48]; + bytes[0..2].copy_from_slice(&self.sn.to_be_bytes()); + bytes[2..4].copy_from_slice(&self.reserved.to_be_bytes()); + bytes[4..28].copy_from_slice(&self.so_pv.encode()); + bytes[28..48].copy_from_slice(&self.de_pv.encode()); + bytes + } + + pub fn decode(bytes: &[u8]) -> Self { + assert!(bytes.len() >= 48, "LS Reply Extended Header too short"); + let sn = u16::from_be_bytes([bytes[0], bytes[1]]); + let reserved = u16::from_be_bytes([bytes[2], bytes[3]]); + let so_pv = LongPositionVector::decode(bytes[4..28].try_into().unwrap()); + let de_pv = ShortPositionVector::decode(bytes[28..48].try_into().unwrap()); + LSReplyExtendedHeader { + sn, + reserved, + so_pv, + de_pv, + } + } +} diff --git a/src/geonet/mib.rs b/src/geonet/mib.rs index 68862ce..36a2486 100644 --- a/src/geonet/mib.rs +++ b/src/geonet/mib.rs @@ -1,25 +1,25 @@ // SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) -use super::gn_address::{GNAddress, M, ST, MID}; +use super::gn_address::{GNAddress, M, MID, ST}; -#[derive(Clone, Copy,PartialEq)] -pub enum LocalGnAddrConfMethod{ +#[derive(Clone, Copy, PartialEq)] +pub enum LocalGnAddrConfMethod { Auto, Managed, Anonymous, } -impl LocalGnAddrConfMethod{ - pub fn encode(&self) -> u8{ - match self{ - LocalGnAddrConfMethod::Auto => {0}, - LocalGnAddrConfMethod::Managed => {1}, - LocalGnAddrConfMethod::Anonymous => {2}, +impl LocalGnAddrConfMethod { + pub fn encode(&self) -> u8 { + match self { + LocalGnAddrConfMethod::Auto => 0, + LocalGnAddrConfMethod::Managed => 1, + LocalGnAddrConfMethod::Anonymous => 2, } } - pub fn decode(value : u8) -> Self{ + pub fn decode(value: u8) -> Self { match value { 0 => LocalGnAddrConfMethod::Auto, 1 => LocalGnAddrConfMethod::Managed, @@ -29,21 +29,21 @@ impl LocalGnAddrConfMethod{ } } -#[derive(Clone, Copy,PartialEq)] -pub enum GnIsMobile{ +#[derive(Clone, Copy, PartialEq)] +pub enum GnIsMobile { Stationary, Mobile, } -impl GnIsMobile{ - pub fn encode(&self) -> u8{ - match self{ - GnIsMobile::Stationary => {0}, - GnIsMobile::Mobile => {1}, +impl GnIsMobile { + pub fn encode(&self) -> u8 { + match self { + GnIsMobile::Stationary => 0, + GnIsMobile::Mobile => 1, } } - pub fn decode(value : u8) -> Self{ + pub fn decode(value: u8) -> Self { match value { 0 => GnIsMobile::Stationary, 1 => GnIsMobile::Mobile, @@ -52,23 +52,23 @@ impl GnIsMobile{ } } -#[derive(Clone, Copy,PartialEq)] -pub enum GnIfType{ +#[derive(Clone, Copy, PartialEq)] +pub enum GnIfType { Unspecified, ItsG5, LteV2x, } -impl GnIfType{ - pub fn encode(&self) -> u8{ - match self{ - GnIfType::Unspecified => {0}, - GnIfType::ItsG5 => {1}, - GnIfType::LteV2x => {2}, +impl GnIfType { + pub fn encode(&self) -> u8 { + match self { + GnIfType::Unspecified => 0, + GnIfType::ItsG5 => 1, + GnIfType::LteV2x => 2, } } - pub fn decode(value : u8) -> Self{ + pub fn decode(value: u8) -> Self { match value { 0 => GnIfType::Unspecified, 1 => GnIfType::ItsG5, @@ -78,21 +78,21 @@ impl GnIfType{ } } -#[derive(Clone, Copy,PartialEq)] -pub enum GnSecurity{ +#[derive(Clone, Copy, PartialEq)] +pub enum GnSecurity { Disabled, Enabled, } -impl GnSecurity{ - pub fn encode(&self) -> u8{ - match self{ - GnSecurity::Disabled => {0}, - GnSecurity::Enabled => {1}, +impl GnSecurity { + pub fn encode(&self) -> u8 { + match self { + GnSecurity::Disabled => 0, + GnSecurity::Enabled => 1, } } - pub fn decode(value : u8) -> Self{ + pub fn decode(value: u8) -> Self { match value { 0 => GnSecurity::Disabled, 1 => GnSecurity::Enabled, @@ -101,21 +101,21 @@ impl GnSecurity{ } } -#[derive(Clone, Copy,PartialEq)] -pub enum SnDecapResultHandling{ +#[derive(Clone, Copy, PartialEq)] +pub enum SnDecapResultHandling { Strict, NonStrict, } -impl SnDecapResultHandling{ - pub fn encode(&self) -> u8{ - match self{ - SnDecapResultHandling::Strict => {0}, - SnDecapResultHandling::NonStrict => {1}, +impl SnDecapResultHandling { + pub fn encode(&self) -> u8 { + match self { + SnDecapResultHandling::Strict => 0, + SnDecapResultHandling::NonStrict => 1, } } - pub fn decode(value : u8) -> Self{ + pub fn decode(value: u8) -> Self { match value { 0 => SnDecapResultHandling::Strict, 1 => SnDecapResultHandling::NonStrict, @@ -124,21 +124,21 @@ impl SnDecapResultHandling{ } } -#[derive(Clone, Copy,PartialEq)] -pub enum NonAreaForwardingAlgorithm{ +#[derive(Clone, Copy, PartialEq)] +pub enum NonAreaForwardingAlgorithm { Unspecified, Greedy, } -impl NonAreaForwardingAlgorithm{ - pub fn encode(&self) -> u8{ - match self{ - NonAreaForwardingAlgorithm::Unspecified => {0}, - NonAreaForwardingAlgorithm::Greedy => {1}, +impl NonAreaForwardingAlgorithm { + pub fn encode(&self) -> u8 { + match self { + NonAreaForwardingAlgorithm::Unspecified => 0, + NonAreaForwardingAlgorithm::Greedy => 1, } } - pub fn decode(value : u8) -> Self{ + pub fn decode(value: u8) -> Self { match value { 0 => NonAreaForwardingAlgorithm::Unspecified, 1 => NonAreaForwardingAlgorithm::Greedy, @@ -147,23 +147,23 @@ impl NonAreaForwardingAlgorithm{ } } -#[derive(Clone, Copy,PartialEq)] -pub enum AreaForwardingAlgorithm{ +#[derive(Clone, Copy, PartialEq)] +pub enum AreaForwardingAlgorithm { Unspecified, Simple, Cbf, } -impl AreaForwardingAlgorithm{ - pub fn encode(&self) -> u8{ - match self{ - AreaForwardingAlgorithm::Unspecified => {0}, - AreaForwardingAlgorithm::Simple => {1}, - AreaForwardingAlgorithm::Cbf => {2}, +impl AreaForwardingAlgorithm { + pub fn encode(&self) -> u8 { + match self { + AreaForwardingAlgorithm::Unspecified => 0, + AreaForwardingAlgorithm::Simple => 1, + AreaForwardingAlgorithm::Cbf => 2, } } - pub fn decode(value : u8) -> Self{ + pub fn decode(value: u8) -> Self { match value { 0 => AreaForwardingAlgorithm::Unspecified, 1 => AreaForwardingAlgorithm::Simple, @@ -174,84 +174,88 @@ impl AreaForwardingAlgorithm{ } #[derive(Clone, Copy, PartialEq)] -pub struct Mib{ - pub itsGnLocalGnAddr : GNAddress, - pub itsGnLocalGnAddrConfMethod : LocalGnAddrConfMethod, - pub itsGnProtocolVersion : u8, - pub itsGnIsMobile : GnIsMobile, - pub itsGnIfType : GnIfType, - pub itsGnMinUpdateFrequencyEPV : u16, - pub itsGnPaiInterval : u8, - pub itsGnMaxSduSize : u16, - pub itsGnMaxGeoNetworkingHeaderSize : u16, - pub itsGnLifetimeLocTE : u16, - pub itsGnSecurity : GnSecurity, - pub itsGnSnDecapResultHandling : SnDecapResultHandling, - pub itsGnLocationServiceMaxRetrans : u8, - pub itsGnLocationServiceRetransmitTimer : u16, - pub itsGnLocationServicePacketBufferSize : u16, - pub itsGnBeaconServiceRetransmitTimer : u16, - pub itsGnBeaconServiceMaxJitter : u16, - pub itsGnDefaultHopLimit : u8, - pub itsGnDPLLength : u8, - pub itsGnMaxPacketLifetime : u16, - pub itsGnDefaultPacketLifetime : u8, - pub itsGnMaxPacketDataRate : u32, - pub itsGnMaxPacketDataRateEmaBeta : u8, - pub itsGnMaxGeoAreaSize : u16, - pub itsGnMinPacketRepetitionInterval : u16, - pub itsGnNonAreaForwardingAlgorithm : NonAreaForwardingAlgorithm, - pub itsGnAreaForwardingAlgorithm : AreaForwardingAlgorithm, - pub itsGnCbfMinTime : u16, - pub itsGnCbfMaxTime : u16, - pub itsGnDefaultMaxCommunicationRange : u16, - pub itsGnBroadcastCBFDefSectorAngle : u8, - pub itsGnUcForwardingPacketBufferSize : u16, - pub itsGnBcForwardingPacketBufferSize : u16, - pub itsGnCbfPacketBufferSize : u16, - pub itsGnDefaultTrafficClass : u8, +pub struct Mib { + pub itsGnLocalGnAddr: GNAddress, + pub itsGnLocalGnAddrConfMethod: LocalGnAddrConfMethod, + pub itsGnProtocolVersion: u8, + pub itsGnIsMobile: GnIsMobile, + pub itsGnIfType: GnIfType, + pub itsGnMinUpdateFrequencyEPV: u16, + pub itsGnPaiInterval: u8, + pub itsGnMaxSduSize: u16, + pub itsGnMaxGeoNetworkingHeaderSize: u16, + pub itsGnLifetimeLocTE: u16, + pub itsGnSecurity: GnSecurity, + pub itsGnSnDecapResultHandling: SnDecapResultHandling, + pub itsGnLocationServiceMaxRetrans: u8, + pub itsGnLocationServiceRetransmitTimer: u16, + pub itsGnLocationServicePacketBufferSize: u16, + pub itsGnBeaconServiceRetransmitTimer: u16, + pub itsGnBeaconServiceMaxJitter: u16, + pub itsGnDefaultHopLimit: u8, + pub itsGnDPLLength: u8, + pub itsGnMaxPacketLifetime: u16, + pub itsGnDefaultPacketLifetime: u8, + pub itsGnMaxPacketDataRate: u32, + pub itsGnMaxPacketDataRateEmaBeta: u8, + pub itsGnMaxGeoAreaSize: u16, + pub itsGnMinPacketRepetitionInterval: u16, + pub itsGnNonAreaForwardingAlgorithm: NonAreaForwardingAlgorithm, + pub itsGnAreaForwardingAlgorithm: AreaForwardingAlgorithm, + pub itsGnCbfMinTime: u16, + pub itsGnCbfMaxTime: u16, + pub itsGnDefaultMaxCommunicationRange: u16, + pub itsGnBroadcastCBFDefSectorAngle: u8, + pub itsGnUcForwardingPacketBufferSize: u16, + pub itsGnBcForwardingPacketBufferSize: u16, + pub itsGnCbfPacketBufferSize: u16, + pub itsGnDefaultTrafficClass: u8, } -impl Mib{ - pub fn new() -> Self{ +impl Mib { + pub fn new() -> Self { { - Mib{ - itsGnLocalGnAddr : GNAddress::new(M::GnUnicast, ST::Unknown, MID::new([0,0,0,0,0,0])), - itsGnLocalGnAddrConfMethod : LocalGnAddrConfMethod::Auto, - itsGnProtocolVersion : 1, - itsGnIsMobile : GnIsMobile::Mobile, - itsGnIfType : GnIfType::Unspecified, - itsGnMinUpdateFrequencyEPV : 1000, - itsGnPaiInterval : 80, - itsGnMaxSduSize : 1398, - itsGnMaxGeoNetworkingHeaderSize : 88, - itsGnLifetimeLocTE : 20, - itsGnSecurity : GnSecurity::Disabled, - itsGnSnDecapResultHandling : SnDecapResultHandling::Strict, - itsGnLocationServiceMaxRetrans : 10, - itsGnLocationServiceRetransmitTimer : 1000, - itsGnLocationServicePacketBufferSize : 1024, - itsGnBeaconServiceRetransmitTimer : 3000, - itsGnBeaconServiceMaxJitter : 3000/4, - itsGnDefaultHopLimit : 10, - itsGnDPLLength : 8, - itsGnMaxPacketLifetime : 600, - itsGnDefaultPacketLifetime : 60, - itsGnMaxPacketDataRate : 100, - itsGnMaxPacketDataRateEmaBeta : 90, - itsGnMaxGeoAreaSize : 10, - itsGnMinPacketRepetitionInterval : 100, - itsGnNonAreaForwardingAlgorithm : NonAreaForwardingAlgorithm::Greedy, - itsGnAreaForwardingAlgorithm : AreaForwardingAlgorithm::Cbf, - itsGnCbfMinTime : 1, - itsGnCbfMaxTime : 100, - itsGnDefaultMaxCommunicationRange : 1000, - itsGnBroadcastCBFDefSectorAngle : 30, - itsGnUcForwardingPacketBufferSize : 256, - itsGnBcForwardingPacketBufferSize : 1024, - itsGnCbfPacketBufferSize : 256, - itsGnDefaultTrafficClass : 0, + Mib { + itsGnLocalGnAddr: GNAddress::new( + M::GnUnicast, + ST::Unknown, + MID::new([0, 0, 0, 0, 0, 0]), + ), + itsGnLocalGnAddrConfMethod: LocalGnAddrConfMethod::Auto, + itsGnProtocolVersion: 1, + itsGnIsMobile: GnIsMobile::Mobile, + itsGnIfType: GnIfType::Unspecified, + itsGnMinUpdateFrequencyEPV: 1000, + itsGnPaiInterval: 80, + itsGnMaxSduSize: 1398, + itsGnMaxGeoNetworkingHeaderSize: 88, + itsGnLifetimeLocTE: 20, + itsGnSecurity: GnSecurity::Disabled, + itsGnSnDecapResultHandling: SnDecapResultHandling::Strict, + itsGnLocationServiceMaxRetrans: 10, + itsGnLocationServiceRetransmitTimer: 1000, + itsGnLocationServicePacketBufferSize: 1024, + itsGnBeaconServiceRetransmitTimer: 3000, + itsGnBeaconServiceMaxJitter: 3000 / 4, + itsGnDefaultHopLimit: 10, + itsGnDPLLength: 8, + itsGnMaxPacketLifetime: 600, + itsGnDefaultPacketLifetime: 60, + itsGnMaxPacketDataRate: 100, + itsGnMaxPacketDataRateEmaBeta: 90, + itsGnMaxGeoAreaSize: 10, + itsGnMinPacketRepetitionInterval: 100, + itsGnNonAreaForwardingAlgorithm: NonAreaForwardingAlgorithm::Greedy, + itsGnAreaForwardingAlgorithm: AreaForwardingAlgorithm::Cbf, + itsGnCbfMinTime: 1, + itsGnCbfMaxTime: 100, + itsGnDefaultMaxCommunicationRange: 1000, + itsGnBroadcastCBFDefSectorAngle: 30, + itsGnUcForwardingPacketBufferSize: 256, + itsGnBcForwardingPacketBufferSize: 1024, + itsGnCbfPacketBufferSize: 256, + itsGnDefaultTrafficClass: 0, } } } -} \ No newline at end of file +} diff --git a/src/geonet/mod.rs b/src/geonet/mod.rs index 3c7eced..8db734f 100644 --- a/src/geonet/mod.rs +++ b/src/geonet/mod.rs @@ -5,8 +5,11 @@ pub mod basic_header; pub mod common_header; pub mod gbc_extended_header; pub mod gn_address; +pub mod guc_extended_header; pub mod location_table; -pub mod position_vector; +pub mod ls_extended_header; pub mod mib; +pub mod position_vector; pub mod router; pub mod service_access_point; +pub mod tsb_extended_header; diff --git a/src/geonet/position_vector.rs b/src/geonet/position_vector.rs index 130c9ec..20e6dd0 100644 --- a/src/geonet/position_vector.rs +++ b/src/geonet/position_vector.rs @@ -1,18 +1,18 @@ // SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) +use std::cmp::{PartialEq, PartialOrd}; use std::ops::{Add, Sub}; -use std::cmp::{PartialOrd, PartialEq}; use super::gn_address::GNAddress; #[derive(Clone, Copy, Debug)] -pub struct Tst{ - msec : u32, +pub struct Tst { + msec: u32, } -impl Tst{ - pub fn set_in_normal_timestamp_milliseconds(msec : u64) -> Self{ +impl Tst { + pub fn set_in_normal_timestamp_milliseconds(msec: u64) -> Self { // TAI epoch offset: 2004-01-01T00:00:00 UTC = 1 072 911 600 s // Timestamp = (unix_ms - tai_offset_ms) mod 2^32 const TAI_OFFSET_MS: u64 = 1_072_911_600_000; @@ -24,56 +24,52 @@ impl Tst{ Tst { msec: value } } - pub fn set_in_normal_timestamp_seconds(sec : u64) -> Self{ - let value: u32 = (((sec-1072911600)*1000) % 2u64.pow(32)).try_into().unwrap(); - Tst{ - msec : value - } + pub fn set_in_normal_timestamp_seconds(sec: u64) -> Self { + let value: u32 = (((sec - 1072911600) * 1000) % 2u64.pow(32)) + .try_into() + .unwrap(); + Tst { msec: value } } - - pub fn encode(&self) -> [u8; 4]{ + pub fn encode(&self) -> [u8; 4] { self.msec.to_be_bytes() } - pub fn decode(bytes : &[u8]) -> Self{ - let mut msec : u32 = 0; - for i in 0..4{ - msec |= (bytes[i] as u32) << (8*(3-i)); - } - Tst{ - msec + pub fn decode(bytes: &[u8]) -> Self { + let mut msec: u32 = 0; + for i in 0..4 { + msec |= (bytes[i] as u32) << (8 * (3 - i)); } + Tst { msec } } } - -impl Sub for Tst{ +impl Sub for Tst { type Output = u32; - fn sub(self, other: Self) -> u32{ + fn sub(self, other: Self) -> u32 { self.msec - other.msec } } -impl Add for Tst{ +impl Add for Tst { type Output = Tst; - fn add(self, other: Self) -> Tst{ - Tst{ - msec : self.msec + other.msec + fn add(self, other: Self) -> Tst { + Tst { + msec: self.msec + other.msec, } } } -impl PartialEq for Tst{ - fn eq(&self, other: &Self) -> bool{ +impl PartialEq for Tst { + fn eq(&self, other: &Self) -> bool { self.msec == other.msec } } -impl PartialOrd for Tst{ - fn partial_cmp(&self, other: &Self) -> Option{ +impl PartialOrd for Tst { + fn partial_cmp(&self, other: &Self) -> Option { // 2^31 is the serial-number half-range used for TAI ms wraparound comparison // (RFC 1982 / ETSI EN 302 636-4-1 §9.2.2.1). // We must NOT use 2u32.pow(32) — that overflows u32 in debug builds. @@ -91,19 +87,19 @@ impl PartialOrd for Tst{ } #[derive(Clone, Copy, Debug)] -pub struct LongPositionVector{ - pub gn_addr : GNAddress, - pub tst : Tst, - pub latitude : u32, - pub longitude : u32, - pub pai : bool, - pub s : u16, - pub h : u16, +pub struct LongPositionVector { + pub gn_addr: GNAddress, + pub tst: Tst, + pub latitude: u32, + pub longitude: u32, + pub pai: bool, + pub s: u16, + pub h: u16, } impl LongPositionVector { - pub fn encode(&self) -> [u8; 24]{ - let mut bytes : [u8; 24] = [0; 24]; + pub fn encode(&self) -> [u8; 24] { + let mut bytes: [u8; 24] = [0; 24]; bytes[0..8].clone_from_slice(&self.gn_addr.encode()); bytes[8..12].clone_from_slice(&self.tst.encode()); bytes[12..16].clone_from_slice(&self.latitude.to_be_bytes()); @@ -115,25 +111,25 @@ impl LongPositionVector { bytes } - pub fn decode(bytes : [u8; 24]) -> Self{ + pub fn decode(bytes: [u8; 24]) -> Self { let gn_addr = GNAddress::decode(&bytes[0..8]); let tst = Tst::decode(&bytes[8..12]); - let mut latitude : u32 = 0; - for i in 0..4{ - latitude |= (bytes[12+i] as u32) << (8*(3-i)); + let mut latitude: u32 = 0; + for i in 0..4 { + latitude |= (bytes[12 + i] as u32) << (8 * (3 - i)); } - let mut longitude : u32 = 0; - for i in 0..4{ - longitude |= (bytes[16+i] as u32) << (8*(3-i)); + let mut longitude: u32 = 0; + for i in 0..4 { + longitude |= (bytes[16 + i] as u32) << (8 * (3 - i)); } let pai = (bytes[20] >> 7) == 1; // Speed is 15 bits: lower 7 bits of byte 20 + all of byte 21 let s: u16 = (((bytes[20] & 0x7F) as u16) << 8) | (bytes[21] as u16); - let mut h : u16 = 0; - for i in 0..2{ - h |= (bytes[22+i] as u16) << (8*(1-i)); + let mut h: u16 = 0; + for i in 0..2 { + h |= (bytes[22 + i] as u16) << (8 * (1 - i)); } - LongPositionVector{ + LongPositionVector { gn_addr, tst, latitude, @@ -145,9 +141,15 @@ impl LongPositionVector { } } -impl PartialEq for LongPositionVector{ - fn eq(&self, other: &Self) -> bool{ - self.gn_addr == other.gn_addr && self.tst == other.tst && self.latitude == other.latitude && self.longitude == other.longitude && self.pai == other.pai && self.s == other.s && self.h == other.h +impl PartialEq for LongPositionVector { + fn eq(&self, other: &Self) -> bool { + self.gn_addr == other.gn_addr + && self.tst == other.tst + && self.latitude == other.latitude + && self.longitude == other.longitude + && self.pai == other.pai + && self.s == other.s + && self.h == other.h } } @@ -185,16 +187,17 @@ impl LongPositionVector { } } -pub struct ShortPositionVector{ - pub gn_address : GNAddress, - pub tst : Tst, - pub latitude : u32, - pub longitude : u32, +#[derive(Clone, Debug)] +pub struct ShortPositionVector { + pub gn_address: GNAddress, + pub tst: Tst, + pub latitude: u32, + pub longitude: u32, } -impl ShortPositionVector{ - pub fn encode(&self) -> [u8; 20]{ - let mut bytes : [u8; 20] = [0; 20]; +impl ShortPositionVector { + pub fn encode(&self) -> [u8; 20] { + let mut bytes: [u8; 20] = [0; 20]; bytes[0..8].clone_from_slice(&self.gn_address.encode()); bytes[8..12].clone_from_slice(&self.tst.encode()); bytes[12..16].clone_from_slice(&self.latitude.to_be_bytes()); @@ -202,18 +205,18 @@ impl ShortPositionVector{ bytes } - pub fn decode(bytes : [u8; 20]) -> Self{ + pub fn decode(bytes: [u8; 20]) -> Self { let gn_address = GNAddress::decode(&bytes[0..8]); let tst = Tst::decode(&bytes[8..12]); - let mut latitude : u32 = 0; - for i in 0..4{ - latitude |= (bytes[12+i] as u32) << (8*(3-i)); + let mut latitude: u32 = 0; + for i in 0..4 { + latitude |= (bytes[12 + i] as u32) << (8 * (3 - i)); } - let mut longitude : u32 = 0; - for i in 0..4{ - longitude |= (bytes[16+i] as u32) << (8*(3-i)); + let mut longitude: u32 = 0; + for i in 0..4 { + longitude |= (bytes[16 + i] as u32) << (8 * (3 - i)); } - ShortPositionVector{ + ShortPositionVector { gn_address, tst, latitude, @@ -222,8 +225,11 @@ impl ShortPositionVector{ } } -impl PartialEq for ShortPositionVector{ - fn eq(&self, other: &Self) -> bool{ - self.gn_address == other.gn_address && self.tst == other.tst && self.latitude == other.latitude && self.longitude == other.longitude +impl PartialEq for ShortPositionVector { + fn eq(&self, other: &Self) -> bool { + self.gn_address == other.gn_address + && self.tst == other.tst + && self.latitude == other.latitude + && self.longitude == other.longitude } -} \ No newline at end of file +} diff --git a/src/geonet/router.rs b/src/geonet/router.rs index 5ab3e97..c0c9dfe 100644 --- a/src/geonet/router.rs +++ b/src/geonet/router.rs @@ -1,85 +1,67 @@ // SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) -//! GeoNetworking Router — ETSI EN 302 636-4-1. +//! GeoNetworking Router — ETSI EN 302 636-4-1 V1.4.1 (2020-01). //! -//! The router is implemented as a single-threaded actor. All interaction -//! happens through a [`RouterHandle`] that serialises messages into the -//! router's message queue. -//! -//! # Architecture -//! ```text -//! Facilities / BTP -//! │ GNDataRequest GNDataIndication ↑ -//! ▼ │ -//! ┌─────────────────────────────────────────────┐ │ -//! │ GeoNetworking Router │─┘ -//! │ (BasicHeader, CommonHeader, SHB/GBC/Beacon) │ -//! └─────────────────────────────────────────────┘ -//! │ Vec (raw GN packet) Vec ↑ -//! ▼ │ -//! Link Layer (Ethernet / C-V2X) -//! ``` -//! -//! # Concurrency -//! The router spawns **one** background thread. The link layer, BTP layer and -//! any position-update threads all communicate with it exclusively through the -//! [`RouterHandle`] MPSC sender. There are no shared mutable references — -//! all state lives inside the router thread. - -use std::sync::mpsc::{self, Sender, Receiver}; +//! Fully standard-compliant router with: +//! - SHB, GBC, GAC, GUC, TSB multi-hop, Beacon +//! - Location Service (LS) request/reply (§10.3.7) +//! - Security integration (sign on TX, verify on RX) +//! - Contention-Based Forwarding (CBF, §F.3) +//! - Greedy Forwarding (§E.2) +//! - Forwarding algorithm selection (Annex D) + +use std::collections::HashMap; +use std::convert::TryInto; +use std::sync::mpsc::{self, Receiver, Sender}; use std::thread; -use std::time::Duration; +use std::time::{Duration, Instant}; use super::basic_header::{BasicHeader, BasicNH}; use super::common_header::CommonHeader; use super::gbc_extended_header::GBCExtendedHeader; use super::gn_address::GNAddress; +use super::guc_extended_header::GUCExtendedHeader; use super::location_table::LocationTable; -use super::mib::Mib; -use super::position_vector::LongPositionVector; +use super::ls_extended_header::{LSReplyExtendedHeader, LSRequestExtendedHeader}; +use super::mib::{AreaForwardingAlgorithm, GnSecurity, Mib}; +use super::position_vector::{LongPositionVector, ShortPositionVector, Tst}; use super::service_access_point::{ - Area, CommunicationProfile, GNDataConfirm, GNDataIndication, GNDataRequest, GeoBroadcastHST, - HeaderSubType, PacketTransportType, ResultCode, HeaderType, TopoBroadcastHST, CommonNH, - UnspecifiedHST, + Area, CommonNH, CommunicationProfile, GNDataConfirm, GNDataIndication, GNDataRequest, + GeoAnycastHST, GeoBroadcastHST, HeaderSubType, HeaderType, LocationServiceHST, + PacketTransportType, ResultCode, TopoBroadcastHST, TrafficClass, UnspecifiedHST, }; +use super::tsb_extended_header::TSBExtendedHeader; +use crate::security::certificate_library::CertificateLibrary; +use crate::security::ecdsa_backend::EcdsaBackend; +use crate::security::sign_service::SignService; +use crate::security::sn_sap::{ + GenerationLocation, ReportVerify, SNSignRequest, SNVerifyRequest, SecurityProfile, +}; +use crate::security::verify_service; - -/// Approximate Earth radius used in the geographic containment function. -const EARTH_RADIUS: f32 = 6371000.0; +const EARTH_RADIUS: f64 = 6371000.0; // ------------------------------------------------------------------ // Router message types // ------------------------------------------------------------------ -/// Messages that the router processes from its input queue. pub enum RouterInput { - /// A higher-layer data request (send a GN packet). GNDataRequest(GNDataRequest), - /// A raw packet received from the link layer. IncomingPacket(Vec), - /// Update the router's ego position vector (e.g. from a GPS fix). PositionVectorUpdate(LongPositionVector), - /// Trigger a graceful shutdown of the router thread. Shutdown, } -/// Response type sent from the router to the BTP layer on receive. pub enum RouterOutput { - /// A raw packet destined for the link layer. LinkLayerPacket(Vec), - /// An indication to pass up to the BTP layer. BTPIndication(GNDataIndication), } -/// Result of the GN forwarding algorithm selection. #[derive(PartialEq)] pub enum GNForwardingAlgorithmResponse { - /// Node is inside the destination area — forward using area forwarding. AreaForwarding, - /// Node is outside the destination area — use non-area (greedy) forwarding. NonAreaForwarding, - /// Packet should be discarded. Discarted, } @@ -93,7 +75,6 @@ impl GNForwardingAlgorithmResponse { } } -/// Trait for receiving GN data indications via a callback. pub trait GNIndicationCallback { fn gn_indication_callback(&mut self, indication: GNDataIndication); } @@ -102,53 +83,53 @@ pub trait GNIndicationCallback { // Router struct and RouterHandle // ------------------------------------------------------------------ -/// The internal GeoNetworking router state. -/// -/// Instantiated and owned exclusively by the router thread — do not -/// construct this directly; use [`Router::spawn`] instead. pub struct Router { - /// Management Information Base — protocol parameters. pub mib: Mib, - /// This node's GN address (derived from MIB + MAC). pub gn_address: GNAddress, - /// Current ego Long Position Vector (updated by GPS). pub ego_position_vector: LongPositionVector, - /// 16-bit sequence number, wraps at 2^16-1. pub sequence_number: u16, - /// Location table (neighbours + remote stations). pub location_table: LocationTable, - /// Channel to the link layer (GN → LL). link_layer_tx: Sender>, - /// Channel to the BTP layer (GN → BTP). btp_tx: Sender, + + // Security services + sign_service: Option, + verify_backend: Option, + verify_cert_library: Option, + + // Location Service state (§10.3.7) + ls_timers: HashMap, + ls_retransmit_counters: HashMap, + ls_packet_buffers: HashMap>, + + // CBF state (§F.3) — keyed by (so_gn_addr_int, sn) + cbf_buffer: HashMap<(u64, u16), (Instant, Vec)>, + + // Beacon reset flag — set when SHB is transmitted + beacon_reset: bool, } -/// A clonable handle to the running GeoNetworking router. -/// -/// All public interaction with the router must go through this handle. #[derive(Clone)] pub struct RouterHandle { input_tx: Sender, } impl RouterHandle { - /// Send a GN data request (i.e. transmit a GN packet). pub fn send_gn_data_request(&self, request: GNDataRequest) { let _ = self.input_tx.send(RouterInput::GNDataRequest(request)); } - /// Inject a raw packet received from the link layer into the router. pub fn send_incoming_packet(&self, packet: Vec) { let _ = self.input_tx.send(RouterInput::IncomingPacket(packet)); } - /// Push a new position vector update (e.g. from a GPS thread). pub fn update_position_vector(&self, position_vector: LongPositionVector) { - let _ = self.input_tx.send(RouterInput::PositionVectorUpdate(position_vector)); + let _ = self + .input_tx + .send(RouterInput::PositionVectorUpdate(position_vector)); } - /// Shut down the router thread. pub fn shutdown(self) { let _ = self.input_tx.send(RouterInput::Shutdown); } @@ -159,18 +140,20 @@ impl RouterHandle { // ------------------------------------------------------------------ impl Router { - /// Create a new router instance. Prefer [`Router::spawn`]. pub fn new( mib: Mib, link_layer_tx: Sender>, btp_tx: Sender, + sign_service: Option, + verify_backend: Option, + verify_cert_library: Option, ) -> Self { Router { - mib: mib.clone(), + mib, gn_address: mib.itsGnLocalGnAddr, ego_position_vector: LongPositionVector { gn_addr: mib.itsGnLocalGnAddr, - tst: super::position_vector::Tst::set_in_normal_timestamp_milliseconds(0), + tst: Tst::set_in_normal_timestamp_milliseconds(0), latitude: 0, longitude: 0, pai: false, @@ -181,72 +164,89 @@ impl Router { sequence_number: 0, link_layer_tx, btp_tx, + sign_service, + verify_backend, + verify_cert_library, + ls_timers: HashMap::new(), + ls_retransmit_counters: HashMap::new(), + ls_packet_buffers: HashMap::new(), + cbf_buffer: HashMap::new(), + beacon_reset: false, } } /// Spawn the router actor thread. /// - /// Returns: - /// - A [`RouterHandle`] for sending messages to the router. - /// - A [`Receiver>`] for raw packets that the router wants to send - /// to the link layer. - /// - A [`Receiver`] for indications to forward to BTP. - /// - /// Also starts the Beacon service if - /// `mib.itsGnBeaconServiceRetransmitTimer > 0`. + /// Accepts optional security services for integrated signing/verification. pub fn spawn( mib: Mib, + sign_service: Option, + verify_backend: Option, + verify_cert_library: Option, ) -> (RouterHandle, Receiver>, Receiver) { let (input_tx, input_rx) = mpsc::channel::(); let (link_layer_tx, link_layer_rx) = mpsc::channel::>(); let (btp_tx, btp_rx) = mpsc::channel::(); let beacon_timer = mib.itsGnBeaconServiceRetransmitTimer; - let handle = RouterHandle { input_tx: input_tx.clone() }; + let handle = RouterHandle { + input_tx: input_tx.clone(), + }; - // Start the Beacon service before the main router thread so that the - // handle clone used by the beacon thread is ready. if beacon_timer > 0 { - let beacon_handle = RouterHandle { input_tx: input_tx.clone() }; + let beacon_handle = RouterHandle { + input_tx: input_tx.clone(), + }; thread::spawn(move || { - // Initial jitter: up to itsGnBeaconServiceMaxJitter ms - // (simplified: just delay half the timer for the first beacon) thread::sleep(Duration::from_millis(beacon_timer as u64 / 2)); loop { - // Send a Beacon request through the regular message queue - let _ = beacon_handle.input_tx.send(RouterInput::GNDataRequest( - GNDataRequest { - upper_protocol_entity: CommonNH::Any, - packet_transport_type: PacketTransportType { - header_type: HeaderType::Beacon, - header_sub_type: HeaderSubType::Unspecified( - UnspecifiedHST::Unspecified, - ), - }, - communication_profile: CommunicationProfile::Unspecified, - traffic_class: super::service_access_point::TrafficClass { - scf: false, - channel_offload: false, - tc_id: 0, - }, - length: 0, - data: vec![], - area: Area { - latitude: 0, - longitude: 0, - a: 0, - b: 0, - angle: 0, - }, - }, - )); + let _ = + beacon_handle + .input_tx + .send(RouterInput::GNDataRequest(GNDataRequest { + upper_protocol_entity: CommonNH::Any, + packet_transport_type: PacketTransportType { + header_type: HeaderType::Beacon, + header_sub_type: HeaderSubType::Unspecified( + UnspecifiedHST::Unspecified, + ), + }, + communication_profile: CommunicationProfile::Unspecified, + traffic_class: TrafficClass { + scf: false, + channel_offload: false, + tc_id: 0, + }, + security_profile: SecurityProfile::NoSecurity, + its_aid: 0, + security_permissions: vec![], + max_hop_limit: 1, + max_packet_lifetime: None, + destination: None, + length: 0, + data: vec![], + area: Area { + latitude: 0, + longitude: 0, + a: 0, + b: 0, + angle: 0, + }, + })); thread::sleep(Duration::from_millis(beacon_timer as u64)); } }); } let _thread_handle = thread::spawn(move || { - let mut router = Router::new(mib, link_layer_tx, btp_tx); + let mut router = Router::new( + mib, + link_layer_tx, + btp_tx, + sign_service, + verify_backend, + verify_cert_library, + ); router.run(input_rx); }); @@ -264,7 +264,7 @@ impl Router { let _ = self.gn_data_request(request); } Ok(RouterInput::IncomingPacket(packet)) => { - self.gn_data_indicate(packet); + self.process_basic_header(&packet); } Ok(RouterInput::PositionVectorUpdate(position_vector)) => { self.refresh_ego_position_vector(position_vector); @@ -280,192 +280,537 @@ impl Router { // Helpers // ------------------------------------------------------------------ - /// Increment and return the 16-bit sequence number, wrapping at 2^16-1. pub fn get_sequence_number(&mut self) -> u16 { self.sequence_number = self.sequence_number.wrapping_add(1); self.sequence_number } + fn send_to_link_layer(&self, packet: Vec) -> GNDataConfirm { + match self.link_layer_tx.send(packet) { + Ok(_) => GNDataConfirm { + result_code: ResultCode::Accepted, + }, + Err(_) => GNDataConfirm { + result_code: ResultCode::Unspecified, + }, + } + } + // ------------------------------------------------------------------ // GN data request — transmit path // ------------------------------------------------------------------ - /// Build and send a **Single-Hop Broadcast** (SHB / TSB) GN packet. - fn gn_data_request_shb(&self, request: GNDataRequest) -> GNDataConfirm { - let mut basic_header = BasicHeader::initialize_with_mib(&self.mib); - basic_header.rhl = 1; // SHB: hop limit fixed to 1 - let common_header = CommonHeader::initialize_with_request(&request); + fn gn_data_request_shb(&mut self, request: GNDataRequest) -> GNDataConfirm { + let basic_header = BasicHeader::initialize_with_mib_request_and_rhl( + &self.mib, + request.max_packet_lifetime, + 1, + ); + let common_header = CommonHeader::initialize_with_request(&request, &self.mib); let media_dependent_data: [u8; 4] = [0; 4]; + + let packet: Vec; + + if self.mib.itsGnSecurity == GnSecurity::Enabled { + if let Some(ref mut sign_service) = self.sign_service { + // TBS = CommonHeader + ExtHeader(LPV + MediaDep) + payload + let mut tbs: Vec = Vec::new(); + tbs.extend_from_slice(&common_header.encode()); + tbs.extend_from_slice(&self.ego_position_vector.encode()); + tbs.extend_from_slice(&media_dependent_data); + tbs.extend_from_slice(&request.data); + + let sign_request = SNSignRequest { + tbs_message: tbs, + its_aid: request.its_aid, + permissions: request.security_permissions.clone(), + generation_location: None, + }; + + let sign_confirm = sign_service.sign_request(&sign_request); + let secured_basic = basic_header.set_nh(BasicNH::SecuredPacket); + + packet = secured_basic + .encode() + .iter() + .copied() + .chain(sign_confirm.sec_message.iter().copied()) + .collect(); + } else { + eprintln!("[GN] Security enabled but no SignService configured"); + return GNDataConfirm { + result_code: ResultCode::Unspecified, + }; + } + } else { + packet = basic_header + .encode() + .iter() + .copied() + .chain(common_header.encode().iter().copied()) + .chain(self.ego_position_vector.encode().iter().copied()) + .chain(media_dependent_data.iter().copied()) + .chain(request.data.iter().copied()) + .collect(); + } + + // Reset beacon timer on SHB transmission (§10.3.10.2 step 7) + self.beacon_reset = true; + + self.send_to_link_layer(packet) + } + + fn gn_data_request_beacon(&self) -> GNDataConfirm { + let basic_header = BasicHeader::initialize_with_mib_request_and_rhl(&self.mib, None, 1); + let common_header = CommonHeader::initialize_beacon(&self.mib); + let packet: Vec = basic_header .encode() .iter() .copied() .chain(common_header.encode().iter().copied()) .chain(self.ego_position_vector.encode().iter().copied()) - .chain(media_dependent_data.iter().copied()) - .chain(request.data.iter().copied()) .collect(); - match self.link_layer_tx.send(packet) { - Ok(_) => GNDataConfirm { result_code: ResultCode::Accepted }, - Err(_) => GNDataConfirm { result_code: ResultCode::Unspecified }, + self.send_to_link_layer(packet) + } + + fn gn_data_request_gbc(&mut self, request: GNDataRequest) -> GNDataConfirm { + // §B.3: area size control + if let HeaderSubType::GeoBroadcast(ref hst) = request.packet_transport_type.header_sub_type + { + if Self::compute_area_size_m2_gb(hst, &request.area) + > self.mib.itsGnMaxGeoAreaSize as f64 * 1_000_000.0 + { + return GNDataConfirm { + result_code: ResultCode::GeographicalScopeTooLarge, + }; + } + } + + let hop_limit = if request.max_hop_limit <= 1 { + self.mib.itsGnDefaultHopLimit + } else { + request.max_hop_limit + }; + let basic_header = BasicHeader::initialize_with_mib_request_and_rhl( + &self.mib, + request.max_packet_lifetime, + hop_limit, + ); + + let req_with_hl = GNDataRequest { + max_hop_limit: hop_limit, + ..clone_request(&request) + }; + + let common_header = CommonHeader::initialize_with_request(&req_with_hl, &self.mib); + let sn = self.get_sequence_number(); + let gbc_ext = GBCExtendedHeader::initialize_with_request_sequence_number_ego_pv( + &request, + sn, + self.ego_position_vector, + ); + + // Security encapsulation + let sec_payload: Option>; + let mut actual_basic = basic_header; + + if self.mib.itsGnSecurity == GnSecurity::Enabled + && request.security_profile + == SecurityProfile::DecentralizedEnvironmentalNotificationMessage + { + if let Some(ref mut sign_service) = self.sign_service { + let mut tbs: Vec = Vec::new(); + tbs.extend_from_slice(&common_header.encode()); + tbs.extend_from_slice(&gbc_ext.encode()); + tbs.extend_from_slice(&request.data); + + let sign_request = SNSignRequest { + tbs_message: tbs, + its_aid: request.its_aid, + permissions: request.security_permissions.clone(), + generation_location: Some(GenerationLocation { + latitude: self.ego_position_vector.latitude as i32, + longitude: self.ego_position_vector.longitude as i32, + elevation: 0xF000, + }), + }; + let sign_confirm = sign_service.sign_request(&sign_request); + actual_basic = actual_basic.set_nh(BasicNH::SecuredPacket); + sec_payload = Some(sign_confirm.sec_message); + } else { + sec_payload = None; + } + } else { + sec_payload = None; + } + + // SCF + no neighbours → buffer (stub) + if self.location_table.get_neighbours().is_empty() && request.traffic_class.scf { + return GNDataConfirm { + result_code: ResultCode::Accepted, + }; + } + + let algorithm = self.gn_forwarding_algorithm_selection(&request, None); + + let build_packet = |bh: &BasicHeader| -> Vec { + let inner: Vec = if let Some(ref sp) = sec_payload { + sp.clone() + } else { + let mut v = Vec::new(); + v.extend_from_slice(&common_header.encode()); + v.extend_from_slice(&gbc_ext.encode()); + v.extend_from_slice(&request.data); + v + }; + bh.encode() + .iter() + .copied() + .chain(inner.iter().copied()) + .collect() + }; + + if algorithm == GNForwardingAlgorithmResponse::AreaForwarding { + let packet = build_packet(&actual_basic); + return self.send_to_link_layer(packet); + } else if algorithm == GNForwardingAlgorithmResponse::NonAreaForwarding { + if self.gn_greedy_forwarding( + request.area.latitude as i32, + request.area.longitude as i32, + &request.traffic_class, + ) { + let packet = build_packet(&actual_basic); + return self.send_to_link_layer(packet); + } + } + + GNDataConfirm { + result_code: ResultCode::Accepted, } } - /// Build and send a **Beacon** GN packet. - /// - /// A Beacon carries no payload; it just announces the node's presence and - /// position to neighbours. - fn gn_data_request_beacon(&self) -> GNDataConfirm { - let mut basic_header = BasicHeader::initialize_with_mib(&self.mib); - basic_header.rhl = 1; - // CommonHeader for Beacon: ht = Beacon, nh = Any, pl = 0 - let beacon_bytes: [u8; 8] = { - let ht = HeaderType::Beacon.encode() << 4; - let nh = CommonNH::Any.encode() << 4; - let tc = 0u8; - let flags = 0u8; - let pl: u16 = 0; - let mhl: u8 = 1; - [nh, ht, tc, flags, (pl >> 8) as u8, pl as u8, mhl, 0] + fn gn_data_request_gac(&mut self, request: GNDataRequest) -> GNDataConfirm { + self.gn_data_request_gbc(request) + } + + fn gn_data_request_guc(&mut self, request: GNDataRequest) -> GNDataConfirm { + let hop_limit = if request.max_hop_limit <= 1 { + self.mib.itsGnDefaultHopLimit + } else { + request.max_hop_limit + }; + let basic_header = BasicHeader::initialize_with_mib_request_and_rhl( + &self.mib, + request.max_packet_lifetime, + hop_limit, + ); + + let req_with_hl = GNDataRequest { + max_hop_limit: hop_limit, + ..clone_request(&request) + }; + let common_header = CommonHeader::initialize_with_request(&req_with_hl, &self.mib); + + // Look up DE PV from LocT + let dest_addr = match &request.destination { + Some(addr) => *addr, + None => { + eprintln!("[GN] GUC request missing destination"); + return GNDataConfirm { + result_code: ResultCode::Unspecified, + }; + } + }; + + let de_entry = self.location_table.get_entry_ref(&dest_addr); + let de_pv = match de_entry { + Some(entry) => ShortPositionVector { + gn_address: entry.position_vector.gn_addr, + tst: entry.position_vector.tst, + latitude: entry.position_vector.latitude, + longitude: entry.position_vector.longitude, + }, + None => { + // No LocTE → invoke Location Service + self.gn_ls_request(&dest_addr, Some(request)); + return GNDataConfirm { + result_code: ResultCode::Accepted, + }; + } }; + + let sn = self.get_sequence_number(); + let guc_ext = GUCExtendedHeader::initialize_with_sequence_number_ego_pv_de_pv( + sn, + self.ego_position_vector, + de_pv.clone(), + ); + + // SCF + no neighbours → buffer (stub) + if self.location_table.get_neighbours().is_empty() && request.traffic_class.scf { + return GNDataConfirm { + result_code: ResultCode::Accepted, + }; + } + + if !self.gn_greedy_forwarding( + de_pv.latitude as i32, + de_pv.longitude as i32, + &request.traffic_class, + ) { + return GNDataConfirm { + result_code: ResultCode::Accepted, + }; + } + let packet: Vec = basic_header .encode() .iter() .copied() - .chain(beacon_bytes.iter().copied()) - .chain(self.ego_position_vector.encode().iter().copied()) + .chain(common_header.encode().iter().copied()) + .chain(guc_ext.encode().iter().copied()) + .chain(request.data.iter().copied()) .collect(); - match self.link_layer_tx.send(packet) { - Ok(_) => GNDataConfirm { result_code: ResultCode::Accepted }, - Err(_) => GNDataConfirm { result_code: ResultCode::Unspecified }, + self.send_to_link_layer(packet) + } + + pub fn gn_data_request(&mut self, request: GNDataRequest) -> GNDataConfirm { + match request.packet_transport_type.header_type { + HeaderType::Beacon => self.gn_data_request_beacon(), + HeaderType::Tsb => { + if let HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop) = + request.packet_transport_type.header_sub_type + { + self.gn_data_request_shb(request) + } else { + eprintln!("[GN] TSB multi-hop source not yet fully implemented"); + GNDataConfirm { + result_code: ResultCode::Unspecified, + } + } + } + HeaderType::GeoBroadcast => self.gn_data_request_gbc(request), + HeaderType::GeoAnycast => self.gn_data_request_gac(request), + HeaderType::GeoUnicast => self.gn_data_request_guc(request), + _ => { + eprintln!("[GN] Header type not supported"); + GNDataConfirm { + result_code: ResultCode::Unspecified, + } + } } } - /// Build and send a **Geo-Broadcast** (GBC) GN packet. - fn gn_data_request_gbc(&mut self, request: GNDataRequest) -> GNDataConfirm { - let basic_header = BasicHeader::initialize_with_mib(&self.mib); - let common_header = CommonHeader::initialize_with_request(&request); + // ------------------------------------------------------------------ + // GN indicate — receive path + // ------------------------------------------------------------------ - let gbc_extended_header = GBCExtendedHeader { - sn: self.get_sequence_number(), - reserved: 0, - so_pv: self.ego_position_vector, - latitude: request.area.latitude, - longitude: request.area.longitude, - a: request.area.a, - b: request.area.b, - angle: request.area.angle, - reserved2: 0, - }; + /// Top-level receive dispatcher — process Basic Header. + pub fn process_basic_header(&mut self, packet: &[u8]) { + if packet.len() < 4 { + eprintln!("[GN] Packet too short for Basic Header"); + return; + } + let basic_header = BasicHeader::decode( + packet[0..4] + .try_into() + .expect("BasicHeader slice wrong length"), + ); + if basic_header.version != self.mib.itsGnProtocolVersion { + eprintln!("[GN] Protocol version mismatch"); + return; + } + let remaining = &packet[4..]; + match basic_header.nh { + BasicNH::CommonHeader => { + // When itsGnSecurity is ENABLED, unsecured packets must be discarded + if self.mib.itsGnSecurity == GnSecurity::Enabled { + return; + } + self.process_common_header(remaining, &basic_header); + } + BasicNH::SecuredPacket => { + self.process_security_header(remaining, &basic_header); + } + _ => { + eprintln!("[GN] Basic NH not supported"); + } + } + } + + /// Process security header — verify and dispatch. + fn process_security_header(&mut self, packet: &[u8], basic_header: &BasicHeader) { + let (backend, cert_library) = + match (&self.verify_backend, &mut self.verify_cert_library) { + (Some(b), Some(cl)) => (b, cl), + _ => { + eprintln!("[GN] Secured packet received but no verify service configured"); + return; + } + }; - // Only send if we have neighbours or SCF is disabled - if !self.location_table.get_neighbours().is_empty() || !request.traffic_class.scf { - let algorithm = self.gn_forwarding_algorithm_selection(&request); + let verify_request = SNVerifyRequest { + message: packet.to_vec(), + }; - if algorithm == GNForwardingAlgorithmResponse::AreaForwarding - || algorithm == GNForwardingAlgorithmResponse::NonAreaForwarding - { - let packet: Vec = basic_header - .encode() - .iter() - .copied() - .chain(common_header.encode().iter().copied()) - .chain(gbc_extended_header.encode().iter().copied()) - .chain(request.data.iter().copied()) - .collect(); + let (confirm, _events) = + verify_service::verify_message(&verify_request, backend, cert_library); - return match self.link_layer_tx.send(packet) { - Ok(_) => GNDataConfirm { result_code: ResultCode::Accepted }, - Err(_) => GNDataConfirm { result_code: ResultCode::Unspecified }, - }; - } + if confirm.report != ReportVerify::Success { + eprintln!( + "[GN] Secured packet verification failed: {:?}", + confirm.report + ); + return; } - GNDataConfirm { result_code: ResultCode::Unspecified } + + // Dispatch plain_message as Common Header + payload + let plain_basic = basic_header.clone().set_nh(BasicNH::CommonHeader); + self.process_common_header(&confirm.plain_message, &plain_basic); } - /// Dispatch a [`GNDataRequest`] from the higher layer. - pub fn gn_data_request(&mut self, request: GNDataRequest) -> GNDataConfirm { - match request.packet_transport_type.header_type { + /// Process Common Header and dispatch to appropriate handler. + fn process_common_header(&mut self, packet: &[u8], basic_header: &BasicHeader) { + if packet.len() < 8 { + eprintln!("[GN] Packet too short for Common Header"); + return; + } + let common_header = CommonHeader::decode( + packet[0..8] + .try_into() + .expect("CommonHeader slice wrong length"), + ); + let payload = &packet[8..]; + + if basic_header.rhl > common_header.mhl { + eprintln!("[GN] Hop limit exceeded"); + return; + } + + let indication: Option = match common_header.ht { HeaderType::Beacon => { - return self.gn_data_request_beacon(); + self.gn_data_indicate_beacon(payload); + None } HeaderType::Tsb => { if let HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop) = - request.packet_transport_type.header_sub_type + common_header.hst + { + self.gn_data_indicate_shb(payload, &common_header, basic_header) + } else if let HeaderSubType::TopoBroadcast(TopoBroadcastHST::MultiHop) = + common_header.hst { - return self.gn_data_request_shb(request); + self.gn_data_indicate_tsb(payload, &common_header, basic_header) + } else { + eprintln!("[GN] Unsupported TSB sub-type"); + None } - eprintln!("[GN] Unsupported TSB sub-type"); } HeaderType::GeoBroadcast => { - return self.gn_data_request_gbc(request); + self.gn_data_indicate_gbc(payload, &common_header, basic_header) + } + HeaderType::GeoAnycast => { + self.gn_data_indicate_gac(payload, &common_header, basic_header) + } + HeaderType::GeoUnicast => { + self.gn_data_indicate_guc(payload, &common_header, basic_header) + } + HeaderType::Ls => { + self.gn_data_indicate_ls(payload, &common_header, basic_header); + None // LS never delivers to upper entity } _ => { eprintln!("[GN] Header type not supported"); + None } + }; + + if let Some(ind) = indication { + let _ = self.btp_tx.send(ind); } - GNDataConfirm { result_code: ResultCode::Unspecified } } // ------------------------------------------------------------------ - // GN indicate — receive path + // Individual indicate handlers // ------------------------------------------------------------------ - /// Process a received SHB packet and return a [`GNDataIndication`]. fn gn_data_indicate_shb( &mut self, - packet: Vec, - common_header: CommonHeader, - ) -> GNDataIndication { + packet: &[u8], + common_header: &CommonHeader, + basic_header: &BasicHeader, + ) -> Option { + if packet.len() < 28 { + eprintln!("[GN] SHB packet too short"); + return None; + } let lpv = LongPositionVector::decode( - packet[0..24].try_into().expect("SHB: LPV slice wrong length"), + packet[0..24] + .try_into() + .expect("SHB: LPV slice wrong length"), ); - // Payload starts after LPV (24 bytes) + media-dependent field (4 bytes) + // Media dependent data (4 bytes) + payload let payload = packet[28..].to_vec(); - self.location_table.new_shb_packet(&lpv, &packet); - GNDataIndication { - upper_protocol_entity: common_header.nh, + + // DAD + if self.duplicate_address_detection(lpv.gn_addr) { + return None; + } + + self.location_table.new_shb_packet(&lpv, &payload); + + Some(GNDataIndication { + upper_protocol_entity: common_header.nh.clone(), packet_transport_type: PacketTransportType { - header_type: common_header.ht, - header_sub_type: common_header.hst, + header_type: HeaderType::Tsb, + header_sub_type: HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop), }, source_position_vector: lpv, traffic_class: common_header.tc, + destination_area: None, + remaining_packet_lifetime: Some( + basic_header.lt.get_value_in_milliseconds() as f64 / 1000.0, + ), + remaining_hop_limit: Some(basic_header.rhl), length: payload.len() as u16, data: payload, - } + }) } - /// Process a received Beacon packet (update location table only; no indication). - fn gn_data_indicate_beacon(&mut self, packet: Vec) { - if packet.len() >= 24 { - let lpv = LongPositionVector::decode( - packet[0..24].try_into().expect("Beacon: LPV slice wrong length"), - ); - self.location_table.new_shb_packet(&lpv, &packet); + fn gn_data_indicate_beacon(&mut self, packet: &[u8]) { + if packet.len() < 24 { + return; + } + let lpv = LongPositionVector::decode( + packet[0..24] + .try_into() + .expect("Beacon: LPV slice wrong length"), + ); + if self.duplicate_address_detection(lpv.gn_addr) { + return; } + self.location_table.new_shb_packet(&lpv, &packet[24..]); } - /// Process a received GBC packet. - /// - /// Returns `Some(indication)` if: - /// - The packet is not a duplicate. - /// - The node is inside the destination area. - /// - /// Also re-forwards the packet if the node is inside the area - /// and `rhl > 1`. fn gn_data_indicate_gbc( &mut self, - packet: Vec, - common_header: CommonHeader, - basic_header: BasicHeader, - area_type: GeoBroadcastHST, + packet: &[u8], + common_header: &CommonHeader, + basic_header: &BasicHeader, ) -> Option { - let ext: GBCExtendedHeader = GBCExtendedHeader::decode( - packet[0..44].try_into().expect("GBC: extended header slice wrong length"), + if packet.len() < 44 { + eprintln!("[GN] GBC packet too short"); + return None; + } + let ext = GBCExtendedHeader::decode( + packet[0..44] + .try_into() + .expect("GBC ext header slice wrong"), ); + let payload = &packet[44..]; let area = Area { latitude: ext.latitude, longitude: ext.longitude, @@ -474,255 +819,956 @@ impl Router { angle: ext.angle, }; + let area_hst = match &common_header.hst { + HeaderSubType::GeoBroadcast(h) => h.clone(), + _ => return None, + }; + let area_f = self.gn_geometric_function_f( - &area_type, + &area_hst, &area, &self.ego_position_vector.latitude, &self.ego_position_vector.longitude, ); - // Duplicate address detection: drop packets originating from self + // DAD if self.duplicate_address_detection(ext.so_pv.gn_addr) { return None; } - self.location_table.new_gbc_packet(&ext, &packet); - - // Re-forward if inside area and hop limit allows it - if area_f >= 0.0 && basic_header.rhl > 1 { - self.gn_data_forward_gbc(&basic_header, &common_header, &ext, &packet); + // DPD + LocTE update + if self.location_table.new_gbc_packet(&ext, payload) { + return None; // duplicate } + let mut indication: Option = None; + + // Inside/at border → deliver if area_f >= 0.0 { - let payload = packet[44..].to_vec(); - return Some(GNDataIndication { - upper_protocol_entity: common_header.nh, + indication = Some(GNDataIndication { + upper_protocol_entity: common_header.nh.clone(), packet_transport_type: PacketTransportType { - header_type: common_header.ht, - header_sub_type: common_header.hst, + header_type: HeaderType::GeoBroadcast, + header_sub_type: common_header.hst.clone(), }, + destination_area: Some(area), source_position_vector: ext.so_pv, traffic_class: common_header.tc, + remaining_packet_lifetime: Some( + basic_header.lt.get_value_in_milliseconds() as f64 / 1000.0, + ), + remaining_hop_limit: Some(basic_header.rhl), length: payload.len() as u16, - data: payload, + data: payload.to_vec(), }); } - None - } - /// Top-level receive dispatcher. - /// - /// Parses the Basic Header and Common Header, then dispatches to the - /// appropriate sub-function based on header type. - pub fn gn_data_indicate(&mut self, packet: Vec) { - if packet.len() < 12 { - eprintln!("[GN] Packet too short to contain headers"); - return; - } - let basic_header = BasicHeader::decode( - packet[0..4].try_into().expect("BasicHeader slice wrong length"), - ); - if basic_header.version != self.mib.itsGnProtocolVersion { - eprintln!("[GN] Protocol version mismatch"); - return; + // §B.3: area size control + if Self::compute_area_size_m2_gb(&area_hst, &area) + > self.mib.itsGnMaxGeoAreaSize as f64 * 1_000_000.0 + { + return indication; } - match basic_header.nh { - BasicNH::CommonHeader => { - if basic_header.rhl > self.mib.itsGnDefaultHopLimit { - eprintln!("[GN] Hop limit exceeded"); - return; - } - let common_header = CommonHeader::decode( - packet[4..12].try_into().expect("CommonHeader slice wrong length"), - ); - let payload = packet[12..].to_vec(); - match common_header.ht { - HeaderType::Beacon => { - self.gn_data_indicate_beacon(payload); - } - HeaderType::Tsb => { - if let HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop) = - common_header.hst - { - let indication = - self.gn_data_indicate_shb(payload, common_header); - let _ = self.btp_tx.send(indication); - } else { - eprintln!("[GN] Unsupported TSB sub-type"); - } - } - HeaderType::GeoBroadcast => { - if let HeaderSubType::GeoBroadcast(area_type) = common_header.hst { - let indication = self.gn_data_indicate_gbc( - payload, - common_header, - basic_header, - area_type, - ); - if let Some(ind) = indication { - let _ = self.btp_tx.send(ind); - } - } else { - eprintln!("[GN] GBC header sub-type mismatch"); - } - } - _ => { - eprintln!("[GN] Header type not supported"); - } - } - } - BasicNH::SecuredPacket => { - // Security decapsulation not implemented — drop silently - } - _ => { - eprintln!("[GN] Basic NH not supported"); + + // §B.2: PDR enforcement + if let Some(entry) = self + .location_table + .get_entry_ref(&ext.so_pv.gn_addr) + { + if entry.pdr > self.mib.itsGnMaxPacketDataRate as f64 * 1000.0 { + return indication; } } - } - // ------------------------------------------------------------------ - // GBC forwarding - // ------------------------------------------------------------------ + // Forward if RHL > 1 + let new_rhl = basic_header.rhl.saturating_sub(1); + if new_rhl > 0 { + self.gn_data_forward_gbc(basic_header, common_header, &ext, payload); + } - /// Re-forward a received GBC packet towards the destination area. - /// - /// Decrements the RHL, re-runs the forwarding algorithm, and transmits - /// if the result is [`GNForwardingAlgorithmResponse::AreaForwarding`]. - fn gn_data_forward_gbc( - &self, - basic_header: &BasicHeader, + indication + } + + fn gn_data_indicate_gac( + &mut self, + packet: &[u8], common_header: &CommonHeader, - gbc_extended_header: &GBCExtendedHeader, - payload: &[u8], - ) -> GNDataConfirm { - let mut fwd_basic = *basic_header; - fwd_basic.rhl = basic_header.rhl.saturating_sub(1); + basic_header: &BasicHeader, + ) -> Option { + if packet.len() < 44 { + return None; + } + let ext = GBCExtendedHeader::decode( + packet[0..44] + .try_into() + .expect("GAC ext header slice wrong"), + ); + let payload = &packet[44..]; + let area = Area { + latitude: ext.latitude, + longitude: ext.longitude, + a: ext.a, + b: ext.b, + angle: ext.angle, + }; - if fwd_basic.rhl == 0 { - return GNDataConfirm { result_code: ResultCode::Unspecified }; + let area_hst = match &common_header.hst { + HeaderSubType::GeoAnycast(h) => h.clone(), + _ => return None, + }; + + let area_f = self.gn_geometric_function_f_anycast( + &area_hst, + &area, + &self.ego_position_vector.latitude, + &self.ego_position_vector.longitude, + ); + + if self.duplicate_address_detection(ext.so_pv.gn_addr) { + return None; + } + + if self.location_table.new_gac_packet(&ext, payload) { + return None; // duplicate + } + + // Inside/at border → deliver and STOP (no forwarding) + if area_f >= 0.0 { + return Some(GNDataIndication { + upper_protocol_entity: common_header.nh.clone(), + packet_transport_type: PacketTransportType { + header_type: HeaderType::GeoAnycast, + header_sub_type: common_header.hst.clone(), + }, + destination_area: Some(area), + source_position_vector: ext.so_pv, + traffic_class: common_header.tc, + remaining_packet_lifetime: Some( + basic_header.lt.get_value_in_milliseconds() as f64 / 1000.0, + ), + remaining_hop_limit: Some(basic_header.rhl), + length: payload.len() as u16, + data: payload.to_vec(), + }); + } + + // Outside → forward only, no delivery + let new_rhl = basic_header.rhl.saturating_sub(1); + if new_rhl == 0 { + return None; + } + + // §B.2 PDR enforcement + if let Some(entry) = self + .location_table + .get_entry_ref(&ext.so_pv.gn_addr) + { + if entry.pdr > self.mib.itsGnMaxPacketDataRate as f64 * 1000.0 { + return None; + } + } + + let fwd_basic = basic_header.clone().set_rhl(new_rhl); + + if self.location_table.get_neighbours().is_empty() && common_header.tc.scf { + return None; } - let request = GNDataRequest { + // Greedy forwarding towards area centre + if self.gn_greedy_forwarding( + area.latitude as i32, + area.longitude as i32, + &common_header.tc, + ) { + let fwd_packet: Vec = fwd_basic + .encode() + .iter() + .copied() + .chain(common_header.encode().iter().copied()) + .chain(ext.encode().iter().copied()) + .chain(payload.iter().copied()) + .collect(); + let _ = self.link_layer_tx.send(fwd_packet); + } + + None + } + + fn gn_data_indicate_tsb( + &mut self, + packet: &[u8], + common_header: &CommonHeader, + basic_header: &BasicHeader, + ) -> Option { + if packet.len() < 28 { + return None; + } + let ext = TSBExtendedHeader::decode(&packet[0..28]); + let payload = &packet[28..]; + + if self.duplicate_address_detection(ext.so_pv.gn_addr) { + return None; + } + + if self.location_table.new_tsb_packet(&ext, payload) { + return None; // duplicate + } + + let indication = GNDataIndication { upper_protocol_entity: common_header.nh.clone(), - communication_profile: CommunicationProfile::Unspecified, + packet_transport_type: PacketTransportType { + header_type: HeaderType::Tsb, + header_sub_type: HeaderSubType::TopoBroadcast(TopoBroadcastHST::MultiHop), + }, + source_position_vector: ext.so_pv, traffic_class: common_header.tc, + destination_area: None, + remaining_packet_lifetime: Some( + basic_header.lt.get_value_in_milliseconds() as f64 / 1000.0, + ), + remaining_hop_limit: Some(basic_header.rhl), length: payload.len() as u16, data: payload.to_vec(), - area: Area { - latitude: gbc_extended_header.latitude, - longitude: gbc_extended_header.longitude, - a: gbc_extended_header.a, - b: gbc_extended_header.b, - angle: gbc_extended_header.angle, - }, - packet_transport_type: PacketTransportType { - header_type: common_header.ht.clone(), - header_sub_type: common_header.hst.clone(), - }, }; - if self.gn_forwarding_algorithm_selection(&request) - == GNForwardingAlgorithmResponse::AreaForwarding + // §B.2 PDR enforcement + if let Some(entry) = self + .location_table + .get_entry_ref(&ext.so_pv.gn_addr) { - let fwd_packet: Vec = fwd_basic + if entry.pdr > self.mib.itsGnMaxPacketDataRate as f64 * 1000.0 { + return Some(indication); + } + } + + // Forward + let new_rhl = basic_header.rhl.saturating_sub(1); + if new_rhl > 0 { + if !(self.location_table.get_neighbours().is_empty() && common_header.tc.scf) { + let fwd_basic = basic_header.clone().set_rhl(new_rhl); + let fwd_packet: Vec = fwd_basic + .encode() + .iter() + .copied() + .chain(common_header.encode().iter().copied()) + .chain(ext.encode().iter().copied()) + .chain(payload.iter().copied()) + .collect(); + let _ = self.link_layer_tx.send(fwd_packet); + } + } + + Some(indication) + } + + fn gn_data_indicate_guc( + &mut self, + packet: &[u8], + common_header: &CommonHeader, + basic_header: &BasicHeader, + ) -> Option { + if packet.len() < 48 { + return None; + } + let ext = GUCExtendedHeader::decode(&packet[0..48]); + let payload = &packet[48..]; + + if self.duplicate_address_detection(ext.so_pv.gn_addr) { + return None; + } + + if self.location_table.new_guc_packet(&ext, payload) { + return None; // duplicate + } + + // Check if we are the destination + let is_destination = ext.de_pv.gn_address == self.mib.itsGnLocalGnAddr; + + if is_destination { + return Some(GNDataIndication { + upper_protocol_entity: common_header.nh.clone(), + packet_transport_type: PacketTransportType { + header_type: HeaderType::GeoUnicast, + header_sub_type: HeaderSubType::Unspecified(UnspecifiedHST::Unspecified), + }, + source_position_vector: ext.so_pv, + traffic_class: common_header.tc, + destination_area: None, + remaining_packet_lifetime: Some( + basic_header.lt.get_value_in_milliseconds() as f64 / 1000.0, + ), + remaining_hop_limit: Some(basic_header.rhl), + length: payload.len() as u16, + data: payload.to_vec(), + }); + } + + // Forwarder operations + // §B.2 PDR + if let Some(entry) = self + .location_table + .get_entry_ref(&ext.so_pv.gn_addr) + { + if entry.pdr > self.mib.itsGnMaxPacketDataRate as f64 * 1000.0 { + return None; + } + } + + // Update DE PV from LocT if DE is a neighbour with newer PV + let mut fwd_ext = ext.clone(); + if let Some(de_entry) = self + .location_table + .get_entry_ref(&ext.de_pv.gn_address) + { + if de_entry.is_neighbour && de_entry.position_vector.tst > ext.de_pv.tst { + let de_lpv = &de_entry.position_vector; + fwd_ext = fwd_ext.with_de_pv(ShortPositionVector { + gn_address: de_lpv.gn_addr, + tst: de_lpv.tst, + latitude: de_lpv.latitude, + longitude: de_lpv.longitude, + }); + } + } + + let new_rhl = basic_header.rhl.saturating_sub(1); + if new_rhl > 0 { + if !(self.location_table.get_neighbours().is_empty() && common_header.tc.scf) { + if self.gn_greedy_forwarding( + fwd_ext.de_pv.latitude as i32, + fwd_ext.de_pv.longitude as i32, + &common_header.tc, + ) { + let fwd_basic = basic_header.clone().set_rhl(new_rhl); + let fwd_packet: Vec = fwd_basic + .encode() + .iter() + .copied() + .chain(common_header.encode().iter().copied()) + .chain(fwd_ext.encode().iter().copied()) + .chain(payload.iter().copied()) + .collect(); + let _ = self.link_layer_tx.send(fwd_packet); + } + } + } + + None + } + + // ------------------------------------------------------------------ + // Location Service (§10.3.7) + // ------------------------------------------------------------------ + + fn gn_data_indicate_ls( + &mut self, + packet: &[u8], + common_header: &CommonHeader, + basic_header: &BasicHeader, + ) { + match &common_header.hst { + HeaderSubType::LocationService(LocationServiceHST::LsRequest) => { + self.gn_data_indicate_ls_request(packet, common_header, basic_header); + } + HeaderSubType::LocationService(LocationServiceHST::LsReply) => { + self.gn_data_indicate_ls_reply(packet, common_header, basic_header); + } + _ => { + eprintln!("[GN] Unknown LS HST"); + } + } + } + + fn gn_data_indicate_ls_request( + &mut self, + packet: &[u8], + common_header: &CommonHeader, + basic_header: &BasicHeader, + ) { + if packet.len() < 36 { + return; + } + let ls_req = LSRequestExtendedHeader::decode(&packet[0..36]); + let payload = &packet[36..]; + + if self.duplicate_address_detection(ls_req.so_pv.gn_addr) { + return; + } + + if self.location_table.new_ls_request_packet(&ls_req, payload) { + return; // duplicate + } + + if ls_req.request_gn_addr == self.mib.itsGnLocalGnAddr { + // We are the destination — send LS Reply + let so_entry_pv = self + .location_table + .get_entry_ref(&ls_req.so_pv.gn_addr) + .map(|e| e.position_vector); + let so_lpv = match so_entry_pv { + Some(pv) => pv, + None => return, + }; + let de_pv = ShortPositionVector { + gn_address: so_lpv.gn_addr, + tst: so_lpv.tst, + latitude: so_lpv.latitude, + longitude: so_lpv.longitude, + }; + + let reply_basic = + BasicHeader::initialize_with_mib_request_and_rhl( + &self.mib, + None, + self.mib.itsGnDefaultHopLimit, + ); + let reply_common = CommonHeader { + nh: CommonNH::Any, + reserved: 0, + ht: HeaderType::Ls, + hst: HeaderSubType::LocationService(LocationServiceHST::LsReply), + tc: TrafficClass { + scf: false, + channel_offload: false, + tc_id: 0, + }, + flags: (self.mib.itsGnIsMobile.encode()) << 7, + pl: 0, + mhl: self.mib.itsGnDefaultHopLimit, + reserved2: 0, + }; + let sn = self.get_sequence_number(); + let reply_ext = + LSReplyExtendedHeader::initialize(sn, self.ego_position_vector, de_pv); + + let reply_packet: Vec = reply_basic .encode() .iter() .copied() - .chain(common_header.encode().iter().copied()) - .chain(gbc_extended_header.encode().iter().copied()) - .chain(payload.iter().copied()) + .chain(reply_common.encode().iter().copied()) + .chain(reply_ext.encode().iter().copied()) .collect(); - return match self.link_layer_tx.send(fwd_packet) { - Ok(_) => GNDataConfirm { result_code: ResultCode::Accepted }, - Err(_) => GNDataConfirm { result_code: ResultCode::Unspecified }, + let _ = self.link_layer_tx.send(reply_packet); + } else { + // Forwarder: re-broadcast + // §B.2 PDR enforcement + if let Some(entry) = self.location_table.get_entry_ref(&ls_req.so_pv.gn_addr) { + if entry.pdr > self.mib.itsGnMaxPacketDataRate as f64 * 1000.0 { + return; + } + } + let new_rhl = basic_header.rhl.saturating_sub(1); + if new_rhl > 0 { + let fwd_basic = basic_header.clone().set_rhl(new_rhl); + let fwd_packet: Vec = fwd_basic + .encode() + .iter() + .copied() + .chain(common_header.encode().iter().copied()) + .chain(ls_req.encode().iter().copied()) + .chain(payload.iter().copied()) + .collect(); + let _ = self.link_layer_tx.send(fwd_packet); + } + } + } + + fn gn_data_indicate_ls_reply( + &mut self, + packet: &[u8], + common_header: &CommonHeader, + basic_header: &BasicHeader, + ) { + if packet.len() < 48 { + return; + } + let ls_reply = LSReplyExtendedHeader::decode(&packet[0..48]); + let payload = &packet[48..]; + + if self.duplicate_address_detection(ls_reply.so_pv.gn_addr) { + return; + } + + if self.location_table.new_ls_reply_packet(&ls_reply, payload) { + return; // duplicate + } + + let sought_gn_addr = ls_reply.so_pv.gn_addr; + + if ls_reply.de_pv.gn_address == self.mib.itsGnLocalGnAddr { + // We are the original requester + let key = sought_gn_addr.encode_to_int(); + self.ls_timers.remove(&key); + self.ls_retransmit_counters.remove(&key); + let buffered = self.ls_packet_buffers.remove(&key).unwrap_or_default(); + + if let Some(entry) = self.location_table.get_entry(&sought_gn_addr) { + entry.ls_pending = false; + } + + // Flush LS packet buffer + for req in buffered { + let _ = self.gn_data_request_guc(req); + } + } else { + // Forwarder: forward like GUC forwarder + // §B.2 PDR enforcement + if let Some(entry) = self.location_table.get_entry_ref(&ls_reply.so_pv.gn_addr) + { + if entry.pdr > self.mib.itsGnMaxPacketDataRate as f64 * 1000.0 { + return; + } + } + + let mut fwd_reply = ls_reply.clone(); + if let Some(de_entry) = self.location_table.get_entry_ref(&ls_reply.de_pv.gn_address) + { + if de_entry.is_neighbour && de_entry.position_vector.tst > ls_reply.de_pv.tst { + let de_lpv = &de_entry.position_vector; + fwd_reply = LSReplyExtendedHeader { + sn: fwd_reply.sn, + reserved: fwd_reply.reserved, + so_pv: fwd_reply.so_pv, + de_pv: ShortPositionVector { + gn_address: de_lpv.gn_addr, + tst: de_lpv.tst, + latitude: de_lpv.latitude, + longitude: de_lpv.longitude, + }, + }; + } + } + + let new_rhl = basic_header.rhl.saturating_sub(1); + if new_rhl > 0 { + let fwd_basic = basic_header.clone().set_rhl(new_rhl); + let fwd_packet: Vec = fwd_basic + .encode() + .iter() + .copied() + .chain(common_header.encode().iter().copied()) + .chain(fwd_reply.encode().iter().copied()) + .chain(payload.iter().copied()) + .collect(); + let _ = self.link_layer_tx.send(fwd_packet); + } + } + } + + /// Initiate Location Service request (§10.3.7.1.2). + fn gn_ls_request(&mut self, sought_addr: &GNAddress, buffered_request: Option) { + let key = sought_addr.encode_to_int(); + + // Check if LS is already in progress + if let Some(entry) = self.location_table.get_entry_ref(sought_addr) { + if entry.ls_pending { + if let Some(req) = buffered_request { + self.ls_packet_buffers + .entry(key) + .or_insert_with(Vec::new) + .push(req); + } + return; + } + } + + // Create/fetch LocTE and set ls_pending + let entry = self.location_table.ensure_entry(sought_addr); + entry.ls_pending = true; + + self.ls_packet_buffers.insert( + key, + if let Some(req) = buffered_request { + vec![req] + } else { + vec![] + }, + ); + self.ls_retransmit_counters.insert(key, 0); + self.ls_timers.insert(key, Instant::now()); + + // Send LS Request packet + self.send_ls_request_packet(sought_addr); + } + + fn send_ls_request_packet(&mut self, sought_addr: &GNAddress) { + let basic_header = BasicHeader::initialize_with_mib_request_and_rhl( + &self.mib, + None, + self.mib.itsGnDefaultHopLimit, + ); + let common_header = CommonHeader { + nh: CommonNH::Any, + reserved: 0, + ht: HeaderType::Ls, + hst: HeaderSubType::LocationService(LocationServiceHST::LsRequest), + tc: TrafficClass { + scf: false, + channel_offload: false, + tc_id: 0, + }, + flags: (self.mib.itsGnIsMobile.encode()) << 7, + pl: 0, + mhl: self.mib.itsGnDefaultHopLimit, + reserved2: 0, + }; + let sn = self.get_sequence_number(); + let ls_req = + LSRequestExtendedHeader::initialize(sn, self.ego_position_vector, *sought_addr); + + let packet: Vec = basic_header + .encode() + .iter() + .copied() + .chain(common_header.encode().iter().copied()) + .chain(ls_req.encode().iter().copied()) + .collect(); + + let _ = self.link_layer_tx.send(packet); + } + + // ------------------------------------------------------------------ + // GBC forwarding + // ------------------------------------------------------------------ + + fn gn_data_forward_gbc( + &mut self, + basic_header: &BasicHeader, + common_header: &CommonHeader, + gbc_ext: &GBCExtendedHeader, + payload: &[u8], + ) -> GNDataConfirm { + let mut fwd_basic = basic_header.clone(); + fwd_basic.rhl = basic_header.rhl.saturating_sub(1); + + if fwd_basic.rhl == 0 { + return GNDataConfirm { + result_code: ResultCode::Unspecified, }; } - GNDataConfirm { result_code: ResultCode::Unspecified } + + if !self.location_table.get_neighbours().is_empty() || !common_header.tc.scf { + let area = Area { + latitude: gbc_ext.latitude, + longitude: gbc_ext.longitude, + a: gbc_ext.a, + b: gbc_ext.b, + angle: gbc_ext.angle, + }; + let pseudo_request = GNDataRequest { + upper_protocol_entity: common_header.nh.clone(), + packet_transport_type: PacketTransportType { + header_type: common_header.ht.clone(), + header_sub_type: common_header.hst.clone(), + }, + communication_profile: CommunicationProfile::Unspecified, + traffic_class: common_header.tc, + security_profile: SecurityProfile::NoSecurity, + its_aid: 0, + security_permissions: vec![], + max_hop_limit: common_header.mhl, + max_packet_lifetime: None, + destination: None, + length: payload.len() as u16, + data: vec![], + area, + }; + let algorithm = self.gn_forwarding_algorithm_selection( + &pseudo_request, + Some(&gbc_ext.so_pv.gn_addr), + ); + + if algorithm == GNForwardingAlgorithmResponse::AreaForwarding { + if self.mib.itsGnAreaForwardingAlgorithm == AreaForwardingAlgorithm::Cbf { + self.gn_area_cbf_forwarding(&fwd_basic, common_header, gbc_ext, payload); + return GNDataConfirm { + result_code: ResultCode::Accepted, + }; + } + let fwd_packet: Vec = fwd_basic + .encode() + .iter() + .copied() + .chain(common_header.encode().iter().copied()) + .chain(gbc_ext.encode().iter().copied()) + .chain(payload.iter().copied()) + .collect(); + return self.send_to_link_layer(fwd_packet); + } else if algorithm == GNForwardingAlgorithmResponse::NonAreaForwarding { + if self.gn_greedy_forwarding( + gbc_ext.latitude as i32, + gbc_ext.longitude as i32, + &common_header.tc, + ) { + let fwd_packet: Vec = fwd_basic + .encode() + .iter() + .copied() + .chain(common_header.encode().iter().copied()) + .chain(gbc_ext.encode().iter().copied()) + .chain(payload.iter().copied()) + .collect(); + return self.send_to_link_layer(fwd_packet); + } + } + } + + GNDataConfirm { + result_code: ResultCode::Accepted, + } + } + + // ------------------------------------------------------------------ + // Forwarding algorithms + // ------------------------------------------------------------------ + + /// Greedy forwarding (§E.2 MFR policy). + fn gn_greedy_forwarding( + &self, + dest_lat: i32, + dest_lon: i32, + traffic_class: &TrafficClass, + ) -> bool { + let mut mfr = Self::distance_m( + dest_lat, + dest_lon, + self.ego_position_vector.latitude as i32, + self.ego_position_vector.longitude as i32, + ); + let mut progress_found = false; + for entry in self.location_table.get_neighbours() { + let pv = &entry.position_vector; + let d = Self::distance_m(dest_lat, dest_lon, pv.latitude as i32, pv.longitude as i32); + if d < mfr { + mfr = d; + progress_found = true; + } + } + if progress_found { + return true; + } + // Local optimum + if traffic_class.scf { + return false; // buffer + } + true // BCAST fallback + } + + /// Forwarding algorithm selection (Annex D). + fn gn_forwarding_algorithm_selection( + &self, + request: &GNDataRequest, + sender_gn_addr: Option<&GNAddress>, + ) -> GNForwardingAlgorithmResponse { + let (f_ego, _area_hst) = match &request.packet_transport_type.header_sub_type { + HeaderSubType::GeoBroadcast(hst) => ( + self.gn_geometric_function_f( + hst, + &request.area, + &self.ego_position_vector.latitude, + &self.ego_position_vector.longitude, + ), + true, + ), + HeaderSubType::GeoAnycast(hst) => ( + self.gn_geometric_function_f_anycast( + hst, + &request.area, + &self.ego_position_vector.latitude, + &self.ego_position_vector.longitude, + ), + true, + ), + _ => return GNForwardingAlgorithmResponse::Discarted, + }; + + if f_ego >= 0.0 { + return GNForwardingAlgorithmResponse::AreaForwarding; + } + + // Ego is outside — check sender position (Annex D) + if let Some(se_addr) = sender_gn_addr { + if let Some(se_entry) = self.location_table.get_entry_ref(se_addr) { + if se_entry.position_vector.pai { + let f_se = match &request.packet_transport_type.header_sub_type { + HeaderSubType::GeoBroadcast(hst) => self.gn_geometric_function_f( + hst, + &request.area, + &se_entry.position_vector.latitude, + &se_entry.position_vector.longitude, + ), + HeaderSubType::GeoAnycast(hst) => self.gn_geometric_function_f_anycast( + hst, + &request.area, + &se_entry.position_vector.latitude, + &se_entry.position_vector.longitude, + ), + _ => -1.0, + }; + if f_se >= 0.0 { + return GNForwardingAlgorithmResponse::Discarted; + } + } + } + } + + GNForwardingAlgorithmResponse::NonAreaForwarding + } + + /// CBF forwarding (§F.3). + fn gn_area_cbf_forwarding( + &mut self, + basic_header: &BasicHeader, + common_header: &CommonHeader, + gbc_ext: &GBCExtendedHeader, + payload: &[u8], + ) -> bool { + let cbf_key = (gbc_ext.so_pv.gn_addr.encode_to_int(), gbc_ext.sn); + + if let Some(_) = self.cbf_buffer.remove(&cbf_key) { + // Duplicate while buffering — suppress + return false; + } + + // Compute timeout + let timeout_ms = if let Some(se_entry) = + self.location_table.get_entry_ref(&gbc_ext.so_pv.gn_addr) + { + if se_entry.position_vector.pai && self.ego_position_vector.pai { + let dist = Self::distance_m( + se_entry.position_vector.latitude as i32, + se_entry.position_vector.longitude as i32, + self.ego_position_vector.latitude as i32, + self.ego_position_vector.longitude as i32, + ); + self.cbf_compute_timeout_ms(dist) + } else { + self.mib.itsGnCbfMaxTime as f64 + } + } else { + self.mib.itsGnCbfMaxTime as f64 + }; + + let full_packet: Vec = basic_header + .encode() + .iter() + .copied() + .chain(common_header.encode().iter().copied()) + .chain(gbc_ext.encode().iter().copied()) + .chain(payload.iter().copied()) + .collect(); + + // Store in CBF buffer with timestamp + self.cbf_buffer.insert( + cbf_key, + ( + Instant::now() + Duration::from_millis(timeout_ms as u64), + full_packet, + ), + ); + + true // buffered + } + + fn cbf_compute_timeout_ms(&self, dist_m: f64) -> f64 { + let dist_max = self.mib.itsGnDefaultMaxCommunicationRange as f64; + let to_min = self.mib.itsGnCbfMinTime as f64; + let to_max = self.mib.itsGnCbfMaxTime as f64; + if dist_m >= dist_max { + return to_min; + } + to_max + (to_min - to_max) / dist_max * dist_m } // ------------------------------------------------------------------ // Geometric helper functions // ------------------------------------------------------------------ - fn calculate_distance(coord1: (f32, f32), coord2: (f32, f32)) -> (f32, f32) { + fn calculate_distance(coord1: (f64, f64), coord2: (f64, f64)) -> (f64, f64) { let (lat1, lon1) = coord1; let (lat2, lon2) = coord2; let lat1 = lat1.to_radians(); let lon1 = lon1.to_radians(); let lat2 = lat2.to_radians(); let lon2 = lon2.to_radians(); - let y = EARTH_RADIUS * (lon2 - lon1) * f32::cos((lat1 + lat2) / 2.0); + let y = EARTH_RADIUS * (lon2 - lon1) * f64::cos((lat1 + lat2) / 2.0); let x = -EARTH_RADIUS * (lat2 - lat1); (x, y) } - /// Evaluate whether a point at (`lat`, `lon`) is inside `area`. - /// - /// Returns a value ≥ 0 when the point is inside, < 0 when outside. fn gn_geometric_function_f( &self, area_type: &GeoBroadcastHST, area: &Area, lat: &u32, lon: &u32, - ) -> f32 { + ) -> f64 { let coord1 = ( - (area.latitude as f32) / 10_000_000.0, - (area.longitude as f32) / 10_000_000.0, - ); - let coord2 = ( - (*lat as f32) / 10_000_000.0, - (*lon as f32) / 10_000_000.0, + (area.latitude as f64) / 10_000_000.0, + (area.longitude as f64) / 10_000_000.0, ); + let coord2 = ((*lat as f64) / 10_000_000.0, (*lon as f64) / 10_000_000.0); let (x, y) = Router::calculate_distance(coord1, coord2); + let a = area.a as f64; + let b = area.b as f64; match area_type { - GeoBroadcastHST::GeoBroadcastCircle => { - 1.0 - (x / area.a as f32).powi(2) - (y / area.a as f32).powi(2) - } + GeoBroadcastHST::GeoBroadcastCircle => 1.0 - (x / a).powi(2) - (y / a).powi(2), + GeoBroadcastHST::GeoBroadcastEllipse => 1.0 - (x / a).powi(2) - (y / b).powi(2), GeoBroadcastHST::GeoBroadcastRectangle => { - 1.0 - (x / area.a as f32).powi(2) - (y / area.b as f32).powi(2) - } - GeoBroadcastHST::GeoBroadcastEllipse => { - (1.0 - (x / area.a as f32).powi(2)) - .min(1.0 - (y / area.b as f32).powi(2)) + (1.0 - (x / a).powi(2)).min(1.0 - (y / b).powi(2)) } } } - /// Select the forwarding algorithm for a GN data request. - fn gn_forwarding_algorithm_selection( + fn gn_geometric_function_f_anycast( &self, - request: &GNDataRequest, - ) -> GNForwardingAlgorithmResponse { - if let HeaderSubType::GeoBroadcast(ref hst) = - request.packet_transport_type.header_sub_type - { - let f = self.gn_geometric_function_f( - hst, - &request.area, - &self.ego_position_vector.latitude, - &self.ego_position_vector.longitude, - ); - if f >= 0.0 { - GNForwardingAlgorithmResponse::AreaForwarding - } else { - GNForwardingAlgorithmResponse::NonAreaForwarding + area_type: &GeoAnycastHST, + area: &Area, + lat: &u32, + lon: &u32, + ) -> f64 { + let coord1 = ( + (area.latitude as f64) / 10_000_000.0, + (area.longitude as f64) / 10_000_000.0, + ); + let coord2 = ((*lat as f64) / 10_000_000.0, (*lon as f64) / 10_000_000.0); + let (x, y) = Router::calculate_distance(coord1, coord2); + let a = area.a as f64; + let b = area.b as f64; + match area_type { + GeoAnycastHST::GeoAnycastCircle => 1.0 - (x / a).powi(2) - (y / a).powi(2), + GeoAnycastHST::GeoAnycastEllipse => 1.0 - (x / a).powi(2) - (y / b).powi(2), + GeoAnycastHST::GeoAnycastRectangle => { + (1.0 - (x / a).powi(2)).min(1.0 - (y / b).powi(2)) } - } else { - GNForwardingAlgorithmResponse::Discarted } } + fn compute_area_size_m2_gb(area_type: &GeoBroadcastHST, area: &Area) -> f64 { + let a = area.a as f64; + let b = area.b as f64; + match area_type { + GeoBroadcastHST::GeoBroadcastCircle => std::f64::consts::PI * a * a, + GeoBroadcastHST::GeoBroadcastEllipse => std::f64::consts::PI * a * b, + GeoBroadcastHST::GeoBroadcastRectangle => 4.0 * a * b, + } + } + + fn distance_m(lat1: i32, lon1: i32, lat2: i32, lon2: i32) -> f64 { + let c1 = (lat1 as f64 / 10_000_000.0, lon1 as f64 / 10_000_000.0); + let c2 = (lat2 as f64 / 10_000_000.0, lon2 as f64 / 10_000_000.0); + let (dx, dy) = Router::calculate_distance(c1, c2); + (dx * dx + dy * dy).sqrt() + } + // ------------------------------------------------------------------ // Miscellaneous // ------------------------------------------------------------------ - /// Return `true` if `address` is this node's own GN address. pub fn duplicate_address_detection(&self, address: GNAddress) -> bool { self.mib.itsGnLocalGnAddr == address } - /// Update the ego position vector from a new GPS fix. pub fn refresh_ego_position_vector(&mut self, position_vector: LongPositionVector) { self.ego_position_vector.latitude = position_vector.latitude; self.ego_position_vector.longitude = position_vector.longitude; @@ -733,9 +1779,21 @@ impl Router { } } -// ------------------------------------------------------------------ -// Boiler-plate trait impls needed by compiler -// ------------------------------------------------------------------ - -use std::convert::TryInto; - +/// Clone a GNDataRequest (all fields). +fn clone_request(r: &GNDataRequest) -> GNDataRequest { + GNDataRequest { + upper_protocol_entity: r.upper_protocol_entity.clone(), + packet_transport_type: r.packet_transport_type.clone(), + communication_profile: r.communication_profile.clone(), + traffic_class: r.traffic_class, + security_profile: r.security_profile, + its_aid: r.its_aid, + security_permissions: r.security_permissions.clone(), + max_hop_limit: r.max_hop_limit, + max_packet_lifetime: r.max_packet_lifetime, + destination: r.destination, + length: r.length, + data: r.data.clone(), + area: r.area, + } +} diff --git a/src/geonet/service_access_point.rs b/src/geonet/service_access_point.rs index 1c2880d..2bd7bd3 100644 --- a/src/geonet/service_access_point.rs +++ b/src/geonet/service_access_point.rs @@ -1,39 +1,41 @@ // SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) +use super::gn_address::GNAddress; use super::position_vector::LongPositionVector; +use crate::security::sn_sap::SecurityProfile; #[derive(Clone, PartialEq, Debug)] -pub enum CommonNH{ +pub enum CommonNH { Any, BtpA, BtpB, IpV6, } -impl CommonNH{ - pub fn decode(value : u8) -> Self{ +impl CommonNH { + pub fn decode(value: u8) -> Self { match value { 0 => CommonNH::Any, 1 => CommonNH::BtpA, 2 => CommonNH::BtpB, 3 => CommonNH::IpV6, - _ => panic!("Invalid Next Header Value!") - } + _ => panic!("Invalid Next Header Value!"), + } } - pub fn encode(&self) -> u8{ - match self{ - CommonNH::Any => {0}, - CommonNH::BtpA => {1}, - CommonNH::BtpB => {2}, - CommonNH::IpV6 => {3} + pub fn encode(&self) -> u8 { + match self { + CommonNH::Any => 0, + CommonNH::BtpA => 1, + CommonNH::BtpB => 2, + CommonNH::IpV6 => 3, } } } #[derive(Clone, PartialEq, Debug)] -pub enum HeaderType{ +pub enum HeaderType { Any, Beacon, GeoUnicast, @@ -43,8 +45,8 @@ pub enum HeaderType{ Ls, } -impl HeaderType{ - pub fn decode(value: u8) -> Self{ +impl HeaderType { + pub fn decode(value: u8) -> Self { match value { 0 => HeaderType::Any, 1 => HeaderType::Beacon, @@ -53,25 +55,25 @@ impl HeaderType{ 4 => HeaderType::GeoBroadcast, 5 => HeaderType::Tsb, 6 => HeaderType::Ls, - _ => panic!("Invalid Header Type Value!") + _ => panic!("Invalid Header Type Value!"), } } - pub fn encode(&self) -> u8{ - match self{ - HeaderType::Any => {0}, - HeaderType::Beacon => {1}, - HeaderType::GeoUnicast => {2}, - HeaderType::GeoAnycast => {3}, - HeaderType::GeoBroadcast => {4}, - HeaderType::Tsb => {5}, - HeaderType::Ls => {6}, + pub fn encode(&self) -> u8 { + match self { + HeaderType::Any => 0, + HeaderType::Beacon => 1, + HeaderType::GeoUnicast => 2, + HeaderType::GeoAnycast => 3, + HeaderType::GeoBroadcast => 4, + HeaderType::Tsb => 5, + HeaderType::Ls => 6, } } } #[derive(Clone, PartialEq, Debug)] -pub enum HeaderSubType{ +pub enum HeaderSubType { Unspecified(UnspecifiedHST), GeoAnycast(GeoAnycastHST), GeoBroadcast(GeoBroadcastHST), @@ -79,9 +81,8 @@ pub enum HeaderSubType{ LocationService(LocationServiceHST), } - impl HeaderSubType { - pub fn decode(header_type : &HeaderType, value : u8) -> Self{ + pub fn decode(header_type: &HeaderType, value: u8) -> Self { match header_type { HeaderType::Any => HeaderSubType::Unspecified(UnspecifiedHST::decode(value)), HeaderType::Beacon => HeaderSubType::Unspecified(UnspecifiedHST::decode(value)), @@ -93,33 +94,33 @@ impl HeaderSubType { } } - pub fn encode(&self) -> u8{ - match self{ - HeaderSubType::Unspecified(hst) => {hst.encode()}, - HeaderSubType::GeoAnycast(hst) => {hst.encode()}, - HeaderSubType::GeoBroadcast(hst) => {hst.encode()}, - HeaderSubType::TopoBroadcast(hst) => {hst.encode()}, - HeaderSubType::LocationService(hst) => {hst.encode()}, + pub fn encode(&self) -> u8 { + match self { + HeaderSubType::Unspecified(hst) => hst.encode(), + HeaderSubType::GeoAnycast(hst) => hst.encode(), + HeaderSubType::GeoBroadcast(hst) => hst.encode(), + HeaderSubType::TopoBroadcast(hst) => hst.encode(), + HeaderSubType::LocationService(hst) => hst.encode(), } } } #[derive(Clone, PartialEq, Copy, Debug)] -pub enum UnspecifiedHST{ +pub enum UnspecifiedHST { Unspecified, } -impl UnspecifiedHST{ - pub fn decode(value : u8) -> Self{ +impl UnspecifiedHST { + pub fn decode(value: u8) -> Self { match value { 0 => UnspecifiedHST::Unspecified, - _ => panic!("Invalid Header Sub Type Value!") + _ => panic!("Invalid Header Sub Type Value!"), } } - pub fn encode(&self) -> u8{ - match self{ - UnspecifiedHST::Unspecified => {0}, + pub fn encode(&self) -> u8 { + match self { + UnspecifiedHST::Unspecified => 0, } } } @@ -132,20 +133,20 @@ pub enum GeoAnycastHST { } impl GeoAnycastHST { - pub fn encode(&self) -> u8{ - match self{ - GeoAnycastHST::GeoAnycastCircle => {0}, - GeoAnycastHST::GeoAnycastRectangle => {1}, - GeoAnycastHST::GeoAnycastEllipse => {2}, + pub fn encode(&self) -> u8 { + match self { + GeoAnycastHST::GeoAnycastCircle => 0, + GeoAnycastHST::GeoAnycastRectangle => 1, + GeoAnycastHST::GeoAnycastEllipse => 2, } } - pub fn decode(value: u8) -> Self{ + pub fn decode(value: u8) -> Self { match value { 0 => GeoAnycastHST::GeoAnycastCircle, 1 => GeoAnycastHST::GeoAnycastRectangle, 2 => GeoAnycastHST::GeoAnycastEllipse, - _ => panic!("Invalid GeoAnycast Header Sub Type Value!") + _ => panic!("Invalid GeoAnycast Header Sub Type Value!"), } } } @@ -158,20 +159,20 @@ pub enum GeoBroadcastHST { } impl GeoBroadcastHST { - pub fn encode(&self) -> u8{ - match self{ - GeoBroadcastHST::GeoBroadcastCircle => {0}, - GeoBroadcastHST::GeoBroadcastRectangle => {1}, - GeoBroadcastHST::GeoBroadcastEllipse => {2}, + pub fn encode(&self) -> u8 { + match self { + GeoBroadcastHST::GeoBroadcastCircle => 0, + GeoBroadcastHST::GeoBroadcastRectangle => 1, + GeoBroadcastHST::GeoBroadcastEllipse => 2, } } - pub fn decode(value: u8) -> Self{ + pub fn decode(value: u8) -> Self { match value { 0 => GeoBroadcastHST::GeoBroadcastCircle, 1 => GeoBroadcastHST::GeoBroadcastRectangle, 2 => GeoBroadcastHST::GeoBroadcastEllipse, - _ => panic!("Invalid GeoBroadcast Header Sub Type Value!") + _ => panic!("Invalid GeoBroadcast Header Sub Type Value!"), } } } @@ -183,21 +184,20 @@ pub enum TopoBroadcastHST { } impl TopoBroadcastHST { - pub fn encode(&self) -> u8{ - match self{ - TopoBroadcastHST::SingleHop => {0}, - TopoBroadcastHST::MultiHop => {1}, + pub fn encode(&self) -> u8 { + match self { + TopoBroadcastHST::SingleHop => 0, + TopoBroadcastHST::MultiHop => 1, } } - pub fn decode(value: u8) -> Self{ + pub fn decode(value: u8) -> Self { match value { 0 => TopoBroadcastHST::SingleHop, 1 => TopoBroadcastHST::MultiHop, - _ => panic!("Invalid TopoBroadcast Header Sub Type Value!") + _ => panic!("Invalid TopoBroadcast Header Sub Type Value!"), } } - } #[derive(Clone, PartialEq, Copy, Debug)] @@ -207,47 +207,47 @@ pub enum LocationServiceHST { } impl LocationServiceHST { - pub fn encode(&self) -> u8{ - match self{ - LocationServiceHST::LsRequest => {0}, - LocationServiceHST::LsReply => {1}, + pub fn encode(&self) -> u8 { + match self { + LocationServiceHST::LsRequest => 0, + LocationServiceHST::LsReply => 1, } } - pub fn decode(value: u8) -> Self{ + pub fn decode(value: u8) -> Self { match value { 0 => LocationServiceHST::LsRequest, 1 => LocationServiceHST::LsReply, - _ => panic!("Invalid LocationService Header Sub Type Value!") + _ => panic!("Invalid LocationService Header Sub Type Value!"), } } } #[derive(Clone, PartialEq, Copy, Debug)] -pub struct TrafficClass{ - pub scf : bool, - pub channel_offload : bool, - pub tc_id : u8, +pub struct TrafficClass { + pub scf: bool, + pub channel_offload: bool, + pub tc_id: u8, } -impl TrafficClass{ - pub fn encode(&self) -> u8{ - let mut value : u8 = 0; - if self.scf{ +impl TrafficClass { + pub fn encode(&self) -> u8 { + let mut value: u8 = 0; + if self.scf { value |= 0b1000_0000; } - if self.channel_offload{ + if self.channel_offload { value |= 0b0100_0000; } value |= self.tc_id & 0b0011_1111; value } - pub fn decode(value : u8) -> Self{ + pub fn decode(value: u8) -> Self { let scf = value & 0b1000_0000 != 0; let channel_offload = value & 0b0100_0000 != 0; let tc_id = value & 0b0011_1111; - TrafficClass{ + TrafficClass { scf, channel_offload, tc_id, @@ -256,36 +256,42 @@ impl TrafficClass{ } #[derive(Clone, PartialEq, Debug)] -pub struct PacketTransportType{ - pub header_type : HeaderType, - pub header_sub_type : HeaderSubType, +pub struct PacketTransportType { + pub header_type: HeaderType, + pub header_sub_type: HeaderSubType, } #[derive(Clone, PartialEq)] -pub enum CommunicationProfile{ +pub enum CommunicationProfile { Unspecified, } #[derive(Clone, PartialEq, Copy, Debug)] -pub struct Area{ - pub latitude : u32, - pub longitude : u32, - pub a : u16, - pub b : u16, - pub angle : u16, +pub struct Area { + pub latitude: u32, + pub longitude: u32, + pub a: u16, + pub b: u16, + pub angle: u16, } -pub struct GNDataRequest{ - pub upper_protocol_entity : CommonNH, - pub packet_transport_type : PacketTransportType, - pub communication_profile : CommunicationProfile, - pub traffic_class : TrafficClass, - pub length : u16, - pub data : Vec, - pub area : Area, +pub struct GNDataRequest { + pub upper_protocol_entity: CommonNH, + pub packet_transport_type: PacketTransportType, + pub communication_profile: CommunicationProfile, + pub traffic_class: TrafficClass, + pub security_profile: SecurityProfile, + pub its_aid: u64, + pub security_permissions: Vec, + pub max_hop_limit: u8, + pub max_packet_lifetime: Option, + pub destination: Option, + pub length: u16, + pub data: Vec, + pub area: Area, } -pub enum ResultCode{ +pub enum ResultCode { Accepted, MaximumLengthExceeded, MaximumLifetimeExceeded, @@ -295,16 +301,18 @@ pub enum ResultCode{ Unspecified, } -pub struct GNDataConfirm{ - pub result_code : ResultCode, +pub struct GNDataConfirm { + pub result_code: ResultCode, } -pub struct GNDataIndication{ - pub upper_protocol_entity : CommonNH, - pub packet_transport_type : PacketTransportType, - pub source_position_vector : LongPositionVector, - pub traffic_class : TrafficClass, - pub length : u16, - pub data : Vec, +pub struct GNDataIndication { + pub upper_protocol_entity: CommonNH, + pub packet_transport_type: PacketTransportType, + pub source_position_vector: LongPositionVector, + pub traffic_class: TrafficClass, + pub destination_area: Option, + pub remaining_packet_lifetime: Option, + pub remaining_hop_limit: Option, + pub length: u16, + pub data: Vec, } - diff --git a/src/geonet/tsb_extended_header.rs b/src/geonet/tsb_extended_header.rs new file mode 100644 index 0000000..994d0d0 --- /dev/null +++ b/src/geonet/tsb_extended_header.rs @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) + +//! TSB Extended Header — ETSI EN 302 636-4-1 V1.4.1 (2020-01) §9.8.3 (Table 12). +//! +//! Layout (28 bytes): +//! SN 2 octets +//! Reserved 2 octets +//! SO PV 24 octets (Long Position Vector) + +use super::position_vector::LongPositionVector; + +#[derive(Clone, PartialEq, Debug)] +pub struct TSBExtendedHeader { + pub sn: u16, + pub reserved: u16, + pub so_pv: LongPositionVector, +} + +impl TSBExtendedHeader { + pub fn initialize_with_sequence_number_ego_pv( + sequence_number: u16, + ego_pv: LongPositionVector, + ) -> Self { + TSBExtendedHeader { + sn: sequence_number, + reserved: 0, + so_pv: ego_pv, + } + } + + pub fn encode(&self) -> [u8; 28] { + let mut bytes = [0u8; 28]; + bytes[0..2].copy_from_slice(&self.sn.to_be_bytes()); + bytes[2..4].copy_from_slice(&self.reserved.to_be_bytes()); + bytes[4..28].copy_from_slice(&self.so_pv.encode()); + bytes + } + + pub fn decode(bytes: &[u8]) -> Self { + assert!(bytes.len() >= 28, "TSB Extended Header too short"); + let sn = u16::from_be_bytes([bytes[0], bytes[1]]); + let reserved = u16::from_be_bytes([bytes[2], bytes[3]]); + let so_pv = LongPositionVector::decode(bytes[4..28].try_into().unwrap()); + TSBExtendedHeader { + sn, + reserved, + so_pv, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 829156d..66b9c7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,3 +45,4 @@ pub mod btp; pub mod facilities; pub mod geonet; pub mod link_layer; +pub mod security; diff --git a/src/link_layer/mod.rs b/src/link_layer/mod.rs index 88abc47..88bace7 100644 --- a/src/link_layer/mod.rs +++ b/src/link_layer/mod.rs @@ -7,5 +7,5 @@ //! uses to send and receive raw packets. Any concrete implementation //! (Ethernet, C-V2X, loopback, …) can implement this trait. -pub mod raw_link_layer; pub mod packet_consts; +pub mod raw_link_layer; diff --git a/src/link_layer/packet_consts.rs b/src/link_layer/packet_consts.rs index d9bf6e3..1ef4e14 100644 --- a/src/link_layer/packet_consts.rs +++ b/src/link_layer/packet_consts.rs @@ -1,4 +1,4 @@ // SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) -pub const ETH_P_GEONET: u16 = 0x8947; \ No newline at end of file +pub const ETH_P_GEONET: u16 = 0x8947; diff --git a/src/link_layer/raw_link_layer.rs b/src/link_layer/raw_link_layer.rs index 25b74a8..81f8c50 100644 --- a/src/link_layer/raw_link_layer.rs +++ b/src/link_layer/raw_link_layer.rs @@ -163,19 +163,21 @@ impl RawLinkLayer { std::mem::size_of::() as libc::socklen_t, ); if ret < 0 { - eprintln!("[LL RX] PACKET_IGNORE_OUTGOING not supported, falling back to MAC filter"); + eprintln!( + "[LL RX] PACKET_IGNORE_OUTGOING not supported, falling back to MAC filter" + ); } } // Bind to the specific interface so we don't receive from all NICs. let sll = libc::sockaddr_ll { - sll_family: libc::AF_PACKET as u16, + sll_family: libc::AF_PACKET as u16, sll_protocol: (ETH_P_GEONET as u16).to_be(), - sll_ifindex: if_index as i32, - sll_hatype: 0, - sll_pkttype: 0, - sll_halen: 0, - sll_addr: [0u8; 8], + sll_ifindex: if_index as i32, + sll_hatype: 0, + sll_pkttype: 0, + sll_halen: 0, + sll_addr: [0u8; 8], }; let ret = unsafe { libc::bind( @@ -186,12 +188,17 @@ impl RawLinkLayer { }; if ret < 0 { eprintln!("[LL RX] Failed to bind socket"); - unsafe { libc::close(sock); } + unsafe { + libc::close(sock); + } return; } // Set a 100 ms receive timeout so we can check the stop flag. - let tv = libc::timeval { tv_sec: 0, tv_usec: 100_000 }; + let tv = libc::timeval { + tv_sec: 0, + tv_usec: 100_000, + }; unsafe { libc::setsockopt( sock, @@ -208,12 +215,7 @@ impl RawLinkLayer { break; } let n = unsafe { - libc::recv( - sock, - buf.as_mut_ptr() as *mut libc::c_void, - buf.len(), - 0, - ) + libc::recv(sock, buf.as_mut_ptr() as *mut libc::c_void, buf.len(), 0) }; if n < 0 { // EAGAIN / EWOULDBLOCK = timeout, just loop @@ -236,7 +238,9 @@ impl RawLinkLayer { let _ = gn_tx.send(frame[14..].to_vec()); } - unsafe { libc::close(sock); } + unsafe { + libc::close(sock); + } eprintln!("[LL RX] Thread exiting"); }); diff --git a/src/security/certificate.rs b/src/security/certificate.rs new file mode 100644 index 0000000..99bfae0 --- /dev/null +++ b/src/security/certificate.rs @@ -0,0 +1,508 @@ +//! Certificate handling — ETSI TS 103 097 V2.1.1 profiles. +//! +//! Provides [`Certificate`] (read-only wrapper) and [`OwnCertificate`] +//! (includes the private-key identifier for signing). + +use rasn::prelude::*; +use sha2::{Digest, Sha256}; + +use crate::security::ecdsa_backend::EcdsaBackend; +use crate::security::security_asn::etsi_ts103097_module::EtsiTs103097Certificate; +use crate::security::security_asn::ieee1609_dot2::{ + CertificateBase, CertificateType, IssuerIdentifier, SignerIdentifier, + Ieee1609Dot2Content, Ieee1609Dot2Data, SignedData, ToBeSignedCertificate, + ToBeSignedData, SignedDataPayload, +}; +use crate::security::security_asn::ieee1609_dot2_base_types::Signature as Ieee1609Signature; +use crate::security::security_asn::ieee1609_dot2::{ + Certificate as AsnCertificate, SequenceOfCertificate, +}; +use crate::security::security_asn::ieee1609_dot2_base_types::{ + EccP256CurvePoint, EcdsaP256Signature, HashAlgorithm, HashedId8, + PublicVerificationKey, Uint8, Opaque, +}; +use crate::security::security_asn::ieee1609_dot2::{ + SubjectPermissions, VerificationKeyIndicator, +}; + +// ─── COER encode / decode helpers ──────────────────────────────────────── + +/// COER-encode an `EtsiTs103097Certificate`. +pub fn encode_certificate(cert: &EtsiTs103097Certificate) -> Vec { + rasn::coer::encode(cert).expect("certificate COER encode failed") +} + +/// COER-decode an `EtsiTs103097Certificate`. +pub fn decode_certificate(bytes: &[u8]) -> EtsiTs103097Certificate { + rasn::coer::decode::(bytes).expect("certificate COER decode failed") +} + +/// COER-encode `Ieee1609Dot2Data` (signed message envelope). +pub fn encode_ieee1609_dot2_data(data: &Ieee1609Dot2Data) -> Vec { + rasn::coer::encode(data).expect("Ieee1609Dot2Data COER encode failed") +} + +/// COER-decode `Ieee1609Dot2Data`. +pub fn decode_ieee1609_dot2_data(bytes: &[u8]) -> Ieee1609Dot2Data { + rasn::coer::decode::(bytes).expect("Ieee1609Dot2Data COER decode failed") +} + +/// COER-encode `ToBeSignedData` (the data that is actually signed). +pub fn encode_tbs_data(tbs: &ToBeSignedData) -> Vec { + rasn::coer::encode(tbs).expect("ToBeSignedData COER encode failed") +} + +/// COER-encode `ToBeSignedCertificate`. +pub fn encode_tbs_certificate(tbs: &ToBeSignedCertificate) -> Vec { + rasn::coer::encode(tbs).expect("ToBeSignedCertificate COER encode failed") +} + +// ─── HashedId8 / HashedId3 helpers ─────────────────────────────────────── + +/// Compute the HashedId8 of a COER-encoded certificate. +pub fn compute_hashedid8(cert_bytes: &[u8]) -> [u8; 8] { + EcdsaBackend::hash_to_hashedid8(cert_bytes) +} + +// ─── Certificate (read-only) ───────────────────────────────────────────── + +/// Immutable certificate wrapper. +#[derive(Debug, Clone)] +pub struct Certificate { + /// The inner ASN.1 certificate. + pub inner: EtsiTs103097Certificate, + /// Cached COER encoding (for hashing). + encoded: Vec, + /// Optional issuer certificate (for chain verification). + pub issuer: Option>, +} + +impl Certificate { + /// Build from a decoded ASN.1 certificate. + pub fn from_asn(asn: EtsiTs103097Certificate, issuer: Option) -> Self { + let encoded = encode_certificate(&asn); + Self { + inner: asn, + encoded, + issuer: issuer.map(Box::new), + } + } + + /// Decode from COER bytes. + pub fn from_bytes(bytes: &[u8], issuer: Option) -> Self { + let asn = decode_certificate(bytes); + let encoded = bytes.to_vec(); + Self { + inner: asn, + encoded, + issuer: issuer.map(Box::new), + } + } + + /// Access the inner `CertificateBase`. + pub fn base(&self) -> &CertificateBase { + &(self.inner.0).0 + } + + /// Access `toBeSigned`. + pub fn tbs(&self) -> &ToBeSignedCertificate { + &self.base().to_be_signed + } + + /// Return the COER encoding. + pub fn encode(&self) -> &[u8] { + &self.encoded + } + + /// Compute the SHA-256 HashedId8 of the certificate. + pub fn as_hashedid8(&self) -> [u8; 8] { + compute_hashedid8(&self.encoded) + } + + /// Compute the last 3 bytes of HashedId8. + pub fn as_hashedid3(&self) -> [u8; 3] { + let h8 = self.as_hashedid8(); + [h8[5], h8[6], h8[7]] + } + + /// Return the list of ITS-AID (PSID) values from `appPermissions`. + pub fn get_list_of_its_aid(&self) -> Vec { + let mut aids = Vec::new(); + if let Some(ref perms) = self.tbs().app_permissions { + for psid_ssp in perms.0.iter() { + // Psid wraps an Integer — convert via i128 then u64. + let val = u64::try_from(&psid_ssp.psid.0).unwrap_or(0); + aids.push(val); + } + } + aids + } + + /// Get issuer HashedId8 stored inside the certificate. + /// Returns `None` if self-signed. + pub fn get_issuer_hashedid8(&self) -> Option<[u8; 8]> { + match &self.base().issuer { + IssuerIdentifier::sha256AndDigest(h) => { + let mut out = [0u8; 8]; + out.copy_from_slice(h.0.as_ref()); + Some(out) + } + IssuerIdentifier::R_self(_) => None, + IssuerIdentifier::sha384AndDigest(h) => { + let mut out = [0u8; 8]; + out.copy_from_slice(h.0.as_ref()); + Some(out) + } + _ => None, + } + } + + /// Check whether the signature uses NIST P-256. + pub fn signature_is_nist_p256(&self) -> bool { + matches!( + self.base().signature, + Some(Ieee1609Signature::ecdsaNistP256Signature(_)) + ) + } + + /// Check whether the verification key is NIST P-256. + pub fn verification_key_is_nist_p256(&self) -> bool { + matches!( + &self.tbs().verify_key_indicator, + VerificationKeyIndicator::verificationKey( + PublicVerificationKey::ecdsaNistP256(_) + ) + ) + } + + /// Is the certificate self-signed? + pub fn is_self_signed(&self) -> bool { + matches!( + &self.base().issuer, + IssuerIdentifier::R_self(HashAlgorithm::sha256) + ) + } + + /// Is the certificate issued (not self-signed)? + pub fn is_issued(&self) -> bool { + matches!( + &self.base().issuer, + IssuerIdentifier::sha256AndDigest(_) | IssuerIdentifier::sha384AndDigest(_) + ) + } + + /// Check whether the issuer's HashedId8 matches the given issuer certificate. + pub fn check_corresponding_issuer(&self, issuer: &Certificate) -> bool { + match self.get_issuer_hashedid8() { + Some(h) => h == issuer.as_hashedid8(), + None => false, + } + } + + /// Does `certIssuePermissions` have a subjectPermissions of `all`? + pub fn has_all_permissions(&self) -> bool { + if let Some(ref cip) = self.tbs().cert_issue_permissions { + for perm in cip.0.iter() { + if matches!(perm.subject_permissions, SubjectPermissions::all(_)) { + return true; + } + } + } + false + } + + /// Get the PSID list from `certIssuePermissions.subjectPermissions.explicit`. + pub fn get_allowed_permissions(&self) -> Vec { + let mut out = Vec::new(); + if let Some(ref cip) = self.tbs().cert_issue_permissions { + for perm in cip.0.iter() { + if let SubjectPermissions::explicit(ref ranges) = perm.subject_permissions { + for r in ranges.0.iter() { + let val = u64::try_from(&r.psid.0).unwrap_or(0); + out.push(val); + } + } + } + } + out + } + + /// Check whether the issuer has the right permissions to issue this cert. + pub fn check_issuer_has_subject_permissions(&self, issuer: &Certificate) -> bool { + if issuer.has_all_permissions() { + return true; + } + let needed = self.get_list_of_its_aid(); + let allowed = issuer.get_allowed_permissions(); + needed.iter().all(|n| allowed.contains(n)) + } + + /// Verify the certificate signature. + pub fn verify_signature_with( + backend: &EcdsaBackend, + tbs: &ToBeSignedCertificate, + signature: &Ieee1609Signature, + verification_key: &PublicVerificationKey, + ) -> bool { + let tbs_bytes = encode_tbs_certificate(tbs); + backend.verify_with_pk(&tbs_bytes, signature, verification_key) + } + + /// Verify an issued certificate (chain to issuer). + fn verify_issued(&self, backend: &EcdsaBackend) -> bool { + let issuer = match &self.issuer { + Some(i) => i, + None => return false, + }; + if !self.is_issued() { + return false; + } + if !self.check_corresponding_issuer(issuer) { + return false; + } + if !self.check_issuer_has_subject_permissions(issuer) { + return false; + } + if !self.signature_is_nist_p256() || !self.verification_key_is_nist_p256() { + return false; + } + let sig = match &self.base().signature { + Some(s) => s, + None => return false, + }; + let issuer_vk = match &issuer.tbs().verify_key_indicator { + VerificationKeyIndicator::verificationKey(pk) => pk, + _ => return false, + }; + Certificate::verify_signature_with(backend, self.tbs(), sig, issuer_vk) + } + + /// Verify a self-signed certificate. + fn verify_self_signed(&self, backend: &EcdsaBackend) -> bool { + if !self.is_self_signed() { + return false; + } + if !self.signature_is_nist_p256() || !self.verification_key_is_nist_p256() { + return false; + } + let sig = match &self.base().signature { + Some(s) => s, + None => return false, + }; + let pk = match &self.tbs().verify_key_indicator { + VerificationKeyIndicator::verificationKey(vk) => vk, + _ => return false, + }; + Certificate::verify_signature_with(backend, self.tbs(), sig, pk) + } + + /// Full certificate verification (self-signed or chained). + pub fn verify(&self, backend: &EcdsaBackend) -> bool { + // §6: verifyKeyIndicator must match certificate type + match self.base().r_type { + CertificateType::explicit => { + if !matches!( + self.tbs().verify_key_indicator, + VerificationKeyIndicator::verificationKey(_) + ) { + return false; + } + } + CertificateType::implicit => { + if !matches!( + self.tbs().verify_key_indicator, + VerificationKeyIndicator::reconstructionValue(_) + ) { + return false; + } + } + _ => return false, + } + if self.issuer.is_some() && self.is_issued() { + return self.verify_issued(backend); + } + if self.is_self_signed() { + return self.verify_self_signed(backend); + } + false + } + + // ── Profile checks (§7.2) ──────────────────────────────────────────── + + /// §7.2.1 Authorization Ticket profile. + pub fn is_authorization_ticket(&self) -> bool { + if !self.is_issued() { + return false; + } + let tbs = self.tbs(); + if !matches!(tbs.id, crate::security::security_asn::ieee1609_dot2::CertificateId::none(_)) { + return false; + } + if tbs.cert_issue_permissions.is_some() { + return false; + } + tbs.app_permissions.is_some() + } + + /// §7.2.3 Root CA profile. + pub fn is_root_ca(&self) -> bool { + if !matches!(self.base().r_type, CertificateType::explicit) { + return false; + } + if !self.is_self_signed() { + return false; + } + let tbs = self.tbs(); + tbs.cert_issue_permissions.is_some() && tbs.app_permissions.is_some() + } +} + +// ─── OwnCertificate ───────────────────────────────────────────────────── + +/// A certificate for which we hold the private key. +#[derive(Debug, Clone)] +pub struct OwnCertificate { + pub cert: Certificate, + pub key_id: usize, +} + +impl OwnCertificate { + /// Wrap an existing `Certificate` together with its key id. + pub fn new(cert: Certificate, key_id: usize) -> Self { + Self { cert, key_id } + } + + /// Convenience delegations. + pub fn as_hashedid8(&self) -> [u8; 8] { + self.cert.as_hashedid8() + } + + pub fn get_list_of_its_aid(&self) -> Vec { + self.cert.get_list_of_its_aid() + } + + pub fn tbs(&self) -> &ToBeSignedCertificate { + self.cert.tbs() + } + + pub fn base(&self) -> &CertificateBase { + self.cert.base() + } + + pub fn verify(&self, backend: &EcdsaBackend) -> bool { + self.cert.verify(backend) + } + + /// Sign arbitrary data with this certificate's key. + pub fn sign_message(&self, backend: &EcdsaBackend, data: &[u8]) -> Ieee1609Signature { + backend.sign(data, self.key_id) + } + + /// Sign (or re-sign) `target`'s `toBeSigned` and return a new `Certificate` + /// with the updated signature. + pub fn sign_certificate( + &self, + backend: &EcdsaBackend, + target: &Certificate, + ) -> Certificate { + let tbs_bytes = encode_tbs_certificate(target.tbs()); + let sig = backend.sign(&tbs_bytes, self.key_id); + + let mut new_base = target.base().clone(); + new_base.signature = Some(sig); + + let new_asn = EtsiTs103097Certificate(AsnCertificate(new_base)); + Certificate::from_asn(new_asn, Some(self.cert.clone())) + } + + /// Build the inner `AsnCertificate` from the wrapper's certificate dict, + /// setting the issuer to `self`, signing, and returning the result. + pub fn issue_certificate( + &self, + backend: &EcdsaBackend, + target: &Certificate, + ) -> Certificate { + // Set issuer to our HashedId8 + let mut new_base = target.base().clone(); + let h8 = self.as_hashedid8(); + new_base.issuer = + IssuerIdentifier::sha256AndDigest(HashedId8(rasn::types::FixedOctetString::from(h8))); + + // Sign + let tbs_bytes = encode_tbs_certificate(&new_base.to_be_signed); + let sig = backend.sign(&tbs_bytes, self.key_id); + new_base.signature = Some(sig); + + let new_asn = EtsiTs103097Certificate(AsnCertificate(new_base)); + Certificate::from_asn(new_asn, Some(self.cert.clone())) + } + + /// Create a brand-new self-signed certificate. + pub fn initialize_self_signed( + backend: &mut EcdsaBackend, + tbs: ToBeSignedCertificate, + ) -> Self { + let key_id = backend.create_key(); + let pk = backend.get_public_key(key_id); + + let mut tbs_owned = tbs; + tbs_owned.verify_key_indicator = VerificationKeyIndicator::verificationKey(pk); + + // Placeholder signature — will be replaced below. + let placeholder_sig = Ieee1609Signature::ecdsaNistP256Signature(EcdsaP256Signature { + r_sig: EccP256CurvePoint::x_only(vec![0u8; 32].into()), + s_sig: vec![0u8; 32].into(), + }); + + let base = CertificateBase::new( + Uint8(3), + CertificateType::explicit, + IssuerIdentifier::R_self(HashAlgorithm::sha256), + tbs_owned, + Some(placeholder_sig), + ); + + // Sign the toBeSigned + let tbs_bytes = encode_tbs_certificate(&base.to_be_signed); + let sig = backend.sign(&tbs_bytes, key_id); + + let mut final_base = base; + final_base.signature = Some(sig); + + let asn = EtsiTs103097Certificate(AsnCertificate(final_base)); + let cert = Certificate::from_asn(asn, None); + OwnCertificate { cert, key_id } + } + + /// Create a new certificate issued by `issuer`. + pub fn initialize_issued( + backend: &mut EcdsaBackend, + tbs: ToBeSignedCertificate, + issuer: &OwnCertificate, + ) -> Self { + let key_id = backend.create_key(); + let pk = backend.get_public_key(key_id); + + let mut tbs_owned = tbs; + tbs_owned.verify_key_indicator = VerificationKeyIndicator::verificationKey(pk); + + let h8 = issuer.as_hashedid8(); + let base = CertificateBase::new( + Uint8(3), + CertificateType::explicit, + IssuerIdentifier::sha256AndDigest(HashedId8(rasn::types::FixedOctetString::from(h8))), + tbs_owned, + None, // will be filled by sign + ); + + let tbs_bytes = encode_tbs_certificate(&base.to_be_signed); + let sig = backend.sign(&tbs_bytes, issuer.key_id); + + let mut final_base = base; + final_base.signature = Some(sig); + + let asn = EtsiTs103097Certificate(AsnCertificate(final_base)); + let cert = Certificate::from_asn(asn, Some(issuer.cert.clone())); + OwnCertificate { cert, key_id } + } +} diff --git a/src/security/certificate_library.rs b/src/security/certificate_library.rs new file mode 100644 index 0000000..18318e0 --- /dev/null +++ b/src/security/certificate_library.rs @@ -0,0 +1,188 @@ +//! Certificate library — storage and chain validation. +//! +//! Manages four collections of certificates: +//! - Own certificates (can sign with them). +//! - Known authorization tickets (can verify messages from them). +//! - Known authorization authorities. +//! - Known root CA certificates. + +use std::collections::HashMap; + +use crate::security::certificate::{Certificate, OwnCertificate}; +use crate::security::ecdsa_backend::EcdsaBackend; +use crate::security::security_asn::ieee1609_dot2::IssuerIdentifier; + +/// Certificate library holding all trusted and own certificates. +pub struct CertificateLibrary { + pub own_certificates: HashMap<[u8; 8], OwnCertificate>, + pub known_authorization_tickets: HashMap<[u8; 8], Certificate>, + pub known_authorization_authorities: HashMap<[u8; 8], Certificate>, + pub known_root_certificates: HashMap<[u8; 8], Certificate>, +} + +impl CertificateLibrary { + /// Create and populate a library with initial root, AA and AT certificates. + pub fn new( + backend: &EcdsaBackend, + root_certificates: Vec, + aa_certificates: Vec, + at_certificates: Vec, + ) -> Self { + let mut lib = Self { + own_certificates: HashMap::new(), + known_authorization_tickets: HashMap::new(), + known_authorization_authorities: HashMap::new(), + known_root_certificates: HashMap::new(), + }; + for c in root_certificates { + lib.add_root_certificate(backend, c); + } + for c in aa_certificates { + lib.add_authorization_authority(backend, c); + } + for c in at_certificates { + lib.add_authorization_ticket(backend, c); + } + lib + } + + /// Look up the issuer certificate (root or AA) by the HashedId8 stored + /// inside `cert`'s `issuer` field. + pub fn get_issuer_certificate(&self, cert: &Certificate) -> Option<&Certificate> { + match &cert.base().issuer { + IssuerIdentifier::R_self(_) => None, + IssuerIdentifier::sha256AndDigest(h) | IssuerIdentifier::sha384AndDigest(h) => { + let mut key = [0u8; 8]; + key.copy_from_slice(h.0.as_ref()); + self.known_root_certificates + .get(&key) + .or_else(|| self.known_authorization_authorities.get(&key)) + } + _ => None, + } + } + + /// Add a root CA certificate if it verifies (self-signed). + pub fn add_root_certificate(&mut self, backend: &EcdsaBackend, cert: Certificate) { + if cert.verify(backend) { + self.known_root_certificates + .insert(cert.as_hashedid8(), cert); + } + } + + /// Add an authorization authority certificate if its issuer is known and it verifies. + pub fn add_authorization_authority(&mut self, backend: &EcdsaBackend, cert: Certificate) { + let h = cert.as_hashedid8(); + if self.known_authorization_authorities.contains_key(&h) { + return; + } + if self.get_issuer_certificate(&cert).is_some() && cert.verify(backend) { + self.known_authorization_authorities.insert(h, cert); + } + } + + /// Add an authorization ticket if its issuer is known and it verifies. + pub fn add_authorization_ticket(&mut self, backend: &EcdsaBackend, cert: Certificate) { + let h = cert.as_hashedid8(); + if self.known_authorization_tickets.contains_key(&h) { + return; + } + if self.get_issuer_certificate(&cert).is_some() && cert.verify(backend) { + self.known_authorization_tickets.insert(h, cert); + } + } + + /// Add an own certificate if its issuer is known and it verifies. + pub fn add_own_certificate(&mut self, backend: &EcdsaBackend, cert: OwnCertificate) { + if self.get_issuer_certificate(&cert.cert).is_some() && cert.verify(backend) { + self.own_certificates.insert(cert.as_hashedid8(), cert); + } + } + + /// Retrieve an AT by HashedId8. + pub fn get_authorization_ticket_by_hashedid8( + &self, + h: &[u8; 8], + ) -> Option<&Certificate> { + self.known_authorization_tickets.get(h) + } + + /// Verify a sequence of certificates as specified in IEEE 1609.2 signer + /// identifier. The first entry is the AT, subsequent entries form the + /// issuer chain up to a trusted root. + pub fn verify_sequence_of_certificates( + &mut self, + certs: &[Certificate], + backend: &EcdsaBackend, + ) -> Option { + if certs.is_empty() { + return None; + } + if certs.len() == 1 { + let temp = &certs[0]; + let h = temp.as_hashedid8(); + if self.known_authorization_tickets.contains_key(&h) { + return self.known_authorization_tickets.get(&h).cloned(); + } + // Try to find issuer in our store + if let Some(issuer) = self.get_issuer_certificate(temp).cloned() { + let with_issuer = Certificate::from_asn(temp.inner.clone(), Some(issuer)); + if with_issuer.verify(backend) { + let h2 = with_issuer.as_hashedid8(); + self.known_authorization_tickets.insert(h2, with_issuer.clone()); + return Some(with_issuer); + } + } + return None; + } + if certs.len() == 2 { + // certs[1] = AA, certs[0] = AT + let aa_temp = &certs[1]; + if let Some(issuer_h) = aa_temp.get_issuer_hashedid8() { + if let Some(root) = self.known_root_certificates.get(&issuer_h).cloned() { + let aa_with_issuer = + Certificate::from_asn(aa_temp.inner.clone(), Some(root)); + if aa_with_issuer.verify(backend) { + let aa_h = aa_with_issuer.as_hashedid8(); + self.known_authorization_authorities + .insert(aa_h, aa_with_issuer.clone()); + + let at_with_issuer = + Certificate::from_asn(certs[0].inner.clone(), Some(aa_with_issuer)); + if at_with_issuer.verify(backend) { + let at_h = at_with_issuer.as_hashedid8(); + self.known_authorization_tickets + .insert(at_h, at_with_issuer.clone()); + return Some(at_with_issuer); + } + } + } + } + return None; + } + if certs.len() == 3 { + // certs[2] = Root, certs[1] = AA, certs[0] = AT + let root_temp = &certs[2]; + let root_h = root_temp.as_hashedid8(); + if self.known_root_certificates.contains_key(&root_h) { + return self.verify_sequence_of_certificates(&certs[..2], backend); + } + } + None + } + + /// Look up an AA or Root CA certificate by HashedId3 (last 3 bytes of HashedId8). + pub fn get_ca_certificate_by_hashedid3(&self, h3: &[u8; 3]) -> Option<&Certificate> { + for (h8, cert) in &self.known_authorization_authorities { + if h8[5..8] == *h3 { + return Some(cert); + } + } + for (h8, cert) in &self.known_root_certificates { + if h8[5..8] == *h3 { + return Some(cert); + } + } + None + } +} diff --git a/src/security/ecdsa_backend.rs b/src/security/ecdsa_backend.rs new file mode 100644 index 0000000..ee996d5 --- /dev/null +++ b/src/security/ecdsa_backend.rs @@ -0,0 +1,147 @@ +//! ECDSA backend — NIST P-256 signing and verification. +//! +//! Uses the `p256` + `ecdsa` crates. Keys are stored in an in-memory map +//! keyed by sequential integer identifiers, mirroring the Python +//! `PythonECDSABackend`. +//! +//! All public-key and signature representations use the IEEE 1609.2 / +//! `security_asn` types so they can be embedded directly into certificates +//! and signed messages without conversion. + +use p256::ecdsa::signature::Signer; +use p256::ecdsa::{Signature, SigningKey, VerifyingKey}; +use p256::ecdsa::signature::Verifier; +use p256::elliptic_curve::sec1::ToEncodedPoint; +use sha2::{Digest, Sha256}; +use std::collections::HashMap; + +use crate::security::security_asn::ieee1609_dot2_base_types::{ + EccP256CurvePoint, EccP256CurvePointUncompressedP256, EcdsaP256Signature, +}; +use crate::security::security_asn::ieee1609_dot2_base_types::{ + PublicVerificationKey, +}; +use crate::security::security_asn::ieee1609_dot2_base_types::Signature as Ieee1609Signature; + +/// ECDSA backend managing NIST P-256 key pairs. +pub struct EcdsaBackend { + keys: HashMap, + next_id: usize, +} + +impl EcdsaBackend { + pub fn new() -> Self { + Self { + keys: HashMap::new(), + next_id: 0, + } + } + + /// Generate a new P-256 key pair. Returns the key identifier. + pub fn create_key(&mut self) -> usize { + let sk = SigningKey::random(&mut rand::thread_rng()); + let id = self.next_id; + self.keys.insert(id, sk); + self.next_id += 1; + id + } + + /// Return the public verification key in IEEE 1609.2 format. + pub fn get_public_key(&self, id: usize) -> PublicVerificationKey { + let vk = self.keys[&id].verifying_key(); + let pt = vk.to_encoded_point(false); + let x = pt.x().expect("x coord").to_vec().into(); + let y = pt.y().expect("y coord").to_vec().into(); + PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::uncompressedP256( + EccP256CurvePointUncompressedP256 { x, y }, + )) + } + + /// Sign `data` (pre-hash with SHA-256 is done by the `ecdsa` crate). + /// Returns an IEEE 1609.2 `Signature`. + pub fn sign(&self, data: &[u8], id: usize) -> Ieee1609Signature { + let sk = &self.keys[&id]; + // Hash with SHA-256 first to match the Python behaviour (which signs + // the raw data and lets the `ecdsa` library hash it). + let sig: Signature = sk.sign(data); + let (r_bytes, s_bytes) = sig.split_bytes(); + Ieee1609Signature::ecdsaNistP256Signature(EcdsaP256Signature { + r_sig: EccP256CurvePoint::x_only(r_bytes.to_vec().into()), + s_sig: s_bytes.to_vec().into(), + }) + } + + /// Verify `data` against `signature` using public key `pk`. + /// Both come in IEEE 1609.2 format. + pub fn verify_with_pk( + &self, + data: &[u8], + signature: &Ieee1609Signature, + pk: &PublicVerificationKey, + ) -> bool { + let (r_bytes, s_bytes) = match signature { + Ieee1609Signature::ecdsaNistP256Signature(sig) => { + let r = match &sig.r_sig { + EccP256CurvePoint::x_only(b) => b.as_ref(), + _ => return false, + }; + (r, sig.s_sig.as_ref()) + } + _ => return false, + }; + let point = match pk { + PublicVerificationKey::ecdsaNistP256(pt) => pt, + _ => return false, + }; + let (x, y) = match point { + EccP256CurvePoint::uncompressedP256(u) => (u.x.as_ref(), u.y.as_ref()), + _ => return false, + }; + // Reconstruct verifying key from uncompressed point + let mut sec1 = Vec::with_capacity(65); + sec1.push(0x04); + sec1.extend_from_slice(x); + sec1.extend_from_slice(y); + let vk = match VerifyingKey::from_sec1_bytes(&sec1) { + Ok(v) => v, + Err(_) => return false, + }; + // Reconstruct ECDSA signature (r || s) + let mut sig_bytes = Vec::with_capacity(64); + sig_bytes.extend_from_slice(r_bytes); + sig_bytes.extend_from_slice(s_bytes); + let sig = match Signature::from_slice(&sig_bytes) { + Ok(s) => s, + Err(_) => return false, + }; + vk.verify(data, &sig).is_ok() + } + + /// Export the signing key as SEC1 DER bytes. + pub fn export_signing_key(&self, id: usize) -> Vec { + self.keys[&id].to_bytes().to_vec() + } + + /// Import a signing key from SEC1 DER bytes. Returns the new key id. + pub fn import_signing_key(&mut self, key_bytes: &[u8]) -> usize { + let sk = SigningKey::from_slice(key_bytes).expect("invalid key bytes"); + let id = self.next_id; + self.keys.insert(id, sk); + self.next_id += 1; + id + } + + /// Hash the COER-encoded certificate bytes and return the last 8 bytes (HashedId8). + pub fn hash_to_hashedid8(data: &[u8]) -> [u8; 8] { + let digest = Sha256::digest(data); + let mut out = [0u8; 8]; + out.copy_from_slice(&digest[24..32]); + out + } +} + +impl Default for EcdsaBackend { + fn default() -> Self { + Self::new() + } +} diff --git a/src/security/mod.rs b/src/security/mod.rs new file mode 100644 index 0000000..ed44d78 --- /dev/null +++ b/src/security/mod.rs @@ -0,0 +1,29 @@ +//! # Security Layer +//! +//! ETSI TS 103 097 V2.1.1 security services for C-ITS messages. +//! +//! This module provides: +//! - ASN.1 data types generated by `rasn` (COER codec) in [`security_asn`]. +//! - ECDSA signing/verification backend in [`ecdsa_backend`]. +//! - Certificate handling (ETSI TS 103 097 profiles) in [`certificate`]. +//! - Certificate chain storage and validation in [`certificate_library`]. +//! - SN-SAP interface types (ETSI TS 102 723-8) in [`sn_sap`]. +//! - Message signing service in [`sign_service`]. +//! - Message verification service in [`verify_service`]. + +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod security_asn; + +pub mod certificate; +pub mod certificate_library; +pub mod ecdsa_backend; +pub mod sign_service; +pub mod sn_sap; +pub mod time_service; +pub mod verify_service; diff --git a/src/security/security_asn.rs b/src/security/security_asn.rs new file mode 100644 index 0000000..47c3c3c --- /dev/null +++ b/src/security/security_asn.rs @@ -0,0 +1,8408 @@ +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod etsi_ts102941_base_types { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + pub use super::etsi_ts103097_module::{ + EtsiTs103097Data, EtsiTs103097DataEncrypted, EtsiTs103097DataEncryptedUnicast, + EtsiTs103097DataSigned, EtsiTs103097DataSignedAndEncryptedUnicast, + EtsiTs103097DataSignedExternalPayload, EtsiTs103097DataUnsecured, + }; + pub use super::ieee1609_dot2::{ + CertificateId, GeographicRegion, HashedData, SequenceOfPsidGroupPermissions, + SequenceOfPsidSsp, SubjectAssurance, ValidityPeriod, + }; + pub use super::ieee1609_dot2_base_types::{ + HashedId8, PublicEncryptionKey, PublicVerificationKey, Signature, Time32, + }; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("1..=255"))] + pub struct CertificateFormat(pub u8); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct CertificateSubjectAttributes { + pub id: Option, + #[rasn(identifier = "validityPeriod")] + pub validity_period: Option, + pub region: Option, + #[rasn(identifier = "assuranceLevel")] + pub assurance_level: Option, + #[rasn(identifier = "appPermissions")] + pub app_permissions: Option, + #[rasn(identifier = "certIssuePermissions")] + pub cert_issue_permissions: Option, + } + impl CertificateSubjectAttributes { + pub fn new( + id: Option, + validity_period: Option, + region: Option, + assurance_level: Option, + app_permissions: Option, + cert_issue_permissions: Option, + ) -> Self { + Self { + id, + validity_period, + region, + assurance_level, + app_permissions, + cert_issue_permissions, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + pub enum EcSignature { + #[rasn(value("0.."))] + encryptedEcSignature(Ieee1609Dot2Data), + ecSignature(EtsiTs103097DataSignedExternalPayload), + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct PublicKeys { + #[rasn(identifier = "verificationKey")] + pub verification_key: PublicVerificationKey, + #[rasn(identifier = "encryptionKey")] + pub encryption_key: Option, + } + impl PublicKeys { + pub fn new( + verification_key: PublicVerificationKey, + encryption_key: Option, + ) -> Self { + Self { + verification_key, + encryption_key, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct Version(pub Integer); +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod etsi_ts102941_messages_ca { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::etsi_ts102941_base_types::Version; + use super::etsi_ts102941_trust_lists::{ToBeSignedCrl, ToBeSignedRcaCtl, ToBeSignedTlmCtl}; + use super::etsi_ts102941_types_authorization::{ + EtsiTs102941ButterflyAuthorizationRequestX509Signed, InnerAtRequest, InnerAtResponse, + }; + use super::etsi_ts102941_types_authorization_validation::{ + AuthorizationValidationRequest, AuthorizationValidationResponse, + }; + use super::etsi_ts102941_types_ca_management::CaCertificateRequest; + use super::etsi_ts102941_types_enrolment::{InnerEcRequestSignedForPop, InnerEcResponse}; + use super::etsi_ts102941_types_link_certificate::{ + ToBeSignedLinkCertificate, ToBeSignedLinkCertificateRca, ToBeSignedLinkCertificateTlm, + }; + use super::etsi_ts103097_module::{ + EtsiTs103097Data, EtsiTs103097DataEncryptedUnicast, EtsiTs103097DataSigned, + EtsiTs103097DataSignedAndEncryptedUnicast, EtsiTs103097DataSignedExternalPayload, + }; + use super::ieee1609_dot2_dot1_aca_ra_interface::{AcaRaCertResponse, RaAcaCertRequest}; + use super::ieee1609_dot2_dot1_ee_ra_interface::{ + EeRaCertRequest, EeRaDownloadRequest, RaEeCertInfo, + }; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct AuthorizationValidationRequestMessage { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl AuthorizationValidationRequestMessage { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct AuthorizationValidationResponseMessage { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl AuthorizationValidationResponseMessage { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct ButterflyCertRequestMessage { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl ButterflyCertRequestMessage { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct ButterflyCertResponseMessage { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl ButterflyCertResponseMessage { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct CaCertificateRekeyingMessage(pub Ieee1609Dot2Data); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct CaCertificateRequestMessage(pub Ieee1609Dot2Data); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct RcaDoubleSignedLinkCertificateMessage(pub Ieee1609Dot2Data); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct RcaSingleSignedLinkCertificateMessage(pub Ieee1609Dot2Data); +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod etsi_ts102941_messages_itss_optional_privacy { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::etsi_ts102941_base_types::Version; + use super::etsi_ts102941_trust_lists::{ToBeSignedCrl, ToBeSignedRcaCtl, ToBeSignedTlmCtl}; + use super::etsi_ts102941_types_authorization::{ + EtsiTs102941ButterflyAuthorizationRequestX509Signed, InnerAtRequest, InnerAtResponse, + }; + use super::etsi_ts102941_types_enrolment::{InnerEcRequestSignedForPop, InnerEcResponse}; + use super::etsi_ts102941_types_link_certificate::{ + ToBeSignedLinkCertificate, ToBeSignedLinkCertificateTlm, + }; + use super::etsi_ts103097_module::{ + EtsiTs103097Data, EtsiTs103097DataEncryptedUnicast, EtsiTs103097DataSigned, + EtsiTs103097DataSignedAndEncryptedUnicast, + }; + use super::ieee1609_dot2_dot1_ee_ra_interface::{ + EeRaCertRequest, EeRaDownloadRequest, RaEeCertInfo, + }; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct AuthorizationRequestMessage { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl AuthorizationRequestMessage { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct AuthorizationRequestMessageWithPop { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl AuthorizationRequestMessageWithPop { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct AuthorizationResponseMessage { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl AuthorizationResponseMessage { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct ButterflyAtDownloadRequestMessage { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl ButterflyAtDownloadRequestMessage { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct ButterflyAuthorizationRequestMessage { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl ButterflyAuthorizationRequestMessage { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct ButterflyAuthorizationResponseMessage(pub Ieee1609Dot2Data); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct CertificateRevocationListMessage(pub Ieee1609Dot2Data); + #[doc = "***********"] + #[doc = "-- Messages"] + #[doc = "***********"] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EnrolmentRequestMessage { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl EnrolmentRequestMessage { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EnrolmentResponseMessage { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl EnrolmentResponseMessage { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "***********"] + #[doc = "-- EtsiTs102941Data"] + #[doc = "***********"] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EtsiTs102941Data { + #[rasn(value("1"))] + pub version: Version, + pub content: EtsiTs102941DataContent, + } + impl EtsiTs102941Data { + pub fn new(version: Version, content: EtsiTs102941DataContent) -> Self { + Self { version, content } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum EtsiTs102941DataContent { + enrolmentRequest(InnerEcRequestSignedForPop), + enrolmentResponse(InnerEcResponse), + authorizationRequest(InnerAtRequest), + authorizationResponse(InnerAtResponse), + certificateRevocationList(ToBeSignedCrl), + certificateTrustListTlm(ToBeSignedTlmCtl), + certificateTrustListRca(ToBeSignedRcaCtl), + authorizationValidationRequest(()), + authorizationValidationResponse(()), + caCertificateRequest(()), + #[rasn(extension_addition)] + linkCertificateTlm(ToBeSignedLinkCertificateTlm), + #[rasn(extension_addition)] + singleSignedLinkCertificateRca(()), + #[rasn(extension_addition)] + doubleSignedlinkCertificateRca(()), + #[rasn(extension_addition)] + butterflyAuthorizationRequest(EeRaCertRequest), + #[rasn(extension_addition)] + x509SignedbutterflyAuthorizationRequest( + EtsiTs102941ButterflyAuthorizationRequestX509Signed, + ), + #[rasn(extension_addition)] + butterflyAuthorizationResponse(RaEeCertInfo), + #[rasn(extension_addition)] + butterflyCertificateRequest(()), + #[rasn(extension_addition)] + butterflyCertificateResponse(()), + #[rasn(extension_addition)] + butterflyAtDownloadRequest(EeRaDownloadRequest), + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct RcaCertificateTrustListMessage(pub Ieee1609Dot2Data); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct TlmCertificateTrustListMessage(pub Ieee1609Dot2Data); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct TlmLinkCertificateMessage(pub Ieee1609Dot2Data); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct X509SignedButterflyAuthorizationRequestMessage(pub EtsiTs103097Data); +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod etsi_ts102941_trust_lists { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::etsi_ts102941_base_types::{HashedId8, Time32, Version}; + use super::etsi_ts103097_module::{ + EtsiTs103097Certificate, EtsiTs103097DataSigned, EtsiTs103097DataSignedAndEncrypted, + }; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct AaEntry { + #[rasn(identifier = "aaCertificate")] + pub aa_certificate: EtsiTs103097Certificate, + #[rasn(identifier = "accessPoint")] + pub access_point: Url, + } + impl AaEntry { + pub fn new(aa_certificate: EtsiTs103097Certificate, access_point: Url) -> Self { + Self { + aa_certificate, + access_point, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct CrlEntry(pub HashedId8); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum CtlCommand { + add(CtlEntry), + delete(CtlDelete), + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum CtlDelete { + cert(HashedId8), + dc(DcDelete), + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum CtlEntry { + rca(RootCaEntry), + ea(EaEntry), + aa(AaEntry), + dc(DcEntry), + tlm(TlmEntry), + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct CtlFormat { + pub version: Version, + #[rasn(identifier = "nextUpdate")] + pub next_update: Time32, + #[rasn(identifier = "isFullCtl")] + pub is_full_ctl: bool, + #[rasn(value("0..=255"), identifier = "ctlSequence")] + pub ctl_sequence: u8, + #[rasn(identifier = "ctlCommands")] + pub ctl_commands: SequenceOf, + } + impl CtlFormat { + pub fn new( + version: Version, + next_update: Time32, + is_full_ctl: bool, + ctl_sequence: u8, + ctl_commands: SequenceOf, + ) -> Self { + Self { + version, + next_update, + is_full_ctl, + ctl_sequence, + ctl_commands, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct DcDelete(pub Url); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct DcEntry { + pub url: Url, + pub cert: SequenceOf, + } + impl DcEntry { + pub fn new(url: Url, cert: SequenceOf) -> Self { + Self { url, cert } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct DeltaCtl(pub CtlFormat); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EaEntry { + #[rasn(identifier = "eaCertificate")] + pub ea_certificate: EtsiTs103097Certificate, + #[rasn(identifier = "aaAccessPoint")] + pub aa_access_point: Url, + #[rasn(identifier = "itsAccessPoint")] + pub its_access_point: Option, + } + impl EaEntry { + pub fn new( + ea_certificate: EtsiTs103097Certificate, + aa_access_point: Url, + its_access_point: Option, + ) -> Self { + Self { + ea_certificate, + aa_access_point, + its_access_point, + } + } + } + #[doc = "***********"] + #[doc = "-- CTL"] + #[doc = "***********"] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct FullCtl(pub CtlFormat); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct RootCaEntry { + #[rasn(identifier = "selfsignedRootCa")] + pub selfsigned_root_ca: EtsiTs103097Certificate, + #[rasn(identifier = "successorTo")] + pub successor_to: Option, + } + impl RootCaEntry { + pub fn new( + selfsigned_root_ca: EtsiTs103097Certificate, + successor_to: Option, + ) -> Self { + Self { + selfsigned_root_ca, + successor_to, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct TlmEntry { + #[rasn(identifier = "selfSignedTLMCertificate")] + pub self_signed_tlmcertificate: EtsiTs103097Certificate, + #[rasn(identifier = "successorTo")] + pub successor_to: Option, + #[rasn(identifier = "accessPoint")] + pub access_point: Url, + } + impl TlmEntry { + pub fn new( + self_signed_tlmcertificate: EtsiTs103097Certificate, + successor_to: Option, + access_point: Url, + ) -> Self { + Self { + self_signed_tlmcertificate, + successor_to, + access_point, + } + } + } + #[doc = "***********"] + #[doc = "-- CRL"] + #[doc = "***********"] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct ToBeSignedCrl { + pub version: Version, + #[rasn(identifier = "thisUpdate")] + pub this_update: Time32, + #[rasn(identifier = "nextUpdate")] + pub next_update: Time32, + pub entries: SequenceOf, + } + impl ToBeSignedCrl { + pub fn new( + version: Version, + this_update: Time32, + next_update: Time32, + entries: SequenceOf, + ) -> Self { + Self { + version, + this_update, + next_update, + entries, + } + } + } + #[doc = "***********"] + #[doc = "-- RCA CTL"] + #[doc = "***********"] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct ToBeSignedRcaCtl(pub CtlFormat); + #[doc = "***********"] + #[doc = "-- TLM CTL"] + #[doc = "***********"] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct ToBeSignedTlmCtl(pub CtlFormat); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct Url(pub Ia5String); +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod etsi_ts102941_types_authorization { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::etsi_ts102941_base_types::{ + CertificateFormat, CertificateSubjectAttributes, EcSignature, HashedId8, PublicKeys, + Version, + }; + use super::etsi_ts103097_module::{EtsiTs103097Certificate, EtsiTs103097DataSigned}; + use super::ieee1609_dot2_dot1_ee_ra_interface::EeRaInterfacePdu; + use super::ieee1609_dot2_dot1_protocol::{ + Ieee1609Dot2DataSignedX509AuthenticatedCertRequest, ScmsPduScoped, SignerSingleX509Cert, + }; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "***********"] + #[doc = "-- AuthorizationRequest/Response"] + #[doc = "***********"] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash, Copy)] + #[rasn(enumerated)] + #[non_exhaustive] + pub enum AuthorizationResponseCode { + ok = 0, + #[rasn(identifier = "its-aa-cantparse")] + its_aa_cantparse = 1, + #[rasn(identifier = "its-aa-badcontenttype")] + its_aa_badcontenttype = 2, + #[rasn(identifier = "its-aa-imnottherecipient")] + its_aa_imnottherecipient = 3, + #[rasn(identifier = "its-aa-unknownencryptionalgorithm")] + its_aa_unknownencryptionalgorithm = 4, + #[rasn(identifier = "its-aa-decryptionfailed")] + its_aa_decryptionfailed = 5, + #[rasn(identifier = "its-aa-keysdontmatch")] + its_aa_keysdontmatch = 6, + #[rasn(identifier = "its-aa-incompleterequest")] + its_aa_incompleterequest = 7, + #[rasn(identifier = "its-aa-invalidencryptionkey")] + its_aa_invalidencryptionkey = 8, + #[rasn(identifier = "its-aa-outofsyncrequest")] + its_aa_outofsyncrequest = 9, + #[rasn(identifier = "its-aa-unknownea")] + its_aa_unknownea = 10, + #[rasn(identifier = "its-aa-invalidea")] + its_aa_invalidea = 11, + #[rasn(identifier = "its-aa-deniedpermissions")] + its_aa_deniedpermissions = 12, + #[rasn(identifier = "aa-ea-cantreachea")] + aa_ea_cantreachea = 13, + #[rasn(identifier = "ea-aa-cantparse")] + ea_aa_cantparse = 14, + #[rasn(identifier = "ea-aa-badcontenttype")] + ea_aa_badcontenttype = 15, + #[rasn(identifier = "ea-aa-imnottherecipient")] + ea_aa_imnottherecipient = 16, + #[rasn(identifier = "ea-aa-unknownencryptionalgorithm")] + ea_aa_unknownencryptionalgorithm = 17, + #[rasn(identifier = "ea-aa-decryptionfailed")] + ea_aa_decryptionfailed = 18, + invalidaa = 19, + invalidaasignature = 20, + wrongea = 21, + unknownits = 22, + invalidsignature = 23, + invalidencryptionkey = 24, + deniedpermissions = 25, + deniedtoomanycerts = 26, + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn( + automatic_tags, + identifier = "EtsiTs102941ButterflyAuthorizationRequest-X509Signed" + )] + pub struct EtsiTs102941ButterflyAuthorizationRequestX509Signed { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl EtsiTs102941ButterflyAuthorizationRequestX509Signed { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct InnerAtRequest { + #[rasn(identifier = "publicKeys")] + pub public_keys: PublicKeys, + #[rasn(size("32"), identifier = "hmacKey")] + pub hmac_key: OctetString, + #[rasn(identifier = "sharedAtRequest")] + pub shared_at_request: SharedAtRequest, + #[rasn(identifier = "ecSignature")] + pub ec_signature: EcSignature, + } + impl InnerAtRequest { + pub fn new( + public_keys: PublicKeys, + hmac_key: OctetString, + shared_at_request: SharedAtRequest, + ec_signature: EcSignature, + ) -> Self { + Self { + public_keys, + hmac_key, + shared_at_request, + ec_signature, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct InnerAtResponse { + #[rasn(size("16"), identifier = "requestHash")] + pub request_hash: OctetString, + #[rasn(identifier = "responseCode")] + pub response_code: AuthorizationResponseCode, + pub certificate: Option, + } + impl InnerAtResponse { + pub fn new( + request_hash: OctetString, + response_code: AuthorizationResponseCode, + certificate: Option, + ) -> Self { + Self { + request_hash, + response_code, + certificate, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct SharedAtRequest { + #[rasn(identifier = "eaId")] + pub ea_id: HashedId8, + #[rasn(size("16"), identifier = "keyTag")] + pub key_tag: OctetString, + #[rasn(identifier = "certificateFormat")] + pub certificate_format: CertificateFormat, + #[rasn(value("0.."), identifier = "requestedSubjectAttributes")] + pub requested_subject_attributes: CertificateSubjectAttributes, + } + impl SharedAtRequest { + pub fn new( + ea_id: HashedId8, + key_tag: OctetString, + certificate_format: CertificateFormat, + requested_subject_attributes: CertificateSubjectAttributes, + ) -> Self { + Self { + ea_id, + key_tag, + certificate_format, + requested_subject_attributes, + } + } + } +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod etsi_ts102941_types_authorization_validation { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::etsi_ts102941_base_types::{ + CertificateFormat, CertificateSubjectAttributes, EcSignature, HashedId8, PublicKeys, + Version, + }; + use super::etsi_ts102941_types_authorization::SharedAtRequest; + use super::etsi_ts103097_module::EtsiTs103097Certificate; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct AuthorizationValidationRequest { + #[rasn(identifier = "sharedAtRequest")] + pub shared_at_request: SharedAtRequest, + #[rasn(identifier = "ecSignature")] + pub ec_signature: EcSignature, + } + impl AuthorizationValidationRequest { + pub fn new(shared_at_request: SharedAtRequest, ec_signature: EcSignature) -> Self { + Self { + shared_at_request, + ec_signature, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct AuthorizationValidationResponse { + #[rasn(size("16"), identifier = "requestHash")] + pub request_hash: OctetString, + #[rasn(identifier = "responseCode")] + pub response_code: AuthorizationValidationResponseCode, + #[rasn(value("0.."), identifier = "confirmedSubjectAttributes")] + pub confirmed_subject_attributes: Option, + } + impl AuthorizationValidationResponse { + pub fn new( + request_hash: OctetString, + response_code: AuthorizationValidationResponseCode, + confirmed_subject_attributes: Option, + ) -> Self { + Self { + request_hash, + response_code, + confirmed_subject_attributes, + } + } + } + #[doc = "***********"] + #[doc = "-- AuthorizationValidationRequest/Response"] + #[doc = "***********"] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash, Copy)] + #[rasn(enumerated)] + #[non_exhaustive] + pub enum AuthorizationValidationResponseCode { + ok = 0, + cantparse = 1, + badcontenttype = 2, + imnottherecipient = 3, + unknownencryptionalgorithm = 4, + decryptionfailed = 5, + invalidaa = 6, + invalidaasignature = 7, + wrongea = 8, + unknownits = 9, + invalidsignature = 10, + invalidencryptionkey = 11, + deniedpermissions = 12, + deniedtoomanycerts = 13, + deniedrequest = 14, + } +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod etsi_ts102941_types_ca_management { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::etsi_ts102941_base_types::{CertificateSubjectAttributes, PublicKeys}; + use super::etsi_ts103097_module::{EtsiTs103097Certificate, EtsiTs103097DataSigned}; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "***********"] + #[doc = "-- CA certificate request "] + #[doc = "***********"] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct CaCertificateRequest { + #[rasn(identifier = "publicKeys")] + pub public_keys: PublicKeys, + #[rasn(identifier = "requestedSubjectAttributes")] + pub requested_subject_attributes: CertificateSubjectAttributes, + } + impl CaCertificateRequest { + pub fn new( + public_keys: PublicKeys, + requested_subject_attributes: CertificateSubjectAttributes, + ) -> Self { + Self { + public_keys, + requested_subject_attributes, + } + } + } +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod etsi_ts102941_types_enrolment { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::etsi_ts102941_base_types::{ + CertificateFormat, CertificateSubjectAttributes, EcSignature, HashedId8, PublicKeys, + Version, + }; + use super::etsi_ts103097_module::{EtsiTs103097Certificate, EtsiTs103097DataSigned}; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "***********"] + #[doc = "-- EnrolmentRequest/Response"] + #[doc = "***********"] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash, Copy)] + #[rasn(enumerated)] + #[non_exhaustive] + pub enum EnrolmentResponseCode { + ok = 0, + cantparse = 1, + badcontenttype = 2, + imnottherecipient = 3, + unknownencryptionalgorithm = 4, + decryptionfailed = 5, + unknownits = 6, + invalidsignature = 7, + invalidencryptionkey = 8, + baditsstatus = 9, + incompleterequest = 10, + deniedpermissions = 11, + invalidkeys = 12, + deniedrequest = 13, + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct InnerEcRequest { + #[rasn(identifier = "itsId")] + pub its_id: OctetString, + #[rasn(identifier = "certificateFormat")] + pub certificate_format: CertificateFormat, + #[rasn(identifier = "publicKeys")] + pub public_keys: PublicKeys, + #[rasn(value("0.."), identifier = "requestedSubjectAttributes")] + pub requested_subject_attributes: CertificateSubjectAttributes, + } + impl InnerEcRequest { + pub fn new( + its_id: OctetString, + certificate_format: CertificateFormat, + public_keys: PublicKeys, + requested_subject_attributes: CertificateSubjectAttributes, + ) -> Self { + Self { + its_id, + certificate_format, + public_keys, + requested_subject_attributes, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct InnerEcRequestSignedForPop(pub Ieee1609Dot2Data); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct InnerEcResponse { + #[rasn(size("16"), identifier = "requestHash")] + pub request_hash: OctetString, + #[rasn(identifier = "responseCode")] + pub response_code: EnrolmentResponseCode, + pub certificate: Option, + } + impl InnerEcResponse { + pub fn new( + request_hash: OctetString, + response_code: EnrolmentResponseCode, + certificate: Option, + ) -> Self { + Self { + request_hash, + response_code, + certificate, + } + } + } +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod etsi_ts102941_types_link_certificate { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::etsi_ts102941_base_types::{HashedData, Time32}; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "***********"] + #[doc = "-- Link certificate messages "] + #[doc = "***********"] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct ToBeSignedLinkCertificate { + #[rasn(identifier = "expiryTime")] + pub expiry_time: Time32, + #[rasn(identifier = "certificateHash")] + pub certificate_hash: HashedData, + } + impl ToBeSignedLinkCertificate { + pub fn new(expiry_time: Time32, certificate_hash: HashedData) -> Self { + Self { + expiry_time, + certificate_hash, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct ToBeSignedLinkCertificateRca(pub ToBeSignedLinkCertificate); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct ToBeSignedLinkCertificateTlm(pub ToBeSignedLinkCertificate); +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod etsi_ts103097_extension_module { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::ieee1609_dot2_base_types::{HashedId8, Time32}; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EtsiOriginatingHeaderInfoExtension { + #[rasn(value("0..=255"))] + pub id: u8, + pub content: Any, + } + impl EtsiOriginatingHeaderInfoExtension { + pub fn new(id: u8, content: Any) -> Self { + Self { id, content } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EtsiTs102941CrlRequest { + #[rasn(identifier = "issuerId")] + pub issuer_id: HashedId8, + #[rasn(identifier = "lastKnownUpdate")] + pub last_known_update: Option, + } + impl EtsiTs102941CrlRequest { + pub fn new(issuer_id: HashedId8, last_known_update: Option) -> Self { + Self { + issuer_id, + last_known_update, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EtsiTs102941CtlRequest { + #[rasn(identifier = "issuerId")] + pub issuer_id: HashedId8, + #[rasn(value("0..=255"), identifier = "lastKnownCtlSequence")] + pub last_known_ctl_sequence: Option, + } + impl EtsiTs102941CtlRequest { + pub fn new(issuer_id: HashedId8, last_known_ctl_sequence: Option) -> Self { + Self { + issuer_id, + last_known_ctl_sequence, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct EtsiTs102941DeltaCtlRequest(pub EtsiTs102941CtlRequest); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct EtsiTs103097HeaderInfoExtensionId(pub ExtId); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("1"))] + pub struct ExtensionModuleVersion(pub u8); + pub const ETSI_TS102941_CRL_REQUEST_ID: EtsiTs103097HeaderInfoExtensionId = + EtsiTs103097HeaderInfoExtensionId(ExtId(1)); + #[doc = "'01'H"] + pub const ETSI_TS102941_DELTA_CTL_REQUEST_ID: EtsiTs103097HeaderInfoExtensionId = + EtsiTs103097HeaderInfoExtensionId(ExtId(2)); +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod etsi_ts103097_module { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::etsi_ts103097_extension_module::ExtensionModuleVersion; + use super::ieee1609_dot2::{Certificate, Ieee1609Dot2Data}; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct EtsiTs103097Certificate(pub Certificate); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct EtsiTs103097Data(pub Ieee1609Dot2Data); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, identifier = "EtsiTs103097Data-SignedExternalPayload")] + pub struct EtsiTs103097DataSignedExternalPayload(pub EtsiTs103097Data); + // Type aliases for WITH COMPONENTS constrained types (constraints removed for rasn compat) + pub type EtsiTs103097DataSigned = EtsiTs103097Data; + pub type EtsiTs103097DataEncrypted = EtsiTs103097Data; + pub type EtsiTs103097DataEncryptedUnicast = EtsiTs103097Data; + pub type EtsiTs103097DataSignedAndEncryptedUnicast = EtsiTs103097Data; + pub type EtsiTs103097DataSignedAndEncrypted = EtsiTs103097Data; + pub type EtsiTs103097DataUnsecured = EtsiTs103097Data; +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2 { + extern crate alloc; + use super::etsi_ts103097_extension_module::EtsiOriginatingHeaderInfoExtension; + pub use super::ieee1609_dot2_base_types::*; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This type is defined only for backwards compatibility."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct Aes128CcmCiphertext(pub One28BitCcmCiphertext); + #[doc = "* "] + #[doc = " * @class AesCcmCiphertext"] + #[doc = " *"] + #[doc = " * @brief This data structure encapsulates an encrypted ciphertext for the"] + #[doc = " * AES-CCM symmetric algorithm. It contains the following fields:"] + #[doc = " *"] + #[doc = " *

The ciphertext is 16 bytes longer than the corresponding plaintext."] + #[doc = " *"] + #[doc = " *

The plaintext resulting from a correct decryption of the"] + #[doc = " * ciphertext is a COER-encoded Ieee1609Dot2Data structure."] + #[doc = " *"] + #[doc = " * @param nonce contains the nonce N as specified in 5.3.7. "] + #[doc = " *"] + #[doc = " * @param ccmCiphertext contains the ciphertext C as specified in 5.3.7."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct AesCcmCiphertext { + #[rasn(size("12"))] + pub nonce: OctetString, + #[rasn(identifier = "ccmCiphertext")] + pub ccm_ciphertext: Opaque, + } + impl AesCcmCiphertext { + pub fn new(nonce: OctetString, ccm_ciphertext: Opaque) -> Self { + Self { + nonce, + ccm_ciphertext, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains an individual AppExtension. AppExtensions "] + #[doc = " * specified in this standard are drawn from the ASN.1 Information Object Set "] + #[doc = " * SetCertExtensions. This set, and its use in the AppExtension type, is "] + #[doc = " * structured so that each AppExtension is associated with a "] + #[doc = " * CertIssueExtension and a CertRequestExtension and all are identified by "] + #[doc = " * the same id value. In this structure:"] + #[doc = " * "] + #[doc = " * @param id: identifies the extension type."] + #[doc = " * "] + #[doc = " * @param content: provides the content of the extension."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct AppExtension { + pub id: ExtId, + pub content: Any, + } + impl AppExtension { + pub fn new(id: ExtId, content: Any) -> Self { + Self { id, content } + } + } + #[doc = " Inner type "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + pub enum CertIssueExtensionPermissions { + specific(Any), + all(()), + } + #[doc = "*"] + #[doc = " * @brief This field contains an individual CertIssueExtension. "] + #[doc = " * CertIssueExtensions specified in this standard are drawn from the ASN.1 "] + #[doc = " * Information Object Set SetCertExtensions. This set, and its use in the "] + #[doc = " * CertIssueExtension type, is structured so that each CertIssueExtension "] + #[doc = " * is associated with a AppExtension and a CertRequestExtension and all are "] + #[doc = " * identified by the same id value. In this structure:"] + #[doc = " * "] + #[doc = " * @param id: identifies the extension type."] + #[doc = " * "] + #[doc = " * @param permissions: indicates the permissions. Within this field."] + #[doc = " * - all indicates that the certificate is entitled to issue all values of"] + #[doc = " * the extension."] + #[doc = " * - specific is used to specify which values of the extension may be "] + #[doc = " * issued in the case where all does not apply."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct CertIssueExtension { + pub id: ExtId, + pub permissions: CertIssueExtensionPermissions, + } + impl CertIssueExtension { + pub fn new(id: ExtId, permissions: CertIssueExtensionPermissions) -> Self { + Self { id, permissions } + } + } + #[doc = " Inner type "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + pub enum CertRequestExtensionPermissions { + content(Any), + all(()), + } + #[doc = "*"] + #[doc = " * @brief This field contains an individual CertRequestExtension. "] + #[doc = " * CertRequestExtensions specified in this standard are drawn from the "] + #[doc = " * ASN.1 Information Object Set SetCertExtensions. This set, and its use in "] + #[doc = " * the CertRequestExtension type, is structured so that each "] + #[doc = " * CertRequestExtension is associated with a AppExtension and a "] + #[doc = " * CertRequestExtension and all are identified by the same id value. In this "] + #[doc = " * structure:"] + #[doc = " * "] + #[doc = " * @param id: identifies the extension type."] + #[doc = " * "] + #[doc = " * @param permissions: indicates the permissions. Within this field."] + #[doc = " * - all indicates that the certificate is entitled to issue all values of"] + #[doc = " * the extension."] + #[doc = " * - specific is used to specify which values of the extension may be "] + #[doc = " * issued in the case where all does not apply."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct CertRequestExtension { + pub id: ExtId, + pub permissions: CertRequestExtensionPermissions, + } + impl CertRequestExtension { + pub fn new(id: ExtId, permissions: CertRequestExtensionPermissions) -> Self { + Self { id, permissions } + } + } + #[doc = "***************************************************************************"] + #[doc = " Certificates and other Security Management "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This structure is a profile of the structure CertificateBase which"] + #[doc = " * specifies the valid combinations of fields to transmit implicit and"] + #[doc = " * explicit certificates."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization "] + #[doc = " * for the relevant operations specified in 6.1.2. The canonicalization "] + #[doc = " * applies to the CertificateBase."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct Certificate(pub CertificateBase); + #[doc = "*"] + #[doc = " * @brief The fields in this structure have the following meaning:"] + #[doc = " *"] + #[doc = " * @param version: contains the version of the certificate format. In this"] + #[doc = " * version of the data structures, this field is set to 3."] + #[doc = " *"] + #[doc = " * @param type: states whether the certificate is implicit or explicit. This"] + #[doc = " * field is set to explicit for explicit certificates and to implicit for"] + #[doc = " * implicit certificates. See ExplicitCertificate and ImplicitCertificate for"] + #[doc = " * more details."] + #[doc = " *"] + #[doc = " * @param issuer: identifies the issuer of the certificate."] + #[doc = " *"] + #[doc = " * @param toBeSigned: is the certificate contents. This field is an input to"] + #[doc = " * the hash when generating or verifying signatures for an explicit"] + #[doc = " * certificate, or generating or verifying the public key from the"] + #[doc = " * reconstruction value for an implicit certificate. The details of how this"] + #[doc = " * field are encoded are given in the description of the"] + #[doc = " * ToBeSignedCertificate type."] + #[doc = " *"] + #[doc = " * @param signature: is included in an ExplicitCertificate. It is the"] + #[doc = " * signature, calculated by the signer identified in the issuer field, over"] + #[doc = " * the hash of toBeSigned. The hash is calculated as specified in 5.3.1, where:"] + #[doc = " * - Data input is the encoding of toBeSigned following the COER."] + #[doc = " * - Signer identifier input depends on the verification type, which in"] + #[doc = " * turn depends on the choice indicated by issuer. If the choice indicated by"] + #[doc = " * issuer is self, the verification type is self-signed and the signer"] + #[doc = " * identifier input is the empty string. If the choice indicated by issuer is"] + #[doc = " * not self, the verification type is certificate and the signer identifier"] + #[doc = " * input is the canonicalized COER encoding of the certificate indicated by"] + #[doc = " * issuer. The canonicalization is carried out as specified in the "] + #[doc = " * Canonicalization section of this subclause."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization "] + #[doc = " * for the relevant operations specified in 6.1.2. The canonicalization "] + #[doc = " * applies to the ToBeSignedCertificate and to the Signature."] + #[doc = " *"] + #[doc = " * @note Whole-certificate hash: If the entirety of a certificate is hashed "] + #[doc = " * to calculate a HashedId3, HashedId8, or HashedId10, the algorithm used for "] + #[doc = " * this purpose is known as the whole-certificate hash. The method used to "] + #[doc = " * determine the whole-certificate hash algorithm is specified in 5.3.9.2."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct CertificateBase { + #[rasn(value("3"))] + pub version: Uint8, + #[rasn(identifier = "type")] + pub r_type: CertificateType, + pub issuer: IssuerIdentifier, + #[rasn(identifier = "toBeSigned")] + pub to_be_signed: ToBeSignedCertificate, + pub signature: Option, + } + impl CertificateBase { + pub fn new( + version: Uint8, + r_type: CertificateType, + issuer: IssuerIdentifier, + to_be_signed: ToBeSignedCertificate, + signature: Option, + ) -> Self { + Self { + version, + r_type, + issuer, + to_be_signed, + signature, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains information that is used to identify the"] + #[doc = " * certificate holder if necessary."] + #[doc = " *"] + #[doc = " * @param linkageData: is used to identify the certificate for revocation"] + #[doc = " * purposes in the case of certificates that appear on linked certificate"] + #[doc = " * CRLs. See 5.1.3 and 7.3 for further discussion."] + #[doc = " *"] + #[doc = " * @param name: is used to identify the certificate holder in the case of"] + #[doc = " * non-anonymous certificates. The contents of this field are a matter of"] + #[doc = " * policy and are expected to be human-readable."] + #[doc = " *"] + #[doc = " * @param binaryId: supports identifiers that are not human-readable."] + #[doc = " *"] + #[doc = " * @param none: indicates that the certificate does not include an identifier."] + #[doc = " *"] + #[doc = " * @note Critical information fields:"] + #[doc = " * - If present, this is a critical information field as defined in 5.2.6."] + #[doc = " * An implementation that does not recognize the choice indicated in this"] + #[doc = " * field shall reject a signed SPDU as invalid."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum CertificateId { + linkageData(LinkageData), + name(Hostname), + #[rasn(size("1..=64"))] + binaryId(OctetString), + none(()), + } + #[doc = "*"] + #[doc = " * @brief This enumerated type indicates whether a certificate is explicit or"] + #[doc = " * implicit."] + #[doc = " *"] + #[doc = " * @note Critical information fields: If present, this is a critical"] + #[doc = " * information field as defined in 5.2.5. An implementation that does not"] + #[doc = " * recognize the indicated CHOICE for this type when verifying a signed SPDU"] + #[doc = " * shall indicate that the signed SPDU is invalid in the sense of 4.2.2.3.2, "] + #[doc = " * that is, it is invalid in the sense that its validity cannot be "] + #[doc = " * established."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash, Copy)] + #[rasn(enumerated)] + #[non_exhaustive] + pub enum CertificateType { + explicit = 0, + implicit = 1, + } + #[doc = " Anonymous SEQUENCE OF member "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn( + delegate, + identifier = "IOFR$IEEE1609DOT2-HEADERINFO-CONTRIBUTED-EXTENSION$&Extn" + )] + pub struct AnonymousContributedExtensionBlockExtns(pub Any); + #[doc = " Inner type "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, size("1.."))] + pub struct ContributedExtensionBlockExtns( + pub SequenceOf, + ); + #[doc = "*"] + #[doc = " * @brief This data structure defines the format of an extension block"] + #[doc = " * provided by an identified contributor by using the temnplate provided"] + #[doc = " * in the class IEEE1609DOT2-HEADERINFO-CONTRIBUTED-EXTENSION constraint"] + #[doc = " * to the objects in the set Ieee1609Dot2HeaderInfoContributedExtensions."] + #[doc = " *"] + #[doc = " * @param contributorId: uniquely identifies the contributor."] + #[doc = " *"] + #[doc = " * @param extns: contains a list of extensions from that contributor. "] + #[doc = " * Extensions are expected and not required to follow the format specified "] + #[doc = " * in 6.5."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct ContributedExtensionBlock { + #[rasn(identifier = "contributorId")] + pub contributor_id: HeaderInfoContributorId, + pub extns: ContributedExtensionBlockExtns, + } + impl ContributedExtensionBlock { + pub fn new( + contributor_id: HeaderInfoContributorId, + extns: ContributedExtensionBlockExtns, + ) -> Self { + Self { + contributor_id, + extns, + } + } + } + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, size("1.."))] + pub struct ContributedExtensionBlocks(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This data structure is used to perform a countersignature over an"] + #[doc = " * already-signed SPDU. This is the profile of an Ieee1609Dot2Data containing"] + #[doc = " * a signedData. The tbsData within content is composed of a payload"] + #[doc = " * containing the hash (extDataHash) of the externally generated, pre-signed"] + #[doc = " * SPDU over which the countersignature is performed."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct Countersignature(pub Ieee1609Dot2Data); + #[doc = "***************************************************************************"] + #[doc = " Encrypted Data "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This data structure encodes data that has been encrypted to one or "] + #[doc = " * more recipients using the recipients’ public or symmetric keys as "] + #[doc = " * specified in 5.3.4."] + #[doc = " *"] + #[doc = " * @param recipients: contains one or more RecipientInfos. These entries may"] + #[doc = " * be more than one RecipientInfo, and more than one type of RecipientInfo,"] + #[doc = " * as long as all entries are indicating or containing the same data encryption"] + #[doc = " * key."] + #[doc = " *"] + #[doc = " * @param ciphertext: contains the encrypted data. This is the encryption of"] + #[doc = " * an encoded Ieee1609Dot2Data structure as specified in 5.3.4.2."] + #[doc = " *"] + #[doc = " * @note Critical information fields:"] + #[doc = " * - If present, recipients is a critical information field as defined in"] + #[doc = " * 5.2.6. An implementation that does not support the number of RecipientInfo"] + #[doc = " * in recipients when decrypted shall indicate that the encrypted SPDU could"] + #[doc = " * not be decrypted due to unsupported critical information fields. A"] + #[doc = " * compliant implementation shall support recipients fields containing at"] + #[doc = " * least eight entries."] + #[doc = " *"] + #[doc = " * @note If the plaintext is raw data, i.e., it has not been output from a "] + #[doc = " * previous operation of the SDS, then it is trivial to encapsulate it in an"] + #[doc = " * Ieee1609Dot2Data of type unsecuredData as noted in 4.2.2.2.2. For example,"] + #[doc = " * '03 80 08 01 23 45 67 89 AB CD EF' is the C-OER encoding of '01 23 45 67 "] + #[doc = " * 89 AB CD EF' encapsulated in an Ieee1609Dot2Data of type unsecuredData. "] + #[doc = " * The first byte of the encoding 03 is the protocolVersion, the second byte "] + #[doc = " * 80 indicates the choice unsecuredData, and the third byte 08 is the length "] + #[doc = " * of the raw data '01 23 45 67 89 AB CD EF'."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EncryptedData { + pub recipients: SequenceOfRecipientInfo, + pub ciphertext: SymmetricCiphertext, + } + impl EncryptedData { + pub fn new(recipients: SequenceOfRecipientInfo, ciphertext: SymmetricCiphertext) -> Self { + Self { + recipients, + ciphertext, + } + } + } + #[doc = "*"] + #[doc = " * @brief This data structure contains an encrypted data encryption key, "] + #[doc = " * where the data encryption key is input to the data encryption key "] + #[doc = " * encryption process with no headers, encapsulation, or length indication."] + #[doc = " *"] + #[doc = " * Critical information fields: If present and applicable to"] + #[doc = " * the receiving SDEE, this is a critical information field as defined in"] + #[doc = " * 5.2.6. If an implementation receives an encrypted SPDU and determines that"] + #[doc = " * one or more RecipientInfo fields are relevant to it, and if all of those"] + #[doc = " * RecipientInfos contain an EncryptedDataEncryptionKey such that the"] + #[doc = " * implementation does not recognize the indicated CHOICE, the implementation"] + #[doc = " * shall indicate that the encrypted SPDU is not decryptable."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum EncryptedDataEncryptionKey { + eciesNistP256(EciesP256EncryptedKey), + eciesBrainpoolP256r1(EciesP256EncryptedKey), + #[rasn(extension_addition)] + ecencSm2256(EcencP256EncryptedKey), + } + #[doc = "*"] + #[doc = " * @brief This type indicates which type of permissions may appear in"] + #[doc = " * end-entity certificates the chain of whose permissions passes through the"] + #[doc = " * PsidGroupPermissions field containing this value. If app is indicated, the"] + #[doc = " * end-entity certificate may contain an appPermissions field. If enroll is"] + #[doc = " * indicated, the end-entity certificate may contain a certRequestPermissions"] + #[doc = " * field."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct EndEntityType(pub FixedBitString<8usize>); + #[doc = "*"] + #[doc = " * @brief This is a profile of the CertificateBase structure providing all"] + #[doc = " * the fields necessary for an explicit certificate, and no others."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct ExplicitCertificate(pub CertificateBase); + #[doc = "*"] + #[doc = " * @brief This structure contains the hash of some data with a specified hash"] + #[doc = " * algorithm. See 5.3.3 for specification of the permitted hash algorithms."] + #[doc = " *"] + #[doc = " * @param sha256HashedData: indicates data hashed with SHA-256."] + #[doc = " *"] + #[doc = " * @param sha384HashedData: indicates data hashed with SHA-384."] + #[doc = " * "] + #[doc = " * @param sm3HashedData: indicates data hashed with SM3."] + #[doc = " *"] + #[doc = " * @note Critical information fields: If present, this is a critical "] + #[doc = " * information field as defined in 5.2.6. An implementation that does not "] + #[doc = " * recognize the indicated CHOICE for this type when verifying a signed SPDU "] + #[doc = " * shall indicate that the signed SPDU is invalid in the sense of 4.2.2.3.2, "] + #[doc = " * that is, it is invalid in the sense that its validity cannot be established."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum HashedData { + sha256HashedData(HashedId32), + #[rasn(extension_addition)] + sha384HashedData(HashedId48), + #[rasn(extension_addition)] + sm3HashedData(HashedId32), + } + #[doc = "*"] + #[doc = " * @brief This structure contains information that is used to establish"] + #[doc = " * validity by the criteria of 5.2."] + #[doc = " *"] + #[doc = " * @param psid: indicates the application area with which the sender is"] + #[doc = " * claiming the payload is to be associated."] + #[doc = " *"] + #[doc = " * @param generationTime: indicates the time at which the structure was"] + #[doc = " * generated. See 5.2.5.2.2 and 5.2.5.2.3 for discussion of the use of this"] + #[doc = " * field."] + #[doc = " *"] + #[doc = " * @param expiryTime: if present, contains the time after which the data"] + #[doc = " * is no longer considered relevant. If both generationTime and"] + #[doc = " * expiryTime are present, the signed SPDU is invalid if generationTime is"] + #[doc = " * not strictly earlier than expiryTime."] + #[doc = " *"] + #[doc = " * @param generationLocation: if present, contains the location at which the"] + #[doc = " * signature was generated."] + #[doc = " *"] + #[doc = " * @param p2pcdLearningRequest: if present, is used by the SDS to request "] + #[doc = " * certificates for which it has seen identifiers and does not know the "] + #[doc = " * entire certificate. A specification of this peer-to-peer certificate "] + #[doc = " * distribution (P2PCD) mechanism is given in Clause 8. This field is used "] + #[doc = " * for the separate-certificate-pdu flavor of P2PCD and shall only be present "] + #[doc = " * if inlineP2pcdRequest is not present. The HashedId3 is calculated with the "] + #[doc = " * whole-certificate hash algorithm, determined as described in 6.4.3, "] + #[doc = " * applied to the COER-encoded certificate, canonicalized as defined in the "] + #[doc = " * definition of Certificate."] + #[doc = " *"] + #[doc = " * @param missingCrlIdentifier: if present, is used by the SDS to request"] + #[doc = " * CRLs which it knows to have been issued and have not received. This is"] + #[doc = " * provided for future use and the associated mechanism is not defined in"] + #[doc = " * this version of this standard."] + #[doc = " *"] + #[doc = " * @param encryptionKey: if present, is used to provide a key that is to "] + #[doc = " * be used to encrypt at least one response to this SPDU. The SDEE "] + #[doc = " * specification is expected to specify which response SPDUs are to be "] + #[doc = " * encrypted with this key. One possible use of this key to encrypt a "] + #[doc = " * response is specified in 6.3.35, 6.3.37, and 6.3.34. An encryptionKey "] + #[doc = " * field of type symmetric should only be used if the SignedData containing "] + #[doc = " * this field is securely encrypted by some means."] + #[doc = " *"] + #[doc = " * @param inlineP2pcdRequest: if present, is used by the SDS to request"] + #[doc = " * unknown certificates per the inline peer-to-peer certificate distribution"] + #[doc = " * mechanism is given in Clause 8. This field shall only be present if"] + #[doc = " * p2pcdLearningRequest is not present. The HashedId3 is calculated with the"] + #[doc = " * whole-certificate hash algorithm, determined as described in 6.4.3, applied"] + #[doc = " * to the COER-encoded certificate, canonicalized as defined in the definition"] + #[doc = " * of Certificate."] + #[doc = " *"] + #[doc = " * @param requestedCertificate: if present, is used by the SDS to provide"] + #[doc = " * certificates per the \"inline\" version of the peer-to-peer certificate"] + #[doc = " * distribution mechanism given in Clause 8."] + #[doc = " *"] + #[doc = " * @param pduFunctionalType: if present, is used to indicate that the SPDU is"] + #[doc = " * to be consumed by a process other than an application process as defined"] + #[doc = " * in ISO 21177 [B14a]. See 6.3.23b for more details."] + #[doc = " *"] + #[doc = " * @param contributedExtensions: if present, is used to contain additional "] + #[doc = " * extensions defined using the ContributedExtensionBlocks structure."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization "] + #[doc = " * for the relevant operations specified in 6.1.2. The canonicalization"] + #[doc = " * applies to the EncryptionKey. If encryptionKey is present, and indicates"] + #[doc = " * the choice public, and contains a BasePublicEncryptionKey that is an"] + #[doc = " * elliptic curve point (i.e., of type EccP256CurvePoint or "] + #[doc = " * EccP384CurvePoint), then the elliptic curve point is encoded in compressed"] + #[doc = " * form, i.e., such that the choice indicated within the Ecc*CurvePoint is"] + #[doc = " * compressed-y-0 or compressed-y-1."] + #[doc = " * The canonicalization does not apply to any fields after the extension "] + #[doc = " * marker, including any fields in contributedExtensions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct HeaderInfo { + pub psid: Psid, + #[rasn(identifier = "generationTime")] + pub generation_time: Option, + #[rasn(identifier = "expiryTime")] + pub expiry_time: Option, + #[rasn(identifier = "generationLocation")] + pub generation_location: Option, + #[rasn(identifier = "p2pcdLearningRequest")] + pub p2pcd_learning_request: Option, + #[rasn(identifier = "missingCrlIdentifier")] + pub missing_crl_identifier: Option, + #[rasn(identifier = "encryptionKey")] + pub encryption_key: Option, + #[rasn(extension_addition, identifier = "inlineP2pcdRequest")] + pub inline_p2pcd_request: Option, + #[rasn(extension_addition, identifier = "requestedCertificate")] + pub requested_certificate: Option, + #[rasn(extension_addition, identifier = "pduFunctionalType")] + pub pdu_functional_type: Option, + #[rasn(extension_addition, identifier = "contributedExtensions")] + pub contributed_extensions: Option, + } + impl HeaderInfo { + pub fn new( + psid: Psid, + generation_time: Option, + expiry_time: Option, + generation_location: Option, + p2pcd_learning_request: Option, + missing_crl_identifier: Option, + encryption_key: Option, + inline_p2pcd_request: Option, + requested_certificate: Option, + pdu_functional_type: Option, + contributed_extensions: Option, + ) -> Self { + Self { + psid, + generation_time, + expiry_time, + generation_location, + p2pcd_learning_request, + missing_crl_identifier, + encryption_key, + inline_p2pcd_request, + requested_certificate, + pdu_functional_type, + contributed_extensions, + } + } + } + #[doc = "*"] + #[doc = " * @brief This is an integer used to identify a HeaderInfo extension"] + #[doc = " * contributing organization. In this version of this standard two values are"] + #[doc = " * defined: "] + #[doc = " * - ieee1609OriginatingExtensionId indicating extensions originating with "] + #[doc = " * IEEE 1609."] + #[doc = " * - etsiOriginatingExtensionId indicating extensions originating with "] + #[doc = " * ETSI TC ITS."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("0..=255"))] + pub struct HeaderInfoContributorId(pub u8); + #[doc = "*"] + #[doc = " * @brief This structure uses the parameterized type Extension to define an "] + #[doc = " * Ieee1609ContributedHeaderInfoExtension as an open Extension Content field "] + #[doc = " * identified by an extension identifier. The extension identifier value is "] + #[doc = " * unique to extensions defined by ETSI and need not be unique among all "] + #[doc = " * extension identifier values defined by all contributing organizations."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct Ieee1609ContributedHeaderInfoExtension { + pub id: ExtId, + pub content: Any, + } + impl Ieee1609ContributedHeaderInfoExtension { + pub fn new(id: ExtId, content: Any) -> Self { + Self { id, content } + } + } + #[doc = "*"] + #[doc = " * @brief In this structure:"] + #[doc = " *"] + #[doc = " * @param unsecuredData: indicates that the content is an OCTET STRING to be"] + #[doc = " * consumed outside the SDS."] + #[doc = " *"] + #[doc = " * @param signedData: indicates that the content has been signed according to"] + #[doc = " * this standard."] + #[doc = " *"] + #[doc = " * @param encryptedData: indicates that the content has been encrypted"] + #[doc = " * according to this standard."] + #[doc = " *"] + #[doc = " * @param signedCertificateRequest: indicates that the content is a "] + #[doc = " * certificate request signed by an IEEE 1609.2 certificate or self-signed."] + #[doc = " *"] + #[doc = " * @param signedX509CertificateRequest: indicates that the content is a "] + #[doc = " * certificate request signed by an ITU-T X.509 certificate."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization "] + #[doc = " * for the relevant operations specified in 6.1.2 if it is of type signedData."] + #[doc = " * The canonicalization applies to the SignedData."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum Ieee1609Dot2Content { + unsecuredData(Opaque), + signedData(SignedData), + encryptedData(EncryptedData), + signedCertificateRequest(Opaque), + #[rasn(extension_addition)] + signedX509CertificateRequest(Opaque), + } + #[doc = "***************************************************************************"] + #[doc = " Secured Data "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This data type is used to contain the other data types in this"] + #[doc = " * clause. The fields in the Ieee1609Dot2Data have the following meanings:"] + #[doc = " *"] + #[doc = " * @param protocolVersion: contains the current version of the protocol. The"] + #[doc = " * version specified in this standard is version 3, represented by the"] + #[doc = " * integer 3. There are no major or minor version numbers."] + #[doc = " *"] + #[doc = " * @param content: contains the content in the form of an Ieee1609Dot2Content."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization"] + #[doc = " * for the relevant operations specified in 6.1.2. The canonicalization "] + #[doc = " * applies to the Ieee1609Dot2Content."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct Ieee1609Dot2Data { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl Ieee1609Dot2Data { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This is an integer used to identify an "] + #[doc = " * Ieee1609ContributedHeaderInfoExtension."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct Ieee1609HeaderInfoExtensionId(pub ExtId); + #[doc = "*"] + #[doc = " * @brief This is a profile of the CertificateBase structure providing all"] + #[doc = " * the fields necessary for an implicit certificate, and no others."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct ImplicitCertificate(pub CertificateBase); + #[doc = "*"] + #[doc = " * @brief This structure allows the recipient of a certificate to determine"] + #[doc = " * which keying material to use to authenticate the certificate."] + #[doc = " *"] + #[doc = " * If the choice indicated is sha256AndDigest, sha384AndDigest, or "] + #[doc = " * sm3AndDigest:"] + #[doc = " * - The structure contains the HashedId8 of the issuing certificate. The "] + #[doc = " * HashedId8 is calculated with the whole-certificate hash algorithm, "] + #[doc = " * determined as described in 6.4.3, applied to the COER-encoded certificate, "] + #[doc = " * canonicalized as defined in the definition of Certificate. "] + #[doc = " * - The hash algorithm to be used to generate the hash of the certificate "] + #[doc = " * for verification is SHA-256 (in the case of sha256AndDigest), SM3 (in the "] + #[doc = " * case of sm3AndDigest) or SHA-384 (in the case of sha384AndDigest)."] + #[doc = " * - The certificate is to be verified with the public key of the"] + #[doc = " * indicated issuing certificate."] + #[doc = " *"] + #[doc = " * If the choice indicated is self:"] + #[doc = " * - The structure indicates what hash algorithm is to be used to generate"] + #[doc = " * the hash of the certificate for verification."] + #[doc = " * - The certificate is to be verified with the public key indicated by"] + #[doc = " * the verifyKeyIndicator field in theToBeSignedCertificate."] + #[doc = " *"] + #[doc = " * @note Critical information fields: If present, this is a critical"] + #[doc = " * information field as defined in 5.2.5. An implementation that does not"] + #[doc = " * recognize the indicated CHOICE for this type when verifying a signed SPDU"] + #[doc = " * shall indicate that the signed SPDU is invalid in the sense of 4.2.2.3.2, "] + #[doc = " * that is, it is invalid in the sense that its validity cannot be "] + #[doc = " * established."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum IssuerIdentifier { + sha256AndDigest(HashedId8), + #[rasn(identifier = "self")] + R_self(HashAlgorithm), + #[rasn(extension_addition)] + sha384AndDigest(HashedId8), + #[rasn(extension_addition)] + sm3AndDigest(HashedId8), + } + #[doc = "*"] + #[doc = " * @brief This structure contains information that is matched against"] + #[doc = " * information obtained from a linkage ID-based CRL to determine whether the"] + #[doc = " * containing certificate has been revoked. See 5.1.3.4 and 7.3 for details"] + #[doc = " * of use."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct LinkageData { + #[rasn(identifier = "iCert")] + pub i_cert: IValue, + #[rasn(identifier = "linkage-value")] + pub linkage_value: LinkageValue, + #[rasn(identifier = "group-linkage-value")] + pub group_linkage_value: Option, + } + impl LinkageData { + pub fn new( + i_cert: IValue, + linkage_value: LinkageValue, + group_linkage_value: Option, + ) -> Self { + Self { + i_cert, + linkage_value, + group_linkage_value, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure may be used to request a CRL that the SSME knows to"] + #[doc = " * have been issued and has not yet received. It is provided for future use"] + #[doc = " * and its use is not defined in this version of this standard."] + #[doc = " *"] + #[doc = " * @param cracaId: is the HashedId3 of the CRACA, as defined in 5.1.3. The "] + #[doc = " * HashedId3 is calculated with the whole-certificate hash algorithm, "] + #[doc = " * determined as described in 6.4.3, applied to the COER-encoded certificate,"] + #[doc = " * canonicalized as defined in the definition of Certificate."] + #[doc = " *"] + #[doc = " * @param crlSeries: is the requested CRL Series value. See 5.1.3 for more"] + #[doc = " * information."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct MissingCrlIdentifier { + #[rasn(identifier = "cracaId")] + pub craca_id: HashedId3, + #[rasn(identifier = "crlSeries")] + pub crl_series: CrlSeries, + } + impl MissingCrlIdentifier { + pub fn new(craca_id: HashedId3, crl_series: CrlSeries) -> Self { + Self { + craca_id, + crl_series, + } + } + } + #[doc = "*"] + #[doc = " * @brief This data structure encapsulates an encrypted ciphertext for any "] + #[doc = " * symmetric algorithm with 128-bit blocks in CCM mode. The ciphertext is "] + #[doc = " * 16 bytes longer than the corresponding plaintext due to the inclusion of "] + #[doc = " * the message authentication code (MAC). The plaintext resulting from a "] + #[doc = " * correct decryption of the ciphertext is either a COER-encoded "] + #[doc = " * Ieee1609Dot2Data structure (see 6.3.41), or a 16-byte symmetric key "] + #[doc = " * (see 6.3.44)."] + #[doc = " *"] + #[doc = " * The ciphertext is 16 bytes longer than the corresponding plaintext."] + #[doc = " *"] + #[doc = " * The plaintext resulting from a correct decryption of the"] + #[doc = " * ciphertext is a COER-encoded Ieee1609Dot2Data structure."] + #[doc = " *"] + #[doc = " * @param nonce: contains the nonce N as specified in 5.3.8."] + #[doc = " *"] + #[doc = " * @param ccmCiphertext: contains the ciphertext C as specified in 5.3.8."] + #[doc = " *"] + #[doc = " * @note In the name of this structure, \"One28\" indicates that the "] + #[doc = " * symmetric cipher block size is 128 bits. It happens to also be the case "] + #[doc = " * that the keys used for both AES-128-CCM and SM4-CCM are also 128 bits long. "] + #[doc = " * This is, however, not what “One28” refers to. Since the cipher is used in "] + #[doc = " * counter mode, i.e., as a stream cipher, the fact that that block size is 128"] + #[doc = " * bits affects only the size of the MAC and does not affect the size of the"] + #[doc = " * raw ciphertext."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct One28BitCcmCiphertext { + #[rasn(size("12"))] + pub nonce: OctetString, + #[rasn(identifier = "ccmCiphertext")] + pub ccm_ciphertext: Opaque, + } + impl One28BitCcmCiphertext { + pub fn new(nonce: OctetString, ccm_ciphertext: Opaque) -> Self { + Self { + nonce, + ccm_ciphertext, + } + } + } + #[doc = "*"] + #[doc = " * @brief This type is the AppExtension used to identify an operating "] + #[doc = " * organization. The associated CertIssueExtension and CertRequestExtension "] + #[doc = " * are both of type OperatingOrganizationId."] + #[doc = " * To determine consistency between this type and an SPDU, the SDEE "] + #[doc = " * specification for that SPDU is required to specify how the SPDU can be "] + #[doc = " * used to determine an OBJECT IDENTIFIER (for example, by including the "] + #[doc = " * full OBJECT IDENTIFIER in the SPDU, or by including a RELATIVE-OID with "] + #[doc = " * clear instructions about how a full OBJECT IDENTIFIER can be obtained from"] + #[doc = " * the RELATIVE-OID). The SPDU is then consistent with this type if the "] + #[doc = " * OBJECT IDENTIFIER determined from the SPDU is identical to the OBJECT "] + #[doc = " * IDENTIFIER contained in this field."] + #[doc = " * This AppExtension does not have consistency conditions with a "] + #[doc = " * corresponding CertIssueExtension. It can appear in a certificate issued "] + #[doc = " * by any CA."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct OperatingOrganizationId(pub ObjectIdentifier); + #[doc = "*"] + #[doc = " * @brief This data structure contains the following fields:"] + #[doc = " *"] + #[doc = " * @param recipientId: contains the hash of the container for the encryption"] + #[doc = " * public key as specified in the definition of RecipientInfo. Specifically,"] + #[doc = " * depending on the choice indicated by the containing RecipientInfo structure:"] + #[doc = " * - If the containing RecipientInfo structure indicates certRecipInfo,"] + #[doc = " * this field contains the HashedId8 of the certificate. The HashedId8 is"] + #[doc = " * calculated with the whole-certificate hash algorithm, determined as"] + #[doc = " * described in 6.4.3, applied to the COER-encoded certificate, canonicalized"] + #[doc = " * as defined in the definition of Certificate."] + #[doc = " * - If the containing RecipientInfo structure indicates "] + #[doc = " * signedDataRecipInfo, this field contains the HashedId8 of the "] + #[doc = " * Ieee1609Dot2Data of type signedData that contained the encryption key, "] + #[doc = " * with that Ieee¬¬1609¬Dot2¬¬Data canonicalized per 6.3.4. The HashedId8 is "] + #[doc = " * calculated with the hash algorithm determined as specified in 5.3.9.5."] + #[doc = " * - If the containing RecipientInfo structure indicates rekRecipInfo, this "] + #[doc = " * field contains the HashedId8 of the COER encoding of a PublicEncryptionKey "] + #[doc = " * structure containing the response encryption key. The HashedId8 is "] + #[doc = " * calculated with the hash algorithm determined as specified in 5.3.9.5."] + #[doc = " *"] + #[doc = " * @param encKey: contains the encrypted data encryption key, where the data "] + #[doc = " * encryption key is input to the data encryption key encryption process with "] + #[doc = " * no headers, encapsulation, or length indication. "] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct PKRecipientInfo { + #[rasn(identifier = "recipientId")] + pub recipient_id: HashedId8, + #[rasn(identifier = "encKey")] + pub enc_key: EncryptedDataEncryptionKey, + } + impl PKRecipientInfo { + pub fn new(recipient_id: HashedId8, enc_key: EncryptedDataEncryptionKey) -> Self { + Self { + recipient_id, + enc_key, + } + } + } + #[doc = "*"] + #[doc = " * @brief This data structure identifies the functional entity that is "] + #[doc = " * intended to consume an SPDU, for the case where that functional entity is "] + #[doc = " * not an application process, and are instead security support services for an"] + #[doc = " * application process. Further details and the intended use of this field are "] + #[doc = " * defined in ISO 21177 [B20]."] + #[doc = " *"] + #[doc = " * @param tlsHandshake: indicates that the Signed SPDU is not to be directly "] + #[doc = " * consumed as an application PDU and is to be used to provide information "] + #[doc = " * about the holder’s permissions to a Transport Layer Security (TLS) "] + #[doc = " * (IETF 5246 [B15], IETF 8446 [B16]) handshake process operating to secure "] + #[doc = " * communications to an application process. See IETF [B15] and ISO 21177 "] + #[doc = " * [B20] for further information."] + #[doc = " *"] + #[doc = " * @param iso21177ExtendedAuth: indicates that the Signed SPDU is not to be "] + #[doc = " * directly consumed as an application PDU and is to be used to provide "] + #[doc = " * additional information about the holder’s permissions to the ISO 21177 "] + #[doc = " * Security Subsystem for an application process. See ISO 21177 [B20] for "] + #[doc = " * further information."] + #[doc = " *"] + #[doc = " * @param iso21177SessionExtension: indicates that the Signed SPDU is not to "] + #[doc = " * be directly consumed as an application PDU and is to be used to extend an "] + #[doc = " * existing ISO 21177 secure session. This enables a secure session to "] + #[doc = " * persist beyond the lifetime of the certificates used to establish that "] + #[doc = " * session."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("0..=255"))] + pub struct PduFunctionalType(pub u8); + #[doc = "*"] + #[doc = " * @brief This data structure is used to indicate a symmetric key that may "] + #[doc = " * be used directly to decrypt a SymmetricCiphertext. It consists of the "] + #[doc = " * low-order 8 bytes of the hash of the COER encoding of a "] + #[doc = " * SymmetricEncryptionKey structure containing the symmetric key in question. "] + #[doc = " * The HashedId8 is calculated with the hash algorithm determined as "] + #[doc = " * specified in 5.3.9.3. The symmetric key may be established by any "] + #[doc = " * appropriate means agreed by the two parties to the exchange."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct PreSharedKeyRecipientInfo(pub HashedId8); + #[doc = "*"] + #[doc = " * @brief This structure states the permissions that a certificate holder has"] + #[doc = " * with respect to issuing and requesting certificates for a particular set"] + #[doc = " * of PSIDs. For examples, see D.5.3 and D.5.4."] + #[doc = " *"] + #[doc = " * @param subjectPermissions: indicates PSIDs and SSP Ranges covered by this"] + #[doc = " * field."] + #[doc = " *"] + #[doc = " * @param minChainLength: and chainLengthRange indicate how long the"] + #[doc = " * certificate chain from this certificate to the end-entity certificate is"] + #[doc = " * permitted to be. As specified in 5.1.2.1, the length of the certificate"] + #[doc = " * chain is the number of certificates \"below\" this certificate in the chain,"] + #[doc = " * down to and including the end-entity certificate. The length is permitted"] + #[doc = " * to be (a) greater than or equal to minChainLength certificates and (b)"] + #[doc = " * less than or equal to minChainLength + chainLengthRange certificates. A"] + #[doc = " * value of 0 for minChainLength is not permitted when this type appears in"] + #[doc = " * the certIssuePermissions field of a ToBeSignedCertificate; a certificate"] + #[doc = " * that has a value of 0 for this field is invalid. The value -1 for"] + #[doc = " * chainLengthRange is a special case: if the value of chainLengthRange is -1"] + #[doc = " * it indicates that the certificate chain may be any length equal to or"] + #[doc = " * greater than minChainLength. See the examples below for further discussion."] + #[doc = " *"] + #[doc = " * @param eeType: takes one or more of the values app and enroll and indicates"] + #[doc = " * the type of certificates or requests that this instance of"] + #[doc = " * PsidGroupPermissions in the certificate is entitled to authorize. "] + #[doc = " * Different instances of PsidGroupPermissions within a ToBeSignedCertificate"] + #[doc = " * may have different values for eeType."] + #[doc = " * - If this field indicates app, the chain is allowed to end in an "] + #[doc = " * authorization certificate, i.e., a certficate in which these permissions "] + #[doc = " * appear in an appPermissions field (in other words, if the field does not "] + #[doc = " * indicate app and the chain ends in an authorization certificate, the "] + #[doc = " * chain shall be considered invalid)."] + #[doc = " * - If this field indicates enroll, the chain is allowed to end in an "] + #[doc = " * enrollment certificate, i.e., a certificate in which these permissions "] + #[doc = " * appear in a certReqPermissions permissions field (in other words, if the "] + #[doc = " * field does not indicate enroll and the chain ends in an enrollment "] + #[doc = " * certificate, the chain shall be considered invalid)."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct PsidGroupPermissions { + #[rasn(identifier = "subjectPermissions")] + pub subject_permissions: SubjectPermissions, + #[rasn( + default = "psid_group_permissions_min_chain_length_default", + identifier = "minChainLength" + )] + pub min_chain_length: Integer, + #[rasn( + default = "psid_group_permissions_chain_length_range_default", + identifier = "chainLengthRange" + )] + pub chain_length_range: Integer, + #[rasn( + default = "psid_group_permissions_ee_type_default", + identifier = "eeType" + )] + pub ee_type: EndEntityType, + } + impl PsidGroupPermissions { + pub fn new( + subject_permissions: SubjectPermissions, + min_chain_length: Integer, + chain_length_range: Integer, + ee_type: EndEntityType, + ) -> Self { + Self { + subject_permissions, + min_chain_length, + chain_length_range, + ee_type, + } + } + } + fn psid_group_permissions_min_chain_length_default() -> Integer { + Integer::from(1i128) + } + fn psid_group_permissions_chain_length_range_default() -> Integer { + Integer::from(0i128) + } + fn psid_group_permissions_ee_type_default() -> EndEntityType { + let mut bits = FixedBitString::<8>::default(); + bits.set(0, true); + // bit 1 is false by default + EndEntityType(bits) + } + #[doc = "*"] + #[doc = " * @brief This data structure is used to transfer the data encryption key to"] + #[doc = " * an individual recipient of an EncryptedData. The option pskRecipInfo is"] + #[doc = " * selected if the EncryptedData was encrypted using the static encryption"] + #[doc = " * key approach specified in 5.3.4. The other options are selected if the"] + #[doc = " * EncryptedData was encrypted using the ephemeral encryption key approach"] + #[doc = " * specified in 5.3.4. The meanings of the choices are:"] + #[doc = " *"] + #[doc = " * See Annex C.7 for guidance on when it may be appropriate to use"] + #[doc = " * each of these approaches."] + #[doc = " *"] + #[doc = " * @param pskRecipInfo: The data was encrypted directly using a pre-shared "] + #[doc = " * symmetric key."] + #[doc = " *"] + #[doc = " * @param symmRecipInfo: The data was encrypted with a data encryption key,"] + #[doc = " * and the data encryption key was encrypted using a symmetric key."] + #[doc = " *"] + #[doc = " * @param certRecipInfo: The data was encrypted with a data encryption key, "] + #[doc = " * the data encryption key was encrypted using a public key encryption scheme,"] + #[doc = " * where the public encryption key was obtained from a certificate. In this "] + #[doc = " * case, the parameter P1 to ECIES as defined in 5.3.5 is the hash of the "] + #[doc = " * certificate, calculated with the whole-certificate hash algorithm, "] + #[doc = " * determined as described in 6.4.3, applied to the COER-encoded certificate, "] + #[doc = " * canonicalized as defined in the definition of Certificate."] + #[doc = " *"] + #[doc = " * @note If the encryption algorithm is SM2, there is no equivalent of the "] + #[doc = " * parameter P1 and so no input to the encryption process that uses the hash"] + #[doc = " * of the certificate."] + #[doc = " *"] + #[doc = " * @param signedDataRecipInfo: The data was encrypted with a data encryption "] + #[doc = " * key, the data encryption key was encrypted using a public key encryption "] + #[doc = " * scheme, where the public encryption key was obtained as the public response "] + #[doc = " * encryption key from a SignedData. In this case, if ECIES is the encryption "] + #[doc = " * algorithm, then the parameter P1 to ECIES as defined in 5.3.5 is the "] + #[doc = " * SHA-256 hash of the Ieee1609Dot2Data of type signedData containing the "] + #[doc = " * response encryption key, canonicalized as defined in the definition of "] + #[doc = " * Ieee1609Dot2Data."] + #[doc = " *"] + #[doc = " * @note If the encryption algorithm is SM2, there is no equivalent of the "] + #[doc = " * parameter P1 and so no input to the encryption process that uses the hash"] + #[doc = " * of the Ieee1609Dot2Data."] + #[doc = " *"] + #[doc = " * @param rekRecipInfo: The data was encrypted with a data encryption key, "] + #[doc = " * the data encryption key was encrypted using a public key encryption scheme,"] + #[doc = " * where the public encryption key was not obtained from a Signed-Data or a "] + #[doc = " * certificate. In this case, the SDEE specification is expected to specify "] + #[doc = " * how the public key is obtained, and if ECIES is the encryption algorithm, "] + #[doc = " * then the parameter P1 to ECIES as defined in 5.3.5 is the hash of the "] + #[doc = " * empty string."] + #[doc = " *"] + #[doc = " * @note If the encryption algorithm is SM2, there is no equivalent of the "] + #[doc = " * parameter P1 and so no input to the encryption process that uses the hash "] + #[doc = " * of the empty string."] + #[doc = " *"] + #[doc = " * @note The material input to encryption is the bytes of the encryption key "] + #[doc = " * with no headers, encapsulation, or length indication. Contrast this to "] + #[doc = " * encryption of data, where the data is encapsulated in an Ieee1609Dot2Data."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + pub enum RecipientInfo { + pskRecipInfo(PreSharedKeyRecipientInfo), + symmRecipInfo(SymmRecipientInfo), + certRecipInfo(PKRecipientInfo), + signedDataRecipInfo(PKRecipientInfo), + rekRecipInfo(PKRecipientInfo), + } + #[doc = "*"] + #[doc = " * @brief This structure contains any AppExtensions that apply to the "] + #[doc = " * certificate holder. As specified in 5.2.4.2.3, each individual "] + #[doc = " * AppExtension type is associated with consistency conditions, specific to "] + #[doc = " * that extension, that govern its consistency with SPDUs signed by the "] + #[doc = " * certificate holder and with the CertIssueExtensions in the CA certificates "] + #[doc = " * in that certificate holder’s chain. Those consistency conditions are "] + #[doc = " * specified for each individual AppExtension below."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, size("1.."))] + pub struct SequenceOfAppExtensions(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This field contains any CertIssueExtensions that apply to the "] + #[doc = " * certificate holder. As specified in 5.2.4.2.3, each individual "] + #[doc = " * CertIssueExtension type is associated with consistency conditions, "] + #[doc = " * specific to that extension, that govern its consistency with "] + #[doc = " * AppExtensions in certificates issued by the certificate holder and with "] + #[doc = " * the CertIssueExtensions in the CA certificates in that certificate "] + #[doc = " * holder’s chain. Those consistency conditions are specified for each "] + #[doc = " * individual CertIssueExtension below."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, size("1.."))] + pub struct SequenceOfCertIssueExtensions(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This field contains any CertRequestExtensions that apply to the "] + #[doc = " * certificate holder. As specified in 5.2.4.2.3, each individual "] + #[doc = " * CertRequestExtension type is associated with consistency conditions, "] + #[doc = " * specific to that extension, that govern its consistency with "] + #[doc = " * AppExtensions in certificates issued by the certificate holder and with "] + #[doc = " * the CertRequestExtensions in the CA certificates in that certificate "] + #[doc = " * holder’s chain. Those consistency conditions are specified for each "] + #[doc = " * individual CertRequestExtension below."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, size("1.."))] + pub struct SequenceOfCertRequestExtensions(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfCertificate(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfPsidGroupPermissions(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfRecipientInfo(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief In this structure:"] + #[doc = " *"] + #[doc = " * @param hashId: indicates the hash algorithm to be used to generate the hash"] + #[doc = " * of the message for signing and verification."] + #[doc = " *"] + #[doc = " * @param tbsData: contains the data that is hashed as input to the signature."] + #[doc = " *"] + #[doc = " * @param signer: determines the keying material and hash algorithm used to"] + #[doc = " * sign the data."] + #[doc = " *"] + #[doc = " * @param signature: contains the digital signature itself, calculated as"] + #[doc = " * specified in 5.3.1."] + #[doc = " * - If signer indicates the choice self, then the signature calculation"] + #[doc = " * is parameterized as follows:"] + #[doc = " * - Data input is equal to the COER encoding of the tbsData field"] + #[doc = " * canonicalized according to the encoding considerations given in 6.3.6."] + #[doc = " * - Verification type is equal to self."] + #[doc = " * - Signer identifier input is equal to the empty string."] + #[doc = " * - If signer indicates certificate or digest, then the signature"] + #[doc = " * calculation is parameterized as follows:"] + #[doc = " * - Data input is equal to the COER encoding of the tbsData field"] + #[doc = " * canonicalized according to the encoding considerations given in 6.3.6."] + #[doc = " * - Verification type is equal to certificate."] + #[doc = " * - Signer identifier input equal to the COER-encoding of the"] + #[doc = " * Certificate that is to be used to verify the SPDU, canonicalized according"] + #[doc = " * to the encoding considerations given in 6.4.3."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization "] + #[doc = " * for the relevant operations specified in 6.1.2. The canonicalization "] + #[doc = " * applies to the ToBeSignedData and the Signature."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct SignedData { + #[rasn(identifier = "hashId")] + pub hash_id: HashAlgorithm, + #[rasn(identifier = "tbsData")] + pub tbs_data: ToBeSignedData, + pub signer: SignerIdentifier, + pub signature: Signature, + } + impl SignedData { + pub fn new( + hash_id: HashAlgorithm, + tbs_data: ToBeSignedData, + signer: SignerIdentifier, + signature: Signature, + ) -> Self { + Self { + hash_id, + tbs_data, + signer, + signature, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains the data payload of a ToBeSignedData. This "] + #[doc = " * structure contains at least one of the optional elements, and may contain "] + #[doc = " * more than one. See 5.2.4.3.4 for more details."] + #[doc = " * The security profile in Annex C allows an implementation of this standard "] + #[doc = " * to state which forms of Signed¬Data¬Payload are supported by that "] + #[doc = " * implementation, and also how the signer and verifier are intended to obtain"] + #[doc = " * the external data for hashing. The specification of an SDEE that uses "] + #[doc = " * external data is expected to be explicit and unambiguous about how this "] + #[doc = " * data is obtained and how it is formatted prior to processing by the hash "] + #[doc = " * function."] + #[doc = " *"] + #[doc = " * @param data: contains data that is explicitly transported within the"] + #[doc = " * structure."] + #[doc = " *"] + #[doc = " * @param extDataHash: contains the hash of data that is not explicitly "] + #[doc = " * transported within the structure, and which the creator of the structure "] + #[doc = " * wishes to cryptographically bind to the signature. "] + #[doc = " *"] + #[doc = " * @param omitted: indicates that there is external data to be included in the"] + #[doc = " * hash calculation for the signature.The mechanism for including the external"] + #[doc = " * data in the hash calculation is specified in 6.3.6."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization "] + #[doc = " * for the relevant operations specified in 6.1.2. The canonicalization "] + #[doc = " * applies to the Ieee1609Dot2Data."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct SignedDataPayload { + pub data: Option, + #[rasn(identifier = "extDataHash")] + pub ext_data_hash: Option, + #[rasn(extension_addition)] + pub omitted: Option<()>, + } + impl SignedDataPayload { + pub fn new( + data: Option, + ext_data_hash: Option, + omitted: Option<()>, + ) -> Self { + Self { + data, + ext_data_hash, + omitted, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure allows the recipient of data to determine which"] + #[doc = " * keying material to use to authenticate the data. It also indicates the"] + #[doc = " * verification type to be used to generate the hash for verification, as"] + #[doc = " * specified in 5.3.1."] + #[doc = " *"] + #[doc = " * @param digest: If the choice indicated is digest:"] + #[doc = " * - The structure contains the HashedId8 of the relevant certificate. The"] + #[doc = " * HashedId8 is calculated with the whole-certificate hash algorithm,"] + #[doc = " * determined as described in 6.4.3."] + #[doc = " * - The verification type is certificate and the certificate data"] + #[doc = " * passed to the hash function as specified in 5.3.1 is the authorization"] + #[doc = " * certificate."] + #[doc = " *"] + #[doc = " * @param certificate: If the choice indicated is certificate:"] + #[doc = " * - The structure contains one or more Certificate structures, in order"] + #[doc = " * such that the first certificate is the authorization certificate and each"] + #[doc = " * subsequent certificate is the issuer of the one before it."] + #[doc = " * - The verification type is certificate and the certificate data"] + #[doc = " * passed to the hash function as specified in 5.3.1 is the authorization"] + #[doc = " * certificate."] + #[doc = " *"] + #[doc = " * @param self: If the choice indicated is self:"] + #[doc = " * - The structure does not contain any data beyond the indication that"] + #[doc = " * the choice value is self."] + #[doc = " * - The verification type is self-signed."] + #[doc = " *"] + #[doc = " * @note Critical information fields:"] + #[doc = " * - If present, this is a critical information field as defined in 5.2.6."] + #[doc = " * An implementation that does not recognize the CHOICE value for this type"] + #[doc = " * when verifying a signed SPDU shall indicate that the signed SPDU is invalid."] + #[doc = " * - If present, certificate is a critical information field as defined in"] + #[doc = " * 5.2.6. An implementation that does not support the number of certificates"] + #[doc = " * in certificate when verifying a signed SPDU shall indicate that the signed"] + #[doc = " * SPDU is invalid. A compliant implementation shall support certificate"] + #[doc = " * fields containing at least one certificate."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization"] + #[doc = " * for the relevant operations specified in 6.1.2. The canonicalization "] + #[doc = " * applies to every Certificate in the certificate field."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum SignerIdentifier { + digest(HashedId8), + certificate(SequenceOfCertificate), + #[rasn(identifier = "self")] + R_self(()), + } + #[doc = "*"] + #[doc = " * @brief This indicates the PSIDs and associated SSPs for which certificate"] + #[doc = " * issuance or request permissions are granted by a PsidGroupPermissions"] + #[doc = " * structure. If this takes the value explicit, the enclosing"] + #[doc = " * PsidGroupPermissions structure grants certificate issuance or request"] + #[doc = " * permissions for the indicated PSIDs and SSP Ranges. If this takes the"] + #[doc = " * value all, the enclosing PsidGroupPermissions structure grants certificate"] + #[doc = " * issuance or request permissions for all PSIDs not indicated by other"] + #[doc = " * PsidGroupPermissions in the same certIssuePermissions or"] + #[doc = " * certRequestPermissions field."] + #[doc = " *"] + #[doc = " * @note Critical information fields:"] + #[doc = " * - If present, this is a critical information field as defined in 5.2.6."] + #[doc = " * An implementation that does not recognize the indicated CHOICE when"] + #[doc = " * verifying a signed SPDU shall indicate that the signed SPDU is"] + #[doc = " * invalidin the sense of 4.2.2.3.2, that is, it is invalid in the sense that"] + #[doc = " * its validity cannot be established."] + #[doc = " * - If present, explicit is a critical information field as defined in"] + #[doc = " * 5.2.6. An implementation that does not support the number of PsidSspRange"] + #[doc = " * in explicit when verifying a signed SPDU shall indicate that the signed"] + #[doc = " * SPDU is invalid in the sense of 4.2.2.3.2, that is, it is invalid in the "] + #[doc = " * sense that its validity cannot be established. A conformant implementation"] + #[doc = " * shall support explicit fields containing at least eight entries."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum SubjectPermissions { + explicit(SequenceOfPsidSspRange), + all(()), + } + #[doc = "*"] + #[doc = " * @brief This data structure contains the following fields:"] + #[doc = " *"] + #[doc = " * @param recipientId: contains the hash of the symmetric key encryption key "] + #[doc = " * that may be used to decrypt the data encryption key. It consists of the "] + #[doc = " * low-order 8 bytes of the hash of the COER encoding of a "] + #[doc = " * SymmetricEncryptionKey structure containing the symmetric key in question. "] + #[doc = " * The HashedId8 is calculated with the hash algorithm determined as "] + #[doc = " * specified in 5.3.9.4. The symmetric key may be established by any "] + #[doc = " * appropriate means agreed by the two parties to the exchange."] + #[doc = " *"] + #[doc = " * @param encKey: contains the encrypted data encryption key within a "] + #[doc = " * SymmetricCiphertext, where the data encryption key is input to the data "] + #[doc = " * encryption key encryption process with no headers, encapsulation, or "] + #[doc = " * length indication."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct SymmRecipientInfo { + #[rasn(identifier = "recipientId")] + pub recipient_id: HashedId8, + #[rasn(identifier = "encKey")] + pub enc_key: SymmetricCiphertext, + } + impl SymmRecipientInfo { + pub fn new(recipient_id: HashedId8, enc_key: SymmetricCiphertext) -> Self { + Self { + recipient_id, + enc_key, + } + } + } + #[doc = "*"] + #[doc = " * @brief This data structure encapsulates a ciphertext generated with an"] + #[doc = " * approved symmetric algorithm."] + #[doc = " *"] + #[doc = " * @note Critical information fields: If present, this is a critical"] + #[doc = " * information field as defined in 5.2.6. An implementation that does not"] + #[doc = " * recognize the indicated CHOICE value for this type in an encrypted SPDU"] + #[doc = " * shall indicate that the signed SPDU is invalid in the sense of 4.2.2.3.2, "] + #[doc = " * that is, it is invalid in the sense that its validity cannot be established."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum SymmetricCiphertext { + aes128ccm(One28BitCcmCiphertext), + #[rasn(extension_addition)] + sm4Ccm(One28BitCcmCiphertext), + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct TestCertificate(pub Certificate); + #[doc = "*"] + #[doc = " * @brief The fields in the ToBeSignedCertificate structure have the"] + #[doc = " * following meaning:"] + #[doc = " *"] + #[doc = " * For both implicit and explicit certificates, when the certificate"] + #[doc = " * is hashed to create or recover the public key (in the case of an implicit"] + #[doc = " * certificate) or to generate or verify the signature (in the case of an"] + #[doc = " * explicit certificate), the hash is Hash (Data input) || Hash ("] + #[doc = " * Signer identifier input), where:"] + #[doc = " * - Data input is the COER encoding of toBeSigned, canonicalized"] + #[doc = " * as described above."] + #[doc = " * - Signer identifier input depends on the verification type,"] + #[doc = " * which in turn depends on the choice indicated by issuer. If the choice"] + #[doc = " * indicated by issuer is self, the verification type is self-signed and the"] + #[doc = " * signer identifier input is the empty string. If the choice indicated by"] + #[doc = " * issuer is not self, the verification type is certificate and the signer"] + #[doc = " * identifier input is the COER encoding of the canonicalization per 6.4.3 of"] + #[doc = " * the certificate indicated by issuer."] + #[doc = " *"] + #[doc = " * In other words, for implicit certificates, the value H (CertU) in SEC 4,"] + #[doc = " * section 3, is for purposes of this standard taken to be H [H"] + #[doc = " * (canonicalized ToBeSignedCertificate from the subordinate certificate) ||"] + #[doc = " * H (entirety of issuer Certificate)]. See 5.3.2 for further discussion,"] + #[doc = " * including material differences between this standard and SEC 4 regarding"] + #[doc = " * how the hash function output is converted from a bit string to an integer."] + #[doc = " *"] + #[doc = " * @param id: contains information that is used to identify the certificate"] + #[doc = " * holder if necessary."] + #[doc = " *"] + #[doc = " * @param cracaId: identifies the Certificate Revocation Authorization CA"] + #[doc = " * (CRACA) responsible for certificate revocation lists (CRLs) on which this"] + #[doc = " * certificate might appear. Use of the cracaId is specified in 5.1.3. The"] + #[doc = " * HashedId3 is calculated with the whole-certificate hash algorithm,"] + #[doc = " * determined as described in 6.4.3, applied to the COER-encoded certificate, "] + #[doc = " * canonicalized as defined in the definition of Certificate."] + #[doc = " *"] + #[doc = " * @param crlSeries: represents the CRL series relevant to a particular"] + #[doc = " * Certificate Revocation Authorization CA (CRACA) on which the certificate"] + #[doc = " * might appear. Use of this field is specified in 5.1.3."] + #[doc = " *"] + #[doc = " * @param validityPeriod: contains the validity period of the certificate."] + #[doc = " *"] + #[doc = " * @param region: if present, indicates the validity region of the"] + #[doc = " * certificate. If it is omitted the validity region is indicated as follows:"] + #[doc = " * - If enclosing certificate is self-signed, i.e., the choice indicated"] + #[doc = " * by the issuer field in the enclosing certificate structure is self, the"] + #[doc = " * certificate is valid worldwide."] + #[doc = " * - Otherwise, the certificate has the same validity region as the"] + #[doc = " * certificate that issued it."] + #[doc = " *"] + #[doc = " * @param assuranceLevel: indicates the assurance level of the certificate"] + #[doc = " * holder."] + #[doc = " *"] + #[doc = " * @param appPermissions: indicates the permissions that the certificate"] + #[doc = " * holder has to sign application data with this certificate. A valid"] + #[doc = " * instance of appPermissions contains any particular Psid value in at most"] + #[doc = " * one entry."] + #[doc = " *"] + #[doc = " * @param certIssuePermissions: indicates the permissions that the certificate"] + #[doc = " * holder has to sign certificates with this certificate. A valid instance of"] + #[doc = " * this array contains no more than one entry whose psidSspRange field"] + #[doc = " * indicates all. If the array has multiple entries and one entry has its"] + #[doc = " * psidSspRange field indicate all, then the entry indicating all specifies"] + #[doc = " * the permissions for all PSIDs other than the ones explicitly specified in"] + #[doc = " * the other entries. See the description of PsidGroupPermissions for further"] + #[doc = " * discussion."] + #[doc = " *"] + #[doc = " * @param certRequestPermissions: indicates the permissions that the "] + #[doc = " * certificate holder can request in its certificate. A valid instance of this"] + #[doc = " * array contains no more than one entry whose psidSspRange field indicates "] + #[doc = " * all. If the array has multiple entries and one entry has its psidSspRange "] + #[doc = " * field indicate all, then the entry indicating all specifies the permissions "] + #[doc = " * for all PSIDs other than the ones explicitly specified in the other entries."] + #[doc = " * See the description of PsidGroupPermissions for further discussion."] + #[doc = " *"] + #[doc = " * @param canRequestRollover: indicates that the certificate may be used to"] + #[doc = " * sign a request for another certificate with the same permissions. This"] + #[doc = " * field is provided for future use and its use is not defined in this"] + #[doc = " * version of this standard."] + #[doc = " *"] + #[doc = " * @param encryptionKey: contains a public key for encryption for which the"] + #[doc = " * certificate holder holds the corresponding private key."] + #[doc = " *"] + #[doc = " * @param verifyKeyIndicator: contains material that may be used to recover"] + #[doc = " * the public key that may be used to verify data signed by this certificate."] + #[doc = " *"] + #[doc = " * @param flags: indicates additional yes/no properties of the certificate "] + #[doc = " * holder. The only bit with defined semantics in this string in this version "] + #[doc = " * of this standard is usesCubk. If set, the usesCubk bit indicates that the "] + #[doc = " * certificate holder supports the compact unified butterfly key response. "] + #[doc = " * Further material about the compact unified butterfly key response can be "] + #[doc = " * found in IEEE Std 1609.2.1."] + #[doc = " *"] + #[doc = " * @note usesCubk is only relevant for CA certificates, and the only "] + #[doc = " * functionality defined associated with this field is associated with "] + #[doc = " * consistency checks on received certificate responses. No functionality "] + #[doc = " * associated with communications between peer SDEEs is defined associated "] + #[doc = " * with this field."] + #[doc = " *"] + #[doc = " * @param appExtensions: indicates additional permissions that may be applied"] + #[doc = " * to application activities that the certificate holder is carrying out. "] + #[doc = " *"] + #[doc = " * @param certIssueExtensions: indicates additional permissions to issue "] + #[doc = " * certificates containing endEntityExtensions. "] + #[doc = " *"] + #[doc = " * @param certRequestExtensions: indicates additional permissions to request "] + #[doc = " * certificates containing endEntityExtensions."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization "] + #[doc = " * for the relevant operations specified in 6.1.2. The canonicalization "] + #[doc = " * applies to the PublicEncryptionKey and to the VerificationKeyIndicator."] + #[doc = " *"] + #[doc = " * If the PublicEncryptionKey contains a BasePublicEncryptionKey that is an "] + #[doc = " * elliptic curve point (i.e., of type EccP256CurvePoint or EccP384CurvePoint),"] + #[doc = " * then the elliptic curve point is encoded in compressed form, i.e., such "] + #[doc = " * that the choice indicated within the Ecc*CurvePoint is compressed-y-0 or "] + #[doc = " * compressed-y-1."] + #[doc = " *"] + #[doc = " * @note Critical information fields:"] + #[doc = " * - If present, appPermissions is a critical information field as defined "] + #[doc = " * in 5.2.6. If an implementation of verification does not support the number "] + #[doc = " * of PsidSsp in the appPermissions field of a certificate that signed a "] + #[doc = " * signed SPDU, that implementation shall indicate that the signed SPDU is "] + #[doc = " * invalid in the sense of 4.2.2.3.2, that is, it is invalid in the sense "] + #[doc = " * that its validity cannot be established.. A conformant implementation "] + #[doc = " * shall support appPermissions fields containing at least eight entries. "] + #[doc = " * It may be the case that an implementation of verification does not support "] + #[doc = " * the number of entries in the appPermissions field and the appPermissions "] + #[doc = " * field is not relevant to the verification: this will occur, for example, "] + #[doc = " * if the certificate in question is a CA certificate and so the "] + #[doc = " * certIssuePermissions field is relevant to the verification and the "] + #[doc = " * appPermissions field is not. In this case, whether the implementation "] + #[doc = " * indicates that the signed SPDU is valid (because it could validate all "] + #[doc = " * relevant fields) or invalid (because it could not parse the entire "] + #[doc = " * certificate) is implementation-specific."] + #[doc = " * - If present, certIssuePermissions is a critical information field as "] + #[doc = " * defined in 5.2.6. If an implementation of verification does not support "] + #[doc = " * the number of PsidGroupPermissions in the certIssuePermissions field of a "] + #[doc = " * CA certificate in the chain of a signed SPDU, the implementation shall "] + #[doc = " * indicate that the signed SPDU is invalid in the sense of 4.2.2.3.2, that "] + #[doc = " * is, it is invalid in the sense that its validity cannot be established. "] + #[doc = " * A conformant implementation shall support certIssuePermissions fields "] + #[doc = " * containing at least eight entries."] + #[doc = " * It may be the case that an implementation of verification does not support"] + #[doc = " * the number of entries in the certIssuePermissions field and the "] + #[doc = " * certIssuePermissions field is not relevant to the verification: this will "] + #[doc = " * occur, for example, if the certificate in question is the signing "] + #[doc = " * certificate for the SPDU and so the appPermissions field is relevant to "] + #[doc = " * the verification and the certIssuePermissions field is not. In this case, "] + #[doc = " * whether the implementation indicates that the signed SPDU is valid "] + #[doc = " * (because it could validate all relevant fields) or invalid (because it "] + #[doc = " * could not parse the entire certificate) is implementation-specific."] + #[doc = " * - If present, certRequestPermissions is a critical information field as "] + #[doc = " * defined in 5.2.6. If an implementaiton of verification of a certificate "] + #[doc = " * request does not support the number of PsidGroupPermissions in "] + #[doc = " * certRequestPermissions, the implementation shall indicate that the signed "] + #[doc = " * SPDU is invalid in the sense of 4.2.2.3.2, that is, it is invalid in the "] + #[doc = " * sense that its validity cannot be established. A conformant implementation "] + #[doc = " * shall support certRequestPermissions fields containing at least eight "] + #[doc = " * entries."] + #[doc = " * It may be the case that an implementation of verification does not support "] + #[doc = " * the number of entries in the certRequestPermissions field and the "] + #[doc = " * certRequestPermissions field is not relevant to the verification: this will "] + #[doc = " * occur, for example, if the certificate in question is the signing "] + #[doc = " * certificate for the SPDU and so the appPermissions field is relevant to "] + #[doc = " * the verification and the certRequestPermissions field is not. In this "] + #[doc = " * case, whether the implementation indicates that the signed SPDU is valid "] + #[doc = " * (because it could validate all relevant fields) or invalid (because it "] + #[doc = " * could not parse the entire certificate) is implementation-specific."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct ToBeSignedCertificate { + pub id: CertificateId, + #[rasn(identifier = "cracaId")] + pub craca_id: HashedId3, + #[rasn(identifier = "crlSeries")] + pub crl_series: CrlSeries, + #[rasn(identifier = "validityPeriod")] + pub validity_period: ValidityPeriod, + pub region: Option, + #[rasn(identifier = "assuranceLevel")] + pub assurance_level: Option, + #[rasn(identifier = "appPermissions")] + pub app_permissions: Option, + #[rasn(identifier = "certIssuePermissions")] + pub cert_issue_permissions: Option, + #[rasn(identifier = "certRequestPermissions")] + pub cert_request_permissions: Option, + #[rasn(identifier = "canRequestRollover")] + pub can_request_rollover: Option<()>, + #[rasn(identifier = "encryptionKey")] + pub encryption_key: Option, + #[rasn(identifier = "verifyKeyIndicator")] + pub verify_key_indicator: VerificationKeyIndicator, + #[rasn(extension_addition, size("8"))] + pub flags: Option, + #[rasn(extension_addition, identifier = "appExtensions")] + pub app_extensions: SequenceOfAppExtensions, + #[rasn(extension_addition, identifier = "certIssueExtensions")] + pub cert_issue_extensions: SequenceOfCertIssueExtensions, + #[rasn(extension_addition, identifier = "certRequestExtension")] + pub cert_request_extension: SequenceOfCertRequestExtensions, + } + impl ToBeSignedCertificate { + pub fn new( + id: CertificateId, + craca_id: HashedId3, + crl_series: CrlSeries, + validity_period: ValidityPeriod, + region: Option, + assurance_level: Option, + app_permissions: Option, + cert_issue_permissions: Option, + cert_request_permissions: Option, + can_request_rollover: Option<()>, + encryption_key: Option, + verify_key_indicator: VerificationKeyIndicator, + flags: Option, + app_extensions: SequenceOfAppExtensions, + cert_issue_extensions: SequenceOfCertIssueExtensions, + cert_request_extension: SequenceOfCertRequestExtensions, + ) -> Self { + Self { + id, + craca_id, + crl_series, + validity_period, + region, + assurance_level, + app_permissions, + cert_issue_permissions, + cert_request_permissions, + can_request_rollover, + encryption_key, + verify_key_indicator, + flags, + app_extensions, + cert_issue_extensions, + cert_request_extension, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains the data to be hashed when generating or"] + #[doc = " * verifying a signature. See 6.3.4 for the specification of the input to the"] + #[doc = " * hash."] + #[doc = " *"] + #[doc = " * @param payload: contains data that is provided by the entity that invokes"] + #[doc = " * the SDS."] + #[doc = " *"] + #[doc = " * @param headerInfo: contains additional data that is inserted by the SDS."] + #[doc = " * This structure is used as follows to determine the \"data input\" to the "] + #[doc = " * hash operation for signing or verification as specified in 5.3.1.2.2 or "] + #[doc = " * 5.3.1.3."] + #[doc = " * - If payload does not contain the field omitted, the data input to the "] + #[doc = " * hash operation is the COER encoding of the ToBeSignedData. "] + #[doc = " * - If payload field in this ToBeSignedData instance contains the field "] + #[doc = " * omitted, the data input to the hash operation is the COER encoding of the"] + #[doc = " * ToBeSignedData, concatenated with the hash of the omitted payload. The hash"] + #[doc = " * of the omitted payload is calculated with the same hash algorithm that is "] + #[doc = " * used to calculate the hash of the data input for signing or verification. "] + #[doc = " * The data input to the hash operation is simply the COER enocding of the "] + #[doc = " * ToBeSignedData, concatenated with the hash of the omitted payload: there is"] + #[doc = " * no additional wrapping or length indication. As noted in 5.2.4.3.4, the "] + #[doc = " * means by which the signer and verifier establish the contents of the "] + #[doc = " * omitted payload are out of scope for this standard."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization "] + #[doc = " * for the relevant operations specified in 6.1.2. The canonicalization "] + #[doc = " * applies to the SignedDataPayload if it is of type data, and to the "] + #[doc = " * HeaderInfo."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct ToBeSignedData { + pub payload: Box, + #[rasn(identifier = "headerInfo")] + pub header_info: HeaderInfo, + } + impl ToBeSignedData { + pub fn new(payload: Box, header_info: HeaderInfo) -> Self { + Self { + payload, + header_info, + } + } + } + #[doc = "*"] + #[doc = " * @brief The contents of this field depend on whether the certificate is an"] + #[doc = " * implicit or an explicit certificate."] + #[doc = " *"] + #[doc = " * @param verificationKey: is included in explicit certificates. It contains"] + #[doc = " * the public key to be used to verify signatures generated by the holder of"] + #[doc = " * the Certificate."] + #[doc = " *"] + #[doc = " * @param reconstructionValue: is included in implicit certificates. It"] + #[doc = " * contains the reconstruction value, which is used to recover the public key"] + #[doc = " * as specified in SEC 4 and 5.3.2."] + #[doc = " *"] + #[doc = " * @note Critical information fields: If present, this is a critical "] + #[doc = " * information field as defined in 5.2.5. An implementation that does not "] + #[doc = " * recognize the indicated CHOICE for this type when verifying a signed SPDU "] + #[doc = " * shall indicate that the signed SPDU is invalid indicate that the signed "] + #[doc = " * SPDU is invalid in the sense of 4.2.2.3.2, that is, it is invalid in the "] + #[doc = " * sense that its validity cannot be established."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization "] + #[doc = " * for the relevant operations specified in 6.1.2. The canonicalization "] + #[doc = " * applies to the PublicVerificationKey and to the EccP256CurvePoint. The "] + #[doc = " * EccP256CurvePoint is encoded in compressed form, i.e., such that the "] + #[doc = " * choice indicated within the EccP256CurvePoint is compressed-y-0 or "] + #[doc = " * compressed-y-1."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum VerificationKeyIndicator { + verificationKey(PublicVerificationKey), + reconstructionValue(EccP256CurvePoint), + } + pub const CERT_EXT_ID_OPERATING_ORGANIZATION: ExtId = ExtId(1); + pub const ETSI_HEADER_INFO_CONTRIBUTOR_ID: HeaderInfoContributorId = HeaderInfoContributorId(2); + pub const IEEE1609_HEADER_INFO_CONTRIBUTOR_ID: HeaderInfoContributorId = + HeaderInfoContributorId(1); + pub const ISO21177_EXTENDED_AUTH: PduFunctionalType = PduFunctionalType(2); + pub const ISO21177_SESSION_EXTENSION: PduFunctionalType = PduFunctionalType(3); + pub const P2PCD8_BYTE_LEARNING_REQUEST_ID: Ieee1609HeaderInfoExtensionId = + Ieee1609HeaderInfoExtensionId(ExtId(1)); + pub const TLS_HANDSHAKE: PduFunctionalType = PduFunctionalType(1); +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_base_types { + extern crate alloc; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This structure specifies the bytes of a public encryption key for "] + #[doc = " * a particular algorithm. Supported public key encryption algorithms are "] + #[doc = " * defined in 5.3.5."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization"] + #[doc = " * for the relevant operations specified in 6.1.2 if it appears in a "] + #[doc = " * HeaderInfo or in a ToBeSignedCertificate. See the definitions of HeaderInfo"] + #[doc = " * and ToBeSignedCertificate for a specification of the canonicalization"] + #[doc = " * operations."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum BasePublicEncryptionKey { + eciesNistP256(EccP256CurvePoint), + eciesBrainpoolP256r1(EccP256CurvePoint), + #[rasn(extension_addition)] + ecencSm2(EccP256CurvePoint), + } + #[doc = "*"] + #[doc = " * @brief This structure represents a bitmap representation of a SSP. The"] + #[doc = " * mapping of the bits of the bitmap to constraints on the signed SPDU is"] + #[doc = " * PSID-specific."] + #[doc = " *"] + #[doc = " * @note Consistency with issuing certificate: If a certificate has an"] + #[doc = " * appPermissions entry A for which the ssp field is bitmapSsp, A is"] + #[doc = " * consistent with the issuing certificate if the certificate contains one"] + #[doc = " * of the following:"] + #[doc = " * - (OPTION 1) A SubjectPermissions field indicating the choice all and"] + #[doc = " * no PsidSspRange field containing the psid field in A;"] + #[doc = " * - (OPTION 2) A PsidSspRange P for which the following holds:"] + #[doc = " * - The psid field in P is equal to the psid field in A and one of the"] + #[doc = " * following is true:"] + #[doc = " * - EITHER The sspRange field in P indicates all"] + #[doc = " * - OR The sspRange field in P indicates bitmapSspRange and for every"] + #[doc = " * bit set to 1 in the sspBitmask in P, the bit in the identical position in"] + #[doc = " * the sspValue in A is set equal to the bit in that position in the"] + #[doc = " * sspValue in P."] + #[doc = " *"] + #[doc = " * @note A BitmapSsp B is consistent with a BitmapSspRange R if for every"] + #[doc = " * bit set to 1 in the sspBitmask in R, the bit in the identical position in"] + #[doc = " * B is set equal to the bit in that position in the sspValue in R. For each"] + #[doc = " * bit set to 0 in the sspBitmask in R, the corresponding bit in the"] + #[doc = " * identical position in B may be freely set to 0 or 1, i.e., if a bit is"] + #[doc = " * set to 0 in the sspBitmask in R, the value of corresponding bit in the"] + #[doc = " * identical position in B has no bearing on whether B and R are consistent."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, size("0..=31"))] + pub struct BitmapSsp(pub OctetString); + #[doc = "*"] + #[doc = " * @brief This structure represents a bitmap representation of a SSP. The"] + #[doc = " * sspValue indicates permissions. The sspBitmask contains an octet string"] + #[doc = " * used to permit or constrain sspValue fields in issued certificates. The"] + #[doc = " * sspValue and sspBitmask fields shall be of the same length."] + #[doc = " *"] + #[doc = " * @note Consistency with issuing certificate: If a certificate has an"] + #[doc = " * PsidSspRange value P for which the sspRange field is bitmapSspRange,"] + #[doc = " * P is consistent with the issuing certificate if the issuing certificate"] + #[doc = " * contains one of the following:"] + #[doc = " * - (OPTION 1) A SubjectPermissions field indicating the choice all and"] + #[doc = " * no PsidSspRange field containing the psid field in P;"] + #[doc = " * - (OPTION 2) A PsidSspRange R for which the following holds:"] + #[doc = " * - The psid field in R is equal to the psid field in P and one of the"] + #[doc = " * following is true:"] + #[doc = " * - EITHER The sspRange field in R indicates all"] + #[doc = " * - OR The sspRange field in R indicates bitmapSspRange and for every"] + #[doc = " * bit set to 1 in the sspBitmask in R:"] + #[doc = " * - The bit in the identical position in the sspBitmask in P is set"] + #[doc = " * equal to 1, AND"] + #[doc = " * - The bit in the identical position in the sspValue in P is set equal"] + #[doc = " * to the bit in that position in the sspValue in R."] + #[doc = " *"] + #[doc = " * Reference ETSI TS 103 097 for more information on bitmask SSPs."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct BitmapSspRange { + #[rasn(size("1..=32"), identifier = "sspValue")] + pub ssp_value: OctetString, + #[rasn(size("1..=32"), identifier = "sspBitmask")] + pub ssp_bitmask: OctetString, + } + impl BitmapSspRange { + pub fn new(ssp_value: OctetString, ssp_bitmask: OctetString) -> Self { + Self { + ssp_value, + ssp_bitmask, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure specifies a circle with its center at center, its"] + #[doc = " * radius given in meters, and located tangential to the reference ellipsoid."] + #[doc = " * The indicated region is all the points on the surface of the reference"] + #[doc = " * ellipsoid whose distance to the center point over the reference ellipsoid"] + #[doc = " * is less than or equal to the radius. A point which contains an elevation"] + #[doc = " * component is considered to be within the circular region if its horizontal"] + #[doc = " * projection onto the reference ellipsoid lies within the region."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct CircularRegion { + pub center: TwoDLocation, + pub radius: Uint16, + } + impl CircularRegion { + pub fn new(center: TwoDLocation, radius: Uint16) -> Self { + Self { center, radius } + } + } + #[doc = "*"] + #[doc = " * @brief A conformant implementation that supports CountryAndRegions shall "] + #[doc = " * support a regions field containing at least eight entries."] + #[doc = " * A conformant implementation that implements this type shall recognize "] + #[doc = " * (in the sense of \"be able to determine whether a two dimensional location "] + #[doc = " * lies inside or outside the borders identified by\") at least one value of "] + #[doc = " * UnCountryId and at least one value for a region within the country "] + #[doc = " * indicated by that recognized UnCountryId value. In this version of this "] + #[doc = " * standard, the only means to satisfy this is for a conformant "] + #[doc = " * implementation to recognize the value of UnCountryId indicating USA and "] + #[doc = " * at least one of the FIPS state codes for US states. The Protocol "] + #[doc = " * Implementation Conformance Statement (PICS) provided in Annex A allows "] + #[doc = " * an implementation to state which UnCountryId values it recognizes and "] + #[doc = " * which region values are recognized within that country."] + #[doc = " * If a verifying implementation is required to check that an relevant "] + #[doc = " * geographic information in a signed SPDU is consistent with a certificate "] + #[doc = " * containing one or more instances of this type, then the SDS is permitted "] + #[doc = " * to indicate that the signed SPDU is valid even if some values of country "] + #[doc = " * or within regions are unrecognized in the sense defined above, so long "] + #[doc = " * as the recognized instances of this type completely contain the relevant "] + #[doc = " * geographic information. Informally, if the recognized values in the "] + #[doc = " * certificate allow the SDS to determine that the SPDU is valid, then it "] + #[doc = " * can make that determination even if there are also unrecognized values "] + #[doc = " * in the certificate. This field is therefore not a \"critical information "] + #[doc = " * field\" as defined in 5.2.6, because unrecognized values are permitted so "] + #[doc = " * long as the validity of the SPDU can be established with the recognized "] + #[doc = " * values. However, as discussed in 5.2.6, the presence of an unrecognized "] + #[doc = " * value in a certificate can make it impossible to determine whether the "] + #[doc = " * certificate is valid and so whether the SPDU is valid."] + #[doc = " * In this type:"] + #[doc = " *"] + #[doc = " * @param countryOnly: is a UnCountryId as defined above."] + #[doc = " *"] + #[doc = " * @param regions: identifies one or more regions within the country. If "] + #[doc = " * country indicates the United States of America, the values in this field "] + #[doc = " * identify the state or statistically equivalent entity using the integer "] + #[doc = " * version of the 2010 FIPS codes as provided by the U.S. Census Bureau "] + #[doc = " * (see normative references in Clause 0). For other values of country, the "] + #[doc = " * meaning of region is not defined in this version of this standard."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct CountryAndRegions { + #[rasn(identifier = "countryOnly")] + pub country_only: UnCountryId, + pub regions: SequenceOfUint8, + } + impl CountryAndRegions { + pub fn new(country_only: UnCountryId, regions: SequenceOfUint8) -> Self { + Self { + country_only, + regions, + } + } + } + #[doc = "*"] + #[doc = " * @brief A conformant implementation that supports CountryAndSubregions "] + #[doc = " * shall support a regionAndSubregions field containing at least eight "] + #[doc = " * entries."] + #[doc = " * A conformant implementation that implements this type shall recognize "] + #[doc = " * (in the sense of “be able to determine whether a two dimensional location "] + #[doc = " * lies inside or outside the borders identified by”) at least one value of "] + #[doc = " * country and at least one value for a region within the country indicated "] + #[doc = " * by that recognized country value. In this version of this standard, the "] + #[doc = " * only means to satisfy this is for a conformant implementation to recognize "] + #[doc = " * the value of UnCountryId indicating USA and at least one of the FIPS state "] + #[doc = " * codes for US states. The Protocol Implementation Conformance Statement "] + #[doc = " * (PICS) provided in Annex A allows an implementation to state which "] + #[doc = " * UnCountryId values it recognizes and which region values are recognized "] + #[doc = " * within that country."] + #[doc = " * If a verifying implementation is required to check that an relevant "] + #[doc = " * geographic information in a signed SPDU is consistent with a certificate "] + #[doc = " * containing one or more instances of this type, then the SDS is permitted "] + #[doc = " * to indicate that the signed SPDU is valid even if some values of country "] + #[doc = " * or within regionAndSubregions are unrecognized in the sense defined above,"] + #[doc = " * so long as the recognized instances of this type completely contain the "] + #[doc = " * relevant geographic information. Informally, if the recognized values in "] + #[doc = " * the certificate allow the SDS to determine that the SPDU is valid, then "] + #[doc = " * it can make that determination even if there are also unrecognized values "] + #[doc = " * in the certificate. This field is therefore not a \"critical information "] + #[doc = " * field\" as defined in 5.2.6, because unrecognized values are permitted so "] + #[doc = " * long as the validity of the SPDU can be established with the recognized "] + #[doc = " * values. However, as discussed in 5.2.6, the presence of an unrecognized "] + #[doc = " * value in a certificate can make it impossible to determine whether the "] + #[doc = " * certificate is valid and so whether the SPDU is valid."] + #[doc = " * In this structure:"] + #[doc = " *"] + #[doc = " * @param countryOnly: is a UnCountryId as defined above."] + #[doc = " *"] + #[doc = " * @param regionAndSubregions: identifies one or more subregions within "] + #[doc = " * country."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct CountryAndSubregions { + #[rasn(identifier = "countryOnly")] + pub country_only: UnCountryId, + #[rasn(identifier = "regionAndSubregions")] + pub region_and_subregions: SequenceOfRegionAndSubregions, + } + impl CountryAndSubregions { + pub fn new( + country_only: UnCountryId, + region_and_subregions: SequenceOfRegionAndSubregions, + ) -> Self { + Self { + country_only, + region_and_subregions, + } + } + } + #[doc = "*"] + #[doc = " * @brief This type is defined only for backwards compatibility."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct CountryOnly(pub UnCountryId); + #[doc = "*"] + #[doc = " * @brief This integer identifies a series of CRLs issued under the authority"] + #[doc = " * of a particular CRACA."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct CrlSeries(pub Uint16); + #[doc = "*"] + #[doc = " * @brief This structure represents the duration of validity of a"] + #[doc = " * certificate. The Uint16 value is the duration, given in the units denoted"] + #[doc = " * by the indicated choice. A year is considered to be 31556952 seconds,"] + #[doc = " * which is the average number of seconds in a year."] + #[doc = " * "] + #[doc = " * @note Years can be mapped more closely to wall-clock days using the hours "] + #[doc = " * choice for up to 7 years and the sixtyHours choice for up to 448 years. "] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + pub enum Duration { + microseconds(Uint16), + milliseconds(Uint16), + seconds(Uint16), + minutes(Uint16), + hours(Uint16), + sixtyHours(Uint16), + years(Uint16), + } + #[doc = " Inner type "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EccP256CurvePointUncompressedP256 { + #[rasn(size("32"))] + pub x: OctetString, + #[rasn(size("32"))] + pub y: OctetString, + } + impl EccP256CurvePointUncompressedP256 { + pub fn new(x: OctetString, y: OctetString) -> Self { + Self { x, y } + } + } + #[doc = "*"] + #[doc = " * @brief This structure specifies a point on an elliptic curve in Weierstrass"] + #[doc = " * form defined over a 256-bit prime number. The curves supported in this"] + #[doc = " * standard are NIST p256 as defined in FIPS 186-4, Brainpool p256r1 as"] + #[doc = " * defined in RFC 5639, and the SM2 curve as defined in GB/T 32918.5-2017."] + #[doc = " * The fields in this structure are OCTET STRINGS produced with the elliptic"] + #[doc = " * curve point encoding and decoding methods defined in subclause 5.5.6 of"] + #[doc = " * IEEE Std 1363-2000. The x-coordinate is encoded as an unsigned integer of"] + #[doc = " * length 32 octets in network byte order for all values of the CHOICE; the"] + #[doc = " * encoding of the y-coordinate y depends on whether the point is x-only,"] + #[doc = " * compressed, or uncompressed. If the point is x-only, y is omitted. If the"] + #[doc = " * point is compressed, the value of type depends on the least significant"] + #[doc = " * bit of y: if the least significant bit of y is 0, type takes the value"] + #[doc = " * compressed-y-0, and if the least significant bit of y is 1, type takes the"] + #[doc = " * value compressed-y-1. If the point is uncompressed, y is encoded explicitly"] + #[doc = " * as an unsigned integer of length 32 octets in network byte order."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization"] + #[doc = " * for the relevant operations specified in 6.1.2 if it appears in a "] + #[doc = " * HeaderInfo or in a ToBeSignedCertificate. See the definitions of HeaderInfo"] + #[doc = " * and ToBeSignedCertificate for a specification of the canonicalization "] + #[doc = " * operations."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + pub enum EccP256CurvePoint { + #[rasn(size("32"), identifier = "x-only")] + x_only(OctetString), + fill(()), + #[rasn(size("32"), identifier = "compressed-y-0")] + compressed_y_0(OctetString), + #[rasn(size("32"), identifier = "compressed-y-1")] + compressed_y_1(OctetString), + uncompressedP256(EccP256CurvePointUncompressedP256), + } + #[doc = " Inner type "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EccP384CurvePointUncompressedP384 { + #[rasn(size("48"))] + pub x: OctetString, + #[rasn(size("48"))] + pub y: OctetString, + } + impl EccP384CurvePointUncompressedP384 { + pub fn new(x: OctetString, y: OctetString) -> Self { + Self { x, y } + } + } + #[doc = "*"] + #[doc = " * @brief This structure specifies a point on an elliptic curve in"] + #[doc = " * Weierstrass form defined over a 384-bit prime number. The only supported"] + #[doc = " * such curve in this standard is Brainpool p384r1 as defined in RFC 5639."] + #[doc = " * The fields in this structure are octet strings produced with the elliptic"] + #[doc = " * curve point encoding and decoding methods defined in subclause 5.5.6 of"] + #[doc = " * IEEE Std 1363-2000. The x-coordinate is encoded as an unsigned integer of"] + #[doc = " * length 48 octets in network byte order for all values of the CHOICE; the"] + #[doc = " * encoding of the y-coordinate y depends on whether the point is x-only,"] + #[doc = " * compressed, or uncompressed. If the point is x-only, y is omitted. If the"] + #[doc = " * point is compressed, the value of type depends on the least significant"] + #[doc = " * bit of y: if the least significant bit of y is 0, type takes the value"] + #[doc = " * compressed-y-0, and if the least significant bit of y is 1, type takes the"] + #[doc = " * value compressed-y-1. If the point is uncompressed, y is encoded"] + #[doc = " * explicitly as an unsigned integer of length 48 octets in network byte order."] + #[doc = " * "] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization"] + #[doc = " * for the relevant operations specified in 6.1.2 if it appears in a "] + #[doc = " * HeaderInfo or in a ToBeSignedCertificate. See the definitions of HeaderInfo"] + #[doc = " * and ToBeSignedCertificate for a specification of the canonicalization "] + #[doc = " * operations."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + pub enum EccP384CurvePoint { + #[rasn(size("48"), identifier = "x-only")] + x_only(OctetString), + fill(()), + #[rasn(size("48"), identifier = "compressed-y-0")] + compressed_y_0(OctetString), + #[rasn(size("48"), identifier = "compressed-y-1")] + compressed_y_1(OctetString), + uncompressedP384(EccP384CurvePointUncompressedP384), + } + #[doc = "*"] + #[doc = " * @brief This structure represents an ECDSA signature. The signature is"] + #[doc = " * generated as specified in 5.3.1."] + #[doc = " *"] + #[doc = " * If the signature process followed the specification of FIPS 186-4"] + #[doc = " * and output the integer r, r is represented as an EccP256CurvePoint"] + #[doc = " * indicating the selection x-only."] + #[doc = " *"] + #[doc = " * If the signature process followed the specification of SEC 1 and"] + #[doc = " * output the elliptic curve point R to allow for fast verification, R is"] + #[doc = " * represented as an EccP256CurvePoint indicating the choice compressed-y-0,"] + #[doc = " * compressed-y-1, or uncompressed at the sender's discretion."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization "] + #[doc = " * for the relevant operations specified in 6.1.2. When this data structure "] + #[doc = " * is canonicalized, the EccP256CurvePoint in rSig is represented in the "] + #[doc = " * form x-only."] + #[doc = " *"] + #[doc = " * @note When the signature is of form x-only, the x-value in rSig is"] + #[doc = " * an integer mod n, the order of the group; when the signature is of form"] + #[doc = " * compressed-y-\\*, the x-value in rSig is an integer mod p, the underlying"] + #[doc = " * prime defining the finite field. In principle this means that to convert a"] + #[doc = " * signature from form compressed-y-\\* to form x-only, the converter checks "] + #[doc = " * the x-value to see if it lies between n and p and reduces it mod n if so. "] + #[doc = " * In practice this check is unnecessary: Haase's Theorem states that "] + #[doc = " * difference between n and p is always less than 2*square-root(p), and so the "] + #[doc = " * chance that an integer lies between n and p, for a 256-bit curve, is "] + #[doc = " * bounded above by approximately square-root(p)/p or 2^(-128). For the "] + #[doc = " * 256-bit curves in this standard, the exact values of n and p in hexadecimal "] + #[doc = " * are:"] + #[doc = " *"] + #[doc = " * NISTp256:"] + #[doc = " * - p = FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF"] + #[doc = " * - n = FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"] + #[doc = " *"] + #[doc = " * Brainpoolp256:"] + #[doc = " * - p = A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377"] + #[doc = " * - n = A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7"] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EcdsaP256Signature { + #[rasn(identifier = "rSig")] + pub r_sig: EccP256CurvePoint, + #[rasn(size("32"), identifier = "sSig")] + pub s_sig: OctetString, + } + impl EcdsaP256Signature { + pub fn new(r_sig: EccP256CurvePoint, s_sig: OctetString) -> Self { + Self { r_sig, s_sig } + } + } + #[doc = "*"] + #[doc = " * @brief This structure represents an ECDSA signature. The signature is"] + #[doc = " * generated as specified in 5.3.1."] + #[doc = " *"] + #[doc = " * If the signature process followed the specification of FIPS 186-4"] + #[doc = " * and output the integer r, r is represented as an EccP384CurvePoint"] + #[doc = " * indicating the selection x-only."] + #[doc = " *"] + #[doc = " * If the signature process followed the specification of SEC 1 and"] + #[doc = " * output the elliptic curve point R to allow for fast verification, R is"] + #[doc = " * represented as an EccP384CurvePoint indicating the choice compressed-y-0,"] + #[doc = " * compressed-y-1, or uncompressed at the sender's discretion."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization "] + #[doc = " * for the relevant operations specified in 6.1.2. When this data structure "] + #[doc = " * is canonicalized, the EccP384CurvePoint in rSig is represented in the "] + #[doc = " * form x-only."] + #[doc = " *"] + #[doc = " * @note When the signature is of form x-only, the x-value in rSig is"] + #[doc = " * an integer mod n, the order of the group; when the signature is of form"] + #[doc = " * compressed-y-\\*, the x-value in rSig is an integer mod p, the underlying"] + #[doc = " * prime defining the finite field. In principle this means that to convert a "] + #[doc = " * signature from form compressed-y-* to form x-only, the converter checks the"] + #[doc = " * x-value to see if it lies between n and p and reduces it mod n if so. In"] + #[doc = " * practice this check is unnecessary: Haase's Theorem states that difference"] + #[doc = " * between n and p is always less than 2*square-root(p), and so the chance"] + #[doc = " * that an integer lies between n and p, for a 384-bit curve, is bounded"] + #[doc = " * above by approximately square-root(p)/p or 2^(-192). For the 384-bit curve"] + #[doc = " * in this standard, the exact values of n and p in hexadecimal are:"] + #[doc = " * - p = 8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123"] + #[doc = " * ACD3A729901D1A71874700133107EC53"] + #[doc = " * - n = 8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7"] + #[doc = " * CF3AB6AF6B7FC3103B883202E9046565"] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EcdsaP384Signature { + #[rasn(identifier = "rSig")] + pub r_sig: EccP384CurvePoint, + #[rasn(size("48"), identifier = "sSig")] + pub s_sig: OctetString, + } + impl EcdsaP384Signature { + pub fn new(r_sig: EccP384CurvePoint, s_sig: OctetString) -> Self { + Self { r_sig, s_sig } + } + } + #[doc = "*"] + #[doc = " * @brief This data structure is used to transfer a 16-byte symmetric key "] + #[doc = " * encrypted using SM2 encryption as specified in 5.3.3. The symmetric key is "] + #[doc = " * input to the key encryption process with no headers, encapsulation, or "] + #[doc = " * length indication. Encryption and decryption are carried out as specified "] + #[doc = " * in 5.3.5.2."] + #[doc = " * "] + #[doc = " * @param v: is the sender's ephemeral public key, which is the output V from"] + #[doc = " * encryption as specified in 5.3.5.2."] + #[doc = " *"] + #[doc = " * @param c: is the encrypted symmetric key, which is the output C from "] + #[doc = " * encryption as specified in 5.3.5.2. The algorithm for the symmetric key "] + #[doc = " * is identified by the CHOICE indicated in the following SymmetricCiphertext. "] + #[doc = " * For SM2 this algorithm shall be SM4."] + #[doc = " *"] + #[doc = " * @param t: is the authentication tag, which is the output tag from"] + #[doc = " * encryption as specified in 5.3.5.2."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EcencP256EncryptedKey { + pub v: EccP256CurvePoint, + #[rasn(size("16"))] + pub c: OctetString, + #[rasn(size("32"))] + pub t: OctetString, + } + impl EcencP256EncryptedKey { + pub fn new(v: EccP256CurvePoint, c: OctetString, t: OctetString) -> Self { + Self { v, c, t } + } + } + #[doc = "*"] + #[doc = " * @brief This data structure is used to transfer a 16-byte symmetric key"] + #[doc = " * encrypted using ECIES as specified in IEEE Std 1363a-2004. The symmetric "] + #[doc = " * key is input to the key encryption process with no headers, encapsulation, "] + #[doc = " * or length indication. Encryption and decryption are carried out as "] + #[doc = " * specified in 5.3.5.1."] + #[doc = " *"] + #[doc = " * @param v: is the sender's ephemeral public key, which is the output V from"] + #[doc = " * encryption as specified in 5.3.5.1."] + #[doc = " *"] + #[doc = " * @param c: is the encrypted symmetric key, which is the output C from "] + #[doc = " * encryption as specified in 5.3.5.1. The algorithm for the symmetric key "] + #[doc = " * is identified by the CHOICE indicated in the following SymmetricCiphertext. "] + #[doc = " * For ECIES this shall be AES-128."] + #[doc = " *"] + #[doc = " * @param t: is the authentication tag, which is the output tag from"] + #[doc = " * encryption as specified in 5.3.5.1."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EciesP256EncryptedKey { + pub v: EccP256CurvePoint, + #[rasn(size("16"))] + pub c: OctetString, + #[rasn(size("16"))] + pub t: OctetString, + } + impl EciesP256EncryptedKey { + pub fn new(v: EccP256CurvePoint, c: OctetString, t: OctetString) -> Self { + Self { v, c, t } + } + } + #[doc = "*"] + #[doc = " * @brief This structure represents a elliptic curve signature where the"] + #[doc = " * component r is constrained to be an integer. This structure supports SM2 "] + #[doc = " * signatures as specified in 5.3.1.3."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EcsigP256Signature { + #[rasn(size("32"), identifier = "rSig")] + pub r_sig: OctetString, + #[rasn(size("32"), identifier = "sSig")] + pub s_sig: OctetString, + } + impl EcsigP256Signature { + pub fn new(r_sig: OctetString, s_sig: OctetString) -> Self { + Self { r_sig, s_sig } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains an estimate of the geodetic altitude above"] + #[doc = " * or below the WGS84 ellipsoid. The 16-bit value is interpreted as an"] + #[doc = " * integer number of decimeters representing the height above a minimum"] + #[doc = " * height of -409.5 m, with the maximum height being 6143.9 m."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct Elevation(pub Uint16); + #[doc = "*"] + #[doc = " * @brief This structure contains an encryption key, which may be a public or"] + #[doc = " * a symmetric key."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization "] + #[doc = " * for the relevant operations specified in 6.1.2 if it appears in a "] + #[doc = " * HeaderInfo or in a ToBeSignedCertificate. The canonicalization applies to"] + #[doc = " * the PublicEncryptionKey. See the definitions of HeaderInfo and "] + #[doc = " * ToBeSignedCertificate for a specification of the canonicalization "] + #[doc = " * operations."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + pub enum EncryptionKey { + public(PublicEncryptionKey), + symmetric(SymmetricEncryptionKey), + } + #[doc = "*"] + #[doc = " * @brief This type is used as an identifier for instances of ExtContent "] + #[doc = " * within an EXT-TYPE. "] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("0..=255"))] + pub struct ExtId(pub u8); + #[doc = "***************************************************************************"] + #[doc = " Location Structures "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This structure represents a geographic region of a specified form."] + #[doc = " * A certificate is not valid if any part of the region indicated in its"] + #[doc = " * scope field lies outside the region indicated in the scope of its issuer."] + #[doc = " *"] + #[doc = " * @param circularRegion: contains a single instance of the CircularRegion"] + #[doc = " * structure."] + #[doc = " *"] + #[doc = " * @param rectangularRegion: is an array of RectangularRegion structures"] + #[doc = " * containing at least one entry. This field is interpreted as a series of"] + #[doc = " * rectangles, which may overlap or be disjoint. The permitted region is any"] + #[doc = " * point within any of the rectangles."] + #[doc = " *"] + #[doc = " * @param polygonalRegion: contains a single instance of the PolygonalRegion"] + #[doc = " * structure."] + #[doc = " *"] + #[doc = " * @param identifiedRegion: is an array of IdentifiedRegion structures"] + #[doc = " * containing at least one entry. The permitted region is any point within"] + #[doc = " * any of the identified regions."] + #[doc = " *"] + #[doc = " * @note Critical information fields:"] + #[doc = " * - If present, this is a critical information field as defined in 5.2.6."] + #[doc = " * An implementation that does not recognize the indicated CHOICE when"] + #[doc = " * verifying a signed SPDU shall indicate that the signed SPDU is invalid in "] + #[doc = " * the sense of 4.2.2.3.2, that is, it is invalid in the sense that its "] + #[doc = " * validity cannot be established."] + #[doc = " * - If selected, rectangularRegion is a critical information field as"] + #[doc = " * defined in 5.2.6. An implementation that does not support the number of"] + #[doc = " * RectangularRegion in rectangularRegions when verifying a signed SPDU shall"] + #[doc = " * indicate that the signed SPDU is invalid in the sense of 4.2.2.3.2, that "] + #[doc = " * is, it is invalid in the sense that its validity cannot be established. "] + #[doc = " * A conformant implementation shall support rectangularRegions fields "] + #[doc = " * containing at least eight entries."] + #[doc = " * - If selected, identifiedRegion is a critical information field as"] + #[doc = " * defined in 5.2.6. An implementation that does not support the number of"] + #[doc = " * IdentifiedRegion in identifiedRegion shall reject the signed SPDU as"] + #[doc = " * invalid in the sense of 4.2.2.3.2, that is, it is invalid in the sense "] + #[doc = " * that its validity cannot be established. A conformant implementation shall "] + #[doc = " * support identifiedRegion fields containing at least eight entries."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum GeographicRegion { + circularRegion(CircularRegion), + rectangularRegion(SequenceOfRectangularRegion), + polygonalRegion(PolygonalRegion), + identifiedRegion(SequenceOfIdentifiedRegion), + } + #[doc = "*"] + #[doc = " * @brief This is the group linkage value. See 5.1.3 and 7.3 for details of"] + #[doc = " * use."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct GroupLinkageValue { + #[rasn(size("4"), identifier = "jValue")] + pub j_value: OctetString, + #[rasn(size("9"))] + pub value: OctetString, + } + impl GroupLinkageValue { + pub fn new(j_value: OctetString, value: OctetString) -> Self { + Self { j_value, value } + } + } + #[doc = "*"] + #[doc = " * @brief This structure identifies a hash algorithm. The value sha256, "] + #[doc = " * indicates SHA-256. The value sha384 indicates SHA-384. The value sm3 "] + #[doc = " * indicates SM3. See 5.3.3 for more details."] + #[doc = " *"] + #[doc = " * @note Critical information fields: This is a critical information field as"] + #[doc = " * defined in 5.2.6. An implementation that does not recognize the enumerated"] + #[doc = " * value of this type in a signed SPDU when verifying a signed SPDU shall "] + #[doc = " * indicate that the signed SPDU is invalid in the sense of 4.2.2.3.2, that "] + #[doc = " * is, it is invalid in the sense that its validity cannot be established."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash, Copy)] + #[rasn(enumerated)] + #[non_exhaustive] + pub enum HashAlgorithm { + sha256 = 0, + #[rasn(extension_addition)] + sha384 = 1, + #[rasn(extension_addition)] + sm3 = 2, + } + #[doc = "*"] + #[doc = " * @brief This type contains the truncated hash of another data structure."] + #[doc = " * The HashedId10 for a given data structure is calculated by calculating the"] + #[doc = " * hash of the encoded data structure and taking the low-order ten bytes of"] + #[doc = " * the hash output. The low-order ten bytes are the last ten bytes of the "] + #[doc = " * hash when represented in network byte order. If the data structure"] + #[doc = " * is subject to canonicalization it is canonicalized before hashing. See "] + #[doc = " * Example below."] + #[doc = " *"] + #[doc = " * The hash algorithm to be used to calculate a HashedId10 within a"] + #[doc = " * structure depends on the context. In this standard, for each structure"] + #[doc = " * that includes a HashedId10 field, the corresponding text indicates how the"] + #[doc = " * hash algorithm is determined. See also the discussion in 5.3.9."] + #[doc = " *"] + #[doc = " * Example: Consider the SHA-256 hash of the empty string:"] + #[doc = " *"] + #[doc = " * SHA-256(\"\") ="] + #[doc = " * e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"] + #[doc = " *"] + #[doc = " * The HashedId10 derived from this hash corresponds to the following:"] + #[doc = " *"] + #[doc = " * HashedId10 = 934ca495991b7852b855."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct HashedId10(pub FixedOctetString<10usize>); + #[doc = "*"] + #[doc = " * @brief This type contains the truncated hash of another data structure."] + #[doc = " * The HashedId3 for a given data structure is calculated by calculating the"] + #[doc = " * hash of the encoded data structure and taking the low-order three bytes of"] + #[doc = " * the hash output. The low-order three bytes are the last three bytes of the"] + #[doc = " * 32-byte hash when represented in network byte order. If the data structure"] + #[doc = " * is subject to canonicalization it is canonicalized before hashing. See "] + #[doc = " * Example below."] + #[doc = " *"] + #[doc = " * The hash algorithm to be used to calculate a HashedId3 within a"] + #[doc = " * structure depends on the context. In this standard, for each structure"] + #[doc = " * that includes a HashedId3 field, the corresponding text indicates how the"] + #[doc = " * hash algorithm is determined. See also the discussion in 5.3.9."] + #[doc = " *"] + #[doc = " * Example: Consider the SHA-256 hash of the empty string:"] + #[doc = " *"] + #[doc = " * SHA-256(\"\") ="] + #[doc = " * e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"] + #[doc = " *"] + #[doc = " * The HashedId3 derived from this hash corresponds to the following:"] + #[doc = " *"] + #[doc = " * HashedId3 = 52b855."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct HashedId3(pub FixedOctetString<3usize>); + #[doc = "*"] + #[doc = " * @brief This data structure contains the truncated hash of another data"] + #[doc = " * structure. The HashedId32 for a given data structure is calculated by "] + #[doc = " * calculating the hash of the encoded data structure and taking the "] + #[doc = " * low-order 32 bytes of the hash output. The low-order 32 bytes are the last"] + #[doc = " * 32 bytes of the hash when represented in network byte order. If the data "] + #[doc = " * structure is subject to canonicalization it is canonicalized before "] + #[doc = " * hashing. See Example below."] + #[doc = " *"] + #[doc = " * The hash algorithm to be used to calculate a HashedId32 within a"] + #[doc = " * structure depends on the context. In this standard, for each structure"] + #[doc = " * that includes a HashedId32 field, the corresponding text indicates how the"] + #[doc = " * hash algorithm is determined. See also the discussion in 5.3.9."] + #[doc = " *"] + #[doc = " * Example: Consider the SHA-256 hash of the empty string:"] + #[doc = " *"] + #[doc = " * SHA-256(\"\") ="] + #[doc = " * e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"] + #[doc = " *"] + #[doc = " * The HashedId32 derived from this hash corresponds to the following:"] + #[doc = " *"] + #[doc = " * HashedId32 = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8"] + #[doc = " * 55."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct HashedId32(pub FixedOctetString<32usize>); + #[doc = "*"] + #[doc = " * @brief This data structure contains the truncated hash of another data "] + #[doc = " * structure. The HashedId48 for a given data structure is calculated by"] + #[doc = " * calculating the hash of the encoded data structure and taking the "] + #[doc = " * low-order 48 bytes of the hash output. The low-order 48 bytes are the last"] + #[doc = " * 48 bytes of the hash when represented in network byte order. If the data"] + #[doc = " * structure is subject to canonicalization it is canonicalized before"] + #[doc = " * hashing. See Example below."] + #[doc = " *"] + #[doc = " * The hash algorithm to be used to calculate a HashedId48 within a"] + #[doc = " * structure depends on the context. In this standard, for each structure"] + #[doc = " * that includes a HashedId48 field, the corresponding text indicates how the"] + #[doc = " * hash algorithm is determined. See also the discussion in 5.3.9."] + #[doc = " *"] + #[doc = " * Example: Consider the SHA-384 hash of the empty string:"] + #[doc = " *"] + #[doc = " * SHA-384(\"\") = 38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6"] + #[doc = " * e1da274edebfe76f65fbd51ad2f14898b95b"] + #[doc = " *"] + #[doc = " * The HashedId48 derived from this hash corresponds to the following:"] + #[doc = " *"] + #[doc = " * HashedId48 = 38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e"] + #[doc = " * 1da274edebfe76f65fbd51ad2f14898b95b."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct HashedId48(pub FixedOctetString<48usize>); + #[doc = "*"] + #[doc = " * @brief This type contains the truncated hash of another data structure."] + #[doc = " * The HashedId8 for a given data structure is calculated by calculating the"] + #[doc = " * hash of the encoded data structure and taking the low-order eight bytes of"] + #[doc = " * the hash output. The low-order eight bytes are the last eight bytes of the"] + #[doc = " * hash when represented in network byte order. If the data structure"] + #[doc = " * is subject to canonicalization it is canonicalized before hashing. See "] + #[doc = " * Example below."] + #[doc = " *"] + #[doc = " * The hash algorithm to be used to calculate a HashedId8 within a"] + #[doc = " * structure depends on the context. In this standard, for each structure"] + #[doc = " * that includes a HashedId8 field, the corresponding text indicates how the"] + #[doc = " * hash algorithm is determined. See also the discussion in 5.3.9."] + #[doc = " *"] + #[doc = " * Example: Consider the SHA-256 hash of the empty string:"] + #[doc = " *"] + #[doc = " * SHA-256(\"\") ="] + #[doc = " * e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"] + #[doc = " *"] + #[doc = " * The HashedId8 derived from this hash corresponds to the following:"] + #[doc = " *"] + #[doc = " * HashedId8 = a495991b7852b855."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct HashedId8(pub FixedOctetString<8usize>); + #[doc = "*"] + #[doc = " * @brief This is a UTF-8 string as defined in IETF RFC 3629. The contents"] + #[doc = " * are determined by policy."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, size("0..=255"))] + pub struct Hostname(pub Utf8String); + #[doc = "***************************************************************************"] + #[doc = " Pseudonym Linkage "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This atomic type is used in the definition of other data structures."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct IValue(pub Uint16); + #[doc = "*"] + #[doc = " * @brief This structure indicates the region of validity of a certificate "] + #[doc = " * using region identifiers. "] + #[doc = " * A conformant implementation that supports this type shall support at least "] + #[doc = " * one of the possible CHOICE values. The Protocol Implementation Conformance "] + #[doc = " * Statement (PICS) provided in Annex A allows an implementation to state "] + #[doc = " * which CountryOnly values it recognizes."] + #[doc = " *"] + #[doc = " * @param countryOnly: indicates that only a country (or a geographic entity "] + #[doc = " * included in a country list) is given."] + #[doc = " *"] + #[doc = " * @param countryAndRegions: indicates that one or more top-level regions "] + #[doc = " * within a country (as defined by the region listing associated with that "] + #[doc = " * country) is given."] + #[doc = " *"] + #[doc = " * @param countryAndSubregions: indicates that one or more regions smaller "] + #[doc = " * than the top-level regions within a country (as defined by the region "] + #[doc = " * listing associated with that country) is given."] + #[doc = " *"] + #[doc = " * Critical information fields: If present, this is a critical"] + #[doc = " * information field as defined in 5.2.6. An implementation that does not"] + #[doc = " * recognize the indicated CHOICE when verifying a signed SPDU shall indicate"] + #[doc = " * that the signed SPDU is invalid in the sense of 4.2.2.3.2, that is, it is "] + #[doc = " * invalid in the sense that its validity cannot be established."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum IdentifiedRegion { + countryOnly(UnCountryId), + countryAndRegions(CountryAndRegions), + countryAndSubregions(CountryAndSubregions), + } + #[doc = "*"] + #[doc = " * @brief The known latitudes are from -900,000,000 to +900,000,000 in 0.1"] + #[doc = " * microdegree intervals."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("-900000000..=900000000"))] + pub struct KnownLatitude(pub NinetyDegreeInt); + #[doc = "*"] + #[doc = " * @brief The known longitudes are from -1,799,999,999 to +1,800,000,000 in"] + #[doc = " * 0.1 microdegree intervals."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("-1799999999..=1800000000"))] + pub struct KnownLongitude(pub OneEightyDegreeInt); + #[doc = "*"] + #[doc = " * @brief This structure contains a LA Identifier for use in the algorithms"] + #[doc = " * specified in 5.1.3.4."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct LaId(pub FixedOctetString<2usize>); + #[doc = "*"] + #[doc = " * @brief This type contains an INTEGER encoding an estimate of the latitude"] + #[doc = " * with precision 1/10th microdegree relative to the World Geodetic System"] + #[doc = " * (WGS)-84 datum as defined in NIMA Technical Report TR8350.2."] + #[doc = " * The integer in the latitude field is no more than 900 000 000 and no less "] + #[doc = " * than ?900 000 000, except that the value 900 000 001 is used to indicate "] + #[doc = " * the latitude was not available to the sender."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct Latitude(pub NinetyDegreeInt); + #[doc = "*"] + #[doc = " * @brief This structure contains a linkage seed value for use in the"] + #[doc = " * algorithms specified in 5.1.3.4."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct LinkageSeed(pub FixedOctetString<16usize>); + #[doc = "*"] + #[doc = " * @brief This is the individual linkage value. See 5.1.3 and 7.3 for details"] + #[doc = " * of use."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct LinkageValue(pub FixedOctetString<9usize>); + #[doc = "*"] + #[doc = " * @brief This type contains an INTEGER encoding an estimate of the longitude"] + #[doc = " * with precision 1/10th microdegree relative to the World Geodetic System"] + #[doc = " * (WGS)-84 datum as defined in NIMA Technical Report TR8350.2."] + #[doc = " * The integer in the longitude field is no more than 1 800 000 000 and no "] + #[doc = " * less than ?1 799 999 999, except that the value 1 800 000 001 is used to "] + #[doc = " * indicate that the longitude was not available to the sender."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct Longitude(pub OneEightyDegreeInt); + #[doc = "*"] + #[doc = " * @brief The integer in the latitude field is no more than 900,000,000 and"] + #[doc = " * no less than -900,000,000, except that the value 900,000,001 is used to"] + #[doc = " * indicate the latitude was not available to the sender."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("-900000000..=900000001"))] + pub struct NinetyDegreeInt(pub i32); + #[doc = "*"] + #[doc = " * @brief The integer in the longitude field is no more than 1,800,000,000"] + #[doc = " * and no less than -1,799,999,999, except that the value 1,800,000,001 is"] + #[doc = " * used to indicate that the longitude was not available to the sender."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("-1799999999..=1800000001"))] + pub struct OneEightyDegreeInt(pub i32); + #[doc = "***************************************************************************"] + #[doc = " OCTET STRING Types "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This is a synonym for ASN.1 OCTET STRING, and is used in the"] + #[doc = " * definition of other data structures."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct Opaque(pub OctetString); + #[doc = "*"] + #[doc = " * @brief This structure defines a region using a series of distinct"] + #[doc = " * geographic points, defined on the surface of the reference ellipsoid. The"] + #[doc = " * region is specified by connecting the points in the order they appear,"] + #[doc = " * with each pair of points connected by the geodesic on the reference"] + #[doc = " * ellipsoid. The polygon is completed by connecting the final point to the"] + #[doc = " * first point. The allowed region is the interior of the polygon and its"] + #[doc = " * boundary."] + #[doc = " *"] + #[doc = " * A point which contains an elevation component is considered to be"] + #[doc = " * within the polygonal region if its horizontal projection onto the"] + #[doc = " * reference ellipsoid lies within the region."] + #[doc = " *"] + #[doc = " * A valid PolygonalRegion contains at least three points. In a valid"] + #[doc = " * PolygonalRegion, the implied lines that make up the sides of the polygon"] + #[doc = " * do not intersect."] + #[doc = " *"] + #[doc = " * @note This type does not support enclaves / exclaves. This might be "] + #[doc = " * addressed in a future version of this standard."] + #[doc = " *"] + #[doc = " * @note Critical information fields: If present, this is a critical"] + #[doc = " * information field as defined in 5.2.6. An implementation that does not"] + #[doc = " * support the number of TwoDLocation in the PolygonalRegion when verifying a"] + #[doc = " * signed SPDU shall indicate that the signed SPDU is invalid. A compliant"] + #[doc = " * implementation shall support PolygonalRegions containing at least eight"] + #[doc = " * TwoDLocation entries."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, size("3.."))] + pub struct PolygonalRegion(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type represents the PSID defined in IEEE Std 1609.12."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("0.."))] + pub struct Psid(pub Integer); + #[doc = "***************************************************************************"] + #[doc = " PSID / ITS-AID "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This structure represents the permissions that the certificate "] + #[doc = " * holder has with respect to activities for a single application area, "] + #[doc = " * identified by a Psid. "] + #[doc = " *"] + #[doc = " * @note The determination as to whether the activities are consistent with "] + #[doc = " * the permissions indicated by the PSID and ServiceSpecificPermissions is "] + #[doc = " * made by the SDEE and not by the SDS; the SDS provides the PSID and SSP "] + #[doc = " * information to the SDEE to enable the SDEE to make that determination. "] + #[doc = " * See 5.2.4.3.3 for more information."] + #[doc = " *"] + #[doc = " * @note The SDEE specification is expected to specify what application "] + #[doc = " * activities are permitted by particular ServiceSpecificPermissions values."] + #[doc = " * The SDEE specification is also expected EITHER to specify application "] + #[doc = " * activities that are permitted if the ServiceSpecificPermissions is "] + #[doc = " * omitted, OR to state that the ServiceSpecificPermissions need to always be "] + #[doc = " * present."] + #[doc = " *"] + #[doc = " * @note Consistency with signed SPDU: As noted in 5.1.1,"] + #[doc = " * consistency between the SSP and the signed SPDU is defined by rules"] + #[doc = " * specific to the given PSID and is out of scope for this standard."] + #[doc = " *"] + #[doc = " * @note Consistency with issuing certificate: If a certificate has an"] + #[doc = " * appPermissions entry A for which the ssp field is omitted, A is consistent"] + #[doc = " * with the issuing certificate if the issuing certificate contains a"] + #[doc = " * PsidSspRange P for which the following holds:"] + #[doc = " * - The psid field in P is equal to the psid field in A and one of the"] + #[doc = " * following is true:"] + #[doc = " * - The sspRange field in P indicates all."] + #[doc = " * - The sspRange field in P indicates opaque and one of the entries in"] + #[doc = " * opaque is an OCTET STRING of length 0."] + #[doc = " *"] + #[doc = " * For consistency rules for other forms of the ssp field, see the"] + #[doc = " * following subclauses."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct PsidSsp { + pub psid: Psid, + pub ssp: Option, + } + impl PsidSsp { + pub fn new(psid: Psid, ssp: Option) -> Self { + Self { psid, ssp } + } + } + #[doc = "*"] + #[doc = " * @brief This structure represents the certificate issuing or requesting"] + #[doc = " * permissions of the certificate holder with respect to one particular set"] + #[doc = " * of application permissions."] + #[doc = " *"] + #[doc = " * @param psid: identifies the application area."] + #[doc = " *"] + #[doc = " * @param sspRange: identifies the SSPs associated with that PSID for which"] + #[doc = " * the holder may issue or request certificates. If sspRange is omitted, the"] + #[doc = " * holder may issue or request certificates for any SSP for that PSID."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct PsidSspRange { + pub psid: Psid, + #[rasn(identifier = "sspRange")] + pub ssp_range: Option, + } + impl PsidSspRange { + pub fn new(psid: Psid, ssp_range: Option) -> Self { + Self { psid, ssp_range } + } + } + #[doc = "*"] + #[doc = " * @brief This structure specifies a public encryption key and the associated"] + #[doc = " * symmetric algorithm which is used for bulk data encryption when encrypting"] + #[doc = " * for that public key."] + #[doc = " * "] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization"] + #[doc = " * for the relevant operations specified in 6.1.2 if it appears in a "] + #[doc = " * HeaderInfo or in a ToBeSignedCertificate. The canonicalization applies to "] + #[doc = " * the BasePublicEncryptionKey. See the definitions of HeaderInfo and "] + #[doc = " * ToBeSignedCertificate for a specification of the canonicalization "] + #[doc = " * operations."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct PublicEncryptionKey { + #[rasn(identifier = "supportedSymmAlg")] + pub supported_symm_alg: SymmAlgorithm, + #[rasn(identifier = "publicKey")] + pub public_key: BasePublicEncryptionKey, + } + impl PublicEncryptionKey { + pub fn new(supported_symm_alg: SymmAlgorithm, public_key: BasePublicEncryptionKey) -> Self { + Self { + supported_symm_alg, + public_key, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure represents a public key and states with what "] + #[doc = " * algorithm the public key is to be used. Cryptographic mechanisms are "] + #[doc = " * defined in 5.3."] + #[doc = " * An EccP256CurvePoint or EccP384CurvePoint within a PublicVerificationKey "] + #[doc = " * structure is invalid if it indicates the choice x-only. "] + #[doc = " *"] + #[doc = " * @note Critical information fields: If present, this is a critical "] + #[doc = " * information field as defined in 5.2.6. An implementation that does not "] + #[doc = " * recognize the indicated CHOICE when verifying a signed SPDU shall indicate "] + #[doc = " * that the signed SPDU is invalid indicate that the signed SPDU is invalid "] + #[doc = " * in the sense of 4.2.2.3.2, that is, it is invalid in the sense that its "] + #[doc = " * validity cannot be established. "] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization "] + #[doc = " * for the relevant operations specified in 6.1.2. The canonicalization "] + #[doc = " * applies to the EccP256CurvePoint and the Ecc384CurvePoint. Both forms of "] + #[doc = " * point are encoded in compressed form, i.e., such that the choice indicated "] + #[doc = " * within the Ecc*CurvePoint is compressed-y-0 or compressed-y-1."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum PublicVerificationKey { + ecdsaNistP256(EccP256CurvePoint), + ecdsaBrainpoolP256r1(EccP256CurvePoint), + #[rasn(extension_addition)] + ecdsaBrainpoolP384r1(EccP384CurvePoint), + #[rasn(extension_addition)] + ecdsaNistP384(EccP384CurvePoint), + #[rasn(extension_addition)] + ecsigSm2(EccP256CurvePoint), + } + #[doc = "*"] + #[doc = " * @brief This structure specifies a “rectangle” on the surface of the WGS84 ellipsoid where the "] + #[doc = " * sides are given by lines of constant latitude or longitude. "] + #[doc = " * A point which contains an elevation component is considered to be within the rectangular region "] + #[doc = " * if its horizontal projection onto the reference ellipsoid lies within the region. "] + #[doc = " * A RectangularRegion is invalid if the northWest value is south of the southEast value, or if the "] + #[doc = " * latitude values in the two points are equal, or if the longitude values in the two points are "] + #[doc = " * equal; otherwise it is valid. A certificate that contains an invalid RectangularRegion is invalid."] + #[doc = " *"] + #[doc = " * @param northWest: is the north-west corner of the rectangle."] + #[doc = " *"] + #[doc = " * @param southEast is the south-east corner of the rectangle."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct RectangularRegion { + #[rasn(identifier = "northWest")] + pub north_west: TwoDLocation, + #[rasn(identifier = "southEast")] + pub south_east: TwoDLocation, + } + impl RectangularRegion { + pub fn new(north_west: TwoDLocation, south_east: TwoDLocation) -> Self { + Self { + north_west, + south_east, + } + } + } + #[doc = "*"] + #[doc = " * @brief The meanings of the fields in this structure are to be interpreted"] + #[doc = " * in the context of a country within which the region is located, referred "] + #[doc = " * to as the \"enclosing country\". If this structure is used in a "] + #[doc = " * CountryAndSubregions structure, the enclosing country is the one indicated "] + #[doc = " * by the country field in the CountryAndSubregions structure. If other uses "] + #[doc = " * are defined for this structure in future, it is expected that that "] + #[doc = " * definition will include a specification of how the enclosing country can "] + #[doc = " * be determined."] + #[doc = " * If the enclosing country is the United States of America:"] + #[doc = " * - The region field identifies the state or statistically equivalent "] + #[doc = " * entity using the integer version of the 2010 FIPS codes as provided by the"] + #[doc = " * U.S. Census Bureau (see normative references in Clause 0). "] + #[doc = " * - The values in the subregions field identify the county or county "] + #[doc = " * equivalent entity using the integer version of the 2010 FIPS codes as "] + #[doc = " * provided by the U.S. Census Bureau."] + #[doc = " * If the enclosing country is a different country from the USA, the meaning "] + #[doc = " * of regionAndSubregions is not defined in this version of this standard."] + #[doc = " * A conformant implementation that implements this type shall recognize (in "] + #[doc = " * the sense of \"be able to determine whether a two-dimensional location lies "] + #[doc = " * inside or outside the borders identified by\"), for at least one enclosing"] + #[doc = " * country, at least one value for a region within that country and at least "] + #[doc = " * one subregion for the indicated region. In this version of this standard, "] + #[doc = " * the only means to satisfy this is for a conformant implementation to "] + #[doc = " * recognize, for the USA, at least one of the FIPS state codes for US "] + #[doc = " * states, and at least one of the county codes in at least one of the "] + #[doc = " * recognized states. The Protocol Implementation Conformance Statement "] + #[doc = " * (PICS) provided in Annex A allows an implementation to state which "] + #[doc = " * UnCountryId values it recognizes and which region values are recognized "] + #[doc = " * within that country."] + #[doc = " * If a verifying implementation is required to check that an relevant "] + #[doc = " * geographic information in a signed SPDU is consistent with a certificate "] + #[doc = " * containing one or more instances of this type, then the SDS is permitted "] + #[doc = " * to indicate that the signed SPDU is valid even if some values within "] + #[doc = " * subregions are unrecognized in the sense defined above, so long as the "] + #[doc = " * recognized instances of this type completely contain the relevant "] + #[doc = " * geographic information. Informally, if the recognized values in the "] + #[doc = " * certificate allow the SDS to determine that the SPDU is valid, then it "] + #[doc = " * can make that determination even if there are also unrecognized values "] + #[doc = " * in the certificate. This field is therefore not not a \"critical "] + #[doc = " * information field\" as defined in 5.2.6, because unrecognized values are "] + #[doc = " * permitted so long as the validity of the SPDU can be established with the "] + #[doc = " * recognized values. However, as discussed in 5.2.6, the presence of an "] + #[doc = " * unrecognized value in a certificate can make it impossible to determine "] + #[doc = " * whether the certificate is valid and so whether the SPDU is valid."] + #[doc = " * In this structure:"] + #[doc = " *"] + #[doc = " * @param region: identifies a region within a country."] + #[doc = " *"] + #[doc = " * @param subregions: identifies one or more subregions within region. A "] + #[doc = " * conformant implementation that supports RegionAndSubregions shall support "] + #[doc = " * a subregions field containing at least eight entries."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct RegionAndSubregions { + pub region: Uint8, + pub subregions: SequenceOfUint16, + } + impl RegionAndSubregions { + pub fn new(region: Uint8, subregions: SequenceOfUint16) -> Self { + Self { region, subregions } + } + } + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfHashedId3(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfIdentifiedRegion(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfLinkageSeed(pub SequenceOf); + #[doc = " Anonymous SEQUENCE OF member "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, identifier = "OCTET_STRING")] + pub struct AnonymousSequenceOfOctetString(pub OctetString); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfOctetString(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfPsid(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfPsidSsp(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfPsidSspRange(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfRectangularRegion(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfRegionAndSubregions(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfUint16(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfUint8(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This structure represents the Service Specific Permissions (SSP)"] + #[doc = " * relevant to a given entry in a PsidSsp. The meaning of the SSP is specific"] + #[doc = " * to the associated Psid. SSPs may be PSID-specific octet strings or"] + #[doc = " * bitmap-based. See Annex C for further discussion of how application"] + #[doc = " * specifiers may choose which SSP form to use."] + #[doc = " *"] + #[doc = " * @note Consistency with issuing certificate: If a certificate has an"] + #[doc = " * appPermissions entry A for which the ssp field is opaque, A is consistent"] + #[doc = " * with the issuing certificate if the issuing certificate contains one of"] + #[doc = " * the following:"] + #[doc = " * - (OPTION 1) A SubjectPermissions field indicating the choice all and"] + #[doc = " * no PsidSspRange field containing the psid field in A;"] + #[doc = " * - (OPTION 2) A PsidSspRange P for which the following holds:"] + #[doc = " * - The psid field in P is equal to the psid field in A and one of the"] + #[doc = " * following is true:"] + #[doc = " * - The sspRange field in P indicates all."] + #[doc = " * - The sspRange field in P indicates opaque and one of the entries in"] + #[doc = " * the opaque field in P is an OCTET STRING identical to the opaque field in"] + #[doc = " * A."] + #[doc = " *"] + #[doc = " * For consistency rules for other types of ServiceSpecificPermissions,"] + #[doc = " * see the following subclauses."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum ServiceSpecificPermissions { + opaque(OctetString), + #[rasn(extension_addition)] + bitmapSsp(BitmapSsp), + } + #[doc = "***************************************************************************"] + #[doc = " Crypto Structures "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This structure represents a signature for a supported public key"] + #[doc = " * algorithm. It may be contained within SignedData or Certificate."] + #[doc = " *"] + #[doc = " * @note Critical information fields: If present, this is a critical "] + #[doc = " * information field as defined in 5.2.5. An implementation that does not "] + #[doc = " * recognize the indicated CHOICE for this type when verifying a signed SPDU"] + #[doc = " * shall indicate that the signed SPDU is invalid in the sense of 4.2.2.3.2,"] + #[doc = " * that is, it is invalid in the sense that its validity cannot be "] + #[doc = " * established."] + #[doc = " *"] + #[doc = " * @note Canonicalization: This data structure is subject to canonicalization"] + #[doc = " * for the relevant operations specified in 6.1.2. The canonicalization"] + #[doc = " * applies to instances of this data structure of form EcdsaP256Signature"] + #[doc = " * and EcdsaP384Signature."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum Signature { + ecdsaNistP256Signature(EcdsaP256Signature), + ecdsaBrainpoolP256r1Signature(EcdsaP256Signature), + #[rasn(extension_addition)] + ecdsaBrainpoolP384r1Signature(EcdsaP384Signature), + #[rasn(extension_addition)] + ecdsaNistP384Signature(EcdsaP384Signature), + #[rasn(extension_addition)] + sm2Signature(EcsigP256Signature), + } + #[doc = "*"] + #[doc = " * @brief This structure identifies the SSPs associated with a PSID for"] + #[doc = " * which the holder may issue or request certificates."] + #[doc = " *"] + #[doc = " * @note Consistency with issuing certificate: If a certificate has a"] + #[doc = " * PsidSspRange A for which the ssp field is opaque, A is consistent with"] + #[doc = " * the issuing certificate if the issuing certificate contains one of the"] + #[doc = " * following:"] + #[doc = " * - (OPTION 1) A SubjectPermissions field indicating the choice all and"] + #[doc = " * no PsidSspRange field containing the psid field in A;"] + #[doc = " * - (OPTION 2) A PsidSspRange P for which the following holds:"] + #[doc = " * - The psid field in P is equal to the psid field in A and one of the"] + #[doc = " * following is true:"] + #[doc = " * - The sspRange field in P indicates all."] + #[doc = " * - The sspRange field in P indicates opaque, and the sspRange field in"] + #[doc = " * A indicates opaque, and every OCTET STRING within the opaque in A is a"] + #[doc = " * duplicate of an OCTET STRING within the opaque in P."] + #[doc = " *"] + #[doc = " * If a certificate has a PsidSspRange A for which the ssp field is all,"] + #[doc = " * A is consistent with the issuing certificate if the issuing certificate"] + #[doc = " * contains a PsidSspRange P for which the following holds:"] + #[doc = " * - (OPTION 1) A SubjectPermissions field indicating the choice all and"] + #[doc = " * no PsidSspRange field containing the psid field in A;"] + #[doc = " * - (OPTION 2) A PsidSspRange P for which the psid field in P is equal to"] + #[doc = " * the psid field in A and the sspRange field in P indicates all."] + #[doc = " *"] + #[doc = " * For consistency rules for other types of SspRange, see the following"] + #[doc = " * subclauses."] + #[doc = " *"] + #[doc = " * @note The choice \"all\" may also be indicated by omitting the"] + #[doc = " * SspRange in the enclosing PsidSspRange structure. Omitting the SspRange is"] + #[doc = " * preferred to explicitly indicating \"all\"."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum SspRange { + opaque(SequenceOfOctetString), + all(()), + #[rasn(extension_addition)] + bitmapSspRange(BitmapSspRange), + } + #[doc = "***************************************************************************"] + #[doc = " Certificate Components "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This field contains the certificate holder's assurance level, which"] + #[doc = " * indicates the security of both the platform and storage of secret keys as"] + #[doc = " * well as the confidence in this assessment."] + #[doc = " *"] + #[doc = " * This field is encoded as defined in Table 1, where \"A\" denotes bit"] + #[doc = " * fields specifying an assurance level, \"R\" reserved bit fields, and \"C\" bit"] + #[doc = " * fields specifying the confidence."] + #[doc = " *"] + #[doc = " * Table 1: Bitwise encoding of subject assurance"] + #[doc = " *"] + #[doc = " * | Bit number | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |"] + #[doc = " * | -------------- | --- | --- | --- | --- | --- | --- | --- | --- |"] + #[doc = " * | Interpretation | A | A | A | R | R | R | C | C |"] + #[doc = " *"] + #[doc = " * In Table 1, bit number 0 denotes the least significant bit. Bit 7"] + #[doc = " * to bit 5 denote the device's assurance levels, bit 4 to bit 2 are reserved"] + #[doc = " * for future use, and bit 1 and bit 0 denote the confidence."] + #[doc = " *"] + #[doc = " * The specification of these assurance levels as well as the"] + #[doc = " * encoding of the confidence levels is outside the scope of the present"] + #[doc = " * standard. It can be assumed that a higher assurance value indicates that"] + #[doc = " * the holder is more trusted than the holder of a certificate with lower"] + #[doc = " * assurance value and the same confidence value."] + #[doc = " *"] + #[doc = " * @note This field was originally specified in ETSI TS 103 097 and"] + #[doc = " * future uses of this field are anticipated to be consistent with future"] + #[doc = " * versions of that standard."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SubjectAssurance(pub FixedOctetString<1usize>); + #[doc = "*"] + #[doc = " * @brief This enumerated value indicates supported symmetric algorithms. The"] + #[doc = " * algorithm identifier identifies both the algorithm itself and a specific"] + #[doc = " * mode of operation. The symmetric algorithms supported in this version of"] + #[doc = " * this standard are AES-128 and SM4. The only mode of operation supported is"] + #[doc = " * Counter Mode Encryption With Cipher Block Chaining Message Authentication"] + #[doc = " * Code (CCM). Full details are given in 5.3.8."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash, Copy)] + #[rasn(enumerated)] + #[non_exhaustive] + pub enum SymmAlgorithm { + aes128Ccm = 0, + #[rasn(extension_addition)] + sm4Ccm = 1, + } + #[doc = "*"] + #[doc = " * @brief This structure provides the key bytes for use with an identified "] + #[doc = " * symmetric algorithm. The supported symmetric algorithms are AES-128 and "] + #[doc = " * SM4 in CCM mode as specified in 5.3.8."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum SymmetricEncryptionKey { + #[rasn(size("16"))] + aes128Ccm(OctetString), + #[rasn(extension_addition, size("16"))] + sm4Ccm(OctetString), + } + #[doc = "*"] + #[doc = " * @brief This structure contains an estimate of 3D location. The details of"] + #[doc = " * the structure are given in the definitions of the individual fields below."] + #[doc = " *"] + #[doc = " * @note The units used in this data structure are consistent with the "] + #[doc = " * location data structures used in \tSAE J2735 [B26], though the encoding is"] + #[doc = " * incompatible."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct ThreeDLocation { + pub latitude: Latitude, + pub longitude: Longitude, + pub elevation: Elevation, + } + impl ThreeDLocation { + pub fn new(latitude: Latitude, longitude: Longitude, elevation: Elevation) -> Self { + Self { + latitude, + longitude, + elevation, + } + } + } + #[doc = "***************************************************************************"] + #[doc = " Time Structures "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This type gives the number of (TAI) seconds since 00:00:00 UTC, 1"] + #[doc = " * January, 2004."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct Time32(pub Uint32); + #[doc = "*"] + #[doc = " * @brief This data structure is a 64-bit integer giving an estimate of the "] + #[doc = " * number of (TAI) microseconds since 00:00:00 UTC, 1 January, 2004."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct Time64(pub Uint64); + #[doc = "*"] + #[doc = " * @brief This structure is used to define validity regions for use in"] + #[doc = " * certificates. The latitude and longitude fields contain the latitude and"] + #[doc = " * longitude as defined above."] + #[doc = " *"] + #[doc = " * @note This data structure is consistent with the location encoding"] + #[doc = " * used in SAE J2735, except that values 900 000 001 for latitude (used to"] + #[doc = " * indicate that the latitude was not available) and 1 800 000 001 for"] + #[doc = " * longitude (used to indicate that the longitude was not available) are not"] + #[doc = " * valid."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct TwoDLocation { + pub latitude: Latitude, + pub longitude: Longitude, + } + impl TwoDLocation { + pub fn new(latitude: Latitude, longitude: Longitude) -> Self { + Self { + latitude, + longitude, + } + } + } + #[doc = "*"] + #[doc = " * @brief This atomic type is used in the definition of other data structures."] + #[doc = " * It is for non-negative integers up to 65,535, i.e., (hex)ff ff."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("0..=65535"))] + pub struct Uint16(pub u16); + #[doc = "***************************************************************************"] + #[doc = " Integer Types "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This atomic type is used in the definition of other data structures."] + #[doc = " * It is for non-negative integers up to 7, i.e., (hex)07."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("0..=7"))] + pub struct Uint3(pub u8); + #[doc = "*"] + #[doc = " * @brief This atomic type is used in the definition of other data structures."] + #[doc = " * It is for non-negative integers up to 4,294,967,295, i.e.,"] + #[doc = " * (hex)ff ff ff ff."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("0..=4294967295"))] + pub struct Uint32(pub u32); + #[doc = "*"] + #[doc = " * @brief This atomic type is used in the definition of other data structures."] + #[doc = " * It is for non-negative integers up to 18,446,744,073,709,551,615, i.e.,"] + #[doc = " * (hex)ff ff ff ff ff ff ff ff."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("0..=18446744073709551615"))] + pub struct Uint64(pub u64); + #[doc = "*"] + #[doc = " * @brief This atomic type is used in the definition of other data structures."] + #[doc = " * It is for non-negative integers up to 255, i.e., (hex)ff."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("0..=255"))] + pub struct Uint8(pub u8); + #[doc = "*"] + #[doc = " * @brief This type contains the integer representation of the country or "] + #[doc = " * area identifier as defined by the United Nations Statistics Division in "] + #[doc = " * October 2013 (see normative references in Clause 0)."] + #[doc = " * A conformant implementation that implements IdentifiedRegion shall "] + #[doc = " * recognize (in the sense of “be able to determine whether a two dimensional "] + #[doc = " * location lies inside or outside the borders identified by”) at least one "] + #[doc = " * value of UnCountryId. The Protocol Implementation Conformance Statement "] + #[doc = " * (PICS) provided in Annex A allows an implementation to state which "] + #[doc = " * UnCountryId values it recognizes."] + #[doc = " * Since 2013 and before the publication of this version of this standard, "] + #[doc = " * three changes have been made to the country code list, to define the "] + #[doc = " * region \"sub-Saharan Africa\" and remove the \"developed regions\", and "] + #[doc = " * \"developing regions\". A conformant implementation may recognize these "] + #[doc = " * region identifiers in the sense defined in the previous paragraph."] + #[doc = " * If a verifying implementation is required to check that relevant "] + #[doc = " * geographic information in a signed SPDU is consistent with a certificate "] + #[doc = " * containing one or more instances of this type, then the SDS is permitted "] + #[doc = " * to indicate that the signed SPDU is valid even if some instances of this "] + #[doc = " * type are unrecognized in the sense defined above, so long as the "] + #[doc = " * recognized instances of this type completely contain the relevant "] + #[doc = " * geographic information. Informally, if the recognized values in the "] + #[doc = " * certificate allow the SDS to determine that the SPDU is valid, then it "] + #[doc = " * can make that determination even if there are also unrecognized values in "] + #[doc = " * the certificate. This field is therefore not a \"critical information "] + #[doc = " * field\" as defined in 5.2.6, because unrecognized values are permitted so "] + #[doc = " * long as the validity of the SPDU can be established with the recognized "] + #[doc = " * values. However, as discussed in 5.2.6, the presence of an unrecognized "] + #[doc = " * value in a certificate can make it impossible to determine whether the "] + #[doc = " * certificate and the SPDU are valid."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct UnCountryId(pub Uint16); + #[doc = "*"] + #[doc = " * @brief The value 900,000,001 indicates that the latitude was not"] + #[doc = " * available to the sender."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("900000001"))] + pub struct UnknownLatitude(pub NinetyDegreeInt); + #[doc = "*"] + #[doc = " * @brief The value 1,800,000,001 indicates that the longitude was not"] + #[doc = " * available to the sender."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("1800000001"))] + pub struct UnknownLongitude(pub OneEightyDegreeInt); + #[doc = "*"] + #[doc = " * @brief This type gives the validity period of a certificate. The start of "] + #[doc = " * the validity period is given by start and the end is given by "] + #[doc = " * start + duration."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct ValidityPeriod { + pub start: Time32, + pub duration: Duration, + } + impl ValidityPeriod { + pub fn new(start: Time32, duration: Duration) -> Self { + Self { start, duration } + } + } +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_crl { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::ieee1609_dot2::Ieee1609Dot2Data; + use super::ieee1609_dot2_base_types::{Opaque, Psid}; + use super::ieee1609_dot2_crl_base_types::CrlContents; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This is the PSID for the CRL application."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("256"))] + pub struct CrlPsid(pub Psid); + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to contain a signed CRL. A valid "] + #[doc = " * signed CRL meets the validity criteria of 7.4."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SecuredCrl(pub Ieee1609Dot2Data); +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_crl_base_types { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + pub use super::ieee1609_dot2_base_types::{ + CrlSeries, Duration, GeographicRegion, HashedId10, HashedId8, IValue, LaId, LinkageSeed, + Opaque, Psid, SequenceOfLinkageSeed, Signature, Time32, Uint16, Uint3, Uint32, Uint8, + ValidityPeriod, + }; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief The fields in this structure have the following meaning:"] + #[doc = " *"] + #[doc = " * @param version: is the version number of the CRL. For this version of this"] + #[doc = " * standard it is 1."] + #[doc = " *"] + #[doc = " * @param crlSeries: represents the CRL series to which this CRL belongs. This"] + #[doc = " * is used to determine whether the revocation information in a CRL is relevant"] + #[doc = " * to a particular certificate as specified in 5.1.3.2."] + #[doc = " *"] + #[doc = " * @param crlCraca: contains the low-order eight octets of the hash of the"] + #[doc = " * certificate of the Certificate Revocation Authorization CA (CRACA) that"] + #[doc = " * ultimately authorized the issuance of this CRL. This is used to determine"] + #[doc = " * whether the revocation information in a CRL is relevant to a particular"] + #[doc = " * certificate as specified in 5.1.3.2. In a valid signed CRL as specified in"] + #[doc = " * 7.4 the crlCraca is consistent with the associatedCraca field in the"] + #[doc = " * Service Specific Permissions as defined in 7.4.3.3. The HashedId8 is"] + #[doc = " * calculated with the whole-certificate hash algorithm, determined as"] + #[doc = " * described in 6.4.3, applied to the COER-encoded certificate, canonicalized "] + #[doc = " * as defined in the definition of Certificate."] + #[doc = " *"] + #[doc = " * @param issueDate: specifies the time when the CRL was issued."] + #[doc = " *"] + #[doc = " * @param nextCrl: contains the time when the next CRL with the same crlSeries"] + #[doc = " * and cracaId is expected to be issued. The CRL is invalid unless nextCrl is"] + #[doc = " * strictly after issueDate. This field is used to set the expected update time"] + #[doc = " * for revocation information associated with the (crlCraca, crlSeries) pair as"] + #[doc = " * specified in 5.1.3.6."] + #[doc = " *"] + #[doc = " * @param priorityInfo: contains information that assists devices with limited"] + #[doc = " * storage space in determining which revocation information to retain and"] + #[doc = " * which to discard."] + #[doc = " *"] + #[doc = " * @param\ttypeSpecific: contains the CRL body."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct CrlContents { + #[rasn(value("1"))] + pub version: Uint8, + #[rasn(identifier = "crlSeries")] + pub crl_series: CrlSeries, + #[rasn(identifier = "crlCraca")] + pub crl_craca: HashedId8, + #[rasn(identifier = "issueDate")] + pub issue_date: Time32, + #[rasn(identifier = "nextCrl")] + pub next_crl: Time32, + #[rasn(identifier = "priorityInfo")] + pub priority_info: CrlPriorityInfo, + #[rasn(identifier = "typeSpecific")] + pub type_specific: TypeSpecificCrlContents, + } + impl CrlContents { + pub fn new( + version: Uint8, + crl_series: CrlSeries, + crl_craca: HashedId8, + issue_date: Time32, + next_crl: Time32, + priority_info: CrlPriorityInfo, + type_specific: TypeSpecificCrlContents, + ) -> Self { + Self { + version, + crl_series, + crl_craca, + issue_date, + next_crl, + priority_info, + type_specific, + } + } + } + #[doc = "*"] + #[doc = " * @brief This data structure contains information that assists devices with"] + #[doc = " * limited storage space in determining which revocation information to retain"] + #[doc = " * and which to discard."] + #[doc = " *"] + #[doc = " * @param priority: indicates the priority of the revocation information"] + #[doc = " * relative to other CRLs issued for certificates with the same cracaId and"] + #[doc = " * crlSeries values. A higher value for this field indicates higher importance"] + #[doc = " * of this revocation information."] + #[doc = " *"] + #[doc = " * @note This mechanism is for future use; details are not specified in this"] + #[doc = " * version of the standard."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct CrlPriorityInfo { + pub priority: Option, + } + impl CrlPriorityInfo { + pub fn new(priority: Option) -> Self { + Self { priority } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains an identifier for the algorithms specified "] + #[doc = " * in 5.1.3.4."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash, Copy)] + #[rasn(enumerated)] + #[non_exhaustive] + pub enum ExpansionAlgorithmIdentifier { + #[rasn(identifier = "sha256ForI-aesForJ")] + sha256ForI_aesForJ = 0, + #[rasn(identifier = "sm3ForI-sm4ForJ")] + sm3ForI_sm4ForJ = 1, + } + #[doc = "*"] + #[doc = " * @brief In this structure:"] + #[doc = " *"] + #[doc = " * @param iMax: indicates that for these certificates, revocation information "] + #[doc = " * need no longer be calculated once iCert > iMax as the holders are known "] + #[doc = " * to have no more valid certs for that (crlCraca, crlSeries) at that point."] + #[doc = " *"] + #[doc = " * @param la1Id: is the value LinkageAuthorityIdentifier1 used in the "] + #[doc = " * algorithm given in 5.1.3.4. This value applies to all linkage-based "] + #[doc = " * revocation information included within contents."] + #[doc = " *"] + #[doc = " * @param linkageSeed1: is the value LinkageSeed1 used in the algorithm given "] + #[doc = " * in 5.1.3.4."] + #[doc = " *"] + #[doc = " * @param la2Id: is the value LinkageAuthorityIdentifier2 used in the "] + #[doc = " * algorithm given in 5.1.3.4. This value applies to all linkage-based "] + #[doc = " * revocation information included within contents."] + #[doc = " *"] + #[doc = " * @param linkageSeed2: is the value LinkageSeed2 used in the algorithm given "] + #[doc = " * in 5.1.3.4."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct GroupCrlEntry { + #[rasn(identifier = "iMax")] + pub i_max: Uint16, + #[rasn(identifier = "la1Id")] + pub la1_id: LaId, + #[rasn(identifier = "linkageSeed1")] + pub linkage_seed1: LinkageSeed, + #[rasn(identifier = "la2Id")] + pub la2_id: LaId, + #[rasn(identifier = "linkageSeed2")] + pub linkage_seed2: LinkageSeed, + } + impl GroupCrlEntry { + pub fn new( + i_max: Uint16, + la1_id: LaId, + linkage_seed1: LinkageSeed, + la2_id: LaId, + linkage_seed2: LinkageSeed, + ) -> Self { + Self { + i_max, + la1_id, + linkage_seed1, + la2_id, + linkage_seed2, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains the linkage seed for group revocation with "] + #[doc = " * a single seed. The seed is used as specified in the algorithms in 5.1.3.4."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct GroupSingleSeedCrlEntry { + #[rasn(identifier = "iMax")] + pub i_max: Uint16, + #[rasn(identifier = "laId")] + pub la_id: LaId, + #[rasn(identifier = "linkageSeed")] + pub linkage_seed: LinkageSeed, + } + impl GroupSingleSeedCrlEntry { + pub fn new(i_max: Uint16, la_id: LaId, linkage_seed: LinkageSeed) -> Self { + Self { + i_max, + la_id, + linkage_seed, + } + } + } + #[doc = "*"] + #[doc = " * @brief In this structure:"] + #[doc = " *"] + #[doc = " * @param\tid: is the HashedId10 identifying the revoked certificate. The "] + #[doc = " * HashedId10 is calculated with the whole-certificate hash algorithm, "] + #[doc = " * determined as described in 6.4.3, applied to the COER-encoded certificate,"] + #[doc = " * canonicalized as defined in the definition of Certificate."] + #[doc = " *"] + #[doc = " * @param expiry: is the value computed from the validity period's start and"] + #[doc = " * duration values in that certificate."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct HashBasedRevocationInfo { + pub id: HashedId10, + pub expiry: Time32, + } + impl HashBasedRevocationInfo { + pub fn new(id: HashedId10, expiry: Time32) -> Self { + Self { id, expiry } + } + } + #[doc = "*"] + #[doc = " * @brief In this structure:"] + #[doc = " *"] + #[doc = " * @param iMax indicates that for the entries in contents, revocation "] + #[doc = " * information need no longer be calculated once iCert > iMax as the holder "] + #[doc = " * is known to have no more valid certs at that point. iMax is not directly "] + #[doc = " * used in the calculation of the linkage values, it is used to determine "] + #[doc = " * when revocation information can safely be deleted."] + #[doc = " *"] + #[doc = " * @param contents contains individual linkage data for certificates that are "] + #[doc = " * revoked using two seeds, per the algorithm given in per the mechanisms "] + #[doc = " * given in 5.1.3.4 and with seedEvolutionFunctionIdentifier and "] + #[doc = " * linkageValueGenerationFunctionIdentifier obtained as specified in 7.3.3."] + #[doc = " *"] + #[doc = " * @param singleSeed contains individual linkage data for certificates that "] + #[doc = " * are revoked using a single seed, per the algorithm given in per the "] + #[doc = " * mechanisms given in 5.1.3.4 and with seedEvolutionFunctionIdentifier and "] + #[doc = " * linkageValueGenerationFunctionIdentifier obtained as specified in 7.3.3."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct IMaxGroup { + #[rasn(identifier = "iMax")] + pub i_max: Uint16, + pub contents: SequenceOfIndividualRevocation, + #[rasn(extension_addition, identifier = "singleSeed")] + pub single_seed: Option, + } + impl IMaxGroup { + pub fn new( + i_max: Uint16, + contents: SequenceOfIndividualRevocation, + single_seed: Option, + ) -> Self { + Self { + i_max, + contents, + single_seed, + } + } + } + #[doc = "*"] + #[doc = " * @brief In this structure:"] + #[doc = " *"] + #[doc = " * @param linkageSeed1 is the value LinkageSeed1 used in the algorithm given "] + #[doc = " * in 5.1.3.4."] + #[doc = " *"] + #[doc = " * @param linkageSeed2 is the value LinkageSeed2 used in the algorithm given "] + #[doc = " * in 5.1.3.4."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct IndividualRevocation { + #[rasn(identifier = "linkageSeed1")] + pub linkage_seed1: LinkageSeed, + #[rasn(identifier = "linkageSeed2")] + pub linkage_seed2: LinkageSeed, + } + impl IndividualRevocation { + pub fn new(linkage_seed1: LinkageSeed, linkage_seed2: LinkageSeed) -> Self { + Self { + linkage_seed1, + linkage_seed2, + } + } + } + #[doc = "*"] + #[doc = " * @brief In this structure:"] + #[doc = " *"] + #[doc = " * @param\tjMax: is the value jMax used in the algorithm given in 5.1.3.4. This"] + #[doc = " * value applies to all linkage-based revocation information included within"] + #[doc = " * contents."] + #[doc = " *"] + #[doc = " * @param contents: contains individual linkage data."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct JMaxGroup { + pub jmax: Uint8, + pub contents: SequenceOfLAGroup, + } + impl JMaxGroup { + pub fn new(jmax: Uint8, contents: SequenceOfLAGroup) -> Self { + Self { jmax, contents } + } + } + #[doc = "*"] + #[doc = " * @brief In this structure:"] + #[doc = " *"] + #[doc = " * @param la1Id: is the value LinkageAuthorityIdentifier1 used in the"] + #[doc = " * algorithm given in 5.1.3.4. This value applies to all linkage-based"] + #[doc = " * revocation information included within contents."] + #[doc = " *"] + #[doc = " * @param la2Id: is the value LinkageAuthorityIdentifier2 used in the"] + #[doc = " * algorithm given in 5.1.3.4. This value applies to all linkage-based"] + #[doc = " * revocation information included within contents."] + #[doc = " *"] + #[doc = " * @param contents: contains individual linkage data."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct LAGroup { + #[rasn(identifier = "la1Id")] + pub la1_id: LaId, + #[rasn(identifier = "la2Id")] + pub la2_id: LaId, + pub contents: SequenceOfIMaxGroup, + } + impl LAGroup { + pub fn new(la1_id: LaId, la2_id: LaId, contents: SequenceOfIMaxGroup) -> Self { + Self { + la1_id, + la2_id, + contents, + } + } + } + #[doc = "*"] + #[doc = " * @brief This is the identifier for the linkage value generation function. "] + #[doc = " * See 5.1.3 for details of use."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash, Copy)] + #[rasn(delegate)] + pub struct LvGenerationFunctionIdentifier(pub ()); + #[doc = "*"] + #[doc = " * @brief This is the identifier for the seed evolution function. See 5.1.3 "] + #[doc = " * for details of use."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash, Copy)] + #[rasn(delegate)] + pub struct SeedEvolutionFunctionIdentifier(pub ()); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfGroupCrlEntry(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfGroupSingleSeedCrlEntry(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfHashBasedRevocationInfo(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfIMaxGroup(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfIndividualRevocation(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfJMaxGroup(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfLAGroup(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This data structure represents information about a revoked"] + #[doc = " * certificate."] + #[doc = " *"] + #[doc = " * @param crlSerial: is a counter that increments by 1 every time a new full"] + #[doc = " * or delta CRL is issued for the indicated crlCraca and crlSeries values."] + #[doc = " *"] + #[doc = " * @param entries: contains the individual revocation information items."] + #[doc = " *"] + #[doc = " * @note To indicate that a hash-based CRL contains no individual revocation "] + #[doc = " * information items, the recommended approach is for the SEQUENCE OF in the "] + #[doc = " * SequenceOfHashBasedRevocationInfo in this field to indicate zero entries."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct ToBeSignedHashIdCrl { + #[rasn(identifier = "crlSerial")] + pub crl_serial: Uint32, + pub entries: SequenceOfHashBasedRevocationInfo, + } + impl ToBeSignedHashIdCrl { + pub fn new(crl_serial: Uint32, entries: SequenceOfHashBasedRevocationInfo) -> Self { + Self { + crl_serial, + entries, + } + } + } + #[doc = "*"] + #[doc = " * @brief In this structure:"] + #[doc = " *"] + #[doc = " * @param\tiRev: is the value iRev used in the algorithm given in 5.1.3.4. This"] + #[doc = " * value applies to all linkage-based revocation information included within"] + #[doc = " * either indvidual or groups."] + #[doc = " *"] + #[doc = " * @param\tindexWithinI: is a counter that is set to 0 for the first CRL issued"] + #[doc = " * for the indicated combination of crlCraca, crlSeries, and iRev, and"] + #[doc = " * increments by 1 every time a new full or delta CRL is issued for the"] + #[doc = " * indicated crlCraca and crlSeries values without changing iRev."] + #[doc = " *"] + #[doc = " * @param individual: contains individual linkage data."] + #[doc = " *"] + #[doc = " * @note To indicate that a linkage ID-based CRL contains no individual"] + #[doc = " * linkage data, the recommended approach is for the SEQUENCE OF in the"] + #[doc = " * SequenceOfJMaxGroup in this field to indicate zero entries."] + #[doc = " *"] + #[doc = " * @param groups: contains group linkage data."] + #[doc = " *"] + #[doc = " * @note To indicate that a linkage ID-based CRL contains no group linkage"] + #[doc = " * data, the recommended approach is for the SEQUENCE OF in the"] + #[doc = " * SequenceOfGroupCrlEntry in this field to indicate zero entries."] + #[doc = " *"] + #[doc = " * @param groupsSingleSeed: contains group linkage data generated with a single "] + #[doc = " * seed."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct ToBeSignedLinkageValueCrl { + #[rasn(identifier = "iRev")] + pub i_rev: IValue, + #[rasn(identifier = "indexWithinI")] + pub index_within_i: Uint8, + pub individual: Option, + pub groups: Option, + #[rasn(extension_addition, identifier = "groupsSingleSeed")] + pub groups_single_seed: Option, + } + impl ToBeSignedLinkageValueCrl { + pub fn new( + i_rev: IValue, + index_within_i: Uint8, + individual: Option, + groups: Option, + groups_single_seed: Option, + ) -> Self { + Self { + i_rev, + index_within_i, + individual, + groups, + groups_single_seed, + } + } + } + #[doc = "*"] + #[doc = " * @brief In this structure:"] + #[doc = " * "] + #[doc = " * @param iRev is the value iRev used in the algorithm given in 5.1.3.4. This "] + #[doc = " * value applies to all linkage-based revocation information included within "] + #[doc = " * either indvidual or groups."] + #[doc = " * "] + #[doc = " * @param indexWithinI is a counter that is set to 0 for the first CRL issued "] + #[doc = " * for the indicated combination of crlCraca, crlSeries, and iRev, and increments by 1 every time a new full or delta CRL is issued for the indicated crlCraca and crlSeries values without changing iRev."] + #[doc = " * "] + #[doc = " * @param seedEvolution contains an identifier for the seed evolution "] + #[doc = " * function, used as specified in 5.1.3.4."] + #[doc = " * "] + #[doc = " * @param lvGeneration contains an identifier for the linkage value "] + #[doc = " * generation function, used as specified in 5.1.3.4."] + #[doc = " * "] + #[doc = " * @param individual contains individual linkage data."] + #[doc = " * "] + #[doc = " * @param groups contains group linkage data for linkage value generation "] + #[doc = " * with two seeds."] + #[doc = " * "] + #[doc = " * @param groupsSingleSeed contains group linkage data for linkage value "] + #[doc = " * generation with one seed."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct ToBeSignedLinkageValueCrlWithAlgIdentifier { + #[rasn(identifier = "iRev")] + pub i_rev: IValue, + #[rasn(identifier = "indexWithinI")] + pub index_within_i: Uint8, + #[rasn(identifier = "seedEvolution")] + pub seed_evolution: SeedEvolutionFunctionIdentifier, + #[rasn(identifier = "lvGeneration")] + pub lv_generation: LvGenerationFunctionIdentifier, + pub individual: Option, + pub groups: Option, + #[rasn(identifier = "groupsSingleSeed")] + pub groups_single_seed: Option, + } + impl ToBeSignedLinkageValueCrlWithAlgIdentifier { + pub fn new( + i_rev: IValue, + index_within_i: Uint8, + seed_evolution: SeedEvolutionFunctionIdentifier, + lv_generation: LvGenerationFunctionIdentifier, + individual: Option, + groups: Option, + groups_single_seed: Option, + ) -> Self { + Self { + i_rev, + index_within_i, + seed_evolution, + lv_generation, + individual, + groups, + groups_single_seed, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains type-specific CRL contents."] + #[doc = " *"] + #[doc = " * @param fullHashCrl: contains a full hash-based CRL, i.e., a listing of the"] + #[doc = " * hashes of all certificates that:"] + #[doc = " * - contain the indicated cracaId and crlSeries values, and"] + #[doc = " * - are revoked by hash, and"] + #[doc = " * - have been revoked, and"] + #[doc = " * - have not expired."] + #[doc = " *"] + #[doc = " * @param deltaHashCrl: contains a delta hash-based CRL, i.e., a listing of"] + #[doc = " * the hashes of all certificates that:"] + #[doc = " * - contain the indicated cracaId and crlSeries values, and"] + #[doc = " * - are revoked by hash, and"] + #[doc = " * - have been revoked since the previous CRL that contained the indicated"] + #[doc = " * cracaId and crlSeries values."] + #[doc = " *"] + #[doc = " * @param fullLinkedCrl and fullLinkedCrlWithAlg: contain a full linkage"] + #[doc = " * ID-based CRL, i.e., a listing of the individual and/or group linkage data"] + #[doc = " * for all certificates that:"] + #[doc = " * - contain the indicated cracaId and crlSeries values, and"] + #[doc = " * - are revoked by linkage value, and"] + #[doc = " * - have been revoked, and"] + #[doc = " * - have not expired."] + #[doc = " * The difference between fullLinkedCrl and fullLinkedCrlWithAlg is in how"] + #[doc = " * the cryptographic algorithms to be used in the seed evolution function and"] + #[doc = " * linkage value generation function of 5.1.3.4 are communicated to the"] + #[doc = " * receiver of the CRL. See below in this subclause for details."] + #[doc = " *"] + #[doc = " * @param deltaLinkedCrl and deltaLinkedCrlWithAlg: contain a delta linkage"] + #[doc = " * ID-based CRL, i.e., a listing of the individual and/or group linkage data"] + #[doc = " * for all certificates that:"] + #[doc = " * - contain the specified cracaId and crlSeries values, and"] + #[doc = " * -\tare revoked by linkage data, and"] + #[doc = " * -\thave been revoked since the previous CRL that contained the indicated"] + #[doc = " * cracaId and crlSeries values."] + #[doc = " * The difference between deltaLinkedCrl and deltaLinkedCrlWithAlg is in how"] + #[doc = " * the cryptographic algorithms to be used in the seed evolution function"] + #[doc = " * and linkage value generation function of 5.1.3.4 are communicated to the"] + #[doc = " * receiver of the CRL. See below in this subclause for details."] + #[doc = " *"] + #[doc = " * @note It is the intent of this standard that once a certificate is revoked,"] + #[doc = " * it remains revoked for the rest of its lifetime. CRL signers are expected "] + #[doc = " * to include a revoked certificate on all CRLs issued between the "] + #[doc = " * certificate's revocation and its expiry."] + #[doc = " *"] + #[doc = " * @note Seed evolution function and linkage value generation function"] + #[doc = " * identification. In order to derive linkage values per the mechanisms given"] + #[doc = " * in 5.1.3.4, a receiver needs to know the seed evolution function and the"] + #[doc = " * linkage value generation function."] + #[doc = " *"] + #[doc = " * If the contents of this structure is a"] + #[doc = " * ToBeSignedLinkageValueCrlWithAlgIdentifier, then the seed evolution function"] + #[doc = " * and linkage value generation function are given explicitly as specified in"] + #[doc = " * the specification of ToBeSignedLinkageValueCrlWithAlgIdentifier."] + #[doc = " *"] + #[doc = " * If the contents of this structure is a ToBeSignedLinkageValueCrl, then the"] + #[doc = " * seed evolution function and linkage value generation function are obtained"] + #[doc = " * based on the crlCraca field in the CrlContents:"] + #[doc = " * - If crlCraca was obtained with SHA-256 or SHA-384, then"] + #[doc = " * seedEvolutionFunctionIdentifier is seedEvoFn1-sha256 and"] + #[doc = " * linkageValueGenerationFunctionIdentifier is lvGenFn1-aes128."] + #[doc = " * - If crlCraca was obtained with SM3, then seedEvolutionFunctionIdentifier"] + #[doc = " * is seedEvoFn1-sm3 and linkageValueGenerationFunctionIdentifier is"] + #[doc = " * lvGenFn1-sm4."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum TypeSpecificCrlContents { + fullHashCrl(ToBeSignedHashIdCrl), + deltaHashCrl(ToBeSignedHashIdCrl), + fullLinkedCrl(ToBeSignedLinkageValueCrl), + deltaLinkedCrl(ToBeSignedLinkageValueCrl), + #[rasn(extension_addition)] + fullLinkedCrlWithAlg(ToBeSignedLinkageValueCrlWithAlgIdentifier), + #[rasn(extension_addition)] + deltaLinkedCrlWithAlg(ToBeSignedLinkageValueCrlWithAlgIdentifier), + } +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_dot1_aca_ee_interface { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::ieee1609_dot2::Certificate; + use super::ieee1609_dot2_base_types::{Time32, Uint8}; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This structure contains a certificate and associated data as"] + #[doc = " * generated by the ACA for the EE that will be the holder of that"] + #[doc = " * certificate. An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @note In the case where the butterfly expansion function is used"] + #[doc = " * to set certEncKey in RaAcaCertRequest, the value j is not communicated to"] + #[doc = " * the ACA. However, the EE that receives the certificate response can only"] + #[doc = " * decrypt the response if it knows j. The RA is therefore anticipated to"] + #[doc = " * store j so that it can be associated with the appropriate certificate"] + #[doc = " * response. The RA encodes j in the filename."] + #[doc = " *"] + #[doc = " * @param version: contains the current version of the structure."] + #[doc = " *"] + #[doc = " * @param generationTime: contains the generation time of AcaEeCertResponse."] + #[doc = " *"] + #[doc = " * @param certificate: contains an authorization certificate generated by the"] + #[doc = " * ACA. It is of the type indicated by the type field in the corresponding"] + #[doc = " * request (if the requester requested an incorrect type, the response would"] + #[doc = " * be an error not an instance of this structure)."] + #[doc = " *"] + #[doc = " * @param privateKeyInfo: shall be:"] + #[doc = " * - Present and contain the private key randomization value, if the field"] + #[doc = " * certificate.type is explicit and the butterfly key mechanism was used to"] + #[doc = " * generate the certificate. This is used by the EE in deriving the butterfly"] + #[doc = " * private key for explicit certificates as specified in 9.3."] + #[doc = " * - Present and contain the private key reconstruction value, if the field"] + #[doc = " * certificate.type is implicit. This is used by the EE as specified in 5.3.2"] + #[doc = " * of IEEE Std 1609.2a-2017 (also 9.3 if the butterfly key mechanism is used)."] + #[doc = " * - Absent otherwise."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct AcaEeCertResponse { + #[rasn(value("2"))] + pub version: Uint8, + #[rasn(identifier = "generationTime")] + pub generation_time: Time32, + pub certificate: Certificate, + #[rasn(size("32"), identifier = "privateKeyInfo")] + pub private_key_info: Option, + } + impl AcaEeCertResponse { + pub fn new( + version: Uint8, + generation_time: Time32, + certificate: Certificate, + private_key_info: Option, + ) -> Self { + Self { + version, + generation_time, + certificate, + private_key_info, + } + } + } + #[doc = "*"] + #[doc = " * @brief This is the parent structure for all structures exchanged between"] + #[doc = " * the ACA and the EE. The ACA - EE interface is a logical interface rather"] + #[doc = " * than a direct communications interface in that there is no direct message"] + #[doc = " * flow between the ACA and the EE: Messages from the ACA are stored"] + #[doc = " * by the RA and subsequently forwarded to the EE. The PDUs are identified as"] + #[doc = " * ACA-EE PDUs even though the RA acts as a forwarder for them because those"] + #[doc = " * PDUs are created by the ACA and encrypted for the EE, and not modified and"] + #[doc = " * frequently not read by the RA. An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @param acaEeCertResponse: contains the ACA's response to"] + #[doc = " * RaAcaCertRequestSPDU, which is meant for the EE and sent via the RA."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum AcaEeInterfacePdu { + acaEeCertResponse(AcaEeCertResponse), + } +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_dot1_aca_la_interface { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This structure is not used by EEs, so it is defined as NULL for"] + #[doc = " * purposes of this document."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash, Copy)] + #[rasn(delegate)] + pub struct AcaLaInterfacePdu(pub ()); +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_dot1_aca_ma_interface { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This structure is not used by EEs, so it is defined as NULL for"] + #[doc = " * purposes of this document."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash, Copy)] + #[rasn(delegate)] + pub struct AcaMaInterfacePdu(pub ()); +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_dot1_aca_ra_interface { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::ieee1609_dot2::{CertificateType, ToBeSignedCertificate}; + use super::ieee1609_dot2_base_types::{ + HashAlgorithm, HashedId8, LaId, PublicEncryptionKey, Time32, Uint8, + }; + use super::ieee1609_dot2_dot1_protocol::{ + AcaEeCertResponseCubkSpdu, AcaEeCertResponsePlainSpdu, AcaEeCertResponsePrivateSpdu, + Ieee1609Dot2DataSymmEncryptedSingleRecipient, + }; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This structure contains a certificate response by the ACA,"] + #[doc = " * encapsulated for consumption by the EE, as well as associated data for"] + #[doc = " * consumption by the RA. The response is of form AcaEeCertResponsePlainSpdu,"] + #[doc = " * AcaEeCertResponsePrivateSpdu, or AcaEeCertResponseCubkSpdu, and is"] + #[doc = " * generated in response to a successful RaAcaCertRequestSpdu. In this"] + #[doc = " * structure:"] + #[doc = " *"] + #[doc = " * @param version: contains the current version of the structure."] + #[doc = " *"] + #[doc = " * @param generationTime: contains the generation time of AcaRaCertResponse."] + #[doc = " *"] + #[doc = " * @param requestHash: contains the hash of the corresponding"] + #[doc = " * RaAcaCertRequestSPDU."] + #[doc = " *"] + #[doc = " * @param acaResponse: contains the certificate for the EE in a suitable form"] + #[doc = " * as determined from the corresponding RaAcaCertRequestSPDU."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct AcaRaCertResponse { + #[rasn(value("2"))] + pub version: Uint8, + #[rasn(identifier = "generationTime")] + pub generation_time: Time32, + #[rasn(identifier = "requestHash")] + pub request_hash: HashedId8, + #[rasn(identifier = "acaResponse")] + pub aca_response: AcaResponse, + } + impl AcaRaCertResponse { + pub fn new( + version: Uint8, + generation_time: Time32, + request_hash: HashedId8, + aca_response: AcaResponse, + ) -> Self { + Self { + version, + generation_time, + request_hash, + aca_response, + } + } + } + #[doc = "*"] + #[doc = " * @brief This is the parent structure for all structures exchanged between"] + #[doc = " * the ACA and the RA. An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @param raAcaCertRequest: contains the request for an authorization"] + #[doc = " * certificate from the RA to the ACA on behalf of the EE."] + #[doc = " *"] + #[doc = " * @param acaRaCertResponse: contains the ACA's response to RaAcaCertRequest."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum AcaRaInterfacePdu { + raAcaCertRequest(RaAcaCertRequest), + acaRaCertResponse(AcaRaCertResponse), + } + #[doc = "*"] + #[doc = " * @brief This structure contains the certificate for the EE in a suitable"] + #[doc = " * form as determined from the corresponding RaAcaCertRequestSPDU. In this"] + #[doc = " * structure:"] + #[doc = " *"] + #[doc = " * @param plain: contains the certificate for the EE in plain, that is, without"] + #[doc = " * encryption or signature. This choice is used only when the field"] + #[doc = " * certEncKey is absent and flags.cubk is not set in the corresponding"] + #[doc = " * RaAcaCertRequest."] + #[doc = " *"] + #[doc = " * @param private: contains the certificate for the EE in an encrypted then"] + #[doc = " * signed form to protect the EE's privacy from the RA. This choice is used"] + #[doc = " * only when the field certEncKey is present and flags.cubk is not set in the"] + #[doc = " * corresponding RaAcaCertRequest."] + #[doc = " *"] + #[doc = " * @param cubk: contains the certificate for the EE in an encrypted form. This"] + #[doc = " * choice is used only when the field certEncKey is absent and flags.cubk is"] + #[doc = " * set in the corresponding RaAcaCertRequest."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum AcaResponse { + plain(AcaEeCertResponsePlainSpdu), + private(AcaEeCertResponsePrivateSpdu), + cubk(AcaEeCertResponseCubkSpdu), + } + #[doc = " Inner type "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EncryptedIndividualPLVEncPlv { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl EncryptedIndividualPLVEncPlv { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains an individual prelinkage value encrypted by"] + #[doc = " * the LA for the ACA using the shared secret key. An overview of this"] + #[doc = " * structure is as follows:"] + #[doc = " *"] + #[doc = " * @note How the ACA obtains the shared symmetric key and how the RA"] + #[doc = " * associates the encPlv1 and encPlv2 with the correct certificate request are"] + #[doc = " * outside the scope of this document."] + #[doc = " *"] + #[doc = " * @param version: contains the current version of the structure."] + #[doc = " *"] + #[doc = " * @param laId: contains the ID of the LA that created the prelinkage value."] + #[doc = " * See Annex D for further discussion of LA IDs."] + #[doc = " *"] + #[doc = " * @param encPlv: contains the encrypted individual prelinkage value, that is,"] + #[doc = " * the ciphertext field decrypts to a PreLinkageValue. It contains a pointer"] + #[doc = " * (hash of the shared symmetric key) to the used shared secret encryption key."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EncryptedIndividualPLV { + #[rasn(value("2"))] + pub version: Uint8, + #[rasn(identifier = "laId")] + pub la_id: LaId, + #[rasn(identifier = "encPlv")] + pub enc_plv: EncryptedIndividualPLVEncPlv, + } + impl EncryptedIndividualPLV { + pub fn new(version: Uint8, la_id: LaId, enc_plv: EncryptedIndividualPLVEncPlv) -> Self { + Self { + version, + la_id, + enc_plv, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains parameters needed to generate a linkage"] + #[doc = " * value for a given (EE, i, j). An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @note See Annex D for further discussion of LAs."] + #[doc = " *"] + #[doc = " * @param encPlv1: contains the EncryptedIndividualPLV from one of the LAs."] + #[doc = " *"] + #[doc = " * @param encPlv2: contains the EncryptedIndividualPLV from the other LA."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct LinkageInfo { + #[rasn(identifier = "encPlv1")] + pub enc_plv1: EncryptedIndividualPLV, + #[rasn(identifier = "encPlv2")] + pub enc_plv2: EncryptedIndividualPLV, + } + impl LinkageInfo { + pub fn new(enc_plv1: EncryptedIndividualPLV, enc_plv2: EncryptedIndividualPLV) -> Self { + Self { enc_plv1, enc_plv2 } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains an individual prelinkage value. It is an"] + #[doc = " * octet string of length 9 octets."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct PreLinkageValue(pub FixedOctetString<9usize>); + #[doc = "*"] + #[doc = " * @brief This structure contains parameters needed to request an individual"] + #[doc = " * authorization certificate. An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @note:"] + #[doc = " * - In the case where the butterfly key mechanism is used to set"] + #[doc = " * certEncKey, the value of j is not communicated to the ACA. However, the EE"] + #[doc = " * that receives the certificate response can only decrypt the response if it"] + #[doc = " * knows j. The RA is therefore anticipated to store j so that it can be"] + #[doc = " * associated with the appropriate certificate response."] + #[doc = " *"] + #[doc = " * - The cracaId and crlSeries are set to the indicated values"] + #[doc = " * in the request. The ACA replaces these values with the appropriate values"] + #[doc = " * in the response."] + #[doc = " *"] + #[doc = " * - The ACA is not bound by the contents of the request and can"] + #[doc = " * issue certificates that are different from those requested, if so directed"] + #[doc = " * by policy."] + #[doc = " *"] + #[doc = " * @param version: contains the current version of the structure."] + #[doc = " *"] + #[doc = " * @param generationTime: contains the generation time of RaAcaCertRequest."] + #[doc = " *"] + #[doc = " * @param type: indicates whether the request is for an explicit or implicit"] + #[doc = " * certificate (see 4.1.1, 4.1.3.3.1)."] + #[doc = " *"] + #[doc = " * @param flags: contains the flags related to the use of the butterfly key"] + #[doc = " * mechanism, and provides the following instructions to the ACA as to how"] + #[doc = " * to generate the response:"] + #[doc = " * - If the flag butterflyExplicit is set, the request is valid only if"] + #[doc = " * the type field is set to explicit. In this case, the ACA uses the"] + #[doc = " * butterfly key derivation for explicit certificates as specified in 9.3."] + #[doc = " * The field tbsCert.verifyKeyIndicator.verificationKey is used by the ACA as"] + #[doc = " * the cocoon public key for signing. The field privateKeyInfo in the"] + #[doc = " * corresponding AcaEeCertResponse is used by the EE as the random integer to"] + #[doc = " * recover the butterfly private key for signing."] + #[doc = " * - If the flag cubk is set, the request is valid only if the certEncKey"] + #[doc = " * field is absent. In this case, the ACA uses the compact unified variation"] + #[doc = " * of the butterfly key mechanism as specified in 9.3. This means that the"] + #[doc = " * ACA generates an AcaEeCertResponseCubkSpdu instead of an"] + #[doc = " * AcaEeCertResponsePrivateSpdu, and the response is valid only if the ACA"] + #[doc = " * certificate has the flag cubk set."] + #[doc = " *"] + #[doc = " * @param linkageInfo: contains the encrypted prelinkage values needed to"] + #[doc = " * generate the linkage value for the certificate. If linkageInfo is present,"] + #[doc = " * the field tbsCert.id is of type LinkageData, where the iCert field is set"] + #[doc = " * to the actual i-period value and the linkage-value field is set to a dummy"] + #[doc = " * value to be replaced by the ACA with the actual linkage value. The"] + #[doc = " * encrypted prelinkage values are encrypted for the ACA by the LAs."] + #[doc = " *"] + #[doc = " * @param certEncKey: is used in combination with flags.cubk to indicate"] + #[doc = " * the type of response that is expected from the ACA. It is as follows:"] + #[doc = " * - Absent and flags.cubk is not set if the ACA's response doesn't need"] + #[doc = " * to be encrypted. In this case, the ACA responds with"] + #[doc = " * AcaEeCertResponsePlainSpdu."] + #[doc = " * - Absent and flags.cubk is set if the ACA's response is to be encrypted"] + #[doc = " * with the verification key from the request and not signed. In this case,"] + #[doc = " * the ACA responds with AcaEeCertResponseCubkSpdu."] + #[doc = " * - Present and flags.cubk is not set if the ACA's response is to be"] + #[doc = " * encrypted with certEncKey and then signed by the ACA. In this case, the"] + #[doc = " * ACA responds with AcaEeCertResponsePrivateSpdu."] + #[doc = " *"] + #[doc = " * @param tbsCert: contains parameters of the requested certificate. The"] + #[doc = " * certificate type depends on the field type, as follows:"] + #[doc = " * - If type is explicit, the request is valid only if"] + #[doc = " * tbsCert.verifyKeyIndicator is a verificationKey."] + #[doc = " * - If type is implicit, the request is valid only if"] + #[doc = " * tbsCert.verifyKeyIndicator is a reconstructionValue."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct RaAcaCertRequest { + #[rasn(value("2"))] + pub version: Uint8, + #[rasn(identifier = "generationTime")] + pub generation_time: Time32, + #[rasn(identifier = "type")] + pub r_type: CertificateType, + pub flags: RaAcaCertRequestFlags, + #[rasn(identifier = "linkageInfo")] + pub linkage_info: Option, + #[rasn(identifier = "certEncKey")] + pub cert_enc_key: Option, + #[rasn(value("0.."), identifier = "tbsCert")] + pub tbs_cert: ToBeSignedCertificate, + } + impl RaAcaCertRequest { + pub fn new( + version: Uint8, + generation_time: Time32, + r_type: CertificateType, + flags: RaAcaCertRequestFlags, + linkage_info: Option, + cert_enc_key: Option, + tbs_cert: ToBeSignedCertificate, + ) -> Self { + Self { + version, + generation_time, + r_type, + flags, + linkage_info, + cert_enc_key, + tbs_cert, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure is used to convey information from the RA to the ACA"] + #[doc = " * about operations to be carried out when generating the certificate. For"] + #[doc = " * more details see the specification of RaAcaCertRequest. An overview of"] + #[doc = " * this structure is as follows:"] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct RaAcaCertRequestFlags(pub FixedBitString<8usize>); +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_dot1_acpc { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::ieee1609_dot2_base_types::{HashAlgorithm, IValue, Psid, Time32, Uint8}; + use super::ieee1609_dot2_dot1_protocol::{Ieee1609Dot2DataSigned, Ieee1609Dot2DataUnsecured}; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This is a 16 byte string that represents the value of a node in the"] + #[doc = " * ACPC tree."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct AcpcNodeValue(pub FixedOctetString<16usize>); + #[doc = "*"] + #[doc = " * @brief This structure contains an APrV structure produced by the CAM. An"] + #[doc = " * overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @param tree: contains an AprvBinaryTree."] + #[doc = " *"] + #[doc = " * @param aprv: contains a single IndividualAprv."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum AcpcPdu { + tree(AprvBinaryTree), + aprv(IndividualAprv), + } + #[doc = "*"] + #[doc = " * @brief This is the PSID used to indicate activities in ACPC as specified in"] + #[doc = " * this document."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("2113696"))] + pub struct AcpcPsid(pub Psid); + #[doc = "*"] + #[doc = " * @brief This is an 8 byte string that identifies an ACPC tree series. It is"] + #[doc = " * required to be globally unique within the system and is the same for all"] + #[doc = " * ACPC tree instances within the ACPC tree series. Registration of AcpcTreeId"] + #[doc = " * values is managed by the IEEE RA; see http://standards.ieee.org/regauth. A"] + #[doc = " * list of assigned AcpcTreeId values is provided in L.2."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct AcpcTreeId(pub FixedOctetString<8usize>); + #[doc = "*"] + #[doc = " * @brief This structure encodes a binary tree. An overview of this structure"] + #[doc = " * is as follows:"] + #[doc = " *"] + #[doc = " * @param version: contains the current version of the structure."] + #[doc = " *"] + #[doc = " * @param generationTime: contains the generation time of AprvBinaryTree."] + #[doc = " *"] + #[doc = " * @param currentI: contains the i-value associated with the batch of"] + #[doc = " * certificates."] + #[doc = " *"] + #[doc = " * @param acpcTreeId: contains an identifier for the CAM creating this binary"] + #[doc = " * tree."] + #[doc = " *"] + #[doc = " * @param hashAlgorithmId: contains the identifier of the hash algorithm used"] + #[doc = " * inside the binary tree."] + #[doc = " *"] + #[doc = " * @param tree: contains a bit string indicating which nodes of the tree are"] + #[doc = " * present. It is calculated as specified in 9.5.4.2, and can be used by the"] + #[doc = " * EE to determine which entry in nodeValueList to use to derive that EE's"] + #[doc = " * APrV as specified in 9.5.2."] + #[doc = " *"] + #[doc = " * @param nodeValueList: contains the values of the nodes that are present in"] + #[doc = " * the order indicated by tree."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct AprvBinaryTree { + #[rasn(value("2"))] + pub version: Uint8, + #[rasn(identifier = "generationTime")] + pub generation_time: Time32, + #[rasn(identifier = "currentI")] + pub current_i: IValue, + #[rasn(identifier = "acpcTreeId")] + pub acpc_tree_id: AcpcTreeId, + #[rasn(identifier = "hashAlgorithmId")] + pub hash_algorithm_id: HashAlgorithm, + pub tree: BitString, + #[rasn(size("1.."), identifier = "nodeValueList")] + pub node_value_list: SequenceOf, + } + impl AprvBinaryTree { + pub fn new( + version: Uint8, + generation_time: Time32, + current_i: IValue, + acpc_tree_id: AcpcTreeId, + hash_algorithm_id: HashAlgorithm, + tree: BitString, + node_value_list: SequenceOf, + ) -> Self { + Self { + version, + generation_time, + current_i, + acpc_tree_id, + hash_algorithm_id, + tree, + node_value_list, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure, C-OER encoded, is the input to the hash function to"] + #[doc = " * calculate child node values from a parent node. By including the ID fields"] + #[doc = " * it \"firewalls\" the hash function so that an attacker who inverts the hash"] + #[doc = " * has only found the hash preimage for a specific node, in a specific tree,"] + #[doc = " * for a specific time period. An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @param version: contains the current version of the structure."] + #[doc = " *"] + #[doc = " * @param acpcTreeId: contains an identifier for this ACPC tree series."] + #[doc = " *"] + #[doc = " * @param acpcPeriod: contains an identifier for the time period for this tree."] + #[doc = " * If the certificates for which this set of APrVs are intended have an IValue"] + #[doc = " * field, acpcPeriod in this structure shall be the IValue field in the"] + #[doc = " * certificates. How the RA and the CAM synchronize on this value is outside"] + #[doc = " * the scope of this document."] + #[doc = " *"] + #[doc = " * @param childNodeId: contains a bit string of length l encoding the node"] + #[doc = " * location within the l'th level."] + #[doc = " *"] + #[doc = " * @param parentNodeValue: contains the value of the parent node."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct AprvHashCalculationInput { + #[rasn(value("2"))] + pub version: Uint8, + #[rasn(identifier = "acpcTreeId")] + pub acpc_tree_id: AcpcTreeId, + #[rasn(identifier = "acpcPeriod")] + pub acpc_period: IValue, + #[rasn(identifier = "childNodeId")] + pub child_node_id: BitString, + #[rasn(size("16"), identifier = "parentNodeValue")] + pub parent_node_value: OctetString, + } + impl AprvHashCalculationInput { + pub fn new( + version: Uint8, + acpc_tree_id: AcpcTreeId, + acpc_period: IValue, + child_node_id: BitString, + parent_node_value: OctetString, + ) -> Self { + Self { + version, + acpc_tree_id, + acpc_period, + child_node_id, + parent_node_value, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains an individual APrV. An overview of this"] + #[doc = " * structure is as follows:"] + #[doc = " *"] + #[doc = " * @param version: contains the current version of the structure."] + #[doc = " *"] + #[doc = " * @param generationTime: contains the generation time of IndividualAprv."] + #[doc = " *"] + #[doc = " * @param currentI: contains the i-value associated with the batch of"] + #[doc = " * certificates."] + #[doc = " *"] + #[doc = " * @param acpcTreeId: contains an identifier for the CAM creating this binary"] + #[doc = " * tree."] + #[doc = " *"] + #[doc = " * @param nodeId: contains the identifier of the node."] + #[doc = " *"] + #[doc = " * @param nodeValue: contains the value of the node."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct IndividualAprv { + #[rasn(value("2"))] + pub version: Uint8, + #[rasn(identifier = "generationTime")] + pub generation_time: Time32, + #[rasn(identifier = "currentI")] + pub current_i: IValue, + #[rasn(identifier = "acpcTreeId")] + pub acpc_tree_id: AcpcTreeId, + #[rasn(identifier = "nodeId")] + pub node_id: BitString, + #[rasn(identifier = "nodeValue")] + pub node_value: AcpcNodeValue, + } + impl IndividualAprv { + pub fn new( + version: Uint8, + generation_time: Time32, + current_i: IValue, + acpc_tree_id: AcpcTreeId, + node_id: BitString, + node_value: AcpcNodeValue, + ) -> Self { + Self { + version, + generation_time, + current_i, + acpc_tree_id, + node_id, + node_value, + } + } + } + #[doc = "*"] + #[doc = " * @brief This is used to wrap an AprvBinaryTree in an Ieee1609Dot2Data for"] + #[doc = " * transmission if the policy is that the AprvBinaryTree be signed. See 9.5.6"] + #[doc = " * for discussion."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct SignedAprvBinaryTree { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl SignedAprvBinaryTree { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This is used to wrap an IndividualAprv in an Ieee1609Dot2Data for"] + #[doc = " * transmission if the policy is that the IndividualAprv be signed. See 9.5.6"] + #[doc = " * for discussion."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct SignedIndividualAprv { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl SignedIndividualAprv { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This is used to wrap an AprvBinaryTree in an Ieee1609Dot2Data for"] + #[doc = " * transmission if the policy is that the AprvBinaryTree need not be signed."] + #[doc = " * See 9.5.6 for discussion."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct UnsecuredAprvBinaryTree { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl UnsecuredAprvBinaryTree { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_dot1_cam_ra_interface { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::ieee1609_dot2_base_types::{EccP256CurvePoint, HashedId8, IValue, Uint8}; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This is a blinded ACPC encryption key produced by the CAM."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct BlindedKey(pub EccP256CurvePoint); + #[doc = "*"] + #[doc = " * @brief This structure contains a blinded batch of keys for the EE during"] + #[doc = " * ACPC enrollment. An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @param version: contains the current version of the structure."] + #[doc = " *"] + #[doc = " * @param requestHash: contains the hash of the corresponding request"] + #[doc = " * RaCamBatchRequest."] + #[doc = " *"] + #[doc = " * @param batch: contains a sequence of blinded keys, each mapped to one"] + #[doc = " * IValue from the periodList field of the request."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct CamRaBatchResponse { + #[rasn(value("2"))] + pub version: Uint8, + #[rasn(identifier = "requestHash")] + pub request_hash: HashedId8, + pub batch: SequenceOf, + } + impl CamRaBatchResponse { + pub fn new(version: Uint8, request_hash: HashedId8, batch: SequenceOf) -> Self { + Self { + version, + request_hash, + batch, + } + } + } + #[doc = "*"] + #[doc = " * @brief This is the parent structure for all structures exchanged between"] + #[doc = " * the CAM and the RA during ACPC enrollment. An overview of this structure"] + #[doc = " * is as follows:"] + #[doc = " *"] + #[doc = " * @param raCamBatchRequest: contains the ACPC blinded key batch request sent"] + #[doc = " * by the RA to the CAM."] + #[doc = " *"] + #[doc = " * @param camRaBatchResponse: contains the CAM's response to RaCamBatchRequest."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum CamRaInterfacePdu { + raCamBatchRequest(RaCamBatchRequest), + camRaBatchResponse(CamRaBatchResponse), + } + #[doc = "*"] + #[doc = " * @brief This structure contains parameters needed to request a blinded batch"] + #[doc = " * of keys for the EE during ACPC enrollment. An overview of this structure"] + #[doc = " * is as follows:"] + #[doc = " *"] + #[doc = " * @param version: contains the current version of the structure."] + #[doc = " *"] + #[doc = " * @param eeId: contains the EE's ID generated by the RA for the production of"] + #[doc = " * ACPC batch keys by the CAM."] + #[doc = " *"] + #[doc = " * @param periodList: contains the list of i-periods covered by the batch."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct RaCamBatchRequest { + #[rasn(value("2"))] + pub version: Uint8, + #[rasn(size("5"), identifier = "eeId")] + pub ee_id: OctetString, + #[rasn(identifier = "periodList")] + pub period_list: SequenceOf, + } + impl RaCamBatchRequest { + pub fn new(version: Uint8, ee_id: OctetString, period_list: SequenceOf) -> Self { + Self { + version, + ee_id, + period_list, + } + } + } +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_dot1_cert_management { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::ieee1609_dot2::{Certificate, SequenceOfCertificate}; + use super::ieee1609_dot2_base_types::{HashedId32, HashedId48, HashedId8, Time32, Uint8}; + use super::ieee1609_dot2_crl::SecuredCrl; + use super::ieee1609_dot2_crl_base_types::CrlSeries; + use super::ieee1609_dot2_dot1_protocol::{ + CtlSignatureSpdu, MultiSignedCtlSpdu, SequenceOfPsid, + }; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This is the parent structure for all SCMS component certificate"] + #[doc = " * management structures. An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @param compositeCrl: contains zero or more SecuredCrl as defined in IEEE"] + #[doc = " * Std 1609.2, and the CTL."] + #[doc = " *"] + #[doc = " * @param certificateChain: contains a collection of certificates and the CTL."] + #[doc = " *"] + #[doc = " * @param multiSignedCtl: contains the CTL signed by multiple"] + #[doc = " * signers, the electors."] + #[doc = " *"] + #[doc = " * @param tbsCtlSignature: contains the CTL-instance-specific information used"] + #[doc = " * to generate a signature on the CTL."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum CertManagementPdu { + compositeCrl(CompositeCrl), + certificateChain(CertificateChain), + multiSignedCtl(MultiSignedCtl), + tbsCtlSignature(ToBeSignedCtlSignature), + infoStatus(CertificateManagementInfoStatus), + } + #[doc = "*"] + #[doc = " * @brief This structure is used to encapsulate certificates and a CTL. An"] + #[doc = " * overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @param homeCtl: contains a CTL. If the certificate chain was requested via"] + #[doc = " * the mechanisms given in 6.3.5.7, the CtlSeriesId in this CTL is the"] + #[doc = " * same as the CtlSeriesId provided in the request. The intent is that"] + #[doc = " * this is the \"home\" CTL of the requester, but this field can in practice be"] + #[doc = " * used to provide any CTL."] + #[doc = " *"] + #[doc = " * @param others: contains additional valid certificates of the CAs and the"] + #[doc = " * MAs chosen by means outside the scope of this document."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct CertificateChain { + #[rasn(identifier = "homeCtl")] + pub home_ctl: MultiSignedCtlSpdu, + pub others: SequenceOf, + } + impl CertificateChain { + pub fn new(home_ctl: MultiSignedCtlSpdu, others: SequenceOf) -> Self { + Self { home_ctl, others } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains the status of different certificate"] + #[doc = " * management information, including CRLs, CTLs, and individual certificates"] + #[doc = " * of CAs, MAs, and the RA."] + #[doc = " *"] + #[doc = " * @param crl: contains the status information for CRLs."] + #[doc = " *"] + #[doc = " * @param ctl: contains the status information for CTLs."] + #[doc = " *"] + #[doc = " * @param caCcf: contains the time of the last update of any of the CA"] + #[doc = " * certificates in the CCF."] + #[doc = " *"] + #[doc = " * @param ma: contains the status information for MA certificates."] + #[doc = " *"] + #[doc = " * @param ra: shall be present and contain the time of last update of the RA's"] + #[doc = " * certificate, if this structure is sent by an RA."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct CertificateManagementInfoStatus { + pub crl: SequenceOfCrlInfoStatus, + pub ctl: SequenceOfCtlInfoStatus, + #[rasn(identifier = "caCcf")] + pub ca_ccf: Time32, + pub ma: SequenceOfMaInfoStatus, + pub ra: Option, + } + impl CertificateManagementInfoStatus { + pub fn new( + crl: SequenceOfCrlInfoStatus, + ctl: SequenceOfCtlInfoStatus, + ca_ccf: Time32, + ma: SequenceOfMaInfoStatus, + ra: Option, + ) -> Self { + Self { + crl, + ctl, + ca_ccf, + ma, + ra, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure is used to encapsulate CRLs and a CTL. An overview"] + #[doc = " * of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @param crl: contains a list of signed CRLs for different (CRACA ID, CRL"] + #[doc = " * series) pairs. The CRLs are signed individually, and this document does not"] + #[doc = " * specify the order in which they should appear."] + #[doc = " *"] + #[doc = " * @param homeCtl: contains a CTL. If the composite CRL was requested via the"] + #[doc = " * mechanisms given in 6.3.5.8, the CtlSeriesId in this CTL is the same as"] + #[doc = " * the CtlSeriesId provided in the request. The intent is that this is the"] + #[doc = " * \"home\" CTL of the requester, but this field can in practice be used to"] + #[doc = " * provide any CTL with any CtlSeriesId value."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct CompositeCrl { + pub crl: SequenceOf, + #[rasn(identifier = "homeCtl")] + pub home_ctl: MultiSignedCtlSpdu, + } + impl CompositeCrl { + pub fn new(crl: SequenceOf, home_ctl: MultiSignedCtlSpdu) -> Self { + Self { crl, home_ctl } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains the status information for a CRL."] + #[doc = " *"] + #[doc = " * @param cracaId: contains the CRACA ID of the CRL."] + #[doc = " *"] + #[doc = " * @param series: contains the CRL series of the CRL."] + #[doc = " *"] + #[doc = " * @param issueDate: contains the time of the last update of the CRL."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct CrlInfoStatus { + #[rasn(identifier = "cracaId")] + pub craca_id: HashedId8, + pub series: CrlSeries, + #[rasn(identifier = "issueDate")] + pub issue_date: Time32, + } + impl CrlInfoStatus { + pub fn new(craca_id: HashedId8, series: CrlSeries, issue_date: Time32) -> Self { + Self { + craca_id, + series, + issue_date, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains the hash of an elector certificate."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct CtlElectorEntry(pub HashedId48); + #[doc = "*"] + #[doc = " * @brief This structure contains the status information for a CTL."] + #[doc = " *"] + #[doc = " * @param ctlSeriesId: contains the elector group ID of the CTL."] + #[doc = " *"] + #[doc = " * @param sequenceNumber: contains the sequence number of the CTL."] + #[doc = " *"] + #[doc = " * @param lastUpdate: contains the time of the last update of the CTL."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct CtlInfoStatus { + #[rasn(identifier = "ctlSeriesId")] + pub ctl_series_id: CtlSeriesId, + #[rasn(identifier = "sequenceNumber")] + pub sequence_number: CtlSequenceNumber, + #[rasn(identifier = "lastUpdate")] + pub last_update: Time32, + } + impl CtlInfoStatus { + pub fn new( + ctl_series_id: CtlSeriesId, + sequence_number: CtlSequenceNumber, + last_update: Time32, + ) -> Self { + Self { + ctl_series_id, + sequence_number, + last_update, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains the hash of a root CA certificate."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct CtlRootCaEntry(pub HashedId32); + #[doc = "*"] + #[doc = " * @brief This structure is used to encode the CTL sequence number. This"] + #[doc = " * document does not specify semantics of this type once it reaches its"] + #[doc = " * maximum value."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("0..=65535"))] + pub struct CtlSequenceNumber(pub u16); + #[doc = "*"] + #[doc = " * @brief This structure identifies a group of electors that sign a series of"] + #[doc = " * CTLs for a specific purpose. Registration of CtlSeriesId values is"] + #[doc = " * managed by the IEEE RA; see http://standards.ieee.org/regauth. A list of"] + #[doc = " * assigned CtlSeriesId values is provided in K.1."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct CtlSeriesId(pub FixedOctetString<8usize>); + #[doc = "*"] + #[doc = " * @brief This structure specifies a CTL that contains information about the"] + #[doc = " * complete set of certificates trusted by the electors that sign the CTL. An"] + #[doc = " * overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @note:"] + #[doc = " * - If in future CTL types are defined that contain the same"] + #[doc = " * information as, or a subset of the information in, the fullIeeeCtl, those"] + #[doc = " * types are anticipated to contain the same sequence number as the"] + #[doc = " * corresponding fullIeeeCtl."] + #[doc = " *"] + #[doc = " * - Any root CA or elector certificate that is not on the CTL is"] + #[doc = " * not trusted. The electorRemove and rootCaRemove are intended to be used"] + #[doc = " * only if the SCMS manager wants to explicitly indicate that a previously"] + #[doc = " * trusted entity (elector or root CA) is now not trusted even though that"] + #[doc = " * entity's certificate is still within its validity period. In practice, it"] + #[doc = " * is anticipated that the remove fields (electorRemove and rootCaRemove)"] + #[doc = " * will almost always be sequences of length 0."] + #[doc = " *"] + #[doc = " * @param type: contains the type of the CTL. It is identical to the type"] + #[doc = " * field that appears in the enclosing MultiSignedCtl. The field is included"] + #[doc = " * here as well to provide the simplest mechanism to help ensure that the"] + #[doc = " * type is included in the calculated CTL hash."] + #[doc = " *"] + #[doc = " * @param CtlSeriesId: contains the group of electors that have signed the"] + #[doc = " * CTL. It plays a role similar to CrlSeries in a CRL. This field is intended"] + #[doc = " * to be globally unique in the universe of all systems that use the"] + #[doc = " * MultiSignedCtl. See the specification of CtlSeriesId for discussion of"] + #[doc = " * a convention that can be followed to enable uniqueness."] + #[doc = " *"] + #[doc = " * @param sequenceNumber: contains the sequence number of the CTL. This is"] + #[doc = " * incremented by 1 every time a new FullIeeeTbsCtl is issued."] + #[doc = " *"] + #[doc = " * @param effectiveDate: contains the time when the CTL is to take effect."] + #[doc = " * This is to be greater than or equal to the effectiveDate field in the CTL"] + #[doc = " * with the same CtlSeriesId and the previous sequence number."] + #[doc = " *"] + #[doc = " * @param electorApprove: contains the list of hashes of the elector"] + #[doc = " * certificates that are approved as of the effective date. The hash is"] + #[doc = " * calculated with the same hash algorithm that is used to hash the elector"] + #[doc = " * certificate for signing."] + #[doc = " *"] + #[doc = " * @param electorRemove: contains the list of hashes of the elector"] + #[doc = " * certificates that are valid (that is, not expired) on the effective date and"] + #[doc = " * are not approved, as of the effective date, to sign a CTL. The hash is"] + #[doc = " * calculated with the same hash algorithm that is used to hash the elector"] + #[doc = " * certificate for signing. This field is to be considered informational as a"] + #[doc = " * certificate that is not included in electorApprove is not valid even if it"] + #[doc = " * does not appear in electorRemove."] + #[doc = " *"] + #[doc = " * @param rootCaApprove: contains the list of root CA certificates that are"] + #[doc = " * approved as of the effective date. The hash is calculated with the same"] + #[doc = " * hash algorithm that is used to hash the root certificate for signing. If"] + #[doc = " * the root certificate is signed with a hash function with a 48 octet"] + #[doc = " * output, this is truncated to the low-order 32 bytes for inclusion in the"] + #[doc = " * CTL."] + #[doc = " *"] + #[doc = " * @param rootCaRemove: contains the list of root CA certificates that are"] + #[doc = " * valid (that is, not expired) on the effective date and are not approved, as"] + #[doc = " * of the effective date, to issue certificates or carry out other"] + #[doc = " * activities. If the root certificate is signed with a hash function"] + #[doc = " * with a 48 octet output, this is truncated to the low-order 32 bytes for"] + #[doc = " * inclusion in the CTL. This field is to be considered informational as a"] + #[doc = " * certificate that is not included in rootCaApprove is not valid even if it"] + #[doc = " * does not appear in rootCaRemove."] + #[doc = " *"] + #[doc = " * @param quorum: contains the quorum, that is, the number of the electors"] + #[doc = " * required to sign the next CTL with the same CtlSeriesId value for that"] + #[doc = " * CTL to be trusted. If this field is absent, the quorum for the next CTL"] + #[doc = " * shall be the quorum for the current CTL."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct FullIeeeTbsCtl { + #[rasn(value("1"), identifier = "type")] + pub r_type: Ieee1609dot2dot1MsctlType, + #[rasn(identifier = "ctlSeriesId")] + pub ctl_series_id: CtlSeriesId, + #[rasn(identifier = "sequenceNumber")] + pub sequence_number: CtlSequenceNumber, + #[rasn(identifier = "effectiveDate")] + pub effective_date: Time32, + #[rasn(identifier = "electorApprove")] + pub elector_approve: SequenceOf, + #[rasn(identifier = "electorRemove")] + pub elector_remove: SequenceOf, + #[rasn(identifier = "rootCaApprove")] + pub root_ca_approve: SequenceOf, + #[rasn(identifier = "rootCaRemove")] + pub root_ca_remove: SequenceOf, + #[rasn(extension_addition)] + pub quorum: Integer, + } + impl FullIeeeTbsCtl { + pub fn new( + r_type: Ieee1609dot2dot1MsctlType, + ctl_series_id: CtlSeriesId, + sequence_number: CtlSequenceNumber, + effective_date: Time32, + elector_approve: SequenceOf, + elector_remove: SequenceOf, + root_ca_approve: SequenceOf, + root_ca_remove: SequenceOf, + quorum: Integer, + ) -> Self { + Self { + r_type, + ctl_series_id, + sequence_number, + effective_date, + elector_approve, + elector_remove, + root_ca_approve, + root_ca_remove, + quorum, + } + } + } + #[doc = "*"] + #[doc = " * @brief This is the integer used to identify the type of the CTL."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("0..=255"))] + pub struct Ieee1609dot2dot1MsctlType(pub u8); + #[doc = "*"] + #[doc = " * @brief This structure contains the status information for an MA's"] + #[doc = " * certificate."] + #[doc = " *"] + #[doc = " * @param psids: contains the PSIDs associated with the misbehavior that is to"] + #[doc = " * be reported to that MA."] + #[doc = " *"] + #[doc = " * @param updated: contains the time of the last update of the MA's certificate."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct MaInfoStatus { + pub psids: SequenceOfPsid, + pub updated: Time32, + } + impl MaInfoStatus { + pub fn new(psids: SequenceOfPsid, updated: Time32) -> Self { + Self { psids, updated } + } + } + #[doc = "*"] + #[doc = " * @brief This structure a certificate trust list (CTL) signed by multiple"] + #[doc = " * signers, the electors. An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @param type: contains the type of the multi-signed CTL. Only one type of"] + #[doc = " * multi-signed CTL is supported in this version of this document."] + #[doc = " *"] + #[doc = " * @param tbsCtl: contains the CTL contents."] + #[doc = " *"] + #[doc = " * @param unsigned: contains data that are associated with the CTL and that"] + #[doc = " * are not included directly in tbsCtl. For example, if the type is"] + #[doc = " * fullIeeeCtlType, the FullIeeeTbsCtl contains the hashes of the"] + #[doc = " * certificates, and the certificates themselves are contained in unsigned."] + #[doc = " *"] + #[doc = " * @param signatures: contains the signatures. How the signatures are"] + #[doc = " * calculated is specified in the definition of ToBeSignedCtlSignature. The"] + #[doc = " * number of signatures shall be no more than the number of electors. Each"] + #[doc = " * signature shall have been generated by a distinct elector."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct MultiSignedCtl { + #[rasn(identifier = "type")] + pub r_type: Ieee1609dot2dot1MsctlType, + #[rasn(identifier = "tbsCtl")] + pub tbs_ctl: Any, + pub unsigned: Any, + #[rasn(size("1.."))] + pub signatures: SequenceOf, + } + impl MultiSignedCtl { + pub fn new( + r_type: Ieee1609dot2dot1MsctlType, + tbs_ctl: Any, + unsigned: Any, + signatures: SequenceOf, + ) -> Self { + Self { + r_type, + tbs_ctl, + unsigned, + signatures, + } + } + } + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfCrlInfoStatus(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfCtlInfoStatus(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SequenceOfMaInfoStatus(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This structure contains the CTL-instance-specific information used"] + #[doc = " * to generate a signature on the CTL. An overview of this structure is as"] + #[doc = " * follows:"] + #[doc = " *"] + #[doc = " * @param ctlSeriesId: contains the CtlSeriesId that appears in the CTL."] + #[doc = " *"] + #[doc = " * @param ctlType: identifies the type of the CTL."] + #[doc = " *"] + #[doc = " * @param sequenceNumber: contains the sequence number of the CTL being signed."] + #[doc = " *"] + #[doc = " * @param tbsCtlHash: contains the hash of the C-OER encoded tbsCtl field"] + #[doc = " * in the MultiSignedCtl. The hash is calculated using the same hash"] + #[doc = " * algorithm that is used to generate the signature on this structure when it"] + #[doc = " * is contained in a CtlSignatureSpdu. This algorithm can be determined from"] + #[doc = " * the headers of the CtlSignatureSpdu."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct ToBeSignedCtlSignature { + #[rasn(identifier = "ctlSeriesId")] + pub ctl_series_id: CtlSeriesId, + #[rasn(identifier = "ctlType")] + pub ctl_type: Ieee1609dot2dot1MsctlType, + #[rasn(identifier = "sequenceNumber")] + pub sequence_number: CtlSequenceNumber, + #[rasn(identifier = "tbsCtlHash")] + pub tbs_ctl_hash: HashedId48, + } + impl ToBeSignedCtlSignature { + pub fn new( + ctl_series_id: CtlSeriesId, + ctl_type: Ieee1609dot2dot1MsctlType, + sequence_number: CtlSequenceNumber, + tbs_ctl_hash: HashedId48, + ) -> Self { + Self { + ctl_series_id, + ctl_type, + sequence_number, + tbs_ctl_hash, + } + } + } + pub const FULL_IEEE_CTL: Ieee1609dot2dot1MsctlType = Ieee1609dot2dot1MsctlType(1); +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_dot1_eca_ee_interface { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::ieee1609_dot2::{Certificate, CertificateType, SequenceOfCertificate}; + use super::ieee1609_dot2_base_types::{EccP256CurvePoint, HashedId8, Time32, Uint8}; + use super::ieee1609_dot2_dot1_protocol::{PublicVerificationKey, ToBeSignedCertificate}; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This structure is used by the ECA to respond to an EE's enrollment"] + #[doc = " * certificate request. Additional bootstrapping information including the"] + #[doc = " * RA's certificate are provided by the DCM. The specification of the DCM is"] + #[doc = " * outside the scope of this document. An overview of this structure is as"] + #[doc = " * follows:"] + #[doc = " *"] + #[doc = " * The definition of validity for a certificate request, including"] + #[doc = " * constraints on the fields in this structure, is specified in 10.1."] + #[doc = " *"] + #[doc = " * @param version: contains the current version of the structure."] + #[doc = " *"] + #[doc = " * @param generationTime: contains the generation time of EcaEeCertResponse."] + #[doc = " *"] + #[doc = " * @param requestHash: contains the following hash:"] + #[doc = " * - EeEcaCertRequestSPDU, if the corresponding request was"] + #[doc = " * EeEcaCertRequestSPDU. This is calculated without \"canonicalizing\" the"] + #[doc = " * signature, i.e., it is calculated over the signature as given in the"] + #[doc = " * EeEcaCertRequestSpdu without re-encoding the signature's r component in"] + #[doc = " * x-coordinate-only form. See IEEE Std 1609.2 for further details on"] + #[doc = " * canonicalization."] + #[doc = " * - EeRaSuccessorEnrollmentCertRequestSpd, if the corresponding request"] + #[doc = " * was EeRaSuccessorEnrollmentCertRequestSpd."] + #[doc = " *"] + #[doc = " * @param ecaCertChain: contains the ECA's currently valid certificate and the"] + #[doc = " * certificate chain, up to and including the root CA."] + #[doc = " *"] + #[doc = " * @param certificate: contains the enrollment certificate generated by the"] + #[doc = " * ECA, which shall be of the type indicated by the type field in the"] + #[doc = " * corresponding request."] + #[doc = " *"] + #[doc = " * @param privateKeyInfo: shall be present and contain the private key"] + #[doc = " * reconstruction value, if certificate.type is implicit. This is used by the"] + #[doc = " * EE as specified in 9.3.5.1."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct EcaEeCertResponse { + #[rasn(value("2"))] + pub version: Uint8, + #[rasn(identifier = "generationTime")] + pub generation_time: Time32, + #[rasn(identifier = "requestHash")] + pub request_hash: HashedId8, + #[rasn(identifier = "ecaCertChain")] + pub eca_cert_chain: SequenceOfCertificate, + pub certificate: Certificate, + #[rasn(size("32"), identifier = "privateKeyInfo")] + pub private_key_info: Option, + } + impl EcaEeCertResponse { + pub fn new( + version: Uint8, + generation_time: Time32, + request_hash: HashedId8, + eca_cert_chain: SequenceOfCertificate, + certificate: Certificate, + private_key_info: Option, + ) -> Self { + Self { + version, + generation_time, + request_hash, + eca_cert_chain, + certificate, + private_key_info, + } + } + } + #[doc = "*"] + #[doc = " * @brief This is the parent structure for all structures exchanged between"] + #[doc = " * the ECA and the EE. An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @param eeEcaCertRequest: contains the enrollment certificate request sent"] + #[doc = " * by the EE to the ECA."] + #[doc = " *"] + #[doc = " * @param ecaEeCertResponse: contains the enrollment certificate response sent"] + #[doc = " * by the ECA to the EE."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum EcaEeInterfacePdu { + eeEcaCertRequest(EeEcaCertRequest), + ecaEeCertResponse(EcaEeCertResponse), + } + #[doc = "*"] + #[doc = " * @brief This structure contains parameters needed to request an enrollment"] + #[doc = " * certificate from the ECA. The ECA may, subject to policy, issue an"] + #[doc = " * enrollment certificate with different contents than the contents requested."] + #[doc = " * An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @note:"] + #[doc = " * - The tbsCert.cracaId and tbsCert.crlSeries are set to the"] + #[doc = " * indicated values in the corresponding EeEcaCertRequest. In the issued"] + #[doc = " * enrollment certificate, they may have different values, set by the ECA."] + #[doc = " * - The EE uses the type field to indicate whether it is"] + #[doc = " * requesting an explicit or an implicit enrollment certificate. A policy is"] + #[doc = " * anticipated that determines what type of certificate is appropriate for a"] + #[doc = " * given set of circumstances (such as PSIDs, other end entity information,"] + #[doc = " * and locality) and that if the EE has requested a kind of certificate that"] + #[doc = " * is not allowed by policy, the ECA returns an error to the EE."] + #[doc = " *"] + #[doc = " * @param version: contains the current version of the structure."] + #[doc = " *"] + #[doc = " * @param generationTime: contains the generation time of EeEcaCertRequest."] + #[doc = " *"] + #[doc = " * @param type: indicates whether the request is for an explicit or implicit"] + #[doc = " * certificate (see 4.1.1, 4.1.4.3.1)."] + #[doc = " *"] + #[doc = " * @param tbsCert: contains the parameters used by the ECA to generate the"] + #[doc = " * enrollment certificate. tbsCert.verifyKeyIndicator.verificationKey"] + #[doc = " * contains the public key information sent by the requester. The"] + #[doc = " * verifyKeyIndicator field indicates the choice verificationKey even if type"] + #[doc = " * is implicit, as this allows the requester to indicate which signature"] + #[doc = " * algorithm and curve they are requesting. The value in this field is used"] + #[doc = " * as the verification key in the certificate if the certificate issued in"] + #[doc = " * response to this request is explicit, and as the input public key value"] + #[doc = " * for implicit certificate generation if the certificate issued in response"] + #[doc = " * to this request is implicit."] + #[doc = " *"] + #[doc = " * @param canonicalId: shall be present and contain the canonical identifier"] + #[doc = " * for the device per 4.1.4.2, if the enclosing EeEcaCertRequestSpdu was"] + #[doc = " * signed by the canonical private key. The receiver is intended to use the"] + #[doc = " * canonicalId to look up the canonical public key to verify the certificate"] + #[doc = " * request."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct EeEcaCertRequest { + #[rasn(value("2"))] + pub version: Uint8, + #[rasn(identifier = "generationTime")] + pub generation_time: Time32, + #[rasn(identifier = "type")] + pub r_type: CertificateType, + #[rasn(value("0.."), identifier = "tbsCert")] + pub tbs_cert: ToBeSignedCertificate, + #[rasn(identifier = "canonicalId")] + pub canonical_id: Option, + } + impl EeEcaCertRequest { + pub fn new( + version: Uint8, + generation_time: Time32, + r_type: CertificateType, + tbs_cert: ToBeSignedCertificate, + canonical_id: Option, + ) -> Self { + Self { + version, + generation_time, + r_type, + tbs_cert, + canonical_id, + } + } + } +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_dot1_ee_ma_interface { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This structure is currently being defined outside of this document,"] + #[doc = " * so it is defined as NULL for purposes of this document."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash, Copy)] + #[rasn(delegate)] + pub struct EeMaInterfacePdu(pub ()); +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_dot1_ee_ra_interface { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use super::ieee1609_dot2::CertificateType; + use super::ieee1609_dot2_base_types::{HashedId8, IValue, PublicEncryptionKey, Time32, Uint8}; + use super::ieee1609_dot2_dot1_acpc::AcpcTreeId; + use super::ieee1609_dot2_dot1_protocol::{ + EeEcaCertRequestSpdu, PublicVerificationKey, ToBeSignedCertificate, + }; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This structure contains parameters for the butterfly key mechanism."] + #[doc = " * An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @param original: contains the parameters for the original variant."] + #[doc = " *"] + #[doc = " * @param unified: contains the expansion function for signing to be used for"] + #[doc = " * the unified variant. The caterpillar public key and expansion function for"] + #[doc = " * encryption are the same as those for signing."] + #[doc = " *"] + #[doc = " * @param compactUnified: contains the expansion function for signing to be"] + #[doc = " * used for the compact unified variant. The caterpillar public key and"] + #[doc = " * expansion function for encryption are the same as those for signing."] + #[doc = " *"] + #[doc = " * @param encryptionKey: contains the public key for encrypting the"] + #[doc = " * certificate if the butterfly key mechanism is not used."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum AdditionalParams { + original(ButterflyParamsOriginal), + unified(ButterflyExpansion), + compactUnified(ButterflyExpansion), + encryptionKey(PublicEncryptionKey), + } + #[doc = "*"] + #[doc = " * @brief This structure contains material used in the butterfly key"] + #[doc = " * calculations as specified in 9.3.5.1 and 9.3.5.2. An overview of this"] + #[doc = " * structure is as follows:"] + #[doc = " *"] + #[doc = " * @param aes128: indicates that the symmetric algorithm used in the expansion"] + #[doc = " * function is AES-128 with the indicated 16 byte string used as the key."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum ButterflyExpansion { + #[rasn(size("16"))] + aes128(OctetString), + } + #[doc = "*"] + #[doc = " * @brief This structure contains parameters for the original variation of the"] + #[doc = " * butterfly key mechanism. An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @param signingExpansion: contains the expansion function for signing."] + #[doc = " *"] + #[doc = " * @param encryptionKey: contains the caterpillar public key for encryption."] + #[doc = " *"] + #[doc = " * @param encryptionExpansion: contains the expansion function for encryption."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct ButterflyParamsOriginal { + #[rasn(identifier = "signingExpansion")] + pub signing_expansion: ButterflyExpansion, + #[rasn(identifier = "encryptionKey")] + pub encryption_key: PublicEncryptionKey, + #[rasn(identifier = "encryptionExpansion")] + pub encryption_expansion: ButterflyExpansion, + } + impl ButterflyParamsOriginal { + pub fn new( + signing_expansion: ButterflyExpansion, + encryption_key: PublicEncryptionKey, + encryption_expansion: ButterflyExpansion, + ) -> Self { + Self { + signing_expansion, + encryption_key, + encryption_expansion, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains parameters needed to request different types"] + #[doc = " * of authorization certificates. An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * The definition of validity for a certificate request, including"] + #[doc = " * constraints on the fields in this structure, is specified in 10.1."] + #[doc = " *"] + #[doc = " * @note:"] + #[doc = " * - In the case where the butterfly key mechanism is used to"] + #[doc = " * derive the certificate encryption key, the value j is not communicated to"] + #[doc = " * the ACA. However, the EE that receives the certificate response can only"] + #[doc = " * decrypt the response if it knows j. The RA is therefore anticipated to"] + #[doc = " * store j so that it can be associated with the appropriate certificate"] + #[doc = " * response."] + #[doc = " * - If the type of id is LinkageData, the contents of the"] + #[doc = " * field in the request are replaced by random data by the RA when it sends"] + #[doc = " * the individual certificate requests to the ACA. The ACA then in turn"] + #[doc = " * replaces that data with the linkage values generated with the help of the"] + #[doc = " * LAs; see Annex D."] + #[doc = " * - This document does not specify a method to include an"] + #[doc = " * encryptionKey in the requested certificates, if the butterfly key"] + #[doc = " * mechanism is used. The EE using such a certificate to sign a message"] + #[doc = " * cannot request that the response is encrypted to the certificate. Instead,"] + #[doc = " * it can request an encrypted response using the"] + #[doc = " * tbsData.headerInfo.encryptionKey field of the SignedData; see 6.3.9,"] + #[doc = " * 6.3.33, 6.3.34, and 6.3.36 of IEEE Std 1609.2 for more details."] + #[doc = " *"] + #[doc = " * @param version: contains the current version of the structure."] + #[doc = " *"] + #[doc = " * @param generationTime: contains the generation time of EeRaCertRequest."] + #[doc = " *"] + #[doc = " * @param type: indicates whether the request is for an explicit or implicit"] + #[doc = " * certificate (see 4.1.1 and 4.1.4.3.1)."] + #[doc = " *"] + #[doc = " * @param tbsCert: contains the parameters to be used by the ACA to generate"] + #[doc = " * authorization certificate(s)."] + #[doc = " * - id contains the identity information sent by the requester. If the"] + #[doc = " * type is LinkageData, the contents of the field are chosen by the EE using"] + #[doc = " * any appropriate means. RA replaces that in the certificates with the"] + #[doc = " * linkage values generated with the help of the LAs and the ACA; see Annex D."] + #[doc = " * - validityPeriod contains the requested validity period of the first"] + #[doc = " * batch of certificates."] + #[doc = " * - region, assuranceLevel, canRequestRollover, and encryptionKey, if"] + #[doc = " * present, contain the information sent by the requester for the requested"] + #[doc = " * certificates."] + #[doc = " * - verifyKeyIndicator.verificationKey contains the public key"] + #[doc = " * information sent by the requester. The verifyKeyIndicator field indicates"] + #[doc = " * the choice verificationKey even if type is implicit, as this allows the"] + #[doc = " * requester to indicate which signature algorithm and curve they are"] + #[doc = " * requesting."] + #[doc = " * - If the certificate issued in response to this request is explicit and"] + #[doc = " * butterfly expansion is not used, the value in this field is the"] + #[doc = " * verification key that appears in that certificate."] + #[doc = " * - If the certificate issued in response to this request is implicit and"] + #[doc = " * butterfly expansion is not used, the value in this field is the input"] + #[doc = " * public key value for implicit certificate generation."] + #[doc = " * - If butterfly expansion is used, that is, if one of (original, unified,"] + #[doc = " * compactUnified) options is present in the field additionalParams, the"] + #[doc = " * value in this field is combined with the values in the additionalParams"] + #[doc = " * field as specified in 9.3."] + #[doc = " *"] + #[doc = " * @param additionalParams: shall be present and contain relevant parameters if"] + #[doc = " * the requested certificates are to be generated using the butterfly key"] + #[doc = " * mechanism as specified in 9.3, or if the requested certificates are to be"] + #[doc = " * encrypted without using the butterfly key mechanism. If present, the field"] + #[doc = " * tbsCert.verifyKeyIndicator shall be used as the caterpillar public key for"] + #[doc = " * signing in the butterfly key mechanism."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct EeRaCertRequest { + #[rasn(value("2"))] + pub version: Uint8, + #[rasn(identifier = "generationTime")] + pub generation_time: Time32, + #[rasn(identifier = "type")] + pub r_type: CertificateType, + #[rasn(value("0.."), identifier = "tbsCert")] + pub tbs_cert: ToBeSignedCertificate, + #[rasn(identifier = "additionalParams")] + pub additional_params: Option, + } + impl EeRaCertRequest { + pub fn new( + version: Uint8, + generation_time: Time32, + r_type: CertificateType, + tbs_cert: ToBeSignedCertificate, + additional_params: Option, + ) -> Self { + Self { + version, + generation_time, + r_type, + tbs_cert, + additional_params, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains parameters needed to request the download of"] + #[doc = " * certificates from the RA. An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @param generationTime: contains the generation time of EeRaDownloadRequest."] + #[doc = " *"] + #[doc = " * @param filename: contains the name of the file requested for download,"] + #[doc = " * formed as specified in 8.2.2."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct EeRaDownloadRequest { + #[rasn(identifier = "generationTime")] + pub generation_time: Time32, + pub filename: Utf8String, + } + impl EeRaDownloadRequest { + pub fn new(generation_time: Time32, filename: Utf8String) -> Self { + Self { + generation_time, + filename, + } + } + } + #[doc = "*"] + #[doc = " * @brief This is the parent structure for all structures exchanged between"] + #[doc = " * the EE and the RA. An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @note This CHOICE does not include a PDU type for encrypted"] + #[doc = " * misbehavior report upload; see 4.1.5."] + #[doc = " *"] + #[doc = " * @param eeRaCertRequest: contains the certificate generation request sent by"] + #[doc = " * the EE to the RA."] + #[doc = " *"] + #[doc = " * @param raEeCertAck: contains the RA's acknowledgement of the receipt of"] + #[doc = " * EeRaCertRequestSpdu."] + #[doc = " *"] + #[doc = " * @param raEeCertInfo: contains the information about certificate download."] + #[doc = " *"] + #[doc = " * @param eeRaDownloadRequest: contains the download request sent by the EE to"] + #[doc = " * the RA."] + #[doc = " *"] + #[doc = " * @param eeRaSuccessorEnrollmentCertRequest: contains a self-signed request"] + #[doc = " * for an enrollment certificate, identical in format to the one submitted"] + #[doc = " * for an initial enrollment certificate. (This becomes a request for a"] + #[doc = " * successor enrollment certificate by virtue of being signed by the current"] + #[doc = " * enrollment certificate.)"] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum EeRaInterfacePdu { + eeRaCertRequest(EeRaCertRequest), + raEeCertAck(RaEeCertAck), + raEeCertInfo(RaEeCertInfo), + eeRaDownloadRequest(EeRaDownloadRequest), + eeRaSuccessorEnrollmentCertRequest(EeEcaCertRequestSpdu), + } + #[doc = "*"] + #[doc = " * @brief This structure is used to create the acknowledgement for certificate"] + #[doc = " * requests. An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @param version: contains the current version of the structure."] + #[doc = " *"] + #[doc = " * @param generationTime: contains the generation time of RaEeCertAck."] + #[doc = " *"] + #[doc = " * @param requestHash: contains the hash of the corresponding"] + #[doc = " * EeRaCertRequestSpdu."] + #[doc = " *"] + #[doc = " * @param firstI: shall be present and contain the i-value that will be"] + #[doc = " * associated with the first certificate or the certificate batch that will be"] + #[doc = " * made available to the EE, if the corresponding EeRaCertRequest uses the"] + #[doc = " * butterfly key mechanism as indicated in the field additionalParams. The EE"] + #[doc = " * uses this to form the download filename for the download request as"] + #[doc = " * specified in 8.2.2."] + #[doc = " *"] + #[doc = " * @param nextDlTime: contains the time after which the EE should connect to"] + #[doc = " * the RA to download the certificates."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct RaEeCertAck { + #[rasn(value("2"))] + pub version: Uint8, + #[rasn(identifier = "generationTime")] + pub generation_time: Time32, + #[rasn(identifier = "requestHash")] + pub request_hash: HashedId8, + #[rasn(identifier = "firstI")] + pub first_i: Option, + #[rasn(identifier = "nextDlTime")] + pub next_dl_time: Time32, + } + impl RaEeCertAck { + pub fn new( + version: Uint8, + generation_time: Time32, + request_hash: HashedId8, + first_i: Option, + next_dl_time: Time32, + ) -> Self { + Self { + version, + generation_time, + request_hash, + first_i, + next_dl_time, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure is used to create the info file that accompanies a"] + #[doc = " * batch of certificates for download as specified in 8.2.3. It is used when"] + #[doc = " * certificates were generated using the butterfly key expansion mechanism"] + #[doc = " * specified in 9.3. An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * @param version: contains the current version of the structure."] + #[doc = " *"] + #[doc = " * @param generationTime: contains the generation time of RaEeCertInfo."] + #[doc = " *"] + #[doc = " * @param currentI: contains the i-value associated with the batch of"] + #[doc = " * certificates."] + #[doc = " *"] + #[doc = " * @param requestHash: contains the hash of the corresponding"] + #[doc = " * EeRaCertRequestSpdu."] + #[doc = " *"] + #[doc = " * @param nextDlTime: contains the time after which the EE should connect to"] + #[doc = " * the RA to download the certificates."] + #[doc = " *"] + #[doc = " * @param acpcTreeId: shall be present and contain the ACPC Tree Id, if the"] + #[doc = " * certificates were generated using ACPC as specified in 9.5."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct RaEeCertInfo { + #[rasn(value("2"))] + pub version: Uint8, + #[rasn(identifier = "generationTime")] + pub generation_time: Time32, + #[rasn(identifier = "currentI")] + pub current_i: IValue, + #[rasn(identifier = "requestHash")] + pub request_hash: HashedId8, + #[rasn(identifier = "nextDlTime")] + pub next_dl_time: Time32, + #[rasn(identifier = "acpcTreeId")] + pub acpc_tree_id: Option, + } + impl RaEeCertInfo { + pub fn new( + version: Uint8, + generation_time: Time32, + current_i: IValue, + request_hash: HashedId8, + next_dl_time: Time32, + acpc_tree_id: Option, + ) -> Self { + Self { + version, + generation_time, + current_i, + request_hash, + next_dl_time, + acpc_tree_id, + } + } + } +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_dot1_la_ma_interface { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This structure is not used by EEs, so it is defined as NULL for"] + #[doc = " * purposes of this document."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash, Copy)] + #[rasn(delegate)] + pub struct LaMaInterfacePdu(pub ()); +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_dot1_la_ra_interface { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This structure is not used by EEs, so it is defined as NULL for"] + #[doc = " * purposes of this document."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash, Copy)] + #[rasn(delegate)] + pub struct LaRaInterfacePdu(pub ()); +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_dot1_ma_ra_interface { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This structure is not used by EEs, so it is defined as NULL for"] + #[doc = " * purposes of this document."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash, Copy)] + #[rasn(delegate)] + pub struct MaRaInterfacePdu(pub ()); +} +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod ieee1609_dot2_dot1_protocol { + extern crate alloc; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; + pub use super::ieee1609_dot2::{ + Certificate, CertificateId, Ieee1609Dot2Data, SequenceOfCertificate, + SequenceOfPsidGroupPermissions, SignerIdentifier, ToBeSignedCertificate, + VerificationKeyIndicator, + }; + pub use super::ieee1609_dot2_base_types::{ + CrlSeries, EccP256CurvePoint, EccP384CurvePoint, EcdsaP256Signature, EcdsaP384Signature, + GeographicRegion, HashAlgorithm, HashedId3, Psid, PublicEncryptionKey, + PublicVerificationKey, SequenceOfPsid, SequenceOfPsidSsp, Signature, SubjectAssurance, + Uint16, Uint8, ValidityPeriod, + }; + use super::ieee1609_dot2_dot1_aca_ee_interface::AcaEeInterfacePdu; + use super::ieee1609_dot2_dot1_aca_la_interface::AcaLaInterfacePdu; + use super::ieee1609_dot2_dot1_aca_ma_interface::AcaMaInterfacePdu; + use super::ieee1609_dot2_dot1_aca_ra_interface::AcaRaInterfacePdu; + use super::ieee1609_dot2_dot1_acpc::AcpcTreeId; + use super::ieee1609_dot2_dot1_cert_management::CertManagementPdu; + use super::ieee1609_dot2_dot1_eca_ee_interface::EcaEeInterfacePdu; + use super::ieee1609_dot2_dot1_ee_ma_interface::EeMaInterfacePdu; + use super::ieee1609_dot2_dot1_ee_ra_interface::EeRaInterfacePdu; + use super::ieee1609_dot2_dot1_la_ma_interface::LaMaInterfacePdu; + use super::ieee1609_dot2_dot1_la_ra_interface::LaRaInterfacePdu; + use super::ieee1609_dot2_dot1_ma_ra_interface::MaRaInterfacePdu; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[doc = "*"] + #[doc = " * @brief This structure contains a certificate response for consumption by"] + #[doc = " * the EE. In the architecture of this document, although it is created by"] + #[doc = " * the ACA, it is made available to the EE via the RA as described in 8.2."] + #[doc = " *"] + #[doc = " * The ACA creates a certificate response in this form when the"] + #[doc = " * compact unified butterfly key mechanism is being used. If the"] + #[doc = " * RaAcaCertRequest structure was used to communicate between the RA and the"] + #[doc = " * ACA, the RA indicated use of compact unified butterfly keys by setting the"] + #[doc = " * cubk (1) bit in the bkType field in the corresponding RaAcaCertRequest."] + #[doc = " *"] + #[doc = " * The AcaEeCertResponse is encrypted by the ACA using the cocoon"] + #[doc = " * public key for encryption. See 9.3.4.2 for how the ACA derives the cocoon"] + #[doc = " * public key for encryption, using the tbsCert.verifyKeyIndicator field in the"] + #[doc = " * corresponding RaAcaCertRequest as the input cocoon public key for signing"] + #[doc = " * Bt. See 9.3.4.1 for how the EE derives the corresponding cocoon private"] + #[doc = " * key for encryption."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct AcaEeCertResponseCubkSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl AcaEeCertResponseCubkSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "***************************************************************************"] + #[doc = " ACA - EE Interface "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This structure contains a certificate response for consumption by"] + #[doc = " * the EE. In the architecture of this document, although it is created by the"] + #[doc = " * ACA, it is made available to the EE via the RA as described in 8.2."] + #[doc = " *"] + #[doc = " * The ACA creates this response when 1) the compact unified"] + #[doc = " * butterfly key mechanism is not being used (that is, some other flavor of"] + #[doc = " * butterfly key is being used, or butterfly keys are not being used) and 2)"] + #[doc = " * it is not necessary to protect the EE's privacy from the RA, for example,"] + #[doc = " * when the certificate being returned is not a pseudonym certificate."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct AcaEeCertResponsePlainSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl AcaEeCertResponsePlainSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = " Inner type "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum AcaEeCertResponsePrivateSpduContent { + unsecuredData(Opaque), + signedData(SignedData), + encryptedData(EncryptedData), + signedCertificateRequest(Opaque), + #[rasn(extension_addition)] + signedX509CertificateRequest(Opaque), + } + #[doc = "*"] + #[doc = " * @brief This structure contains a certificate response for consumption by"] + #[doc = " * the EE. In the architecture of this document, although it is created by the"] + #[doc = " * ACA, it is made available to the EE via the RA as described in 8.2."] + #[doc = " *"] + #[doc = " * The ACA creates this response when 1) the compact unified"] + #[doc = " * butterfly key mechanism is not being used (that is, some other flavor of"] + #[doc = " * butterfly key is being used, or butterfly keys are not being used) and 2)"] + #[doc = " * it is necessary to protect the EE's privacy from the RA, for example when"] + #[doc = " * the certificate being returned is a pseudonym certificate."] + #[doc = " *"] + #[doc = " * The structure consists of a signed SPDU containing an encrypted"] + #[doc = " * SPDU."] + #[doc = " *"] + #[doc = " * The encrypted SPDU is encrypted with the response"] + #[doc = " * encryption key that was provided to the ACA for that purpose. This key is"] + #[doc = " * determined as follows:"] + #[doc = " * - If the original EeRaCertRequest from the end entity indicated a single"] + #[doc = " * response encryption key, that is, if the additionalParams.encryptionKey"] + #[doc = " * field was present in the request, then the response is encrypted with that"] + #[doc = " * key."] + #[doc = " * - If the original EeRaCertRequest from the end entity indicated a"] + #[doc = " * response encryption key generated with the \"original\" butterfly key"] + #[doc = " * mechanism, that is, the additionalParams.original field was provided in the"] + #[doc = " * request, then the response is encrypted with the cocoon encryption key"] + #[doc = " * derived from additionalParams.original.encryptionKey and"] + #[doc = " * additionalParams.original.encryptionExpansion as specified in 9.3.4.2"] + #[doc = " * and the corresponding decryption private key is derived as specified in"] + #[doc = " * 9.3.4.1."] + #[doc = " * - If the original EeRaCertRequest from the end entity indicated a"] + #[doc = " * response encryption key generated with the \"unified\" butterfly key"] + #[doc = " * mechanism, that is, the additionalParams.unified field was provided in the"] + #[doc = " * request, then the response is encrypted with the cocoon encryption key"] + #[doc = " * derived from tbsCert.verifyKeyIndicator and additionalParams.unified as"] + #[doc = " * specified in 9.3.4.2 and the corresponding decryption private key is"] + #[doc = " * derived as specified in 9.3.4.1."] + #[doc = " *"] + #[doc = " * See 9.3 for more material about butterfly keys."] + #[doc = " *"] + #[doc = " * The resulting Ieee1609Dot2Data of content type encryptedData is"] + #[doc = " * signed by the same ACA certificate that was used to issue the certificate"] + #[doc = " * field in the AcaEeCertResponse. If this structure is signed by a different"] + #[doc = " * ACA certificate, it is invalid. The ACA certificate shall follow the ACA"] + #[doc = " * certificate profile given in 7.7.3.2."] + #[doc = " *"] + #[doc = " * @note:"] + #[doc = " * - Other potential responses to an authorization certificate"] + #[doc = " * request: If the original request indicated the use of \"compact unified\""] + #[doc = " * butterfly key mechanism by including the additionalParams.compactUnified"] + #[doc = " * field, the response shall be a AcaEeCertResponseCubkSpdu, not a"] + #[doc = " * AcaEeCertResponsePrivateSpdu."] + #[doc = " *"] + #[doc = " * - How the ACA obtains the response encryption key: This"] + #[doc = " * document provides the RaAcaCertRequest structure to allow the RA to"] + #[doc = " * indicate whether the original or unified butterfly key mechanism is to be"] + #[doc = " * used via the flags field. The encryption key for encrypting"] + #[doc = " * AcaEeCertResponse is calculated by the indicated method even if the RA"] + #[doc = " * does not use an RaAcaCertRequest as defined in this document to"] + #[doc = " * communicate the certificate request to the ACA."] + #[doc = " *"] + #[doc = " * - Consistency between inner and outer signers, and the IEEE"] + #[doc = " * Std 1609.2 model. This SPDU introduces a new type of validity condition"] + #[doc = " * by requiring that the ACA that signs the outer signed SPDU is also the ACA"] + #[doc = " * that issued the certificate inside the encrypted SPDU. This requires that"] + #[doc = " * to verify the inner \"SPDU\", that is, the certificate, the verifier"] + #[doc = " * needs to store the information from the outer SPDU. This is not a violation"] + #[doc = " * of the IEEE 1609.2 model: Subclause 4.2.2.3 of IEEE Std 1609.2 considers all"] + #[doc = " * operations carried out on received data to be atomic and does not put any"] + #[doc = " * restrictions on the information that is stored between operations. However,"] + #[doc = " * it should be noted that because the IEEE 1609.2 approach enables SPDUs to"] + #[doc = " * be nested within one another as Ieee1609Dot2Data, in principle an"] + #[doc = " * implementation could be built that iterated through the layers of a nested"] + #[doc = " * SPDU within a single call from the invoking application instance. (And it"] + #[doc = " * should also be noted that IEEE Std 1609.2 was consciously designed to"] + #[doc = " * enable this approach: Although the primitives provided in IEEE Std 1609.2"] + #[doc = " * only support the series-of-single-operations approach, an implementation"] + #[doc = " * could layer this \"one-invocation processing\" on top of the IEEE 1609.2"] + #[doc = " * interface as an optimization.) A \"one-invocation processing\" implementation"] + #[doc = " * of that type would have to anticipate situations of coupling between inner"] + #[doc = " * and outer SPDUs like the one created by this AcaEeCertResponsePrivateSpdu,"] + #[doc = " * and allow the invoking certificate management service to check consistency"] + #[doc = " * at the application layer, perhaps by (for example) returning the signing"] + #[doc = " * certificates for all nested signed SPDUs. How this is to be implemented is"] + #[doc = " * implementation specific; this note is intended as a notification of this"] + #[doc = " * potential issue to implementers planning to implement one-invocation"] + #[doc = " * processing."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct AcaEeCertResponsePrivateSpdu { + #[rasn(value("0..=255"), identifier = "protocolVersion")] + pub protocol_version: u8, + pub content: AcaEeCertResponsePrivateSpduContent, + } + impl AcaEeCertResponsePrivateSpdu { + pub fn new(protocol_version: u8, content: AcaEeCertResponsePrivateSpduContent) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send a signed AcaRaCertResponse."] + #[doc = " * For the signature to be valid the signing certificate shall contain a PSID"] + #[doc = " * equal to SecurityMgmtPsid and a corresponding SSP containing the C-OER"] + #[doc = " * encoding of a SecurityMgmtSsp indicating AcaSsp."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct AcaRaCertResponseSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl AcaRaCertResponseSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure defines the SSP for an authorization CA when it is"] + #[doc = " * authorizing SecurityMgmtPsid messages. It has no parameters other than the"] + #[doc = " * version number."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct AcaSsp { + #[rasn(value("2"))] + pub version: Uint8, + } + impl AcaSsp { + pub fn new(version: Uint8) -> Self { + Self { version } + } + } + #[doc = "*"] + #[doc = " * @brief This is a container for ACPC-related SSPs, specifying one SSP for"] + #[doc = " * each role. The only SSP defined in this document is the CamSsp, used in"] + #[doc = " * the CAM certificate that signs a SignedAprvBinaryTree or a"] + #[doc = " * SignedIndividualAprv. The SSP shall be C-OER encoded for inclusion in the"] + #[doc = " * CAM certificate. New versions of the CAM SSP should be handled by"] + #[doc = " * extending this structure rather than by use of a version number in the"] + #[doc = " * CamSsp structure."] + #[doc = " *"] + #[doc = " * The AcpcSsp is associated with the AcpcPsid in the CAM certificate's"] + #[doc = " * appPermissions field."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum AcpcSsp { + cam(CamSsp), + } + #[doc = "*"] + #[doc = " * @brief This is a list of the ACPC Tree IDs for which the containing CAM"] + #[doc = " * certificate is entitled to sign a SignedAprvBinaryTree or a"] + #[doc = " * SignedIndividualAprv. The SSP entitles the certificate holder to sign"] + #[doc = " * either of these structures."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, size("1.."))] + pub struct CamSsp(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send an unsecured"] + #[doc = " * CertificateChain. It is used to create certificate chain files as"] + #[doc = " * specified in 8.4."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct CertificateChainSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl CertificateChainSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send a signed"] + #[doc = " * CertManagementInfoStatus. For the signature to be valid the signing"] + #[doc = " * certificate shall conform to the RA certificate profile given in 7.7.3.9 or"] + #[doc = " * the DC certificate profile given in 7.7.3.10."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct CertificateManagementInformationStatusSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl CertificateManagementInformationStatusSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "***************************************************************************"] + #[doc = " Certificate Management "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send an unsecured CompositeCrl."] + #[doc = " * It is used to create composite CRL files as specified in 8.5."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct CompositeCrlSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl CompositeCrlSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure defines the SSP for a CRL signer when it is"] + #[doc = " * authorizing SecurityMgmtPsid messages. It has no parameters other than the"] + #[doc = " * version number."] + #[doc = " *"] + #[doc = " * @note The SSP for a CRL signer when signing CRLs is associated with"] + #[doc = " * PSID 0x0100 and is defined in IEEE Std 1609.2."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct CrlSignerSsp { + #[rasn(value("2"))] + pub version: Uint8, + } + impl CrlSignerSsp { + pub fn new(version: Uint8) -> Self { + Self { version } + } + } + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send a signed"] + #[doc = " * ToBeSignedCtlSignature. For the signature to be valid, the signing"] + #[doc = " * certificate shall match the elector certificate profile in 7.7.3.7. This"] + #[doc = " * means that the signature is calculated as specified in IEEE Std 1609.2,"] + #[doc = " * with the data input to the hash process consisting of the C-OER encoding"] + #[doc = " * of the tbsData that includes the ToBeSignedCtlSignature."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct CtlSignatureSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl CtlSignatureSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure defines the SSP for a distribution center when it is"] + #[doc = " * authorizing SecurityMgmtPsid messages. It has no parameters other than the"] + #[doc = " * version number."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct DcSsp { + #[rasn(value("2"))] + pub version: Uint8, + } + impl DcSsp { + pub fn new(version: Uint8) -> Self { + Self { version } + } + } + #[doc = "*"] + #[doc = " * @brief This structure defines the SSP for a device configuration manager"] + #[doc = " * when it is authorizing SecurityMgmtPsid messages. It has no parameters"] + #[doc = " * other than the version number."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct DcmSsp { + #[rasn(value("2"))] + pub version: Uint8, + } + impl DcmSsp { + pub fn new(version: Uint8) -> Self { + Self { version } + } + } + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send a signed EcaEeCertResponse."] + #[doc = " * For the signature to be valid, the signing certificate shall contain a PSID"] + #[doc = " * equal to SecurityMgmtPsid and a corresponding SSP containing the C-OER"] + #[doc = " * encoding of a SecurityMgmtSsp indicating EcaSsp."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EcaEeCertResponseSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl EcaEeCertResponseSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure defines the SSP for an enrollment CA when it is"] + #[doc = " * authorizing SecurityMgmtPsid messages. It has no parameters other than the"] + #[doc = " * version number."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct EcaSsp { + #[rasn(value("2"))] + pub version: Uint8, + } + impl EcaSsp { + pub fn new(version: Uint8) -> Self { + Self { version } + } + } + #[doc = "***************************************************************************"] + #[doc = " ECA - EE Interface "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send a signed EeEcaCertRequest,"] + #[doc = " * as follows:"] + #[doc = " * - If eeEcaCertRequest.canonicalId is not present, the EE signs this"] + #[doc = " * structure using the private key corresponding to the"] + #[doc = " * tbsCert.verifyKeyIndicator field of the EeEcaCertRequest."] + #[doc = " * - If eeEcaCertRequest.canonicalId is present, the EE signs this"] + #[doc = " * structure using the canonical private key as specified in 4.1.4.2."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EeEcaCertRequestSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl EeEcaCertRequestSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = " Inner type "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum EeRa1609Dot2AuthenticatedCertRequestSpduContent { + unsecuredData(Opaque), + signedData(SignedData), + encryptedData(EncryptedData), + signedCertificateRequest(Opaque), + #[rasn(extension_addition)] + signedX509CertificateRequest(Opaque), + } + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send a signed then encrypted IEEE"] + #[doc = " * 1609.2 authenticated certificate request. The EE signs this structure"] + #[doc = " * using its enrollment certificate. The enrollment certificate shall conform"] + #[doc = " * to the enrollment certificate profile given in 7.7.3.5. The EE encrypts"] + #[doc = " * the signed structure using the encryptionKey from the RA's certificate."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EeRa1609Dot2AuthenticatedCertRequestSpdu { + #[rasn(value("0..=255"), identifier = "protocolVersion")] + pub protocol_version: u8, + pub content: EeRa1609Dot2AuthenticatedCertRequestSpduContent, + } + impl EeRa1609Dot2AuthenticatedCertRequestSpdu { + pub fn new( + protocol_version: u8, + content: EeRa1609Dot2AuthenticatedCertRequestSpduContent, + ) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "***************************************************************************"] + #[doc = " EE - MA Interface "] + #[doc = "***************************************************************************"] + #[doc = "***************************************************************************"] + #[doc = " EE - RA Interface "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send a signed then encrypted"] + #[doc = " * EeRaCertRequest. It is a choice of the IEEE 1609.2 authenticated"] + #[doc = " * certificate request, which may be any kind of EE-RA certificate request,"] + #[doc = " * and the ITU-T X.509 certificate request, which is required to be an"] + #[doc = " * authorization certificate request."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct EeRaCertRequestSpdu(pub Ieee1609Dot2Data); + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send an unsecured"] + #[doc = " * EeRaDownloadRequest."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EeRaDownloadRequestPlainSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl EeRaDownloadRequestPlainSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = " Inner type "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum EeRaDownloadRequestSpduContent { + unsecuredData(Opaque), + signedData(SignedData), + encryptedData(EncryptedData), + signedCertificateRequest(Opaque), + #[rasn(extension_addition)] + signedX509CertificateRequest(Opaque), + } + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send a signed then encrypted"] + #[doc = " * EeRaDownloadRequest. The EE signs this structure using its enrollment"] + #[doc = " * certificate. The enrollment certificate shall conform to the enrollment"] + #[doc = " * certificate profile given in 7.7.3.5. The EE encrypts the signed"] + #[doc = " * structure using the encryptionKey from the RA's certificate."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EeRaDownloadRequestSpdu { + #[rasn(value("0..=255"), identifier = "protocolVersion")] + pub protocol_version: u8, + pub content: EeRaDownloadRequestSpduContent, + } + impl EeRaDownloadRequestSpdu { + pub fn new(protocol_version: u8, content: EeRaDownloadRequestSpduContent) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = " Inner type "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum EeRaSuccessorEnrollmentCertRequestSpduContent { + unsecuredData(Opaque), + signedData(SignedData), + encryptedData(EncryptedData), + signedCertificateRequest(Opaque), + #[rasn(extension_addition)] + signedX509CertificateRequest(Opaque), + } + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send a signed then encrypted"] + #[doc = " * EeEcaCertRequestSpdu. The EE signs this structure using its enrollment"] + #[doc = " * certificate. The enrollment certificate shall conform to the enrollment"] + #[doc = " * certificate profile given in 7.7.3.5. The EE encrypts the signed"] + #[doc = " * structure using the encryptionKey from the RA's certificate."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EeRaSuccessorEnrollmentCertRequestSpdu { + #[rasn(value("0..=255"), identifier = "protocolVersion")] + pub protocol_version: u8, + pub content: EeRaSuccessorEnrollmentCertRequestSpduContent, + } + impl EeRaSuccessorEnrollmentCertRequestSpdu { + pub fn new( + protocol_version: u8, + content: EeRaSuccessorEnrollmentCertRequestSpduContent, + ) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send a signed then encrypted ITU-T"] + #[doc = " * X.509authenticated certificate request. The EE signs this structure"] + #[doc = " * using its enrollment certificate. The enrollment certificate shall conform"] + #[doc = " * to the enrollment certificate profile given in 7.7.3.6. The EE encrypts"] + #[doc = " * the signed structure using the encryptionKey from the RA's certificate."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct EeRaX509AuthenticatedCertRequestSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl EeRaX509AuthenticatedCertRequestSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure defines the SSP for an end entity when it is"] + #[doc = " * authorizing SecurityMgmtPsid messages. It has no parameters other than the"] + #[doc = " * version number."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct EeSsp { + #[rasn(value("2"))] + pub version: Uint8, + } + impl EeSsp { + pub fn new(version: Uint8) -> Self { + Self { version } + } + } + #[doc = "*"] + #[doc = " * @brief This structure defines the SSP for an elector when it is authorizing"] + #[doc = " * SecurityMgmtPsid messages. It has no parameters other than the version"] + #[doc = " * number."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct ElectorSsp { + #[rasn(value("2"))] + pub version: Uint8, + } + impl ElectorSsp { + pub fn new(version: Uint8) -> Self { + Self { version } + } + } + #[doc = "*"] + #[doc = " * @brief This structure defines the SSP for an intermediate CA when it is"] + #[doc = " * authorizing SecurityMgmtPsid messages. It has no parameters other than the"] + #[doc = " * version number."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct IcaSsp { + #[rasn(value("2"))] + pub version: Uint8, + } + impl IcaSsp { + pub fn new(version: Uint8) -> Self { + Self { version } + } + } + #[doc = "*"] + #[doc = " * @brief This structure defines the SSP for a linkage authority when it is"] + #[doc = " * authorizing SecurityMgmtPsid messages. It has no parameters other than the"] + #[doc = " * version number."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct LaSsp { + #[rasn(value("2"))] + pub version: Uint8, + #[rasn(identifier = "laId")] + pub la_id: Uint16, + } + impl LaSsp { + pub fn new(version: Uint8, la_id: Uint16) -> Self { + Self { version, la_id } + } + } + #[doc = "*"] + #[doc = " * @brief This structure defines the SSP for a location obscurer proxy (LOP)"] + #[doc = " * when it is authorizing SecurityMgmtPsid messages. It has no parameters"] + #[doc = " * other than the version number."] + #[doc = " *"] + #[doc = " * @note The LOP is in the SSP for backward compatibility reasons, and"] + #[doc = " * in practice, in this design the LOP does not have a certificate."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct LopSsp { + #[rasn(value("2"))] + pub version: Uint8, + } + impl LopSsp { + pub fn new(version: Uint8) -> Self { + Self { version } + } + } + #[doc = "*"] + #[doc = " * @brief This structure defines the SSP for a misbehavior authority when it"] + #[doc = " * is authorizing SecurityMgmtPsid messages. Its parameters"] + #[doc = " * indicate the PSIDs associated with the misbehavior that is to be reported"] + #[doc = " * to that MA (see 4.1.5 for further details). The certificate containing"] + #[doc = " * this SSP is the MA Certificate to which an end entity should encrypt"] + #[doc = " * misbehavior reports related to the indicated PSIDs."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct MaSsp { + #[rasn(value("2"))] + pub version: Uint8, + #[rasn(identifier = "relevantPsids")] + pub relevant_psids: SequenceOfPsid, + } + impl MaSsp { + pub fn new(version: Uint8, relevant_psids: SequenceOfPsid) -> Self { + Self { + version, + relevant_psids, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send an unsecured MultiSignedCtl."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct MultiSignedCtlSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl MultiSignedCtlSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure defines the SSP for a policy generator when it is"] + #[doc = " * authorizing SecurityMgmtPsid messages. It has no parameters other than the"] + #[doc = " * version number."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct PgSsp { + #[rasn(value("2"))] + pub version: Uint8, + } + impl PgSsp { + pub fn new(version: Uint8) -> Self { + Self { version } + } + } + #[doc = "***************************************************************************"] + #[doc = " ACA - LA Interface "] + #[doc = "***************************************************************************"] + #[doc = "***************************************************************************"] + #[doc = " ACA - MA Interface "] + #[doc = "***************************************************************************"] + #[doc = "***************************************************************************"] + #[doc = " ACA - RA Interface "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send a signed RaAcaCertRequest."] + #[doc = " * For the signature to be valid the signing certificate shall conform to the"] + #[doc = " * RA certificate profile given in 7.7.3.9, contain a PSID equal to"] + #[doc = " * SecurityMgmtPsid and a corresponding SSP containing the C-OER encoding of a"] + #[doc = " * SecurityMgmtSsp indicating RaSsp. The toBeSigned.certRequestPermissions"] + #[doc = " * field of the RA certificate shall permit the requested permissions in the"] + #[doc = " * raAcaCertRequest.tbsCert.appPermissions field."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct RaAcaCertRequestSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl RaAcaCertRequestSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send a signed RaEeCertAck to"] + #[doc = " * acknowledge the receipt of an EeRaCertRequestSpdu. For the signature to be"] + #[doc = " * valid the signing certificate shall conform to the RA certificate profile"] + #[doc = " * given in 7.7.3.9."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct RaEeCertAckSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl RaEeCertAckSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to create a signed .info file to"] + #[doc = " * be included in a certificate batch zip file as specified in 8.2. This"] + #[doc = " * SPDU is used if the RaEeCertInfo contains an acpcTreeId field. For the"] + #[doc = " * signature to be valid the signing certificate shall conform to the RA"] + #[doc = " * certificate profile given in 7.7.3.9."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct RaEeCertAndAcpcInfoSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl RaEeCertAndAcpcInfoSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to create an unsigned .info file"] + #[doc = " * to be included in a certificate batch zip file as specified in 8.2. This"] + #[doc = " * SPDU is used if the RaEeCertInfo does not contain an acpcTreeId field."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct RaEeCertInfoSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl RaEeCertInfoSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure is the SPDU used to send a signed RaEeCertInfo. For"] + #[doc = " * the signature to be valid the signing certificate shall conform to the RA"] + #[doc = " * certificate profile given in 7.7.3.9."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct RaEeEnrollmentCertAckSpdu { + #[rasn(value("3"), identifier = "protocolVersion")] + pub protocol_version: Uint8, + pub content: Ieee1609Dot2Content, + } + impl RaEeEnrollmentCertAckSpdu { + pub fn new(protocol_version: Uint8, content: Ieee1609Dot2Content) -> Self { + Self { + protocol_version, + content, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure defines the SSP for an RA when it is authorizing"] + #[doc = " * SecurityMgmtPsid messages. It has no parameters other than the version"] + #[doc = " * number."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct RaSsp { + #[rasn(value("2"))] + pub version: Uint8, + } + impl RaSsp { + pub fn new(version: Uint8) -> Self { + Self { version } + } + } + #[doc = "*"] + #[doc = " * @brief This structure defines the SSP for a root CA when it is authorizing"] + #[doc = " * SecurityMgmtPsid messages. It has no parameters other than the version"] + #[doc = " * number."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + #[non_exhaustive] + pub struct RootCaSsp { + #[rasn(value("2"))] + pub version: Uint8, + } + impl RootCaSsp { + pub fn new(version: Uint8) -> Self { + Self { version } + } + } + #[doc = " Inner type "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum ScmsPduContent { + #[rasn(identifier = "aca-ee")] + aca_ee(AcaEeInterfacePdu), + #[rasn(identifier = "aca-la")] + aca_la(AcaLaInterfacePdu), + #[rasn(identifier = "aca-ma")] + aca_ma(AcaMaInterfacePdu), + #[rasn(identifier = "aca-ra")] + aca_ra(AcaRaInterfacePdu), + cert(CertManagementPdu), + #[rasn(identifier = "eca-ee")] + eca_ee(EcaEeInterfacePdu), + #[rasn(identifier = "ee-ma")] + ee_ma(EeMaInterfacePdu), + #[rasn(identifier = "ee-ra")] + ee_ra(EeRaInterfacePdu), + #[rasn(identifier = "la-ma")] + la_ma(LaMaInterfacePdu), + #[rasn(identifier = "la-ra")] + la_ra(LaRaInterfacePdu), + #[rasn(identifier = "ma-ra")] + ma_ra(MaRaInterfacePdu), + } + #[doc = "*"] + #[doc = " * @brief This is the parent structure that encompasses all parent structures"] + #[doc = " * of interfaces defined in the SCMS. An overview of this structure is as"] + #[doc = " * follows:"] + #[doc = " * - aca-ee contains the interface structures defined for interaction"] + #[doc = " * between the ACA and the EE."] + #[doc = " * - aca-la contains the interface structures defined for interaction"] + #[doc = " * between the ACA and the LA."] + #[doc = " * - aca-ma contains the interface structures defined for interaction"] + #[doc = " * between the ACA and the MA."] + #[doc = " * - aca-ra contains the interface structures defined for interaction"] + #[doc = " * between the ACA and the RA."] + #[doc = " * - cert contains the interface structures defined for certificate"] + #[doc = " * management."] + #[doc = " * - eca-ee contains the interface structures defined for interaction"] + #[doc = " * between the ECA and the EE."] + #[doc = " * - ee-ma contains the interface structures defined for interaction"] + #[doc = " * between the EE and the MA."] + #[doc = " * - ee-ra contains the interface structures defined for interaction"] + #[doc = " * between the EE and the RA."] + #[doc = " * - la-ma contains the interface structures defined for interaction"] + #[doc = " * between the LA and the MA."] + #[doc = " * - la-ra contains the interface structures defined for interaction"] + #[doc = " * between the LA and the RA."] + #[doc = " * - ma-ra contains the interface structures defined for interactions"] + #[doc = " * between the MA and the RA."] + #[doc = " *"] + #[doc = " * @param version: contains the current version of the structure."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct ScmsPdu { + #[rasn(value("2"))] + pub version: Uint8, + pub content: ScmsPduContent, + } + impl ScmsPdu { + pub fn new(version: Uint8, content: ScmsPduContent) -> Self { + Self { version, content } + } + } + #[doc = "***************************************************************************"] + #[doc = " Certificate Requests "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This structure defines the all certificate request structures as a"] + #[doc = " * scoped version of the ScmsPdu."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct ScopedCertificateRequest(pub ScmsPdu); + #[doc = "*"] + #[doc = " * @brief This PSID, 0x23, identifies security management activities as"] + #[doc = " * defined in this document."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, value("35"))] + pub struct SecurityMgmtPsid(pub Psid); + #[doc = "***************************************************************************"] + #[doc = " LA - MA Interface "] + #[doc = "***************************************************************************"] + #[doc = "***************************************************************************"] + #[doc = " LA - RA Interface "] + #[doc = "***************************************************************************"] + #[doc = "***************************************************************************"] + #[doc = " MA - RA Interface "] + #[doc = "***************************************************************************"] + #[doc = "***************************************************************************"] + #[doc = " Service Specific Permissions "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This parent structure defines the SSP for SecurityMgmtPsid and"] + #[doc = " * encompasses all SSP structures defined in this document. An overview of"] + #[doc = " * this structure is as follows:"] + #[doc = " *"] + #[doc = " * @note The LOP is in the SSP for backward compatibility reasons,"] + #[doc = " * and in practice, in this design the LOP does not have a certificate."] + #[doc = " *"] + #[doc = " * @param elector: contains the SSP defined for an elector."] + #[doc = " *"] + #[doc = " * @param root: contains the SSP defined for a root CA."] + #[doc = " *"] + #[doc = " * @param pg: contains the SSP defined for a policy generator."] + #[doc = " *"] + #[doc = " * @param ica: contains the SSP defined for an intermediate CA."] + #[doc = " *"] + #[doc = " * @param eca: contains the SSP defined for an enrollment CA."] + #[doc = " *"] + #[doc = " * @param aca: contains the SSP defined for an authorization CA."] + #[doc = " *"] + #[doc = " * @param crl: contains the SSP defined for a CRL signer."] + #[doc = " *"] + #[doc = " * @param dcm: contains the SSP defined for a device configuration manager."] + #[doc = " *"] + #[doc = " * @param la: contains the SSP defined for a linkage authority."] + #[doc = " *"] + #[doc = " * @param lop: contains the SSP defined for a location obscurer proxy."] + #[doc = " *"] + #[doc = " * @param ma: contains the SSP defined for a misbehavior authority."] + #[doc = " *"] + #[doc = " * @param ra: contains the SSP defined for a registration authority."] + #[doc = " *"] + #[doc = " * @param ee: contains the SSP defined for an end entity."] + #[doc = " *"] + #[doc = " * @param dc: contains the SSP defined for a distribution center."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum SecurityMgmtSsp { + elector(ElectorSsp), + root(RootCaSsp), + pg(PgSsp), + ica(IcaSsp), + eca(EcaSsp), + aca(AcaSsp), + crl(CrlSignerSsp), + dcm(DcmSsp), + la(LaSsp), + lop(LopSsp), + ma(MaSsp), + ra(RaSsp), + ee(EeSsp), + #[rasn(extension_addition)] + dc(DcSsp), + } + #[doc = "*"] + #[doc = " * @brief This type is used for clarity of definitions."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, size("1.."))] + pub struct SequenceOfX509Certificate(pub SequenceOf); + #[doc = "*"] + #[doc = " * @brief This structure defines the format of a signed certificate request."] + #[doc = " * An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * The signature is generated on the hash of this structure, obtained"] + #[doc = " * per the rules specified for hashing data objects in 5.3.1 of IEEE Std"] + #[doc = " * 1609.2a-2017, where the parameter Data Input shall be the C-OER"] + #[doc = " * encoding of tbsRequest, and the parameter Signer Identifier Input"] + #[doc = " * depending on whether the request is self-signed or signed using an"] + #[doc = " * enrollment certificate:"] + #[doc = " * - If the request is self-signed, the parameter Signer Identifier"] + #[doc = " * Input shall be the empty string, i.e., a string of length 0."] + #[doc = " * - If the request is signed using an enrollment certificate, the"] + #[doc = " * parameter Signer Identifier Input shall be the signer's enrollment"] + #[doc = " * certificate."] + #[doc = " *"] + #[doc = " * @param hashAlgorithmId: contains the identifier of the hash algorithm used"] + #[doc = " * to calculate the hash of tbsRequest."] + #[doc = " *"] + #[doc = " * @param tbsRequest: contains the certificate request information that is"] + #[doc = " * signed by the recipient."] + #[doc = " *"] + #[doc = " * @param signer: denotes the signing entity's identifier."] + #[doc = " *"] + #[doc = " * @param signature: contains the request sender's signature."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct SignedCertificateRequest { + #[rasn(identifier = "hashAlgorithmId")] + pub hash_algorithm_id: HashAlgorithm, + #[rasn(identifier = "tbsRequest")] + pub tbs_request: ScopedCertificateRequest, + pub signer: SignerIdentifier, + pub signature: Signature, + } + impl SignedCertificateRequest { + pub fn new( + hash_algorithm_id: HashAlgorithm, + tbs_request: ScopedCertificateRequest, + signer: SignerIdentifier, + signature: Signature, + ) -> Self { + Self { + hash_algorithm_id, + tbs_request, + signer, + signature, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure contains a certificate request signed with an ITU-T"] + #[doc = " * X.509 certificate. The only type of certificate request signed with an"] + #[doc = " * ITU-T X.509 certificate supported in this document is an authorization"] + #[doc = " * certificate request. An overview of this structure is as follows:"] + #[doc = " *"] + #[doc = " * The signature is generated on the hash of this structure, obtained"] + #[doc = " * per the rules specified for hashing data objects in 5.3.1 of IEEE Std"] + #[doc = " * 1609.2a-2017, where the parameter Data Input shall be the C-OER"] + #[doc = " * encoding of tbsRequest, and the parameter Signer Identifier Input"] + #[doc = " * shall be the signer's certificate, that is, the ITU-T X.509 certificate"] + #[doc = " * contained in the OCTET STRING indicated by the first X509Certificate in"] + #[doc = " * signer. For example, if the signer is as below, the first 6 bytes are the"] + #[doc = " * ASN.1 encoding overhead, where 80 01 01 is the overhead for signer, and"] + #[doc = " * then 82 01 AC is the overhead introduced by the OCTET STRING encoding for"] + #[doc = " * the first (in this case, the only) X509Certificate; and the first"] + #[doc = " * X509Certificate is contained in the next 428 bytes (30 82 01 ... 00 00 00),"] + #[doc = " * so the parameter Signer Identifier Input shall be '30 82 01 ... 00 00 00'."] + #[doc = " *"] + #[doc = " * An example X509SignerIdentifier with one X509Certificate:"] + #[doc = " *"] + #[doc = " * 80 01 01 82 01 AC 30 82 01 A8 30 82 01 4D A0 03 02 01 02 02 04 90"] + #[doc = " * C5 9D 21 30 0A 06 08 2A 86 48 CE 3D 04 03 02 30 24 31 0A 30 08 06 03 55 04"] + #[doc = " * 06 13 01 00 31 0A 30 08 06 03 55 04 0A 13 01 00 31 0A 30 08 06 03 55 04 03"] + #[doc = " * 13 01 00 30 1E 17 0D 30 30 30 31 30 31 30 30 30 30 30 30 5A 17 0D 30 30 30"] + #[doc = " * 31 30 31 30 30 30 30 30 30 5A 30 24 31 0A 30 08 06 03 55 04 06 13 01 00 31"] + #[doc = " * 0A 30 08 06 03 55 04 0A 13 01 00 31 0A 30 08 06 03 55 04 03 13 01 00 30 59"] + #[doc = " * 30 13 06 07 2A 86 48 CE 3D 02 01 06 08 2A 86 48 CE 3D 03 01 07 03 42 00 00"] + #[doc = " * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"] + #[doc = " * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"] + #[doc = " * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 A3 6D 30 6B 30 0A 06 03 55 1D 0E"] + #[doc = " * 04 03 04 01 00 30 0A 06 03 55 1D 23 04 03 04 01 00 30 0C 06 03 55 1D 13 01"] + #[doc = " * 01 FF 04 02 30 00 30 0E 06 03 55 1D 0F 01 01 FF 04 04 03 02 03 C8 30 0A 06"] + #[doc = " * 03 55 1D 25 04 03 04 01 00 30 0A 06 03 55 1D 1F 04 03 04 01 00 30 0F 06 08"] + #[doc = " * 2B 06 01 05 05 07 01 01 04 03 04 01 00 30 0A 06 03 55 1D 20 04 03 04 01 00"] + #[doc = " * 30 0A 06 08 2A 86 48 CE 3D 04 03 02 03 49 00 00 00 00 00 00 00 00 00 00 00"] + #[doc = " * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"] + #[doc = " * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"] + #[doc = " * 00 00 00 00 00 00 00 00 00 00 00 00"] + #[doc = " *"] + #[doc = " * @param hashAlgorithmId: contains the identifier of the hash algorithm used"] + #[doc = " * inside the binary tree."] + #[doc = " *"] + #[doc = " * @param tbsRequest: contains the certificate request information that is"] + #[doc = " * signed by the recipient."] + #[doc = " *"] + #[doc = " * @param signer: denotes the signing entity's identifier."] + #[doc = " *"] + #[doc = " * @param signature: contains the request sender's signature."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(automatic_tags)] + pub struct SignedX509CertificateRequest { + #[rasn(identifier = "hashAlgorithmId")] + pub hash_algorithm_id: HashAlgorithm, + #[rasn(identifier = "tbsRequest")] + pub tbs_request: ScopedCertificateRequest, + pub signer: X509SignerIdentifier, + pub signature: Signature, + } + impl SignedX509CertificateRequest { + pub fn new( + hash_algorithm_id: HashAlgorithm, + tbs_request: ScopedCertificateRequest, + signer: X509SignerIdentifier, + signature: Signature, + ) -> Self { + Self { + hash_algorithm_id, + tbs_request, + signer, + signature, + } + } + } + #[doc = "*"] + #[doc = " * @brief This structure is used to indicate a SignerIdentifier of type self."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SignerSelf(pub SignerIdentifier); + #[doc = "***************************************************************************"] + #[doc = " Signer Types "] + #[doc = "***************************************************************************"] + #[doc = "*"] + #[doc = " * @brief This structure is used to indicate a SignerIdentifier with a"] + #[doc = " * certificate chain of size 1."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SignerSingleCert(pub SignerIdentifier); + #[doc = "*"] + #[doc = " * @brief This structure is used to indicate an X509SignerIdentifier with a"] + #[doc = " * certificate chain of size 1."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct SignerSingleX509Cert(pub X509SignerIdentifier); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct TestSecurityMgmtSsp(pub SecurityMgmtSsp); + #[doc = "*"] + #[doc = " * @brief This structure is a wrapper for an ITU-T X.509 certificate."] + #[doc = " *"] + #[doc = " * @note ITU-T X.509 certificates are encoded with the ASN.1 DER"] + #[doc = " * rather than the OER used in this document and so cannot be \"directly\""] + #[doc = " * imported into these structures."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct X509Certificate(pub OctetString); + #[doc = "*"] + #[doc = " * @brief This structure identifies an ITU-T X.509 certificate used to sign a"] + #[doc = " * signed data structure. The only data structure currently defined that can"] + #[doc = " * be signed by an ITU-T X.509 certificate is SignedX509CertificateRequest."] + #[doc = " "] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(choice, automatic_tags)] + #[non_exhaustive] + pub enum X509SignerIdentifier { + certificate(SequenceOfX509Certificate), + } + // Type aliases for WITH COMPONENTS constrained types (constraints removed for rasn compat) + pub type Ieee1609Dot2DataSigned = Ieee1609Dot2Data; + pub type Ieee1609Dot2DataUnsecured = Ieee1609Dot2Data; + pub type Ieee1609Dot2DataSymmEncryptedSingleRecipient = Ieee1609Dot2Data; + pub type Ieee1609Dot2DataSignedX509AuthenticatedCertRequest = Ieee1609Dot2Data; + pub type ScmsPduScoped = Ieee1609Dot2Data; +} diff --git a/src/security/sign_service.rs b/src/security/sign_service.rs new file mode 100644 index 0000000..8b23aa4 --- /dev/null +++ b/src/security/sign_service.rs @@ -0,0 +1,302 @@ +//! Sign service — ETSI TS 103 097 message signing. +//! +//! Provides [`SignService`] which wraps [`EcdsaBackend`] and +//! [`CertificateLibrary`] to produce signed `Ieee1609Dot2Data` envelopes. +//! +//! Three message profiles are supported: +//! - CAM (ITS-AID 36) — §7.1.1 +//! - DENM (ITS-AID 37) — §7.1.2 +//! - Other — §7.1.3 + +use rasn::prelude::*; + +use crate::security::certificate::{ + encode_ieee1609_dot2_data, encode_tbs_data, Certificate, OwnCertificate, +}; +use crate::security::certificate_library::CertificateLibrary; +use crate::security::ecdsa_backend::EcdsaBackend; +use crate::security::security_asn::ieee1609_dot2::{ + Ieee1609Dot2Content, Ieee1609Dot2Data, SequenceOfCertificate, SignedData, + SignedDataPayload, SignerIdentifier, ToBeSignedData, +}; +use crate::security::security_asn::ieee1609_dot2::Certificate as AsnCertificate; +use crate::security::security_asn::ieee1609_dot2_base_types::{ + EccP256CurvePoint, EcdsaP256Signature, HashAlgorithm, HashedId8, Psid, + ThreeDLocation, Time64, Elevation, Latitude, Longitude, + NinetyDegreeInt, OneEightyDegreeInt, Opaque, Uint8, Uint16, Uint64, +}; +use crate::security::security_asn::ieee1609_dot2_base_types::Signature as Ieee1609Signature; +use crate::security::security_asn::ieee1609_dot2::HeaderInfo; +use crate::security::sn_sap::{GenerationLocation, SNSignConfirm, SNSignRequest}; +use crate::security::time_service::timestamp_its_microseconds; + +// ─── CAM signer state ──────────────────────────────────────────────────── + +/// Manages the CAM-specific signer selection rule (§7.1.1): +/// certificate is included once per second, otherwise digest. +struct CamSignerState { + last_full_cert_time: f64, + requested_own_certificate: bool, +} + +impl CamSignerState { + fn new() -> Self { + Self { + last_full_cert_time: 0.0, + requested_own_certificate: false, + } + } + + fn choose_signer( + &mut self, + cert: &OwnCertificate, + ) -> SignerIdentifier { + let now = crate::security::time_service::unix_time_secs(); + if now - self.last_full_cert_time > 1.0 || self.requested_own_certificate { + self.last_full_cert_time = now; + self.requested_own_certificate = false; + let asn_cert: AsnCertificate = cert.cert.inner.0.clone(); + SignerIdentifier::certificate(SequenceOfCertificate( + vec![asn_cert].into(), + )) + } else { + let h = cert.as_hashedid8(); + SignerIdentifier::digest(HashedId8(FixedOctetString::from(h))) + } + } +} + +// ─── SignService ───────────────────────────────────────────────────────── + +/// Signing service for ETSI TS 103 097-secured messages. +pub struct SignService { + pub backend: EcdsaBackend, + pub cert_library: CertificateLibrary, + cam_state: CamSignerState, + /// HashedId3 values of unknown ATs to include in `inlineP2pcdRequest`. + pub unknown_ats: Vec<[u8; 3]>, + /// HashedId3 values for which we should embed `requestedCertificate`. + pub requested_ats: Vec<[u8; 3]>, +} + +impl SignService { + pub fn new(backend: EcdsaBackend, cert_library: CertificateLibrary) -> Self { + Self { + backend, + cert_library, + cam_state: CamSignerState::new(), + unknown_ats: Vec::new(), + requested_ats: Vec::new(), + } + } + + /// Route to the correct profile based on ITS-AID. + pub fn sign_request(&mut self, request: &SNSignRequest) -> SNSignConfirm { + match request.its_aid { + 36 => self.sign_cam(request), + 37 => self.sign_denm(request), + _ => self.sign_other(request), + } + } + + /// Find the own certificate that covers the given ITS-AID. + fn get_present_at(&self, its_aid: u64) -> Option<&OwnCertificate> { + for cert in self.cert_library.own_certificates.values() { + if cert.get_list_of_its_aid().contains(&its_aid) { + return Some(cert); + } + } + None + } + + // ── Helper: build the Ieee1609Dot2Data envelope ────────────────────── + + fn build_signed_data( + &self, + payload: &[u8], + header_info: HeaderInfo, + signer: SignerIdentifier, + at: &OwnCertificate, + ) -> Vec { + let inner_data = Ieee1609Dot2Data::new( + Uint8(3), + Ieee1609Dot2Content::unsecuredData(Opaque(payload.to_vec().into())), + ); + + let tbs_data = ToBeSignedData::new( + Box::new(SignedDataPayload { + data: Some(inner_data), + ext_data_hash: None, + omitted: None, + }), + header_info, + ); + + let tbs_bytes = encode_tbs_data(&tbs_data); + let signature = at.sign_message(&self.backend, &tbs_bytes); + + let signed_data = SignedData::new( + HashAlgorithm::sha256, + tbs_data, + signer, + signature, + ); + + let outer = Ieee1609Dot2Data::new( + Uint8(3), + Ieee1609Dot2Content::signedData(signed_data), + ); + encode_ieee1609_dot2_data(&outer) + } + + // ── §7.1.3 generic signed messages ─────────────────────────────────── + + fn sign_other(&self, request: &SNSignRequest) -> SNSignConfirm { + let at = self + .get_present_at(request.its_aid) + .expect("No AT for signing"); + + let h = at.as_hashedid8(); + let signer = SignerIdentifier::digest(HashedId8(FixedOctetString::from(h))); + + let header_info = HeaderInfo::new( + Psid(Integer::from(request.its_aid as i64)), + Some(Time64(Uint64(timestamp_its_microseconds()))), + None, None, None, None, None, None, None, None, None, + ); + + let sec_message = self.build_signed_data(&request.tbs_message, header_info, signer, at); + SNSignConfirm { sec_message } + } + + // ── §7.1.2 DENM ───────────────────────────────────────────────────── + + fn sign_denm(&self, request: &SNSignRequest) -> SNSignConfirm { + let at = self + .get_present_at(request.its_aid) + .expect("No AT for signing DENM"); + + let gen_loc = request + .generation_location + .as_ref() + .expect("DENM requires generation_location"); + + let asn_cert: AsnCertificate = at.cert.inner.0.clone(); + let signer = SignerIdentifier::certificate(SequenceOfCertificate( + vec![asn_cert].into(), + )); + + let header_info = HeaderInfo::new( + Psid(Integer::from(request.its_aid as i64)), + Some(Time64(Uint64(timestamp_its_microseconds()))), + None, + Some(ThreeDLocation { + latitude: Latitude(NinetyDegreeInt(gen_loc.latitude)), + longitude: Longitude(OneEightyDegreeInt(gen_loc.longitude)), + elevation: Elevation(Uint16(gen_loc.elevation)), + }), + None, None, None, None, None, None, None, + ); + + let sec_message = self.build_signed_data(&request.tbs_message, header_info, signer, at); + SNSignConfirm { sec_message } + } + + // ── §7.1.1 CAM ────────────────────────────────────────────────────── + + fn sign_cam(&mut self, request: &SNSignRequest) -> SNSignConfirm { + let at = self + .get_present_at(request.its_aid) + .expect("No AT for signing CAM") + .clone(); + + let signer = self.cam_state.choose_signer(&at); + + // Build P2PCD inline request if needed + let inline_p2pcd = if !self.unknown_ats.is_empty() { + let hashes: Vec<_> = self + .unknown_ats + .drain(..) + .map(|h3| { + crate::security::security_asn::ieee1609_dot2_base_types::HashedId3( + FixedOctetString::from(h3), + ) + }) + .collect(); + Some( + crate::security::security_asn::ieee1609_dot2_base_types::SequenceOfHashedId3( + hashes.into(), + ), + ) + } else { + None + }; + + // Embed requestedCertificate if pending + let requested_cert = if !self.requested_ats.is_empty() { + let h3 = self.requested_ats.remove(0); + self.cert_library + .get_ca_certificate_by_hashedid3(&h3) + .map(|c| c.inner.0.clone()) + } else { + None + }; + + let header_info = HeaderInfo::new( + Psid(Integer::from(request.its_aid as i64)), + Some(Time64(Uint64(timestamp_its_microseconds()))), + None, None, None, None, None, + inline_p2pcd, + requested_cert, + None, None, + ); + + let sec_message = self.build_signed_data(&request.tbs_message, header_info, signer, &at); + SNSignConfirm { sec_message } + } + + // ── P2PCD notification helpers ─────────────────────────────────────── + + /// Record an unknown AT HashedId3 and force own cert inclusion in next CAM. + pub fn notify_unknown_at(&mut self, hashedid8: &[u8; 8]) { + let h3 = [hashedid8[5], hashedid8[6], hashedid8[7]]; + if !self.unknown_ats.contains(&h3) { + self.unknown_ats.push(h3); + } + self.cam_state.requested_own_certificate = true; + } + + /// Process a received `inlineP2pcdRequest`. + pub fn notify_inline_p2pcd_request(&mut self, request_list: &[[u8; 3]]) { + for own in self.cert_library.own_certificates.values() { + let own_h3 = { + let h8 = own.as_hashedid8(); + [h8[5], h8[6], h8[7]] + }; + if request_list.contains(&own_h3) { + self.cam_state.requested_own_certificate = true; + } + } + for h3 in request_list { + if self.cert_library.get_ca_certificate_by_hashedid3(h3).is_some() + && !self.requested_ats.contains(h3) + { + self.requested_ats.push(*h3); + } + } + } + + /// Process a received CA certificate from `requestedCertificate`. + pub fn notify_received_ca_certificate(&mut self, cert: Certificate) { + let h3 = cert.as_hashedid3(); + self.requested_ats.retain(|x| *x != h3); + self.unknown_ats.retain(|x| *x != h3); + self.cert_library + .add_authorization_authority(&self.backend, cert); + } + + /// Add an own certificate. + pub fn add_own_certificate(&mut self, cert: OwnCertificate) { + self.cert_library.add_own_certificate(&self.backend, cert); + } +} diff --git a/src/security/sn_sap.rs b/src/security/sn_sap.rs new file mode 100644 index 0000000..1835a01 --- /dev/null +++ b/src/security/sn_sap.rs @@ -0,0 +1,71 @@ +//! SN-SAP — Security Network Service Access Point. +//! +//! Data types for the SN-SIGN and SN-VERIFY primitives as specified in +//! ETSI TS 102 723-8 V1.1.1 (2016-04). + +/// SN-SIGN.request +#[derive(Debug, Clone)] +pub struct SNSignRequest { + pub tbs_message: Vec, + pub its_aid: u64, + pub permissions: Vec, + /// Optional 3-D location for DENM headerInfo.generationLocation. + pub generation_location: Option, +} + +/// 3-D location embedded in signed message headers. +#[derive(Debug, Clone)] +pub struct GenerationLocation { + pub latitude: i32, + pub longitude: i32, + pub elevation: u16, +} + +/// SN-SIGN.confirm +#[derive(Debug, Clone)] +pub struct SNSignConfirm { + pub sec_message: Vec, +} + +/// SN-VERIFY.request +#[derive(Debug, Clone)] +pub struct SNVerifyRequest { + pub message: Vec, +} + +/// Verification outcome codes (Table 5, §5.2.2.3). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ReportVerify { + Success, + FalseSignature, + InvalidCertificate, + RevokedCertificate, + InconsistentChain, + InvalidTimestamp, + DuplicateMessage, + InvalidMobilityData, + UnsignedMessage, + SignerCertificateNotFound, + UnsupportedSignerIdentifierType, + IncompatibleProtocol, +} + +/// SN-VERIFY.confirm +#[derive(Debug, Clone)] +pub struct SNVerifyConfirm { + pub report: ReportVerify, + pub certificate_id: Vec, + pub its_aid: u64, + pub permissions: Vec, + /// The verified plain-text payload extracted from the signed message. + pub plain_message: Vec, +} + +/// Security profile selector (mirrors the Python SecurityProfile enum). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SecurityProfile { + NoSecurity = 0, + CooperativeAwarenessMessage = 1, + DecentralizedEnvironmentalNotificationMessage = 2, + VruAwarenessMessage = 3, +} diff --git a/src/security/time_service.rs b/src/security/time_service.rs new file mode 100644 index 0000000..f145d2f --- /dev/null +++ b/src/security/time_service.rs @@ -0,0 +1,33 @@ +//! ITS time utilities. +//! +//! The ITS epoch is 2004-01-01 00:00:00 UTC (TAI), which corresponds to +//! Unix timestamp 1072915200. A leap-second offset of 5 s is added to +//! convert from UTC to TAI. + +use std::time::{SystemTime, UNIX_EPOCH}; + +/// Unix timestamp of the ITS epoch (2004-01-01T00:00:00 UTC). +const ITS_EPOCH: u64 = 1_072_915_200; + +/// Leap-second offset UTC → TAI at the ITS epoch. +const ELAPSED_SECONDS: u64 = 5; + +/// Return the current UTC timestamp in seconds (as `f64`). +pub fn unix_time_secs() -> f64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system clock before Unix epoch") + .as_secs_f64() +} + +/// Return the current ITS timestamp in **microseconds** (TAI, ITS epoch). +/// +/// This matches the `Time64` type used in `generationTime` fields inside +/// IEEE 1609.2 headers. +pub fn timestamp_its_microseconds() -> u64 { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system clock before Unix epoch"); + let its_secs = now.as_secs() - ITS_EPOCH + ELAPSED_SECONDS; + its_secs * 1_000_000 + u64::from(now.subsec_micros()) +} diff --git a/src/security/verify_service.rs b/src/security/verify_service.rs new file mode 100644 index 0000000..5233061 --- /dev/null +++ b/src/security/verify_service.rs @@ -0,0 +1,346 @@ +//! Verify service — ETSI TS 103 097 message verification. +//! +//! Decodes a received `Ieee1609Dot2Data` envelope, validates the signer +//! chain, checks per-profile header constraints, and verifies the ECDSA +//! signature. + +use rasn::prelude::*; + +use crate::security::certificate::{decode_ieee1609_dot2_data, encode_tbs_data, Certificate}; +use crate::security::certificate_library::CertificateLibrary; +use crate::security::ecdsa_backend::EcdsaBackend; +use crate::security::security_asn::ieee1609_dot2::{ + Ieee1609Dot2Content, SignerIdentifier, +}; +use crate::security::security_asn::ieee1609_dot2_base_types::PublicVerificationKey; +use crate::security::security_asn::ieee1609_dot2::VerificationKeyIndicator; +use crate::security::sign_service::SignService; +use crate::security::sn_sap::{ReportVerify, SNVerifyConfirm, SNVerifyRequest}; + +/// Message verification service. +pub struct VerifyService<'a> { + pub backend: &'a EcdsaBackend, + pub cert_library: &'a mut CertificateLibrary, + pub sign_service: Option<&'a mut SignService>, +} + +impl<'a> VerifyService<'a> { + pub fn new( + backend: &'a EcdsaBackend, + cert_library: &'a mut CertificateLibrary, + sign_service: Option<&'a mut SignService>, + ) -> Self { + Self { + backend, + cert_library, + sign_service, + } + } +} + +/// Stateless verification function that does not require a mutable `VerifyService`. +/// This can be called from the GN router without borrow issues. +pub fn verify_message( + request: &SNVerifyRequest, + backend: &EcdsaBackend, + cert_library: &mut CertificateLibrary, +) -> (SNVerifyConfirm, Vec) { + let mut events = Vec::new(); + + let data = decode_ieee1609_dot2_data(&request.message); + let signed_data = match &data.content { + Ieee1609Dot2Content::signedData(sd) => sd, + _ => { + return ( + SNVerifyConfirm { + report: ReportVerify::UnsignedMessage, + certificate_id: vec![], + its_aid: 0, + permissions: vec![], + plain_message: vec![], + }, + events, + ); + } + }; + + let tbs_bytes = encode_tbs_data(&signed_data.tbs_data); + let signer = &signed_data.signer; + + // Determine PSID for per-profile checks + let psid = u64::try_from(&signed_data.tbs_data.header_info.psid.0).unwrap_or(0); + + // §7.1.2: DENMs must use 'certificate' signer + if psid == 37 { + if !matches!(signer, SignerIdentifier::certificate(_)) { + return ( + SNVerifyConfirm { + report: ReportVerify::UnsupportedSignerIdentifierType, + certificate_id: vec![], + its_aid: psid, + permissions: vec![], + plain_message: vec![], + }, + events, + ); + } + } + + // Resolve the authorization ticket from the signer + let authorization_ticket: Option = match signer { + SignerIdentifier::certificate(seq) => { + let certs_asn = &seq.0; + if certs_asn.len() != 1 { + return ( + SNVerifyConfirm { + report: ReportVerify::UnsupportedSignerIdentifierType, + certificate_id: vec![], + its_aid: psid, + permissions: vec![], + plain_message: vec![], + }, + events, + ); + } + // Build Certificate wrappers + let cert_vec: Vec = certs_asn + .iter() + .map(|c| { + use crate::security::security_asn::etsi_ts103097_module::EtsiTs103097Certificate; + Certificate::from_asn(EtsiTs103097Certificate(c.clone()), None) + }) + .collect(); + match cert_library.verify_sequence_of_certificates(&cert_vec, backend) { + Some(at) => Some(at), + None => { + // Unknown issuer — notify sign service + let cert_dict = &certs_asn[0]; + let issuer_field = &cert_dict.0.issuer; + match issuer_field { + crate::security::security_asn::ieee1609_dot2::IssuerIdentifier::sha256AndDigest(h) + | crate::security::security_asn::ieee1609_dot2::IssuerIdentifier::sha384AndDigest(h) => { + let mut h8 = [0u8; 8]; + h8.copy_from_slice(h.0.as_ref()); + events.push(VerifyEvent::UnknownAt(h8)); + } + _ => {} + } + return ( + SNVerifyConfirm { + report: ReportVerify::InconsistentChain, + certificate_id: vec![], + its_aid: psid, + permissions: vec![], + plain_message: vec![], + }, + events, + ); + } + } + } + SignerIdentifier::digest(h) => { + let mut key = [0u8; 8]; + key.copy_from_slice(h.0.as_ref()); + match cert_library.get_authorization_ticket_by_hashedid8(&key) { + Some(at) => Some(at.clone()), + None => { + events.push(VerifyEvent::UnknownAt(key)); + return ( + SNVerifyConfirm { + report: ReportVerify::SignerCertificateNotFound, + certificate_id: vec![], + its_aid: psid, + permissions: vec![], + plain_message: vec![], + }, + events, + ); + } + } + } + _ => { + return ( + SNVerifyConfirm { + report: ReportVerify::UnsupportedSignerIdentifierType, + certificate_id: vec![], + its_aid: psid, + permissions: vec![], + plain_message: vec![], + }, + events, + ); + } + }; + + let at = match authorization_ticket { + Some(a) => a, + None => { + return ( + SNVerifyConfirm { + report: ReportVerify::InvalidCertificate, + certificate_id: vec![], + its_aid: psid, + permissions: vec![], + plain_message: vec![], + }, + events, + ); + } + }; + + // Validate: must be an AT with verificationKey + if !at.is_authorization_ticket() { + return ( + SNVerifyConfirm { + report: ReportVerify::InvalidCertificate, + certificate_id: at.as_hashedid8().to_vec(), + its_aid: psid, + permissions: vec![], + plain_message: vec![], + }, + events, + ); + } + + let vk = match &at.tbs().verify_key_indicator { + VerificationKeyIndicator::verificationKey(pk) => pk, + _ => { + return ( + SNVerifyConfirm { + report: ReportVerify::InvalidCertificate, + certificate_id: at.as_hashedid8().to_vec(), + its_aid: psid, + permissions: vec![], + plain_message: vec![], + }, + events, + ); + } + }; + + // §5.2: generationTime must be present + if signed_data.tbs_data.header_info.generation_time.is_none() { + return ( + SNVerifyConfirm { + report: ReportVerify::InvalidTimestamp, + certificate_id: at.as_hashedid8().to_vec(), + its_aid: psid, + permissions: vec![], + plain_message: vec![], + }, + events, + ); + } + + // §5.2: p2pcdLearningRequest and missingCrlIdentifier must be absent + if signed_data.tbs_data.header_info.p2pcd_learning_request.is_some() + || signed_data.tbs_data.header_info.missing_crl_identifier.is_some() + { + return ( + SNVerifyConfirm { + report: ReportVerify::IncompatibleProtocol, + certificate_id: at.as_hashedid8().to_vec(), + its_aid: psid, + permissions: vec![], + plain_message: vec![], + }, + events, + ); + } + + // §7.1.2: DENM-specific header constraints + if psid == 37 { + if signed_data.tbs_data.header_info.generation_location.is_none() { + return ( + SNVerifyConfirm { + report: ReportVerify::IncompatibleProtocol, + certificate_id: at.as_hashedid8().to_vec(), + its_aid: psid, + permissions: vec![], + plain_message: vec![], + }, + events, + ); + } + // Forbidden fields for DENM + if signed_data.tbs_data.header_info.expiry_time.is_some() + || signed_data.tbs_data.header_info.encryption_key.is_some() + || signed_data.tbs_data.header_info.inline_p2pcd_request.is_some() + || signed_data.tbs_data.header_info.requested_certificate.is_some() + { + return ( + SNVerifyConfirm { + report: ReportVerify::IncompatibleProtocol, + certificate_id: at.as_hashedid8().to_vec(), + its_aid: psid, + permissions: vec![], + plain_message: vec![], + }, + events, + ); + } + } + + // Verify the signature + let verified = backend.verify_with_pk(&tbs_bytes, &signed_data.signature, vk); + if !verified { + return ( + SNVerifyConfirm { + report: ReportVerify::FalseSignature, + certificate_id: at.as_hashedid8().to_vec(), + its_aid: psid, + permissions: vec![], + plain_message: vec![], + }, + events, + ); + } + + // Extract the plain message + let plain_message = match &signed_data.tbs_data.payload.data { + Some(inner) => match &inner.content { + Ieee1609Dot2Content::unsecuredData(opaque) => opaque.0.to_vec(), + _ => vec![], + }, + None => vec![], + }; + + // P2PCD events + if let Some(ref req) = signed_data.tbs_data.header_info.inline_p2pcd_request { + let h3s: Vec<[u8; 3]> = req + .0 + .iter() + .map(|h| { + let mut arr = [0u8; 3]; + arr.copy_from_slice(h.0.as_ref()); + arr + }) + .collect(); + events.push(VerifyEvent::InlineP2pcdRequest(h3s)); + } + if let Some(ref cert_asn) = signed_data.tbs_data.header_info.requested_certificate { + use crate::security::security_asn::etsi_ts103097_module::EtsiTs103097Certificate; + let c = Certificate::from_asn(EtsiTs103097Certificate(cert_asn.clone()), None); + events.push(VerifyEvent::ReceivedCaCertificate(c)); + } + + ( + SNVerifyConfirm { + report: ReportVerify::Success, + certificate_id: at.as_hashedid8().to_vec(), + its_aid: psid, + permissions: vec![], + plain_message, + }, + events, + ) +} + +/// Events produced during verification that should be forwarded to the +/// sign service for P2PCD handling. +#[derive(Debug)] +pub enum VerifyEvent { + UnknownAt([u8; 8]), + InlineP2pcdRequest(Vec<[u8; 3]>), + ReceivedCaCertificate(Certificate), +} From dab6527a136378b0933937eb2141ee49baf26b7d Mon Sep 17 00:00:00 2001 From: Jordi Marias I Parella Date: Tue, 7 Apr 2026 10:50:31 +0200 Subject: [PATCH 2/6] CI/CD and tests --- .cargo/config.toml | 3 + .github/workflows/ci.yml | 89 ++++ Cargo.lock | 2 +- Cargo.toml | 26 +- README.md | 31 ++ examples/secured_cam_sender_receiver.rs | 48 +- examples/secured_vam_sender_receiver.rs | 412 ++++++++++++++++++ src/btp/btp_header.rs | 120 +++++ src/btp/router.rs | 208 +++++++++ src/btp/service_access_point.rs | 97 +++++ src/facilities/ca_basic_service/cam_coder.rs | 58 +++ .../denm_coder.rs | 38 ++ .../local_dynamic_map/ldm_service.rs | 301 +++++++++++++ .../local_dynamic_map/ldm_storage.rs | 206 +++++++++ src/facilities/local_dynamic_map/ldm_types.rs | 134 ++++++ src/facilities/location_service.rs | 75 ++++ .../vru_awareness_service/vam_coder.rs | 49 +++ src/geonet/basic_header.rs | 142 ++++++ src/geonet/common_header.rs | 127 ++++++ src/geonet/gbc_extended_header.rs | 48 ++ src/geonet/gn_address.rs | 146 ++++++- src/geonet/guc_extended_header.rs | 56 +++ src/geonet/location_table.rs | 294 ++++++++++++- src/geonet/ls_extended_header.rs | 79 ++++ src/geonet/mib.rs | 139 +++++- src/geonet/position_vector.rs | 148 ++++++- src/geonet/router.rs | 246 +++++++++++ src/geonet/service_access_point.rs | 164 +++++++ src/geonet/tsb_extended_header.rs | 42 ++ src/link_layer/packet_consts.rs | 16 + src/link_layer/raw_link_layer.rs | 66 +++ src/security/certificate.rs | 226 ++++++++++ src/security/certificate_library.rs | 215 +++++++++ src/security/ecdsa_backend.rs | 111 +++++ src/security/sign_service.rs | 191 ++++++++ src/security/sn_sap.rs | 96 ++++ src/security/time_service.rs | 36 ++ src/security/verify_service.rs | 261 +++++++++++ 38 files changed, 4708 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 examples/secured_vam_sender_receiver.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 3c32d25..b075516 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,5 @@ [target.aarch64-unknown-linux-gnu] linker = "aarch64-linux-gnu-gcc" + +[env] +CARGO_ENCODED_RUSTFLAGS = { value = "", force = true } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..37a1dd6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,89 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +env: + CARGO_TERM_COLOR: always + +jobs: + check: + name: Static checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + + - uses: Swatinem/rust-cache@v2 + + - name: Check formatting + run: cargo fmt --all -- --check + + - name: Clippy lints + run: cargo clippy --all-targets -- -D warnings + + test: + name: Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + + - uses: Swatinem/rust-cache@v2 + + - name: Run tests + run: cargo test --all-targets + + build-x86_64: + name: Build x86_64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-unknown-linux-gnu + + - uses: Swatinem/rust-cache@v2 + + - name: Build library + run: cargo build --release --target x86_64-unknown-linux-gnu + + - name: Build examples + run: cargo build --release --examples --target x86_64-unknown-linux-gnu + + build-aarch64: + name: Build aarch64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + targets: aarch64-unknown-linux-gnu + + - uses: Swatinem/rust-cache@v2 + + - name: Install cross-compilation toolchain + run: sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu + + - name: Configure linker for aarch64 + run: | + mkdir -p .cargo + cat >> .cargo/config.toml < SignService { + let cert_dir = Path::new("certs"); + + // ── Load certificates ──────────────────────────────────────────────── + let root_bytes = fs::read(cert_dir.join("root_ca.cert")) + .expect("root_ca.cert not found — run generate_certificate_chain first"); + let aa_bytes = fs::read(cert_dir.join("aa.cert")) + .expect("aa.cert not found — run generate_certificate_chain first"); + + let root_ca = Certificate::from_bytes(&root_bytes, None); + let aa = Certificate::from_bytes(&aa_bytes, Some(root_ca.clone())); + + // Load both ATs — one is "ours", the other is a known peer + let at1_cert_bytes = fs::read(cert_dir.join("at1.cert")).expect("at1.cert not found"); + let at2_cert_bytes = fs::read(cert_dir.join("at2.cert")).expect("at2.cert not found"); + + let at1 = Certificate::from_bytes(&at1_cert_bytes, Some(aa.clone())); + let at2 = Certificate::from_bytes(&at2_cert_bytes, Some(aa.clone())); + + // Load our private key + let own_key_file = if at_index == 1 { "at1.key" } else { "at2.key" }; + let key_bytes = fs::read(cert_dir.join(own_key_file)) + .unwrap_or_else(|_| panic!("{} not found", own_key_file)); + + // ── Create backend and import key ──────────────────────────────────── + let mut backend = EcdsaBackend::new(); + let key_id = backend.import_signing_key(&key_bytes); + + let own_cert = if at_index == 1 { at1.clone() } else { at2.clone() }; + let peer_cert = if at_index == 1 { at2.clone() } else { at1.clone() }; + + // ── Build certificate library ──────────────────────────────────────── + let cert_library = CertificateLibrary::new( + &backend, + vec![root_ca], + vec![aa], + vec![own_cert.clone(), peer_cert], + ); + + // ── Create sign service and add own certificate ────────────────────── + let mut sign_service = SignService::new(backend, cert_library); + let own = OwnCertificate::new(own_cert, key_id); + sign_service.add_own_certificate(own); + + sign_service +} + +fn main() { + // ── Parse arguments ────────────────────────────────────────────────── + let args: Vec = env::args().collect(); + let mut at_index: usize = 1; + let mut iface = "lo".to_string(); + + let mut i = 1; + while i < args.len() { + match args[i].as_str() { + "--at" => { + i += 1; + at_index = args[i].parse::().expect("--at must be 1 or 2"); + assert!(at_index == 1 || at_index == 2, "--at must be 1 or 2"); + } + "--iface" | "-i" => { + i += 1; + iface = args[i].clone(); + } + other => { + // Positional argument: interface name + iface = other.to_string(); + } + } + i += 1; + } + + println!("=== Secured VAM Sender/Receiver (VRU Awareness Service) ==="); + println!("AT index: {}", at_index); + println!("Interface: {}", iface); + + // ── Build security stack ───────────────────────────────────────────── + let sign_service = build_security_stack(at_index); + println!( + "Security stack loaded. Own AT HashedId8: {:02x?}", + sign_service + .cert_library + .own_certificates + .keys() + .next() + .unwrap() + ); + + // Wrap in Arc so it can be shared between threads + let sign_service = Arc::new(Mutex::new(sign_service)); + + // ── Generate a random locally-administered MAC ─────────────────────── + let mac = { + use std::time::{SystemTime, UNIX_EPOCH}; + let seed = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .subsec_nanos() + ^ (at_index as u32 * 0x1234_5678); // different seed per AT + [ + 0x02u8, + (seed >> 24) as u8, + (seed >> 16) as u8, + (seed >> 8) as u8, + seed as u8, + at_index as u8, + ] + }; + println!( + "MAC: {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] + ); + + // ── MIB ────────────────────────────────────────────────────────────── + let mut mib = Mib::new(); + mib.itsGnLocalGnAddr = GNAddress::new(M::GnMulticast, ST::Cyclist, MID::new(mac)); + mib.itsGnBeaconServiceRetransmitTimer = 0; + + // ── Location Service ───────────────────────────────────────────────── + let mut loc_svc = LocationService::new(); + let gn_gps_rx = loc_svc.subscribe(); + let vru_gps_rx = loc_svc.subscribe(); + + // ── Spawn GN router and BTP router ─────────────────────────────────── + let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone(), None, None, None); + let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); + + // ── Wire RawLinkLayer ──────────────────────────────────────────────── + let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::>(); + + // ── Security middleware: TX path ───────────────────────────────────── + // + // Intercept packets from GN router → link layer. + // Sign the payload (CommonHeader + extended header + data) and wrap + // in a secured GN packet with BasicNH::SecuredPacket. + let (secured_ll_tx, secured_ll_rx) = mpsc::channel::>(); + let sign_svc_tx = Arc::clone(&sign_service); + thread::spawn(move || { + while let Ok(packet) = gn_to_ll_rx.recv() { + if packet.len() < 4 { + let _ = secured_ll_tx.send(packet); + continue; + } + let bh_bytes: [u8; 4] = packet[0..4].try_into().unwrap(); + let bh = BasicHeader::decode(bh_bytes); + + match bh.nh { + BasicNH::CommonHeader if packet.len() > 4 => { + let inner_payload = &packet[4..]; + + let request = SNSignRequest { + tbs_message: inner_payload.to_vec(), + its_aid: ITS_AID_VAM, + permissions: vec![], + generation_location: None, + }; + + let sec_message = { + let mut svc = sign_svc_tx.lock().unwrap(); + svc.sign_request(&request).sec_message + }; + + // Build new packet: BasicHeader(nh=SecuredPacket) + sec_message + let mut new_bh = bh; + new_bh.nh = BasicNH::SecuredPacket; + let secured_packet: Vec = new_bh + .encode() + .iter() + .copied() + .chain(sec_message.iter().copied()) + .collect(); + let _ = secured_ll_tx.send(secured_packet); + } + _ => { + // Pass through (e.g. beacons) + let _ = secured_ll_tx.send(packet); + } + } + } + }); + + // The link layer reads from secured_ll_rx (post-signing) + let raw_ll = RawLinkLayer::new(ll_to_gn_tx, secured_ll_rx, &iface, mac); + raw_ll.start(); + + // ── Security middleware: RX path ───────────────────────────────────── + // + // Intercept packets from link layer → GN router. + // If BasicNH::SecuredPacket, verify and extract, then forward + // with BasicNH::CommonHeader. + let gn_h_rx = gn_handle.clone(); + let sign_svc_rx = Arc::clone(&sign_service); + thread::spawn(move || { + while let Ok(packet) = ll_to_gn_rx.recv() { + if packet.len() < 4 { + gn_h_rx.send_incoming_packet(packet); + continue; + } + let bh_bytes: [u8; 4] = packet[0..4].try_into().unwrap(); + let bh = BasicHeader::decode(bh_bytes); + + match bh.nh { + BasicNH::SecuredPacket if packet.len() > 4 => { + let sec_message = &packet[4..]; + let request = SNVerifyRequest { + message: sec_message.to_vec(), + }; + + let (confirm, _events) = { + let mut svc = sign_svc_rx.lock().unwrap(); + let svc = &mut *svc; + let result = verify_message( + &request, + &svc.backend, + &mut svc.cert_library, + ); + // Process VerifyEvents for P2PCD + for event in &result.1 { + match event { + VerifyEvent::UnknownAt(h8) => { + svc.notify_unknown_at(h8); + } + VerifyEvent::InlineP2pcdRequest(h3s) => { + svc.notify_inline_p2pcd_request(h3s); + } + VerifyEvent::ReceivedCaCertificate(cert) => { + svc.notify_received_ca_certificate(cert.clone()); + } + } + } + result + }; + + if confirm.report == ReportVerify::Success { + println!( + "[SEC RX] Verified OK — ITS-AID={}, cert={:02x?}", + confirm.its_aid, + &confirm.certificate_id[..], + ); + // Rebuild the packet: BasicHeader(nh=CommonHeader) + plain_message + let mut new_bh = bh; + new_bh.nh = BasicNH::CommonHeader; + let plain_packet: Vec = new_bh + .encode() + .iter() + .copied() + .chain(confirm.plain_message.iter().copied()) + .collect(); + gn_h_rx.send_incoming_packet(plain_packet); + } else { + eprintln!( + "[SEC RX] Verification failed: {:?}", + confirm.report + ); + } + } + _ => { + // Non-secured packet — forward directly + gn_h_rx.send_incoming_packet(packet); + } + } + } + }); + + // ── GN → BTP ───────────────────────────────────────────────────────── + let btp_h1 = btp_handle.clone(); + thread::spawn(move || { + while let Ok(ind) = gn_to_btp_rx.recv() { + btp_h1.send_gn_data_indication(ind); + } + }); + + // ── BTP → GN ───────────────────────────────────────────────────────── + let gn_h2 = gn_handle.clone(); + thread::spawn(move || { + while let Ok(req) = btp_to_gn_rx.recv() { + gn_h2.send_gn_data_request(req); + } + }); + + // ── LocationService → GN position vector ───────────────────────────── + let gn_h3 = gn_handle.clone(); + thread::spawn(move || { + while let Ok(fix) = gn_gps_rx.recv() { + let mut epv = LongPositionVector::decode([0u8; 24]); + epv.update_from_gps( + fix.latitude, + fix.longitude, + fix.speed_mps, + fix.heading_deg, + fix.pai, + ); + gn_h3.update_position_vector(epv); + } + }); + + // ── VRU Awareness Service ──────────────────────────────────────────── + let station_id = u32::from_be_bytes([mac[2], mac[3], mac[4], mac[5]]); + let device_data = DeviceData { + station_id, + station_type: 2, // cyclist + }; + + let (vru_svc, vam_rx) = VruAwarenessService::new(btp_handle.clone(), device_data); + vru_svc.start(vru_gps_rx); + + // ── Decoded VAM printer ────────────────────────────────────────────── + thread::spawn(move || { + while let Ok(vam) = vam_rx.recv() { + let lat = vam + .vam + .vam_parameters + .basic_container + .reference_position + .latitude + .0 as f64 + / 1e7; + let lon = vam + .vam + .vam_parameters + .basic_container + .reference_position + .longitude + .0 as f64 + / 1e7; + println!( + "[VAM RX] station={:>10} lat={:.5} lon={:.5}", + vam.header.0.station_id.0, lat, lon, + ); + } + }); + + // ── GPS publisher (simulates a VRU GNSS sensor at 10 Hz) ───────────── + thread::sleep(Duration::from_millis(100)); + println!("Publishing GPS fixes @ 10 Hz — Ctrl+C to stop\n"); + + loop { + thread::sleep(Duration::from_millis(100)); + // 41.552°N 2.134°E — Parc Tecnològic del Vallès + loc_svc.publish(GpsFix { + latitude: 41.552, + longitude: 2.134, + altitude_m: 120.0, + speed_mps: 1.5, + heading_deg: 90.0, + pai: true, + }); + } +} diff --git a/src/btp/btp_header.rs b/src/btp/btp_header.rs index b59d3d1..3127303 100644 --- a/src/btp/btp_header.rs +++ b/src/btp/btp_header.rs @@ -120,3 +120,123 @@ impl BTPBHeader { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::btp::service_access_point::BTPDataRequest; + + // ── BTPAHeader ──────────────────────────────────────────────────────── + + #[test] + fn btpa_new_defaults_to_zero() { + let h = BTPAHeader::new(); + assert_eq!(h.destination_port(), 0); + assert_eq!(h.source_port(), 0); + } + + #[test] + fn btpa_encode_decode_roundtrip() { + let h = BTPAHeader { + destination_port: 2001, + source_port: 5000, + }; + let encoded = h.encode(); + let decoded = BTPAHeader::decode(encoded); + assert_eq!(decoded.destination_port(), 2001); + assert_eq!(decoded.source_port(), 5000); + } + + #[test] + fn btpa_encode_big_endian() { + let h = BTPAHeader { + destination_port: 0x07D1, // 2001 + source_port: 0x1388, // 5000 + }; + let bytes = h.encode(); + assert_eq!(bytes, [0x07, 0xD1, 0x13, 0x88]); + } + + #[test] + fn btpa_initialize_with_request() { + let mut req = BTPDataRequest::new(); + req.destination_port = 2001; + req.source_port = 3000; + let h = BTPAHeader::initialize_with_request(&req); + assert_eq!(h.destination_port(), 2001); + assert_eq!(h.source_port(), 3000); + } + + #[test] + fn btpa_max_port_values() { + let h = BTPAHeader { + destination_port: u16::MAX, + source_port: u16::MAX, + }; + let decoded = BTPAHeader::decode(h.encode()); + assert_eq!(decoded.destination_port(), u16::MAX); + assert_eq!(decoded.source_port(), u16::MAX); + } + + // ── BTPBHeader ──────────────────────────────────────────────────────── + + #[test] + fn btpb_new_defaults_to_zero() { + let h = BTPBHeader::new(); + assert_eq!(h.destination_port, 0); + assert_eq!(h.destination_port_info, 0); + } + + #[test] + fn btpb_encode_decode_roundtrip() { + let h = BTPBHeader { + destination_port: 2002, + destination_port_info: 42, + }; + let encoded = h.encode(); + let decoded = BTPBHeader::decode(encoded); + assert_eq!(decoded.destination_port, 2002); + assert_eq!(decoded.destination_port_info, 42); + } + + #[test] + fn btpb_encode_big_endian() { + let h = BTPBHeader { + destination_port: 0x07D2, // 2002 + destination_port_info: 0x002A, // 42 + }; + let bytes = h.encode(); + assert_eq!(bytes, [0x07, 0xD2, 0x00, 0x2A]); + } + + #[test] + fn btpb_initialize_with_request() { + let mut req = BTPDataRequest::new(); + req.destination_port = 2001; + req.destination_port_info = 99; + let h = BTPBHeader::initialize_with_request(&req); + assert_eq!(h.destination_port, 2001); + assert_eq!(h.destination_port_info, 99); + } + + #[test] + fn btpb_cam_port() { + let h = BTPBHeader { + destination_port: 2001, + destination_port_info: 0, + }; + let decoded = BTPBHeader::decode(h.encode()); + assert_eq!(decoded.destination_port, 2001); + assert_eq!(decoded.destination_port_info, 0); + } + + #[test] + fn btpb_denm_port() { + let h = BTPBHeader { + destination_port: 2002, + destination_port_info: 0, + }; + let decoded = BTPBHeader::decode(h.encode()); + assert_eq!(decoded.destination_port, 2002); + } +} diff --git a/src/btp/router.rs b/src/btp/router.rs index da7ff78..2b1d998 100644 --- a/src/btp/router.rs +++ b/src/btp/router.rs @@ -230,3 +230,211 @@ impl Router { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::btp::btp_header::{BTPAHeader, BTPBHeader}; + use crate::geonet::gn_address::{GNAddress, M, MID, ST}; + use crate::geonet::mib::Mib; + use crate::geonet::position_vector::LongPositionVector; + use crate::geonet::service_access_point::{ + Area, CommonNH, CommunicationProfile, GNDataIndication, HeaderSubType, HeaderType, + PacketTransportType, TopoBroadcastHST, TrafficClass, UnspecifiedHST, + }; + use crate::security::sn_sap::SecurityProfile; + use std::sync::mpsc; + use std::time::Duration; + + fn make_mib() -> Mib { + Mib::new() + } + + fn make_gn_data_indication(nh: CommonNH, data: Vec) -> GNDataIndication { + GNDataIndication { + upper_protocol_entity: nh, + packet_transport_type: PacketTransportType { + header_type: HeaderType::Tsb, + header_sub_type: HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop), + }, + source_position_vector: LongPositionVector::decode([0u8; 24]), + traffic_class: TrafficClass { + scf: false, + channel_offload: false, + tc_id: 0, + }, + destination_area: None, + remaining_packet_lifetime: None, + remaining_hop_limit: None, + length: data.len() as u16, + data, + } + } + + #[test] + fn spawn_and_shutdown() { + let mib = make_mib(); + let (handle, _gn_rx) = Router::spawn(mib); + handle.shutdown(); + } + + #[test] + fn btpb_request_produces_gn_request() { + let mib = make_mib(); + let (handle, gn_rx) = Router::spawn(mib); + + let mut req = BTPDataRequest::new(); + req.btp_type = CommonNH::BtpB; + req.destination_port = 2001; + req.destination_port_info = 0; + req.gn_packet_transport_type = PacketTransportType { + header_type: HeaderType::Tsb, + header_sub_type: HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop), + }; + req.data = vec![0xCA, 0xFE]; + req.length = 2; + + handle.send_btp_data_request(req); + + let gn_req = gn_rx.recv_timeout(Duration::from_secs(1)).unwrap(); + // Payload should be 4 (BTP-B header) + 2 (data) = 6 bytes + assert_eq!(gn_req.data.len(), 6); + // First two bytes should be destination port 2001 = 0x07D1 + assert_eq!(gn_req.data[0], 0x07); + assert_eq!(gn_req.data[1], 0xD1); + // Next two bytes — port info = 0 + assert_eq!(gn_req.data[2], 0x00); + assert_eq!(gn_req.data[3], 0x00); + // Payload + assert_eq!(gn_req.data[4], 0xCA); + assert_eq!(gn_req.data[5], 0xFE); + + handle.shutdown(); + } + + #[test] + fn btpa_request_produces_gn_request() { + let mib = make_mib(); + let (handle, gn_rx) = Router::spawn(mib); + + let mut req = BTPDataRequest::new(); + req.btp_type = CommonNH::BtpA; + req.destination_port = 2001; + req.source_port = 3000; + req.gn_packet_transport_type = PacketTransportType { + header_type: HeaderType::GeoUnicast, + header_sub_type: HeaderSubType::Unspecified(UnspecifiedHST::Unspecified), + }; + req.data = vec![0xDE, 0xAD]; + req.length = 2; + + handle.send_btp_data_request(req); + + let gn_req = gn_rx.recv_timeout(Duration::from_secs(1)).unwrap(); + assert_eq!(gn_req.data.len(), 6); + // BTP-A header: dest port (2001) + source port (3000) + let btpa = BTPAHeader::decode(gn_req.data[0..4].try_into().unwrap()); + assert_eq!(btpa.destination_port(), 2001); + assert_eq!(btpa.source_port(), 3000); + + handle.shutdown(); + } + + #[test] + fn btpb_indication_dispatches_to_registered_port() { + let mib = make_mib(); + let (handle, _gn_rx) = Router::spawn(mib); + + // Register a callback on port 2001 + let (port_tx, port_rx) = mpsc::channel(); + handle.register_port(2001, port_tx); + + // Small sleep to let registration take effect + std::thread::sleep(Duration::from_millis(50)); + + // Build a GN indication with BTP-B header for port 2001 + let btpb = BTPBHeader { + destination_port: 2001, + destination_port_info: 0, + }; + let mut payload = btpb.encode().to_vec(); + payload.extend_from_slice(&[0xAA, 0xBB]); + + let gn_ind = make_gn_data_indication(CommonNH::BtpB, payload); + handle.send_gn_data_indication(gn_ind); + + let ind = port_rx.recv_timeout(Duration::from_secs(1)).unwrap(); + assert_eq!(ind.destination_port, 2001); + assert_eq!(ind.data, vec![0xAA, 0xBB]); + + handle.shutdown(); + } + + #[test] + fn btpa_indication_dispatches_to_registered_port() { + let mib = make_mib(); + let (handle, _gn_rx) = Router::spawn(mib); + + let (port_tx, port_rx) = mpsc::channel(); + handle.register_port(2001, port_tx); + std::thread::sleep(Duration::from_millis(50)); + + let btpa = BTPAHeader::decode([ + (2001u16 >> 8) as u8, (2001u16 & 0xFF) as u8, + (5000u16 >> 8) as u8, (5000u16 & 0xFF) as u8, + ]); + let mut payload = btpa.encode().to_vec(); + payload.extend_from_slice(&[0xCC, 0xDD]); + + let gn_ind = make_gn_data_indication(CommonNH::BtpA, payload); + handle.send_gn_data_indication(gn_ind); + + let ind = port_rx.recv_timeout(Duration::from_secs(1)).unwrap(); + assert_eq!(ind.destination_port, 2001); + assert_eq!(ind.source_port, 5000); + assert_eq!(ind.data, vec![0xCC, 0xDD]); + + handle.shutdown(); + } + + #[test] + fn unregister_port_stops_dispatch() { + let mib = make_mib(); + let (handle, _gn_rx) = Router::spawn(mib); + + let (port_tx, port_rx) = mpsc::channel(); + handle.register_port(2001, port_tx); + std::thread::sleep(Duration::from_millis(50)); + + handle.unregister_port(2001); + std::thread::sleep(Duration::from_millis(50)); + + let btpb = BTPBHeader { + destination_port: 2001, + destination_port_info: 0, + }; + let gn_ind = make_gn_data_indication(CommonNH::BtpB, btpb.encode().to_vec()); + handle.send_gn_data_indication(gn_ind); + + // Should NOT receive anything + assert!(port_rx.recv_timeout(Duration::from_millis(200)).is_err()); + + handle.shutdown(); + } + + #[test] + fn indication_with_no_registered_port_does_not_crash() { + let mib = make_mib(); + let (handle, _gn_rx) = Router::spawn(mib); + + let btpb = BTPBHeader { + destination_port: 9999, + destination_port_info: 0, + }; + let gn_ind = make_gn_data_indication(CommonNH::BtpB, btpb.encode().to_vec()); + handle.send_gn_data_indication(gn_ind); + + std::thread::sleep(Duration::from_millis(100)); + handle.shutdown(); + } +} diff --git a/src/btp/service_access_point.rs b/src/btp/service_access_point.rs index 6278d44..b36d7d8 100644 --- a/src/btp/service_access_point.rs +++ b/src/btp/service_access_point.rs @@ -168,3 +168,100 @@ impl BTPDataIndication { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::geonet::service_access_point::{ + CommonNH, GNDataIndication, HeaderSubType, HeaderType, PacketTransportType, + TopoBroadcastHST, TrafficClass, + }; + use crate::geonet::position_vector::LongPositionVector; + + #[test] + fn btp_data_request_default() { + let req = BTPDataRequest::new(); + assert_eq!(req.source_port, 0); + assert_eq!(req.destination_port, 0); + assert_eq!(req.destination_port_info, 0); + assert_eq!(req.gn_max_hop_limit, 1); + assert_eq!(req.length, 0); + assert!(req.data.is_empty()); + assert!(req.gn_max_packet_lifetime.is_none()); + } + + #[test] + fn btp_data_indication_default() { + let ind = BTPDataIndication::new(); + assert_eq!(ind.source_port, 0); + assert_eq!(ind.destination_port, 0); + assert_eq!(ind.length, 0); + assert!(ind.data.is_empty()); + assert!(ind.gn_security_report.is_none()); + } + + #[test] + fn btp_data_indication_set_port() { + let ind = BTPDataIndication::new(); + let updated = ind.set_destination_port_and_info(2001, 42); + assert_eq!(updated.destination_port, 2001); + assert_eq!(updated.destination_port_info, 42); + // Other fields should remain at defaults + assert_eq!(updated.source_port, 0); + } + + #[test] + fn btp_data_indication_from_gn_data_indication() { + // Build a minimal GNDataIndication with 6 bytes payload (4 BTP header + 2 data) + let gn_ind = GNDataIndication { + upper_protocol_entity: CommonNH::BtpB, + packet_transport_type: PacketTransportType { + header_type: HeaderType::Tsb, + header_sub_type: HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop), + }, + source_position_vector: LongPositionVector::decode([0u8; 24]), + traffic_class: TrafficClass { + scf: false, + channel_offload: false, + tc_id: 0, + }, + destination_area: None, + remaining_packet_lifetime: Some(1.0), + remaining_hop_limit: None, + length: 6, + data: vec![0x07, 0xD1, 0x00, 0x00, 0xAA, 0xBB], + }; + + let btp_ind = BTPDataIndication::initialize_with_gn_data_indication(&gn_ind); + // First 4 bytes (BTP header) should be stripped + assert_eq!(btp_ind.data, vec![0xAA, 0xBB]); + assert_eq!(btp_ind.length, 2); + assert_eq!(btp_ind.gn_remaining_packet_lifetime, Some(1.0)); + } + + #[test] + fn btp_data_indication_from_gn_short_payload() { + let gn_ind = GNDataIndication { + upper_protocol_entity: CommonNH::BtpB, + packet_transport_type: PacketTransportType { + header_type: HeaderType::Tsb, + header_sub_type: HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop), + }, + source_position_vector: LongPositionVector::decode([0u8; 24]), + traffic_class: TrafficClass { + scf: false, + channel_offload: false, + tc_id: 0, + }, + destination_area: None, + remaining_packet_lifetime: None, + remaining_hop_limit: None, + length: 2, + data: vec![0x07, 0xD1], // Only 2 bytes — too short for BTP header + }; + + let btp_ind = BTPDataIndication::initialize_with_gn_data_indication(&gn_ind); + assert!(btp_ind.data.is_empty()); + assert_eq!(btp_ind.length, 0); + } +} diff --git a/src/facilities/ca_basic_service/cam_coder.rs b/src/facilities/ca_basic_service/cam_coder.rs index 1c9233a..2025cc8 100644 --- a/src/facilities/ca_basic_service/cam_coder.rs +++ b/src/facilities/ca_basic_service/cam_coder.rs @@ -86,3 +86,61 @@ impl CamCoder { rasn::uper::decode::(bytes).map_err(|e| format!("CAM UPER decode error: {e}")) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn generation_delta_time_from_unix_ms_basic() { + let gdt = generation_delta_time_from_unix_ms(ITS_EPOCH_MS + 1000); + assert_eq!(gdt.0, 1000); + } + + #[test] + fn generation_delta_time_wraps_at_65536() { + let gdt = generation_delta_time_from_unix_ms(ITS_EPOCH_MS + 65_537); + assert_eq!(gdt.0, 1); + } + + #[test] + fn generation_delta_time_before_epoch() { + let gdt = generation_delta_time_from_unix_ms(0); + assert_eq!(gdt.0, 0); + } + + #[test] + fn generation_delta_time_now_nonzero() { + let gdt = generation_delta_time_now(); + // Should be non-zero since we're well after ITS epoch + assert!(gdt.0 > 0 || gdt.0 == 0); // always true, but exercises code path + } + + #[test] + fn cam_header_fields() { + let hdr = cam_header(42); + assert_eq!(hdr.protocol_version.0, 2); + assert_eq!(hdr.message_id.0, 2); + assert_eq!(hdr.station_id.0, 42); + } + + #[test] + fn cam_coder_new() { + let coder = CamCoder::new(); + let coder2 = coder.clone(); + // Just ensure Clone works + let _ = format!("{:?}", coder2); + } + + #[test] + fn cam_coder_default() { + let _coder = CamCoder::default(); + } + + #[test] + fn cam_coder_decode_invalid_bytes() { + let coder = CamCoder::new(); + let result = coder.decode(&[0xFF, 0xFF]); + assert!(result.is_err()); + } +} diff --git a/src/facilities/decentralized_environmental_notification_service/denm_coder.rs b/src/facilities/decentralized_environmental_notification_service/denm_coder.rs index 6ca211c..a7299b6 100644 --- a/src/facilities/decentralized_environmental_notification_service/denm_coder.rs +++ b/src/facilities/decentralized_environmental_notification_service/denm_coder.rs @@ -85,3 +85,41 @@ impl DenmCoder { rasn::uper::decode::(bytes).map_err(|e| format!("DENM UPER decode error: {e}")) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn timestamp_its_now_positive() { + let ts = timestamp_its_now(); + assert!(ts.0 > 0); + } + + #[test] + fn denm_header_fields() { + let hdr = denm_header(99); + assert_eq!(hdr.protocol_version.0, 2); + assert_eq!(hdr.message_id.0, 1); + assert_eq!(hdr.station_id.0, 99); + } + + #[test] + fn denm_coder_new() { + let coder = DenmCoder::new(); + let coder2 = coder.clone(); + let _ = format!("{:?}", coder2); + } + + #[test] + fn denm_coder_default() { + let _coder = DenmCoder::default(); + } + + #[test] + fn denm_coder_decode_invalid_bytes() { + let coder = DenmCoder::new(); + let result = coder.decode(&[0xFF, 0xFF]); + assert!(result.is_err()); + } +} diff --git a/src/facilities/local_dynamic_map/ldm_service.rs b/src/facilities/local_dynamic_map/ldm_service.rs index 6272151..f934a60 100644 --- a/src/facilities/local_dynamic_map/ldm_service.rs +++ b/src/facilities/local_dynamic_map/ldm_service.rs @@ -463,3 +463,304 @@ fn record_to_entry(record: &StoredRecord) -> DataObjectEntry { data_object, } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::facilities::local_dynamic_map::ldm_storage::ItsDataObject; + use crate::facilities::local_dynamic_map::ldm_types::*; + use std::sync::mpsc; + + fn make_service() -> Arc { + let store = Arc::new(RwLock::new(LdmStore::new())); + LdmService::new(store) + } + + fn unknown_data(aid: u32) -> ItsDataObject { + ItsDataObject::Unknown { + its_aid: aid, + raw: vec![0xAA], + } + } + + #[test] + fn register_and_deregister_provider() { + let svc = make_service(); + let resp = svc.register_data_provider(RegisterDataProviderReq { application_id: 36 }); + assert_eq!(resp.result, RegisterDataProviderResult::Accepted); + + let resp = svc.deregister_data_provider(DeregisterDataProviderReq { application_id: 36 }); + assert_eq!(resp.ack, DeregisterDataProviderAck::Accepted); + + // Deregister again should be rejected + let resp = svc.deregister_data_provider(DeregisterDataProviderReq { application_id: 36 }); + assert_eq!(resp.ack, DeregisterDataProviderAck::Rejected); + } + + #[test] + fn register_and_deregister_consumer() { + let svc = make_service(); + let resp = svc.register_data_consumer(RegisterDataConsumerReq { application_id: 36 }); + assert_eq!(resp.result, RegisterDataConsumerResult::Accepted); + + let resp = svc.deregister_data_consumer(DeregisterDataConsumerReq { application_id: 36 }); + assert_eq!(resp.ack, DeregisterDataConsumerAck::Succeed); + + let resp = svc.deregister_data_consumer(DeregisterDataConsumerReq { application_id: 36 }); + assert_eq!(resp.ack, DeregisterDataConsumerAck::Failed); + } + + #[test] + fn add_provider_data() { + let svc = make_service(); + let resp = svc.add_provider_data(AddDataProviderReq { + application_id: 36, + timestamp_its: 1_000_000, + time_validity_s: 3600, + lat_etsi: 415520000, + lon_etsi: 21340000, + altitude_cm: 12000, + data_object: unknown_data(36), + }); + assert_eq!(resp.result, AddDataProviderResult::Succeed); + assert!(resp.record_id.is_some()); + } + + #[test] + fn update_provider_data_existing() { + let svc = make_service(); + let add_resp = svc.add_provider_data(AddDataProviderReq { + application_id: 36, + timestamp_its: 1_000_000, + time_validity_s: 3600, + lat_etsi: 0, + lon_etsi: 0, + altitude_cm: 0, + data_object: unknown_data(36), + }); + let id = add_resp.record_id.unwrap(); + + let upd_resp = svc.update_provider_data(UpdateDataProviderReq { + record_id: id, + timestamp_its: 2_000_000, + time_validity_s: 7200, + lat_etsi: 100, + lon_etsi: 200, + altitude_cm: 300, + data_object: unknown_data(36), + }); + assert_eq!(upd_resp.result, UpdateDataProviderResult::Succeed); + } + + #[test] + fn update_provider_data_nonexistent() { + let svc = make_service(); + let resp = svc.update_provider_data(UpdateDataProviderReq { + record_id: 999, + timestamp_its: 0, + time_validity_s: 0, + lat_etsi: 0, + lon_etsi: 0, + altitude_cm: 0, + data_object: unknown_data(0), + }); + assert_eq!(resp.result, UpdateDataProviderResult::UnknownId); + } + + #[test] + fn delete_provider_data() { + let svc = make_service(); + let add_resp = svc.add_provider_data(AddDataProviderReq { + application_id: 36, + timestamp_its: now_its_ms(), + time_validity_s: 3600, + lat_etsi: 0, + lon_etsi: 0, + altitude_cm: 0, + data_object: unknown_data(36), + }); + let id = add_resp.record_id.unwrap(); + + let del_resp = svc.delete_provider_data(DeleteDataProviderReq { record_id: id }); + assert_eq!(del_resp.result, DeleteDataProviderResult::Succeed); + + let del_resp = svc.delete_provider_data(DeleteDataProviderReq { record_id: id }); + assert_eq!(del_resp.result, DeleteDataProviderResult::Failed); + } + + #[test] + fn request_data_objects_no_filter() { + let svc = make_service(); + svc.add_provider_data(AddDataProviderReq { + application_id: 36, + timestamp_its: now_its_ms(), + time_validity_s: 3600, + lat_etsi: 415520000, + lon_etsi: 21340000, + altitude_cm: 12000, + data_object: unknown_data(36), + }); + svc.add_provider_data(AddDataProviderReq { + application_id: 37, + timestamp_its: now_its_ms(), + time_validity_s: 3600, + lat_etsi: 0, + lon_etsi: 0, + altitude_cm: 0, + data_object: unknown_data(37), + }); + + let resp = svc.request_data_objects(RequestDataObjectsReq { + application_id: 36, + data_object_types: vec![], + filter: None, + order: None, + max_results: None, + }); + assert_eq!(resp.result, RequestedDataObjectsResult::Succeed); + assert_eq!(resp.data_objects.len(), 2); + } + + #[test] + fn request_data_objects_with_type_filter() { + let svc = make_service(); + svc.add_provider_data(AddDataProviderReq { + application_id: 36, + timestamp_its: now_its_ms(), + time_validity_s: 3600, + lat_etsi: 0, + lon_etsi: 0, + altitude_cm: 0, + data_object: unknown_data(36), + }); + svc.add_provider_data(AddDataProviderReq { + application_id: 37, + timestamp_its: now_its_ms(), + time_validity_s: 3600, + lat_etsi: 0, + lon_etsi: 0, + altitude_cm: 0, + data_object: unknown_data(37), + }); + + let resp = svc.request_data_objects(RequestDataObjectsReq { + application_id: 36, + data_object_types: vec![36], + filter: None, + order: None, + max_results: None, + }); + assert_eq!(resp.data_objects.len(), 1); + } + + #[test] + fn request_data_objects_with_filter() { + let svc = make_service(); + svc.add_provider_data(AddDataProviderReq { + application_id: 36, + timestamp_its: now_its_ms(), + time_validity_s: 3600, + lat_etsi: 415520000, + lon_etsi: 21340000, + altitude_cm: 0, + data_object: unknown_data(36), + }); + svc.add_provider_data(AddDataProviderReq { + application_id: 36, + timestamp_its: now_its_ms(), + time_validity_s: 3600, + lat_etsi: 400000000, + lon_etsi: 21340000, + altitude_cm: 0, + data_object: unknown_data(36), + }); + + let resp = svc.request_data_objects(RequestDataObjectsReq { + application_id: 36, + data_object_types: vec![], + filter: Some(Filter { + stmt1: FilterStatement { + attribute: FilterAttribute::Latitude, + operator: ComparisonOperator::GreaterThan, + ref_value: 410000000, + }, + logical: None, + stmt2: None, + }), + order: None, + max_results: None, + }); + assert_eq!(resp.data_objects.len(), 1); + } + + #[test] + fn request_data_objects_max_results() { + let svc = make_service(); + for _ in 0..5 { + svc.add_provider_data(AddDataProviderReq { + application_id: 36, + timestamp_its: now_its_ms(), + time_validity_s: 3600, + lat_etsi: 0, + lon_etsi: 0, + altitude_cm: 0, + data_object: unknown_data(36), + }); + } + + let resp = svc.request_data_objects(RequestDataObjectsReq { + application_id: 36, + data_object_types: vec![], + filter: None, + order: None, + max_results: Some(3), + }); + assert_eq!(resp.data_objects.len(), 3); + } + + #[test] + fn subscribe_and_unsubscribe() { + let svc = make_service(); + let (tx, _rx) = mpsc::channel(); + let resp = svc.subscribe_data_consumer( + SubscribeDataObjectsReq { + application_id: 36, + data_object_types: vec![], + filter: None, + notify_interval_ms: 1000, + max_results: None, + }, + tx, + ); + assert_eq!(resp.result, SubscribeDataObjectsResult::Successful); + let sub_id = resp.subscription_id.unwrap(); + + let unsub_resp = + svc.unsubscribe_data_consumer(UnsubscribeDataConsumerReq { subscription_id: sub_id }); + assert_eq!(unsub_resp.ack, UnsubscribeDataConsumerAck::Succeed); + + // Unsubscribe again should fail + let unsub_resp = + svc.unsubscribe_data_consumer(UnsubscribeDataConsumerReq { subscription_id: sub_id }); + assert_eq!(unsub_resp.ack, UnsubscribeDataConsumerAck::Failed); + } + + #[test] + fn fire_subscriptions_prunes_dead() { + let svc = make_service(); + let (tx, rx) = mpsc::channel(); + svc.subscribe_data_consumer( + SubscribeDataObjectsReq { + application_id: 36, + data_object_types: vec![], + filter: None, + notify_interval_ms: 0, // fire immediately + max_results: None, + }, + tx, + ); + drop(rx); // drop receiver + svc.fire_subscriptions(); // should prune + assert_eq!(svc.subscriptions.lock().unwrap().len(), 0); + } +} diff --git a/src/facilities/local_dynamic_map/ldm_storage.rs b/src/facilities/local_dynamic_map/ldm_storage.rs index d813317..d776558 100644 --- a/src/facilities/local_dynamic_map/ldm_storage.rs +++ b/src/facilities/local_dynamic_map/ldm_storage.rs @@ -234,3 +234,209 @@ impl Default for LdmStore { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn store_new_empty() { + let store = LdmStore::new(); + assert!(store.is_empty()); + assert_eq!(store.len(), 0); + } + + #[test] + fn store_default() { + let store = LdmStore::default(); + assert!(store.is_empty()); + } + + #[test] + fn store_insert_returns_ids() { + let mut store = LdmStore::new(); + let id1 = store.insert( + 36, + 1_000_000, + 60, + 415520000, + 21340000, + 12000, + ItsDataObject::Unknown { + its_aid: 36, + raw: vec![0xCA], + }, + ); + let id2 = store.insert( + 37, + 1_000_001, + 60, + 415520000, + 21340000, + 12000, + ItsDataObject::Unknown { + its_aid: 37, + raw: vec![0xDE], + }, + ); + assert_eq!(id1, 1); + assert_eq!(id2, 2); + assert_eq!(store.len(), 2); + } + + #[test] + fn store_update_existing() { + let mut store = LdmStore::new(); + let id = store.insert( + 36, + 1_000_000, + 60, + 0, + 0, + 0, + ItsDataObject::Unknown { + its_aid: 36, + raw: vec![], + }, + ); + let ok = store.update( + id, + 2_000_000, + 120, + 100, + 200, + 300, + ItsDataObject::Unknown { + its_aid: 36, + raw: vec![1], + }, + ); + assert!(ok); + let rec = store.iter().find(|r| r.id == id).unwrap(); + assert_eq!(rec.timestamp_its_ms, 2_000_000); + assert_eq!(rec.time_validity_s, 120); + } + + #[test] + fn store_update_nonexistent() { + let mut store = LdmStore::new(); + let ok = store.update( + 999, + 0, + 0, + 0, + 0, + 0, + ItsDataObject::Unknown { + its_aid: 0, + raw: vec![], + }, + ); + assert!(!ok); + } + + #[test] + fn store_remove() { + let mut store = LdmStore::new(); + let id = store.insert( + 36, + 0, + 60, + 0, + 0, + 0, + ItsDataObject::Unknown { + its_aid: 36, + raw: vec![], + }, + ); + assert!(store.remove(id)); + assert!(store.is_empty()); + assert!(!store.remove(id)); + } + + #[test] + fn store_remove_expired() { + let mut store = LdmStore::new(); + // Insert a record that expired long ago + store.insert( + 36, + 0, // timestamp = epoch + 1, // validity = 1 second + 0, + 0, + 0, + ItsDataObject::Unknown { + its_aid: 36, + raw: vec![], + }, + ); + let removed = store.remove_expired(); + assert_eq!(removed, 1); + assert!(store.is_empty()); + } + + #[test] + fn store_remove_out_of_area() { + let mut store = LdmStore::new(); + // Insert a record at Barcelona (lat 41.552, lon 2.134) + store.insert( + 36, + now_its_ms(), + 3600, + 415520000, + 21340000, + 0, + ItsDataObject::Unknown { + its_aid: 36, + raw: vec![], + }, + ); + // Remove everything outside 1m of Madrid (40.4168, -3.7038) + let removed = store.remove_out_of_area(404168000, -37038000, 1.0); + assert_eq!(removed, 1); + } + + #[test] + fn store_iter() { + let mut store = LdmStore::new(); + store.insert( + 36, + 0, + 3600, + 0, + 0, + 0, + ItsDataObject::Unknown { + its_aid: 36, + raw: vec![], + }, + ); + store.insert( + 37, + 0, + 3600, + 0, + 0, + 0, + ItsDataObject::Unknown { + its_aid: 37, + raw: vec![], + }, + ); + let count = store.iter().count(); + assert_eq!(count, 2); + } + + #[test] + fn its_data_object_its_aid() { + assert_eq!( + ItsDataObject::Unknown { + its_aid: 99, + raw: vec![] + } + .its_aid(), + 99 + ); + } +} diff --git a/src/facilities/local_dynamic_map/ldm_types.rs b/src/facilities/local_dynamic_map/ldm_types.rs index f75dd9e..a76047e 100644 --- a/src/facilities/local_dynamic_map/ldm_types.rs +++ b/src/facilities/local_dynamic_map/ldm_types.rs @@ -369,3 +369,137 @@ pub struct UnsubscribeDataConsumerReq { pub struct UnsubscribeDataConsumerResp { pub ack: UnsubscribeDataConsumerAck, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn register_data_provider_result_eq() { + assert_eq!( + RegisterDataProviderResult::Accepted, + RegisterDataProviderResult::Accepted + ); + assert_ne!( + RegisterDataProviderResult::Accepted, + RegisterDataProviderResult::Rejected + ); + } + + #[test] + fn add_data_provider_result_variants() { + assert_eq!(AddDataProviderResult::Succeed, AddDataProviderResult::Succeed); + assert_ne!(AddDataProviderResult::Succeed, AddDataProviderResult::Failed); + } + + #[test] + fn update_data_provider_result_variants() { + assert_eq!( + UpdateDataProviderResult::Succeed, + UpdateDataProviderResult::Succeed + ); + assert_ne!( + UpdateDataProviderResult::Succeed, + UpdateDataProviderResult::UnknownId + ); + assert_ne!( + UpdateDataProviderResult::Succeed, + UpdateDataProviderResult::InconsistentType + ); + } + + #[test] + fn comparison_operator_eq() { + assert_eq!(ComparisonOperator::Equal, ComparisonOperator::Equal); + assert_ne!(ComparisonOperator::Equal, ComparisonOperator::NotEqual); + } + + #[test] + fn logical_operator_eq() { + assert_eq!(LogicalOperator::And, LogicalOperator::And); + assert_ne!(LogicalOperator::And, LogicalOperator::Or); + } + + #[test] + fn filter_attribute_eq() { + assert_eq!(FilterAttribute::StationType, FilterAttribute::StationType); + assert_ne!(FilterAttribute::StationType, FilterAttribute::StationId); + } + + #[test] + fn ordering_direction_eq() { + assert_eq!(OrderingDirection::Ascending, OrderingDirection::Ascending); + assert_ne!( + OrderingDirection::Ascending, + OrderingDirection::Descending + ); + } + + #[test] + fn filter_statement_construction() { + let stmt = FilterStatement { + attribute: FilterAttribute::Speed, + operator: ComparisonOperator::GreaterThan, + ref_value: 100, + }; + assert_eq!(stmt.attribute, FilterAttribute::Speed); + } + + #[test] + fn filter_single_statement() { + let f = Filter { + stmt1: FilterStatement { + attribute: FilterAttribute::Latitude, + operator: ComparisonOperator::GreaterThanOrEqual, + ref_value: 415000000, + }, + logical: None, + stmt2: None, + }; + assert!(f.logical.is_none()); + assert!(f.stmt2.is_none()); + } + + #[test] + fn filter_compound() { + let f = Filter { + stmt1: FilterStatement { + attribute: FilterAttribute::Latitude, + operator: ComparisonOperator::GreaterThanOrEqual, + ref_value: 415000000, + }, + logical: Some(LogicalOperator::And), + stmt2: Some(FilterStatement { + attribute: FilterAttribute::Longitude, + operator: ComparisonOperator::LessThan, + ref_value: 30000000, + }), + }; + assert!(f.logical.is_some()); + assert!(f.stmt2.is_some()); + } + + #[test] + fn subscribe_data_objects_result_variants() { + assert_eq!( + SubscribeDataObjectsResult::Successful, + SubscribeDataObjectsResult::Successful + ); + assert_ne!( + SubscribeDataObjectsResult::Successful, + SubscribeDataObjectsResult::InvalidItsAid + ); + } + + #[test] + fn requested_data_objects_result_variants() { + assert_eq!( + RequestedDataObjectsResult::Succeed, + RequestedDataObjectsResult::Succeed + ); + assert_ne!( + RequestedDataObjectsResult::Succeed, + RequestedDataObjectsResult::InvalidFilter + ); + } +} diff --git a/src/facilities/location_service.rs b/src/facilities/location_service.rs index 839a3c9..41db8f1 100644 --- a/src/facilities/location_service.rs +++ b/src/facilities/location_service.rs @@ -132,3 +132,78 @@ impl Default for LocationService { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_has_no_subscribers() { + let svc = LocationService::new(); + assert_eq!(svc.subscriber_count(), 0); + } + + #[test] + fn default_same_as_new() { + let svc = LocationService::default(); + assert_eq!(svc.subscriber_count(), 0); + } + + #[test] + fn subscribe_increases_count() { + let mut svc = LocationService::new(); + let _rx1 = svc.subscribe(); + assert_eq!(svc.subscriber_count(), 1); + let _rx2 = svc.subscribe(); + assert_eq!(svc.subscriber_count(), 2); + } + + #[test] + fn publish_delivers_to_subscribers() { + let mut svc = LocationService::new(); + let rx = svc.subscribe(); + let fix = GpsFix { + latitude: 41.552, + longitude: 2.134, + altitude_m: 120.0, + speed_mps: 10.0, + heading_deg: 90.0, + pai: true, + }; + svc.publish(fix); + let received = rx.recv().unwrap(); + assert!((received.latitude - 41.552).abs() < 1e-6); + assert!((received.longitude - 2.134).abs() < 1e-6); + assert!(received.pai); + } + + #[test] + fn publish_to_multiple_subscribers() { + let mut svc = LocationService::new(); + let rx1 = svc.subscribe(); + let rx2 = svc.subscribe(); + let fix = GpsFix::default(); + svc.publish(fix); + assert!(rx1.recv().is_ok()); + assert!(rx2.recv().is_ok()); + } + + #[test] + fn dead_receiver_pruned_on_publish() { + let mut svc = LocationService::new(); + let rx = svc.subscribe(); + assert_eq!(svc.subscriber_count(), 1); + drop(rx); + svc.publish(GpsFix::default()); + assert_eq!(svc.subscriber_count(), 0); + } + + #[test] + fn gps_fix_default() { + let fix = GpsFix::default(); + assert_eq!(fix.latitude, 0.0); + assert_eq!(fix.longitude, 0.0); + assert_eq!(fix.speed_mps, 0.0); + assert!(!fix.pai); + } +} diff --git a/src/facilities/vru_awareness_service/vam_coder.rs b/src/facilities/vru_awareness_service/vam_coder.rs index 2e1c786..b17a09e 100644 --- a/src/facilities/vru_awareness_service/vam_coder.rs +++ b/src/facilities/vru_awareness_service/vam_coder.rs @@ -93,3 +93,52 @@ impl VamCoder { rasn::uper::decode::(bytes).map_err(|e| format!("VAM UPER decode error: {e}")) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn generation_delta_time_from_unix_ms_basic() { + let gdt = generation_delta_time_from_unix_ms(ITS_EPOCH_MS + 2000); + assert_eq!(gdt.0, 2000); + } + + #[test] + fn generation_delta_time_wraps() { + let gdt = generation_delta_time_from_unix_ms(ITS_EPOCH_MS + 65_536); + assert_eq!(gdt.0, 0); + } + + #[test] + fn generation_delta_time_now_runs() { + let _ = generation_delta_time_now(); + } + + #[test] + fn vam_header_fields() { + let hdr = vam_header(123); + assert_eq!(hdr.0.protocol_version.0, 3); + assert_eq!(hdr.0.message_id.0, 16); + assert_eq!(hdr.0.station_id.0, 123); + } + + #[test] + fn vam_coder_new() { + let coder = VamCoder::new(); + let coder2 = coder.clone(); + let _ = format!("{:?}", coder2); + } + + #[test] + fn vam_coder_default() { + let _coder = VamCoder::default(); + } + + #[test] + fn vam_coder_decode_invalid_bytes() { + let coder = VamCoder::new(); + let result = coder.decode(&[0xFF, 0xFF]); + assert!(result.is_err()); + } +} diff --git a/src/geonet/basic_header.rs b/src/geonet/basic_header.rs index 57e43ee..d1031ed 100644 --- a/src/geonet/basic_header.rs +++ b/src/geonet/basic_header.rs @@ -186,3 +186,145 @@ impl BasicHeader { ] } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::geonet::mib::Mib; + + // ── BasicNH ─────────────────────────────────────────────────────── + + #[test] + fn basic_nh_encode_decode_roundtrip() { + for (nh, val) in [ + (BasicNH::Any, 0u8), + (BasicNH::CommonHeader, 1), + (BasicNH::SecuredPacket, 2), + ] { + assert_eq!(nh.encode(), val); + assert_eq!(BasicNH::decode(val).encode(), val); + } + } + + #[test] + #[should_panic(expected = "Invalid BasicNH Value")] + fn basic_nh_decode_invalid() { + BasicNH::decode(99); + } + + // ── LTBase ──────────────────────────────────────────────────────── + + #[test] + fn lt_base_encode_decode_roundtrip() { + for i in 0..4u8 { + assert_eq!(LTBase::decode(i).encode(), i); + } + } + + #[test] + #[should_panic(expected = "Invalid LTBase Value")] + fn lt_base_decode_invalid() { + LTBase::decode(5); + } + + // ── LT ──────────────────────────────────────────────────────────── + + #[test] + fn lt_from_milliseconds_50ms_base() { + let lt = LT::start_in_milliseconds(250); + assert_eq!(lt.get_value_in_milliseconds(), 250); + } + + #[test] + fn lt_from_milliseconds_1s_base() { + let lt = LT::start_in_milliseconds(3000); + assert_eq!(lt.get_value_in_milliseconds(), 3000); + } + + #[test] + fn lt_from_milliseconds_10s_base() { + let lt = LT::start_in_milliseconds(30000); + assert_eq!(lt.get_value_in_milliseconds(), 30000); + } + + #[test] + fn lt_from_milliseconds_100s_base() { + let lt = LT::start_in_milliseconds(600000); + assert_eq!(lt.get_value_in_milliseconds(), 600000); + } + + #[test] + fn lt_from_seconds() { + let lt = LT::start_in_seconds(60); + assert_eq!(lt.get_value_in_seconds(), 60); + assert_eq!(lt.get_value_in_milliseconds(), 60000); + } + + #[test] + fn lt_encode_decode_roundtrip() { + let lt = LT::start_in_milliseconds(3000); + let encoded = lt.encode(); + let decoded = LT::decode(encoded); + assert_eq!(decoded.get_value_in_milliseconds(), 3000); + } + + #[test] + #[should_panic(expected = "Invalid LT Value")] + fn lt_too_small() { + LT::start_in_milliseconds(10); + } + + // ── BasicHeader ─────────────────────────────────────────────────── + + #[test] + fn basic_header_encode_decode_roundtrip() { + let mib = Mib::new(); + let bh = BasicHeader::initialize_with_mib(&mib); + let encoded = bh.encode(); + let decoded = BasicHeader::decode(encoded); + assert_eq!(decoded.version, 1); + assert_eq!(decoded.rhl, mib.itsGnDefaultHopLimit); + } + + #[test] + fn basic_header_version_field() { + let mib = Mib::new(); + let bh = BasicHeader::initialize_with_mib(&mib); + let bytes = bh.encode(); + assert_eq!(bytes[0] >> 4, 1); // protocol version 1 + } + + #[test] + fn basic_header_set_nh() { + let mib = Mib::new(); + let bh = BasicHeader::initialize_with_mib(&mib) + .set_nh(BasicNH::SecuredPacket); + assert_eq!(bh.nh.encode(), 2); + } + + #[test] + fn basic_header_set_rhl() { + let mib = Mib::new(); + let bh = BasicHeader::initialize_with_mib(&mib).set_rhl(5); + assert_eq!(bh.rhl, 5); + } + + #[test] + fn basic_header_with_lifetime_and_rhl() { + let mib = Mib::new(); + let bh = BasicHeader::initialize_with_mib_request_and_rhl(&mib, Some(3.0), 7); + assert_eq!(bh.rhl, 7); + assert_eq!(bh.lt.get_value_in_milliseconds(), 3000); + } + + #[test] + fn basic_header_with_default_lifetime() { + let mib = Mib::new(); + let bh = BasicHeader::initialize_with_mib_request_and_rhl(&mib, None, 7); + assert_eq!(bh.rhl, 7); + assert_eq!( + bh.lt.get_value_in_milliseconds(), + mib.itsGnDefaultPacketLifetime as u32 * 1000 + ); + } +} diff --git a/src/geonet/common_header.rs b/src/geonet/common_header.rs index dab6de4..df66545 100644 --- a/src/geonet/common_header.rs +++ b/src/geonet/common_header.rs @@ -96,3 +96,130 @@ impl PartialEq for CommonHeader { && self.reserved2 == other.reserved2 } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::geonet::mib::Mib; + use crate::geonet::service_access_point::{ + CommonNH, GNDataRequest, HeaderSubType, HeaderType, PacketTransportType, + TopoBroadcastHST, GeoBroadcastHST, TrafficClass, Area, CommunicationProfile, + }; + use crate::geonet::gn_address::{GNAddress, M, MID, ST}; + use crate::security::sn_sap::SecurityProfile; + + fn make_gn_request() -> GNDataRequest { + GNDataRequest { + upper_protocol_entity: CommonNH::BtpB, + packet_transport_type: PacketTransportType { + header_type: HeaderType::Tsb, + header_sub_type: HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop), + }, + communication_profile: CommunicationProfile::Unspecified, + traffic_class: TrafficClass { + scf: true, + channel_offload: false, + tc_id: 2, + }, + security_profile: SecurityProfile::NoSecurity, + its_aid: 36, + security_permissions: vec![], + max_hop_limit: 1, + max_packet_lifetime: None, + destination: None, + length: 100, + data: vec![0u8; 100], + area: Area { + latitude: 0, + longitude: 0, + a: 0, + b: 0, + angle: 0, + }, + } + } + + #[test] + fn common_header_encode_decode_roundtrip() { + let mib = Mib::new(); + let req = make_gn_request(); + let ch = CommonHeader::initialize_with_request(&req, &mib); + let encoded = ch.encode(); + let decoded = CommonHeader::decode(encoded); + assert_eq!(ch, decoded); + } + + #[test] + fn common_header_fields() { + let mib = Mib::new(); + let req = make_gn_request(); + let ch = CommonHeader::initialize_with_request(&req, &mib); + assert_eq!(ch.nh, CommonNH::BtpB); + assert_eq!(ch.ht, HeaderType::Tsb); + assert_eq!(ch.pl, 100); + assert_eq!(ch.mhl, 1); + assert!(ch.tc.scf); + assert!(!ch.tc.channel_offload); + assert_eq!(ch.tc.tc_id, 2); + } + + #[test] + fn common_header_beacon() { + let mib = Mib::new(); + let ch = CommonHeader::initialize_beacon(&mib); + assert_eq!(ch.nh, CommonNH::Any); + assert_eq!(ch.ht, HeaderType::Beacon); + assert_eq!(ch.pl, 0); + assert_eq!(ch.mhl, 1); + } + + #[test] + fn common_header_nh_byte() { + let mib = Mib::new(); + let req = make_gn_request(); + let ch = CommonHeader::initialize_with_request(&req, &mib); + let bytes = ch.encode(); + // NH is upper nibble of byte 0: BtpB = 2 → 0x2X + assert_eq!(bytes[0] >> 4, 2); + } + + #[test] + fn common_header_payload_length_encoding() { + let mib = Mib::new(); + let mut req = make_gn_request(); + req.length = 1024; + let ch = CommonHeader::initialize_with_request(&req, &mib); + let bytes = ch.encode(); + let pl_decoded = (bytes[4] as u16) << 8 | bytes[5] as u16; + assert_eq!(pl_decoded, 1024); + } + + #[test] + fn traffic_class_encode_decode() { + let tc = TrafficClass { + scf: true, + channel_offload: true, + tc_id: 0x3F, + }; + let encoded = tc.encode(); + assert_eq!(encoded, 0xFF); + let decoded = TrafficClass::decode(encoded); + assert!(decoded.scf); + assert!(decoded.channel_offload); + assert_eq!(decoded.tc_id, 0x3F); + } + + #[test] + fn traffic_class_all_false() { + let tc = TrafficClass { + scf: false, + channel_offload: false, + tc_id: 0, + }; + assert_eq!(tc.encode(), 0); + let decoded = TrafficClass::decode(0); + assert!(!decoded.scf); + assert!(!decoded.channel_offload); + assert_eq!(decoded.tc_id, 0); + } +} diff --git a/src/geonet/gbc_extended_header.rs b/src/geonet/gbc_extended_header.rs index ee89eeb..ced1623 100644 --- a/src/geonet/gbc_extended_header.rs +++ b/src/geonet/gbc_extended_header.rs @@ -113,3 +113,51 @@ impl GBCExtendedHeader { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::geonet::position_vector::LongPositionVector; + + fn make_gbc() -> GBCExtendedHeader { + GBCExtendedHeader { + sn: 42, + reserved: 0, + so_pv: LongPositionVector::decode([0u8; 24]), + latitude: 415520000, + longitude: 21340000, + a: 1000, + b: 500, + angle: 45, + reserved2: 0, + } + } + + #[test] + fn gbc_encode_decode_roundtrip() { + let header = make_gbc(); + let encoded = header.encode(); + assert_eq!(encoded.len(), 44); + let decoded = GBCExtendedHeader::decode(encoded); + assert_eq!(header, decoded); + } + + #[test] + fn gbc_sequence_number() { + let header = make_gbc(); + let encoded = header.encode(); + let sn = u16::from_be_bytes([encoded[0], encoded[1]]); + assert_eq!(sn, 42); + } + + #[test] + fn gbc_area_fields() { + let header = make_gbc(); + let decoded = GBCExtendedHeader::decode(header.encode()); + assert_eq!(decoded.latitude, 415520000); + assert_eq!(decoded.longitude, 21340000); + assert_eq!(decoded.a, 1000); + assert_eq!(decoded.b, 500); + assert_eq!(decoded.angle, 45); + } +} diff --git a/src/geonet/gn_address.rs b/src/geonet/gn_address.rs index 855e7d4..f6ba956 100644 --- a/src/geonet/gn_address.rs +++ b/src/geonet/gn_address.rs @@ -12,17 +12,17 @@ pub enum M { impl M { pub fn encode_to_address(&self) -> u64 { match self { - M::GnUnicast => (1 << 7) << 8 * 7, - M::GnMulticast => (0 << 7) << 8 * 7, + M::GnUnicast => (0 << 7) << 8 * 7, + M::GnMulticast => (1 << 7) << 8 * 7, } } pub fn decode_from_address(address: u64) -> Self { // Bit 63 is the M (multicast/unicast) flag if (address >> 63) & 1 == 1 { - M::GnUnicast - } else { M::GnMulticast + } else { + M::GnUnicast } } } @@ -47,19 +47,19 @@ pub enum ST { impl ST { pub fn encode_to_address(&self) -> u64 { match self { - ST::Unknown => (0 << 3) << 8 * 7, - ST::Pedestrian => (1 << 3) << 8 * 7, - ST::Cyclist => (2 << 3) << 8 * 7, - ST::Moped => (3 << 3) << 8 * 7, - ST::Motorcycle => (4 << 3) << 8 * 7, - ST::PassengerCar => (5 << 3) << 8 * 7, - ST::Bus => (6 << 3) << 8 * 7, - ST::LightTruck => (7 << 3) << 8 * 7, - ST::HeavyTruck => (8 << 3) << 8 * 7, - ST::Trailer => (9 << 3) << 8 * 7, - ST::SpecialVehicle => (10 << 3) << 8 * 7, - ST::Tram => (11 << 3) << 8 * 7, - ST::RoadSideUnit => (12 << 3) << 8 * 7, + ST::Unknown => (0 << 2) << 8 * 7, + ST::Pedestrian => (1 << 2) << 8 * 7, + ST::Cyclist => (2 << 2) << 8 * 7, + ST::Moped => (3 << 2) << 8 * 7, + ST::Motorcycle => (4 << 2) << 8 * 7, + ST::PassengerCar => (5 << 2) << 8 * 7, + ST::Bus => (6 << 2) << 8 * 7, + ST::LightTruck => (7 << 2) << 8 * 7, + ST::HeavyTruck => (8 << 2) << 8 * 7, + ST::Trailer => (9 << 2) << 8 * 7, + ST::SpecialVehicle => (10 << 2) << 8 * 7, + ST::Tram => (11 << 2) << 8 * 7, + ST::RoadSideUnit => (12 << 2) << 8 * 7, } } @@ -150,3 +150,115 @@ impl PartialEq for GNAddress { self.encode_to_int() == other.encode_to_int() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn m_encode_decode_unicast() { + let encoded = M::GnUnicast.encode_to_address(); + let decoded = M::decode_from_address(encoded); + assert!(matches!(decoded, M::GnUnicast)); + } + + #[test] + fn m_encode_decode_multicast() { + let encoded = M::GnMulticast.encode_to_address(); + let decoded = M::decode_from_address(encoded); + assert!(matches!(decoded, M::GnMulticast)); + } + + #[test] + fn st_encode_decode_all_variants() { + let variants = [ + (ST::Unknown, 0), + (ST::Pedestrian, 1), + (ST::Cyclist, 2), + (ST::Moped, 3), + (ST::Motorcycle, 4), + (ST::PassengerCar, 5), + (ST::Bus, 6), + (ST::LightTruck, 7), + (ST::HeavyTruck, 8), + (ST::Trailer, 9), + (ST::SpecialVehicle, 10), + (ST::Tram, 11), + (ST::RoadSideUnit, 12), + ]; + for (st, _) in &variants { + let encoded = st.encode_to_address(); + let decoded = ST::decode_from_address(encoded); + // Check roundtrip: encode, then decode → same bit pattern + assert_eq!(decoded.encode_to_address(), st.encode_to_address()); + } + } + + #[test] + fn st_decode_unknown_value() { + // Unknown ST values (> 12) should map to Unknown + let addr = 0x1F_u64 << (8 * 7 + 2); // ST bits = 31 + let decoded = ST::decode_from_address(addr); + assert!(matches!(decoded, ST::Unknown)); + } + + #[test] + fn mid_encode_decode_roundtrip() { + let mid = MID::new([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]); + let encoded = mid.encode_to_address(); + let decoded = MID::decode_from_address(encoded); + assert_eq!(decoded.mid, [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]); + } + + #[test] + fn mid_all_zeros() { + let mid = MID::new([0, 0, 0, 0, 0, 0]); + assert_eq!(mid.encode_to_address(), 0); + } + + #[test] + fn gn_address_encode_decode_roundtrip() { + let addr = GNAddress::new( + M::GnUnicast, + ST::PassengerCar, + MID::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]), + ); + let bytes = addr.encode(); + assert_eq!(bytes.len(), 8); + let decoded = GNAddress::decode(&bytes); + assert_eq!(addr, decoded); + } + + #[test] + fn gn_address_equality() { + let a = GNAddress::new(M::GnUnicast, ST::Bus, MID::new([1, 2, 3, 4, 5, 6])); + let b = GNAddress::new(M::GnUnicast, ST::Bus, MID::new([1, 2, 3, 4, 5, 6])); + assert_eq!(a, b); + } + + #[test] + fn gn_address_inequality_different_mid() { + let a = GNAddress::new(M::GnUnicast, ST::Bus, MID::new([1, 2, 3, 4, 5, 6])); + let b = GNAddress::new(M::GnUnicast, ST::Bus, MID::new([6, 5, 4, 3, 2, 1])); + assert_ne!(a, b); + } + + #[test] + fn gn_address_inequality_different_m() { + let a = GNAddress::new(M::GnUnicast, ST::Bus, MID::new([1, 2, 3, 4, 5, 6])); + let b = GNAddress::new(M::GnMulticast, ST::Bus, MID::new([1, 2, 3, 4, 5, 6])); + assert_ne!(a, b); + } + + #[test] + fn gn_address_encode_to_int_consistency() { + let addr = GNAddress::new( + M::GnMulticast, + ST::RoadSideUnit, + MID::new([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]), + ); + let int_val = addr.encode_to_int(); + let bytes = int_val.to_be_bytes(); + assert_eq!(bytes, addr.encode()); + } +} diff --git a/src/geonet/guc_extended_header.rs b/src/geonet/guc_extended_header.rs index 72dddac..0e190fd 100644 --- a/src/geonet/guc_extended_header.rs +++ b/src/geonet/guc_extended_header.rs @@ -66,3 +66,59 @@ impl GUCExtendedHeader { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::geonet::gn_address::{GNAddress, M, MID, ST}; + use crate::geonet::position_vector::{LongPositionVector, ShortPositionVector, Tst}; + + fn make_guc() -> GUCExtendedHeader { + let so_pv = LongPositionVector { + gn_addr: GNAddress::new(M::GnUnicast, ST::PassengerCar, MID::new([1, 2, 3, 4, 5, 6])), + tst: Tst::set_in_normal_timestamp_milliseconds(1_717_200_000_000), + latitude: 415520000, + longitude: 21340000, + pai: true, + s: 500, + h: 1800, + }; + let de_pv = ShortPositionVector { + gn_address: GNAddress::new(M::GnUnicast, ST::Bus, MID::new([6, 5, 4, 3, 2, 1])), + tst: Tst::set_in_normal_timestamp_milliseconds(1_717_200_000_000), + latitude: 415530000, + longitude: 21350000, + }; + GUCExtendedHeader::initialize_with_sequence_number_ego_pv_de_pv(99, so_pv, de_pv) + } + + #[test] + fn guc_encode_decode_roundtrip() { + let header = make_guc(); + let encoded = header.encode(); + assert_eq!(encoded.len(), 48); + let decoded = GUCExtendedHeader::decode(&encoded); + assert_eq!(header, decoded); + } + + #[test] + fn guc_sequence_number() { + let header = make_guc(); + assert_eq!(header.sn, 99); + } + + #[test] + fn guc_with_de_pv() { + let header = make_guc(); + let new_de_pv = ShortPositionVector::decode([0u8; 20]); + let updated = header.with_de_pv(new_de_pv.clone()); + assert_eq!(updated.de_pv, new_de_pv); + assert_eq!(updated.sn, header.sn); + } + + #[test] + #[should_panic(expected = "GUC Extended Header too short")] + fn guc_decode_too_short() { + GUCExtendedHeader::decode(&[0u8; 10]); + } +} diff --git a/src/geonet/location_table.rs b/src/geonet/location_table.rs index 1e846a2..fba14f6 100644 --- a/src/geonet/location_table.rs +++ b/src/geonet/location_table.rs @@ -176,11 +176,11 @@ impl LocationTable { /// Remove expired entries (§8.1.3). pub fn refresh_table(&mut self) { - let current_time = Tst::set_in_normal_timestamp_seconds( + let current_time = Tst::set_in_normal_timestamp_milliseconds( SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .expect("Time went backwards") - .as_secs(), + .as_millis() as u64, ); let lifetime_ms = (self.mib.itsGnLifetimeLocTE as u32) * 1000; self.entries @@ -346,3 +346,293 @@ impl LocationTable { self.entries.values().filter(|e| e.is_neighbour).collect() } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::geonet::gn_address::{GNAddress, M, MID, ST}; + use crate::geonet::position_vector::{LongPositionVector, Tst}; + + fn now_ms() -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis() as u64 + } + + fn make_mib() -> Mib { + Mib::new() + } + + fn make_lpv(mid: [u8; 6], msec: u64) -> LongPositionVector { + LongPositionVector { + gn_addr: GNAddress::new(M::GnUnicast, ST::PassengerCar, MID::new(mid)), + tst: Tst::set_in_normal_timestamp_milliseconds(msec), + latitude: 415520000, + longitude: 21340000, + pai: true, + s: 500, + h: 900, + } + } + + // ── LocationTableEntry ──────────────────────────────────────────── + + #[test] + fn entry_new_defaults() { + let entry = LocationTableEntry::new(make_mib()); + assert!(!entry.is_neighbour); + assert!(!entry.ls_pending); + assert!(entry.dpl_deque.is_empty()); + assert_eq!(entry.pdr, 0.0); + } + + #[test] + fn entry_update_position_vector_accepts_newer() { + let mut entry = LocationTableEntry::new(make_mib()); + let pv1 = make_lpv([1, 2, 3, 4, 5, 6], 1_717_200_000_000); + let pv2 = make_lpv([1, 2, 3, 4, 5, 6], 1_717_200_001_000); + + entry.update_position_vector(&pv1); + assert_eq!(entry.position_vector.tst, pv1.tst); + + entry.update_position_vector(&pv2); + assert_eq!(entry.position_vector.tst, pv2.tst); + } + + #[test] + fn entry_update_position_vector_rejects_older() { + let mut entry = LocationTableEntry::new(make_mib()); + let pv_new = make_lpv([1, 2, 3, 4, 5, 6], 1_717_200_001_000); + let pv_old = make_lpv([1, 2, 3, 4, 5, 6], 1_717_200_000_000); + + entry.update_position_vector(&pv_new); + entry.update_position_vector(&pv_old); + // Should still be pv_new + assert_eq!(entry.position_vector.tst, pv_new.tst); + } + + #[test] + fn entry_check_duplicate_sn_not_duplicate() { + let mut entry = LocationTableEntry::new(make_mib()); + assert!(!entry.check_duplicate_sn(1)); + assert!(!entry.check_duplicate_sn(2)); + assert!(!entry.check_duplicate_sn(3)); + } + + #[test] + fn entry_check_duplicate_sn_is_duplicate() { + let mut entry = LocationTableEntry::new(make_mib()); + assert!(!entry.check_duplicate_sn(42)); + assert!(entry.check_duplicate_sn(42)); + } + + #[test] + fn entry_dpl_ring_buffer_eviction() { + let mib = make_mib(); + let max_len = mib.itsGnDPLLength as usize; + let mut entry = LocationTableEntry::new(mib); + + // Fill the DPL + for i in 0..max_len { + assert!(!entry.check_duplicate_sn(i as u16)); + } + assert_eq!(entry.dpl_deque.len(), max_len); + + // Add one more — oldest (0) should be evicted + assert!(!entry.check_duplicate_sn(100)); + assert_eq!(entry.dpl_deque.len(), max_len); + // SN 0 should no longer be marked as duplicate + assert!(!entry.check_duplicate_sn(0)); + } + + #[test] + fn entry_shb_packet_marks_neighbour() { + let mut entry = LocationTableEntry::new(make_mib()); + let pv = make_lpv([1, 2, 3, 4, 5, 6], 1_717_200_000_000); + entry.update_with_shb_packet(&pv, 100); + assert!(entry.is_neighbour); + } + + #[test] + fn entry_gbc_packet_not_neighbour() { + let mut entry = LocationTableEntry::new(make_mib()); + let gbc = GBCExtendedHeader { + sn: 1, + reserved: 0, + so_pv: make_lpv([1, 2, 3, 4, 5, 6], 1_717_200_000_000), + latitude: 0, + longitude: 0, + a: 0, + b: 0, + angle: 0, + reserved2: 0, + }; + let dup = entry.update_with_gbc_packet(&gbc, 100); + assert!(!dup); + assert!(!entry.is_neighbour); + } + + #[test] + fn entry_gbc_packet_duplicate() { + let mut entry = LocationTableEntry::new(make_mib()); + let gbc = GBCExtendedHeader { + sn: 42, + reserved: 0, + so_pv: make_lpv([1, 2, 3, 4, 5, 6], 1_717_200_000_000), + latitude: 0, + longitude: 0, + a: 0, + b: 0, + angle: 0, + reserved2: 0, + }; + assert!(!entry.update_with_gbc_packet(&gbc, 100)); + assert!(entry.update_with_gbc_packet(&gbc, 100)); + } + + // ── LocationTable ───────────────────────────────────────────────── + + #[test] + fn table_new_empty() { + let table = LocationTable::new(make_mib()); + assert!(table.entries.is_empty()); + } + + #[test] + fn table_new_shb_creates_entry() { + let mut table = LocationTable::new(make_mib()); + let pv = make_lpv([1, 2, 3, 4, 5, 6], now_ms()); + table.new_shb_packet(&pv, &[0u8; 10]); + assert_eq!(table.entries.len(), 1); + let entry = table.get_entry_ref(&pv.gn_addr).unwrap(); + assert!(entry.is_neighbour); + } + + #[test] + fn table_new_gbc_creates_entry() { + let mut table = LocationTable::new(make_mib()); + let gbc = GBCExtendedHeader { + sn: 1, + reserved: 0, + so_pv: make_lpv([1, 2, 3, 4, 5, 6], now_ms()), + latitude: 0, + longitude: 0, + a: 0, + b: 0, + angle: 0, + reserved2: 0, + }; + let dup = table.new_gbc_packet(&gbc, &[0u8; 10]); + assert!(!dup); + assert_eq!(table.entries.len(), 1); + } + + #[test] + fn table_new_gbc_duplicate() { + let mut table = LocationTable::new(make_mib()); + let gbc = GBCExtendedHeader { + sn: 1, + reserved: 0, + so_pv: make_lpv([1, 2, 3, 4, 5, 6], now_ms()), + latitude: 0, + longitude: 0, + a: 0, + b: 0, + angle: 0, + reserved2: 0, + }; + assert!(!table.new_gbc_packet(&gbc, &[0u8; 10])); + assert!(table.new_gbc_packet(&gbc, &[0u8; 10])); + } + + #[test] + fn table_new_tsb_creates_entry() { + let mut table = LocationTable::new(make_mib()); + let tsb = TSBExtendedHeader { + sn: 1, + reserved: 0, + so_pv: make_lpv([1, 2, 3, 4, 5, 6], now_ms()), + }; + let dup = table.new_tsb_packet(&tsb, &[0u8; 10]); + assert!(!dup); + assert_eq!(table.entries.len(), 1); + } + + #[test] + fn table_new_guc_creates_entry() { + let mut table = LocationTable::new(make_mib()); + let guc = GUCExtendedHeader { + sn: 1, + reserved: 0, + so_pv: make_lpv([1, 2, 3, 4, 5, 6], now_ms()), + de_pv: crate::geonet::position_vector::ShortPositionVector::decode([0u8; 20]), + }; + let dup = table.new_guc_packet(&guc, &[0u8; 10]); + assert!(!dup); + assert_eq!(table.entries.len(), 1); + } + + #[test] + fn table_new_ls_request_creates_entry() { + let mut table = LocationTable::new(make_mib()); + let addr = GNAddress::new(M::GnUnicast, ST::Bus, MID::new([0xAA; 6])); + let ls = LSRequestExtendedHeader { + sn: 1, + reserved: 0, + so_pv: make_lpv([1, 2, 3, 4, 5, 6], now_ms()), + request_gn_addr: addr, + }; + let dup = table.new_ls_request_packet(&ls, &[0u8; 10]); + assert!(!dup); + assert_eq!(table.entries.len(), 1); + } + + #[test] + fn table_new_ls_reply_creates_entry() { + let mut table = LocationTable::new(make_mib()); + let ls = LSReplyExtendedHeader { + sn: 1, + reserved: 0, + so_pv: make_lpv([1, 2, 3, 4, 5, 6], now_ms()), + de_pv: crate::geonet::position_vector::ShortPositionVector::decode([0u8; 20]), + }; + let dup = table.new_ls_reply_packet(&ls, &[0u8; 10]); + assert!(!dup); + } + + #[test] + fn table_get_neighbours() { + let mut table = LocationTable::new(make_mib()); + // Add SHB (neighbour) and GBC (not neighbour) + let pv1 = make_lpv([1, 2, 3, 4, 5, 6], now_ms()); + table.new_shb_packet(&pv1, &[0u8; 10]); + + let gbc = GBCExtendedHeader { + sn: 1, + reserved: 0, + so_pv: make_lpv([6, 5, 4, 3, 2, 1], now_ms()), + latitude: 0, + longitude: 0, + a: 0, + b: 0, + angle: 0, + reserved2: 0, + }; + table.new_gbc_packet(&gbc, &[0u8; 10]); + + let neighbours = table.get_neighbours(); + assert_eq!(neighbours.len(), 1); + } + + #[test] + fn table_ensure_entry() { + let mut table = LocationTable::new(make_mib()); + let addr = GNAddress::new(M::GnUnicast, ST::PassengerCar, MID::new([1; 6])); + let _entry = table.ensure_entry(&addr); + assert_eq!(table.entries.len(), 1); + // Calling again doesn't create a second entry + let _entry2 = table.ensure_entry(&addr); + assert_eq!(table.entries.len(), 1); + } +} diff --git a/src/geonet/ls_extended_header.rs b/src/geonet/ls_extended_header.rs index efe4586..ac9fc6c 100644 --- a/src/geonet/ls_extended_header.rs +++ b/src/geonet/ls_extended_header.rs @@ -118,3 +118,82 @@ impl LSReplyExtendedHeader { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::geonet::gn_address::{GNAddress, M, MID, ST}; + use crate::geonet::position_vector::{LongPositionVector, ShortPositionVector, Tst}; + + fn make_lpv() -> LongPositionVector { + LongPositionVector { + gn_addr: GNAddress::new(M::GnUnicast, ST::PassengerCar, MID::new([1, 2, 3, 4, 5, 6])), + tst: Tst::set_in_normal_timestamp_milliseconds(1_717_200_000_000), + latitude: 415520000, + longitude: 21340000, + pai: true, + s: 500, + h: 900, + } + } + + fn make_spv() -> ShortPositionVector { + ShortPositionVector { + gn_address: GNAddress::new(M::GnUnicast, ST::Bus, MID::new([6, 5, 4, 3, 2, 1])), + tst: Tst::set_in_normal_timestamp_milliseconds(1_717_200_000_000), + latitude: 415530000, + longitude: 21350000, + } + } + + // ── LS Request ──────────────────────────────────────────────────── + + #[test] + fn ls_request_encode_decode_roundtrip() { + let req_addr = GNAddress::new(M::GnUnicast, ST::HeavyTruck, MID::new([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF])); + let header = LSRequestExtendedHeader::initialize(55, make_lpv(), req_addr); + let encoded = header.encode(); + assert_eq!(encoded.len(), 36); + let decoded = LSRequestExtendedHeader::decode(&encoded); + assert_eq!(header, decoded); + } + + #[test] + fn ls_request_fields() { + let req_addr = GNAddress::new(M::GnMulticast, ST::Tram, MID::new([1, 1, 1, 1, 1, 1])); + let header = LSRequestExtendedHeader::initialize(100, make_lpv(), req_addr); + assert_eq!(header.sn, 100); + assert_eq!(header.reserved, 0); + assert_eq!(header.request_gn_addr, req_addr); + } + + #[test] + #[should_panic(expected = "LS Request Extended Header too short")] + fn ls_request_decode_too_short() { + LSRequestExtendedHeader::decode(&[0u8; 10]); + } + + // ── LS Reply ───────────────────────────────────────────────────── + + #[test] + fn ls_reply_encode_decode_roundtrip() { + let header = LSReplyExtendedHeader::initialize(200, make_lpv(), make_spv()); + let encoded = header.encode(); + assert_eq!(encoded.len(), 48); + let decoded = LSReplyExtendedHeader::decode(&encoded); + assert_eq!(header, decoded); + } + + #[test] + fn ls_reply_fields() { + let header = LSReplyExtendedHeader::initialize(123, make_lpv(), make_spv()); + assert_eq!(header.sn, 123); + assert_eq!(header.reserved, 0); + } + + #[test] + #[should_panic(expected = "LS Reply Extended Header too short")] + fn ls_reply_decode_too_short() { + LSReplyExtendedHeader::decode(&[0u8; 20]); + } +} diff --git a/src/geonet/mib.rs b/src/geonet/mib.rs index 36a2486..9aa095d 100644 --- a/src/geonet/mib.rs +++ b/src/geonet/mib.rs @@ -3,7 +3,7 @@ use super::gn_address::{GNAddress, M, MID, ST}; -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum LocalGnAddrConfMethod { Auto, Managed, @@ -29,7 +29,7 @@ impl LocalGnAddrConfMethod { } } -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum GnIsMobile { Stationary, Mobile, @@ -52,7 +52,7 @@ impl GnIsMobile { } } -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum GnIfType { Unspecified, ItsG5, @@ -78,7 +78,7 @@ impl GnIfType { } } -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum GnSecurity { Disabled, Enabled, @@ -101,7 +101,7 @@ impl GnSecurity { } } -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum SnDecapResultHandling { Strict, NonStrict, @@ -124,7 +124,7 @@ impl SnDecapResultHandling { } } -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum NonAreaForwardingAlgorithm { Unspecified, Greedy, @@ -147,7 +147,7 @@ impl NonAreaForwardingAlgorithm { } } -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum AreaForwardingAlgorithm { Unspecified, Simple, @@ -173,7 +173,8 @@ impl AreaForwardingAlgorithm { } } -#[derive(Clone, Copy, PartialEq)] +#[allow(non_snake_case)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct Mib { pub itsGnLocalGnAddr: GNAddress, pub itsGnLocalGnAddrConfMethod: LocalGnAddrConfMethod, @@ -259,3 +260,125 @@ impl Mib { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn mib_default_values() { + let mib = Mib::new(); + assert_eq!(mib.itsGnProtocolVersion, 1); + assert_eq!(mib.itsGnDefaultHopLimit, 10); + assert_eq!(mib.itsGnMaxSduSize, 1398); + assert_eq!(mib.itsGnDefaultPacketLifetime, 60); + assert_eq!(mib.itsGnMaxPacketLifetime, 600); + assert_eq!(mib.itsGnBeaconServiceRetransmitTimer, 3000); + assert!(matches!(mib.itsGnIsMobile, GnIsMobile::Mobile)); + assert!(matches!(mib.itsGnSecurity, GnSecurity::Disabled)); + assert!(matches!( + mib.itsGnNonAreaForwardingAlgorithm, + NonAreaForwardingAlgorithm::Greedy + )); + assert!(matches!( + mib.itsGnAreaForwardingAlgorithm, + AreaForwardingAlgorithm::Cbf + )); + } + + #[test] + fn mib_copy() { + let mib1 = Mib::new(); + let mib2 = mib1; + assert_eq!(mib1, mib2); + } + + #[test] + fn local_gn_addr_conf_method_roundtrip() { + for val in 0..3u8 { + assert_eq!(LocalGnAddrConfMethod::decode(val).encode(), val); + } + } + + #[test] + #[should_panic(expected = "Invalid LocalGnAddrConfMethod Value")] + fn local_gn_addr_conf_method_invalid() { + LocalGnAddrConfMethod::decode(99); + } + + #[test] + fn gn_is_mobile_roundtrip() { + assert_eq!(GnIsMobile::Stationary.encode(), 0); + assert_eq!(GnIsMobile::Mobile.encode(), 1); + assert_eq!(GnIsMobile::decode(0).encode(), 0); + assert_eq!(GnIsMobile::decode(1).encode(), 1); + } + + #[test] + #[should_panic(expected = "Invalid GnIsMobile Value")] + fn gn_is_mobile_invalid() { + GnIsMobile::decode(99); + } + + #[test] + fn gn_if_type_roundtrip() { + for val in 0..3u8 { + assert_eq!(GnIfType::decode(val).encode(), val); + } + } + + #[test] + #[should_panic(expected = "Invalid GnIfType Value")] + fn gn_if_type_invalid() { + GnIfType::decode(99); + } + + #[test] + fn gn_security_roundtrip() { + assert_eq!(GnSecurity::Disabled.encode(), 0); + assert_eq!(GnSecurity::Enabled.encode(), 1); + } + + #[test] + #[should_panic(expected = "Invalid GnSecurity Value")] + fn gn_security_invalid() { + GnSecurity::decode(99); + } + + #[test] + fn sn_decap_result_handling_roundtrip() { + assert_eq!(SnDecapResultHandling::Strict.encode(), 0); + assert_eq!(SnDecapResultHandling::NonStrict.encode(), 1); + } + + #[test] + #[should_panic(expected = "Invalid SnDecapResultHandling Value")] + fn sn_decap_result_handling_invalid() { + SnDecapResultHandling::decode(99); + } + + #[test] + fn non_area_forwarding_algorithm_roundtrip() { + assert_eq!(NonAreaForwardingAlgorithm::Unspecified.encode(), 0); + assert_eq!(NonAreaForwardingAlgorithm::Greedy.encode(), 1); + } + + #[test] + #[should_panic(expected = "Invalid NonAreaForwardingAlgorithm Value")] + fn non_area_forwarding_algorithm_invalid() { + NonAreaForwardingAlgorithm::decode(99); + } + + #[test] + fn area_forwarding_algorithm_roundtrip() { + for val in 0..3u8 { + assert_eq!(AreaForwardingAlgorithm::decode(val).encode(), val); + } + } + + #[test] + #[should_panic(expected = "Invalid AreaForwardingAlgorithm Value")] + fn area_forwarding_algorithm_invalid() { + AreaForwardingAlgorithm::decode(99); + } +} diff --git a/src/geonet/position_vector.rs b/src/geonet/position_vector.rs index 20e6dd0..e805000 100644 --- a/src/geonet/position_vector.rs +++ b/src/geonet/position_vector.rs @@ -48,7 +48,7 @@ impl Sub for Tst { type Output = u32; fn sub(self, other: Self) -> u32 { - self.msec - other.msec + self.msec.wrapping_sub(other.msec) } } @@ -233,3 +233,149 @@ impl PartialEq for ShortPositionVector { && self.longitude == other.longitude } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::geonet::gn_address::{GNAddress, M, MID, ST}; + + // ── Tst ──────────────────────────────────────────────────────────── + + #[test] + fn tst_from_unix_ms() { + // 2024-06-01T00:00:00 UTC ≈ 1 717 200 000 000 ms + let unix_ms: u64 = 1_717_200_000_000; + let tst = Tst::set_in_normal_timestamp_milliseconds(unix_ms); + let expected = ((unix_ms - 1_072_911_600_000) & 0xFFFF_FFFF) as u32; + assert_eq!(tst.msec, expected); + } + + #[test] + fn tst_zero_before_epoch() { + // Before TAI offset → should be 0 + let tst = Tst::set_in_normal_timestamp_milliseconds(0); + assert_eq!(tst.msec, 0); + } + + #[test] + fn tst_encode_decode_roundtrip() { + let tst = Tst::set_in_normal_timestamp_milliseconds(1_717_200_000_000); + let bytes = tst.encode(); + let decoded = Tst::decode(&bytes); + assert_eq!(tst, decoded); + } + + #[test] + fn tst_subtraction() { + let a = Tst { msec: 5000 }; + let b = Tst { msec: 3000 }; + assert_eq!(a - b, 2000); + } + + #[test] + fn tst_addition() { + let a = Tst { msec: 1000 }; + let b = Tst { msec: 2000 }; + let c = a + b; + assert_eq!(c.msec, 3000); + } + + #[test] + fn tst_equality() { + let a = Tst { msec: 42 }; + let b = Tst { msec: 42 }; + assert_eq!(a, b); + } + + #[test] + fn tst_ordering_simple() { + let a = Tst { msec: 100 }; + let b = Tst { msec: 200 }; + assert!(a < b); + assert!(b > a); + } + + #[test] + fn tst_ordering_wraparound() { + // RFC 1982: if b - a > HALF, then a > b (wraparound) + let a = Tst { msec: 0xFFFF_FFFF }; + let b = Tst { msec: 1 }; + // a is "before" b in the wraparound sense + assert!(a < b); + } + + // ── LongPositionVector ───────────────────────────────────────────── + + #[test] + fn lpv_encode_decode_roundtrip() { + let addr = GNAddress::new(M::GnUnicast, ST::PassengerCar, MID::new([1, 2, 3, 4, 5, 6])); + let tst = Tst::set_in_normal_timestamp_milliseconds(1_717_200_000_000); + let lpv = LongPositionVector { + gn_addr: addr, + tst, + latitude: 415520000, // 41.552° + longitude: 21340000, // 2.134° + pai: true, + s: 1000, // 10.00 m/s + h: 900, // 90.0° + }; + let encoded = lpv.encode(); + assert_eq!(encoded.len(), 24); + let decoded = LongPositionVector::decode(encoded); + assert_eq!(lpv, decoded); + } + + #[test] + fn lpv_pai_flag() { + let mut lpv = LongPositionVector::decode([0u8; 24]); + lpv.pai = true; + let encoded = lpv.encode(); + assert_eq!(encoded[20] >> 7, 1); + + lpv.pai = false; + let encoded = lpv.encode(); + assert_eq!(encoded[20] >> 7, 0); + } + + #[test] + fn lpv_speed_15bit() { + let mut lpv = LongPositionVector::decode([0u8; 24]); + lpv.s = 0x7FFF; // max 15-bit + lpv.pai = false; + let encoded = lpv.encode(); + let decoded = LongPositionVector::decode(encoded); + assert_eq!(decoded.s, 0x7FFF); + } + + #[test] + fn lpv_equality() { + let a = LongPositionVector::decode([0u8; 24]); + let b = LongPositionVector::decode([0u8; 24]); + assert_eq!(a, b); + } + + // ── ShortPositionVector ──────────────────────────────────────────── + + #[test] + fn spv_encode_decode_roundtrip() { + let addr = GNAddress::new(M::GnMulticast, ST::Bus, MID::new([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF])); + let tst = Tst::set_in_normal_timestamp_milliseconds(1_717_200_000_000); + let spv = ShortPositionVector { + gn_address: addr, + tst, + latitude: 415520000, + longitude: 21340000, + }; + let encoded = spv.encode(); + assert_eq!(encoded.len(), 20); + let decoded = ShortPositionVector::decode(encoded); + assert_eq!(spv, decoded); + } + + #[test] + fn spv_equality() { + let a = ShortPositionVector::decode([0u8; 20]); + let b = ShortPositionVector::decode([0u8; 20]); + assert_eq!(a, b); + } +} diff --git a/src/geonet/router.rs b/src/geonet/router.rs index c0c9dfe..2104c31 100644 --- a/src/geonet/router.rs +++ b/src/geonet/router.rs @@ -1797,3 +1797,249 @@ fn clone_request(r: &GNDataRequest) -> GNDataRequest { area: r.area, } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::geonet::gn_address::{GNAddress, M, MID, ST}; + use crate::geonet::position_vector::LongPositionVector; + use std::sync::mpsc; + + fn make_mib() -> Mib { + Mib::new() + } + + fn make_router() -> (Router, Receiver>, Receiver) { + let (ll_tx, ll_rx) = mpsc::channel(); + let (btp_tx, btp_rx) = mpsc::channel(); + let mib = make_mib(); + let router = Router::new(mib, ll_tx, btp_tx, None, None, None); + (router, ll_rx, btp_rx) + } + + fn make_shb_request(data: Vec) -> GNDataRequest { + GNDataRequest { + upper_protocol_entity: CommonNH::BtpB, + packet_transport_type: PacketTransportType { + header_type: HeaderType::Tsb, + header_sub_type: HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop), + }, + communication_profile: CommunicationProfile::Unspecified, + traffic_class: TrafficClass { + scf: false, + channel_offload: false, + tc_id: 0, + }, + security_profile: SecurityProfile::NoSecurity, + its_aid: 36, + security_permissions: vec![], + max_hop_limit: 1, + max_packet_lifetime: None, + destination: None, + length: data.len() as u16, + data, + area: Area { + latitude: 0, + longitude: 0, + a: 0, + b: 0, + angle: 0, + }, + } + } + + #[test] + fn router_new_defaults() { + let (router, _ll, _btp) = make_router(); + assert_eq!(router.sequence_number, 0); + assert!(router.location_table.entries.is_empty()); + assert!(!router.beacon_reset); + } + + #[test] + fn router_get_sequence_number_increments() { + let (mut router, _ll, _btp) = make_router(); + assert_eq!(router.get_sequence_number(), 1); + assert_eq!(router.get_sequence_number(), 2); + assert_eq!(router.get_sequence_number(), 3); + } + + #[test] + fn router_sequence_number_wraps() { + let (mut router, _ll, _btp) = make_router(); + router.sequence_number = u16::MAX; + assert_eq!(router.get_sequence_number(), 0); + } + + #[test] + fn router_duplicate_address_detection() { + let (router, _ll, _btp) = make_router(); + let own_addr = router.mib.itsGnLocalGnAddr; + assert!(router.duplicate_address_detection(own_addr)); + let other = GNAddress::new(M::GnUnicast, ST::Bus, MID::new([0xAA; 6])); + assert!(!router.duplicate_address_detection(other)); + } + + #[test] + fn router_refresh_ego_position_vector() { + let (mut router, _ll, _btp) = make_router(); + let pv = LongPositionVector { + gn_addr: router.gn_address, + tst: Tst::set_in_normal_timestamp_milliseconds(99999), + latitude: 415520000, + longitude: 21340000, + pai: true, + s: 1000, + h: 900, + }; + router.refresh_ego_position_vector(pv); + assert_eq!(router.ego_position_vector.latitude, 415520000); + assert_eq!(router.ego_position_vector.longitude, 21340000); + assert!(router.ego_position_vector.pai); + assert_eq!(router.ego_position_vector.s, 1000); + assert_eq!(router.ego_position_vector.h, 900); + } + + #[test] + fn router_send_beacon() { + let (router, ll_rx, _btp) = make_router(); + let confirm = router.gn_data_request_beacon(); + assert_eq!(confirm.result_code, ResultCode::Accepted); + let packet = ll_rx.recv().unwrap(); + // BasicHeader (4) + CommonHeader (8) + LPV (24) = 36 bytes + assert_eq!(packet.len(), 36); + } + + #[test] + fn router_send_shb() { + let (mut router, ll_rx, _btp) = make_router(); + let request = make_shb_request(vec![0xCA, 0xFE]); + let confirm = router.gn_data_request(request); + assert_eq!(confirm.result_code, ResultCode::Accepted); + let packet = ll_rx.recv().unwrap(); + // BasicHeader(4) + CommonHeader(8) + LPV(24) + MediaDep(4) + payload(2) = 42 + assert_eq!(packet.len(), 42); + assert!(router.beacon_reset); + } + + #[test] + fn router_compute_area_size_circle() { + let area = Area { + latitude: 0, + longitude: 0, + a: 100, + b: 0, + angle: 0, + }; + let size = Router::compute_area_size_m2_gb(&GeoBroadcastHST::GeoBroadcastCircle, &area); + let expected = std::f64::consts::PI * 100.0 * 100.0; + assert!((size - expected).abs() < 0.01); + } + + #[test] + fn router_compute_area_size_ellipse() { + let area = Area { + latitude: 0, + longitude: 0, + a: 100, + b: 50, + angle: 0, + }; + let size = Router::compute_area_size_m2_gb(&GeoBroadcastHST::GeoBroadcastEllipse, &area); + let expected = std::f64::consts::PI * 100.0 * 50.0; + assert!((size - expected).abs() < 0.01); + } + + #[test] + fn router_compute_area_size_rectangle() { + let area = Area { + latitude: 0, + longitude: 0, + a: 100, + b: 50, + angle: 0, + }; + let size = Router::compute_area_size_m2_gb(&GeoBroadcastHST::GeoBroadcastRectangle, &area); + assert!((size - 20000.0).abs() < 0.01); + } + + #[test] + fn router_calculate_distance_same_point() { + let (x, y) = Router::calculate_distance((41.552, 2.134), (41.552, 2.134)); + assert!(x.abs() < 0.01); + assert!(y.abs() < 0.01); + } + + #[test] + fn router_distance_m_nonzero() { + let d = Router::distance_m(415520000, 21340000, 415530000, 21340000); + // ~1.1 m difference (0.001 degree lat) + assert!(d > 0.0); + } + + #[test] + fn router_cbf_timeout_at_max_range() { + let (router, _ll, _btp) = make_router(); + let dist = router.mib.itsGnDefaultMaxCommunicationRange as f64; + let timeout = router.cbf_compute_timeout_ms(dist); + assert!((timeout - router.mib.itsGnCbfMinTime as f64).abs() < 0.01); + } + + #[test] + fn router_cbf_timeout_at_zero_range() { + let (router, _ll, _btp) = make_router(); + let timeout = router.cbf_compute_timeout_ms(0.0); + assert!((timeout - router.mib.itsGnCbfMaxTime as f64).abs() < 0.01); + } + + #[test] + fn router_forwarding_algorithm_response_encode() { + assert_eq!(GNForwardingAlgorithmResponse::AreaForwarding.encode(), 1); + assert_eq!(GNForwardingAlgorithmResponse::NonAreaForwarding.encode(), 2); + assert_eq!(GNForwardingAlgorithmResponse::Discarted.encode(), 3); + } + + #[test] + fn router_spawn_and_shutdown() { + let mib = make_mib(); + let (handle, _ll_rx, _btp_rx) = Router::spawn(mib, None, None, None); + handle.shutdown(); + } + + #[test] + fn router_handle_send_incoming_packet() { + let mib = make_mib(); + let (handle, _ll_rx, _btp_rx) = Router::spawn(mib, None, None, None); + // Send a short/invalid packet — router should not crash + handle.send_incoming_packet(vec![0u8; 4]); + std::thread::sleep(std::time::Duration::from_millis(50)); + handle.shutdown(); + } + + #[test] + fn router_handle_update_position_vector() { + let mib = make_mib(); + let (handle, _ll_rx, _btp_rx) = Router::spawn(mib, None, None, None); + let pv = LongPositionVector { + gn_addr: mib.itsGnLocalGnAddr, + tst: Tst::set_in_normal_timestamp_milliseconds(99999), + latitude: 415520000, + longitude: 21340000, + pai: true, + s: 500, + h: 900, + }; + handle.update_position_vector(pv); + std::thread::sleep(std::time::Duration::from_millis(50)); + handle.shutdown(); + } + + #[test] + fn clone_request_roundtrip() { + let req = make_shb_request(vec![1, 2, 3]); + let cloned = clone_request(&req); + assert_eq!(cloned.data, req.data); + assert_eq!(cloned.its_aid, req.its_aid); + assert_eq!(cloned.max_hop_limit, req.max_hop_limit); + } +} diff --git a/src/geonet/service_access_point.rs b/src/geonet/service_access_point.rs index 2bd7bd3..d29aae3 100644 --- a/src/geonet/service_access_point.rs +++ b/src/geonet/service_access_point.rs @@ -291,6 +291,7 @@ pub struct GNDataRequest { pub area: Area, } +#[derive(Debug, PartialEq)] pub enum ResultCode { Accepted, MaximumLengthExceeded, @@ -316,3 +317,166 @@ pub struct GNDataIndication { pub length: u16, pub data: Vec, } + +#[cfg(test)] +mod tests { + use super::*; + + // ── CommonNH ────────────────────────────────────────────────────── + + #[test] + fn common_nh_encode_decode_roundtrip() { + for (nh, val) in [ + (CommonNH::Any, 0u8), + (CommonNH::BtpA, 1), + (CommonNH::BtpB, 2), + (CommonNH::IpV6, 3), + ] { + assert_eq!(nh.encode(), val); + assert_eq!(CommonNH::decode(val), nh); + } + } + + #[test] + #[should_panic(expected = "Invalid Next Header Value")] + fn common_nh_decode_invalid() { + CommonNH::decode(99); + } + + // ── HeaderType ──────────────────────────────────────────────────── + + #[test] + fn header_type_encode_decode_roundtrip() { + for (ht, val) in [ + (HeaderType::Any, 0u8), + (HeaderType::Beacon, 1), + (HeaderType::GeoUnicast, 2), + (HeaderType::GeoAnycast, 3), + (HeaderType::GeoBroadcast, 4), + (HeaderType::Tsb, 5), + (HeaderType::Ls, 6), + ] { + assert_eq!(ht.encode(), val); + assert_eq!(HeaderType::decode(val), ht); + } + } + + #[test] + #[should_panic(expected = "Invalid Header Type Value")] + fn header_type_decode_invalid() { + HeaderType::decode(99); + } + + // ── HeaderSubType ───────────────────────────────────────────────── + + #[test] + fn header_sub_type_unspecified() { + let hst = HeaderSubType::decode(&HeaderType::Any, 0); + assert_eq!(hst.encode(), 0); + assert!(matches!(hst, HeaderSubType::Unspecified(_))); + } + + #[test] + fn header_sub_type_geo_anycast() { + for val in 0..3u8 { + let hst = HeaderSubType::decode(&HeaderType::GeoAnycast, val); + assert_eq!(hst.encode(), val); + assert!(matches!(hst, HeaderSubType::GeoAnycast(_))); + } + } + + #[test] + fn header_sub_type_geo_broadcast() { + for val in 0..3u8 { + let hst = HeaderSubType::decode(&HeaderType::GeoBroadcast, val); + assert_eq!(hst.encode(), val); + assert!(matches!(hst, HeaderSubType::GeoBroadcast(_))); + } + } + + #[test] + fn header_sub_type_topo_broadcast() { + let shb = HeaderSubType::decode(&HeaderType::Tsb, 0); + assert!(matches!(shb, HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop))); + let mhb = HeaderSubType::decode(&HeaderType::Tsb, 1); + assert!(matches!(mhb, HeaderSubType::TopoBroadcast(TopoBroadcastHST::MultiHop))); + } + + #[test] + fn header_sub_type_location_service() { + let req = HeaderSubType::decode(&HeaderType::Ls, 0); + assert!(matches!(req, HeaderSubType::LocationService(LocationServiceHST::LsRequest))); + let rep = HeaderSubType::decode(&HeaderType::Ls, 1); + assert!(matches!(rep, HeaderSubType::LocationService(LocationServiceHST::LsReply))); + } + + // ── TrafficClass ────────────────────────────────────────────────── + + #[test] + fn traffic_class_encode_decode() { + let tc = TrafficClass { scf: true, channel_offload: false, tc_id: 10 }; + let encoded = tc.encode(); + let decoded = TrafficClass::decode(encoded); + assert_eq!(tc, decoded); + } + + // ── GeoAnycastHST ───────────────────────────────────────────────── + + #[test] + fn geo_anycast_hst_roundtrip() { + for val in 0..3u8 { + assert_eq!(GeoAnycastHST::decode(val).encode(), val); + } + } + + #[test] + #[should_panic(expected = "Invalid GeoAnycast Header Sub Type Value")] + fn geo_anycast_hst_invalid() { + GeoAnycastHST::decode(99); + } + + // ── GeoBroadcastHST ─────────────────────────────────────────────── + + #[test] + fn geo_broadcast_hst_roundtrip() { + for val in 0..3u8 { + assert_eq!(GeoBroadcastHST::decode(val).encode(), val); + } + } + + #[test] + #[should_panic(expected = "Invalid GeoBroadcast Header Sub Type Value")] + fn geo_broadcast_hst_invalid() { + GeoBroadcastHST::decode(99); + } + + // ── TopoBroadcastHST ────────────────────────────────────────────── + + #[test] + fn topo_broadcast_hst_roundtrip() { + assert_eq!(TopoBroadcastHST::SingleHop.encode(), 0); + assert_eq!(TopoBroadcastHST::MultiHop.encode(), 1); + assert_eq!(TopoBroadcastHST::decode(0).encode(), 0); + assert_eq!(TopoBroadcastHST::decode(1).encode(), 1); + } + + #[test] + #[should_panic(expected = "Invalid TopoBroadcast Header Sub Type Value")] + fn topo_broadcast_hst_invalid() { + TopoBroadcastHST::decode(99); + } + + // ── LocationServiceHST ──────────────────────────────────────────── + + #[test] + fn location_service_hst_roundtrip() { + assert_eq!(LocationServiceHST::LsRequest.encode(), 0); + assert_eq!(LocationServiceHST::LsReply.encode(), 1); + } + + #[test] + #[should_panic(expected = "Invalid LocationService Header Sub Type Value")] + fn location_service_hst_invalid() { + LocationServiceHST::decode(99); + } +} diff --git a/src/geonet/tsb_extended_header.rs b/src/geonet/tsb_extended_header.rs index 994d0d0..98dc118 100644 --- a/src/geonet/tsb_extended_header.rs +++ b/src/geonet/tsb_extended_header.rs @@ -49,3 +49,45 @@ impl TSBExtendedHeader { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::geonet::position_vector::{LongPositionVector, Tst}; + use crate::geonet::gn_address::{GNAddress, M, MID, ST}; + + fn make_tsb() -> TSBExtendedHeader { + let so_pv = LongPositionVector { + gn_addr: GNAddress::new(M::GnUnicast, ST::Motorcycle, MID::new([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF])), + tst: Tst::set_in_normal_timestamp_milliseconds(1_717_200_000_000), + latitude: 415520000, + longitude: 21340000, + pai: true, + s: 300, + h: 450, + }; + TSBExtendedHeader::initialize_with_sequence_number_ego_pv(77, so_pv) + } + + #[test] + fn tsb_encode_decode_roundtrip() { + let header = make_tsb(); + let encoded = header.encode(); + assert_eq!(encoded.len(), 28); + let decoded = TSBExtendedHeader::decode(&encoded); + assert_eq!(header, decoded); + } + + #[test] + fn tsb_sequence_number() { + let header = make_tsb(); + assert_eq!(header.sn, 77); + assert_eq!(header.reserved, 0); + } + + #[test] + #[should_panic(expected = "TSB Extended Header too short")] + fn tsb_decode_too_short() { + TSBExtendedHeader::decode(&[0u8; 5]); + } +} diff --git a/src/link_layer/packet_consts.rs b/src/link_layer/packet_consts.rs index 1ef4e14..52cc66e 100644 --- a/src/link_layer/packet_consts.rs +++ b/src/link_layer/packet_consts.rs @@ -2,3 +2,19 @@ // Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT) pub const ETH_P_GEONET: u16 = 0x8947; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn eth_p_geonet_value() { + assert_eq!(ETH_P_GEONET, 0x8947); + } + + #[test] + fn eth_p_geonet_big_endian_bytes() { + let bytes = ETH_P_GEONET.to_be_bytes(); + assert_eq!(bytes, [0x89, 0x47]); + } +} diff --git a/src/link_layer/raw_link_layer.rs b/src/link_layer/raw_link_layer.rs index 81f8c50..fa1052b 100644 --- a/src/link_layer/raw_link_layer.rs +++ b/src/link_layer/raw_link_layer.rs @@ -290,3 +290,69 @@ impl RawLinkLayer { fn find_interface(name: &str) -> Option { datalink::interfaces().into_iter().find(|i| i.name == name) } + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::mpsc; + + #[test] + fn build_eth_frame_basic() { + let dest = [0xFF; 6]; + let src = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]; + let payload = [0xAA, 0xBB]; + let frame = build_eth_frame(dest, src, &payload); + // 6 dest + 6 src + 2 ethertype + 2 payload = 16 + assert_eq!(frame.len(), 16); + assert_eq!(&frame[0..6], &dest); + assert_eq!(&frame[6..12], &src); + assert_eq!(&frame[12..14], Ð_P_GEONET.to_be_bytes()); + assert_eq!(&frame[14..16], &payload); + } + + #[test] + fn build_eth_frame_empty_payload() { + let frame = build_eth_frame([0; 6], [0; 6], &[]); + assert_eq!(frame.len(), 14); + } + + #[test] + fn is_geonet_frame_valid() { + let frame = build_eth_frame([0xFF; 6], [0; 6], &[0xCA, 0xFE]); + assert!(is_geonet_frame(&frame)); + } + + #[test] + fn is_geonet_frame_too_short() { + assert!(!is_geonet_frame(&[0u8; 13])); + } + + #[test] + fn is_geonet_frame_wrong_ethertype() { + let mut frame = build_eth_frame([0xFF; 6], [0; 6], &[0xCA]); + // Overwrite ethertype + frame[12] = 0x08; + frame[13] = 0x00; + assert!(!is_geonet_frame(&frame)); + } + + #[test] + fn raw_link_layer_new() { + let (gn_tx, _) = mpsc::channel(); + let (_, gn_rx) = mpsc::channel(); + let rll = RawLinkLayer::new(gn_tx, gn_rx, "lo", [0x00; 6]); + assert_eq!(rll.iface_name, "lo"); + assert_eq!(rll.mac_address, [0x00; 6]); + assert!(!rll.stop_flag.load(Ordering::Relaxed)); + } + + #[test] + fn raw_link_layer_stop_flag_clone() { + let (gn_tx, _) = mpsc::channel(); + let (_, gn_rx) = mpsc::channel(); + let rll = RawLinkLayer::new(gn_tx, gn_rx, "eth0", [0xAA; 6]); + let flag = rll.stop_flag(); + flag.store(true, Ordering::Relaxed); + assert!(rll.stop_flag.load(Ordering::Relaxed)); + } +} diff --git a/src/security/certificate.rs b/src/security/certificate.rs index 99bfae0..8195da5 100644 --- a/src/security/certificate.rs +++ b/src/security/certificate.rs @@ -506,3 +506,229 @@ impl OwnCertificate { OwnCertificate { cert, key_id } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::security::ecdsa_backend::EcdsaBackend; + use rasn::prelude::*; + + fn make_root_tbs() -> ToBeSignedCertificate { + use crate::security::security_asn::ieee1609_dot2::{ + CertificateId, PsidGroupPermissions, SequenceOfPsidGroupPermissions, + SequenceOfAppExtensions, SequenceOfCertIssueExtensions, + SequenceOfCertRequestExtensions, EndEntityType, + }; + use crate::security::security_asn::ieee1609_dot2_base_types::{ + CrlSeries, Duration as AsnDuration, ValidityPeriod, + Time32, Uint16, Uint32, HashedId3, + }; + + let validity = ValidityPeriod::new( + Time32(Uint32(0)), + AsnDuration::years(Uint16(30)), + ); + + let perms = SequenceOfPsidGroupPermissions( + vec![PsidGroupPermissions::new( + SubjectPermissions::all(()), + Integer::from(1), + Integer::from(0), + { + let mut bits = FixedBitString::<8>::default(); + bits.set(0, true); + EndEntityType(bits) + }, + )] + .into(), + ); + + let placeholder_pk = PublicVerificationKey::ecdsaNistP256( + EccP256CurvePoint::x_only(vec![0u8; 32].into()), + ); + + ToBeSignedCertificate::new( + CertificateId::none(()), + HashedId3(FixedOctetString::from([0u8; 3])), + CrlSeries(Uint16(0)), + validity, + None, + None, + None, + Some(perms), + None, + None, + None, + VerificationKeyIndicator::verificationKey(placeholder_pk), + None, + SequenceOfAppExtensions(vec![].into()), + SequenceOfCertIssueExtensions(vec![].into()), + SequenceOfCertRequestExtensions(vec![].into()), + ) + } + + fn make_at_tbs() -> ToBeSignedCertificate { + use crate::security::security_asn::ieee1609_dot2::{ + CertificateId, PsidSsp, SequenceOfPsidSsp, + SequenceOfAppExtensions, SequenceOfCertIssueExtensions, + SequenceOfCertRequestExtensions, + }; + use crate::security::security_asn::ieee1609_dot2_base_types::{ + CrlSeries, Duration as AsnDuration, Psid, ValidityPeriod, + Time32, Uint16, Uint32, HashedId3, + }; + + let validity = ValidityPeriod::new( + Time32(Uint32(0)), + AsnDuration::years(Uint16(1)), + ); + + let app_perms = SequenceOfPsidSsp( + vec![PsidSsp::new(Psid(Integer::from(36_i64)), None)] + .into(), + ); + + let placeholder_pk = PublicVerificationKey::ecdsaNistP256( + EccP256CurvePoint::x_only(vec![0u8; 32].into()), + ); + + ToBeSignedCertificate::new( + CertificateId::none(()), + HashedId3(FixedOctetString::from([0u8; 3])), + CrlSeries(Uint16(0)), + validity, + None, + None, + Some(app_perms), + None, + None, + None, + None, + VerificationKeyIndicator::verificationKey(placeholder_pk), + None, + SequenceOfAppExtensions(vec![].into()), + SequenceOfCertIssueExtensions(vec![].into()), + SequenceOfCertRequestExtensions(vec![].into()), + ) + } + + #[test] + fn own_certificate_self_signed_roundtrip() { + let mut backend = EcdsaBackend::new(); + let own = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + assert!(own.verify(&backend)); + assert!(own.cert.is_self_signed()); + assert!(!own.cert.is_issued()); + } + + #[test] + fn own_certificate_issued_roundtrip() { + let mut backend = EcdsaBackend::new(); + let root = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + let at = OwnCertificate::initialize_issued(&mut backend, make_at_tbs(), &root); + assert!(at.verify(&backend)); + assert!(at.cert.is_issued()); + assert!(!at.cert.is_self_signed()); + } + + #[test] + fn certificate_hashedid8_and_hashedid3() { + let mut backend = EcdsaBackend::new(); + let own = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + let h8 = own.as_hashedid8(); + let h3 = own.cert.as_hashedid3(); + assert_eq!(h3, [h8[5], h8[6], h8[7]]); + } + + #[test] + fn certificate_encode_decode_roundtrip() { + let mut backend = EcdsaBackend::new(); + let own = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + let encoded = own.cert.encode().to_vec(); + let decoded = Certificate::from_bytes(&encoded, None); + assert_eq!(decoded.as_hashedid8(), own.as_hashedid8()); + } + + #[test] + fn certificate_from_asn_and_from_bytes_same_hash() { + let mut backend = EcdsaBackend::new(); + let own = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + let bytes = own.cert.encode().to_vec(); + let c1 = Certificate::from_bytes(&bytes, None); + let c2 = Certificate::from_asn(own.cert.inner.clone(), None); + assert_eq!(c1.as_hashedid8(), c2.as_hashedid8()); + } + + #[test] + fn certificate_check_corresponding_issuer() { + let mut backend = EcdsaBackend::new(); + let root = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + let at = OwnCertificate::initialize_issued(&mut backend, make_at_tbs(), &root); + assert!(at.cert.check_corresponding_issuer(&root.cert)); + } + + #[test] + fn certificate_signature_is_nist_p256() { + let mut backend = EcdsaBackend::new(); + let own = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + assert!(own.cert.signature_is_nist_p256()); + assert!(own.cert.verification_key_is_nist_p256()); + } + + #[test] + fn certificate_has_all_permissions() { + let mut backend = EcdsaBackend::new(); + let root = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + assert!(root.cert.has_all_permissions()); + } + + #[test] + fn certificate_get_list_of_its_aid() { + let mut backend = EcdsaBackend::new(); + let root = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + let at = OwnCertificate::initialize_issued(&mut backend, make_at_tbs(), &root); + let aids = at.get_list_of_its_aid(); + assert!(aids.contains(&36)); + } + + #[test] + fn certificate_sign_message() { + let mut backend = EcdsaBackend::new(); + let own = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + let sig = own.sign_message(&backend, b"test data"); + let pk = backend.get_public_key(own.key_id); + assert!(backend.verify_with_pk(b"test data", &sig, &pk)); + } + + #[test] + fn certificate_verify_type_explicit() { + let mut backend = EcdsaBackend::new(); + let own = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + assert_eq!(own.cert.base().r_type, CertificateType::explicit); + } + + #[test] + fn certificate_at_is_authorization_ticket() { + let mut backend = EcdsaBackend::new(); + let root = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + let at = OwnCertificate::initialize_issued(&mut backend, make_at_tbs(), &root); + assert!(at.cert.is_authorization_ticket()); + } + + #[test] + fn certificate_self_signed_get_issuer_hashedid8_none() { + let mut backend = EcdsaBackend::new(); + let own = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + assert!(own.cert.get_issuer_hashedid8().is_none()); + } + + #[test] + fn certificate_issued_get_issuer_hashedid8_some() { + let mut backend = EcdsaBackend::new(); + let root = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + let at = OwnCertificate::initialize_issued(&mut backend, make_at_tbs(), &root); + let issuer_h8 = at.cert.get_issuer_hashedid8(); + assert!(issuer_h8.is_some()); + assert_eq!(issuer_h8.unwrap(), root.as_hashedid8()); + } +} diff --git a/src/security/certificate_library.rs b/src/security/certificate_library.rs index 18318e0..ca1985d 100644 --- a/src/security/certificate_library.rs +++ b/src/security/certificate_library.rs @@ -186,3 +186,218 @@ impl CertificateLibrary { None } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::security::certificate::OwnCertificate; + use crate::security::ecdsa_backend::EcdsaBackend; + use crate::security::security_asn::ieee1609_dot2::{ + CertificateId, PsidGroupPermissions, PsidSsp, SequenceOfPsidGroupPermissions, + SequenceOfPsidSsp, SubjectPermissions, VerificationKeyIndicator, + SequenceOfAppExtensions, SequenceOfCertIssueExtensions, + SequenceOfCertRequestExtensions, EndEntityType, + }; + use crate::security::security_asn::ieee1609_dot2_base_types::{ + CrlSeries, Duration as AsnDuration, EccP256CurvePoint, Psid, + PublicVerificationKey, Time32, Uint16, Uint32, ValidityPeriod, HashedId3, + }; + use rasn::prelude::*; + + fn make_root_tbs() -> crate::security::security_asn::ieee1609_dot2::ToBeSignedCertificate { + let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(30))); + let perms = SequenceOfPsidGroupPermissions( + vec![PsidGroupPermissions::new( + SubjectPermissions::all(()), + Integer::from(1), + Integer::from(0), + { + let mut bits = FixedBitString::<8>::default(); + bits.set(0, true); + EndEntityType(bits) + }, + )] + .into(), + ); + let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only( + vec![0u8; 32].into(), + )); + crate::security::security_asn::ieee1609_dot2::ToBeSignedCertificate::new( + CertificateId::none(()), + HashedId3(FixedOctetString::from([0u8; 3])), + CrlSeries(Uint16(0)), + validity, + None, + None, + None, + Some(perms), + None, + None, + None, + VerificationKeyIndicator::verificationKey(pk), + None, + SequenceOfAppExtensions(vec![].into()), + SequenceOfCertIssueExtensions(vec![].into()), + SequenceOfCertRequestExtensions(vec![].into()), + ) + } + + fn make_at_tbs() -> crate::security::security_asn::ieee1609_dot2::ToBeSignedCertificate { + let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(1))); + let app_perms = + SequenceOfPsidSsp(vec![PsidSsp::new(Psid(Integer::from(36_i64)), None)].into()); + let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only( + vec![0u8; 32].into(), + )); + crate::security::security_asn::ieee1609_dot2::ToBeSignedCertificate::new( + CertificateId::none(()), + HashedId3(FixedOctetString::from([0u8; 3])), + CrlSeries(Uint16(0)), + validity, + None, + None, + Some(app_perms), + None, + None, + None, + None, + VerificationKeyIndicator::verificationKey(pk), + None, + SequenceOfAppExtensions(vec![].into()), + SequenceOfCertIssueExtensions(vec![].into()), + SequenceOfCertRequestExtensions(vec![].into()), + ) + } + + fn make_chain( + backend: &mut EcdsaBackend, + ) -> (OwnCertificate, OwnCertificate, OwnCertificate) { + let root = OwnCertificate::initialize_self_signed(backend, make_root_tbs()); + let aa = OwnCertificate::initialize_issued(backend, make_root_tbs(), &root); + let at = OwnCertificate::initialize_issued(backend, make_at_tbs(), &aa); + (root, aa, at) + } + + #[test] + fn library_new_empty() { + let backend = EcdsaBackend::new(); + let lib = CertificateLibrary::new(&backend, vec![], vec![], vec![]); + assert!(lib.own_certificates.is_empty()); + assert!(lib.known_root_certificates.is_empty()); + assert!(lib.known_authorization_authorities.is_empty()); + assert!(lib.known_authorization_tickets.is_empty()); + } + + #[test] + fn library_add_root_certificate() { + let mut backend = EcdsaBackend::new(); + let root = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + let lib = CertificateLibrary::new(&backend, vec![root.cert.clone()], vec![], vec![]); + assert_eq!(lib.known_root_certificates.len(), 1); + } + + #[test] + fn library_add_aa_and_at() { + let mut backend = EcdsaBackend::new(); + let (root, aa, at) = make_chain(&mut backend); + let lib = CertificateLibrary::new( + &backend, + vec![root.cert.clone()], + vec![aa.cert.clone()], + vec![at.cert.clone()], + ); + assert_eq!(lib.known_root_certificates.len(), 1); + assert_eq!(lib.known_authorization_authorities.len(), 1); + assert_eq!(lib.known_authorization_tickets.len(), 1); + } + + #[test] + fn library_get_authorization_ticket_by_hashedid8() { + let mut backend = EcdsaBackend::new(); + let (root, aa, at) = make_chain(&mut backend); + let lib = CertificateLibrary::new( + &backend, + vec![root.cert.clone()], + vec![aa.cert.clone()], + vec![at.cert.clone()], + ); + let h8 = at.as_hashedid8(); + assert!(lib.get_authorization_ticket_by_hashedid8(&h8).is_some()); + } + + #[test] + fn library_get_ca_certificate_by_hashedid3() { + let mut backend = EcdsaBackend::new(); + let (root, aa, _at) = make_chain(&mut backend); + let lib = CertificateLibrary::new( + &backend, + vec![root.cert.clone()], + vec![aa.cert.clone()], + vec![], + ); + let h3 = aa.cert.as_hashedid3(); + assert!(lib.get_ca_certificate_by_hashedid3(&h3).is_some()); + } + + #[test] + fn library_get_issuer_certificate() { + let mut backend = EcdsaBackend::new(); + let (root, aa, at) = make_chain(&mut backend); + let lib = CertificateLibrary::new( + &backend, + vec![root.cert.clone()], + vec![aa.cert.clone()], + vec![at.cert.clone()], + ); + let issuer = lib.get_issuer_certificate(&at.cert); + assert!(issuer.is_some()); + } + + #[test] + fn library_add_own_certificate() { + let mut backend = EcdsaBackend::new(); + let (root, aa, at) = make_chain(&mut backend); + let mut lib = CertificateLibrary::new( + &backend, + vec![root.cert.clone()], + vec![aa.cert.clone()], + vec![], + ); + lib.add_own_certificate(&backend, at); + assert_eq!(lib.own_certificates.len(), 1); + } + + #[test] + fn library_verify_sequence_single_known_at() { + let mut backend = EcdsaBackend::new(); + let (root, aa, at) = make_chain(&mut backend); + let mut lib = CertificateLibrary::new( + &backend, + vec![root.cert.clone()], + vec![aa.cert.clone()], + vec![at.cert.clone()], + ); + let result = lib.verify_sequence_of_certificates(&[at.cert.clone()], &backend); + assert!(result.is_some()); + } + + #[test] + fn library_verify_sequence_empty() { + let backend = EcdsaBackend::new(); + let mut lib = CertificateLibrary::new(&backend, vec![], vec![], vec![]); + let result = lib.verify_sequence_of_certificates(&[], &backend); + assert!(result.is_none()); + } + + #[test] + fn library_duplicate_aa_not_added_twice() { + let mut backend = EcdsaBackend::new(); + let root = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + let aa = OwnCertificate::initialize_issued(&mut backend, make_root_tbs(), &root); + let mut lib = + CertificateLibrary::new(&backend, vec![root.cert.clone()], vec![], vec![]); + lib.add_authorization_authority(&backend, aa.cert.clone()); + lib.add_authorization_authority(&backend, aa.cert.clone()); + assert_eq!(lib.known_authorization_authorities.len(), 1); + } +} diff --git a/src/security/ecdsa_backend.rs b/src/security/ecdsa_backend.rs index ee996d5..efbaf35 100644 --- a/src/security/ecdsa_backend.rs +++ b/src/security/ecdsa_backend.rs @@ -145,3 +145,114 @@ impl Default for EcdsaBackend { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn backend_new_empty() { + let backend = EcdsaBackend::new(); + assert_eq!(backend.next_id, 0); + assert!(backend.keys.is_empty()); + } + + #[test] + fn backend_default() { + let backend = EcdsaBackend::default(); + assert_eq!(backend.next_id, 0); + } + + #[test] + fn create_key_returns_sequential_ids() { + let mut backend = EcdsaBackend::new(); + assert_eq!(backend.create_key(), 0); + assert_eq!(backend.create_key(), 1); + assert_eq!(backend.create_key(), 2); + } + + #[test] + fn get_public_key_uncompressed_p256() { + let mut backend = EcdsaBackend::new(); + let id = backend.create_key(); + let pk = backend.get_public_key(id); + match pk { + PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::uncompressedP256(u)) => { + assert_eq!(u.x.as_ref().len(), 32); + assert_eq!(u.y.as_ref().len(), 32); + } + _ => panic!("Expected uncompressedP256"), + } + } + + #[test] + fn sign_and_verify() { + let mut backend = EcdsaBackend::new(); + let id = backend.create_key(); + let data = b"Hello, ETSI C-ITS!"; + let signature = backend.sign(data, id); + let pk = backend.get_public_key(id); + assert!(backend.verify_with_pk(data, &signature, &pk)); + } + + #[test] + fn sign_wrong_data_fails_verification() { + let mut backend = EcdsaBackend::new(); + let id = backend.create_key(); + let signature = backend.sign(b"correct data", id); + let pk = backend.get_public_key(id); + assert!(!backend.verify_with_pk(b"wrong data", &signature, &pk)); + } + + #[test] + fn sign_wrong_key_fails_verification() { + let mut backend = EcdsaBackend::new(); + let id1 = backend.create_key(); + let id2 = backend.create_key(); + let data = b"test data"; + let signature = backend.sign(data, id1); + let pk2 = backend.get_public_key(id2); + assert!(!backend.verify_with_pk(data, &signature, &pk2)); + } + + #[test] + fn export_import_key_roundtrip() { + let mut backend = EcdsaBackend::new(); + let id = backend.create_key(); + let exported = backend.export_signing_key(id); + assert_eq!(exported.len(), 32); + + let mut backend2 = EcdsaBackend::new(); + let id2 = backend2.import_signing_key(&exported); + + // Sign with original, verify with imported (same key) + let data = b"roundtrip test"; + let sig = backend.sign(data, id); + let pk = backend2.get_public_key(id2); + assert!(backend2.verify_with_pk(data, &sig, &pk)); + } + + #[test] + fn hash_to_hashedid8_deterministic() { + let data = b"certificate bytes"; + let h1 = EcdsaBackend::hash_to_hashedid8(data); + let h2 = EcdsaBackend::hash_to_hashedid8(data); + assert_eq!(h1, h2); + assert_eq!(h1.len(), 8); + } + + #[test] + fn hash_to_hashedid8_different_input() { + let h1 = EcdsaBackend::hash_to_hashedid8(b"input1"); + let h2 = EcdsaBackend::hash_to_hashedid8(b"input2"); + assert_ne!(h1, h2); + } + + #[test] + fn hash_to_hashedid8_is_last_8_bytes_of_sha256() { + let data = b"test"; + let digest = Sha256::digest(data); + let expected: [u8; 8] = digest[24..32].try_into().unwrap(); + assert_eq!(EcdsaBackend::hash_to_hashedid8(data), expected); + } +} diff --git a/src/security/sign_service.rs b/src/security/sign_service.rs index 8b23aa4..3ef9f1f 100644 --- a/src/security/sign_service.rs +++ b/src/security/sign_service.rs @@ -300,3 +300,194 @@ impl SignService { self.cert_library.add_own_certificate(&self.backend, cert); } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::security::certificate::OwnCertificate; + use crate::security::certificate_library::CertificateLibrary; + use crate::security::ecdsa_backend::EcdsaBackend; + use crate::security::security_asn::ieee1609_dot2::{ + CertificateId, PsidGroupPermissions, PsidSsp, SequenceOfPsidGroupPermissions, + SequenceOfPsidSsp, SubjectPermissions, ToBeSignedCertificate, VerificationKeyIndicator, + SequenceOfAppExtensions, SequenceOfCertIssueExtensions, + SequenceOfCertRequestExtensions, EndEntityType, + }; + use crate::security::security_asn::ieee1609_dot2_base_types::{ + CrlSeries, Duration as AsnDuration, EccP256CurvePoint, + PublicVerificationKey, Time32, Uint16, Uint32, ValidityPeriod, HashedId3, + }; + use crate::security::sn_sap::{GenerationLocation, SNSignRequest}; + + fn make_root_tbs() -> ToBeSignedCertificate { + let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(30))); + let perms = SequenceOfPsidGroupPermissions( + vec![PsidGroupPermissions::new( + SubjectPermissions::all(()), + Integer::from(1), + Integer::from(0), + { + let mut bits = FixedBitString::<8>::default(); + bits.set(0, true); + EndEntityType(bits) + }, + )] + .into(), + ); + let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only( + vec![0u8; 32].into(), + )); + ToBeSignedCertificate::new( + CertificateId::none(()), + HashedId3(FixedOctetString::from([0u8; 3])), + CrlSeries(Uint16(0)), + validity, + None, + None, + None, + Some(perms), + None, + None, + None, + VerificationKeyIndicator::verificationKey(pk), + None, + SequenceOfAppExtensions(vec![].into()), + SequenceOfCertIssueExtensions(vec![].into()), + SequenceOfCertRequestExtensions(vec![].into()), + ) + } + + fn make_at_tbs(its_aid: i64) -> ToBeSignedCertificate { + let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(1))); + let app_perms = + SequenceOfPsidSsp(vec![PsidSsp::new(Psid(Integer::from(its_aid)), None)].into()); + let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only( + vec![0u8; 32].into(), + )); + ToBeSignedCertificate::new( + CertificateId::none(()), + HashedId3(FixedOctetString::from([0u8; 3])), + CrlSeries(Uint16(0)), + validity, + None, + None, + Some(app_perms), + None, + None, + None, + None, + VerificationKeyIndicator::verificationKey(pk), + None, + SequenceOfAppExtensions(vec![].into()), + SequenceOfCertIssueExtensions(vec![].into()), + SequenceOfCertRequestExtensions(vec![].into()), + ) + } + + fn make_sign_service() -> SignService { + let mut backend = EcdsaBackend::new(); + let root = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + let aa = OwnCertificate::initialize_issued(&mut backend, make_root_tbs(), &root); + let at_cam = OwnCertificate::initialize_issued(&mut backend, make_at_tbs(36), &aa); + let at_denm = OwnCertificate::initialize_issued(&mut backend, make_at_tbs(37), &aa); + + let lib = CertificateLibrary::new( + &backend, + vec![root.cert.clone()], + vec![aa.cert.clone()], + vec![], + ); + let mut svc = SignService::new(backend, lib); + svc.add_own_certificate(at_cam); + svc.add_own_certificate(at_denm); + svc + } + + #[test] + fn sign_cam_produces_nonempty_message() { + let mut svc = make_sign_service(); + let req = SNSignRequest { + tbs_message: vec![0xCA, 0xFE], + its_aid: 36, + permissions: vec![], + generation_location: None, + }; + let confirm = svc.sign_request(&req); + assert!(!confirm.sec_message.is_empty()); + } + + #[test] + fn sign_denm_produces_nonempty_message() { + let mut svc = make_sign_service(); + let req = SNSignRequest { + tbs_message: vec![0xDE, 0x01], + its_aid: 37, + permissions: vec![], + generation_location: Some(GenerationLocation { + latitude: 415520000, + longitude: 21340000, + elevation: 0xF000, + }), + }; + let confirm = svc.sign_request(&req); + assert!(!confirm.sec_message.is_empty()); + } + + #[test] + fn sign_other_aid_produces_nonempty_message() { + // Add a cert for a custom AID + let mut backend = EcdsaBackend::new(); + let root = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + let aa = OwnCertificate::initialize_issued(&mut backend, make_root_tbs(), &root); + let at = OwnCertificate::initialize_issued(&mut backend, make_at_tbs(99), &aa); + let lib = CertificateLibrary::new( + &backend, + vec![root.cert.clone()], + vec![aa.cert.clone()], + vec![], + ); + let mut svc = SignService::new(backend, lib); + svc.add_own_certificate(at); + + let req = SNSignRequest { + tbs_message: vec![0x01, 0x02], + its_aid: 99, + permissions: vec![], + generation_location: None, + }; + let confirm = svc.sign_request(&req); + assert!(!confirm.sec_message.is_empty()); + } + + #[test] + fn sign_cam_first_call_includes_certificate() { + let mut svc = make_sign_service(); + let req = SNSignRequest { + tbs_message: vec![0xCA], + its_aid: 36, + permissions: vec![], + generation_location: None, + }; + // First CAM should include full certificate (signer = certificate) + let confirm1 = svc.sign_request(&req); + assert!(!confirm1.sec_message.is_empty()); + } + + #[test] + fn notify_unknown_at() { + let mut svc = make_sign_service(); + let h8 = [1, 2, 3, 4, 5, 6, 7, 8]; + svc.notify_unknown_at(&h8); + assert_eq!(svc.unknown_ats.len(), 1); + assert_eq!(svc.unknown_ats[0], [6, 7, 8]); // last 3 bytes + } + + #[test] + fn notify_unknown_at_no_duplicates() { + let mut svc = make_sign_service(); + let h8 = [1, 2, 3, 4, 5, 6, 7, 8]; + svc.notify_unknown_at(&h8); + svc.notify_unknown_at(&h8); + assert_eq!(svc.unknown_ats.len(), 1); + } +} diff --git a/src/security/sn_sap.rs b/src/security/sn_sap.rs index 1835a01..0fa9949 100644 --- a/src/security/sn_sap.rs +++ b/src/security/sn_sap.rs @@ -69,3 +69,99 @@ pub enum SecurityProfile { DecentralizedEnvironmentalNotificationMessage = 2, VruAwarenessMessage = 3, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn security_profile_values() { + assert_eq!(SecurityProfile::NoSecurity as u8, 0); + assert_eq!(SecurityProfile::CooperativeAwarenessMessage as u8, 1); + assert_eq!(SecurityProfile::DecentralizedEnvironmentalNotificationMessage as u8, 2); + assert_eq!(SecurityProfile::VruAwarenessMessage as u8, 3); + } + + #[test] + fn security_profile_equality() { + assert_eq!(SecurityProfile::NoSecurity, SecurityProfile::NoSecurity); + assert_ne!(SecurityProfile::NoSecurity, SecurityProfile::CooperativeAwarenessMessage); + } + + #[test] + fn report_verify_variants() { + let variants = vec![ + ReportVerify::Success, + ReportVerify::FalseSignature, + ReportVerify::InvalidCertificate, + ReportVerify::RevokedCertificate, + ReportVerify::InconsistentChain, + ReportVerify::InvalidTimestamp, + ReportVerify::DuplicateMessage, + ReportVerify::InvalidMobilityData, + ReportVerify::UnsignedMessage, + ReportVerify::SignerCertificateNotFound, + ReportVerify::UnsupportedSignerIdentifierType, + ReportVerify::IncompatibleProtocol, + ]; + assert_eq!(variants.len(), 12); + } + + #[test] + fn report_verify_equality() { + assert_eq!(ReportVerify::Success, ReportVerify::Success); + assert_ne!(ReportVerify::Success, ReportVerify::FalseSignature); + } + + #[test] + fn sn_sign_request_construction() { + let req = SNSignRequest { + tbs_message: vec![1, 2, 3], + its_aid: 36, + permissions: vec![0xFF], + generation_location: Some(GenerationLocation { + latitude: 415520000, + longitude: 21340000, + elevation: 1200, + }), + }; + assert_eq!(req.its_aid, 36); + assert!(req.generation_location.is_some()); + } + + #[test] + fn sn_sign_request_no_location() { + let req = SNSignRequest { + tbs_message: vec![], + its_aid: 37, + permissions: vec![], + generation_location: None, + }; + assert!(req.generation_location.is_none()); + } + + #[test] + fn sn_verify_confirm_construction() { + let confirm = SNVerifyConfirm { + report: ReportVerify::Success, + certificate_id: vec![1, 2, 3, 4, 5, 6, 7, 8], + its_aid: 36, + permissions: vec![0xFF], + plain_message: vec![0xCA, 0xFE], + }; + assert_eq!(confirm.report, ReportVerify::Success); + assert_eq!(confirm.plain_message.len(), 2); + } + + #[test] + fn generation_location_fields() { + let loc = GenerationLocation { + latitude: 415520000, + longitude: 21340000, + elevation: 0xF000, + }; + assert_eq!(loc.latitude, 415520000); + assert_eq!(loc.longitude, 21340000); + assert_eq!(loc.elevation, 0xF000); + } +} diff --git a/src/security/time_service.rs b/src/security/time_service.rs index f145d2f..c542390 100644 --- a/src/security/time_service.rs +++ b/src/security/time_service.rs @@ -31,3 +31,39 @@ pub fn timestamp_its_microseconds() -> u64 { let its_secs = now.as_secs() - ITS_EPOCH + ELAPSED_SECONDS; its_secs * 1_000_000 + u64::from(now.subsec_micros()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn unix_time_secs_positive() { + let t = unix_time_secs(); + // Should be well past the ITS epoch + assert!(t > ITS_EPOCH as f64); + } + + #[test] + fn timestamp_its_microseconds_positive() { + let t = timestamp_its_microseconds(); + assert!(t > 0); + } + + #[test] + fn timestamp_its_microseconds_monotonic() { + let t1 = timestamp_its_microseconds(); + std::thread::sleep(std::time::Duration::from_millis(1)); + let t2 = timestamp_its_microseconds(); + assert!(t2 > t1); + } + + #[test] + fn its_epoch_constant() { + assert_eq!(ITS_EPOCH, 1_072_915_200); + } + + #[test] + fn elapsed_seconds_constant() { + assert_eq!(ELAPSED_SECONDS, 5); + } +} diff --git a/src/security/verify_service.rs b/src/security/verify_service.rs index 5233061..64a7175 100644 --- a/src/security/verify_service.rs +++ b/src/security/verify_service.rs @@ -344,3 +344,264 @@ pub enum VerifyEvent { InlineP2pcdRequest(Vec<[u8; 3]>), ReceivedCaCertificate(Certificate), } + +#[cfg(test)] +mod tests { + use super::*; + use crate::security::certificate::OwnCertificate; + use crate::security::certificate_library::CertificateLibrary; + use crate::security::ecdsa_backend::EcdsaBackend; + use crate::security::security_asn::ieee1609_dot2::{ + CertificateId, PsidGroupPermissions, PsidSsp, SequenceOfPsidGroupPermissions, + SequenceOfPsidSsp, SubjectPermissions, ToBeSignedCertificate, VerificationKeyIndicator, + SequenceOfAppExtensions, SequenceOfCertIssueExtensions, + SequenceOfCertRequestExtensions, EndEntityType, + }; + use crate::security::security_asn::ieee1609_dot2_base_types::{ + CrlSeries, Duration as AsnDuration, EccP256CurvePoint, Psid, + PublicVerificationKey, Time32, Uint16, Uint32, ValidityPeriod, HashedId3, + }; + use crate::security::sign_service::SignService; + use crate::security::sn_sap::{GenerationLocation, SNSignRequest, SNVerifyRequest}; + + fn make_root_tbs() -> ToBeSignedCertificate { + let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(30))); + let perms = SequenceOfPsidGroupPermissions( + vec![PsidGroupPermissions::new( + SubjectPermissions::all(()), + Integer::from(1), + Integer::from(0), + { + let mut bits = FixedBitString::<8>::default(); + bits.set(0, true); + EndEntityType(bits) + }, + )] + .into(), + ); + let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only( + vec![0u8; 32].into(), + )); + ToBeSignedCertificate::new( + CertificateId::none(()), + HashedId3(FixedOctetString::from([0u8; 3])), + CrlSeries(Uint16(0)), + validity, + None, + None, + None, + Some(perms), + None, + None, + None, + VerificationKeyIndicator::verificationKey(pk), + None, + SequenceOfAppExtensions(vec![].into()), + SequenceOfCertIssueExtensions(vec![].into()), + SequenceOfCertRequestExtensions(vec![].into()), + ) + } + + fn make_at_tbs(its_aid: i64) -> ToBeSignedCertificate { + let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(1))); + let app_perms = + SequenceOfPsidSsp(vec![PsidSsp::new(Psid(Integer::from(its_aid)), None)].into()); + let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only( + vec![0u8; 32].into(), + )); + ToBeSignedCertificate::new( + CertificateId::none(()), + HashedId3(FixedOctetString::from([0u8; 3])), + CrlSeries(Uint16(0)), + validity, + None, + None, + Some(app_perms), + None, + None, + None, + None, + VerificationKeyIndicator::verificationKey(pk), + None, + SequenceOfAppExtensions(vec![].into()), + SequenceOfCertIssueExtensions(vec![].into()), + SequenceOfCertRequestExtensions(vec![].into()), + ) + } + + fn sign_cam_message(svc: &mut SignService) -> Vec { + let req = SNSignRequest { + tbs_message: vec![0xCA, 0xFE], + its_aid: 36, + permissions: vec![], + generation_location: None, + }; + svc.sign_request(&req).sec_message + } + + fn sign_denm_message(svc: &mut SignService) -> Vec { + let req = SNSignRequest { + tbs_message: vec![0xDE, 0x01], + its_aid: 37, + permissions: vec![], + generation_location: Some(GenerationLocation { + latitude: 415520000, + longitude: 21340000, + elevation: 0xF000, + }), + }; + svc.sign_request(&req).sec_message + } + + fn setup() -> (EcdsaBackend, CertificateLibrary, SignService) { + let mut backend = EcdsaBackend::new(); + let root = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); + let aa = OwnCertificate::initialize_issued(&mut backend, make_root_tbs(), &root); + let at_cam = OwnCertificate::initialize_issued(&mut backend, make_at_tbs(36), &aa); + let at_denm = OwnCertificate::initialize_issued(&mut backend, make_at_tbs(37), &aa); + + let lib = CertificateLibrary::new( + &backend, + vec![root.cert.clone()], + vec![aa.cert.clone()], + vec![], + ); + let mut svc = SignService::new(backend, lib); + svc.add_own_certificate(at_cam); + svc.add_own_certificate(at_denm); + + // Build verify library from certs stored in sign service + let verify_lib = CertificateLibrary::new( + &svc.backend, + svc.cert_library + .known_root_certificates + .values() + .cloned() + .collect(), + svc.cert_library + .known_authorization_authorities + .values() + .cloned() + .collect(), + svc.cert_library + .known_authorization_tickets + .values() + .cloned() + .collect(), + ); + + ( + EcdsaBackend::new(), + verify_lib, + svc, + ) + } + + #[test] + fn verify_cam_signed_message() { + let (_, _, mut svc) = setup(); + let sec_msg = sign_cam_message(&mut svc); + + // Verify using sign service's own backend and cert library + let req = SNVerifyRequest { + message: sec_msg, + }; + let mut verify_lib = CertificateLibrary::new( + &svc.backend, + svc.cert_library + .known_root_certificates + .values() + .cloned() + .collect(), + svc.cert_library + .known_authorization_authorities + .values() + .cloned() + .collect(), + svc.cert_library + .known_authorization_tickets + .values() + .cloned() + .collect(), + ); + let (confirm, _events) = verify_message(&req, &svc.backend, &mut verify_lib); + assert_eq!(confirm.report, ReportVerify::Success); + assert_eq!(confirm.its_aid, 36); + assert_eq!(confirm.plain_message, vec![0xCA, 0xFE]); + } + + #[test] + fn verify_denm_signed_message() { + let (_, _, mut svc) = setup(); + let sec_msg = sign_denm_message(&mut svc); + + let req = SNVerifyRequest { + message: sec_msg, + }; + let mut verify_lib = CertificateLibrary::new( + &svc.backend, + svc.cert_library + .known_root_certificates + .values() + .cloned() + .collect(), + svc.cert_library + .known_authorization_authorities + .values() + .cloned() + .collect(), + svc.cert_library + .known_authorization_tickets + .values() + .cloned() + .collect(), + ); + let (confirm, _events) = verify_message(&req, &svc.backend, &mut verify_lib); + assert_eq!(confirm.report, ReportVerify::Success); + assert_eq!(confirm.its_aid, 37); + } + + #[test] + fn verify_tampered_message_fails() { + let (_, _, mut svc) = setup(); + let mut sec_msg = sign_cam_message(&mut svc); + + // Tamper with the message + if let Some(b) = sec_msg.last_mut() { + *b ^= 0xFF; + } + + let req = SNVerifyRequest { + message: sec_msg, + }; + let mut verify_lib = CertificateLibrary::new( + &svc.backend, + svc.cert_library + .known_root_certificates + .values() + .cloned() + .collect(), + svc.cert_library + .known_authorization_authorities + .values() + .cloned() + .collect(), + svc.cert_library + .known_authorization_tickets + .values() + .cloned() + .collect(), + ); + let (confirm, _) = verify_message(&req, &svc.backend, &mut verify_lib); + assert_ne!(confirm.report, ReportVerify::Success); + } + + #[test] + fn verify_event_variants() { + let ev1 = VerifyEvent::UnknownAt([1; 8]); + let ev2 = VerifyEvent::InlineP2pcdRequest(vec![[1, 2, 3]]); + // Just ensure they can be constructed and formatted + assert!(format!("{:?}", ev1).contains("UnknownAt")); + assert!(format!("{:?}", ev2).contains("InlineP2pcdRequest")); + } +} From 25917017094c36eebb9c60e9811510c4111bd317 Mon Sep 17 00:00:00 2001 From: Jordi Marias I Parella Date: Tue, 7 Apr 2026 11:16:56 +0200 Subject: [PATCH 3/6] Fixing warnings --- examples/bench_congestion.rs | 2 +- examples/secured_cam_sender_receiver.rs | 2 +- src/btp/router.rs | 9 +++------ .../denm_transmission.rs | 3 +-- .../mod.rs | 4 ++-- .../vru_awareness_service/vam_transmission.rs | 2 +- src/geonet/common_header.rs | 5 ++--- src/security/certificate.rs | 16 +++++----------- src/security/ecdsa_backend.rs | 1 - src/security/sign_service.rs | 5 ++--- src/security/verify_service.rs | 4 +--- 11 files changed, 19 insertions(+), 34 deletions(-) diff --git a/examples/bench_congestion.rs b/examples/bench_congestion.rs index 6090042..5477277 100644 --- a/examples/bench_congestion.rs +++ b/examples/bench_congestion.rs @@ -33,7 +33,7 @@ use rustflexstack::facilities::ca_basic_service::cam_coder::{ use rustflexstack::facilities::decentralized_environmental_notification_service::denm_coder::CauseCodeChoice; use rustflexstack::facilities::decentralized_environmental_notification_service::{ denm_coder::AccidentSubCauseCode, DENRequest, DecentralizedEnvironmentalNotificationService, - DenmCoder, VehicleData as DenVehicleData, + VehicleData as DenVehicleData, }; use rustflexstack::facilities::vru_awareness_service::vam_coder::{ generation_delta_time_now as vam_gdt, vam_header, AccelerationConfidence as VamAccelConf, diff --git a/examples/secured_cam_sender_receiver.rs b/examples/secured_cam_sender_receiver.rs index 3c8fbd6..eff8a65 100644 --- a/examples/secured_cam_sender_receiver.rs +++ b/examples/secured_cam_sender_receiver.rs @@ -278,7 +278,7 @@ fn main() { message: sec_message.to_vec(), }; - let (confirm, events) = { + let (confirm, _events) = { let mut svc = sign_svc_rx.lock().unwrap(); let svc = &mut *svc; let result = verify_message( diff --git a/src/btp/router.rs b/src/btp/router.rs index 2b1d998..b7bcbd3 100644 --- a/src/btp/router.rs +++ b/src/btp/router.rs @@ -44,7 +44,7 @@ pub enum BTPRouterInput { // ------------------------------------------------------------------ pub struct Router { - mib: Mib, + _mib: Mib, port_callbacks: HashMap>, gn_request_tx: Sender, } @@ -87,7 +87,7 @@ impl BTPRouterHandle { impl Router { pub fn new(mib: Mib, gn_request_tx: Sender) -> Self { Router { - mib, + _mib: mib, port_callbacks: HashMap::new(), gn_request_tx, } @@ -235,14 +235,11 @@ impl Router { mod tests { use super::*; use crate::btp::btp_header::{BTPAHeader, BTPBHeader}; - use crate::geonet::gn_address::{GNAddress, M, MID, ST}; - use crate::geonet::mib::Mib; use crate::geonet::position_vector::LongPositionVector; use crate::geonet::service_access_point::{ - Area, CommonNH, CommunicationProfile, GNDataIndication, HeaderSubType, HeaderType, + CommonNH, GNDataIndication, HeaderSubType, HeaderType, PacketTransportType, TopoBroadcastHST, TrafficClass, UnspecifiedHST, }; - use crate::security::sn_sap::SecurityProfile; use std::sync::mpsc; use std::time::Duration; diff --git a/src/facilities/decentralized_environmental_notification_service/denm_transmission.rs b/src/facilities/decentralized_environmental_notification_service/denm_transmission.rs index f6aacbb..dbf1d23 100644 --- a/src/facilities/decentralized_environmental_notification_service/denm_transmission.rs +++ b/src/facilities/decentralized_environmental_notification_service/denm_transmission.rs @@ -23,7 +23,7 @@ use super::denm_coder::{ DenmPayload, HeadingValue, InformationQuality, Latitude, LocationContainer, Longitude, ManagementContainer, Path, PathPoint, PosConfidenceEllipse, ReferencePosition, SemiAxisLength, SequenceNumber, SituationContainer, Speed, SpeedConfidence, SpeedValue, StationId, StationType, - SubCauseCodeType, Traces, TrafficParticipantType, Wgs84Angle, Wgs84AngleConfidence, + Traces, TrafficParticipantType, Wgs84Angle, Wgs84AngleConfidence, Wgs84AngleValue, }; use crate::btp::router::BTPRouterHandle; @@ -35,7 +35,6 @@ use crate::geonet::service_access_point::{ PacketTransportType, TrafficClass, }; use std::sync::atomic::{AtomicU16, Ordering}; -use std::sync::Arc; use std::thread; use std::time::Duration; diff --git a/src/facilities/decentralized_environmental_notification_service/mod.rs b/src/facilities/decentralized_environmental_notification_service/mod.rs index eccad19..ae71ee4 100644 --- a/src/facilities/decentralized_environmental_notification_service/mod.rs +++ b/src/facilities/decentralized_environmental_notification_service/mod.rs @@ -64,7 +64,7 @@ use std::sync::mpsc::{self, Receiver, Sender}; pub struct DecentralizedEnvironmentalNotificationService { tx_management: DENMTransmissionManagement, /// Reception management writes decoded DENMs into this sender. - denm_tx: Sender, + _denm_tx: Sender, } impl DecentralizedEnvironmentalNotificationService { @@ -84,7 +84,7 @@ impl DecentralizedEnvironmentalNotificationService { let svc = DecentralizedEnvironmentalNotificationService { tx_management, - denm_tx, + _denm_tx: denm_tx, }; (svc, denm_rx) } diff --git a/src/facilities/vru_awareness_service/vam_transmission.rs b/src/facilities/vru_awareness_service/vam_transmission.rs index 5cde233..3dc00e3 100644 --- a/src/facilities/vru_awareness_service/vam_transmission.rs +++ b/src/facilities/vru_awareness_service/vam_transmission.rs @@ -243,7 +243,7 @@ impl VAMTransmissionManagement { gps_rx: Receiver, ) { thread::spawn(move || { - let mut t_gen_vam_ms: u64 = T_GEN_VAM_MIN_MS; + let t_gen_vam_ms: u64 = T_GEN_VAM_MIN_MS; let _ = t_gen_vam_ms; // used for future DCC integration // State of last transmitted VAM diff --git a/src/geonet/common_header.rs b/src/geonet/common_header.rs index df66545..8251bfe 100644 --- a/src/geonet/common_header.rs +++ b/src/geonet/common_header.rs @@ -102,10 +102,9 @@ mod tests { use super::*; use crate::geonet::mib::Mib; use crate::geonet::service_access_point::{ - CommonNH, GNDataRequest, HeaderSubType, HeaderType, PacketTransportType, - TopoBroadcastHST, GeoBroadcastHST, TrafficClass, Area, CommunicationProfile, + Area, CommonNH, CommunicationProfile, GNDataRequest, HeaderSubType, HeaderType, + PacketTransportType, TopoBroadcastHST, TrafficClass, }; - use crate::geonet::gn_address::{GNAddress, M, MID, ST}; use crate::security::sn_sap::SecurityProfile; fn make_gn_request() -> GNDataRequest { diff --git a/src/security/certificate.rs b/src/security/certificate.rs index 8195da5..47e74ed 100644 --- a/src/security/certificate.rs +++ b/src/security/certificate.rs @@ -3,23 +3,18 @@ //! Provides [`Certificate`] (read-only wrapper) and [`OwnCertificate`] //! (includes the private-key identifier for signing). -use rasn::prelude::*; -use sha2::{Digest, Sha256}; - use crate::security::ecdsa_backend::EcdsaBackend; use crate::security::security_asn::etsi_ts103097_module::EtsiTs103097Certificate; use crate::security::security_asn::ieee1609_dot2::{ - CertificateBase, CertificateType, IssuerIdentifier, SignerIdentifier, - Ieee1609Dot2Content, Ieee1609Dot2Data, SignedData, ToBeSignedCertificate, - ToBeSignedData, SignedDataPayload, + CertificateBase, CertificateType, IssuerIdentifier, + Ieee1609Dot2Data, ToBeSignedCertificate, + ToBeSignedData, }; use crate::security::security_asn::ieee1609_dot2_base_types::Signature as Ieee1609Signature; -use crate::security::security_asn::ieee1609_dot2::{ - Certificate as AsnCertificate, SequenceOfCertificate, -}; +use crate::security::security_asn::ieee1609_dot2::Certificate as AsnCertificate; use crate::security::security_asn::ieee1609_dot2_base_types::{ EccP256CurvePoint, EcdsaP256Signature, HashAlgorithm, HashedId8, - PublicVerificationKey, Uint8, Opaque, + PublicVerificationKey, Uint8, }; use crate::security::security_asn::ieee1609_dot2::{ SubjectPermissions, VerificationKeyIndicator, @@ -316,7 +311,6 @@ impl Certificate { return false; } } - _ => return false, } if self.issuer.is_some() && self.is_issued() { return self.verify_issued(backend); diff --git a/src/security/ecdsa_backend.rs b/src/security/ecdsa_backend.rs index efbaf35..91ce234 100644 --- a/src/security/ecdsa_backend.rs +++ b/src/security/ecdsa_backend.rs @@ -11,7 +11,6 @@ use p256::ecdsa::signature::Signer; use p256::ecdsa::{Signature, SigningKey, VerifyingKey}; use p256::ecdsa::signature::Verifier; -use p256::elliptic_curve::sec1::ToEncodedPoint; use sha2::{Digest, Sha256}; use std::collections::HashMap; diff --git a/src/security/sign_service.rs b/src/security/sign_service.rs index 3ef9f1f..597c9ee 100644 --- a/src/security/sign_service.rs +++ b/src/security/sign_service.rs @@ -21,13 +21,12 @@ use crate::security::security_asn::ieee1609_dot2::{ }; use crate::security::security_asn::ieee1609_dot2::Certificate as AsnCertificate; use crate::security::security_asn::ieee1609_dot2_base_types::{ - EccP256CurvePoint, EcdsaP256Signature, HashAlgorithm, HashedId8, Psid, + HashAlgorithm, HashedId8, Psid, ThreeDLocation, Time64, Elevation, Latitude, Longitude, NinetyDegreeInt, OneEightyDegreeInt, Opaque, Uint8, Uint16, Uint64, }; -use crate::security::security_asn::ieee1609_dot2_base_types::Signature as Ieee1609Signature; use crate::security::security_asn::ieee1609_dot2::HeaderInfo; -use crate::security::sn_sap::{GenerationLocation, SNSignConfirm, SNSignRequest}; +use crate::security::sn_sap::{SNSignConfirm, SNSignRequest}; use crate::security::time_service::timestamp_its_microseconds; // ─── CAM signer state ──────────────────────────────────────────────────── diff --git a/src/security/verify_service.rs b/src/security/verify_service.rs index 64a7175..f3dda7c 100644 --- a/src/security/verify_service.rs +++ b/src/security/verify_service.rs @@ -4,15 +4,12 @@ //! chain, checks per-profile header constraints, and verifies the ECDSA //! signature. -use rasn::prelude::*; - use crate::security::certificate::{decode_ieee1609_dot2_data, encode_tbs_data, Certificate}; use crate::security::certificate_library::CertificateLibrary; use crate::security::ecdsa_backend::EcdsaBackend; use crate::security::security_asn::ieee1609_dot2::{ Ieee1609Dot2Content, SignerIdentifier, }; -use crate::security::security_asn::ieee1609_dot2_base_types::PublicVerificationKey; use crate::security::security_asn::ieee1609_dot2::VerificationKeyIndicator; use crate::security::sign_service::SignService; use crate::security::sn_sap::{ReportVerify, SNVerifyConfirm, SNVerifyRequest}; @@ -348,6 +345,7 @@ pub enum VerifyEvent { #[cfg(test)] mod tests { use super::*; + use rasn::prelude::*; use crate::security::certificate::OwnCertificate; use crate::security::certificate_library::CertificateLibrary; use crate::security::ecdsa_backend::EcdsaBackend; From a26f4fb1bfd9e6c45fdad05843ef61a2cb13609d Mon Sep 17 00:00:00 2001 From: Jordi Marias I Parella Date: Tue, 7 Apr 2026 11:21:21 +0200 Subject: [PATCH 4/6] cargo formatted --- examples/generate_certificate_chain.rs | 25 ++--- examples/secured_cam_sender_receiver.rs | 23 ++--- examples/secured_vam_sender_receiver.rs | 23 ++--- src/btp/btp_header.rs | 2 +- src/btp/router.rs | 23 ++--- src/btp/service_access_point.rs | 6 +- .../ca_basic_service/cam_reception.rs | 4 +- .../ca_basic_service/cam_transmission.rs | 33 +++---- .../denm_transmission.rs | 5 +- .../local_dynamic_map/ldm_service.rs | 10 +- src/facilities/local_dynamic_map/ldm_types.rs | 15 +-- .../vru_awareness_service/vam_transmission.rs | 35 +++---- src/geonet/basic_header.rs | 3 +- src/geonet/common_header.rs | 3 +- src/geonet/ls_extended_header.rs | 6 +- src/geonet/position_vector.rs | 14 ++- src/geonet/router.rs | 99 ++++++++----------- src/geonet/service_access_point.rs | 26 ++++- src/geonet/tsb_extended_header.rs | 8 +- src/security/certificate.rs | 82 ++++++--------- src/security/certificate_library.rs | 40 +++----- src/security/ecdsa_backend.rs | 8 +- src/security/security_asn.rs | 52 +++++----- src/security/sign_service.rs | 92 ++++++++--------- src/security/sn_sap.rs | 10 +- src/security/verify_service.rs | 77 ++++++++------- 26 files changed, 344 insertions(+), 380 deletions(-) diff --git a/examples/generate_certificate_chain.rs b/examples/generate_certificate_chain.rs index 7e59865..4d5e7b2 100644 --- a/examples/generate_certificate_chain.rs +++ b/examples/generate_certificate_chain.rs @@ -21,13 +21,12 @@ use rustflexstack::security::certificate::{encode_certificate, OwnCertificate}; use rustflexstack::security::ecdsa_backend::EcdsaBackend; use rustflexstack::security::security_asn::ieee1609_dot2::{ CertificateId, EndEntityType, PsidGroupPermissions, SequenceOfAppExtensions, - SequenceOfCertIssueExtensions, SequenceOfCertRequestExtensions, - SequenceOfPsidGroupPermissions, SubjectPermissions, ToBeSignedCertificate, - VerificationKeyIndicator, + SequenceOfCertIssueExtensions, SequenceOfCertRequestExtensions, SequenceOfPsidGroupPermissions, + SubjectPermissions, ToBeSignedCertificate, VerificationKeyIndicator, }; use rustflexstack::security::security_asn::ieee1609_dot2_base_types::{ - CrlSeries, Duration, HashedId3, Hostname, Psid, PsidSsp, PsidSspRange, - SequenceOfPsidSsp, SequenceOfPsidSspRange, Time32, Uint16, Uint32, ValidityPeriod, + CrlSeries, Duration, HashedId3, Hostname, Psid, PsidSsp, PsidSspRange, SequenceOfPsidSsp, + SequenceOfPsidSspRange, Time32, Uint16, Uint32, ValidityPeriod, }; // ITS-AID values @@ -128,10 +127,7 @@ fn main() { SequenceOfCertRequestExtensions(Default::default()), ); let root_ca = OwnCertificate::initialize_self_signed(&mut backend, root_tbs); - println!( - "Root CA HashedId8: {:02x?}", - root_ca.as_hashedid8() - ); + println!("Root CA HashedId8: {:02x?}", root_ca.as_hashedid8()); // ─── Authorization Authority ───────────────────────────────────────── let aa_tbs = ToBeSignedCertificate::new( @@ -162,10 +158,7 @@ fn main() { SequenceOfCertRequestExtensions(Default::default()), ); let aa = OwnCertificate::initialize_issued(&mut backend, aa_tbs, &root_ca); - println!( - "AA HashedId8: {:02x?}", - aa.as_hashedid8() - ); + println!("AA HashedId8: {:02x?}", aa.as_hashedid8()); // ─── Authorization Tickets ─────────────────────────────────────────── let at_names = ["AT1", "AT2"]; @@ -196,11 +189,7 @@ fn main() { SequenceOfCertRequestExtensions(Default::default()), ); let at = OwnCertificate::initialize_issued(&mut backend, at_tbs, &aa); - println!( - "{:<8} HashedId8: {:02x?}", - name, - at.as_hashedid8() - ); + println!("{:<8} HashedId8: {:02x?}", name, at.as_hashedid8()); at_certs.push((name, at)); } diff --git a/examples/secured_cam_sender_receiver.rs b/examples/secured_cam_sender_receiver.rs index eff8a65..092a5e7 100644 --- a/examples/secured_cam_sender_receiver.rs +++ b/examples/secured_cam_sender_receiver.rs @@ -95,8 +95,16 @@ fn build_security_stack(at_index: usize) -> SignService { let mut backend = EcdsaBackend::new(); let key_id = backend.import_signing_key(&key_bytes); - let own_cert = if at_index == 1 { at1.clone() } else { at2.clone() }; - let peer_cert = if at_index == 1 { at2.clone() } else { at1.clone() }; + let own_cert = if at_index == 1 { + at1.clone() + } else { + at2.clone() + }; + let peer_cert = if at_index == 1 { + at2.clone() + } else { + at1.clone() + }; // ── Build certificate library ──────────────────────────────────────── let cert_library = CertificateLibrary::new( @@ -281,11 +289,7 @@ fn main() { let (confirm, _events) = { let mut svc = sign_svc_rx.lock().unwrap(); let svc = &mut *svc; - let result = verify_message( - &request, - &svc.backend, - &mut svc.cert_library, - ); + let result = verify_message(&request, &svc.backend, &mut svc.cert_library); // Process VerifyEvents for P2PCD for event in &result.1 { match event { @@ -320,10 +324,7 @@ fn main() { .collect(); gn_h_rx.send_incoming_packet(plain_packet); } else { - eprintln!( - "[SEC RX] Verification failed: {:?}", - confirm.report - ); + eprintln!("[SEC RX] Verification failed: {:?}", confirm.report); } } _ => { diff --git a/examples/secured_vam_sender_receiver.rs b/examples/secured_vam_sender_receiver.rs index 289763f..d00f19b 100644 --- a/examples/secured_vam_sender_receiver.rs +++ b/examples/secured_vam_sender_receiver.rs @@ -88,8 +88,16 @@ fn build_security_stack(at_index: usize) -> SignService { let mut backend = EcdsaBackend::new(); let key_id = backend.import_signing_key(&key_bytes); - let own_cert = if at_index == 1 { at1.clone() } else { at2.clone() }; - let peer_cert = if at_index == 1 { at2.clone() } else { at1.clone() }; + let own_cert = if at_index == 1 { + at1.clone() + } else { + at2.clone() + }; + let peer_cert = if at_index == 1 { + at2.clone() + } else { + at1.clone() + }; // ── Build certificate library ──────────────────────────────────────── let cert_library = CertificateLibrary::new( @@ -272,11 +280,7 @@ fn main() { let (confirm, _events) = { let mut svc = sign_svc_rx.lock().unwrap(); let svc = &mut *svc; - let result = verify_message( - &request, - &svc.backend, - &mut svc.cert_library, - ); + let result = verify_message(&request, &svc.backend, &mut svc.cert_library); // Process VerifyEvents for P2PCD for event in &result.1 { match event { @@ -311,10 +315,7 @@ fn main() { .collect(); gn_h_rx.send_incoming_packet(plain_packet); } else { - eprintln!( - "[SEC RX] Verification failed: {:?}", - confirm.report - ); + eprintln!("[SEC RX] Verification failed: {:?}", confirm.report); } } _ => { diff --git a/src/btp/btp_header.rs b/src/btp/btp_header.rs index 3127303..9635985 100644 --- a/src/btp/btp_header.rs +++ b/src/btp/btp_header.rs @@ -202,7 +202,7 @@ mod tests { #[test] fn btpb_encode_big_endian() { let h = BTPBHeader { - destination_port: 0x07D2, // 2002 + destination_port: 0x07D2, // 2002 destination_port_info: 0x002A, // 42 }; let bytes = h.encode(); diff --git a/src/btp/router.rs b/src/btp/router.rs index b7bcbd3..8ef492f 100644 --- a/src/btp/router.rs +++ b/src/btp/router.rs @@ -176,12 +176,8 @@ impl Router { let btp_bytes: [u8; 4] = gn_ind.data[0..4].try_into().unwrap(); let header = BTPBHeader::decode(btp_bytes); - let indication = - BTPDataIndication::initialize_with_gn_data_indication(&gn_ind) - .set_destination_port_and_info( - header.destination_port, - header.destination_port_info, - ); + let indication = BTPDataIndication::initialize_with_gn_data_indication(&gn_ind) + .set_destination_port_and_info(header.destination_port, header.destination_port_info); match self.port_callbacks.get(&indication.destination_port) { Some(tx) => { @@ -204,9 +200,8 @@ impl Router { let btp_bytes: [u8; 4] = gn_ind.data[0..4].try_into().unwrap(); let header = BTPAHeader::decode(btp_bytes); - let mut indication = - BTPDataIndication::initialize_with_gn_data_indication(&gn_ind) - .set_destination_port_and_info(header.destination_port(), 0); + let mut indication = BTPDataIndication::initialize_with_gn_data_indication(&gn_ind) + .set_destination_port_and_info(header.destination_port(), 0); indication.source_port = header.source_port(); match self.port_callbacks.get(&indication.destination_port) { @@ -237,8 +232,8 @@ mod tests { use crate::btp::btp_header::{BTPAHeader, BTPBHeader}; use crate::geonet::position_vector::LongPositionVector; use crate::geonet::service_access_point::{ - CommonNH, GNDataIndication, HeaderSubType, HeaderType, - PacketTransportType, TopoBroadcastHST, TrafficClass, UnspecifiedHST, + CommonNH, GNDataIndication, HeaderSubType, HeaderType, PacketTransportType, + TopoBroadcastHST, TrafficClass, UnspecifiedHST, }; use std::sync::mpsc; use std::time::Duration; @@ -377,8 +372,10 @@ mod tests { std::thread::sleep(Duration::from_millis(50)); let btpa = BTPAHeader::decode([ - (2001u16 >> 8) as u8, (2001u16 & 0xFF) as u8, - (5000u16 >> 8) as u8, (5000u16 & 0xFF) as u8, + (2001u16 >> 8) as u8, + (2001u16 & 0xFF) as u8, + (5000u16 >> 8) as u8, + (5000u16 & 0xFF) as u8, ]); let mut payload = btpa.encode().to_vec(); payload.extend_from_slice(&[0xCC, 0xDD]); diff --git a/src/btp/service_access_point.rs b/src/btp/service_access_point.rs index b36d7d8..38bc310 100644 --- a/src/btp/service_access_point.rs +++ b/src/btp/service_access_point.rs @@ -126,9 +126,7 @@ impl BTPDataIndication { /// Construct a BTPDataIndication from a GNDataIndication. /// Strips the first 4 bytes (BTP header) from the payload. - pub fn initialize_with_gn_data_indication( - gn_data_indication: &GNDataIndication, - ) -> Self { + pub fn initialize_with_gn_data_indication(gn_data_indication: &GNDataIndication) -> Self { let payload = if gn_data_indication.data.len() > 4 { gn_data_indication.data[4..].to_vec() } else { @@ -172,11 +170,11 @@ impl BTPDataIndication { #[cfg(test)] mod tests { use super::*; + use crate::geonet::position_vector::LongPositionVector; use crate::geonet::service_access_point::{ CommonNH, GNDataIndication, HeaderSubType, HeaderType, PacketTransportType, TopoBroadcastHST, TrafficClass, }; - use crate::geonet::position_vector::LongPositionVector; #[test] fn btp_data_request_default() { diff --git a/src/facilities/ca_basic_service/cam_reception.rs b/src/facilities/ca_basic_service/cam_reception.rs index fb40e9a..84776d5 100644 --- a/src/facilities/ca_basic_service/cam_reception.rs +++ b/src/facilities/ca_basic_service/cam_reception.rs @@ -102,7 +102,9 @@ impl CAMReceptionManagement { break; } } - Err(e) => eprintln!("[CAM RX] Decode error (Annex B.3.3.1 — discarding): {}", e), + Err(e) => { + eprintln!("[CAM RX] Decode error (Annex B.3.3.1 — discarding): {}", e) + } } } eprintln!("[CAM RX] Thread exiting"); diff --git a/src/facilities/ca_basic_service/cam_transmission.rs b/src/facilities/ca_basic_service/cam_transmission.rs index f288be6..8b19600 100644 --- a/src/facilities/ca_basic_service/cam_transmission.rs +++ b/src/facilities/ca_basic_service/cam_transmission.rs @@ -199,18 +199,10 @@ fn build_cam( ); let vehicle_width = VehicleWidth(vd.vehicle_width.clamp(1, 62)); - let longitudinal_acceleration = AccelerationComponent::new( - AccelerationValue(161), - AccelerationConfidence(102), - ); - let curvature = Curvature::new( - CurvatureValue(1023), - CurvatureConfidence::unavailable, - ); - let yaw_rate = YawRate::new( - YawRateValue(32767), - YawRateConfidence::unavailable, - ); + let longitudinal_acceleration = + AccelerationComponent::new(AccelerationValue(161), AccelerationConfidence(102)); + let curvature = Curvature::new(CurvatureValue(1023), CurvatureConfidence::unavailable); + let yaw_rate = YawRate::new(YawRateValue(32767), YawRateConfidence::unavailable); let hf = BasicVehicleContainerHighFrequency::new( heading, @@ -299,9 +291,8 @@ impl CAMTransmissionManagement { let mut current_fix: Option = None; // Annex B.2.4 step 1 — non-clock-synchronised start (random initial delay) - let initial_delay = Duration::from_millis( - rand::thread_rng().gen_range(0..T_CHECK_CAM_GEN_MS), - ); + let initial_delay = + Duration::from_millis(rand::thread_rng().gen_range(0..T_CHECK_CAM_GEN_MS)); thread::sleep(initial_delay); loop { @@ -556,11 +547,7 @@ fn build_lf_container( ) } -fn build_path_points( - current_fix: &GpsFix, - history: &[PathEntry], - now_ms: u64, -) -> Vec { +fn build_path_points(current_fix: &GpsFix, history: &[PathEntry], now_ms: u64) -> Vec { let mut result = Vec::new(); for entry in history.iter().rev() { let delta_lat = ((entry.lat - current_fix.latitude) * 1e7).round() as i32; @@ -647,8 +634,10 @@ fn generate_and_send( path_history: &mut Vec, ) { let include_lf = should_include_lf(*cam_count, *last_lf_time, now); - let include_special = should_include_special_vehicle(vd.vehicle_role, *cam_count, *last_special_time, now); - let include_vlf = should_include_vlf(*cam_count, *last_vlf_time, now, include_lf, include_special); + let include_special = + should_include_special_vehicle(vd.vehicle_role, *cam_count, *last_special_time, now); + let include_vlf = + should_include_vlf(*cam_count, *last_vlf_time, now, include_lf, include_special); let include_tw = should_include_two_wheeler(vd.station_type); // Approximate now_ms for path history delta-time calculations diff --git a/src/facilities/decentralized_environmental_notification_service/denm_transmission.rs b/src/facilities/decentralized_environmental_notification_service/denm_transmission.rs index dbf1d23..03ed268 100644 --- a/src/facilities/decentralized_environmental_notification_service/denm_transmission.rs +++ b/src/facilities/decentralized_environmental_notification_service/denm_transmission.rs @@ -23,17 +23,16 @@ use super::denm_coder::{ DenmPayload, HeadingValue, InformationQuality, Latitude, LocationContainer, Longitude, ManagementContainer, Path, PathPoint, PosConfidenceEllipse, ReferencePosition, SemiAxisLength, SequenceNumber, SituationContainer, Speed, SpeedConfidence, SpeedValue, StationId, StationType, - Traces, TrafficParticipantType, Wgs84Angle, Wgs84AngleConfidence, - Wgs84AngleValue, + Traces, TrafficParticipantType, Wgs84Angle, Wgs84AngleConfidence, Wgs84AngleValue, }; use crate::btp::router::BTPRouterHandle; -use crate::security::sn_sap::SecurityProfile; use crate::btp::service_access_point::BTPDataRequest; use crate::geonet::gn_address::{GNAddress, M, MID, ST}; use crate::geonet::service_access_point::{ Area, CommonNH, CommunicationProfile, GeoBroadcastHST, HeaderSubType, HeaderType, PacketTransportType, TrafficClass, }; +use crate::security::sn_sap::SecurityProfile; use std::sync::atomic::{AtomicU16, Ordering}; use std::thread; use std::time::Duration; diff --git a/src/facilities/local_dynamic_map/ldm_service.rs b/src/facilities/local_dynamic_map/ldm_service.rs index f934a60..7336bca 100644 --- a/src/facilities/local_dynamic_map/ldm_service.rs +++ b/src/facilities/local_dynamic_map/ldm_service.rs @@ -735,13 +735,15 @@ mod tests { assert_eq!(resp.result, SubscribeDataObjectsResult::Successful); let sub_id = resp.subscription_id.unwrap(); - let unsub_resp = - svc.unsubscribe_data_consumer(UnsubscribeDataConsumerReq { subscription_id: sub_id }); + let unsub_resp = svc.unsubscribe_data_consumer(UnsubscribeDataConsumerReq { + subscription_id: sub_id, + }); assert_eq!(unsub_resp.ack, UnsubscribeDataConsumerAck::Succeed); // Unsubscribe again should fail - let unsub_resp = - svc.unsubscribe_data_consumer(UnsubscribeDataConsumerReq { subscription_id: sub_id }); + let unsub_resp = svc.unsubscribe_data_consumer(UnsubscribeDataConsumerReq { + subscription_id: sub_id, + }); assert_eq!(unsub_resp.ack, UnsubscribeDataConsumerAck::Failed); } diff --git a/src/facilities/local_dynamic_map/ldm_types.rs b/src/facilities/local_dynamic_map/ldm_types.rs index a76047e..6935a5c 100644 --- a/src/facilities/local_dynamic_map/ldm_types.rs +++ b/src/facilities/local_dynamic_map/ldm_types.rs @@ -388,8 +388,14 @@ mod tests { #[test] fn add_data_provider_result_variants() { - assert_eq!(AddDataProviderResult::Succeed, AddDataProviderResult::Succeed); - assert_ne!(AddDataProviderResult::Succeed, AddDataProviderResult::Failed); + assert_eq!( + AddDataProviderResult::Succeed, + AddDataProviderResult::Succeed + ); + assert_ne!( + AddDataProviderResult::Succeed, + AddDataProviderResult::Failed + ); } #[test] @@ -429,10 +435,7 @@ mod tests { #[test] fn ordering_direction_eq() { assert_eq!(OrderingDirection::Ascending, OrderingDirection::Ascending); - assert_ne!( - OrderingDirection::Ascending, - OrderingDirection::Descending - ); + assert_ne!(OrderingDirection::Ascending, OrderingDirection::Descending); } #[test] diff --git a/src/facilities/vru_awareness_service/vam_transmission.rs b/src/facilities/vru_awareness_service/vam_transmission.rs index 3dc00e3..494a041 100644 --- a/src/facilities/vru_awareness_service/vam_transmission.rs +++ b/src/facilities/vru_awareness_service/vam_transmission.rs @@ -13,9 +13,7 @@ //! - VRU Low-Frequency Container included on first VAM, then every ≥ 2 000 ms (clause 6.2). //! - Security profile: VRU_AWARENESS_MESSAGE, ITS-AID: 638. -use super::vam_bindings::etsi_its_cdd::{ - VruProfileAndSubprofile, VruSubProfilePedestrian, -}; +use super::vam_bindings::etsi_its_cdd::{VruProfileAndSubprofile, VruSubProfilePedestrian}; use super::vam_bindings::vam_pdu_descriptions::VruLowFrequencyContainer; use super::vam_coder::{ generation_delta_time_now, vam_header, AccelerationConfidence, Altitude, AltitudeConfidence, @@ -105,11 +103,7 @@ impl Default for DeviceData { // ─── VAM builder ────────────────────────────────────────────────────────────── /// Build a complete [`Vam`] from a GPS fix and static device data. -fn build_vam( - fix: &GpsFix, - dd: &DeviceData, - lf: Option, -) -> Vam { +fn build_vam(fix: &GpsFix, dd: &DeviceData, lf: Option) -> Vam { let gen_dt = generation_delta_time_now(); // ── BasicContainer ──────────────────────────────────────────────────────── @@ -131,10 +125,7 @@ fn build_vam( // ── VruHighFrequencyContainer ───────────────────────────────────────────── let heading_raw = ((fix.heading_deg * 10.0).round() as u16).clamp(0, 3600); - let heading = Wgs84Angle::new( - Wgs84AngleValue(heading_raw), - Wgs84AngleConfidence(127), - ); + let heading = Wgs84Angle::new(Wgs84AngleValue(heading_raw), Wgs84AngleConfidence(127)); let speed = Speed::new( SpeedValue(((fix.speed_mps * 100.0).round() as u16).min(16_382)), @@ -150,7 +141,17 @@ fn build_vam( heading, speed, longitudinal_acceleration, - None, None, None, None, None, None, None, None, None, None, None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, ); // ── Assemble VAM ────────────────────────────────────────────────────────── @@ -290,8 +291,7 @@ impl VAMTransmissionManagement { // First VAM after activation — send immediately should_send = true; } else { - let elapsed_ms = - now.duration_since(last_vam_time.unwrap()).as_millis() as u64; + let elapsed_ms = now.duration_since(last_vam_time.unwrap()).as_millis() as u64; // Condition 1: elapsed ≥ T_GenVamMax if elapsed_ms >= T_GEN_VAM_MAX_MS { @@ -373,10 +373,7 @@ impl VAMTransmissionManagement { }; btp_handle.send_btp_data_request(req); - eprintln!( - "[VAM TX] Sent VAM: station={}", - device_data.station_id, - ); + eprintln!("[VAM TX] Sent VAM: station={}", device_data.station_id,); // Update state last_vam_time = Some(now); diff --git a/src/geonet/basic_header.rs b/src/geonet/basic_header.rs index d1031ed..f7c612d 100644 --- a/src/geonet/basic_header.rs +++ b/src/geonet/basic_header.rs @@ -297,8 +297,7 @@ mod tests { #[test] fn basic_header_set_nh() { let mib = Mib::new(); - let bh = BasicHeader::initialize_with_mib(&mib) - .set_nh(BasicNH::SecuredPacket); + let bh = BasicHeader::initialize_with_mib(&mib).set_nh(BasicNH::SecuredPacket); assert_eq!(bh.nh.encode(), 2); } diff --git a/src/geonet/common_header.rs b/src/geonet/common_header.rs index 8251bfe..c5088bc 100644 --- a/src/geonet/common_header.rs +++ b/src/geonet/common_header.rs @@ -3,8 +3,7 @@ use super::mib::Mib; use super::service_access_point::{ - CommonNH, GNDataRequest, HeaderSubType, HeaderType, TrafficClass, - UnspecifiedHST, + CommonNH, GNDataRequest, HeaderSubType, HeaderType, TrafficClass, UnspecifiedHST, }; use std::cmp::PartialEq; diff --git a/src/geonet/ls_extended_header.rs b/src/geonet/ls_extended_header.rs index ac9fc6c..9c95c04 100644 --- a/src/geonet/ls_extended_header.rs +++ b/src/geonet/ls_extended_header.rs @@ -150,7 +150,11 @@ mod tests { #[test] fn ls_request_encode_decode_roundtrip() { - let req_addr = GNAddress::new(M::GnUnicast, ST::HeavyTruck, MID::new([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF])); + let req_addr = GNAddress::new( + M::GnUnicast, + ST::HeavyTruck, + MID::new([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]), + ); let header = LSRequestExtendedHeader::initialize(55, make_lpv(), req_addr); let encoded = header.encode(); assert_eq!(encoded.len(), 36); diff --git a/src/geonet/position_vector.rs b/src/geonet/position_vector.rs index e805000..16ee080 100644 --- a/src/geonet/position_vector.rs +++ b/src/geonet/position_vector.rs @@ -313,11 +313,11 @@ mod tests { let lpv = LongPositionVector { gn_addr: addr, tst, - latitude: 415520000, // 41.552° - longitude: 21340000, // 2.134° + latitude: 415520000, // 41.552° + longitude: 21340000, // 2.134° pai: true, - s: 1000, // 10.00 m/s - h: 900, // 90.0° + s: 1000, // 10.00 m/s + h: 900, // 90.0° }; let encoded = lpv.encode(); assert_eq!(encoded.len(), 24); @@ -358,7 +358,11 @@ mod tests { #[test] fn spv_encode_decode_roundtrip() { - let addr = GNAddress::new(M::GnMulticast, ST::Bus, MID::new([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF])); + let addr = GNAddress::new( + M::GnMulticast, + ST::Bus, + MID::new([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]), + ); let tst = Tst::set_in_normal_timestamp_milliseconds(1_717_200_000_000); let spv = ShortPositionVector { gn_address: addr, diff --git a/src/geonet/router.rs b/src/geonet/router.rs index 2104c31..b2c4ddf 100644 --- a/src/geonet/router.rs +++ b/src/geonet/router.rs @@ -643,14 +643,13 @@ impl Router { /// Process security header — verify and dispatch. fn process_security_header(&mut self, packet: &[u8], basic_header: &BasicHeader) { - let (backend, cert_library) = - match (&self.verify_backend, &mut self.verify_cert_library) { - (Some(b), Some(cl)) => (b, cl), - _ => { - eprintln!("[GN] Secured packet received but no verify service configured"); - return; - } - }; + let (backend, cert_library) = match (&self.verify_backend, &mut self.verify_cert_library) { + (Some(b), Some(cl)) => (b, cl), + _ => { + eprintln!("[GN] Secured packet received but no verify service configured"); + return; + } + }; let verify_request = SNVerifyRequest { message: packet.to_vec(), @@ -696,8 +695,7 @@ impl Router { None } HeaderType::Tsb => { - if let HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop) = - common_header.hst + if let HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop) = common_header.hst { self.gn_data_indicate_shb(payload, &common_header, basic_header) } else if let HeaderSubType::TopoBroadcast(TopoBroadcastHST::MultiHop) = @@ -871,10 +869,7 @@ impl Router { } // §B.2: PDR enforcement - if let Some(entry) = self - .location_table - .get_entry_ref(&ext.so_pv.gn_addr) - { + if let Some(entry) = self.location_table.get_entry_ref(&ext.so_pv.gn_addr) { if entry.pdr > self.mib.itsGnMaxPacketDataRate as f64 * 1000.0 { return indication; } @@ -959,10 +954,7 @@ impl Router { } // §B.2 PDR enforcement - if let Some(entry) = self - .location_table - .get_entry_ref(&ext.so_pv.gn_addr) - { + if let Some(entry) = self.location_table.get_entry_ref(&ext.so_pv.gn_addr) { if entry.pdr > self.mib.itsGnMaxPacketDataRate as f64 * 1000.0 { return None; } @@ -1032,10 +1024,7 @@ impl Router { }; // §B.2 PDR enforcement - if let Some(entry) = self - .location_table - .get_entry_ref(&ext.so_pv.gn_addr) - { + if let Some(entry) = self.location_table.get_entry_ref(&ext.so_pv.gn_addr) { if entry.pdr > self.mib.itsGnMaxPacketDataRate as f64 * 1000.0 { return Some(indication); } @@ -1105,10 +1094,7 @@ impl Router { // Forwarder operations // §B.2 PDR - if let Some(entry) = self - .location_table - .get_entry_ref(&ext.so_pv.gn_addr) - { + if let Some(entry) = self.location_table.get_entry_ref(&ext.so_pv.gn_addr) { if entry.pdr > self.mib.itsGnMaxPacketDataRate as f64 * 1000.0 { return None; } @@ -1116,10 +1102,7 @@ impl Router { // Update DE PV from LocT if DE is a neighbour with newer PV let mut fwd_ext = ext.clone(); - if let Some(de_entry) = self - .location_table - .get_entry_ref(&ext.de_pv.gn_address) - { + if let Some(de_entry) = self.location_table.get_entry_ref(&ext.de_pv.gn_address) { if de_entry.is_neighbour && de_entry.position_vector.tst > ext.de_pv.tst { let de_lpv = &de_entry.position_vector; fwd_ext = fwd_ext.with_de_pv(ShortPositionVector { @@ -1216,12 +1199,11 @@ impl Router { longitude: so_lpv.longitude, }; - let reply_basic = - BasicHeader::initialize_with_mib_request_and_rhl( - &self.mib, - None, - self.mib.itsGnDefaultHopLimit, - ); + let reply_basic = BasicHeader::initialize_with_mib_request_and_rhl( + &self.mib, + None, + self.mib.itsGnDefaultHopLimit, + ); let reply_common = CommonHeader { nh: CommonNH::Any, reserved: 0, @@ -1238,8 +1220,7 @@ impl Router { reserved2: 0, }; let sn = self.get_sequence_number(); - let reply_ext = - LSReplyExtendedHeader::initialize(sn, self.ego_position_vector, de_pv); + let reply_ext = LSReplyExtendedHeader::initialize(sn, self.ego_position_vector, de_pv); let reply_packet: Vec = reply_basic .encode() @@ -1313,15 +1294,16 @@ impl Router { } else { // Forwarder: forward like GUC forwarder // §B.2 PDR enforcement - if let Some(entry) = self.location_table.get_entry_ref(&ls_reply.so_pv.gn_addr) - { + if let Some(entry) = self.location_table.get_entry_ref(&ls_reply.so_pv.gn_addr) { if entry.pdr > self.mib.itsGnMaxPacketDataRate as f64 * 1000.0 { return; } } let mut fwd_reply = ls_reply.clone(); - if let Some(de_entry) = self.location_table.get_entry_ref(&ls_reply.de_pv.gn_address) + if let Some(de_entry) = self + .location_table + .get_entry_ref(&ls_reply.de_pv.gn_address) { if de_entry.is_neighbour && de_entry.position_vector.tst > ls_reply.de_pv.tst { let de_lpv = &de_entry.position_vector; @@ -1473,10 +1455,8 @@ impl Router { data: vec![], area, }; - let algorithm = self.gn_forwarding_algorithm_selection( - &pseudo_request, - Some(&gbc_ext.so_pv.gn_addr), - ); + let algorithm = self + .gn_forwarding_algorithm_selection(&pseudo_request, Some(&gbc_ext.so_pv.gn_addr)); if algorithm == GNForwardingAlgorithmResponse::AreaForwarding { if self.mib.itsGnAreaForwardingAlgorithm == AreaForwardingAlgorithm::Cbf { @@ -1631,23 +1611,22 @@ impl Router { } // Compute timeout - let timeout_ms = if let Some(se_entry) = - self.location_table.get_entry_ref(&gbc_ext.so_pv.gn_addr) - { - if se_entry.position_vector.pai && self.ego_position_vector.pai { - let dist = Self::distance_m( - se_entry.position_vector.latitude as i32, - se_entry.position_vector.longitude as i32, - self.ego_position_vector.latitude as i32, - self.ego_position_vector.longitude as i32, - ); - self.cbf_compute_timeout_ms(dist) + let timeout_ms = + if let Some(se_entry) = self.location_table.get_entry_ref(&gbc_ext.so_pv.gn_addr) { + if se_entry.position_vector.pai && self.ego_position_vector.pai { + let dist = Self::distance_m( + se_entry.position_vector.latitude as i32, + se_entry.position_vector.longitude as i32, + self.ego_position_vector.latitude as i32, + self.ego_position_vector.longitude as i32, + ); + self.cbf_compute_timeout_ms(dist) + } else { + self.mib.itsGnCbfMaxTime as f64 + } } else { self.mib.itsGnCbfMaxTime as f64 - } - } else { - self.mib.itsGnCbfMaxTime as f64 - }; + }; let full_packet: Vec = basic_header .encode() diff --git a/src/geonet/service_access_point.rs b/src/geonet/service_access_point.rs index d29aae3..f27b4ae 100644 --- a/src/geonet/service_access_point.rs +++ b/src/geonet/service_access_point.rs @@ -397,24 +397,40 @@ mod tests { #[test] fn header_sub_type_topo_broadcast() { let shb = HeaderSubType::decode(&HeaderType::Tsb, 0); - assert!(matches!(shb, HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop))); + assert!(matches!( + shb, + HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop) + )); let mhb = HeaderSubType::decode(&HeaderType::Tsb, 1); - assert!(matches!(mhb, HeaderSubType::TopoBroadcast(TopoBroadcastHST::MultiHop))); + assert!(matches!( + mhb, + HeaderSubType::TopoBroadcast(TopoBroadcastHST::MultiHop) + )); } #[test] fn header_sub_type_location_service() { let req = HeaderSubType::decode(&HeaderType::Ls, 0); - assert!(matches!(req, HeaderSubType::LocationService(LocationServiceHST::LsRequest))); + assert!(matches!( + req, + HeaderSubType::LocationService(LocationServiceHST::LsRequest) + )); let rep = HeaderSubType::decode(&HeaderType::Ls, 1); - assert!(matches!(rep, HeaderSubType::LocationService(LocationServiceHST::LsReply))); + assert!(matches!( + rep, + HeaderSubType::LocationService(LocationServiceHST::LsReply) + )); } // ── TrafficClass ────────────────────────────────────────────────── #[test] fn traffic_class_encode_decode() { - let tc = TrafficClass { scf: true, channel_offload: false, tc_id: 10 }; + let tc = TrafficClass { + scf: true, + channel_offload: false, + tc_id: 10, + }; let encoded = tc.encode(); let decoded = TrafficClass::decode(encoded); assert_eq!(tc, decoded); diff --git a/src/geonet/tsb_extended_header.rs b/src/geonet/tsb_extended_header.rs index 98dc118..85c4e9a 100644 --- a/src/geonet/tsb_extended_header.rs +++ b/src/geonet/tsb_extended_header.rs @@ -53,12 +53,16 @@ impl TSBExtendedHeader { #[cfg(test)] mod tests { use super::*; - use crate::geonet::position_vector::{LongPositionVector, Tst}; use crate::geonet::gn_address::{GNAddress, M, MID, ST}; + use crate::geonet::position_vector::{LongPositionVector, Tst}; fn make_tsb() -> TSBExtendedHeader { let so_pv = LongPositionVector { - gn_addr: GNAddress::new(M::GnUnicast, ST::Motorcycle, MID::new([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF])), + gn_addr: GNAddress::new( + M::GnUnicast, + ST::Motorcycle, + MID::new([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]), + ), tst: Tst::set_in_normal_timestamp_milliseconds(1_717_200_000_000), latitude: 415520000, longitude: 21340000, diff --git a/src/security/certificate.rs b/src/security/certificate.rs index 47e74ed..e72eea1 100644 --- a/src/security/certificate.rs +++ b/src/security/certificate.rs @@ -5,19 +5,15 @@ use crate::security::ecdsa_backend::EcdsaBackend; use crate::security::security_asn::etsi_ts103097_module::EtsiTs103097Certificate; +use crate::security::security_asn::ieee1609_dot2::Certificate as AsnCertificate; use crate::security::security_asn::ieee1609_dot2::{ - CertificateBase, CertificateType, IssuerIdentifier, - Ieee1609Dot2Data, ToBeSignedCertificate, + CertificateBase, CertificateType, Ieee1609Dot2Data, IssuerIdentifier, ToBeSignedCertificate, ToBeSignedData, }; +use crate::security::security_asn::ieee1609_dot2::{SubjectPermissions, VerificationKeyIndicator}; use crate::security::security_asn::ieee1609_dot2_base_types::Signature as Ieee1609Signature; -use crate::security::security_asn::ieee1609_dot2::Certificate as AsnCertificate; use crate::security::security_asn::ieee1609_dot2_base_types::{ - EccP256CurvePoint, EcdsaP256Signature, HashAlgorithm, HashedId8, - PublicVerificationKey, Uint8, -}; -use crate::security::security_asn::ieee1609_dot2::{ - SubjectPermissions, VerificationKeyIndicator, + EccP256CurvePoint, EcdsaP256Signature, HashAlgorithm, HashedId8, PublicVerificationKey, Uint8, }; // ─── COER encode / decode helpers ──────────────────────────────────────── @@ -164,9 +160,7 @@ impl Certificate { pub fn verification_key_is_nist_p256(&self) -> bool { matches!( &self.tbs().verify_key_indicator, - VerificationKeyIndicator::verificationKey( - PublicVerificationKey::ecdsaNistP256(_) - ) + VerificationKeyIndicator::verificationKey(PublicVerificationKey::ecdsaNistP256(_)) ) } @@ -329,7 +323,10 @@ impl Certificate { return false; } let tbs = self.tbs(); - if !matches!(tbs.id, crate::security::security_asn::ieee1609_dot2::CertificateId::none(_)) { + if !matches!( + tbs.id, + crate::security::security_asn::ieee1609_dot2::CertificateId::none(_) + ) { return false; } if tbs.cert_issue_permissions.is_some() { @@ -394,11 +391,7 @@ impl OwnCertificate { /// Sign (or re-sign) `target`'s `toBeSigned` and return a new `Certificate` /// with the updated signature. - pub fn sign_certificate( - &self, - backend: &EcdsaBackend, - target: &Certificate, - ) -> Certificate { + pub fn sign_certificate(&self, backend: &EcdsaBackend, target: &Certificate) -> Certificate { let tbs_bytes = encode_tbs_certificate(target.tbs()); let sig = backend.sign(&tbs_bytes, self.key_id); @@ -411,11 +404,7 @@ impl OwnCertificate { /// Build the inner `AsnCertificate` from the wrapper's certificate dict, /// setting the issuer to `self`, signing, and returning the result. - pub fn issue_certificate( - &self, - backend: &EcdsaBackend, - target: &Certificate, - ) -> Certificate { + pub fn issue_certificate(&self, backend: &EcdsaBackend, target: &Certificate) -> Certificate { // Set issuer to our HashedId8 let mut new_base = target.base().clone(); let h8 = self.as_hashedid8(); @@ -432,10 +421,7 @@ impl OwnCertificate { } /// Create a brand-new self-signed certificate. - pub fn initialize_self_signed( - backend: &mut EcdsaBackend, - tbs: ToBeSignedCertificate, - ) -> Self { + pub fn initialize_self_signed(backend: &mut EcdsaBackend, tbs: ToBeSignedCertificate) -> Self { let key_id = backend.create_key(); let pk = backend.get_public_key(key_id); @@ -509,19 +495,15 @@ mod tests { fn make_root_tbs() -> ToBeSignedCertificate { use crate::security::security_asn::ieee1609_dot2::{ - CertificateId, PsidGroupPermissions, SequenceOfPsidGroupPermissions, - SequenceOfAppExtensions, SequenceOfCertIssueExtensions, - SequenceOfCertRequestExtensions, EndEntityType, + CertificateId, EndEntityType, PsidGroupPermissions, SequenceOfAppExtensions, + SequenceOfCertIssueExtensions, SequenceOfCertRequestExtensions, + SequenceOfPsidGroupPermissions, }; use crate::security::security_asn::ieee1609_dot2_base_types::{ - CrlSeries, Duration as AsnDuration, ValidityPeriod, - Time32, Uint16, Uint32, HashedId3, + CrlSeries, Duration as AsnDuration, HashedId3, Time32, Uint16, Uint32, ValidityPeriod, }; - let validity = ValidityPeriod::new( - Time32(Uint32(0)), - AsnDuration::years(Uint16(30)), - ); + let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(30))); let perms = SequenceOfPsidGroupPermissions( vec![PsidGroupPermissions::new( @@ -537,9 +519,8 @@ mod tests { .into(), ); - let placeholder_pk = PublicVerificationKey::ecdsaNistP256( - EccP256CurvePoint::x_only(vec![0u8; 32].into()), - ); + let placeholder_pk = + PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only(vec![0u8; 32].into())); ToBeSignedCertificate::new( CertificateId::none(()), @@ -563,28 +544,21 @@ mod tests { fn make_at_tbs() -> ToBeSignedCertificate { use crate::security::security_asn::ieee1609_dot2::{ - CertificateId, PsidSsp, SequenceOfPsidSsp, - SequenceOfAppExtensions, SequenceOfCertIssueExtensions, - SequenceOfCertRequestExtensions, + CertificateId, PsidSsp, SequenceOfAppExtensions, SequenceOfCertIssueExtensions, + SequenceOfCertRequestExtensions, SequenceOfPsidSsp, }; use crate::security::security_asn::ieee1609_dot2_base_types::{ - CrlSeries, Duration as AsnDuration, Psid, ValidityPeriod, - Time32, Uint16, Uint32, HashedId3, + CrlSeries, Duration as AsnDuration, HashedId3, Psid, Time32, Uint16, Uint32, + ValidityPeriod, }; - let validity = ValidityPeriod::new( - Time32(Uint32(0)), - AsnDuration::years(Uint16(1)), - ); + let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(1))); - let app_perms = SequenceOfPsidSsp( - vec![PsidSsp::new(Psid(Integer::from(36_i64)), None)] - .into(), - ); + let app_perms = + SequenceOfPsidSsp(vec![PsidSsp::new(Psid(Integer::from(36_i64)), None)].into()); - let placeholder_pk = PublicVerificationKey::ecdsaNistP256( - EccP256CurvePoint::x_only(vec![0u8; 32].into()), - ); + let placeholder_pk = + PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only(vec![0u8; 32].into())); ToBeSignedCertificate::new( CertificateId::none(()), diff --git a/src/security/certificate_library.rs b/src/security/certificate_library.rs index ca1985d..5495769 100644 --- a/src/security/certificate_library.rs +++ b/src/security/certificate_library.rs @@ -100,10 +100,7 @@ impl CertificateLibrary { } /// Retrieve an AT by HashedId8. - pub fn get_authorization_ticket_by_hashedid8( - &self, - h: &[u8; 8], - ) -> Option<&Certificate> { + pub fn get_authorization_ticket_by_hashedid8(&self, h: &[u8; 8]) -> Option<&Certificate> { self.known_authorization_tickets.get(h) } @@ -129,7 +126,8 @@ impl CertificateLibrary { let with_issuer = Certificate::from_asn(temp.inner.clone(), Some(issuer)); if with_issuer.verify(backend) { let h2 = with_issuer.as_hashedid8(); - self.known_authorization_tickets.insert(h2, with_issuer.clone()); + self.known_authorization_tickets + .insert(h2, with_issuer.clone()); return Some(with_issuer); } } @@ -140,8 +138,7 @@ impl CertificateLibrary { let aa_temp = &certs[1]; if let Some(issuer_h) = aa_temp.get_issuer_hashedid8() { if let Some(root) = self.known_root_certificates.get(&issuer_h).cloned() { - let aa_with_issuer = - Certificate::from_asn(aa_temp.inner.clone(), Some(root)); + let aa_with_issuer = Certificate::from_asn(aa_temp.inner.clone(), Some(root)); if aa_with_issuer.verify(backend) { let aa_h = aa_with_issuer.as_hashedid8(); self.known_authorization_authorities @@ -193,14 +190,14 @@ mod tests { use crate::security::certificate::OwnCertificate; use crate::security::ecdsa_backend::EcdsaBackend; use crate::security::security_asn::ieee1609_dot2::{ - CertificateId, PsidGroupPermissions, PsidSsp, SequenceOfPsidGroupPermissions, - SequenceOfPsidSsp, SubjectPermissions, VerificationKeyIndicator, - SequenceOfAppExtensions, SequenceOfCertIssueExtensions, - SequenceOfCertRequestExtensions, EndEntityType, + CertificateId, EndEntityType, PsidGroupPermissions, PsidSsp, SequenceOfAppExtensions, + SequenceOfCertIssueExtensions, SequenceOfCertRequestExtensions, + SequenceOfPsidGroupPermissions, SequenceOfPsidSsp, SubjectPermissions, + VerificationKeyIndicator, }; use crate::security::security_asn::ieee1609_dot2_base_types::{ - CrlSeries, Duration as AsnDuration, EccP256CurvePoint, Psid, - PublicVerificationKey, Time32, Uint16, Uint32, ValidityPeriod, HashedId3, + CrlSeries, Duration as AsnDuration, EccP256CurvePoint, HashedId3, Psid, + PublicVerificationKey, Time32, Uint16, Uint32, ValidityPeriod, }; use rasn::prelude::*; @@ -219,9 +216,8 @@ mod tests { )] .into(), ); - let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only( - vec![0u8; 32].into(), - )); + let pk = + PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only(vec![0u8; 32].into())); crate::security::security_asn::ieee1609_dot2::ToBeSignedCertificate::new( CertificateId::none(()), HashedId3(FixedOctetString::from([0u8; 3])), @@ -246,9 +242,8 @@ mod tests { let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(1))); let app_perms = SequenceOfPsidSsp(vec![PsidSsp::new(Psid(Integer::from(36_i64)), None)].into()); - let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only( - vec![0u8; 32].into(), - )); + let pk = + PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only(vec![0u8; 32].into())); crate::security::security_asn::ieee1609_dot2::ToBeSignedCertificate::new( CertificateId::none(()), HashedId3(FixedOctetString::from([0u8; 3])), @@ -269,9 +264,7 @@ mod tests { ) } - fn make_chain( - backend: &mut EcdsaBackend, - ) -> (OwnCertificate, OwnCertificate, OwnCertificate) { + fn make_chain(backend: &mut EcdsaBackend) -> (OwnCertificate, OwnCertificate, OwnCertificate) { let root = OwnCertificate::initialize_self_signed(backend, make_root_tbs()); let aa = OwnCertificate::initialize_issued(backend, make_root_tbs(), &root); let at = OwnCertificate::initialize_issued(backend, make_at_tbs(), &aa); @@ -394,8 +387,7 @@ mod tests { let mut backend = EcdsaBackend::new(); let root = OwnCertificate::initialize_self_signed(&mut backend, make_root_tbs()); let aa = OwnCertificate::initialize_issued(&mut backend, make_root_tbs(), &root); - let mut lib = - CertificateLibrary::new(&backend, vec![root.cert.clone()], vec![], vec![]); + let mut lib = CertificateLibrary::new(&backend, vec![root.cert.clone()], vec![], vec![]); lib.add_authorization_authority(&backend, aa.cert.clone()); lib.add_authorization_authority(&backend, aa.cert.clone()); assert_eq!(lib.known_authorization_authorities.len(), 1); diff --git a/src/security/ecdsa_backend.rs b/src/security/ecdsa_backend.rs index 91ce234..5f5226b 100644 --- a/src/security/ecdsa_backend.rs +++ b/src/security/ecdsa_backend.rs @@ -9,18 +9,16 @@ //! and signed messages without conversion. use p256::ecdsa::signature::Signer; -use p256::ecdsa::{Signature, SigningKey, VerifyingKey}; use p256::ecdsa::signature::Verifier; +use p256::ecdsa::{Signature, SigningKey, VerifyingKey}; use sha2::{Digest, Sha256}; use std::collections::HashMap; +use crate::security::security_asn::ieee1609_dot2_base_types::PublicVerificationKey; +use crate::security::security_asn::ieee1609_dot2_base_types::Signature as Ieee1609Signature; use crate::security::security_asn::ieee1609_dot2_base_types::{ EccP256CurvePoint, EccP256CurvePointUncompressedP256, EcdsaP256Signature, }; -use crate::security::security_asn::ieee1609_dot2_base_types::{ - PublicVerificationKey, -}; -use crate::security::security_asn::ieee1609_dot2_base_types::Signature as Ieee1609Signature; /// ECDSA backend managing NIST P-256 key pairs. pub struct EcdsaBackend { diff --git a/src/security/security_asn.rs b/src/security/security_asn.rs index 47c3c3c..5cbd09a 100644 --- a/src/security/security_asn.rs +++ b/src/security/security_asn.rs @@ -7,17 +7,17 @@ )] pub mod etsi_ts102941_base_types { extern crate alloc; - use super::ieee1609_dot2::*; - use super::ieee1609_dot2_base_types::*; pub use super::etsi_ts103097_module::{ EtsiTs103097Data, EtsiTs103097DataEncrypted, EtsiTs103097DataEncryptedUnicast, EtsiTs103097DataSigned, EtsiTs103097DataSignedAndEncryptedUnicast, EtsiTs103097DataSignedExternalPayload, EtsiTs103097DataUnsecured, }; + use super::ieee1609_dot2::*; pub use super::ieee1609_dot2::{ CertificateId, GeographicRegion, HashedData, SequenceOfPsidGroupPermissions, SequenceOfPsidSsp, SubjectAssurance, ValidityPeriod, }; + use super::ieee1609_dot2_base_types::*; pub use super::ieee1609_dot2_base_types::{ HashedId8, PublicEncryptionKey, PublicVerificationKey, Signature, Time32, }; @@ -100,8 +100,6 @@ pub mod etsi_ts102941_base_types { )] pub mod etsi_ts102941_messages_ca { extern crate alloc; - use super::ieee1609_dot2::*; - use super::ieee1609_dot2_base_types::*; use super::etsi_ts102941_base_types::Version; use super::etsi_ts102941_trust_lists::{ToBeSignedCrl, ToBeSignedRcaCtl, ToBeSignedTlmCtl}; use super::etsi_ts102941_types_authorization::{ @@ -119,6 +117,8 @@ pub mod etsi_ts102941_messages_ca { EtsiTs103097Data, EtsiTs103097DataEncryptedUnicast, EtsiTs103097DataSigned, EtsiTs103097DataSignedAndEncryptedUnicast, EtsiTs103097DataSignedExternalPayload, }; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; use super::ieee1609_dot2_dot1_aca_ra_interface::{AcaRaCertResponse, RaAcaCertRequest}; use super::ieee1609_dot2_dot1_ee_ra_interface::{ EeRaCertRequest, EeRaDownloadRequest, RaEeCertInfo, @@ -208,8 +208,6 @@ pub mod etsi_ts102941_messages_ca { )] pub mod etsi_ts102941_messages_itss_optional_privacy { extern crate alloc; - use super::ieee1609_dot2::*; - use super::ieee1609_dot2_base_types::*; use super::etsi_ts102941_base_types::Version; use super::etsi_ts102941_trust_lists::{ToBeSignedCrl, ToBeSignedRcaCtl, ToBeSignedTlmCtl}; use super::etsi_ts102941_types_authorization::{ @@ -223,6 +221,8 @@ pub mod etsi_ts102941_messages_itss_optional_privacy { EtsiTs103097Data, EtsiTs103097DataEncryptedUnicast, EtsiTs103097DataSigned, EtsiTs103097DataSignedAndEncryptedUnicast, }; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; use super::ieee1609_dot2_dot1_ee_ra_interface::{ EeRaCertRequest, EeRaDownloadRequest, RaEeCertInfo, }; @@ -415,12 +415,12 @@ pub mod etsi_ts102941_messages_itss_optional_privacy { )] pub mod etsi_ts102941_trust_lists { extern crate alloc; - use super::ieee1609_dot2::*; - use super::ieee1609_dot2_base_types::*; use super::etsi_ts102941_base_types::{HashedId8, Time32, Version}; use super::etsi_ts103097_module::{ EtsiTs103097Certificate, EtsiTs103097DataSigned, EtsiTs103097DataSignedAndEncrypted, }; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; use core::borrow::Borrow; use rasn::prelude::*; use std::sync::LazyLock; @@ -640,13 +640,13 @@ pub mod etsi_ts102941_trust_lists { )] pub mod etsi_ts102941_types_authorization { extern crate alloc; - use super::ieee1609_dot2::*; - use super::ieee1609_dot2_base_types::*; use super::etsi_ts102941_base_types::{ CertificateFormat, CertificateSubjectAttributes, EcSignature, HashedId8, PublicKeys, Version, }; use super::etsi_ts103097_module::{EtsiTs103097Certificate, EtsiTs103097DataSigned}; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; use super::ieee1609_dot2_dot1_ee_ra_interface::EeRaInterfacePdu; use super::ieee1609_dot2_dot1_protocol::{ Ieee1609Dot2DataSignedX509AuthenticatedCertRequest, ScmsPduScoped, SignerSingleX509Cert, @@ -814,14 +814,14 @@ pub mod etsi_ts102941_types_authorization { )] pub mod etsi_ts102941_types_authorization_validation { extern crate alloc; - use super::ieee1609_dot2::*; - use super::ieee1609_dot2_base_types::*; use super::etsi_ts102941_base_types::{ CertificateFormat, CertificateSubjectAttributes, EcSignature, HashedId8, PublicKeys, Version, }; use super::etsi_ts102941_types_authorization::SharedAtRequest; use super::etsi_ts103097_module::EtsiTs103097Certificate; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; use core::borrow::Borrow; use rasn::prelude::*; use std::sync::LazyLock; @@ -899,10 +899,10 @@ pub mod etsi_ts102941_types_authorization_validation { )] pub mod etsi_ts102941_types_ca_management { extern crate alloc; - use super::ieee1609_dot2::*; - use super::ieee1609_dot2_base_types::*; use super::etsi_ts102941_base_types::{CertificateSubjectAttributes, PublicKeys}; use super::etsi_ts103097_module::{EtsiTs103097Certificate, EtsiTs103097DataSigned}; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; use core::borrow::Borrow; use rasn::prelude::*; use std::sync::LazyLock; @@ -939,13 +939,13 @@ pub mod etsi_ts102941_types_ca_management { )] pub mod etsi_ts102941_types_enrolment { extern crate alloc; - use super::ieee1609_dot2::*; - use super::ieee1609_dot2_base_types::*; use super::etsi_ts102941_base_types::{ CertificateFormat, CertificateSubjectAttributes, EcSignature, HashedId8, PublicKeys, Version, }; use super::etsi_ts103097_module::{EtsiTs103097Certificate, EtsiTs103097DataSigned}; + use super::ieee1609_dot2::*; + use super::ieee1609_dot2_base_types::*; use core::borrow::Borrow; use rasn::prelude::*; use std::sync::LazyLock; @@ -1035,9 +1035,9 @@ pub mod etsi_ts102941_types_enrolment { )] pub mod etsi_ts102941_types_link_certificate { extern crate alloc; + use super::etsi_ts102941_base_types::{HashedData, Time32}; use super::ieee1609_dot2::*; use super::ieee1609_dot2_base_types::*; - use super::etsi_ts102941_base_types::{HashedData, Time32}; use core::borrow::Borrow; use rasn::prelude::*; use std::sync::LazyLock; @@ -1151,10 +1151,10 @@ pub mod etsi_ts103097_extension_module { )] pub mod etsi_ts103097_module { extern crate alloc; - use super::ieee1609_dot2::*; - use super::ieee1609_dot2_base_types::*; use super::etsi_ts103097_extension_module::ExtensionModuleVersion; + use super::ieee1609_dot2::*; use super::ieee1609_dot2::{Certificate, Ieee1609Dot2Data}; + use super::ieee1609_dot2_base_types::*; use core::borrow::Borrow; use rasn::prelude::*; use std::sync::LazyLock; @@ -4502,9 +4502,9 @@ pub mod ieee1609_dot2_base_types { )] pub mod ieee1609_dot2_crl { extern crate alloc; + use super::ieee1609_dot2::Ieee1609Dot2Data; use super::ieee1609_dot2::*; use super::ieee1609_dot2_base_types::*; - use super::ieee1609_dot2::Ieee1609Dot2Data; use super::ieee1609_dot2_base_types::{Opaque, Psid}; use super::ieee1609_dot2_crl_base_types::CrlContents; use core::borrow::Borrow; @@ -5167,9 +5167,9 @@ pub mod ieee1609_dot2_crl_base_types { )] pub mod ieee1609_dot2_dot1_aca_ee_interface { extern crate alloc; + use super::ieee1609_dot2::Certificate; use super::ieee1609_dot2::*; use super::ieee1609_dot2_base_types::*; - use super::ieee1609_dot2::Certificate; use super::ieee1609_dot2_base_types::{Time32, Uint8}; use core::borrow::Borrow; use rasn::prelude::*; @@ -5306,8 +5306,8 @@ pub mod ieee1609_dot2_dot1_aca_ma_interface { pub mod ieee1609_dot2_dot1_aca_ra_interface { extern crate alloc; use super::ieee1609_dot2::*; - use super::ieee1609_dot2_base_types::*; use super::ieee1609_dot2::{CertificateType, ToBeSignedCertificate}; + use super::ieee1609_dot2_base_types::*; use super::ieee1609_dot2_base_types::{ HashAlgorithm, HashedId8, LaId, PublicEncryptionKey, Time32, Uint8, }; @@ -6009,8 +6009,8 @@ pub mod ieee1609_dot2_dot1_cam_ra_interface { pub mod ieee1609_dot2_dot1_cert_management { extern crate alloc; use super::ieee1609_dot2::*; - use super::ieee1609_dot2_base_types::*; use super::ieee1609_dot2::{Certificate, SequenceOfCertificate}; + use super::ieee1609_dot2_base_types::*; use super::ieee1609_dot2_base_types::{HashedId32, HashedId48, HashedId8, Time32, Uint8}; use super::ieee1609_dot2_crl::SecuredCrl; use super::ieee1609_dot2_crl_base_types::CrlSeries; @@ -6498,8 +6498,8 @@ pub mod ieee1609_dot2_dot1_cert_management { pub mod ieee1609_dot2_dot1_eca_ee_interface { extern crate alloc; use super::ieee1609_dot2::*; - use super::ieee1609_dot2_base_types::*; use super::ieee1609_dot2::{Certificate, CertificateType, SequenceOfCertificate}; + use super::ieee1609_dot2_base_types::*; use super::ieee1609_dot2_base_types::{EccP256CurvePoint, HashedId8, Time32, Uint8}; use super::ieee1609_dot2_dot1_protocol::{PublicVerificationKey, ToBeSignedCertificate}; use core::borrow::Borrow; @@ -6697,9 +6697,9 @@ pub mod ieee1609_dot2_dot1_ee_ma_interface { )] pub mod ieee1609_dot2_dot1_ee_ra_interface { extern crate alloc; + use super::ieee1609_dot2::CertificateType; use super::ieee1609_dot2::*; use super::ieee1609_dot2_base_types::*; - use super::ieee1609_dot2::CertificateType; use super::ieee1609_dot2_base_types::{HashedId8, IValue, PublicEncryptionKey, Time32, Uint8}; use super::ieee1609_dot2_dot1_acpc::AcpcTreeId; use super::ieee1609_dot2_dot1_protocol::{ @@ -7129,12 +7129,12 @@ pub mod ieee1609_dot2_dot1_ma_ra_interface { pub mod ieee1609_dot2_dot1_protocol { extern crate alloc; use super::ieee1609_dot2::*; - use super::ieee1609_dot2_base_types::*; pub use super::ieee1609_dot2::{ Certificate, CertificateId, Ieee1609Dot2Data, SequenceOfCertificate, SequenceOfPsidGroupPermissions, SignerIdentifier, ToBeSignedCertificate, VerificationKeyIndicator, }; + use super::ieee1609_dot2_base_types::*; pub use super::ieee1609_dot2_base_types::{ CrlSeries, EccP256CurvePoint, EccP384CurvePoint, EcdsaP256Signature, EcdsaP384Signature, GeographicRegion, HashAlgorithm, HashedId3, Psid, PublicEncryptionKey, diff --git a/src/security/sign_service.rs b/src/security/sign_service.rs index 597c9ee..5c0934a 100644 --- a/src/security/sign_service.rs +++ b/src/security/sign_service.rs @@ -15,17 +15,16 @@ use crate::security::certificate::{ }; use crate::security::certificate_library::CertificateLibrary; use crate::security::ecdsa_backend::EcdsaBackend; +use crate::security::security_asn::ieee1609_dot2::Certificate as AsnCertificate; +use crate::security::security_asn::ieee1609_dot2::HeaderInfo; use crate::security::security_asn::ieee1609_dot2::{ - Ieee1609Dot2Content, Ieee1609Dot2Data, SequenceOfCertificate, SignedData, - SignedDataPayload, SignerIdentifier, ToBeSignedData, + Ieee1609Dot2Content, Ieee1609Dot2Data, SequenceOfCertificate, SignedData, SignedDataPayload, + SignerIdentifier, ToBeSignedData, }; -use crate::security::security_asn::ieee1609_dot2::Certificate as AsnCertificate; use crate::security::security_asn::ieee1609_dot2_base_types::{ - HashAlgorithm, HashedId8, Psid, - ThreeDLocation, Time64, Elevation, Latitude, Longitude, - NinetyDegreeInt, OneEightyDegreeInt, Opaque, Uint8, Uint16, Uint64, + Elevation, HashAlgorithm, HashedId8, Latitude, Longitude, NinetyDegreeInt, OneEightyDegreeInt, + Opaque, Psid, ThreeDLocation, Time64, Uint16, Uint64, Uint8, }; -use crate::security::security_asn::ieee1609_dot2::HeaderInfo; use crate::security::sn_sap::{SNSignConfirm, SNSignRequest}; use crate::security::time_service::timestamp_its_microseconds; @@ -46,18 +45,13 @@ impl CamSignerState { } } - fn choose_signer( - &mut self, - cert: &OwnCertificate, - ) -> SignerIdentifier { + fn choose_signer(&mut self, cert: &OwnCertificate) -> SignerIdentifier { let now = crate::security::time_service::unix_time_secs(); if now - self.last_full_cert_time > 1.0 || self.requested_own_certificate { self.last_full_cert_time = now; self.requested_own_certificate = false; let asn_cert: AsnCertificate = cert.cert.inner.0.clone(); - SignerIdentifier::certificate(SequenceOfCertificate( - vec![asn_cert].into(), - )) + SignerIdentifier::certificate(SequenceOfCertificate(vec![asn_cert].into())) } else { let h = cert.as_hashedid8(); SignerIdentifier::digest(HashedId8(FixedOctetString::from(h))) @@ -134,17 +128,9 @@ impl SignService { let tbs_bytes = encode_tbs_data(&tbs_data); let signature = at.sign_message(&self.backend, &tbs_bytes); - let signed_data = SignedData::new( - HashAlgorithm::sha256, - tbs_data, - signer, - signature, - ); + let signed_data = SignedData::new(HashAlgorithm::sha256, tbs_data, signer, signature); - let outer = Ieee1609Dot2Data::new( - Uint8(3), - Ieee1609Dot2Content::signedData(signed_data), - ); + let outer = Ieee1609Dot2Data::new(Uint8(3), Ieee1609Dot2Content::signedData(signed_data)); encode_ieee1609_dot2_data(&outer) } @@ -161,7 +147,15 @@ impl SignService { let header_info = HeaderInfo::new( Psid(Integer::from(request.its_aid as i64)), Some(Time64(Uint64(timestamp_its_microseconds()))), - None, None, None, None, None, None, None, None, None, + None, + None, + None, + None, + None, + None, + None, + None, + None, ); let sec_message = self.build_signed_data(&request.tbs_message, header_info, signer, at); @@ -181,9 +175,7 @@ impl SignService { .expect("DENM requires generation_location"); let asn_cert: AsnCertificate = at.cert.inner.0.clone(); - let signer = SignerIdentifier::certificate(SequenceOfCertificate( - vec![asn_cert].into(), - )); + let signer = SignerIdentifier::certificate(SequenceOfCertificate(vec![asn_cert].into())); let header_info = HeaderInfo::new( Psid(Integer::from(request.its_aid as i64)), @@ -194,7 +186,13 @@ impl SignService { longitude: Longitude(OneEightyDegreeInt(gen_loc.longitude)), elevation: Elevation(Uint16(gen_loc.elevation)), }), - None, None, None, None, None, None, None, + None, + None, + None, + None, + None, + None, + None, ); let sec_message = self.build_signed_data(&request.tbs_message, header_info, signer, at); @@ -244,10 +242,15 @@ impl SignService { let header_info = HeaderInfo::new( Psid(Integer::from(request.its_aid as i64)), Some(Time64(Uint64(timestamp_its_microseconds()))), - None, None, None, None, None, + None, + None, + None, + None, + None, inline_p2pcd, requested_cert, - None, None, + None, + None, ); let sec_message = self.build_signed_data(&request.tbs_message, header_info, signer, &at); @@ -277,7 +280,10 @@ impl SignService { } } for h3 in request_list { - if self.cert_library.get_ca_certificate_by_hashedid3(h3).is_some() + if self + .cert_library + .get_ca_certificate_by_hashedid3(h3) + .is_some() && !self.requested_ats.contains(h3) { self.requested_ats.push(*h3); @@ -307,14 +313,14 @@ mod tests { use crate::security::certificate_library::CertificateLibrary; use crate::security::ecdsa_backend::EcdsaBackend; use crate::security::security_asn::ieee1609_dot2::{ - CertificateId, PsidGroupPermissions, PsidSsp, SequenceOfPsidGroupPermissions, - SequenceOfPsidSsp, SubjectPermissions, ToBeSignedCertificate, VerificationKeyIndicator, - SequenceOfAppExtensions, SequenceOfCertIssueExtensions, - SequenceOfCertRequestExtensions, EndEntityType, + CertificateId, EndEntityType, PsidGroupPermissions, PsidSsp, SequenceOfAppExtensions, + SequenceOfCertIssueExtensions, SequenceOfCertRequestExtensions, + SequenceOfPsidGroupPermissions, SequenceOfPsidSsp, SubjectPermissions, + ToBeSignedCertificate, VerificationKeyIndicator, }; use crate::security::security_asn::ieee1609_dot2_base_types::{ - CrlSeries, Duration as AsnDuration, EccP256CurvePoint, - PublicVerificationKey, Time32, Uint16, Uint32, ValidityPeriod, HashedId3, + CrlSeries, Duration as AsnDuration, EccP256CurvePoint, HashedId3, PublicVerificationKey, + Time32, Uint16, Uint32, ValidityPeriod, }; use crate::security::sn_sap::{GenerationLocation, SNSignRequest}; @@ -333,9 +339,8 @@ mod tests { )] .into(), ); - let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only( - vec![0u8; 32].into(), - )); + let pk = + PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only(vec![0u8; 32].into())); ToBeSignedCertificate::new( CertificateId::none(()), HashedId3(FixedOctetString::from([0u8; 3])), @@ -360,9 +365,8 @@ mod tests { let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(1))); let app_perms = SequenceOfPsidSsp(vec![PsidSsp::new(Psid(Integer::from(its_aid)), None)].into()); - let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only( - vec![0u8; 32].into(), - )); + let pk = + PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only(vec![0u8; 32].into())); ToBeSignedCertificate::new( CertificateId::none(()), HashedId3(FixedOctetString::from([0u8; 3])), diff --git a/src/security/sn_sap.rs b/src/security/sn_sap.rs index 0fa9949..eb0a573 100644 --- a/src/security/sn_sap.rs +++ b/src/security/sn_sap.rs @@ -78,14 +78,20 @@ mod tests { fn security_profile_values() { assert_eq!(SecurityProfile::NoSecurity as u8, 0); assert_eq!(SecurityProfile::CooperativeAwarenessMessage as u8, 1); - assert_eq!(SecurityProfile::DecentralizedEnvironmentalNotificationMessage as u8, 2); + assert_eq!( + SecurityProfile::DecentralizedEnvironmentalNotificationMessage as u8, + 2 + ); assert_eq!(SecurityProfile::VruAwarenessMessage as u8, 3); } #[test] fn security_profile_equality() { assert_eq!(SecurityProfile::NoSecurity, SecurityProfile::NoSecurity); - assert_ne!(SecurityProfile::NoSecurity, SecurityProfile::CooperativeAwarenessMessage); + assert_ne!( + SecurityProfile::NoSecurity, + SecurityProfile::CooperativeAwarenessMessage + ); } #[test] diff --git a/src/security/verify_service.rs b/src/security/verify_service.rs index f3dda7c..95967ab 100644 --- a/src/security/verify_service.rs +++ b/src/security/verify_service.rs @@ -7,10 +7,8 @@ use crate::security::certificate::{decode_ieee1609_dot2_data, encode_tbs_data, Certificate}; use crate::security::certificate_library::CertificateLibrary; use crate::security::ecdsa_backend::EcdsaBackend; -use crate::security::security_asn::ieee1609_dot2::{ - Ieee1609Dot2Content, SignerIdentifier, -}; use crate::security::security_asn::ieee1609_dot2::VerificationKeyIndicator; +use crate::security::security_asn::ieee1609_dot2::{Ieee1609Dot2Content, SignerIdentifier}; use crate::security::sign_service::SignService; use crate::security::sn_sap::{ReportVerify, SNVerifyConfirm, SNVerifyRequest}; @@ -230,8 +228,16 @@ pub fn verify_message( } // §5.2: p2pcdLearningRequest and missingCrlIdentifier must be absent - if signed_data.tbs_data.header_info.p2pcd_learning_request.is_some() - || signed_data.tbs_data.header_info.missing_crl_identifier.is_some() + if signed_data + .tbs_data + .header_info + .p2pcd_learning_request + .is_some() + || signed_data + .tbs_data + .header_info + .missing_crl_identifier + .is_some() { return ( SNVerifyConfirm { @@ -247,7 +253,12 @@ pub fn verify_message( // §7.1.2: DENM-specific header constraints if psid == 37 { - if signed_data.tbs_data.header_info.generation_location.is_none() { + if signed_data + .tbs_data + .header_info + .generation_location + .is_none() + { return ( SNVerifyConfirm { report: ReportVerify::IncompatibleProtocol, @@ -262,8 +273,16 @@ pub fn verify_message( // Forbidden fields for DENM if signed_data.tbs_data.header_info.expiry_time.is_some() || signed_data.tbs_data.header_info.encryption_key.is_some() - || signed_data.tbs_data.header_info.inline_p2pcd_request.is_some() - || signed_data.tbs_data.header_info.requested_certificate.is_some() + || signed_data + .tbs_data + .header_info + .inline_p2pcd_request + .is_some() + || signed_data + .tbs_data + .header_info + .requested_certificate + .is_some() { return ( SNVerifyConfirm { @@ -345,22 +364,22 @@ pub enum VerifyEvent { #[cfg(test)] mod tests { use super::*; - use rasn::prelude::*; use crate::security::certificate::OwnCertificate; use crate::security::certificate_library::CertificateLibrary; use crate::security::ecdsa_backend::EcdsaBackend; use crate::security::security_asn::ieee1609_dot2::{ - CertificateId, PsidGroupPermissions, PsidSsp, SequenceOfPsidGroupPermissions, - SequenceOfPsidSsp, SubjectPermissions, ToBeSignedCertificate, VerificationKeyIndicator, - SequenceOfAppExtensions, SequenceOfCertIssueExtensions, - SequenceOfCertRequestExtensions, EndEntityType, + CertificateId, EndEntityType, PsidGroupPermissions, PsidSsp, SequenceOfAppExtensions, + SequenceOfCertIssueExtensions, SequenceOfCertRequestExtensions, + SequenceOfPsidGroupPermissions, SequenceOfPsidSsp, SubjectPermissions, + ToBeSignedCertificate, VerificationKeyIndicator, }; use crate::security::security_asn::ieee1609_dot2_base_types::{ - CrlSeries, Duration as AsnDuration, EccP256CurvePoint, Psid, - PublicVerificationKey, Time32, Uint16, Uint32, ValidityPeriod, HashedId3, + CrlSeries, Duration as AsnDuration, EccP256CurvePoint, HashedId3, Psid, + PublicVerificationKey, Time32, Uint16, Uint32, ValidityPeriod, }; use crate::security::sign_service::SignService; use crate::security::sn_sap::{GenerationLocation, SNSignRequest, SNVerifyRequest}; + use rasn::prelude::*; fn make_root_tbs() -> ToBeSignedCertificate { let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(30))); @@ -377,9 +396,8 @@ mod tests { )] .into(), ); - let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only( - vec![0u8; 32].into(), - )); + let pk = + PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only(vec![0u8; 32].into())); ToBeSignedCertificate::new( CertificateId::none(()), HashedId3(FixedOctetString::from([0u8; 3])), @@ -404,9 +422,8 @@ mod tests { let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(1))); let app_perms = SequenceOfPsidSsp(vec![PsidSsp::new(Psid(Integer::from(its_aid)), None)].into()); - let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only( - vec![0u8; 32].into(), - )); + let pk = + PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only(vec![0u8; 32].into())); ToBeSignedCertificate::new( CertificateId::none(()), HashedId3(FixedOctetString::from([0u8; 3])), @@ -488,11 +505,7 @@ mod tests { .collect(), ); - ( - EcdsaBackend::new(), - verify_lib, - svc, - ) + (EcdsaBackend::new(), verify_lib, svc) } #[test] @@ -501,9 +514,7 @@ mod tests { let sec_msg = sign_cam_message(&mut svc); // Verify using sign service's own backend and cert library - let req = SNVerifyRequest { - message: sec_msg, - }; + let req = SNVerifyRequest { message: sec_msg }; let mut verify_lib = CertificateLibrary::new( &svc.backend, svc.cert_library @@ -533,9 +544,7 @@ mod tests { let (_, _, mut svc) = setup(); let sec_msg = sign_denm_message(&mut svc); - let req = SNVerifyRequest { - message: sec_msg, - }; + let req = SNVerifyRequest { message: sec_msg }; let mut verify_lib = CertificateLibrary::new( &svc.backend, svc.cert_library @@ -569,9 +578,7 @@ mod tests { *b ^= 0xFF; } - let req = SNVerifyRequest { - message: sec_msg, - }; + let req = SNVerifyRequest { message: sec_msg }; let mut verify_lib = CertificateLibrary::new( &svc.backend, svc.cert_library From 5965e21c1bdf30d84c71d0c833cbfaf8902696f0 Mon Sep 17 00:00:00 2001 From: Jordi Marias I Parella Date: Tue, 7 Apr 2026 11:51:00 +0200 Subject: [PATCH 5/6] Fixing pipeline --- examples/bench_cam_rx.rs | 4 +- examples/bench_cam_tx.rs | 4 +- examples/bench_congestion.rs | 4 +- examples/cam_sender_receiver.rs | 4 +- examples/denm_sender_receiver.rs | 8 +- examples/generate_certificate_chain.rs | 12 +- examples/secured_cam_sender_receiver.rs | 6 +- examples/secured_vam_sender_receiver.rs | 6 +- examples/vam_sender_receiver.rs | 4 +- src/btp/btp_header.rs | 12 ++ src/btp/service_access_point.rs | 12 ++ .../ca_basic_service/cam_bindings.rs | 6 +- src/facilities/ca_basic_service/cam_coder.rs | 4 +- .../ca_basic_service/cam_reception.rs | 6 +- .../ca_basic_service/cam_transmission.rs | 2 +- .../denm_bindings.rs | 6 +- .../denm_coder.rs | 2 +- .../local_dynamic_map/ldm_storage.rs | 2 + src/facilities/local_dynamic_map/mod.rs | 2 +- .../vru_awareness_service/vam_bindings.rs | 6 +- .../vru_awareness_service/vam_coder.rs | 2 +- .../vru_awareness_service/vam_transmission.rs | 32 +++-- src/geonet/basic_header.rs | 22 +-- src/geonet/common_header.rs | 4 +- src/geonet/gbc_extended_header.rs | 10 +- src/geonet/gn_address.rs | 34 ++--- src/geonet/location_table.rs | 6 +- src/geonet/mib.rs | 6 + src/geonet/position_vector.rs | 4 +- src/geonet/router.rs | 127 +++++++++--------- src/link_layer/raw_link_layer.rs | 10 +- src/security/certificate.rs | 38 +++--- src/security/certificate_library.rs | 40 +++--- src/security/security_asn.rs | 87 ++++++++---- src/security/sign_service.rs | 55 ++++---- src/security/verify_service.rs | 66 +++++---- 36 files changed, 348 insertions(+), 307 deletions(-) diff --git a/examples/bench_cam_rx.rs b/examples/bench_cam_rx.rs index fe59a4b..5b571d6 100644 --- a/examples/bench_cam_rx.rs +++ b/examples/bench_cam_rx.rs @@ -48,8 +48,8 @@ fn main() { mib.itsGnBeaconServiceRetransmitTimer = 0; // ── Routers + link layer ────────────────────────────────────────────────── - let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone(), None, None, None); - let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); + let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib, None, None, None); + let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib); let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::>(); RawLinkLayer::new(ll_to_gn_tx, gn_to_ll_rx, &iface, mac).start(); diff --git a/examples/bench_cam_tx.rs b/examples/bench_cam_tx.rs index 49638d8..5f2db51 100644 --- a/examples/bench_cam_tx.rs +++ b/examples/bench_cam_tx.rs @@ -59,8 +59,8 @@ fn main() { mib.itsGnBeaconServiceRetransmitTimer = 0; // ── Routers + link layer ────────────────────────────────────────────────── - let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone(), None, None, None); - let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); + let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib, None, None, None); + let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib); let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::>(); RawLinkLayer::new(ll_to_gn_tx, gn_to_ll_rx, &iface, mac).start(); diff --git a/examples/bench_congestion.rs b/examples/bench_congestion.rs index 5477277..859e76c 100644 --- a/examples/bench_congestion.rs +++ b/examples/bench_congestion.rs @@ -86,8 +86,8 @@ fn main() { let station_id = u32::from_be_bytes([mac[2], mac[3], mac[4], mac[5]]); // ── Routers + link layer ────────────────────────────────────────────────── - let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone(), None, None, None); - let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); + let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib, None, None, None); + let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib); let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::>(); RawLinkLayer::new(ll_to_gn_tx, gn_to_ll_rx, &iface, mac).start(); diff --git a/examples/cam_sender_receiver.rs b/examples/cam_sender_receiver.rs index d165cc0..edca990 100644 --- a/examples/cam_sender_receiver.rs +++ b/examples/cam_sender_receiver.rs @@ -87,10 +87,10 @@ fn main() { let ca_gps_rx = loc_svc.subscribe(); // → CAM transmission // ── Spawn GeoNetworking router ──────────────────────────────────────────── - let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone(), None, None, None); + let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib, None, None, None); // ── Spawn BTP router ───────────────────────────────────────────────────── - let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); + let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib); // ── Wire RawLinkLayer ──────────────────────────────────────────────────── let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::>(); diff --git a/examples/denm_sender_receiver.rs b/examples/denm_sender_receiver.rs index 1163d04..a1e4c44 100644 --- a/examples/denm_sender_receiver.rs +++ b/examples/denm_sender_receiver.rs @@ -86,10 +86,10 @@ fn main() { let gn_gps_rx = loc_svc.subscribe(); // ── Spawn GeoNetworking router ──────────────────────────────────────────── - let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone(), None, None, None); + let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib, None, None, None); // ── Spawn BTP router ────────────────────────────────────────────────────── - let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); + let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib); // ── Wire RawLinkLayer ───────────────────────────────────────────────────── let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::>(); @@ -176,7 +176,7 @@ fn main() { heading_deg: 90.0, pai: true, }; - loc_svc.publish(gps_fix.clone()); + loc_svc.publish(gps_fix); // ── Trigger DENM: Road Hazard (accident) at current position ────────────── // @@ -199,6 +199,6 @@ fn main() { println!("Publishing GPS fixes @ 1 Hz — Ctrl+C to stop\n"); loop { thread::sleep(Duration::from_secs(1)); - loc_svc.publish(gps_fix.clone()); + loc_svc.publish(gps_fix); } } diff --git a/examples/generate_certificate_chain.rs b/examples/generate_certificate_chain.rs index 4d5e7b2..6297198 100644 --- a/examples/generate_certificate_chain.rs +++ b/examples/generate_certificate_chain.rs @@ -60,7 +60,7 @@ fn app_permissions(psids: &[u64]) -> SequenceOfPsidSsp { .iter() .map(|&p| PsidSsp::new(Psid(Integer::from(p as i128)), None)) .collect(); - SequenceOfPsidSsp(items.into()) + SequenceOfPsidSsp(items) } /// Helper: build a `SequenceOfPsidGroupPermissions` with @@ -74,12 +74,12 @@ fn cert_issue_permissions_explicit( .map(|&p| PsidSspRange::new(Psid(Integer::from(p as i128)), None)) .collect(); let perm = PsidGroupPermissions::new( - SubjectPermissions::explicit(SequenceOfPsidSspRange(ranges.into())), + SubjectPermissions::explicit(SequenceOfPsidSspRange(ranges)), Integer::from(min_chain_length), Integer::from(0i128), default_ee_type(), ); - SequenceOfPsidGroupPermissions(vec![perm].into()) + SequenceOfPsidGroupPermissions(vec![perm]) } /// Helper: build a `SequenceOfPsidGroupPermissions` with @@ -91,7 +91,7 @@ fn cert_issue_permissions_all(min_chain_length: i128) -> SequenceOfPsidGroupPerm Integer::from(0i128), default_ee_type(), ); - SequenceOfPsidGroupPermissions(vec![perm].into()) + SequenceOfPsidGroupPermissions(vec![perm]) } fn main() { @@ -102,7 +102,7 @@ fn main() { // ─── Root CA ───────────────────────────────────────────────────────── let root_tbs = ToBeSignedCertificate::new( - CertificateId::name(Hostname("RootCA".to_string().into())), + CertificateId::name(Hostname("RootCA".to_string())), HashedId3(FixedOctetString::from([0u8; 3])), CrlSeries(Uint16(0)), ValidityPeriod::new(its_time32_now(), Duration::years(Uint16(20))), @@ -131,7 +131,7 @@ fn main() { // ─── Authorization Authority ───────────────────────────────────────── let aa_tbs = ToBeSignedCertificate::new( - CertificateId::name(Hostname("AA".to_string().into())), + CertificateId::name(Hostname("AA".to_string())), HashedId3(FixedOctetString::from([0u8; 3])), CrlSeries(Uint16(0)), ValidityPeriod::new(its_time32_now(), Duration::years(Uint16(10))), diff --git a/examples/secured_cam_sender_receiver.rs b/examples/secured_cam_sender_receiver.rs index 092a5e7..30d7a61 100644 --- a/examples/secured_cam_sender_receiver.rs +++ b/examples/secured_cam_sender_receiver.rs @@ -200,8 +200,8 @@ fn main() { let ca_gps_rx = loc_svc.subscribe(); // ── Spawn GN router and BTP router ─────────────────────────────────── - let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone(), None, None, None); - let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); + let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib, None, None, None); + let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib); // ── Wire RawLinkLayer ──────────────────────────────────────────────── let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::>(); @@ -300,7 +300,7 @@ fn main() { svc.notify_inline_p2pcd_request(h3s); } VerifyEvent::ReceivedCaCertificate(cert) => { - svc.notify_received_ca_certificate(cert.clone()); + svc.notify_received_ca_certificate(cert.as_ref().clone()); } } } diff --git a/examples/secured_vam_sender_receiver.rs b/examples/secured_vam_sender_receiver.rs index d00f19b..f91cd87 100644 --- a/examples/secured_vam_sender_receiver.rs +++ b/examples/secured_vam_sender_receiver.rs @@ -193,8 +193,8 @@ fn main() { let vru_gps_rx = loc_svc.subscribe(); // ── Spawn GN router and BTP router ─────────────────────────────────── - let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone(), None, None, None); - let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); + let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib, None, None, None); + let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib); // ── Wire RawLinkLayer ──────────────────────────────────────────────── let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::>(); @@ -291,7 +291,7 @@ fn main() { svc.notify_inline_p2pcd_request(h3s); } VerifyEvent::ReceivedCaCertificate(cert) => { - svc.notify_received_ca_certificate(cert.clone()); + svc.notify_received_ca_certificate(cert.as_ref().clone()); } } } diff --git a/examples/vam_sender_receiver.rs b/examples/vam_sender_receiver.rs index 631176b..0e81247 100644 --- a/examples/vam_sender_receiver.rs +++ b/examples/vam_sender_receiver.rs @@ -83,10 +83,10 @@ fn main() { let vru_gps_rx = loc_svc.subscribe(); // → VAM transmission // ── Spawn GeoNetworking router ──────────────────────────────────────────── - let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib.clone(), None, None, None); + let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib, None, None, None); // ── Spawn BTP router ────────────────────────────────────────────────────── - let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib.clone()); + let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib); // ── Wire RawLinkLayer ───────────────────────────────────────────────────── let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::>(); diff --git a/src/btp/btp_header.rs b/src/btp/btp_header.rs index 9635985..fb54e20 100644 --- a/src/btp/btp_header.rs +++ b/src/btp/btp_header.rs @@ -28,6 +28,12 @@ pub struct BTPAHeader { source_port: u16, } +impl Default for BTPAHeader { + fn default() -> Self { + Self::new() + } +} + impl BTPAHeader { pub fn new() -> Self { BTPAHeader { @@ -88,6 +94,12 @@ pub struct BTPBHeader { pub destination_port_info: u16, } +impl Default for BTPBHeader { + fn default() -> Self { + Self::new() + } +} + impl BTPBHeader { pub fn new() -> Self { BTPBHeader { diff --git a/src/btp/service_access_point.rs b/src/btp/service_access_point.rs index 38bc310..323e3be 100644 --- a/src/btp/service_access_point.rs +++ b/src/btp/service_access_point.rs @@ -32,6 +32,12 @@ pub struct BTPDataRequest { pub data: Vec, } +impl Default for BTPDataRequest { + fn default() -> Self { + Self::new() + } +} + impl BTPDataRequest { pub fn new() -> Self { BTPDataRequest { @@ -92,6 +98,12 @@ pub struct BTPDataIndication { pub data: Vec, } +impl Default for BTPDataIndication { + fn default() -> Self { + Self::new() + } +} + impl BTPDataIndication { pub fn new() -> Self { BTPDataIndication { diff --git a/src/facilities/ca_basic_service/cam_bindings.rs b/src/facilities/ca_basic_service/cam_bindings.rs index 38d4f60..632a19a 100644 --- a/src/facilities/ca_basic_service/cam_bindings.rs +++ b/src/facilities/ca_basic_service/cam_bindings.rs @@ -6,7 +6,8 @@ non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::doc_overindented_list_items )] pub mod cam_pdu_descriptions { extern crate alloc; @@ -868,7 +869,8 @@ pub mod cam_pdu_descriptions { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::doc_overindented_list_items )] pub mod etsi_its_cdd { extern crate alloc; diff --git a/src/facilities/ca_basic_service/cam_coder.rs b/src/facilities/ca_basic_service/cam_coder.rs index 2025cc8..9531389 100644 --- a/src/facilities/ca_basic_service/cam_coder.rs +++ b/src/facilities/ca_basic_service/cam_coder.rs @@ -113,7 +113,7 @@ mod tests { fn generation_delta_time_now_nonzero() { let gdt = generation_delta_time_now(); // Should be non-zero since we're well after ITS epoch - assert!(gdt.0 > 0 || gdt.0 == 0); // always true, but exercises code path + let _ = gdt; // verify it was created successfully } #[test] @@ -134,7 +134,7 @@ mod tests { #[test] fn cam_coder_default() { - let _coder = CamCoder::default(); + let _coder = CamCoder; } #[test] diff --git a/src/facilities/ca_basic_service/cam_reception.rs b/src/facilities/ca_basic_service/cam_reception.rs index 84776d5..f98ca74 100644 --- a/src/facilities/ca_basic_service/cam_reception.rs +++ b/src/facilities/ca_basic_service/cam_reception.rs @@ -40,7 +40,7 @@ impl CAMReceptionManagement { /// * `coder` — shared [`CamCoder`] instance for UPER decoding. /// * `cam_tx` — sender into which decoded [`Cam`] PDUs are pushed. /// * `ldm` — optional LDM handle; when `Some`, each decoded CAM is - /// inserted into the LDM before forwarding. + /// inserted into the LDM before forwarding. /// /// The caller should hold the corresponding `Receiver` — typically /// returned from [`CooperativeAwarenessBasicService::new`]. @@ -91,8 +91,8 @@ impl CAMReceptionManagement { timestamp_its: now_its_ms(), lat_etsi: lat, lon_etsi: lon, - altitude_cm: (alt / 10) as i32, // AltitudeValue in 0.01 m → cm - time_validity_s: 1, // CAM validity: 1 s + altitude_cm: (alt / 10), // AltitudeValue in 0.01 m → cm + time_validity_s: 1, // CAM validity: 1 s data_object: ItsDataObject::Cam(Box::new(cam.clone())), }); } diff --git a/src/facilities/ca_basic_service/cam_transmission.rs b/src/facilities/ca_basic_service/cam_transmission.rs index 8b19600..36f2a96 100644 --- a/src/facilities/ca_basic_service/cam_transmission.rs +++ b/src/facilities/ca_basic_service/cam_transmission.rs @@ -315,7 +315,7 @@ impl CAMTransmissionManagement { // Timer tick — evaluate CAM conditions (Annex B.2.4 step 2) let fix = match current_fix { - Some(ref f) => f.clone(), + Some(ref f) => *f, None => continue, }; diff --git a/src/facilities/decentralized_environmental_notification_service/denm_bindings.rs b/src/facilities/decentralized_environmental_notification_service/denm_bindings.rs index d276f27..5c67d53 100644 --- a/src/facilities/decentralized_environmental_notification_service/denm_bindings.rs +++ b/src/facilities/decentralized_environmental_notification_service/denm_bindings.rs @@ -6,7 +6,8 @@ non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::doc_overindented_list_items )] pub mod denm_pdu_description { extern crate alloc; @@ -780,7 +781,8 @@ pub mod denm_pdu_description { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::doc_overindented_list_items )] pub mod etsi_its_cdd { extern crate alloc; diff --git a/src/facilities/decentralized_environmental_notification_service/denm_coder.rs b/src/facilities/decentralized_environmental_notification_service/denm_coder.rs index a7299b6..6374879 100644 --- a/src/facilities/decentralized_environmental_notification_service/denm_coder.rs +++ b/src/facilities/decentralized_environmental_notification_service/denm_coder.rs @@ -113,7 +113,7 @@ mod tests { #[test] fn denm_coder_default() { - let _coder = DenmCoder::default(); + let _coder = DenmCoder; } #[test] diff --git a/src/facilities/local_dynamic_map/ldm_storage.rs b/src/facilities/local_dynamic_map/ldm_storage.rs index d776558..26f61ac 100644 --- a/src/facilities/local_dynamic_map/ldm_storage.rs +++ b/src/facilities/local_dynamic_map/ldm_storage.rs @@ -113,6 +113,7 @@ impl LdmStore { // ── Write operations ────────────────────────────────────────────────── /// Insert a new record and return its assigned ID. + #[allow(clippy::too_many_arguments)] pub fn insert( &mut self, application_id: u32, @@ -144,6 +145,7 @@ impl LdmStore { /// Replace an existing record's payload and metadata. /// /// Returns `false` when `id` is not found. + #[allow(clippy::too_many_arguments)] pub fn update( &mut self, id: u64, diff --git a/src/facilities/local_dynamic_map/mod.rs b/src/facilities/local_dynamic_map/mod.rs index 2c59b77..7fa869e 100644 --- a/src/facilities/local_dynamic_map/mod.rs +++ b/src/facilities/local_dynamic_map/mod.rs @@ -98,7 +98,7 @@ impl LdmFacility { /// * `area_lat_etsi` — latitude of the local station (ETSI × 1e7). /// * `area_lon_etsi` — longitude of the local station (ETSI × 1e7). /// * `area_radius_m` — maintenance area radius in metres. - /// Pass `0.0` to disable spatial GC. + /// Pass `0.0` to disable spatial GC. /// /// # Returns /// An `LdmHandle` (`Arc`) that can be shared freely across diff --git a/src/facilities/vru_awareness_service/vam_bindings.rs b/src/facilities/vru_awareness_service/vam_bindings.rs index 83823c8..b3ffe37 100644 --- a/src/facilities/vru_awareness_service/vam_bindings.rs +++ b/src/facilities/vru_awareness_service/vam_bindings.rs @@ -6,7 +6,8 @@ non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::doc_overindented_list_items )] pub mod etsi_its_cdd { extern crate alloc; @@ -7353,7 +7354,8 @@ pub mod etsi_its_cdd { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::doc_overindented_list_items )] pub mod vam_pdu_descriptions { extern crate alloc; diff --git a/src/facilities/vru_awareness_service/vam_coder.rs b/src/facilities/vru_awareness_service/vam_coder.rs index b17a09e..c684a48 100644 --- a/src/facilities/vru_awareness_service/vam_coder.rs +++ b/src/facilities/vru_awareness_service/vam_coder.rs @@ -132,7 +132,7 @@ mod tests { #[test] fn vam_coder_default() { - let _coder = VamCoder::default(); + let _coder = VamCoder; } #[test] diff --git a/src/facilities/vru_awareness_service/vam_transmission.rs b/src/facilities/vru_awareness_service/vam_transmission.rs index 494a041..2a2cd66 100644 --- a/src/facilities/vru_awareness_service/vam_transmission.rs +++ b/src/facilities/vru_awareness_service/vam_transmission.rs @@ -280,35 +280,33 @@ impl VAMTransmissionManagement { } let fix = match current_fix { - Some(ref f) => f.clone(), + Some(ref f) => *f, None => continue, }; let now = Instant::now(); let mut should_send = false; - if last_vam_time.is_none() { - // First VAM after activation — send immediately - should_send = true; - } else { - let elapsed_ms = now.duration_since(last_vam_time.unwrap()).as_millis() as u64; + if let Some(last_time) = last_vam_time { + let elapsed_ms = now.duration_since(last_time).as_millis() as u64; // Condition 1: elapsed ≥ T_GenVamMax - if elapsed_ms >= T_GEN_VAM_MAX_MS { - should_send = true; - } // Conditions 2-4: dynamics changed AND elapsed ≥ T_GenVam_DCC - else if elapsed_ms >= T_GEN_VAM_DCC_MS - && check_triggers( - &fix, - last_vam_lat, - last_vam_lon, - last_vam_speed, - last_vam_heading, - ) + if elapsed_ms >= T_GEN_VAM_MAX_MS + || (elapsed_ms >= T_GEN_VAM_DCC_MS + && check_triggers( + &fix, + last_vam_lat, + last_vam_lon, + last_vam_speed, + last_vam_heading, + )) { should_send = true; } + } else { + // First VAM after activation — send immediately + should_send = true; } if !should_send { diff --git a/src/geonet/basic_header.rs b/src/geonet/basic_header.rs index f7c612d..9e34220 100644 --- a/src/geonet/basic_header.rs +++ b/src/geonet/basic_header.rs @@ -67,25 +67,25 @@ pub struct LT { impl LT { pub fn start_in_milliseconds(value: u32) -> Self { if value >= 100000 { - return LT { + LT { multiplier: (value / 100000) as u8, base: LTBase::OneHundredSeconds, - }; + } } else if value >= 10000 { - return LT { + LT { multiplier: (value / 10000) as u8, base: LTBase::TenSeconds, - }; + } } else if value >= 1000 { - return LT { + LT { multiplier: (value / 1000) as u8, base: LTBase::OneSecond, - }; + } } else if value >= 50 { - return LT { + LT { multiplier: (value / 50) as u8, base: LTBase::FiftyMilliseconds, - }; + } } else { panic!("Invalid LT Value"); } @@ -139,11 +139,11 @@ impl BasicHeader { pub fn initialize_with_mib(mib: &Mib) -> Self { BasicHeader { - version: mib.itsGnProtocolVersion.clone(), + version: mib.itsGnProtocolVersion, nh: BasicNH::CommonHeader, - rhl: mib.itsGnDefaultHopLimit.clone(), + rhl: mib.itsGnDefaultHopLimit, reserved: 0, - lt: LT::start_in_seconds(mib.itsGnDefaultPacketLifetime.clone()), + lt: LT::start_in_seconds(mib.itsGnDefaultPacketLifetime), } } diff --git a/src/geonet/common_header.rs b/src/geonet/common_header.rs index c5088bc..d0887b2 100644 --- a/src/geonet/common_header.rs +++ b/src/geonet/common_header.rs @@ -27,9 +27,9 @@ impl CommonHeader { reserved: 0, ht: request.packet_transport_type.header_type.clone(), hst: request.packet_transport_type.header_sub_type.clone(), - tc: request.traffic_class.clone(), + tc: request.traffic_class, flags: (mib.itsGnIsMobile.encode()) << 7, - pl: request.length.clone(), + pl: request.length, mhl: request.max_hop_limit, reserved2: 0, } diff --git a/src/geonet/gbc_extended_header.rs b/src/geonet/gbc_extended_header.rs index ced1623..f67754a 100644 --- a/src/geonet/gbc_extended_header.rs +++ b/src/geonet/gbc_extended_header.rs @@ -43,11 +43,11 @@ impl GBCExtendedHeader { so_pv: LongPositionVector::decode([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]), - latitude: request.area.latitude.clone(), - longitude: request.area.longitude.clone(), - a: request.area.a.clone(), - b: request.area.b.clone(), - angle: request.area.angle.clone(), + latitude: request.area.latitude, + longitude: request.area.longitude, + a: request.area.a, + b: request.area.b, + angle: request.area.angle, reserved2: 0, } } diff --git a/src/geonet/gn_address.rs b/src/geonet/gn_address.rs index f6ba956..b49f12a 100644 --- a/src/geonet/gn_address.rs +++ b/src/geonet/gn_address.rs @@ -12,8 +12,8 @@ pub enum M { impl M { pub fn encode_to_address(&self) -> u64 { match self { - M::GnUnicast => (0 << 7) << 8 * 7, - M::GnMulticast => (1 << 7) << 8 * 7, + M::GnUnicast => (0 << 7) << (8 * 7), + M::GnMulticast => (1 << 7) << (8 * 7), } } @@ -47,19 +47,19 @@ pub enum ST { impl ST { pub fn encode_to_address(&self) -> u64 { match self { - ST::Unknown => (0 << 2) << 8 * 7, - ST::Pedestrian => (1 << 2) << 8 * 7, - ST::Cyclist => (2 << 2) << 8 * 7, - ST::Moped => (3 << 2) << 8 * 7, - ST::Motorcycle => (4 << 2) << 8 * 7, - ST::PassengerCar => (5 << 2) << 8 * 7, - ST::Bus => (6 << 2) << 8 * 7, - ST::LightTruck => (7 << 2) << 8 * 7, - ST::HeavyTruck => (8 << 2) << 8 * 7, - ST::Trailer => (9 << 2) << 8 * 7, - ST::SpecialVehicle => (10 << 2) << 8 * 7, - ST::Tram => (11 << 2) << 8 * 7, - ST::RoadSideUnit => (12 << 2) << 8 * 7, + ST::Unknown => (0 << 2) << (8 * 7), + ST::Pedestrian => (1 << 2) << (8 * 7), + ST::Cyclist => (2 << 2) << (8 * 7), + ST::Moped => (3 << 2) << (8 * 7), + ST::Motorcycle => (4 << 2) << (8 * 7), + ST::PassengerCar => (5 << 2) << (8 * 7), + ST::Bus => (6 << 2) << (8 * 7), + ST::LightTruck => (7 << 2) << (8 * 7), + ST::HeavyTruck => (8 << 2) << (8 * 7), + ST::Trailer => (9 << 2) << (8 * 7), + ST::SpecialVehicle => (10 << 2) << (8 * 7), + ST::Tram => (11 << 2) << (8 * 7), + ST::RoadSideUnit => (12 << 2) << (8 * 7), } } @@ -106,8 +106,8 @@ impl MID { pub fn decode_from_address(address: u64) -> Self { let mut mid: [u8; 6] = [0; 6]; - for i in 0..6 { - mid[i] = (address >> (8 * (5 - i))) as u8; + for (i, byte) in mid.iter_mut().enumerate() { + *byte = (address >> (8 * (5 - i))) as u8; } MID { mid } } diff --git a/src/geonet/location_table.rs b/src/geonet/location_table.rs index fba14f6..313e00f 100644 --- a/src/geonet/location_table.rs +++ b/src/geonet/location_table.rs @@ -56,9 +56,9 @@ impl LocationTableEntry { /// Update the stored LPV only if strictly newer (§C.2). /// When the stored TST is zero (initial entry), accepts unconditionally. pub fn update_position_vector(&mut self, position_vector: &LongPositionVector) { - if self.position_vector.tst == Tst::set_in_normal_timestamp_milliseconds(0) { - self.position_vector = *position_vector; - } else if position_vector.tst > self.position_vector.tst { + if self.position_vector.tst == Tst::set_in_normal_timestamp_milliseconds(0) + || position_vector.tst > self.position_vector.tst + { self.position_vector = *position_vector; } } diff --git a/src/geonet/mib.rs b/src/geonet/mib.rs index 9aa095d..bfe8b5f 100644 --- a/src/geonet/mib.rs +++ b/src/geonet/mib.rs @@ -213,6 +213,12 @@ pub struct Mib { pub itsGnDefaultTrafficClass: u8, } +impl Default for Mib { + fn default() -> Self { + Self::new() + } +} + impl Mib { pub fn new() -> Self { { diff --git a/src/geonet/position_vector.rs b/src/geonet/position_vector.rs index 16ee080..59ba585 100644 --- a/src/geonet/position_vector.rs +++ b/src/geonet/position_vector.rs @@ -37,8 +37,8 @@ impl Tst { pub fn decode(bytes: &[u8]) -> Self { let mut msec: u32 = 0; - for i in 0..4 { - msec |= (bytes[i] as u32) << (8 * (3 - i)); + for (i, &byte) in bytes.iter().enumerate().take(4) { + msec |= (byte as u32) << (8 * (3 - i)); } Tst { msec } } diff --git a/src/geonet/router.rs b/src/geonet/router.rs index b2c4ddf..4d9ee9c 100644 --- a/src/geonet/router.rs +++ b/src/geonet/router.rs @@ -472,18 +472,16 @@ impl Router { .collect() }; - if algorithm == GNForwardingAlgorithmResponse::AreaForwarding { + if algorithm == GNForwardingAlgorithmResponse::AreaForwarding + || (algorithm == GNForwardingAlgorithmResponse::NonAreaForwarding + && self.gn_greedy_forwarding( + request.area.latitude as i32, + request.area.longitude as i32, + &request.traffic_class, + )) + { let packet = build_packet(&actual_basic); return self.send_to_link_layer(packet); - } else if algorithm == GNForwardingAlgorithmResponse::NonAreaForwarding { - if self.gn_greedy_forwarding( - request.area.latitude as i32, - request.area.longitude as i32, - &request.traffic_class, - ) { - let packet = build_packet(&actual_basic); - return self.send_to_link_layer(packet); - } } GNDataConfirm { @@ -667,7 +665,7 @@ impl Router { } // Dispatch plain_message as Common Header + payload - let plain_basic = basic_header.clone().set_nh(BasicNH::CommonHeader); + let plain_basic = (*basic_header).set_nh(BasicNH::CommonHeader); self.process_common_header(&confirm.plain_message, &plain_basic); } @@ -818,7 +816,7 @@ impl Router { }; let area_hst = match &common_header.hst { - HeaderSubType::GeoBroadcast(h) => h.clone(), + HeaderSubType::GeoBroadcast(h) => *h, _ => return None, }; @@ -908,7 +906,7 @@ impl Router { }; let area_hst = match &common_header.hst { - HeaderSubType::GeoAnycast(h) => h.clone(), + HeaderSubType::GeoAnycast(h) => *h, _ => return None, }; @@ -960,7 +958,7 @@ impl Router { } } - let fwd_basic = basic_header.clone().set_rhl(new_rhl); + let fwd_basic = (*basic_header).set_rhl(new_rhl); if self.location_table.get_neighbours().is_empty() && common_header.tc.scf { return None; @@ -1032,19 +1030,18 @@ impl Router { // Forward let new_rhl = basic_header.rhl.saturating_sub(1); - if new_rhl > 0 { - if !(self.location_table.get_neighbours().is_empty() && common_header.tc.scf) { - let fwd_basic = basic_header.clone().set_rhl(new_rhl); - let fwd_packet: Vec = fwd_basic - .encode() - .iter() - .copied() - .chain(common_header.encode().iter().copied()) - .chain(ext.encode().iter().copied()) - .chain(payload.iter().copied()) - .collect(); - let _ = self.link_layer_tx.send(fwd_packet); - } + if new_rhl > 0 && !(self.location_table.get_neighbours().is_empty() && common_header.tc.scf) + { + let fwd_basic = (*basic_header).set_rhl(new_rhl); + let fwd_packet: Vec = fwd_basic + .encode() + .iter() + .copied() + .chain(common_header.encode().iter().copied()) + .chain(ext.encode().iter().copied()) + .chain(payload.iter().copied()) + .collect(); + let _ = self.link_layer_tx.send(fwd_packet); } Some(indication) @@ -1115,25 +1112,24 @@ impl Router { } let new_rhl = basic_header.rhl.saturating_sub(1); - if new_rhl > 0 { - if !(self.location_table.get_neighbours().is_empty() && common_header.tc.scf) { - if self.gn_greedy_forwarding( - fwd_ext.de_pv.latitude as i32, - fwd_ext.de_pv.longitude as i32, - &common_header.tc, - ) { - let fwd_basic = basic_header.clone().set_rhl(new_rhl); - let fwd_packet: Vec = fwd_basic - .encode() - .iter() - .copied() - .chain(common_header.encode().iter().copied()) - .chain(fwd_ext.encode().iter().copied()) - .chain(payload.iter().copied()) - .collect(); - let _ = self.link_layer_tx.send(fwd_packet); - } - } + if new_rhl > 0 + && !(self.location_table.get_neighbours().is_empty() && common_header.tc.scf) + && self.gn_greedy_forwarding( + fwd_ext.de_pv.latitude as i32, + fwd_ext.de_pv.longitude as i32, + &common_header.tc, + ) + { + let fwd_basic = (*basic_header).set_rhl(new_rhl); + let fwd_packet: Vec = fwd_basic + .encode() + .iter() + .copied() + .chain(common_header.encode().iter().copied()) + .chain(fwd_ext.encode().iter().copied()) + .chain(payload.iter().copied()) + .collect(); + let _ = self.link_layer_tx.send(fwd_packet); } None @@ -1240,7 +1236,7 @@ impl Router { } let new_rhl = basic_header.rhl.saturating_sub(1); if new_rhl > 0 { - let fwd_basic = basic_header.clone().set_rhl(new_rhl); + let fwd_basic = (*basic_header).set_rhl(new_rhl); let fwd_packet: Vec = fwd_basic .encode() .iter() @@ -1323,7 +1319,7 @@ impl Router { let new_rhl = basic_header.rhl.saturating_sub(1); if new_rhl > 0 { - let fwd_basic = basic_header.clone().set_rhl(new_rhl); + let fwd_basic = (*basic_header).set_rhl(new_rhl); let fwd_packet: Vec = fwd_basic .encode() .iter() @@ -1345,10 +1341,7 @@ impl Router { if let Some(entry) = self.location_table.get_entry_ref(sought_addr) { if entry.ls_pending { if let Some(req) = buffered_request { - self.ls_packet_buffers - .entry(key) - .or_insert_with(Vec::new) - .push(req); + self.ls_packet_buffers.entry(key).or_default().push(req); } return; } @@ -1420,7 +1413,7 @@ impl Router { gbc_ext: &GBCExtendedHeader, payload: &[u8], ) -> GNDataConfirm { - let mut fwd_basic = basic_header.clone(); + let mut fwd_basic = *basic_header; fwd_basic.rhl = basic_header.rhl.saturating_sub(1); if fwd_basic.rhl == 0 { @@ -1474,22 +1467,22 @@ impl Router { .chain(payload.iter().copied()) .collect(); return self.send_to_link_layer(fwd_packet); - } else if algorithm == GNForwardingAlgorithmResponse::NonAreaForwarding { - if self.gn_greedy_forwarding( + } else if algorithm == GNForwardingAlgorithmResponse::NonAreaForwarding + && self.gn_greedy_forwarding( gbc_ext.latitude as i32, gbc_ext.longitude as i32, &common_header.tc, - ) { - let fwd_packet: Vec = fwd_basic - .encode() - .iter() - .copied() - .chain(common_header.encode().iter().copied()) - .chain(gbc_ext.encode().iter().copied()) - .chain(payload.iter().copied()) - .collect(); - return self.send_to_link_layer(fwd_packet); - } + ) + { + let fwd_packet: Vec = fwd_basic + .encode() + .iter() + .copied() + .chain(common_header.encode().iter().copied()) + .chain(gbc_ext.encode().iter().copied()) + .chain(payload.iter().copied()) + .collect(); + return self.send_to_link_layer(fwd_packet); } } @@ -1605,7 +1598,7 @@ impl Router { ) -> bool { let cbf_key = (gbc_ext.so_pv.gn_addr.encode_to_int(), gbc_ext.sn); - if let Some(_) = self.cbf_buffer.remove(&cbf_key) { + if self.cbf_buffer.remove(&cbf_key).is_some() { // Duplicate while buffering — suppress return false; } diff --git a/src/link_layer/raw_link_layer.rs b/src/link_layer/raw_link_layer.rs index fa1052b..e7bfb9b 100644 --- a/src/link_layer/raw_link_layer.rs +++ b/src/link_layer/raw_link_layer.rs @@ -142,7 +142,7 @@ impl RawLinkLayer { libc::socket( libc::AF_PACKET, libc::SOCK_RAW, - (ETH_P_GEONET as u16).to_be() as libc::c_int, + ETH_P_GEONET.to_be() as libc::c_int, ) }; if sock < 0 { @@ -172,7 +172,7 @@ impl RawLinkLayer { // Bind to the specific interface so we don't receive from all NICs. let sll = libc::sockaddr_ll { sll_family: libc::AF_PACKET as u16, - sll_protocol: (ETH_P_GEONET as u16).to_be(), + sll_protocol: ETH_P_GEONET.to_be(), sll_ifindex: if_index as i32, sll_hatype: 0, sll_pkttype: 0, @@ -253,8 +253,10 @@ impl RawLinkLayer { return; } }; - let mut config = Config::default(); - config.write_timeout = None; + let config = Config { + write_timeout: None, + ..Config::default() + }; let (mut tx, _rx) = match datalink::channel(&interface, config) { Ok(Channel::Ethernet(t, r)) => (t, r), Ok(_) => { diff --git a/src/security/certificate.rs b/src/security/certificate.rs index e72eea1..dbe558c 100644 --- a/src/security/certificate.rs +++ b/src/security/certificate.rs @@ -505,19 +505,16 @@ mod tests { let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(30))); - let perms = SequenceOfPsidGroupPermissions( - vec![PsidGroupPermissions::new( - SubjectPermissions::all(()), - Integer::from(1), - Integer::from(0), - { - let mut bits = FixedBitString::<8>::default(); - bits.set(0, true); - EndEntityType(bits) - }, - )] - .into(), - ); + let perms = SequenceOfPsidGroupPermissions(vec![PsidGroupPermissions::new( + SubjectPermissions::all(()), + Integer::from(1), + Integer::from(0), + { + let mut bits = FixedBitString::<8>::default(); + bits.set(0, true); + EndEntityType(bits) + }, + )]); let placeholder_pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only(vec![0u8; 32].into())); @@ -536,9 +533,9 @@ mod tests { None, VerificationKeyIndicator::verificationKey(placeholder_pk), None, - SequenceOfAppExtensions(vec![].into()), - SequenceOfCertIssueExtensions(vec![].into()), - SequenceOfCertRequestExtensions(vec![].into()), + SequenceOfAppExtensions(vec![]), + SequenceOfCertIssueExtensions(vec![]), + SequenceOfCertRequestExtensions(vec![]), ) } @@ -554,8 +551,7 @@ mod tests { let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(1))); - let app_perms = - SequenceOfPsidSsp(vec![PsidSsp::new(Psid(Integer::from(36_i64)), None)].into()); + let app_perms = SequenceOfPsidSsp(vec![PsidSsp::new(Psid(Integer::from(36_i64)), None)]); let placeholder_pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only(vec![0u8; 32].into())); @@ -574,9 +570,9 @@ mod tests { None, VerificationKeyIndicator::verificationKey(placeholder_pk), None, - SequenceOfAppExtensions(vec![].into()), - SequenceOfCertIssueExtensions(vec![].into()), - SequenceOfCertRequestExtensions(vec![].into()), + SequenceOfAppExtensions(vec![]), + SequenceOfCertIssueExtensions(vec![]), + SequenceOfCertRequestExtensions(vec![]), ) } diff --git a/src/security/certificate_library.rs b/src/security/certificate_library.rs index 5495769..a353d67 100644 --- a/src/security/certificate_library.rs +++ b/src/security/certificate_library.rs @@ -203,19 +203,16 @@ mod tests { fn make_root_tbs() -> crate::security::security_asn::ieee1609_dot2::ToBeSignedCertificate { let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(30))); - let perms = SequenceOfPsidGroupPermissions( - vec![PsidGroupPermissions::new( - SubjectPermissions::all(()), - Integer::from(1), - Integer::from(0), - { - let mut bits = FixedBitString::<8>::default(); - bits.set(0, true); - EndEntityType(bits) - }, - )] - .into(), - ); + let perms = SequenceOfPsidGroupPermissions(vec![PsidGroupPermissions::new( + SubjectPermissions::all(()), + Integer::from(1), + Integer::from(0), + { + let mut bits = FixedBitString::<8>::default(); + bits.set(0, true); + EndEntityType(bits) + }, + )]); let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only(vec![0u8; 32].into())); crate::security::security_asn::ieee1609_dot2::ToBeSignedCertificate::new( @@ -232,16 +229,15 @@ mod tests { None, VerificationKeyIndicator::verificationKey(pk), None, - SequenceOfAppExtensions(vec![].into()), - SequenceOfCertIssueExtensions(vec![].into()), - SequenceOfCertRequestExtensions(vec![].into()), + SequenceOfAppExtensions(vec![]), + SequenceOfCertIssueExtensions(vec![]), + SequenceOfCertRequestExtensions(vec![]), ) } fn make_at_tbs() -> crate::security::security_asn::ieee1609_dot2::ToBeSignedCertificate { let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(1))); - let app_perms = - SequenceOfPsidSsp(vec![PsidSsp::new(Psid(Integer::from(36_i64)), None)].into()); + let app_perms = SequenceOfPsidSsp(vec![PsidSsp::new(Psid(Integer::from(36_i64)), None)]); let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only(vec![0u8; 32].into())); crate::security::security_asn::ieee1609_dot2::ToBeSignedCertificate::new( @@ -258,9 +254,9 @@ mod tests { None, VerificationKeyIndicator::verificationKey(pk), None, - SequenceOfAppExtensions(vec![].into()), - SequenceOfCertIssueExtensions(vec![].into()), - SequenceOfCertRequestExtensions(vec![].into()), + SequenceOfAppExtensions(vec![]), + SequenceOfCertIssueExtensions(vec![]), + SequenceOfCertRequestExtensions(vec![]), ) } @@ -370,7 +366,7 @@ mod tests { vec![aa.cert.clone()], vec![at.cert.clone()], ); - let result = lib.verify_sequence_of_certificates(&[at.cert.clone()], &backend); + let result = lib.verify_sequence_of_certificates(std::slice::from_ref(&at.cert), &backend); assert!(result.is_some()); } diff --git a/src/security/security_asn.rs b/src/security/security_asn.rs index 5cbd09a..8d112c9 100644 --- a/src/security/security_asn.rs +++ b/src/security/security_asn.rs @@ -3,7 +3,8 @@ non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod etsi_ts102941_base_types { extern crate alloc; @@ -96,7 +97,8 @@ pub mod etsi_ts102941_base_types { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod etsi_ts102941_messages_ca { extern crate alloc; @@ -204,7 +206,8 @@ pub mod etsi_ts102941_messages_ca { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod etsi_ts102941_messages_itss_optional_privacy { extern crate alloc; @@ -411,7 +414,8 @@ pub mod etsi_ts102941_messages_itss_optional_privacy { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod etsi_ts102941_trust_lists { extern crate alloc; @@ -636,7 +640,8 @@ pub mod etsi_ts102941_trust_lists { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod etsi_ts102941_types_authorization { extern crate alloc; @@ -810,7 +815,8 @@ pub mod etsi_ts102941_types_authorization { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod etsi_ts102941_types_authorization_validation { extern crate alloc; @@ -895,7 +901,8 @@ pub mod etsi_ts102941_types_authorization_validation { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod etsi_ts102941_types_ca_management { extern crate alloc; @@ -935,7 +942,8 @@ pub mod etsi_ts102941_types_ca_management { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod etsi_ts102941_types_enrolment { extern crate alloc; @@ -1031,7 +1039,8 @@ pub mod etsi_ts102941_types_enrolment { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod etsi_ts102941_types_link_certificate { extern crate alloc; @@ -1073,7 +1082,8 @@ pub mod etsi_ts102941_types_link_certificate { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod etsi_ts103097_extension_module { extern crate alloc; @@ -1147,7 +1157,8 @@ pub mod etsi_ts103097_extension_module { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod etsi_ts103097_module { extern crate alloc; @@ -1180,7 +1191,8 @@ pub mod etsi_ts103097_module { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2 { extern crate alloc; @@ -2891,7 +2903,8 @@ pub mod ieee1609_dot2 { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_base_types { extern crate alloc; @@ -4498,7 +4511,8 @@ pub mod ieee1609_dot2_base_types { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_crl { extern crate alloc; @@ -4529,7 +4543,8 @@ pub mod ieee1609_dot2_crl { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_crl_base_types { extern crate alloc; @@ -5163,7 +5178,8 @@ pub mod ieee1609_dot2_crl_base_types { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_dot1_aca_ee_interface { extern crate alloc; @@ -5257,7 +5273,8 @@ pub mod ieee1609_dot2_dot1_aca_ee_interface { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_dot1_aca_la_interface { extern crate alloc; @@ -5279,7 +5296,8 @@ pub mod ieee1609_dot2_dot1_aca_la_interface { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_dot1_aca_ma_interface { extern crate alloc; @@ -5301,7 +5319,8 @@ pub mod ieee1609_dot2_dot1_aca_ma_interface { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_dot1_aca_ra_interface { extern crate alloc; @@ -5614,7 +5633,8 @@ pub mod ieee1609_dot2_dot1_aca_ra_interface { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_dot1_acpc { extern crate alloc; @@ -5902,7 +5922,8 @@ pub mod ieee1609_dot2_dot1_acpc { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_dot1_cam_ra_interface { extern crate alloc; @@ -6004,7 +6025,8 @@ pub mod ieee1609_dot2_dot1_cam_ra_interface { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_dot1_cert_management { extern crate alloc; @@ -6493,7 +6515,8 @@ pub mod ieee1609_dot2_dot1_cert_management { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_dot1_eca_ee_interface { extern crate alloc; @@ -6671,7 +6694,8 @@ pub mod ieee1609_dot2_dot1_eca_ee_interface { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_dot1_ee_ma_interface { extern crate alloc; @@ -6693,7 +6717,8 @@ pub mod ieee1609_dot2_dot1_ee_ma_interface { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_dot1_ee_ra_interface { extern crate alloc; @@ -7058,7 +7083,8 @@ pub mod ieee1609_dot2_dot1_ee_ra_interface { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_dot1_la_ma_interface { extern crate alloc; @@ -7080,7 +7106,8 @@ pub mod ieee1609_dot2_dot1_la_ma_interface { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_dot1_la_ra_interface { extern crate alloc; @@ -7102,7 +7129,8 @@ pub mod ieee1609_dot2_dot1_la_ra_interface { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_dot1_ma_ra_interface { extern crate alloc; @@ -7124,7 +7152,8 @@ pub mod ieee1609_dot2_dot1_ma_ra_interface { non_snake_case, non_upper_case_globals, unused, - clippy::too_many_arguments + clippy::too_many_arguments, + clippy::large_enum_variant )] pub mod ieee1609_dot2_dot1_protocol { extern crate alloc; diff --git a/src/security/sign_service.rs b/src/security/sign_service.rs index 5c0934a..36fc489 100644 --- a/src/security/sign_service.rs +++ b/src/security/sign_service.rs @@ -51,7 +51,7 @@ impl CamSignerState { self.last_full_cert_time = now; self.requested_own_certificate = false; let asn_cert: AsnCertificate = cert.cert.inner.0.clone(); - SignerIdentifier::certificate(SequenceOfCertificate(vec![asn_cert].into())) + SignerIdentifier::certificate(SequenceOfCertificate(vec![asn_cert])) } else { let h = cert.as_hashedid8(); SignerIdentifier::digest(HashedId8(FixedOctetString::from(h))) @@ -94,12 +94,11 @@ impl SignService { /// Find the own certificate that covers the given ITS-AID. fn get_present_at(&self, its_aid: u64) -> Option<&OwnCertificate> { - for cert in self.cert_library.own_certificates.values() { - if cert.get_list_of_its_aid().contains(&its_aid) { - return Some(cert); - } - } - None + self.cert_library + .own_certificates + .values() + .find(|&cert| cert.get_list_of_its_aid().contains(&its_aid)) + .map(|v| v as _) } // ── Helper: build the Ieee1609Dot2Data envelope ────────────────────── @@ -175,7 +174,7 @@ impl SignService { .expect("DENM requires generation_location"); let asn_cert: AsnCertificate = at.cert.inner.0.clone(); - let signer = SignerIdentifier::certificate(SequenceOfCertificate(vec![asn_cert].into())); + let signer = SignerIdentifier::certificate(SequenceOfCertificate(vec![asn_cert])); let header_info = HeaderInfo::new( Psid(Integer::from(request.its_aid as i64)), @@ -222,7 +221,7 @@ impl SignService { .collect(); Some( crate::security::security_asn::ieee1609_dot2_base_types::SequenceOfHashedId3( - hashes.into(), + hashes, ), ) } else { @@ -326,19 +325,16 @@ mod tests { fn make_root_tbs() -> ToBeSignedCertificate { let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(30))); - let perms = SequenceOfPsidGroupPermissions( - vec![PsidGroupPermissions::new( - SubjectPermissions::all(()), - Integer::from(1), - Integer::from(0), - { - let mut bits = FixedBitString::<8>::default(); - bits.set(0, true); - EndEntityType(bits) - }, - )] - .into(), - ); + let perms = SequenceOfPsidGroupPermissions(vec![PsidGroupPermissions::new( + SubjectPermissions::all(()), + Integer::from(1), + Integer::from(0), + { + let mut bits = FixedBitString::<8>::default(); + bits.set(0, true); + EndEntityType(bits) + }, + )]); let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only(vec![0u8; 32].into())); ToBeSignedCertificate::new( @@ -355,16 +351,15 @@ mod tests { None, VerificationKeyIndicator::verificationKey(pk), None, - SequenceOfAppExtensions(vec![].into()), - SequenceOfCertIssueExtensions(vec![].into()), - SequenceOfCertRequestExtensions(vec![].into()), + SequenceOfAppExtensions(vec![]), + SequenceOfCertIssueExtensions(vec![]), + SequenceOfCertRequestExtensions(vec![]), ) } fn make_at_tbs(its_aid: i64) -> ToBeSignedCertificate { let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(1))); - let app_perms = - SequenceOfPsidSsp(vec![PsidSsp::new(Psid(Integer::from(its_aid)), None)].into()); + let app_perms = SequenceOfPsidSsp(vec![PsidSsp::new(Psid(Integer::from(its_aid)), None)]); let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only(vec![0u8; 32].into())); ToBeSignedCertificate::new( @@ -381,9 +376,9 @@ mod tests { None, VerificationKeyIndicator::verificationKey(pk), None, - SequenceOfAppExtensions(vec![].into()), - SequenceOfCertIssueExtensions(vec![].into()), - SequenceOfCertRequestExtensions(vec![].into()), + SequenceOfAppExtensions(vec![]), + SequenceOfCertIssueExtensions(vec![]), + SequenceOfCertRequestExtensions(vec![]), ) } diff --git a/src/security/verify_service.rs b/src/security/verify_service.rs index 95967ab..dfdf75c 100644 --- a/src/security/verify_service.rs +++ b/src/security/verify_service.rs @@ -66,19 +66,17 @@ pub fn verify_message( let psid = u64::try_from(&signed_data.tbs_data.header_info.psid.0).unwrap_or(0); // §7.1.2: DENMs must use 'certificate' signer - if psid == 37 { - if !matches!(signer, SignerIdentifier::certificate(_)) { - return ( - SNVerifyConfirm { - report: ReportVerify::UnsupportedSignerIdentifierType, - certificate_id: vec![], - its_aid: psid, - permissions: vec![], - plain_message: vec![], - }, - events, - ); - } + if psid == 37 && !matches!(signer, SignerIdentifier::certificate(_)) { + return ( + SNVerifyConfirm { + report: ReportVerify::UnsupportedSignerIdentifierType, + certificate_id: vec![], + its_aid: psid, + permissions: vec![], + plain_message: vec![], + }, + events, + ); } // Resolve the authorization ticket from the signer @@ -337,7 +335,7 @@ pub fn verify_message( if let Some(ref cert_asn) = signed_data.tbs_data.header_info.requested_certificate { use crate::security::security_asn::etsi_ts103097_module::EtsiTs103097Certificate; let c = Certificate::from_asn(EtsiTs103097Certificate(cert_asn.clone()), None); - events.push(VerifyEvent::ReceivedCaCertificate(c)); + events.push(VerifyEvent::ReceivedCaCertificate(Box::new(c))); } ( @@ -358,7 +356,7 @@ pub fn verify_message( pub enum VerifyEvent { UnknownAt([u8; 8]), InlineP2pcdRequest(Vec<[u8; 3]>), - ReceivedCaCertificate(Certificate), + ReceivedCaCertificate(Box), } #[cfg(test)] @@ -383,19 +381,16 @@ mod tests { fn make_root_tbs() -> ToBeSignedCertificate { let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(30))); - let perms = SequenceOfPsidGroupPermissions( - vec![PsidGroupPermissions::new( - SubjectPermissions::all(()), - Integer::from(1), - Integer::from(0), - { - let mut bits = FixedBitString::<8>::default(); - bits.set(0, true); - EndEntityType(bits) - }, - )] - .into(), - ); + let perms = SequenceOfPsidGroupPermissions(vec![PsidGroupPermissions::new( + SubjectPermissions::all(()), + Integer::from(1), + Integer::from(0), + { + let mut bits = FixedBitString::<8>::default(); + bits.set(0, true); + EndEntityType(bits) + }, + )]); let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only(vec![0u8; 32].into())); ToBeSignedCertificate::new( @@ -412,16 +407,15 @@ mod tests { None, VerificationKeyIndicator::verificationKey(pk), None, - SequenceOfAppExtensions(vec![].into()), - SequenceOfCertIssueExtensions(vec![].into()), - SequenceOfCertRequestExtensions(vec![].into()), + SequenceOfAppExtensions(vec![]), + SequenceOfCertIssueExtensions(vec![]), + SequenceOfCertRequestExtensions(vec![]), ) } fn make_at_tbs(its_aid: i64) -> ToBeSignedCertificate { let validity = ValidityPeriod::new(Time32(Uint32(0)), AsnDuration::years(Uint16(1))); - let app_perms = - SequenceOfPsidSsp(vec![PsidSsp::new(Psid(Integer::from(its_aid)), None)].into()); + let app_perms = SequenceOfPsidSsp(vec![PsidSsp::new(Psid(Integer::from(its_aid)), None)]); let pk = PublicVerificationKey::ecdsaNistP256(EccP256CurvePoint::x_only(vec![0u8; 32].into())); ToBeSignedCertificate::new( @@ -438,9 +432,9 @@ mod tests { None, VerificationKeyIndicator::verificationKey(pk), None, - SequenceOfAppExtensions(vec![].into()), - SequenceOfCertIssueExtensions(vec![].into()), - SequenceOfCertRequestExtensions(vec![].into()), + SequenceOfAppExtensions(vec![]), + SequenceOfCertIssueExtensions(vec![]), + SequenceOfCertRequestExtensions(vec![]), ) } From b3f6f25f8f56c7396b222dcf27042d363207911e Mon Sep 17 00:00:00 2001 From: Jordi Marias I Parella Date: Tue, 7 Apr 2026 11:53:24 +0200 Subject: [PATCH 6/6] Fixing pipeline 2 --- .github/workflows/ci.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37a1dd6..f93a5b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,14 +74,6 @@ jobs: - name: Install cross-compilation toolchain run: sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu - - name: Configure linker for aarch64 - run: | - mkdir -p .cargo - cat >> .cargo/config.toml <