Files
satellite-provider/_docs/02_document/modules/common_uuidv5.md
T
Oleksandr Bezdieniezhnykh 34ee1e0b83 [AZ-808] [AZ-811] Strict validation on region POST + lat/lon GET
AZ-808: FluentValidation for POST /api/satellite/request
- RegionRequestValidator: id non-empty, lat/lon/sizeMeters/zoomLevel ranges
- RequestRegionRequest: [JsonRequired] on every property, no implicit defaults
- Wired via .WithValidation<RequestRegionRequest>() in MapPost chain
- Unit + integration tests + curl probe script
- New contract: contracts/api/region-request.md v1.0.0

AZ-811: FluentValidation + envelope filter for GET /api/satellite/tiles/latlon
- GetTileByLatLonQuery: nullable record (double?/int?) so the minimal-API
  binder never short-circuits with BadHttpRequestException before filters
- GetTileByLatLonQueryValidator: Cascade(Stop) + NotNull + InclusiveBetween
  per param; missing surfaces as `\`<name>\` is required.`
- RejectUnknownQueryParamsEndpointFilter: reusable IEndpointFilter that
  rejects any query key outside the allowed set with errors[<key>] map;
  catches legacy `?Latitude=` typos and hostile probes (`?debug=1&admin=1`)
- Handler: [AsParameters] GetTileByLatLonQuery + .Value deref post-validator
- Unit (validator + filter) + integration tests + curl probe script
- New contract: contracts/api/tile-latlon.md v1.0.0

Shared hygiene
- Promote AssertErrorsContainsMention from per-test-file private helpers to
  ProblemDetailsAssertions (closes batch-1 Low-severity DRY warning)
- Sync Swagger param descriptions, README, blackbox/security/perf scripts,
  uuidv5 doc with the new lat/lon/zoom query-param names

Docs
- system-flows.md F1/F2 reference the new contracts + validation layers
- modules/api_program.md adds Api/Validators + Api/DTOs sections
- _autodev_state.md: batch 2 of 4 complete; next batch = AZ-809

All smoke tests green (mode=smoke, exit 0). AZ-808 + AZ-811 transitioned
to In Testing on Jira.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 16:29:41 +03:00

