cmake_minimum_required(VERSION 3.16)
include(FindPkgConfig)
project(canokey-core C)

option(ENABLE_TESTS "Perform unit tests after build" OFF)
option(ENABLE_FUZZING "Build for fuzzing" OFF)
option(ENABLE_DEBUG_OUTPUT "Print debug messages" ON)
option(
    ENABLE_DUMB_DONGLE
    "Skip all user-presence test (USE AT YOUR OWN RISK)"
    OFF
)
option(VIRTCARD "Virt Card" OFF)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer")

if(ENABLE_DEBUG_OUTPUT)
    add_definitions(-DDEBUG_OUTPUT)
endif(ENABLE_DEBUG_OUTPUT)
if(ENABLE_TESTS OR ENABLE_FUZZING)
    set(CMAKE_POSITION_INDEPENDENT_CODE ON)
    set(CMAKE_C_FLAGS
        "${CMAKE_C_FLAGS} --coverage -fsanitize=address -fsanitize=undefined"
    )
    if(NOT CMAKE_C_COMPILER_ID MATCHES "Clang")
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-usage")
    endif()
endif()
if(ENABLE_DUMB_DONGLE)
    add_definitions(-DDUMB_DONGLE)
endif(ENABLE_DUMB_DONGLE)

add_subdirectory(canokey-crypto EXCLUDE_FROM_ALL)

# tinycbor: build sources directly instead of add_subdirectory.
# tinycbor 7.0's CMakeLists.txt declares project(... LANGUAGES C CXX), which
# triggers CXX compiler detection. On ARM cross-compilation the CXX test
# program fails to link against the scatter file (L6236E: no RESET section).
# Avoid this by not using tinycbor's own CMake build system.
set(TINYCBOR_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tinycbor)
# Generate the two headers that tinycbor 7.0's cbor.h expects
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/tinycbor/tinycbor-export.h
    "#ifndef CBOR_API\n#define CBOR_API\n#endif\n")
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/tinycbor/tinycbor-version.h
    "#define TINYCBOR_VERSION_MAJOR 7\n#define TINYCBOR_VERSION_MINOR 0\n#define TINYCBOR_VERSION_PATCH 0\n")

if(DEFINED PRODUCT_NAME)
    add_definitions(-DUSBD_PRODUCT_STRING="${PRODUCT_NAME}")
endif()

# Generate pre-encoded CBOR segments for ctap_get_info.
# Constants are parsed directly from headers — no duplication.
find_program(PYTHON_EXE NAMES python3 python REQUIRED)
add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/ctap_get_info_cbor.inc
    COMMAND ${PYTHON_EXE}
        ${CMAKE_CURRENT_SOURCE_DIR}/scripts/gen_ctap_get_info.py
        --headers
            ${CMAKE_CURRENT_SOURCE_DIR}/applets/ctap/ctap-internal.h
            ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/USB/class/ctaphid/ctaphid.h
        --credential-id-size 70
        --output ${CMAKE_CURRENT_BINARY_DIR}/ctap_get_info_cbor.inc
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/scripts/gen_ctap_get_info.py
        ${CMAKE_CURRENT_SOURCE_DIR}/applets/ctap/ctap-internal.h
        ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/USB/class/ctaphid/ctaphid.h
    COMMENT "Generating ctap_get_info_cbor.inc"
)
add_custom_target(
    generate_ctap_get_info_cbor
    DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/ctap_get_info_cbor.inc
)

