//! Phase 0 library checks. //! //! Koh 3 depends on five things outside its own source: SQLite (verified in //! src/storage), Blake3, zlib, SHA-256/HMAC and an HTTP client. This file pins //! the behaviour Koh relies on for the four stdlib ones, against vectors //! produced by other implementations of the same algorithms. If a V upgrade //! breaks an assumption, it breaks here rather than in a save. module tests import compress.zlib import crypto.blake3 import crypto.hmac import crypto.sha256 import net import net.http import time // Vectors from the official blake3 bindings, cross-checked against the // naming of real Koh 2 objects: `.koh/objects//` is // blake3(uncompressed bytes) of a zlib stream. fn test_blake3_vectors() { assert blake3.sum256(''.bytes()).hex() == 'af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262' assert blake3.sum256('abc'.bytes()).hex() == '6437b3ac38465133ffb63b75273a8db548c558465d79db03fd359c6cd5bd9d85' assert blake3.sum256('The quick brown fox jumps over the lazy dog'.bytes()).hex() == '2f1514181aadccd913abd94cfa592701a5686ab23f8df1dff1b74710febc6d4a' } fn test_blake3_streaming_matches_one_shot() { mut d := blake3.Digest.new_hash()! d.write('The quick brown '.bytes())! d.write('fox jumps over the lazy dog'.bytes())! assert d.checksum(32) == blake3.sum256('The quick brown fox jumps over the lazy dog'.bytes()) } fn test_zlib_round_trip() { // Object bytes must survive a trip through the store byte for byte, // including an empty payload and one with every byte value. mut all_bytes := []u8{len: 256} for i in 0 .. 256 { all_bytes[i] = u8(i) } mut payloads := [[]u8{}, 'hello koh'.bytes()] payloads << all_bytes for payload in payloads { packed := zlib.compress(payload)! assert zlib.decompress(packed)! == payload } } fn test_sha256_and_hmac_vectors() { assert sha256.sum256('abc'.bytes()).hex() == 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad' assert sha256.hexhash('abc') == 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad' // HMAC-SHA256, the primitive behind SigV4 signing. mac := hmac.new('key'.bytes(), 'The quick brown fox jumps over the lazy dog'.bytes(), sha256.sum256, 64) assert mac.hex() == 'f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8' } struct EtagHandler { mut: seen_if_none_match string } fn (mut h EtagHandler) handle(req http.Request) http.Response { h.seen_if_none_match = req.header.get_custom('If-None-Match', http.HeaderQueryConfig{}) or { '' } mut resp := http.Response{ body: 'payload' } if h.seen_if_none_match == '*' { resp.status_code = 412 resp.status_msg = 'Precondition Failed' } else { resp.status_code = 200 resp.status_msg = 'OK' } resp.header.add_custom('ETag', '"3b1f"') or {} return resp } // Starts the probe server on the first free port, so parallel test runs do not // fight over a fixed one. fn start_probe_server() !(int, &http.Server) { for port in [19301, 19302, 19303, 19304, 19305] { listener := net.listen_tcp(.ip, '127.0.0.1:${port}') or { continue } mut server := &http.Server{ listener: listener handler: EtagHandler{} show_startup_message: false } spawn server.listen_and_serve() server.wait_till_running()! return port, server } return error('no free port for the probe server') } // The S3 backend needs custom request headers, the response status, and the // ETag that conditional writes are keyed on. TLS itself is exercised against a // live bucket, not here. fn test_http_client_sends_headers_and_reads_status_and_etag() { port, mut server := start_probe_server()! defer { server.stop() } url := 'http://127.0.0.1:${port}/probe' mut hdr := http.new_header() hdr.add_custom('If-None-Match', '*')! resp := http.fetch( url: url method: .get header: hdr )! assert resp.status_code == 412 assert resp.header.get_custom('ETag', http.HeaderQueryConfig{}) or { '' } == '"3b1f"' plain := http.fetch(url: url, method: .get)! assert plain.status_code == 200 assert plain.body == 'payload' // Retries: the S3 backend retries network errors and 5xx. A dead port must // surface as an error rather than a hang. if _ := http.fetch( url: 'http://127.0.0.1:19399/gone' method: .get read_timeout: 2 * time.second ) { assert false, 'a request to a closed port should fail' } }