Keep the colleague's microbu-esp32c5 tree in this repository
obu-firmware builds against vanetza-idf from microbu-esp32c5/external, but that tree was gitignored, so a clone of this repository could not build the firmware it ships. It is now committed here as ordinary files in its own folder, microbu-esp32c5/: the colleague's commit cf4b99f plus the V2X2MAP bridge's signature verification (--trust) used on the bench. Nothing is fetched from or pushed to the colleague's repository; this repository and its remotes carry everything. The folder's own .gitignore keeps build output, downloaded components and private key material out, as it did there; the committed file set is identical to that repository's tracked files. The ESP32-C5 is still flashed from obu-firmware/, which only takes vanetza-idf from microbu-esp32c5/, so the two stay separate folders. FLASHING.md says how to take a newer version of the colleague's tree (copy it over the folder, rebuild, test, commit).
This commit is contained in:
@@ -0,0 +1,356 @@
|
||||
# Running external test campaigns
|
||||
|
||||
Install and build the official ETSI TTCN-3 framework separately. This library
|
||||
does not install TITAN, supply the ETSI suites or replace their verdict logic.
|
||||
The adapter build helper links the user's compiled BTP suite with this library's
|
||||
test-port adapter; it preserves the official testcase objects.
|
||||
|
||||
## BTP
|
||||
|
||||
`ports/esp_idf/tools/run_etsi.py` accepts the external TITAN installation,
|
||||
compiled suite and configuration. On Linux, for example:
|
||||
|
||||
```sh
|
||||
python3 ports/esp_idf/tools/run_etsi.py \
|
||||
--titan /path/to/titan/Install \
|
||||
--binary /path/to/adapted/AtsBTP \
|
||||
--config ports/esp_idf/tests/etsi_btp.cfg \
|
||||
--expected-cases ports/esp_idf/tests/etsi_btp_cases.json \
|
||||
--sut /path/to/vidf_sut \
|
||||
--out /path/to/new-result-directory
|
||||
```
|
||||
|
||||
The host SUT and device SUT use `NF_SAP::BTP_DATA_request` and map reception
|
||||
through `BTP_DATA_indication`. Lower test records come from the real router.
|
||||
For hardware, select a Python executable as `--sut`, the absolute
|
||||
`ports/esp_idf/tools/serial_sut.py` path as `--sut-script`, and the DUT's
|
||||
serial port as `--port`. The example `esp_idf_test` firmware must already be
|
||||
built, flashed and running its HIL server. WSL requires Windows Python and a
|
||||
Windows script path to access a Windows COM port.
|
||||
|
||||
Retain `result.json`, `test.cfg`, `expected-cases.json`, the MTC log and console
|
||||
log. The result includes executable/configuration hashes and individual
|
||||
verdicts. A complete campaign has exactly one verdict per expected testcase.
|
||||
|
||||
The provided five-case campaign establishes only its selected BTP behavior.
|
||||
It does not validate GeoNetworking routing, facilities service behavior,
|
||||
security, DCC or the physical radio. Those need their own applicable suites,
|
||||
PICS/PIXIT and test-port implementations. Never acknowledge an unsupported
|
||||
upper-tester command merely to let a suite advance.
|
||||
|
||||
## GeoNetworking
|
||||
|
||||
`ports/esp_idf/tools/build_etsi_geonetworking_adapter.py` builds
|
||||
`ports/esp_idf/tests/etsi_geonetworking_adapter.cpp` against a separately
|
||||
compiled `AtsGeoNetworking`, the same way as the BTP adapter. It implements
|
||||
`GeoNetworkingPort`, `UpperTesterPort` and `AdapterControlPort`; only SHB
|
||||
triggering/observation is wired up (GAP-GN-001), so
|
||||
[etsi_geonetworking.cfg](../../ports/esp_idf/tests/etsi_geonetworking.cfg)
|
||||
disables every other PICS the ATS defaults to "supported".
|
||||
|
||||
```sh
|
||||
python3 ports/esp_idf/tools/build_etsi_geonetworking_adapter.py \
|
||||
--etsi /path/to/TS.ITS --titan /path/to/titan/Install \
|
||||
--out /path/to/new-build-directory
|
||||
python3 ports/esp_idf/tools/run_etsi.py \
|
||||
--titan /path/to/titan/Install \
|
||||
--binary /path/to/new-build-directory/AtsGeoNetworking \
|
||||
--config ports/esp_idf/tests/etsi_geonetworking.cfg \
|
||||
--expected-cases ports/esp_idf/tests/etsi_geonetworking_cases.json \
|
||||
--sut /path/to/vidf_sut \
|
||||
--out /path/to/new-result-directory
|
||||
```
|
||||
|
||||
**The host SUT must be a native Linux build**, unlike BTP's Windows
|
||||
`vidf_sut.exe`: piping this adapter's stdin/stdout to a Windows executable
|
||||
launched through WSL interop silently drops the piped input (the SUT process
|
||||
reads EOF immediately and exits), which is indistinguishable from a crash
|
||||
until you check for it directly. Configure and build `ports/esp_idf` with
|
||||
CMake/Ninja inside WSL against the distribution's own Boost dev package
|
||||
(`apt install cmake ninja-build libboost-dev libboost-date-time-dev`); do
|
||||
this from a native Linux path (e.g. `/root/...`), not `/mnt/c/...` — building
|
||||
from the Windows-mounted path works but is dramatically slower (large,
|
||||
many-small-file compiles can stall for many minutes) because every header
|
||||
read crosses the 9P filesystem boundary. Apt's Boost is missing
|
||||
`boost/core/invoke_swap.hpp`, which `boost/geometry/formulas/karney_inverse.hpp`
|
||||
needs; naively copying it from `external/esp-boost` redefines symbols already
|
||||
in apt Boost's `boost/core/swap.hpp` and fails to compile for a different
|
||||
reason. `ports/esp_idf/compat/include/boost/core/invoke_swap.hpp`
|
||||
is a from-scratch compatibility shim (not a copy of upstream Boost) that
|
||||
builds `invoke_swap` on top of the already-present `boost::swap()` instead of
|
||||
redefining anything, so it works when combined with either Boost source. The
|
||||
`compat/include` directory is appended after every real Boost include root,
|
||||
so the shim is only reached when the selected Boost lacks the header; with
|
||||
`VIDF_BOOST_ROOT` pointing at a complete Boost it is never used.
|
||||
|
||||
`build_etsi_geonetworking_adapter.py` also applies a disposable build overlay
|
||||
for a null-pointer bug in the external framework's own
|
||||
`geonetworking_codec.cc` (decoding any `GeoNetworkingPdu` through the
|
||||
official `fx__dec__GeoNetworkingPdu` wrapper segfaults, since it always
|
||||
decodes with `params=nullptr` and one payload-decode branch dereferences
|
||||
that unconditionally). The pinned source on disk is read but never written;
|
||||
see docs/idf/validation.md for the full diagnosis and
|
||||
[the recorded before/after hashes](evidence/geonetworking-host-01/adapter-build.json).
|
||||
|
||||
Device execution has not been attempted for this suite.
|
||||
|
||||
## Security
|
||||
|
||||
`ports/esp_idf/tools/build_etsi_security_adapter.py` links
|
||||
`ports/esp_idf/tests/etsi_security_adapter.cpp` with a separately compiled
|
||||
`AtsSecurity` (official testcase objects unchanged; the framework's own
|
||||
GeoNetworking/CAM/DENM port objects are replaced, the same disposable
|
||||
`geonetworking_codec.cc` overlay as for GeoNetworking is applied to this build
|
||||
only). The adapter covers the sending side: GN-MGMT beacons through the GN core,
|
||||
CAM and DENM carriers from the test application behind the upper tester ports,
|
||||
and the receiving side through the third configuration below.
|
||||
|
||||
Generate an isolated test trust domain in the framework's certificate pool
|
||||
layout, then run the two configurations (the GN-MGMT cases must run without the
|
||||
CAM carrier, which restarts the beacon timer, TS 103 836-4-1 clause 10.3.5):
|
||||
|
||||
```sh
|
||||
cmake -S ports/esp_idf -B build-host -DVIDF_TESTS=ON -DVIDF_SECURITY=ON
|
||||
cmake --build build-host
|
||||
./build-host/vidf_test_pool /path/to/new-pool-directory # <name>.oer, <name>.vkey, index.lst
|
||||
python3 ports/esp_idf/tools/build_etsi_security_adapter.py \
|
||||
--etsi /path/to/TS.ITS --titan /path/to/titan/Install \
|
||||
--out /path/to/new-build-directory
|
||||
python3 ports/esp_idf/tools/run_etsi.py \
|
||||
--titan /path/to/titan/Install \
|
||||
--binary /path/to/new-build-directory/AtsSecurity \
|
||||
--config ports/esp_idf/tests/etsi_security_gn.cfg \
|
||||
--expected-cases ports/esp_idf/tests/etsi_security_gn_cases.json \
|
||||
--sut ./build-host/vidf_sut --sut-args "--security-pool ./certificates" \
|
||||
--pool /path/to/new-pool-directory \
|
||||
--out /path/to/new-result-directory-gn
|
||||
python3 ports/esp_idf/tools/run_etsi.py \
|
||||
--titan /path/to/titan/Install \
|
||||
--binary /path/to/new-build-directory/AtsSecurity \
|
||||
--config ports/esp_idf/tests/etsi_security_facilities.cfg \
|
||||
--expected-cases ports/esp_idf/tests/etsi_security_facilities_cases.json \
|
||||
--sut ./build-host/vidf_sut --sut-args "--security-pool ./certificates" \
|
||||
--pool /path/to/new-pool-directory \
|
||||
--out /path/to/new-result-directory-facilities
|
||||
```
|
||||
|
||||
`--pool` copies the pool to `<out>/certificates` (the SUT reads it from there
|
||||
through `--sut-args`, the testcases through `PX_CERTIFICATE_POOL_PATH`/
|
||||
`PX_IUT_SEC_CONFIG_NAME`) and records each file's hash. The SUT must be a
|
||||
native Linux build, as for GeoNetworking. Pool tickets start one minute before
|
||||
generation and last 24 hours; regenerate the pool for a campaign run later
|
||||
than that.
|
||||
|
||||
The `[TESTPORT_PARAMETERS]` of both configurations set
|
||||
`enable_security_checks=1` on the GeoNetworking port: the framework's own
|
||||
security services verify every transmission of the SUT against the pool and a
|
||||
failed verification discards the packet. `system.utPort.params` selects the
|
||||
CAM carrier period of the test application (`cam_carrier_ms`), the only
|
||||
stimulus difference between the two configurations. `AcEnableSecurity` names
|
||||
the certificate the *test system* would sign with; the adapter accepts it when
|
||||
it exists in the pool and applies the enforcement flag, but signs no
|
||||
test-system packets.
|
||||
|
||||
A third configuration, `etsi_security_receive.cfg` with
|
||||
`etsi_security_receive_cases.json` (26 receiving-side cases), needs a SUT built
|
||||
with `VIDF_SECURITY_VERIFY` (the default). The test system signs its CAMs and
|
||||
DENMs in TTCN-3 with `CERT_TS_A_AT`, or with `CERT_TS_B_AT` (a ticket restricted
|
||||
to a 5 km circle around the SUT position) where the testcase uses
|
||||
`PX_AT_CERTIFICATE`; the adapter injects them and reports what the SUT passes up
|
||||
after SN-DECAP as `UtGnEventInd`. The SUT trusts both AAs of the pool
|
||||
(`--aa` is repeatable; the defaults are `CERT_IUT_A_AA` and `CERT_TS_A_AA`).
|
||||
|
||||
Expected outcome with this library (retained in `docs/idf/evidence/security-host-09`,
|
||||
`-10` and `-08`; earlier -06/-07/-05 identical): sending side 14 of 15
|
||||
(`TC_SEC_ITSS_SND_GENMSG_05_BV` fails on a unit defect of the testcase), receiving
|
||||
side 24 of 26 (`TC_SEC_ITSS_RCV_DENM_01_BV` and `DENM_02_BV_XX` error on a
|
||||
declaration-order defect of the testcases); docs/idf/validation.md has both
|
||||
analyses. Device execution of this suite has not been attempted; the device runs
|
||||
the same security entity in the component tests (`security-device-06`).
|
||||
|
||||
### Issuing credentials outside the test pool: `vidf_issue`
|
||||
|
||||
`vidf_test_pool` writes the ETSI-named pool with fresh keys. `vidf_issue` (built
|
||||
with the tests, host OpenSSL) issues the same certificate profiles for keys and
|
||||
names of your choosing, so a chain under a real root can be produced and the
|
||||
station's signed frames checked by tools that know nothing of this library:
|
||||
|
||||
```sh
|
||||
vidf_issue root --key root.pem --name "Example Root CA" --id ROOT --out chain # self-signed (clause 7.2.3)
|
||||
vidf_issue authority --issuer chain/ROOT.oer --issuer-key root.pem --name "Example AA" --id AA --out chain
|
||||
vidf_issue ticket --issuer chain/AA.oer --issuer-key chain/AA.vkey --root chain/ROOT.oer --id AT \
|
||||
--permission 36:01FFFC --permission 141 --permission 638:01 --out chain
|
||||
vidf_issue show chain/AT.oer # digest, issuer, validity, permissions, region
|
||||
vidf_issue verify chain/AT.oer chain/AA.oer chain/ROOT.oer # signatures, validity/region nesting, permission consistency
|
||||
```
|
||||
|
||||
`--key`/`--issuer-key` accept a PEM private key (an encrypted PKCS#8 file is
|
||||
opened with OpenSSL's pass-phrase prompt; the pass phrase is never an argument)
|
||||
or a raw 32-octet `.vkey` as the pool writes it; `--start`/`--years`/`--hours`
|
||||
set the validity (the default start is an hour ago but never before the
|
||||
issuer's own start), `--region LAT,LON,RADIUS_M` (1/10 microdegrees, metres) a
|
||||
circular ticket region. `root` writes the EU CCMS CPOC Protocol Release 3.0
|
||||
profile (certIssuePermissions with minChainLength 2 and eeType app+enrol for
|
||||
CA/DEN/VRU/GN-MGMT and the end-entity part of psid 623, a second group for the
|
||||
authorities' psid 623 SSPs; CRL/CTL appPermissions), or with `--like
|
||||
OTHER.oer` the permissions and region of an existing root (a rehearsal twin of
|
||||
a real root with a throwaway key). `authority` derives its issuing
|
||||
permissions from the issuer (every group that reaches two certificates down,
|
||||
IEEE Std 1609.2 6.4.28, with chain length 1) and inherits the issuer's region
|
||||
(6.4.17); `ticket` inherits the issuer's region unless `--region` is given and
|
||||
carries the SSPs of TS 102 941 V2.2.1 Table B.6 for psid 623 where relevant.
|
||||
Every command checks the result the way the receive-side verifier will
|
||||
(signatures, validity nesting, region nesting, permission consistency, with
|
||||
`--root` over the full chain) and writes nothing that would fail. The output
|
||||
directory gets `<id>.oer`, `<id>.vkey` (except for `root`, whose key stays
|
||||
where it was) and an `index.lst`, i.e. a pool `vidf_sut --security-pool`
|
||||
loads with `--root ROOT --aa AA --at AT`.
|
||||
|
||||
Rules for a production root: run the tool where the root key lives, never copy
|
||||
the encrypted PEM or its pass phrase anywhere, and keep the generated `.vkey`
|
||||
files with the same care as the PEM. Nothing in the tests uses a project key;
|
||||
the component tests and the pool generate throwaway keys per run.
|
||||
|
||||
### A distribution centre on localhost (CTL and CRL)
|
||||
|
||||
Receivers that take their AA certificates from the root's CTL rather than from
|
||||
P2P distribution need the root's distribution centre (TS 102 941 V2.2.1 clause
|
||||
6.3, Annex D). Until the real DC is up, the same interface runs on localhost:
|
||||
|
||||
```sh
|
||||
vidf_issue ctl --issuer chain/ROOT.oer --issuer-key root.pem --aa chain/AA.oer=http://aa.example/ \
|
||||
--dc http://127.0.0.1:8080/ --sequence 1 --out lists/ctl-<HASHEDID8>.oer
|
||||
vidf_issue crl --issuer chain/ROOT.oer --issuer-key root.pem [--revoke <HASHEDID8>]... --out lists/crl-<HASHEDID8>.oer
|
||||
python3 ports/esp_idf/tools/local_dc.py --dir lists --port 8080 # GET /getctl/<HASHEDID8>, /getcrl/<HASHEDID8>
|
||||
python3 ports/esp_idf/tools/fetch_trust_lists.py --dc http://127.0.0.1:8080/ --root chain/ROOT.oer --out fetched
|
||||
vidf_issue inspect fetched/ctl-<HASHEDID8>.oer --root chain/ROOT.oer # clause 6.3.6 checks, then the entries
|
||||
```
|
||||
|
||||
The station applies what `fetch_trust_lists.py` fetched through
|
||||
`pki::parse_rca_ctl`/`parse_crl` and `pki::apply` (the fetch itself is the
|
||||
application's transport). c-its' `c-its-download-int-certs` is the
|
||||
independent consumer: with `ctl/root-<HASHEDID8>.oer` and a
|
||||
`root-<HASHEDID8>.json` naming the DC it fetches, validates and extracts the
|
||||
AA certificates (retained run: `docs/idf/evidence/trust-lists-01`).
|
||||
|
||||
### Enrolment and authorization on localhost (EA/AA)
|
||||
|
||||
TS 102 941 V2.2.1 clause 6.2.3: an EC from the EA, then an AT from the AA
|
||||
(which validates the EC's entitlement with the EA). `pki.hpp` builds and
|
||||
parses every message; the HTTP transport between two separate processes is
|
||||
the test tools' job, the same division of labour as the DC above:
|
||||
|
||||
```sh
|
||||
vidf_issue root --key root.pem --name "lab root" --id ROOT --out chain
|
||||
vidf_issue authority --issuer chain/ROOT.oer --issuer-key root.pem --name "lab EA" --id EA --out chain
|
||||
vidf_issue authority --issuer chain/ROOT.oer --issuer-key root.pem --name "lab AA" --id AA --out chain
|
||||
# authority also writes <id>.ekey: the ECIES private key needed to decrypt real requests
|
||||
|
||||
python3 ports/esp_idf/tools/local_pki.py --issue-tool build/vidf_issue \
|
||||
--ea chain/EA.oer --ea-key chain/EA.vkey --ea-enc-key chain/EA.ekey --canonical-key canonical.pem \
|
||||
--aa chain/AA.oer --aa-key chain/AA.vkey --aa-enc-key chain/AA.ekey \
|
||||
--dir issued --port 8090 # POST /ea/enrolment, POST /aa/authorization
|
||||
|
||||
python3 ports/esp_idf/tools/pki_client.py --issue-tool build/vidf_issue --pki http://127.0.0.1:8090/ enrol \
|
||||
--ea chain/EA.oer --canonical-key canonical.pem --its-id my-station --out EC.oer --out-key EC.vkey
|
||||
python3 ports/esp_idf/tools/pki_client.py --issue-tool build/vidf_issue --pki http://127.0.0.1:8090/ authorize \
|
||||
--ea chain/EA.oer --aa chain/AA.oer --ec EC.oer --ec-key EC.vkey --out AT.oer --out-key AT.vkey
|
||||
|
||||
vidf_issue verify EC.oer chain/EA.oer chain/ROOT.oer
|
||||
vidf_issue verify AT.oer chain/AA.oer chain/ROOT.oer
|
||||
```
|
||||
|
||||
`--canonical-key` on both the client and `local_pki.py`/`ea-respond` side is
|
||||
the same private key file: a lab stand-in for the manufacturer's out-of-band
|
||||
canonical-key registry a real EA consults, not something this tool invents.
|
||||
`ea-respond`/`aa-respond` are a lab authority (decrypt, verify, issue, sign,
|
||||
encrypt with the same building blocks the ITS-S side already uses), not a
|
||||
production PKI: no replay protection, no butterfly keys, no revocation.
|
||||
Retained run: `docs/idf/evidence/enrolment-authorization-01`.
|
||||
|
||||
### Independent verification of signed frames
|
||||
|
||||
`ports/esp_idf/tools/capture_pcap.py` drives `vidf_sut` with such a pool,
|
||||
triggers the CAM and DENM carriers and writes the transmissions as an IEEE
|
||||
802.11 pcap (linktype 105, wildcard BSSID, LLC/SNAP 0x8947). Any verifier that
|
||||
reads pcaps can then judge them; the retained run uses c-its
|
||||
(https://github.com/TheEnbyperor/c-its, `c-its-pcap`, roots as
|
||||
`./ctl/root-*.oer`, AAs as `./ctl/aa-*.oer`):
|
||||
|
||||
```sh
|
||||
python3 ports/esp_idf/tools/capture_pcap.py --sut ./build-host/vidf_sut --pool chain \
|
||||
--root ROOT --aa AA --at AT --out sut.pcap
|
||||
mkdir ctl && cp chain/ROOT.oer ctl/root-ROOT.oer && cp chain/AA.oer ctl/aa-AA.oer
|
||||
RUST_LOG=info c-its-pcap sut.pcap # one JSON line per frame: signature, chain, security_authorized
|
||||
```
|
||||
|
||||
The same from an ESP32-C5 running the test application: pack the chain into a
|
||||
credential bundle (`vanetza_idf/credentials.hpp` format, keys in the clear:
|
||||
keep the file like the keys) and let the capture provision the board over the
|
||||
USB serial diagnostic channel (command 9) before it resets and signs:
|
||||
|
||||
```sh
|
||||
python3 ports/esp_idf/tools/credential_bundle.py build --pool chain --root ROOT --aa AA --at AT --out chain.vcr
|
||||
python3 ports/esp_idf/tools/capture_pcap.py --port COM11 --bundle chain.vcr --out device.pcap
|
||||
```
|
||||
|
||||
`vidf_sut --security-bundle chain.vcr` (or `capture_pcap.py --sut ... --bundle`)
|
||||
takes the same bundle on the host; `serial_sut.py --bundle chain.vcr` provisions
|
||||
a device before relaying an ETSI campaign's commands. The device keeps the
|
||||
bundle in RAM for the session; persisting it is the application's
|
||||
`CredentialStore` (the component tests exercise the NVS store).
|
||||
|
||||
Retained in `docs/idf/evidence/independent-verifier-01` (analysis there): with
|
||||
a ticket carrying CAM, DENM and VRU permissions c-its reports the message
|
||||
signature, the chain to the root and the CAM/DENM authorisation as valid; with
|
||||
the standard ticket that also lists psid 141 without an SSP, c-its verifies
|
||||
every signature but does not validate the permissions because it treats an
|
||||
omitted SSP as unsupported (its limitation, not a standard's requirement).
|
||||
`independent-verifier-02` repeats the run under a twin of a real EU CCMS L0
|
||||
root (`root --like`), i.e. with the CPOC permission profile and the EU
|
||||
identified region.
|
||||
|
||||
## Independent radio reception
|
||||
|
||||
After flashing the test application on two C5 boards, use:
|
||||
|
||||
```sh
|
||||
python ports/esp_idf/tools/radio_pair.py \
|
||||
--dut COM20 --receiver COM11 --channel 180 \
|
||||
--allow-transmission --out /path/to/new-radio-result-directory
|
||||
```
|
||||
|
||||
This integration test explicitly enables laboratory transmission. It checks
|
||||
that receive-only mode rejects transmission, injects a unique payload at BTP,
|
||||
sends the resulting GN packet through the DUT radio and checks an independent
|
||||
receiver's raw frame byte-for-byte against exactly what the DUT submitted.
|
||||
It retains a PCAP and a JSON result. Both radios are stopped during cleanup;
|
||||
cleanup failures are reported.
|
||||
|
||||
The captured frames are not independently re-validated for FCS: the
|
||||
receiver's promiscuous filter never sets `WIFI_PROMIS_FILTER_MASK_FCSFAIL`,
|
||||
so the ESP32-C5 WiFi hardware has already discarded any frame that failed
|
||||
FCS before this callback ever runs. `rx_ctrl.sig_len` on this chip is
|
||||
documented as "the length of the reception MPDU" (`esp_wifi_he_types.h`),
|
||||
not "MPDU + FCS" as older-chip ESP-IDF documentation states; measured
|
||||
directly, it is 4 bytes longer than the actual frame, and those 4 trailing
|
||||
bytes are a fixed value independent of frame content (confirmed across
|
||||
captures with different random payloads) — not a real, software-recoverable
|
||||
FCS. They are still written into the pcap for inspection
|
||||
(`result.json`'s `trailing_bytes`), just not checked against anything.
|
||||
|
||||
The diagnostic radio commands are application test hooks, not ETSI primitives:
|
||||
3 configures channel and transmit enable; 4 submits an AL_DATA request; 5 drains
|
||||
independent receive observations; 6 stops the radio. These hooks can support an
|
||||
external access test adapter. They do not themselves produce ETSI verdicts.
|
||||
|
||||
A matched frame establishes that particular reception. It cannot establish
|
||||
receiver sensitivity, EIRP, congestion control behavior or complete ITS-G5
|
||||
compliance. Spectral mask and carrier frequency accuracy are covered
|
||||
separately by the SYS-RF-001 characterization campaign
|
||||
(`experiments/evidence/c5-radio-characterization` in the thesis workspace,
|
||||
HackRF One/PortaPack against ETSI EN 302 571 V2.1.1 clause 4.2.1 and Table 6):
|
||||
carrier frequency offset is generally within the +-20 ppm limit, but the
|
||||
transmitted spectral mask fails Table 6 in every tested bench configuration
|
||||
and MCS rate (0/22 captures per run across three configurations) — this is
|
||||
disclosed as a measured non-conformance, not a passing result.
|
||||
Reference in New Issue
Block a user