Add remaining documentation
[libcontrac.git] / src / contrac.c
index 47070d3..9532114 100644 (file)
@@ -1,25 +1,38 @@
-/** \ingroup contrac
+/** \ingroup KeyGeneration
  * @file
- * @author     David Llewellyn-Jones
+ * @author     David Llewellyn-Jones <david@flypig.co.uk>
  * @version    $(VERSION)
  *
  * @section LICENSE
  *
+ * Copyright David Llewellyn-Jones, 2020
+ * Released under the GPLv2.
  *
- *
- * @brief
+ * @brief Core Contact Tracing functionality
  * @section DESCRIPTION
  *
+ * This class provides the core Contact Tracing functionality. It provides an
+ * interfaces for:
+ * 1. Generating a random Tracing Key.
+ * 2. Generating a Daily Tracing Key based on the current day number.
+ * 3. Generating a Rolling Proximity Identifier based on the current time
+ *    interval number.
  *
+ * Values can be extracted and set in binary or base64 format.
  *
  */
 
+/** \addtogroup KeyGeneration
+ *  @{
+ */
+
 // Includes
 
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 #include <stddef.h>
+#include <time.h>
 
 #include <openssl/crypto.h>
 #include <openssl/rand.h>
 
 // Defines
 
+/**
+ * A mask used internally with the status flags.
+ * 
+ * When set the flag indicates that the Tracing Key has been correctly
+ * initialised.
+ */
 #define STATUS_TK (1 << 0)
+
+/**
+ * A mask used internally with the status flags.
+ * 
+ * When set the flag indicates that the Daily Tracing Key has been correctly
+ * initialised.
+ */
 #define STATUS_DTK (1 << 1)
+
+/**
+ * A mask used internally with the status flags.
+ * 
+ * When set the flag indicates that the Rolling Proximity Identifier has been
+ * correctly initialised.
+ */
 #define STATUS_RPI (1 << 2)
 
+/**
+ * A mask used internally with the status flags.
+ * 
+ * When all of these flags are set it indicates that the structure is fully
+ * initialised.
+ * .
+ */
 #define STATUS_INITIALISED (STATUS_TK | STATUS_DTK | STATUS_RPI)
 // Structures
 
