DOCA SHA
This guide provides developer focused instructions on deploying and programming the DOCA SHA library.
The DOCA SHA library provides a flexible and unified API to leverage the SHA offload engine present in the NVIDIA® BlueField® DPU. For more information on SHA (secure hash standard algorithm), please review the FIPS 180-4 specifications.
SHA hardware acceleration engine is only available on the BlueField-2 DPU. Thus, the DOCA SHA library is not available for BlueField-3 DPUs.
SHA is commonly used in cryptography to generate a given hash value for a supplied input buffer. Depending on the SHA algorithm used, the message length may vary: Any length less than 264 bits for SHA-1, SHA-224, and SHA-256, or less than 2128 bits for SHA-384, SHA-512, SHA-512/224, and SHA-512/256. The resulting output from a SHA operation is called a message digest. The message digests range in length from 160 to 512 bits depending on the selected SHA algorithm. As expected from any cryptography algorithm, any change to a message will, with a very high probability, result in a different message digest and verification failure.
SHA is typically used with other cryptographic algorithms, such as digital signature algorithms and keyed-hash message authentication codes, or in the generation of random numbers.
The DOCA SHA library supports three SHA algorithms, SHA-1, SHA-256, and SHA-512, and aims to comply with the OpenSSL SHA implementation standard. It supports both one-shot and stateful SHA calculations.
One-shot means that the input message is composed of a single segment of data and, therefore, the SHA operation is completed in a single step (i.e., one single SHA engine enqueue and dequeue operation)
Stateful means that the input message is composed of many segments of data and, therefore, its SHA calculation needs more than one SHA enqueue and dequeue operation to finish. During any stateful operation, other SHA operations can also be executed.
DOCA SHA applications can run either on the host machine or directly on the crypto-enabled DPU target. As the DOCA SHA leverages the SHA engine, users must make sure it is enabled:
$ sudo mlxfwmanager
In the output, make sure that Crypto Enabled appears in the command output in the Description line.
The following diagram shows how the DOCA SHA library receives a message and outputs a message digest.
From an application level, the DOCA SHA library can be seen as a black box. DOCA SHA outputs a response regardless of the nature of the input message.
In a one-shot SHA situation, the single output is the correct message digest
In a stateful SHA situation, multiple outputs are expected corresponding to multiple inputs but only the last output is the correct message digest
In the following sections, additional details about the library API are provided. For more information, please refer to the NVIDIA DOCA Library APIs reference.
doca_sha_job_type
The enum defines six job types in the DOCA SHA library.
enum doca_sha_job_type {
DOCA_SHA_JOB_SHA1 = DOCA_ACTION_SHA_FIRST + 1,
DOCA_SHA_JOB_SHA256,
DOCA_SHA_JOB_SHA512,
DOCA_SHA_JOB_SHA1_PARTIAL,
DOCA_SHA_JOB_SHA256_PARTIAL,
DOCA_SHA_JOB_SHA512_PARTIAL,
};
DOCA_SHA_JOB_SHA1; DOCA_SHA_JOB_SHA256; DOCA_SHA_JOB_SHA512 – used to specify a one-shot SHA calculation
DOCA_SHA_JOB_SHA1_PARTIAL; DOCA_SHA_JOB_SHA256_PARTIAL; DOCA_SHA_JOB_SHA512_PARTIAL – used to specify a stateful SHA calculation
DOCA SHA Output Length Macro
These macros define the smallest SHA response buffer length corresponding to different job types.
#define DOCA_SHA1_BYTE_COUNT 20
#define DOCA_SHA256_BYTE_COUNT 32
#define DOCA_SHA512_BYTE_COUNT 64
DOCA_SHA1_BYTE_COUNT – number of message digest bytes for SHA1_PARTIAL and SHA1_PARTIAL
DOCA_SHA256_BYTE_COUNT – number of message digest bytes for SHA256_PARTIAL and SHA256_PARTIAL
DOCA_SHA512_BYTE_COUNT – number of message digest bytes for SHA512_PARTIAL and SHA512_PARTIAL
doca_sha_job_flags
The enum defines flags used for doca_sha_job construction.
enum doca_sha_job_flags {
DOCA_SHA_JOB_FLAGS_NONE = 0,
DOCA_SHA_JOB_FLAGS_SHA_PARTIAL_FINAL
};
DOCA_SHA_JOB_FLAGS_NONE – the default flag suitable for all SHA jobs
DOCA_SHA_JOB_FLAGS_SHA_PARTIAL_FINAL – signifies that the current input is the final segment of a whole stateful job
doca_sha_job
This is the DOCA SHA job definition, suitable for one-shot SHA job types, DOCA_ JOB_SHA1/256/512.
struct doca_sha_job {
struct doca_job base;
struct doca_buf *req_buf;
struct doca_buf *resp_buf;
uint64_t flags;
};
base – an opaque doca_job structure
req_buf – the doca_buf containing the input message
resp_buf – the doca_buf used for the output message digest
flags – the doca_sha_job_flags
doca_sha_partial_session
An opaque structure used in a stateful SHA job.
struct doca_sha_partial_session;
doca_sha_partial_job
This is the DOCA SHA job definition, suitable for stateful SHA job types, DOCA_JOB_SHA1/256/512_PARTIAL.
struct doca_sha_partial_job {
struct doca_sha_job sha_job;
struct doca_sha_partial_session *session;
};
sha_job – contain the fields for the input message, output message digest, and flags
session – contain the state information for a stateful SHA calculation
doca_sha
An opaque structure for DOCA SHA API.
struct doca_sha;
doca_sha_create
Before performing any SHA operation, it is essential to create a doca_sha object.
doca_error_t doca_sha_create(struct doca_sha **ctx);
ctx [in/out] – doca_sha object to be created
Returns – DOCA_SUCCESS on success, error code otherwise
doca_sha_destroy
Used to destroy a doca_sha object after a SHA operation is done:
doca_error_t doca_sha_destroy(struct doca_sha *ctx);
ctx [in] – doca_sha object to be destroyed; it is created by doca_sha_create()
Returns – DOCA_SUCCESS on success, error code otherwise
doca_sha_job_get_supported
Check whether a device can perform doca_sha jobs.
doca_error_t doca_sha_destroy(struct doca_sha *ctx);
devinfo [in] – a pointer to the doca_devinfo object
job_type [in] – doca_sha job type enum
Returns – DOCA_SUCCESS on success, error code otherwise
doca_sha_get_max_list_buf_num_elem
Get the maximum linked_list doca_buf count for the source buffer in a doca_sha job.
doca_error_t doca_sha_get_max_list_buf_num_elem(const struct doca_devinfo *devinfo, uint32_t *max_list_num_elem);
devinfo [in] – a pointer to the doca_devinfo object
max_list_num_elem [out] – maximum linked list doca_buf count
Returns – DOCA_SUCCESS on success, error code otherwise
doca_sha_get_max_src_buffer_size
Get the maximum buffer byte count for the source buffer in a doca_sha job.
doca_error_t doca_sha_get_max_src_buffer_size(const struct doca_devinfo *devinfo, uint64_t *max_buffer_size);
devinfo [in] – a pointer to the doca_devinfo object
max_buffer_size [out] – maximum buffer byte count
Returns – DOCA_SUCCESS on success, error code otherwise
doca_sha_get_min_dst_buffer_size
Get the minimum buffer byte count for the destination buffer in a doca_sha job.
doca_error_t doca_sha_get_max_src_buffer_size(const struct doca_devinfo *devinfo, uint64_t *max_buffer_size);
devinfo [in] – A pointer to the doca_devinfo object
job_type [in] – doca_sha job type enum
min_buffer_size [out] – Minimum buffer byte count
Returns – DOCA_SUCCESS on success, error code otherwise
doca_sha_get_hardware_supported
Check that a doca_sha engine is supported.
doca_error_t doca_sha_get_hardware_supported(const struct doca_devinfo *devinfo);
devinfo [in] – a pointer to the doca_devinfo object
Returns – DOCA_SUCCESS on success, error code otherwise
doca_sha_as_ctx
Convert a doca_sha object into a doca object:
struct doca_ctx *doca_sha_as_ctx(struct doca_sha *ctx);
ctx [in] – a pointer to the doca_sha object
doca_ctx [out] – a pointer to the doca object
Returns – a pointer to the doca object on success, NULL otherwise
doca_sha_partial_session_create
Before doing any stateful SHA calculation, it is necessary to create a doca_sha_partial_session object to keep the state information:
doca_error_t doca_sha_partial_session_create(
struct doca_sha *ctx,
struct doca_workq *workq,
struct doca_sha_partial_session **session);
ctx [in] – a pointer to the doca_sha object
workq [in] – a pointer to the doca_workq object
session [in/out] – a pointer to the doca_sha_partial_session object to be created
Returns – DOCA_SUCCESS on success, error code otherwise
doca_sha_partial_session_destroy
Free stateful SHA session resource:
doca_error_t doca_sha_partial_session_destroy(
struct doca_sha *ctx,
struct doca_workq *workq,
struct doca_sha_partial_session *session);
ctx [in] – a pointer to the doca_sha object
workq [in] – a pointer to the doca_workq object
session [in] – a pointer to the doca_sha_partial_session object to be freed
Returns – DOCA_SUCCESS on success, error code otherwise
doca_sha_partial_session_copy
Copy the stateful SHA session resource:
doca_error_t doca_sha_partial_session_copy(
struct doca_sha *ctx,
struct doca_workq *workq,
struct doca_sha_partial_session *from,
struct doca_sha_partial_session *to);
ctx [in] – a pointer to the doca_sha object
workq [in] – a pointer to the doca_workq object
from [in] – a pointer to the source doca_sha_partial_session object to be copied
to [out] – a pointer to the destination doca_sha_partial_session object
Returns – DOCA_SUCCESS on success, error code otherwise
Capabilities and Limitations
Supported SHA algorithms:
SHA1
SHA256
SHA512
Output message digest length:
20B for SHA1
32B for SHA256
64B for SHA512
Maximum single job size:
For one-shot SHA calculation, the input message size must be ≤ 231
For stateful SHA calculation, the accumulated input message size must be ≤ 231
Stateful SHA job length requirement:
For SHA1/256_PARTIAL, only the last segment allows its byte_count != multiple-of-64
For SHA512_PARTIAL, only the last segment allows its byte_count != multiple-of-128
Performing One-shot SHA Calculation
Construct a doca_sha_job:
struct doca_sha_job job = { .base.type = DOCA_SHA_JOB_SHA1, .req_buf = user_req_buf, .resp_buf = user_resp_buf, .flags = DOCA_SHA_JOB_FLAGS_NONE };
Submit the job until DOCA_SUCCESS is received:
In synchronous mode, we can use: ret = doca_workq_submit(workq, &job.base); if (ret != DOCA_SUCCESS) error_exit;
If doca_workq_submit() returns DOCA_ERROR_INVALID_VALUE, it means the job construction has a problem. If it returns DOCA_ERROR_BAD_STATE, it indicates a fatal internal error and the whole engine must be reinitialized.
In asynchronous mode, doca_workq_submit() may return DOCA_ERROR_NO_MEMORY. In that case, you must first call doca_workq_progress_retrieve() to receive a response so that the job resource can be freed, then retry calling doca_workq_submit().
Possible doca_workq_submit() return codes:
DOCA_SUCCESS
DOCA_ERROR_INVALID_VALUE
DOCA_ERROR_NO_MEMORY
DOCA_ERROR_BAD_STATE
To retrieve a job response until DOCA_SUCCESS is received:
while ((ret = doca_workq_progress_retrieve(workq, &event, DOCA_WORKQ_RETRIEVE_FLAGS_NONE)) == DOCA_ERROR_AGAIN); if (ret != DOCA_SUCCESS) error_exit;
If doca_workq_progress_retrieve() returns DOCA_ERROR_INVALID_VALUE it means invalid input is received. If it returns DOCA_ERROR_IO_FAILED, it signifies fatal internal error and the whole engine needs reinitialized.
Possible doca_workq_progress_retrieve() return codes:
DOCA_SUCCESS
DOCA_ERROR_INVALID_VALUE
DOCA_ERROR_NO_MEMORY
DOCA_ERROR_BAD_STATE
Performing Stateful SHA Calculation
This section describes the steps to finish a stateful SHA1 calculation, assuming the whole job is composed of three or more segments.
Obtain a doca_sha_partial_session:
doca_sha_partial_session *session; doca_sha_partial_session_create(ctx, workq, &session);
Construct a doca_sha_partial_job for the first segment:
struct doca_sha_partial_job job = { .sha_job.base.type = DOCA_SHA_JOB_SHA1_PARTIAL, .sha_job.req_buf = user_req_buf_of_1st_segment, .sha_job.resp_buf = user_resp_buf, .sha_job.flags = DOCA_SHA_JOB_FLAGS_NONE, .session = session, };
Submit the job for the first segment:
ret = doca_workq_submit(workq, &job.base); if (ret != DOCA_SUCCESS) error_exit;
Wait until first segment processing is done:
while ((ret = doca_workq_progress_retrieve(workq, &event, DOCA_WORKQ_RETRIEVE_FLAGS_NONE)) == DOCA_ERROR_AGAIN); if (ret != DOCA_SUCCESS) error_exit;
The purpose of this call is to make sure the first segment processing is finished before continuing to send the next segment, as it is necessary to sequentially process all segments for a correct message digest generation. The user_resp_buf at this moment contains garbage values.
For the second segment, repeat the previous three steps:
struct doca_sha_partial_job job = { .sha_job.base.type = DOCA_SHA_JOB_SHA1_PARTIAL, .sha_job.req_buf = user_req_buf_of_2nd_segment, .sha_job.resp_buf = user_resp_buf, .sha_job.flags = DOCA_SHA_JOB_FLAGS_NONE, .session = session, }; ret = doca_workq_submit(workq, &job.base); if (ret != DOCA_SUCCESS) error_exit; while ((ret = doca_workq_progress_retrieve(workq, &event, DOCA_WORKQ_RETRIEVE_FLAGS_NONE)) == DOCA_ERROR_AGAIN); if (ret != DOCA_SUCCESS) error_exit;
The purpose of this call is still to make sure the second segment processing is finished. The user user_resp_buf at this moment still contains garbage values.
All subsequent segments repeat the same process.
For the last segment, repeat the same process while setting the special flag for the last segment:
struct doca_sha_partial_job job = { .sha_job.base.type = DOCA_SHA_JOB_SHA1_PARTIAL, .sha_job.req_buf = user_req_buf_of_the_last_segment, .sha_job.resp_buf = user_resp_buf, .sha_job.flags = DOCA_SHA_JOB_FLAGS_SHA_PARTIAL_LAST, .session = session, }; ret = doca_workq_submit(workq, &job.base); if (ret != DOCA_SUCCESS) error_exit; while ((ret = doca_workq_progress_retrieve(workq, &event, DOCA_WORKQ_RETRIEVE_FLAGS_NONE)) == DOCA_ERROR_AGAIN); if (ret != DOCA_SUCCESS) error_exit;
After the DOCA_SUCCESS event of the last segment is received the processing of the whole job is done now. You can get the expected SHA message digest from the user_resp_buf now.
Release the session object:
doca_sha_partial_session_destroy(ctx, workq, session);
Notes:
Before submitting the first segment, call doca_sha_partial_session_create() to obtain a "session" object.
During the whole process, make sure to use the same doca_sha_partial_session object used for all segments of the entire job.
If a session object is released before the whole stateful SHA is finished, or if different objects are used for a stateful SHA, the job submission may fail due to job validity check failure. Even the job submission succeeds, a wrong SHA message digest is expected.
The session resource is limited, it is the user's responsibility to properly call doca_sha_partial_session_destroy() to make sure all allocated session objects are released.
For the last segment, the DOCA_SHA_JOB_FLAGS_SHA_PARTIAL_FINAL flag must be set.
If DOCA_SHA_JOB_FLAGS_SHA_PARTIAL_FINAL is not properly set, the engine assumes an intermediate partial SHA calculation and returns an invalid SHA message digest. As only the user knows when the last segment arrives, it is their responsibility to properly set this flag.
Make sure the SHA_PARTIAL segment length requirements are In this example, the first and second segments' byte count must be a multiple of 64. Otherwise, the job submission may fail due to job validity check failure.
Using Session Copy
This section describes the steps for utilizing session_copy() to reduce the stateful SHA calculation overhead.
The example assumes there are two whole jobs, job_0 and job_1, where job_0 is composed of several segments, {header_segment, job_0's other segments}, and job_1 is composed of {header_segment, job_1' other segments}.
Obtain two doca_sha_partial_session:
doca_sha_partial_session *session_0; doca_sha_partial_session_create(ctx, workq, &session_0); doca_sha_partial_session *session_1; doca_sha_partial_session_create(ctx, workq, &session_1);
Construct a doca_sha_partial_job for the header_segment:
struct doca_sha_partial_job job = { .sha_job.base.type = DOCA_SHA_JOB_SHA1_PARTIAL, .sha_job.req_buf = user_req_buf_of_header_segment, .sha_job.resp_buf = user_resp_buf, .sha_job.flags = DOCA_SHA_JOB_FLAGS_NONE, .session = session_0, };
Submit the header_segment of job_0.
ret = doca_workq_submit(workq, &job.base); if (ret != DOCA_SUCCESS) error_exit;
Wait until the processing of header_segment is done:
while ((ret = doca_workq_progress_retrieve(workq, &event, DOCA_WORKQ_RETRIEVE_FLAGS_NONE)) == DOCA_ERROR_AGAIN); if (ret != DOCA_SUCCESS) error_exit;
Perform the session copy so that job_1 does not need to calculate its header_segment:
doca_sha_partial_session_copy(ctx, workq, session_0, session_1);
Continue to calculate job_0 and job_1's other segments until final segment using normal partial_sha calculation process.
struct doca_sha_partial_job job = { .sha_job.base.type = DOCA_SHA_JOB_SHA1_PARTIAL, .sha_job.req_buf = user_req_buf_of_job_0_other_segment, .sha_job.resp_buf = user_resp_buf, .sha_job.flags = DOCA_SHA_JOB_FLAGS_NONE, .session = session_0, }; ret = doca_workq_submit(workq, &job.base); if (ret != DOCA_SUCCESS) error_exit; while ((ret = doca_workq_progress_retrieve(workq, &event, DOCA_WORKQ_RETRIEVE_FLAGS_NONE)) == DOCA_ERROR_AGAIN); if (ret != DOCA_SUCCESS) error_exit; struct doca_sha_partial_job job = { .sha_job.base.type = DOCA_SHA_JOB_SHA1_PARTIAL, .sha_job.req_buf = user_req_buf_of_job_1_other_segment, .sha_job.resp_buf = user_resp_buf, .sha_job.flags = DOCA_SHA_JOB_FLAGS_NONE, .session = session_1, }; ret = doca_workq_submit(workq, &job.base); if (ret != DOCA_SUCCESS) error_exit; while ((ret = doca_workq_progress_retrieve(workq, &event, DOCA_WORKQ_RETRIEVE_FLAGS_NONE)) == DOCA_ERROR_AGAIN); if (ret != DOCA_SUCCESS) error_exit;
Release the session object:
doca_sha_partial_session_destroy(ctx, workq, session_0); doca_sha_partial_session_destroy(ctx, workq, session_1);
This section provides instructions on how to test the DOCA SHA library:
Enable DOCA SHA test apps and build.
user@machine:/home/user$ cd ${YOUR-PATH}/doca user@machine:/home/user$ meson setup build user@machine:/home/user$ cd build user@machine:/home/user/doca/build$ meson configure -Dunit_test_lib_sha=true user@machine:/home/user/doca/build$ ninja
Run the test app test_doca_sha_lite. This test app shows how to do the simplest one-shot SHA calculation. It sends three messages and receives three massage digests for all SHA1, SHA256, and SHA512.
user@machine:/home/user$ cd libs/doca_sha/src/unit_test/ user@machine:/home/user$ ./test_doca_sha_lite --pci_addr 03:00.0
Run the test app test_doca_sha_benchmark. This test app can be used to test throughput and more complex one-shot SHA calculation cases.
Case 1: Test throughput of SHA1 with a 4096-byte input message.
user@machine:/home/user$ ./test_doca_sha_benchmark --pci_addr af:00.0 --data_file test_files/sha-input-4kbyte.txt --nb_iteration 1000000 --sha_type 0
Case 2: Test throughput of SHA256 with an 8192-byte input message.
user@machine:/home/user$ ./test_doca_sha_benchmark --pci_addr af:00.0 --data_file test_files/sha-input-8kbyte.txt --nb_iteration 1000000 --sha_type 1
Case 3: Calculate a SHA512 message digest with a random 16-byte input message.
user@machine:/home/user$ ./test_doca_sha_benchmark --pci_addr af:00.0 --nb_iteratoin 1 --use_random_data --data_byte_count 16 --sha_type 2
Case 4: Calculate a SHA1 message digest with a 1-gigabyte (230) input message.
user@machine:/home/user$ ./test_doca_sha_benchmark --pci_addr af:00.0 --data_file test_files/sha-input-1Gbyte.txt --nb_iteration 1 --sha_type 0
Run the test app test_doca_sha_partial. This test app can be used to show how to perform a stateful SHA calculation and the session_copy function.
Case 1: Calculate a SHA1 message digest of two 129-byte messages. Each message is composed of three segments, the first and second segments are 64 bytes, the third segment is 1 byte.
user@machine:/home/user$ ./test_doca_sha_partial --pci_addr af:00.0 --job_byte_count 129 --segment_byte_count 64 --job_count 2 --mode 0 --sha_type 0
Case 2: Calculate a SHA256 message digest of a 2-gigabyte (231) message. This message is composed of 16 segments, each segment is 128 megabytes (227).
user@machine:/home/user$ ./test_doca_sha_partial --pci_addr af:00.0 --job_byte_count 134217728 --segment_byte_count 134217728 --job_count 1 --mode 3 --nb_sha_partial 16 --sha_type 1
Case 3: Calculate four SHA512 message digests of using the session_copy function. All the four jobs share the same job length, 129 bytes, and the same head segment, 128 bytes.
user@machine:/home/user$ ./test_doca_sha_partial --pci_addr af:00.0 --job_byte_count 129 --segment_byte_count 128 --job_count 4 --mode 4 --sha_type 2
This section describes SHA samples based on the DOCA SHA library. These samples illustrate how to use the DOCA SHA API to calculate secure hash algorithm on a given message.
Running the Sample
Refer to the following documents:
NVIDIA DOCA Installation Guide for Linux for details on how to install BlueField-related software.
NVIDIA DOCA Troubleshooting Guide for any issue you may encounter with the installation, compilation, or execution of DOCA samples.
To build a given sample:
cd /opt/mellanox/doca/samples/doca_sha/<sample_name> meson /tmp/build ninja -C /tmp/build
NoteThe binary doca_<sample_name> will be created under /tmp/build/.
Sample (e.g., doca_sha_create) usage:
Usage: doca_sha_create [DOCA Flags] [Program Flags] DOCA Flags: -h, --help Print a help synopsis -v, --version Print program version information -l, --log-level Set the (numeric) log level
for
the program <10
=DISABLE,20
=CRITICAL,30
=ERROR,40
=WARNING,50
=INFO,60
=DEBUG,70
=TRACE> --sdk-log-level Set the SDK (numeric) log levelfor
the program <10
=DISABLE,20
=CRITICAL,30
=ERROR,40
=WARNING,50
=INFO,60
=DEBUG,70
=TRACE> -j, --json <path> Parse all command flags from an input json file Program Flags: -d, --data user dataFor additional information per sample, use the -h option:
/tmp/build/doca_<sample_name> -h
Samples
SHA Create
This sample illustrates how to send A SHA job and retrieve the result.
The sample logic includes:
Locating a DOCA device.
Initializing the required DOCA core structures.
Populating DOCA memory map with two relevant buffers; one for the source data and one for the result.
Allocating the element in DOCA buffer inventory for each buffer.
Initializing a DOCA SHA job object.
Submitting the SHA job into work queue.
Retrieving the SHA job from the queue once it is done.
Printing the job result.
Destroying all SHA and DOCA core structures.
References:
/opt/mellanox/doca/samples/doca_sha/sha_create/sha_create_sample.c
/opt/mellanox/doca/samples/doca_sha/sha_create/sha_create_main.c
/opt/mellanox/doca/samples/doca_sha/sha_create/meson.build
SHA Partial Create
This sample illustrates how to send partial SHA jobs and retrieve the result. Each job source buffer (except the final) will be 64 bytes.
The sample logic includes:
Locating a DOCA device.
Initializing the required DOCA core structures.
Initializing a partial session for all the jobs.
Populating DOCA memory map with two relevant buffers; one for the source data and one for the result.
Allocating the element in DOCA buffer inventory for the result buffer.
Calculating total jobs; user data length divided by 64.
For each job:
Allocating the element in DOCA buffer inventory for the relevant part in the source buffer.
Initializing the DOCA SHA job object. If it is the final job, send DOCA_SHA_JOB_FLAGS_SHA_PARTIAL_FINAL flag.
Submitting SHA job into work queue.
Retrieving SHA job from the queue once it is done.
Printing the final job result.
Destroying all SHA and DOCA core structures.
References:
/opt/mellanox/doca/samples/doca_sha/sha_partial_create/sha_partial_create_sample.c
/opt/mellanox/doca/samples/doca_sha/sha_partial_create/sha_partial_create_main.c
/opt/mellanox/doca/samples/doca_sha/sha_partial_create/meson.build