// Probele clientului .NET (Aere.Cloud 1.7.0), fara framework de probe: fiecare proba tipareste "--- PASS: Nume" sau "--- FAIL: Nume: motiv", // iar codul de iesire e numarul de probe picate (0 = toate verzi). Aceleasi garzi ca la clientul Go: cheia obligatorie, cheia trimisa NUMAI // unde trebuie, redirectarile NEurmate (dovedit pe un server local real, fiindca numai handlerul implicit al SDK-ului o poate arata), // exportul jurnalului verificat pe octetii primiti, verificarea unei copii pastrate (tinuta / stearsa / eroare), webhook-ul in timp constant. // Controlul negativ (control-negativ.sh) strica fiecare paznic intr-o COPIE a sursei si cere ca proba lui sa pice. using System.Net; using System.Net.Sockets; using System.Security.Cryptography; using System.Text; using System.Text.Json; using Aere.Cloud; const string Cheie = "ak2800.0x1111111111111111111111111111111111111111.secretproba"; int picate = 0, trecute = 0; async Task Proba(string nume, Func f) { try { await f(); Console.WriteLine("--- PASS: " + nume); trecute++; } catch (Exception e) { Console.WriteLine("--- FAIL: " + nume + ": " + e.Message.Split('\n')[0]); picate++; } } void Cer(bool c, string ce) { if (!c) throw new Exception("cerut: " + ce); } string Sha(byte[] b) => "0x" + Convert.ToHexString(SHA256.HashData(b)).ToLowerInvariant(); (AereCloudClient, Ciot) Client(Func? r = null) { var c = new Ciot { Raspunde = r ?? (_ => Json(200, "{}")) }; return (new AereCloudClient(Cheie, "https://ciot.invalid/v1", c), c); } HttpResponseMessage Json(int s, string corp) => new((HttpStatusCode)s) { Content = new StringContent(corp, Encoding.UTF8, "application/json") }; await Proba("CheiaEObligatorie", () => { bool a = false, b = false; try { _ = new AereCloudClient(""); } catch (ArgumentException) { a = true; } try { _ = new AereCloudClient(null!); } catch (ArgumentException) { b = true; } Cer(a && b, "o cheie goala sau lipsa e refuzata la constructie"); return Task.CompletedTask; }); await Proba("ReadinessCheiaNumaiLaAttest", async () => { var (c, ciot) = Client(); await c.ReadinessAsync("exemplu.test"); Cer(!ciot.Ultima.Antete.ContainsKey("x-api-key"), "fara attest cheia NU pleaca"); await c.ReadinessAsync("exemplu.test", attest: true); Cer(ciot.Ultima.Antete.GetValueOrDefault("x-api-key") == Cheie, "cu attest cheia pleaca"); Cer(ciot.Ultima.Corp.Contains("\"attest\":true"), "corpul poarta attest"); await c.ReadinessReportAsync("exemplu.test"); Cer(!ciot.Ultima.Antete.ContainsKey("x-api-key"), "raportul public fara cheie"); await c.PqFinalityAsync(); Cer(!ciot.Ultima.Antete.ContainsKey("x-api-key"), "finalitatea AIP-21 fara cheie"); await c.HealthAsync(); Cer(!ciot.Ultima.Antete.ContainsKey("x-api-key"), "health fara cheie"); await c.AccountAsync(); Cer(ciot.Ultima.Antete.GetValueOrDefault("x-api-key") == Cheie, "account cu cheie"); Cer(ciot.Ultima.Antete.GetValueOrDefault("user-agent", "").StartsWith("aere-cloud-csharp/1.7.0"), "user-agent-ul clientului"); }); await Proba("PerimetruFinalitateDovada", async () => { var (c, ciot) = Client(); await c.ReadinessPerimeterAsync("exemplu.test", new PerimeterOptions(new[] { "www", "api" }, false, true)); using (var d = JsonDocument.Parse(ciot.Ultima.Corp)) { var r = d.RootElement; Cer(r.GetProperty("hosts").GetArrayLength() == 2 && !r.GetProperty("discover").GetBoolean() && r.GetProperty("attest").GetBoolean(), "corpul perimetrului"); } Cer(ciot.Ultima.Antete.ContainsKey("x-api-key"), "perimetrul cere cheia"); await c.FinalityAsync(); Cer(ciot.Ultima.Url == "https://ciot.invalid/v1/data/finality" && ciot.Ultima.Metoda == "GET", "GET /data/finality"); await c.ProofAsync("0xab/c"); Cer(ciot.Ultima.Url.EndsWith("/proof/0xab%2Fc"), "hash-ul scapat in cale: " + ciot.Ultima.Url); }); await Proba("TrustApiVerify", async () => { var (c, ciot) = Client(_ => Json(200, "{\"verdict\":\"VALID\"}")); var r = await c.VerifyAsync(new Dictionary { ["kind"] = "data" }, 28001); Cer(ciot.Ultima.Metoda == "POST" && ciot.Ultima.Url.EndsWith("/verify?chain=28001"), "POST /verify?chain=28001"); Cer(ciot.Ultima.Corp == "{\"kind\":\"data\"}", "plicul trimis ca atare: " + ciot.Ultima.Corp); Cer(r.GetProperty("verdict").GetString() == "VALID", "verdictul intors"); await c.VerifyAsync(new Dictionary { ["kind"] = "data" }); Cer(ciot.Ultima.Url.EndsWith("/verify"), "fara lant: /verify"); }); await Proba("IdentityCompliance", async () => { var (c, ciot) = Client(_ => Json(200, "{\"verdict\":\"PARTIAL\"}")); var r = await c.IdentityVerifyAsync(new Dictionary { ["audience"] = "a", ["nonce"] = "n" }); Cer(ciot.Ultima.Metoda == "POST" && ciot.Ultima.Url.EndsWith("/v1/identity/verify") && ciot.Ultima.Antete.ContainsKey("x-api-key"), "POST /identity/verify cu cheie: " + ciot.Ultima.Url); Cer(ciot.Ultima.Corp == "{\"audience\":\"a\",\"nonce\":\"n\"}", "cererea trimisa ca atare: " + ciot.Ultima.Corp); Cer(r.GetProperty("verdict").GetString() == "PARTIAL", "verdictul intors"); await c.ComplianceCheckAsync(new Dictionary { ["record"] = true }); Cer(ciot.Ultima.Url.EndsWith("/v1/compliance/check") && ciot.Ultima.Antete.ContainsKey("x-api-key") && ciot.Ultima.Corp == "{\"record\":true}", "POST /compliance/check: " + ciot.Ultima.Url + " " + ciot.Ultima.Corp); }); await Proba("ExportulZilei", async () => { var corp = Encoding.UTF8.GetBytes("{\"a\":1}\n{\"b\":2}\n"); string? digest = Sha(corp); string format = "jsonl"; var (c, _) = Client(_ => { var m = new HttpResponseMessage(HttpStatusCode.OK) { Content = new ByteArrayContent(corp) }; if (digest is not null) m.Headers.Add("x-aere-digest", digest); m.Headers.Add("x-aere-day", "2026-09-27"); m.Headers.Add("x-aere-entries", "2"); return m; }); var e = await c.AuditExportAsync("2026-09-27", format); Cer(e.Verified == true && e.Entries == 2 && e.Day == "2026-09-27", "octetii se potrivesc cu amprenta declarata"); digest = "0x" + new string('0', 64); e = await c.AuditExportAsync("2026-09-27", format); Cer(e.Verified == false, "o amprenta care nu e a octetilor: NEverificat"); digest = null; e = await c.AuditExportAsync("2026-09-27", format); Cer(e.Verified == false, "fara amprenta: NEverificat"); e = await c.AuditExportAsync("2026-09-27", "cef"); Cer(e.Verified is null, "cef: nimic de verificat aici"); }); await Proba("ExportulPastrat", async () => { var tinut = Encoding.UTF8.GetBytes("{\"x\":1}\n"); int stare = 200; string piatra = ""; var (c, _) = Client(_ => stare switch { 200 => new HttpResponseMessage(HttpStatusCode.OK) { Content = new ByteArrayContent(tinut) }, 410 => Json(410, piatra), _ => Json(stare, "{\"error\":\"too many requests\"}"), }); var k = await c.AuditVerifyKeptAsync("2026-09-01", tinut); Cer(k.State == "held" && k.Matches, "copia pastrata = ziua tinuta"); k = await c.AuditVerifyKeptAsync("2026-09-01", Encoding.UTF8.GetBytes("altceva")); Cer(k.State == "held" && !k.Matches, "o copie alterata NU se potriveste"); stare = 410; piatra = "{\"expired\":{\"digest\":\"" + Sha(tinut) + "\",\"deletedAt\":\"2026-09-20T00:00:00Z\"}}"; k = await c.AuditVerifyKeptAsync("2026-09-01", tinut); Cer(k.State == "expired" && k.Matches && k.DeletedAt == "2026-09-20T00:00:00Z", "ziua stearsa: piatra de mormant"); k = await c.AuditVerifyKeptAsync("2026-09-01", Encoding.UTF8.GetBytes("altceva")); Cer(k.State == "expired" && !k.Matches, "piatra de mormant nu se potriveste cu o copie alterata"); stare = 429; bool eroare = false; try { await c.AuditVerifyKeptAsync("2026-09-01", tinut); } catch (AereCloudException x) { eroare = x.Status == 429; } Cer(eroare, "un 429 e o EROARE, niciodata 'nu se potriveste'"); }); await Proba("RedirectareaNuEUrmata", async () => { int Port() { var l = new TcpListener(IPAddress.Loopback, 0); l.Start(); var p = ((IPEndPoint)l.LocalEndpoint).Port; l.Stop(); return p; } int pa = Port(), pb = Port(); int loviturib = 0; string? cheiaLaB = null; using var a = new HttpListener(); a.Prefixes.Add($"http://127.0.0.1:{pa}/"); a.Start(); using var b = new HttpListener(); b.Prefixes.Add($"http://127.0.0.1:{pb}/"); b.Start(); var ta = Task.Run(async () => { while (a.IsListening) { HttpListenerContext x; try { x = await a.GetContextAsync(); } catch { break; } x.Response.StatusCode = 302; x.Response.RedirectLocation = $"http://127.0.0.1:{pb}/fura"; x.Response.Close(); } }); var tb = Task.Run(async () => { while (b.IsListening) { HttpListenerContext x; try { x = await b.GetContextAsync(); } catch { break; } loviturib++; cheiaLaB = x.Request.Headers["x-api-key"]; x.Response.StatusCode = 200; x.Response.Close(); } }); using var c = new AereCloudClient(Cheie, $"http://127.0.0.1:{pa}/v1"); // handlerul IMPLICIT al SDK-ului int status = -1; try { await c.AccountAsync(); } catch (AereCloudException x) { status = x.Status; } await Task.Delay(200); a.Stop(); b.Stop(); Cer(status == 302, "un 3xx e un raspuns (AereCloudException 302), nu un drum; primit " + status); Cer(loviturib == 0 && cheiaLaB is null, "gazda din Location NU e contactata (lovituri " + loviturib + ")"); }); await Proba("VerifyWebhook", () => { var corp = Encoding.UTF8.GetBytes("{\"event\":\"anchor\",\"height\":1}"); var sig = Convert.ToHexString(HMACSHA256.HashData(Encoding.UTF8.GetBytes("secret"), corp)).ToLowerInvariant(); Cer(AereCloudClient.VerifyWebhook(corp, sig, "secret"), "semnatura buna trece"); var rea = sig[..^1] + (sig[^1] == '0' ? '1' : '0'); Cer(!AereCloudClient.VerifyWebhook(corp, rea, "secret"), "o cifra schimbata e refuzata"); Cer(!AereCloudClient.VerifyWebhook(corp, sig[..10], "secret"), "o semnatura scurta e refuzata"); Cer(!AereCloudClient.VerifyWebhook(corp, null, "secret"), "fara antet: refuzat"); Cer(!AereCloudClient.VerifyWebhook(corp, sig, "alt-secret"), "alt secret: refuzat"); return Task.CompletedTask; }); await Proba("EroriSiBlockNumber", async () => { var (c, _) = Client(r => r.RequestUri!.AbsolutePath.EndsWith("/rpc") ? Json(200, "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x10\"}") : Json(500, "boom")); Cer(await c.BlockNumberAsync() == 16, "eth_blockNumber 0x10 = 16"); AereCloudException? e = null; try { await c.DataHeadAsync(); } catch (AereCloudException x) { e = x; } Cer(e is { Status: 500, Body: "boom", Path: "/data/head" }, "un 500 poarta statusul, corpul si calea"); var (c2, _) = Client(_ => throw new HttpRequestException("retea cazuta")); AereCloudException? e2 = null; try { await c2.HealthAsync(); } catch (AereCloudException x) { e2 = x; } Cer(e2 is { Status: 0 } && !e2.Message.Contains(Cheie), "fara raspuns: status 0, iar mesajul nu poarta cheia"); var (c3, _) = Client(_ => Json(200, "{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-1}}")); bool refuz = false; try { await c3.BlockNumberAsync(); } catch (AereCloudException x) { refuz = x.Status == 200; } Cer(refuz, "un rezultat fara forma 0x e o eroare, nu un zero"); }); Console.WriteLine($"probe: {trecute} trecute, {picate} picate"); return picate; sealed class Ciot : HttpMessageHandler { public sealed record Cerere(string Metoda, string Url, Dictionary Antete, string Corp); public List Cereri { get; } = new(); public Cerere Ultima => Cereri[^1]; public Func Raspunde { get; set; } = _ => new HttpResponseMessage(HttpStatusCode.OK); protected override async Task SendAsync(HttpRequestMessage r, CancellationToken ct) { var corp = r.Content is null ? "" : await r.Content.ReadAsStringAsync(ct); var h = r.Headers.ToDictionary(x => x.Key.ToLowerInvariant(), x => string.Join(" ", x.Value)); Cereri.Add(new Cerere(r.Method.Method, r.RequestUri!.ToString(), h, corp)); return Raspunde(r); } }