+/**
+ * @brief The core structure for storing Contact Tracing state.
+ *
+ * This is an opaque structure that contains the core state for the library.
+ * 
+ * This must be passed as the first parameter of every non-static function.
+ * 
+ * The structure typedef is in contrac.h
+ */
 struct _Contrac {
        // Tracing key
        unsigned char tk[TK_SIZE];
@@ -48,9 +97,6 @@ struct _Contrac {
        // Rolling proximity identifier
        Rpi * rpi;
 
-       uint32_t day_number;
-       uint8_t time_interval_number;
-       
        uint32_t status;
 };
 
@@ -58,6 +104,11 @@ struct _Contrac {
 
 // Function definitions
 
+/**
+ * Creates a new instance of the class.
+ *
+ * @return The newly created object.
+ */
 Contrac * contrac_new() {
        Contrac * data;
 
@@ -68,6 +119,11 @@ Contrac * contrac_new() {
        return data;
 }
 
+/**
+ * Deletes an instance of the class, freeing up the memory allocated to it.
+ *
+ * @param data The instance to free.
+ */
 void contrac_delete(Contrac * data) {
        if (data) {
                dtk_delete(data->dtk);
@@ -80,6 +136,15 @@ void contrac_delete(Contrac * data) {
        }
 }
 
+/**
+ * Generates a random Contact Tracing Key.
+ *
+ * The operation may fail under certain circumstances, such as there being
+ * insufficient entropy in the system to guarantee a random result.
+ *
+ * @param data The context object to work with.
+ * @return true if the operation completed successfully, false otherwise.
+ */
 bool contrac_generate_tracing_key(Contrac * data) {
        int result;
 
@@ -96,23 +161,63 @@ bool contrac_generate_tracing_key(Contrac * data) {
        return (result == 1);
 }
 
+/**
+ * Sets the current day number.
+ *
+ * This will result in a new Daily Tracing Key being generated based on the
+ * day provided. If neither the Tracing Key nor the day have changed, the DTK
+ * will remain the same.
+ *
+ * The day number is calculated as:
+ *     (Number of Seconds since Epoch) / (60 * 60 * 24)
+ * 
+ * Which can be calculated from the current epoch using the
+ * epoch_to_day_number() function.
+ *
+ * The operation may fail if a Tracing Key has yet to be configured.
+ *
+ * @param data The context object to work with.
+ * @param day_number The day number used to generate the DTK.
+ * @return true if the operation completed successfully, false otherwise.
+ */
 bool contrac_set_day_number(Contrac * data, uint32_t day_number) {
        bool result;
 
        result = ((data->status & STATUS_TK) != 0);
 
        if (result) {
-               result = contrac_generate_daily_key(data->dtk, data, day_number);
+               result = dtk_generate_daily_key(data->dtk, data, day_number);
        }
 
        if (result) {
-               data->day_number = day_number;
                data->status |= STATUS_DTK;
        }
        
        return result;
 }
 
+/**
+ * Sets the current time interval number.
+ *
+ * This will result in a new Rolling Proximity Idetifier being generated based 
+ * on the time interval number. If none of the Tracing Key, day nor time 
+ * interval have changed, the RPI will stay the same.
+ *
+ * The time interval number is calculated as:
+ *     (Seconds Since Start of DayNumber) / (60 * 10)
+ *
+ * and must fall in the interval [0, 143].
+ *
+ * It can be calculated from the current epoch using the
+ * epoch_to_time_interval_number() function.
+ *
+ * The operation may fail if a Tracing Key or Daily Tracing Key have yet to be
+ * configured.
+ *
+ * @param data The context object to work with.
+ * @param time_interval_number The time interval number to set.
+ * @return true if the operation completed successfully, false otherwise.
+ */
 bool contrac_set_time_interval_number(Contrac * data, uint8_t time_interval_number) {
        bool result;
 
@@ -123,27 +228,128 @@ bool contrac_set_time_interval_number(Contrac * data, uint8_t time_interval_numb
        }
 
        if (result) {
-               data->time_interval_number = time_interval_number;
                data->status |= STATUS_RPI;
        }
        
        return result;
 }
 
+/**
+ * Gets whether the internal state has been fully configured or not.
+ *
+ * The internal state must be fully configured before a Daily Tracing Key or
+ * Rolling Proximity Identifier can be calculated. This function returns whether
+ * it is in this state or not.
+ *
+ * In order to fully configure the structure, a Tracing Key must either be
+ * generated using contrac_generate_tracing_key(), or set using either
+ * contrac_set_tracing_key() or contrac_set_tracing_key_base64().
+ *
+ * In addition the day number and time interval number must be set using
+ * contrac_set_day_number() and contrac_set_time_interval_number() respectively.
+ * 
+ * Alternatively these can be set automatically based on the current time using
+ * contrac_update_current_time().
+ *
+ * @param data The context object to work with.
+ * @return true if the state is fully initialised, false otherwise.
+ */
 bool contrac_get_initialised(Contrac const * data) {
        return ((data->status & STATUS_INITIALISED) == STATUS_INITIALISED);
 }
 
+/**
+ * Gets the current day number.
+ *
+ * Gets the current day number used to generate the most recent DTK.
+ *
+ * The day number is calculated as:
+ *     (Number of Seconds since Epoch) / (60 * 60 * 24)
+ * 
+ * Which can be caluclated from the current epoch using the
+ * epoch_to_day_number() function.
+ *
+ * @param data The context object to work with.
+ * @return The day number most recently used to generate the DTK.
+ */
+uint32_t contrac_get_day_number(Contrac * data) {
+       return dtk_get_day_number(data->dtk);
+}
+
+/**
+ * Gets the current time interval number.
+ *
+ * Gets the current time interval number used to generate the most recent RPI.
+ *
+ * The time interval number is calculated as:
+ *     (Seconds Since Start of DayNumber) / (60 * 10)
+ *
+ * and must fall in the interval [0, 143].
+ *
+ * It can be caluclated from the current epoch using the
+ * epoch_to_time_interval_number() function.
+ *
+ * @param data The context object to work with.
+ * @return The time interval number most recently used to generate the RPI.
+ */
+uint8_t contrac_get_time_interval_number(Contrac * data) {
+       return rpi_get_time_interval_number(data->rpi);
+}
+
+/**
+ * Sets the Tracing Key for the device in binary format.
+ *
+ * When first configuring a system, the Tracing Key must be generated
+ * randomly, e.g. using contrac_generate_tracing_key().
+ *
+ * However, on future runs it's important that the Tracing Key stays the same.
+ * In this case the key can be restored using this function.
+ *
+ * The tracing_key buffer passed in must contain exactly TK_SIZE (32) bytes of
+ * data.It doen't have to be null terminated.
+ *
+ * @param data The context object to work with.
+ * @param tracing_key The Tracing Key to set in binary format.
+ */
 void contrac_set_tracing_key(Contrac * data, unsigned char const * tracing_key) {
        memcpy(data->tk, tracing_key, TK_SIZE);
        data->status |= STATUS_TK;
 }
 
-const unsigned char * contrac_get_tracing_key(Contrac const * data) {
+/**
+ * Gets the Tracing Key for the device in binary format.
+ *
+ * This allows the Tracing Key to be extracted. The Tracing Key should be kept
+ * secret (to maintain privacy), however it still may need to be extracted, for
+ * example so it can be saved in persistent storage between runs.
+ *
+ * The buffer returned will contain exactly TK_SIZE (32) bytes of data in binary
+ * format. This may therefore contain null bytes, and the buffer will not 
+ * necessarily be null terminated. Future operations may cause the data to
+ * change, so the caller should make a copy of the buffer rather than keeping
+ * the pointer to it.
+ *
+ * @param data The context object to work with.
+ * @return The Tracing Key in binary format, not null terminated.
+ */
+unsigned char const * contrac_get_tracing_key(Contrac const * data) {
        return data->tk;
 }
 
-// base64 buffer must be at least 45 bytes (TK_SIZE_BASE64 + 1)
+/**
+ * Gets the Tracing Key for the device in base64 format.
+ *
+ * This allows the Tracing Key to be extracted. The Tracing Key should be kept
+ * secret (to maintain privacy), however it still may need to be extracted, for
+ * example so it can be saved in persistent storage between runs.
+ *
+ * The buffer provided must be at least TK_SIZE_BAS64 + 1 (45) bytes long and
+ * will be filled out with the Tracing Key in base64 format (TK_SIZE_BAS64
+ * bytes) followed by a null terminator (1 byte).
+ *
+ * @param data The context object to work with.
+ * @param base64 A buffer of at least TK_SIZE_BAS64 + 1 bytes for the result.
+ */
 void contrac_get_tracing_key_base64(Contrac const * data, char * base64) {
        size_t size = TK_SIZE_BASE64 + 1;
        base64_encode_binary_to_base64(data->tk, TK_SIZE, (unsigned char *)base64, &size);
@@ -153,7 +359,22 @@ void contrac_get_tracing_key_base64(Contrac const * data, char * base64) {
        }
 }
 
-// tracing_key input must be 44 bytes long
+/**
+ * Sets the Tracing Key for the device in base64 format.
+ *
+ * When first configuring a system, the Tracing Key must be generated
+ * randomly, e.g. using contrac_generate_tracing_key().
+ *
+ * However, on future runs it's important that the Tracing Key stays the same.
+ * In this case the key can be restored using this function.
+ *
+ * The tracing_key buffer passed in must contain exactly TK_SIZE_BASE64 (44)
+ * bytes of base64-encoded data. It can be null terminated, but doesn't need to
+ * be.
+ *
+ * @param data The context object to work with.
+ * @param tracing_key The Tracing Key to set in base64 format.
+ */
 bool contrac_set_tracing_key_base64(Contrac * data, char const * tracing_key) {
        bool result = true;
        unsigned char tk[TK_SIZE];
@@ -181,11 +402,42 @@ bool contrac_set_tracing_key_base64(Contrac * data, char const * tracing_key) {
        return result;
 }
 
-const unsigned char * contrac_get_daily_key(Contrac const * data) {
+/**
+ * Gets the Daily Tracing Key for the device in binary format.
+ *
+ * This allows the Daily Tracing Key to be extracted. The Daily Tracing Key
+ * should be kept secret (to maintain privacy) until a positive test is
+ * confirmed, at which point the user may choose to upload the key to a
+ * Diagnosis Server, so that others can be notified.
+ *
+ * The buffer returned will contain exactly DTK_SIZE (16) bytes of data in
+ * binary format. This may therefore contain null bytes, and the buffer will not 
+ * necessarily be null terminated. Future operations may cause the data to
+ * change, so the caller should make a copy of the buffer rather than keeping
+ * the pointer to it.
+ *
+ * @param data The context object to work with.
+ * @return The Daily Tracing Key in binary format, not null terminated.
+ */
+unsigned char const * contrac_get_daily_key(Contrac const * data) {
        return dtk_get_daily_key(data->dtk);
 }
 
-// base64 buffer must be at least 25 bytes (DTK_SIZE_BASE64 + 1)
+/**
+ * Gets the Daily Tracing Key for the device in base64 format.
+ *
+ * This allows the Daily Tracing Key to be extracted. The Daily Tracing Key
+ * should be kept secret (to maintain privacy) until a positive test is
+ * confirmed, at which point the user may choose to upload the key to a
+ * Diagnosis Server, so that others can be notified.
+ *
+ * The buffer provided must be at least DTK_SIZE_BASE64 + 1 (25) bytes long and
+ * will be filled out with the Tracing Key in base64 format (DTK_SIZE_BASE64
+ * bytes) followed by a null terminator (1 byte).
+ *
+ * @param data The context object to work with.
+ * @param base64 A buffer of at least DTK_SIZE_BASE64 + 1 bytes for the result.
+ */
 void contrac_get_daily_key_base64(Contrac const * data, char * base64) {
        size_t size = DTK_SIZE_BASE64 + 1;
        base64_encode_binary_to_base64(dtk_get_daily_key(data->dtk), DTK_SIZE, (unsigned char *)base64, &size);
@@ -195,11 +447,41 @@ void contrac_get_daily_key_base64(Contrac const * data, char * base64) {
        }
 }
 
-const unsigned char * contrac_get_proximity_id(Contrac const * data) {
+/**
+ * Gets the Rolling Proximity Identifier for the device in binary format.
+ *
+ * This allows the Rolling Proximity Identifier to be extracted. The Rolling
+ * Proximity Identifier is for broadcast to other devices using BLE and changes
+ * frequently.
+ *
+ * The buffer returned will contain exactly RPI_SIZE (16) bytes of data in
+ * binary format. This may therefore contain null bytes, and the buffer will not 
+ * necessarily be null terminated. Future operations may cause the data to
+ * change, so the caller should make a copy of the buffer rather than keeping
+ * the pointer to it.
+ *
+ * @param data The context object to work with.
+ * @return The Rolling Proximity Identifier in binary format, not null 
+ *         terminated.
+ */
+unsigned char const * contrac_get_proximity_id(Contrac const * data) {
        return rpi_get_proximity_id(data->rpi);
 }
 
-// base64 buffer must be at least 25 bytes (RPI_SIZE_BASE64 + 1)
+/**
+ * Gets the Rolling Proximity Identifier for the device in base64 format.
+ *
+ * This allows the Rolling Proximity Identifier to be extracted. The Rolling
+ * Proximity Identifier is for broadcast to other devices using BLE and changes
+ * frequently.
+ *
+ * The buffer provided must be at least RPI_SIZE_BASE64 + 1 (25) bytes long and
+ * will be filled out with the Tracing Key in base64 format (RPI_SIZE_BASE64
+ * bytes) followed by a null terminator (1 byte).
+ *
+ * @param data The context object to work with.
+ * @param base64 A buffer of at least RPI_SIZE_BASE64 + 1 bytes for the result.
+ */
 void contrac_get_proximity_id_base64(Contrac const * data, char * base64) {
        size_t size = RPI_SIZE_BASE64 + 1;
        base64_encode_binary_to_base64(rpi_get_proximity_id(data->rpi), RPI_SIZE, (unsigned char *)base64, &size);
@@ -209,8 +491,64 @@ void contrac_get_proximity_id_base64(Contrac const * data, char * base64) {
        }
 }
 
+/**
+ * Updates the Daily Tracing Key and Random Proxmity Identifer.
+ *
+ * The Daily Tracing Key changes every day, the Random Proximity Identifier
+ * changes every 10 minutes.
+ *
+ * Calling this function will update them both based on the current system
+ * time.
+ *
+ * Note that getting either the DTK or RPI does not cause an update, so if you
+ * want to get the correct values based on the time, it makes sense to call
+ * this function before getting them.
+ *
+ * The operation may fail if the state has not yet been fully initialised (for
+ * example if a Tracing Key has not yet been generated or set).
+ *
+ * @param data The context object to work with.
+ * @return true if the operation completed successfully, false otherwise.
+ */
+bool contrac_update_current_time(Contrac * data) {
+       bool result;
+       time_t epoch;
+       uint32_t dn_stored;
+       uint32_t dn_now;
+       uint8_t tn_stored;
+       uint8_t tn_now;
+
+       result = true;
 
+       if ((data->status & STATUS_TK) == 0) {
+               // No Tracing Key has been set, so generate a random key
+               result = contrac_generate_tracing_key(data);
+       }
+
+       epoch = time(NULL);
 
+       if (result) {
+               dn_now = epoch_to_day_number(epoch);
+               dn_stored = dtk_get_day_number(data->dtk);
 
+               // Only set again if uninitialised or the time has changed
+               if ((dn_now != dn_stored) || ((data->status & STATUS_DTK) == 0)) {
+                       result = contrac_set_day_number(data, dn_now);
+               }
+       }
+
+       if (result) {
+               tn_now = epoch_to_time_interval_number(epoch);
+               tn_stored= rpi_get_time_interval_number(data->rpi);
+
+               // Only set again if uninitialised or the time has changed
+               if ((tn_now != tn_stored) || (dn_now != dn_stored) || ((data->status & STATUS_RPI) == 0)) {
+                       result = contrac_set_time_interval_number(data, tn_now);
+               }
+       }
+
+       return result;
+}
 
+/** @} addtogroup KeyGeneration */