77 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Module: Common/Utils/Uuidv5
## Purpose
Deterministic UUIDv5 generator (RFC 9562 §5.5, SHA-1 namespace+name hashing) for tile identity. Pure C# implementation, ≤80 LoC, no third-party dependency. Owns the cross-repo `TileNamespace` constant that pins UUIDv5 outputs to be byte-identical between this workspace (C#) and the sibling `gps-denied-onboard` workspace (Python `uuid.uuid5`).
**csproj**: `SatelliteProvider.Common/Utils/Uuidv5.cs`
**Introduced**: AZ-503 (Cycle 5)
## Public Interface
All members are static on `Uuidv5`:
- `TileNamespace` (Guid, public const) — `5b8d0c2e-7f1a-4d3b-9c5e-1f3a8e7d2b6c`. The shared namespace UUID used for every tile identity computation in this service and its onboard counterpart. **MUST NOT be changed** without coordinating a migration with `gps-denied-onboard/components/c6_tile_cache/_uuid.py`.
- `Create(Guid namespaceId, string name) → Guid` — produces a deterministic UUIDv5 by hashing `namespaceId.ToByteArrayBigEndian() || Encoding.UTF8.GetBytes(name)` with SHA-1, then assembling the 16 bytes per RFC 9562:
- bytes 03 are read as a big-endian uint32 (`time_low`)
- bytes 45 are read as a big-endian uint16 (`time_mid`)
- bytes 67 have their top 4 bits set to `0101` (version 5)
- byte 8 has its top 2 bits set to `10` (variant RFC 4122 / 9562)
- bytes 815 form the variant + clock_seq + node fields
- `Create(Guid namespaceId, ReadOnlySpan<byte> name) → Guid` — same as above but accepts a pre-encoded byte span; useful when the caller already has UTF-8 bytes or wants to avoid an intermediate string allocation.
## Internal Logic
- The .NET 10 `Guid.ToByteArray()` method emits the first three fields in little-endian (Microsoft historical behavior); RFC 9562 requires big-endian. The module uses a local `ToBigEndianByteArray(Guid)` helper that byte-swaps the first 4 bytes (time_low), the next 2 bytes (time_mid), and the next 2 bytes (time_hi_and_version) to produce the canonical big-endian layout before hashing. The same byte-swap is reversed when assembling the output `Guid` from the hash digest, so the in-memory `Guid` value still round-trips through `ToString()` to the expected hex form.
- SHA-1 is invoked via `SHA1.HashData(buffer)` (.NET 7+) which produces the 20-byte digest in one shot; only the first 16 bytes feed the resulting UUID (per RFC).
- The function is allocation-light for typical tile-key sizes: the hash input buffer is stack-allocated via `Span<byte>` when the namespace+name byte-length fits in 1024 bytes (always true for `{z}/{x}/{y}` and `{z}/{x}/{y}/{source}/{flight_id}` strings); larger payloads fall back to a pooled `byte[]`.
- The function is thread-safe (no shared mutable state).
## Reference Vectors
`SatelliteProvider.Tests/Uuidv5Tests.cs` pins 10 reference vectors generated by Python (`uuid.uuid5(TILE_NAMESPACE, name)`). Each vector pairs an input `name` with the expected `Guid` string. The C# implementation must produce byte-identical output. Two representative pairs:
| Name | Expected UUIDv5 |
|------|-----------------|
| `"18/12345/23456"` | `38b26f49-a966-5121-aaf4-9cc476f57869` |
| `"18/12345/23456/google_maps/00000000-0000-0000-0000-000000000000"` | `e228d1aa-25d4-556e-a72d-e0484756e165` |
The second value is observable end-to-end: a fresh `GET /api/satellite/tiles/latlon?lat=47.461747&lon=37.647063&zoom=18` returns `tileId = e228d1aa-25d4-556e-a72d-e0484756e165` because `(47.461747, 37.647063)` maps to slippy `(z=18, x=158485, y=91707)` — and the integration test asserts that exact value. (AZ-811 cycle 8 renamed the query params `Latitude/Longitude/ZoomLevel``lat/lon/zoom` for OSM consistency.)
## Dependencies
- `System.Security.Cryptography.SHA1`
- `System.Buffers.Binary.BinaryPrimitives` (for big-endian byte-swaps)
- `System.Buffers.ArrayPool<byte>` (for the >1024-byte fallback path)
No third-party packages. No NuGet additions for AZ-503.
## Consumers
- `SatelliteProvider.Services.TileDownloader.TileService.BuildTileEntity` — computes `Id` and `LocationHash` for every newly downloaded Google Maps tile.
- `SatelliteProvider.Services.TileDownloader.UavTileUploadHandler.PersistAsync` — computes `Id` and `LocationHash` for every UAV upload.
- `SatelliteProvider.IntegrationTests.UavUploadTests` — seeds `location_hash` values via raw SQL when bypassing the application code path.
- `SatelliteProvider.IntegrationTests.MigrationTests` — generates expected UUIDv5 outputs to validate migration 014's `pg_temp.uuidv5` PL/pgSQL backfill function.
## Data Models
Operates only on `Guid` and `string` / `Span<byte>`. No persistence model.
## Configuration
None. The namespace constant is pinned in source.
## External Integrations
None (pure computation).
## Security
The function is deterministic by design — it is NOT a cryptographic hash for security purposes. Two callers with the same `(namespace, name)` will always produce the same output. Treat the result as a content/location handle, not a secret. SHA-1 is used for RFC 9562 compatibility, not for collision resistance against an adversary.
## Tests
`SatelliteProvider.Tests/Uuidv5Tests.cs`:
- `Create_MatchesPythonReferenceVectors_AC1` — 10 reference vectors (AZ-503 AC-1).
- `Create_IsDeterministic` — re-running with the same inputs returns the same `Guid`.
- `Create_SetsVersionAndVariantBits` — asserts the version nibble is `5` and the variant top-2-bits are `10`.