Developer Setup#

Onboarding#

1. Setup the docker container#

The development of the SDK is done in a local docker container. There is a helper script under scripts/dev-env.sh. Once docker is installed, the developer can use that script to create a docker container with all the dependencies installed. Run the script without any options to see the help.

$ ./scripts/dev-env.sh
Usage: ./scripts/dev-env.sh {setup|start|stop|clean|status|shell}
Manages the nv-attest development Docker environment.

Commands:
  setup    Builds the Docker image 'nvattest-dev-image' and sets ownership
           of '<project directory>' for user '<your user>' (requires sudo).
  start    Starts the Docker container 'nv-attest-dev-container' in the background.
           If stopped, restarts the existing container.
  stop     Stops the running Docker container 'nv-attest-dev-container'.
  clean    Stops and removes the container 'nv-attest-dev-container',
           then removes the image 'nvattest-dev-image'.
  status   Shows the status of the container 'nv-attest-dev-container'.
  shell    Executes an interactive bash shell in the running container.

A typical workflow would be like:

./dev-env.sh setup
./dev-env.sh start
./dev-env.sh shell
# do some work
./dev-env.sh stop

# to nuke the container
./dev-env.sh clean # need to run setup again after this

VSCode has support to connect to docker containers (its called remote explorer). This can make development significantly easier.

The script has a shell command which mounts the attestation-sdk directory in the container. The following steps can be done after shelling into the container.

Note: make sure you run git commands from your host machine, not the docker container as the git configuration (ssh-keys etc) will not be available in the docker container.

2. Source the helper scripts#

To make following steps easier, source the helper script:

source /attestation-sdk/nv-attestation-sdk-cpp/scripts/activate.sh

This script will update your path so that other scripts can be invoked from anywhere in the container.

3. Build the SDK#

The output of the following command is the library libnv_attestation_sdk.dylib or libnv_attestation.so

# Configure dependencies
cd build
cmake .. -DBUILD_TESTING=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON

# Build the SDK from src
cmake --build .

Or the equivalent:

build.sh

CMake Available Options#

-DSANITIZER=[address|thread|undefined|leak|OFF] (Default: OFF)

  • OFF: Perform a normal build with no sanitizer

  • <other>: Compile with the selected sanitizer

-DENABLE_NVML=[ON|OFF] (Default: ON)

  • ON: Enables NVML support and GPU-specific attestation features

  • OFF: Disables NVML support (e.g., for local dev or build pipeline)

-DENABLE_NSCQ=[ON|OFF] (Default: ON)

  • ON: Enables NSCQ support and SWITCH-specific attestation features

  • OFF: Disables NSCQ support (e.g., for local dev or build pipeline)

Note: when cmake is run the first time, it caches settings in CMakeCache.txt in the build folder (and in the root folder of the project). If you modify any of those settings, you need to rm the file for cmake to pick up the new changes. If there are any issues with cmake, clear these cache files in the build and the root folder.

4. VS Code Integration#

By using DCMAKE_EXPORT_COMPILE_COMMANDS=ON, cmake will produce build/compile_commands.json file. Modify settings.json in VS Code settings (workspace settings) as follows:

"C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json",
"C_Cpp.codeAnalysis.clangTidy.enabled": true,
"C_Cpp.codeAnalysis.clangTidy.useBuildPath": true

VSCode will use this to provide language server features, including an integration with clang-tidy for linting.

Code Structure#

The public headers are under include/nv_attestation. The private headers are under src/internal.

The public headers are nested under nv_attestation because it will prevent conflicts if a client is using other dependencies which have the same header file name

Debugging#

  • If you want to debug a dependency, make sure that you have the installed version of the dependency with debug symbols enabled, otherwise it is not possible to step through code in the dependency. More details will be added here once the exact steps for this are known.

This is an example launch config to debug in vscode:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug Test Executable",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/build/integration-tests/nv-attestation-unit-tests",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}/build",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "Set disassembly flavor to Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                },
            ],
            "preLaunchTask": "build",
            "miDebuggerPath": "/usr/bin/gdb"
        }
    ]
} 

This will not work in cursor because it modifies the python environment variables and gdb depends on Python. However this works perfectly in VS Code.

Note: This is for debugging the test executable that is built in the CMake file. For debugging unit tests, the launch configuration will have to be different. This readme will be updated once the unit test framework is set up.

Testing#

