cmake_minimum_required(VERSION 3.16)
project(librsx LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

option(RSX_SANITIZE "Build with ASan+UBSan" OFF)
option(RSX_FUZZ "Build libFuzzer harnesses (needs clang)" OFF)
option(RSX_SHARED "Build shared librsx.so (for ctypes)" ON)

set(RSX_SOURCES
  src/util/wipe.cpp
  src/util/atomic_out.cpp
  src/util/hw_token.cpp
  src/util/hw_linux_tpm.cpp
  src/util/hw_macos.cpp
  src/util/hw_windows.cpp
  src/hash/sha256.cpp
  src/hash/blake2b.cpp
  src/mac/hmac.cpp
  src/cipher/chacha20.cpp
  src/cipher/chacha20_simd.cpp
  src/cipher/feistel.cpp
  src/kdf/argon2id.cpp
  src/kdf/shamir.cpp
  src/format/stream.cpp
  src/format/legacy.cpp
  src/format/container.cpp
  src/archive/lz.cpp
  src/archive/archive.cpp
  src/archive/sanitize.cpp
  src/format/duress.cpp
  src/archive/preview.cpp
  src/session/session.cpp
  src/session/ratchet.cpp
  src/abi.cpp
)

add_library(rsx_static STATIC ${RSX_SOURCES})
target_include_directories(rsx_static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
                                      PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_compile_options(rsx_static PRIVATE -Wall -Wextra -fPIC)
# test-only hooks (Argon2 env overrides). NEVER defined for the shipping shared lib.
target_compile_definitions(rsx_static PRIVATE RSX_TEST_HOOKS=1)
find_package(Threads REQUIRED)
target_link_libraries(rsx_static PUBLIC Threads::Threads)
if(WIN32)
  target_link_libraries(rsx_static PUBLIC bcrypt)
  if(MSVC)
    target_compile_options(rsx_static PRIVATE /FI${CMAKE_CURRENT_SOURCE_DIR}/src/util/win_shim.h)
  else()
    target_compile_options(rsx_static PRIVATE -include ${CMAKE_CURRENT_SOURCE_DIR}/src/util/win_shim.h)
  endif()
endif()
set_target_properties(rsx_static PROPERTIES OUTPUT_NAME rsx POSITION_INDEPENDENT_CODE ON)

if(RSX_SHARED)
  add_library(rsx SHARED ${RSX_SOURCES})
  target_include_directories(rsx PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
                                 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
  target_compile_options(rsx PRIVATE -Wall -Wextra -fPIC -O2)
  target_link_libraries(rsx PUBLIC Threads::Threads)
  if(WIN32)
    target_link_libraries(rsx PUBLIC bcrypt)
    if(MSVC)
      target_compile_options(rsx PRIVATE /FI${CMAKE_CURRENT_SOURCE_DIR}/src/util/win_shim.h)
    else()
      target_compile_options(rsx PRIVATE -include ${CMAKE_CURRENT_SOURCE_DIR}/src/util/win_shim.h)
    endif()
  endif()
  set_target_properties(rsx PROPERTIES OUTPUT_NAME rsx)
endif()

if(RSX_SANITIZE)
  foreach(t rsx_static)
    target_compile_options(${t} PUBLIC -fsanitize=address,undefined -fno-omit-frame-pointer -g)
    target_link_options(${t} PUBLIC -fsanitize=address,undefined)
  endforeach()
endif()

add_executable(test_primitives tests/test_primitives.cpp)
target_link_libraries(test_primitives PRIVATE rsx_static)

add_executable(test_stream tests/test_stream.cpp)
target_link_libraries(test_stream PRIVATE rsx_static)

add_executable(test_rsx3_attacks tests/test_rsx3_attacks.cpp)
target_link_libraries(test_rsx3_attacks PRIVATE rsx_static)

add_executable(test_abi_freeze tests/test_abi_freeze.cpp)
target_link_libraries(test_abi_freeze PRIVATE rsx_static)

enable_testing()
# core-format additions (archive, recovery, tests) live in their own file
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/rsx_extra.cmake OPTIONAL)
add_test(NAME primitives COMMAND test_primitives)
add_test(NAME stream COMMAND test_stream)
add_test(NAME rsx3_attacks COMMAND test_rsx3_attacks)
add_test(NAME abi_freeze COMMAND test_abi_freeze)

add_executable(fuzz_aead_standalone fuzz/fuzz_aead_standalone.cpp)
target_link_libraries(fuzz_aead_standalone PRIVATE rsx_static)

if(RSX_FUZZ AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_executable(fuzz_aead fuzz/fuzz_aead.cpp)
  target_link_libraries(fuzz_aead PRIVATE rsx_static)
  target_compile_options(fuzz_aead PRIVATE -fsanitize=fuzzer,address,undefined)
  target_link_options(fuzz_aead PRIVATE -fsanitize=fuzzer,address,undefined)
endif()
add_executable(test_nonce_v2 tests/test_nonce_v2.cpp)
target_link_libraries(test_nonce_v2 PRIVATE rsx_static)
target_include_directories(test_nonce_v2 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
add_test(NAME nonce_v2 COMMAND test_nonce_v2)
add_executable(test_lenbe_adversarial tests/test_lenbe_adversarial.cpp)
target_link_libraries(test_lenbe_adversarial PRIVATE rsx_static)

# B8: platform keystore libs (OS facilities for sealed hw-token storage only)
if(APPLE)
  find_library(SEC_LIB Security)
  find_library(CF_LIB CoreFoundation)
  find_library(IOKIT_LIB IOKit)
  if(SEC_LIB AND CF_LIB)
    foreach(t rsx_static rsx)
      if(TARGET ${t})
        target_link_libraries(${t} PUBLIC ${SEC_LIB} ${CF_LIB})
        if(IOKIT_LIB)
          target_link_libraries(${t} PUBLIC ${IOKIT_LIB})
        endif()
      endif()
    endforeach()
  endif()
  message(STATUS "B8 macOS Keychain provider: ENABLED (untested on this host)")
elseif(WIN32)
  foreach(t rsx_static rsx)
    if(TARGET ${t})
      target_link_libraries(${t} PUBLIC crypt32 ncrypt)
    endif()
  endforeach()
  message(STATUS "B8 Windows DPAPI/NCrypt provider: ENABLED (untested on this host)")
else()
  message(STATUS "B8 Linux providers: TPM2 (/dev/tpmrm0 best-effort) + 0600 file + machine-id weak fallback")
endif()

# ---- v0.3 A4: experimental image layer (separate lib; NOT part of frozen ABI v1 / librsx exports)
option(RSX_IMAGE "Build experimental librsx_image (rsx_image_exp.h)" ON)
if(RSX_IMAGE)
  set(RSX_IMAGE_SOURCES src/image/rs.cpp src/image/keyed.cpp src/image/png.cpp src/image/render.cpp src/image/img_capi.cpp src/image/decoder.cpp)
  # rsx_static already compiles with -fPIC; make it explicit so it can be linked into a shared object
  set_target_properties(rsx_static PROPERTIES POSITION_INDEPENDENT_CODE ON)
  add_library(rsx_image SHARED ${RSX_IMAGE_SOURCES})
  target_include_directories(rsx_image PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
                                       PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
  target_compile_options(rsx_image PRIVATE -Wall -Wextra -Wno-misleading-indentation -ffp-contract=off -fPIC -O2)
  target_link_libraries(rsx_image PRIVATE rsx_static)   # core SHA-256 / ChaCha20 (no duplicated sources)
  find_package(ZLIB QUIET)   # system zlib only (never vendored): compressed fixed-size PNG writer
  option(RSX_IMAGE_ZLIB "librsx_image: compress carrier PNGs with system zlib (OFF: stored deflate blocks)" ${ZLIB_FOUND})
  if(RSX_IMAGE_ZLIB AND NOT ZLIB_FOUND)
    message(WARNING "RSX_IMAGE_ZLIB=ON but zlib was not found; using stored deflate blocks")
  endif()
  if(RSX_IMAGE_ZLIB AND ZLIB_FOUND)
    target_compile_definitions(rsx_image PRIVATE RSX_IMAGE_HAVE_ZLIB=1)
    target_link_libraries(rsx_image PRIVATE ZLIB::ZLIB)
  endif()
  # export only rsxi_* (no re-export of librsx rsx_* / C++ internals)
  set_target_properties(rsx_image PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN ON
                        LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/image/rsx_image.map)
  target_link_options(rsx_image PRIVATE -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/src/image/rsx_image.map
                      -Wl,--exclude-libs,ALL)
  add_executable(test_image_core tests/test_image_core.cpp)
  target_link_libraries(test_image_core PRIVATE rsx_image)
  add_test(NAME image_core COMMAND test_image_core)
  add_executable(fuzz_image_standalone fuzz/fuzz_image_standalone.cpp fuzz/fuzz_image.cpp)
  target_link_libraries(fuzz_image_standalone PRIVATE rsx_image)
  add_test(NAME image_fuzz_smoke COMMAND fuzz_image_standalone 20000)
  if(RSX_SANITIZE)   # executables must carry the ASan/UBSan runtime when librsx_image is sanitized
    foreach(t test_image_core fuzz_image_standalone)
      target_compile_options(${t} PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer -g)
      target_link_options(${t} PRIVATE -fsanitize=address,undefined)
    endforeach()
  endif()
  if(RSX_FUZZ AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    add_executable(fuzz_image fuzz/fuzz_image.cpp)
    target_link_libraries(fuzz_image PRIVATE rsx_image)
    target_compile_options(fuzz_image PRIVATE -fsanitize=fuzzer,address,undefined)
    target_link_options(fuzz_image PRIVATE -fsanitize=fuzzer,address,undefined)
  endif()
endif()
add_executable(test_b1 tests/test_b1.cpp)
target_link_libraries(test_b1 PRIVATE rsx_static)
add_test(NAME b1 COMMAND test_b1)
add_executable(test_b3 tests/test_b3.cpp)
target_link_libraries(test_b3 PRIVATE rsx_static)
add_test(NAME b3 COMMAND test_b3)
add_executable(test_kxai_compat tests/test_kxai_compat.cpp)
target_link_libraries(test_kxai_compat PRIVATE rsx_static)
add_test(NAME kxai_compat COMMAND test_kxai_compat)
add_executable(test_b2 tests/test_b2.cpp)
target_link_libraries(test_b2 PRIVATE rsx_static)
target_include_directories(test_b2 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
add_test(NAME b2 COMMAND test_b2)
add_executable(test_b4 tests/test_b4.cpp)
target_link_libraries(test_b4 PRIVATE rsx_static)
add_test(NAME b4 COMMAND test_b4)
add_executable(test_r4c tests/test_r4c.cpp)
target_link_libraries(test_r4c PRIVATE rsx_static)
add_test(NAME r4c COMMAND test_r4c)
add_executable(test_b5 tests/test_b5.cpp)
target_link_libraries(test_b5 PRIVATE rsx_static)
add_test(NAME b5 COMMAND test_b5)
# B6: minimal read-only recovery decoder
