fasttree-sys 0.2.0

Surowe bindingi FFI (extern "C") do biblioteki FastTree (Nim). Nie używaj bezpośrednio — patrz crate `fasttree`.
import std/[strutils]

const UsingBlake3* = not defined(fasttreeNoBlake3)

type
  Hash* = object
    ## 32-bajtowy skrót — węzeł drzewa Merkle FastTree.
    bytes*: array[32, byte]

proc `==`*(a, b: Hash): bool {.inline.} = a.bytes == b.bytes

proc `$`*(h: Hash): string =
  result = newStringOfCap(64)
  for b in h.bytes:
    result.add toHex(b.int, 2).toLowerAscii

proc hashFromHex*(s: string): Hash =
  ## UWAGA: to jedna z niewielu funkcji w bibliotece Nim wołana bezpośrednio
  ## z wejściem pochodzącym zza granicy FFI (capi.nim: ft_store_get/ft_store_has
  ## dostają hex od Rust/C). Dlatego walidacja rzuca zwykły `ValueError`
  ## (catchable), a NIE `doAssert`/Defect — Defect przechodzi przez
  ## `except CatchableError` w capi.nim i ubiłby cały proces wołającego
  ## przy zwykłym błędnym wejściu, zamiast zwrócić FT_ERR_INVALID_ARGUMENT.
  if s.len != 64:
    raise newException(ValueError, "oczekiwano 64 znaków hex (32 bajty), otrzymano " & $s.len)
  for i in 0 ..< 32:
    result.bytes[i] = byte(parseHexInt(s[i*2 .. i*2+1]))

when UsingBlake3:
  # --- FFI do libblake3 --------------------------------------------------
  # Wymaga linkowania z -lblake3 (paczka systemowa lub zbudowana z sources
  # BLAKE3-team/BLAKE3/c, patrz README -> "Budowanie z BLAKE3"). Deklaracje
  # odzwierciedlają blake3.h z referencyjnego repo BLAKE3-team/BLAKE3.
  {.passC: "-I/usr/local/include".}
  {.passL: "-L/usr/local/lib -lblake3".}

  const BLAKE3_OUT_LEN = 32

  type Blake3HasherC {.importc: "blake3_hasher", header: "blake3.h".} = object
    # Nieprzezroczysta struktura po stronie C — Nim rezerwuje miejsce jako
    # surowy bufor bajtów. Rozmiar zweryfikowany względem referencyjnego
    # blake3.h (BLAKE3-team/BLAKE3 @ c/blake3.h): key[8]*u32 + chunk_state +
    # cv_stack_len + cv_stack[(BLAKE3_MAX_DEPTH+1)*BLAKE3_OUT_LEN] = 1912 B
    # na x86-64 (sizeof zweryfikowany programem C przy kompilacji tego repo).
    # Runtime sanity-check poniżej (ftBlake3NativeSizeof) łapie rozjazd,
    # gdyby ktoś zbudował/podlinkował inną wersję libblake3.
    opaque: array[1912, byte]

  proc blake3_hasher_init(self: ptr Blake3HasherC) {.importc, header: "blake3.h".}
  proc blake3_hasher_update(self: ptr Blake3HasherC, input: pointer, inputLen: csize_t) {.importc, header: "blake3.h".}
  proc blake3_hasher_finalize(self: ptr Blake3HasherC, output: ptr byte, outLen: csize_t) {.importc, header: "blake3.h".}

  {.emit: """
#include <blake3.h>
size_t ftBlake3NativeSizeof(void) { return sizeof(blake3_hasher); }
""".}
  proc ftBlake3NativeSizeof(): csize_t {.importc, nodecl.}

  var blake3AbiChecked = false

  proc verifyBlake3Abi*() =
    ## Wywoływane raz (leniwie, przy pierwszym `initHasher`) — porównuje
    ## rozmiar struktury zarezerwowany po stronie Nim z prawdziwym
    ## `sizeof(blake3_hasher)` zwróconym przez podlinkowaną libblake3.
    ## Rozjazd (np. po aktualizacji libblake3 do wersji ze zmienionym
    ## layoutem) kończy się jasnym błędem zamiast cichej korupcji pamięci.
    if blake3AbiChecked: return
    let native = ftBlake3NativeSizeof()
    if native.int != sizeof(Blake3HasherC):
      raise newException(AssertionDefect,
        "Niezgodność ABI BLAKE3: Nim rezerwuje " & $sizeof(Blake3HasherC) &
        " B, podlinkowana libblake3 zgłasza sizeof(blake3_hasher)=" & $native &
        " B. Sprawdź wersję blake3.h i zaktualizuj rozmiar tablicy 'opaque' w hashing.nim.")
    blake3AbiChecked = true

  type Hasher* = object
    c: Blake3HasherC

  proc initHasher*(): Hasher =
    verifyBlake3Abi()
    blake3_hasher_init(addr result.c)

  proc update*(h: var Hasher, data: openArray[byte]) =
    if data.len > 0:
      blake3_hasher_update(addr h.c, unsafeAddr data[0], csize_t(data.len))

  proc finalize*(h: var Hasher): Hash =
    blake3_hasher_finalize(addr h.c, addr result.bytes[0], csize_t(BLAKE3_OUT_LEN))

else:
  # --- Fallback: SHA-256 przez nimcrypto ---------------------------------
  import nimcrypto/sha2

  type Hasher* = object
    c: sha256

  proc initHasher*(): Hasher =
    result.c.init()

  proc update*(h: var Hasher, data: openArray[byte]) =
    if data.len > 0:
      h.c.update(data)

  proc finalize*(h: var Hasher): Hash =
    let digest = h.c.finish()
    h.c.clear()
    result.bytes = digest.data

const AlgoName* = (when UsingBlake3: "blake3" else: "sha256-fallback")
  ## Nazwa algorytmu haszującego użytego w tej kompilacji — zapisywana
  ## w manifeście (manifest.nim), żeby dwóch manifestów policzonych różnymi
  ## backendami (blake3 vs fallback sha256) nigdy nie dało się pomylić przez
  ## proste porównanie root-hash (identyczne drzewo, inny algorytm = inny hash).

proc hashBytes*(data: openArray[byte]): Hash =
  ## Skrót pojedynczego bloku danych (np. chunku pliku).
  var h = initHasher()
  h.update(data)
  h.finalize()

proc hashChildren*(children: openArray[Hash]): Hash =
  ## Węzeł wewnętrzny drzewa Merkle: skrót konkatenacji skrótów dzieci.
  ## Używane do budowy roota manifestu z hashy plików/chunków.
  var buf = newSeq[byte](children.len * 32)
  for i, c in children:
    buf[i*32 ..< i*32+32] = c.bytes
  hashBytes(buf)