The unit-tests folder contains all the tests. Some of the tests also work as “integration tests” (i.e they will call into the actual hardware instead of using mock evidence)

This is controlled by environment variables. See here for environment variables used by the unit tests. If running integration tests, care must be taken that the SDK is built with NVML/NSCQ Also see environment variables influencing SDK behaviour: nvat_attest_system().

The unit-test cmake file also copies over the testdata to the build folder so that the unit tests can refer to the test data by the relative path. This copying is done when CMake is configured, for e.g when cmake .. -DBUILD_TESTING=ON is run. usually when developing this configuration is done only once and every time a change is made, the build cmake command is run directly i.e cmake --build . therefore care must be taken that if any new files are added or if the test data directory is modified in any way the cmake configuration command has to be run again so that the folder gets updated in the build directory

The steps to build and run unit tests:

cd build
cmake .. -DBUILD_TESTING=ON
cmake --build .
ctest

A complete to run integration tests for gpu might look like (assuming the SDK was built with ENABLE_NVML=ON):

cd build
ENABLE_NSCQ=OFF TEST_MODE="integration" TEST_DEVICES="gpu" NVAT_RIM_SERVICE_BASE_URL="https://rim.attestation-stg.nvidia.com" NVAT_OCSP_BASE_URL="https://ocsp.ndis-stg.nvidia.com" NVAT_NRAS_BASE_URL="https://nras.attestation-stg.nvidia.com" ctest -L unit -R AttestationTestCApi.*

From inside the development Docker container, you can execute all the commands above using:

test.sh

Additional arguments to ctest can be supplied directly to the script:

# only run RIM tests
test.sh -R Rim

Environment variables controlling testing behaviour#

  • TEST_MODE (“integration” or “unit”)

  • TEST_DEVICE (“gpu” or “nvswitch”)

Running unit-tests against build tree vs install tree#

Unit tests is an executable. To build it against the SDK in the build tree, the -DBUILD_TESTING=ON must be used. This will build the unit test executable along with the SDK and can be run via ctest.

To build it against the installed SDK, the above options must be omitted and the unit test executable must be built directly i.e

cd unit-tests
cmake -S . -B build
cmake --build build
cd build && ./nv-attestation-unit-tests

Building against the installed SDK lets us verify that the installation configuration in the SDK’s CMakeLists is correct.

The functionality to switch between building against the installed SDK or in the build tree is achieved by making use of the unit-test’s CMakeLists - it detects if its being run as sub-directory or is invoked as part of a standalone build.

Run all the automated tests#

ctest

Following are useful ctest commands to conditionally execute automated tests:

## Run only unit tests
ctest -L unit

## Only run unit tests with "Rim" in their name
ctest -L unit -R RIM

Coding standards#

  • Class names are camel cased

  • Function names are snake cased

  • Class member variables are prefixed with m_

  • Static global variables are prefixed with g_ (don’t use global variables)

  • All the code is written in nvattestation namespace

  • To implement functions that can return a result or an error, always use a pointer to return the result; nullptr indicates error and use ErrorStack to record the error.

  • Do not use raw pointers, always use smart pointers. If using an external library that returns raw pointers, the first step should to wrap it in a custom smart pointer with a custom deleter. Refer to nv_types.h. Better yet, the custom smart pointer can be directly constructed from the raw pointer.

  • When writing unit tests, if any data needs to be shared between unit tests, use a test fixture from googletest

Release process#

The SDK will be released via package managers for popular Linux distros. Initially only apt will be supported.

It will be versioned as major.minor.patch

There will be 2 packages released: libnvattest{SO_VERSION} and libnvattest-dev. The latter will always point to the latest version of the former, along with development files such as header files and cmake config files.

Debian packaging rules dictate that the SO_VERSION should be the same as the major version and is incremented every time a backwards incompatible change is made. see here (well they dictate that soversion should change every time a backwards incompatible change is made and it will be less confusing for the entire pipeline if we correspond the SO version with the major version as mentioned above)

For each release branch, the .deb files of the two packages will be created using cpack:

cpack -G DEB

These will be pushed to a staging apt repository maintained by us. Some tests will be run in the CI-CD pipeline which will pull the Debian files from the staging apt repository via apt install commands, make sure that they are installed properly and run some tests against the installed version and then they can be pushed to the production apt repository.

IMP: For every release, make sure to change the version number in the top level CMakeLists. If the major version changes, change the SOVERSION as well.