file(
    GLOB_RECURSE SRC
    src/*.c
    applets/*.c
    interfaces/*.c
    littlefs/lfs.c
    littlefs/lfs_util.c
    tinycbor/src/cborencoder.c
    tinycbor/src/cborparser.c
)
add_library(canokey-core ${SRC})
add_dependencies(canokey-core generate_ctap_get_info_cbor)

if(ENABLE_TESTS)
    target_compile_definitions(canokey-core PUBLIC TEST)
endif(ENABLE_TESTS)
if(ENABLE_FUZZING)
    target_compile_definitions(canokey-core PUBLIC TEST FUZZ)
    if(ENABLE_TESTS)
        message(WARNING "ENABLE_FUZZING will cause some tests to FAIL")
    endif(ENABLE_TESTS)
    # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-coverage=bb -finstrument-functions")
endif(ENABLE_FUZZING)

target_include_directories(
    canokey-core
    SYSTEM
    PUBLIC
        include
        littlefs
        tinycbor/src
        ${CMAKE_CURRENT_BINARY_DIR}/tinycbor
        ${CMAKE_CURRENT_BINARY_DIR}
        interfaces/USB/device
        interfaces/USB/core/inc
        interfaces/USB/class/ccid
        interfaces/USB/class/ctaphid
        interfaces/USB/class/kbdhid
        interfaces/USB/class/webusb
)
target_compile_definitions(canokey-core PRIVATE CBOR_NO_FLOATING_POINT CBOR_STATIC_DEFINE)
target_link_libraries(canokey-core canokey-crypto)

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules")

if(VIRTCARD OR ENABLE_TESTS OR ENABLE_FUZZING)
    set(gitrev_in virt-card/git-rev.h.in)
    set(gitrev virt-card/git-rev.h)
    add_custom_target(
        gitrev
        ${CMAKE_COMMAND} -E remove -f ${CMAKE_CURRENT_SOURCE_DIR}/${gitrev}
        COMMAND
            ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${gitrev_in}
            ${CMAKE_CURRENT_SOURCE_DIR}/${gitrev}
        COMMAND
            git describe --always --tags --long --abbrev=8 --dirty >>
            ${CMAKE_CURRENT_SOURCE_DIR}/${gitrev}
    )
endif()

if(ENABLE_TESTS)
    find_package(CMocka CONFIG REQUIRED)
    include(AddCMockaTest)
    include(AddMockedTest)
    add_subdirectory(test)
    enable_testing()

    add_executable(
        fido-hid-over-udp
        virt-card/usb-dummy.c
        virt-card/device-sim.c
        virt-card/fabrication.c
        virt-card/fido-hid-over-udp.c
        littlefs/bd/lfs_filebd.c
    )
    target_include_directories(
        fido-hid-over-udp
        SYSTEM
        PRIVATE virt-card littlefs
    )
    target_link_libraries(
        fido-hid-over-udp
        general
        canokey-core
        "-fsanitize=address"
    )
    add_dependencies(fido-hid-over-udp gitrev)

    pkg_search_module(PCSCLITE libpcsclite)
    if(PCSCLITE_FOUND)
        add_library(
            u2f-virt-card
            SHARED
            virt-card/usb-dummy.c
            virt-card/device-sim.c
            virt-card/ifdhandler.c
            virt-card/fabrication.c
            littlefs/bd/lfs_filebd.c
        )
        target_include_directories(
            u2f-virt-card
            SYSTEM
            PRIVATE virt-card ${PCSCLITE_INCLUDE_DIRS} littlefs
        )
        target_link_libraries(u2f-virt-card ${PCSCLITE_LIBRARIES} canokey-core)
        add_dependencies(u2f-virt-card gitrev)
    endif()
endif(ENABLE_TESTS)

if(ENABLE_FUZZING)
    add_executable(
        honggfuzz-fuzzer
        fuzzer/honggfuzz-fuzzer.c
        virt-card/usb-dummy.c
        virt-card/device-sim.c
        virt-card/fabrication.c
        littlefs/bd/lfs_filebd.c
    )
    target_include_directories(
        honggfuzz-fuzzer
        SYSTEM
        PRIVATE virt-card littlefs
    )
    target_link_libraries(honggfuzz-fuzzer canokey-core)
    add_dependencies(honggfuzz-fuzzer gitrev)

    add_executable(
        honggfuzz-debug
        fuzzer/honggfuzz-debug.c
        fuzzer/honggfuzz-fuzzer.c
        virt-card/usb-dummy.c
        virt-card/device-sim.c
        virt-card/fabrication.c
        littlefs/bd/lfs_filebd.c
    )
    target_include_directories(
        honggfuzz-debug
        SYSTEM
        PRIVATE virt-card littlefs
    )
    target_link_libraries(honggfuzz-debug canokey-core)
    add_dependencies(honggfuzz-debug gitrev)
endif(ENABLE_FUZZING)
