Files
UA-.NETStandard/fuzzing/Common
romanett 5d26f6f439 Fix JSON encoding fidelity issues found by fuzz crash replay and align with Part 6 (#4540)
# Description

The external crash corpus replayed by `Opc.Ua.Encoders.Fuzz.Tests` still
produced several hundred tolerated *encoding fidelity findings* per run.
They came from the 10 BinaryJson conversion targets (binary decode →
JSON encode → JSON decode). None of them was a crash or an unexpected
exception. Triage reduced them to three JSON encoder defects plus one
oracle rule that was stricter than the specification. While checking the
fixes against OPC 10000-6, two adjacent deviations turned up and are
fixed as well.

### Encoder fixes (`Opc.Ua.Types`)

| Defect | Before | After | Spec |
|---|---|---|---|
| LocalizedText with no Text and an empty/null Locale | `{"Text":null}`
(Verbose) / `{}` (Compact). Reads back as null and re-encodes
differently | JSON `null` (Verbose) / omitted (Compact) | 5.4.2.15 Table
39, 5.1.2 Table 1, 5.4.6 Table 45 |
| LocalizedText with empty Text | `"Text":""` written | Text not
encoded; `LocalizedText("")` is the null LocalizedText | 5.4.2.15 Table
39 ("not encoded if it is null or empty") |
| Variant/DataValue holding a **null array**, Compact | `Value` omitted
→ decoded as a *scalar* default (array rank lost) | `"Value":[]` |
5.4.2.17 Table 41, 5.1.11 |
| Variant/DataValue `Value` omission rule | Omitted for any default
value (including null *arrays*) when `IgnoreDefaultValues`;
`IgnoreNullValues` ignored | Same rules as every other field: a NULL of
a nullable built-in type is omitted when `IgnoreNullValues` or
`IgnoreDefaultValues` is set (like `WriteNull`); a default of a
non-nullable type only when `IgnoreDefaultValues` is set (like
`WriteValueProperty`). An array `Value` is never omitted | 5.4.2.17
Table 41, 5.1.2 Table 1, 5.4.6 Table 45 |
| `ExpandedNodeId.Format(context)` with a ServerIndex/NamespaceUri and a
null identifier | Returned `null`, so JSON wrote `null` and the server
index was lost | `svr=N;i=0` / `nsu=...;i=0`, consistent with the
`IFormatProvider` overload | 5.1.12 Table 6, 5.4.2.11, 5.1.2 Table 1
(NULL is exactly `i=0`) |

### Fuzz oracle and harness (`fuzzing/`)
- The round-trip oracle now treats null and empty arrays, and null/empty
LocalizedText Text/Locale, as equivalent in every mode (5.1.11:
"semantically the same for all DataEncodings… applications shall treat
null and empty arrays as equal"). Previously this only held when
`IgnoreNullValues` was set.
- `FuzzCrashAssets` no longer prints a stack trace or an inline base64
reproducer for *tolerated* fidelity findings. Only the one-line summary
entry is reported, and it now names the actual cause instead of
`TargetInvocationException`. Before, tolerated findings used up the
60-per-target reproducer budget, so a genuine failure later in the
enumeration printed no reproducer. The externally supplied corpus inputs
were also written into the pipeline log on passing runs.
- Three hand-crafted curated reproducers (9–23 bytes, one per encoder
defect) were added under `Assets/Repo/`, which is always enforced
strictly. Each one fails on the previous master and passes with this
change.

### Reviewer notes: observable JSON output changes
- `LocalizedText("")` / `("en","")` now round-trip with `Text == null`
instead of `""` (equivalent per 5.1.11).
- Verbose and Compact scalar Variant output is unchanged.
`IgnoreNullValues` alone now also omits a NULL `Value`, as it does for
every other null field.
- A null *array* in a Variant is written as `"Value":[]` in every mode
instead of being dropped in Compact, and a null *matrix* is written as
`"Value":[]` instead of failing with `BadEncodingError`.
- Variants holding a Variant or DiagnosticInfo still reach the writer
and are rejected as before (`WriteBadVariantThrows`).

### Testing (net10.0, locally)
- Opc.Ua.Types.Tests 10,542 passed, including new JsonEncoder tests for
each rule above
- Opc.Ua.Core.Encoders.Tests 2,929 passed (one expectation updated from
`"Text":""`)
- Opc.Ua.PubSub.Tests 1,369 passed; Opc.Ua.Core.Tests JSON subset 83
passed; Opc.Ua.Aot.Tests 178 passed
- Opc.Ua.Encoders.Fuzz.Tests 8,112 passed; crash-asset tests in the
Certificates/Network/PubSub fuzz projects and Opc.Ua.Fuzzing.Tests
passed
- net48: `Opc.Ua.Encoders.Fuzz.Tests` builds with 0 warnings; net48
tests were not run locally

## Related Issues

- No tracking issue; found by triaging the fuzz crash-asset replay
output of the master pipeline.

## Checklist

- [ ] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [ ] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 07:49:52 +02:00
..