]> Kevux Git Server - fll/commitdiff
Feature: add f_random_array_shuffle() function to f_random().
authorKevin Day <kevin@kevux.org>
Tue, 12 Mar 2024 00:20:12 +0000 (19:20 -0500)
committerKevin Day <kevin@kevux.org>
Tue, 12 Mar 2024 00:20:12 +0000 (19:20 -0500)
Provide an array shuffle function using standard random function calls.

This is ideal for use in randomizing packet header order for increase network packet security.

Add appropriate unit tests.

15 files changed:
level_0/f_random/c/random.c
level_0/f_random/c/random.h
level_0/f_random/data/build/dependencies
level_0/f_random/data/build/settings
level_0/f_random/data/build/settings-tests
level_0/f_random/tests/unit/c/mock-random.c
level_0/f_random/tests/unit/c/mock-random.h
level_0/f_random/tests/unit/c/test-random-array_shuffle.c [new file with mode: 0644]
level_0/f_random/tests/unit/c/test-random-array_shuffle.h [new file with mode: 0644]
level_0/f_random/tests/unit/c/test-random-get.c
level_0/f_random/tests/unit/c/test-random-read.c
level_0/f_random/tests/unit/c/test-random-seed.c
level_0/f_random/tests/unit/c/test-random-seed_set.c
level_0/f_random/tests/unit/c/test-random.c
level_0/f_random/tests/unit/c/test-random.h

index 7fda8505566908cec422712150f6d037829e5405..7d13ef95901e35768f940466e2271143e13329dd 100644 (file)
@@ -4,6 +4,55 @@
 extern "C" {
 #endif
 
+#ifndef _di_f_random_array_shuffle_
+  f_status_t f_random_array_shuffle(const unsigned int flag, const f_number_unsigned_t total, const unsigned short size, f_string_dynamic_t * const cache, void * const indexes) {
+    #ifndef _di_level_0_parameter_checking_
+      if (!cache) return F_status_set_error(F_parameter);
+      if (!indexes) return F_status_set_error(F_parameter);
+    #endif // _di_level_0_parameter_checking_
+
+    if (!size) return F_status_set_error(F_too_small);
+    if (!total) return F_data_not;
+
+    {
+      cache->used = 0;
+
+      const f_status_t status = f_memory_array_increase_by(sizeof(f_number_unsigned_t) + 1, sizeof(f_char_t), (void **) &cache->string, &cache->used, &cache->size);
+      if (F_status_is_error(status)) return status;
+    }
+
+    f_number_unsigned_t index = 0;
+    int8_t old[size];
+
+    for (f_number_unsigned_t max = total - 1; max; --max) {
+
+      if (getrandom((void *) (cache->string), sizeof(f_number_unsigned_t), flag) == -1) {
+        if (errno == EAGAIN) return F_status_set_error(F_again);
+        if (errno == EFAULT) return F_status_set_error(F_buffer);
+        if (errno == EINTR) return F_status_set_error(F_interrupt);
+        if (errno == EINVAL) return F_status_set_error(F_parameter);
+        if (errno == ENOSYS) return F_status_set_error(F_support_not);
+
+        return F_status_set_error(F_failure);
+      }
+
+      index = *((f_number_unsigned_t *) cache->string) % (max + 1);
+
+      if (index == max) continue;
+
+      memset(cache->string, 0, sizeof(f_number_unsigned_t));
+      cache->string[cache->used] = 0;
+
+      // Casting (void *) to (uint8_t *) should result in an increment of size 1 and avoids problems with (void *) having arithmetic issues.
+      memmove((void *) old, (void *) (((uint8_t *) indexes) + (size * max)), size);
+      memmove((void *) (((uint8_t *) indexes) + (size * max)), (void *) (((uint8_t *) indexes) + (size * index)), size);
+      memmove((void *) (((uint8_t *) indexes) + (size * index)), (void *) old, size);
+    } // for
+
+    return F_okay;
+  }
+#endif // _di_f_random_array_shuffle_
+
 #ifndef _di_f_random_get_
   f_status_t f_random_get(long * const destination) {
     #ifndef _di_level_0_parameter_checking_
index cdb7c99d097293efa6dfc786c09c2f3672cab0f8..08a7c6acfaf253f9c8880ae929f562fc0f30e21e 100644 (file)
@@ -29,6 +29,64 @@ extern "C" {
 #endif
 
 /**
+ * Re-organize an array in a random order by swapping the values.
+ *
+ * The standard behavior of this is to call getrandom().
+ *
+ * The behavior of this function might also be replaced with calls to other libraries that are highly security specialized.
+ * If this is done, the meaning behind the flags passed should not be changed.
+ *
+ * This calls the Linux-specific getrandom() by default.
+ * If this is not available or pure POSIX is desired, then the implementation must handle accessing the /dev/random and /dev/urandom themselves (or something equivalent).
+ *
+ * @param flag
+ *   The flags to be passed to getrandom().
+ *
+ *   Flag bits:
+ *     - F_random_seed_flag_block_not_d: Does not block when getting the bits.
+ *     - F_random_seed_flag_source_d:    Random data is taken from the random source, such as /dev/random and not the urandom source.
+ * @param total
+ *   The total number of index positions to randomize.
+ * @param size
+ *   The type size of the array represented in indexes.
+ * @param cache
+ *   A string used when allocating space when calling the getrandom() or similar.
+ *   This changes the length of the cache array as needed.
+ *
+ *   Must not be NULL.
+ * @param indexes
+ *   The array in which the index positions that must be cast to a (void *).
+ *   The index positions are determined by size parameter.
+ *   The first memory address given represents index 0.
+ *   The values at each index position are swapped.
+ *
+ *   This must only be the address to a standard array structure, such as those represented by the syntax "[]".
+ *
+ *   Must not be NULL.
+ *
+ * @return
+ *   F_okay on success.
+ *   F_data_not on success, but total is 0.
+ *
+ *   F_again (with error bit) when in non-blocking mode but reading entropy source would block (such as when the entropy source is busy).
+ *   F_buffer (with error bit) if the address represented by the buffer is outside accessible address space.
+ *   F_interrupt (with error bit) if stopping due to an interrupt.
+ *   F_parameter (with error bit) if a parameter is invalid.
+ *   F_support_not (with error bit) if the running kernel does not support the getrandom() call.
+ *   F_too_small (with error bit) if size is too small.
+ *
+ *   Errors (with error bit) from: f_memory_array_increase_by().
+ *
+ * @see getrandom()
+ * @see memmove()
+ *
+ * @see f_memory_array_increase_by()
+ */
+#ifndef _di_f_random_array_shuffle_
+  extern f_status_t f_random_array_shuffle(const unsigned int flag, const f_number_unsigned_t total, const unsigned short size, f_string_dynamic_t * const cache, void * const indexes);
+#endif // _di_f_random_array_shuffle_
+
+/**
  * Set the destination string by reading from the random or urandom entropy source directly.
  *
  * The standard behavior of this is to call random().
@@ -45,6 +103,8 @@ extern "C" {
  *   F_okay on success.
  *
  *   F_parameter (with error bit) if a parameter is invalid.
+ *
+ * @see random()
  */
 #ifndef _di_f_random_get_
   extern f_status_t f_random_get(long * const destination);
@@ -91,6 +151,8 @@ extern "C" {
  *   F_interrupt (with error bit) if stopping due to an interrupt.
  *   F_parameter (with error bit) if a parameter is invalid.
  *   F_support_not (with error bit) if the running kernel does not support the getrandom() call.
+ *
+ * @see getrandom()
  */
 #ifndef _di_f_random_read_
   extern f_status_t f_random_read(const unsigned int flag, const f_number_unsigned_t length, f_string_t * const destination, ssize_t * const total);
@@ -144,6 +206,8 @@ extern "C" {
  *   F_okay on success.
  *
  *   F_parameter (with error bit) if a parameter is invalid.
+ *
+ * @see srandom()
  */
 #ifndef _di_f_random_seed_set_
   extern f_status_t f_random_seed_set(unsigned int seed);
index c06dda549b9fdbc94bc26f4ce8d1be439fbd81c4..03d7727f6e5960c6d0c1223ea864bd8325caf29b 100644 (file)
@@ -2,4 +2,6 @@
 
 f_type
 f_status
+f_memory
+f_type_array
 f_string
index 773a147147b5e88de2d9f6214c53d68dea88df28..f02f552f1f40b550258c2a548ac92666633ea67a 100644 (file)
@@ -32,7 +32,7 @@ build_indexer_arguments rcs
 build_language c
 
 build_libraries -lc
-build_libraries-individual -lf_memory -lf_string
+build_libraries-individual -lf_memory -lf_string -lf_type_array
 
 build_sources_library random.c
 
index 34eb91c0211af8c5134220f55e2fefbaa152be2f..368ba557605c6951f094905b7f0c8c42ced4c1df 100644 (file)
@@ -25,7 +25,7 @@ build_language c
 build_libraries -lc -lcmocka
 build_libraries-individual -lf_memory -lf_random
 
-build_sources_program test-random-get.c test-random-read.c test-random-seed.c test-random-seed_set.c
+build_sources_program test-random-array_shuffle.c test-random-get.c test-random-read.c test-random-seed.c test-random-seed_set.c
 build_sources_program test-random.c
 
 build_script no
index ec2dc0c755c37e48346ca1378525fa09dc9bd25b..d14599a8600f9a793295f74b3f35592fbcb0631c 100644 (file)
@@ -4,8 +4,14 @@
 extern "C" {
 #endif
 
+int mock_unwrap = 0;
+
 ssize_t __wrap_getrandom(void *buf, size_t buflen, unsigned int flags) {
 
+  if (mock_unwrap) {
+    return __real_getrandom(buf, buflen, flags);
+  }
+
   const bool failure = mock_type(bool);
 
   if (failure) {
@@ -20,10 +26,20 @@ ssize_t __wrap_getrandom(void *buf, size_t buflen, unsigned int flags) {
 }
 
 long __wrap_random(void) {
+
+  if (mock_unwrap) {
+    return __real_random();
+  }
+
   return mock_type(long);
 }
 
 void __wrap_srandom(unsigned seed) {
+
+  if (mock_unwrap) {
+    return __real_srandom(seed);
+  }
+
   // Do nothing.
 }
 
index 205f36642f842c4f03ceb205e849dd5c7c4c3f56..c87ca774e96fc9089ff7eac20689c7b40bfa87d1 100644 (file)
@@ -28,6 +28,12 @@ extern "C" {
 
 const static int mock_errno_generic = 32767;
 
+extern int mock_unwrap;
+
+extern ssize_t __real_getrandom(void *buf, size_t buflen, unsigned int flags);
+extern long __real_random(void);
+extern void __real_srandom(unsigned seed);
+
 extern ssize_t __wrap_getrandom(void *buf, size_t buflen, unsigned int flags);
 extern long __wrap_random(void);
 extern void __wrap_srandom(unsigned seed);
diff --git a/level_0/f_random/tests/unit/c/test-random-array_shuffle.c b/level_0/f_random/tests/unit/c/test-random-array_shuffle.c
new file mode 100644 (file)
index 0000000..25e6b4c
--- /dev/null
@@ -0,0 +1,182 @@
+#include "test-random.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void test__f_random_array_shuffle__fails(void **state) {
+
+  mock_unwrap = 0;
+
+  int errnos[] = {
+    EAGAIN,
+    EFAULT,
+    EINTR,
+    EINVAL,
+    ENOSYS,
+    mock_errno_generic,
+  };
+
+  f_status_t statuss[] = {
+    F_again,
+    F_buffer,
+    F_interrupt,
+    F_parameter,
+    F_support_not,
+    F_failure,
+  };
+
+  f_string_dynamic_t cache = f_string_dynamic_t_initialize;
+  f_number_unsigneds_t indexes = f_number_unsigneds_t_initialize;
+
+  f_number_unsigned_t indexes_array[5] = { 0, 1, 2, 3, 4 };
+  indexes.array = indexes_array;
+  indexes.used = 5;
+
+  for (uint8_t i = 0; i < 6; ++i) {
+
+    will_return(__wrap_getrandom, true);
+    will_return(__wrap_getrandom, errnos[i]);
+    will_return(__wrap_getrandom, -1);
+
+    const f_status_t status = f_random_array_shuffle(0, indexes.used, sizeof(f_number_unsigned_t), &cache, (void *) indexes.array);
+
+    if (status != F_status_set_error(statuss[i])) {
+      printf("[  ERROR   ] --- At index %d, first loop.\n", i);
+    }
+
+    assert_int_equal(status, F_status_set_error(statuss[i]));
+  } // for
+
+  if (cache.string) free(cache.string);
+}
+
+void test__f_random_array_shuffle__parameter_checking(void **state) {
+
+  mock_unwrap = 0;
+
+  {
+    const f_status_t status = f_random_array_shuffle(0, 0, 0, 0, 0);
+
+    assert_int_equal(status, F_status_set_error(F_parameter));
+  }
+
+  {
+    f_string_dynamic_t cache = f_string_dynamic_t_initialize;
+
+    const f_status_t status = f_random_array_shuffle(0, 0, 0, &cache, 0);
+
+    assert_int_equal(status, F_status_set_error(F_parameter));
+  }
+
+  {
+    f_number_unsigneds_t indexes = f_number_unsigneds_t_initialize;
+
+    const f_status_t status = f_random_array_shuffle(0, 0, 0, 0, (void *) indexes.array);
+
+    assert_int_equal(status, F_status_set_error(F_parameter));
+  }
+}
+
+void test__f_random_array_shuffle__returns_data_not(void **state) {
+
+  mock_unwrap = 0;
+
+  f_string_dynamic_t cache = f_string_dynamic_t_initialize;
+  f_number_unsigneds_t indexes = f_number_unsigneds_t_initialize;
+
+  f_number_unsigned_t indexes_array[5] = { 0, 1, 2, 3, 4 };
+  indexes.array = indexes_array;
+  indexes.used = 5;
+
+  {
+    const f_status_t status = f_random_array_shuffle(0, 0, sizeof(f_number_unsigned_t), &cache, (void *) indexes.array);
+    assert_int_equal(status, F_data_not);
+  }
+
+  if (cache.string) free(cache.string);
+}
+
+void test__f_random_array_shuffle__returns_too_small(void **state) {
+
+  mock_unwrap = 0;
+
+  f_string_dynamic_t cache = f_string_dynamic_t_initialize;
+  f_number_unsigneds_t indexes = f_number_unsigneds_t_initialize;
+
+  f_number_unsigned_t indexes_array[5] = { 0, 1, 2, 3, 4 };
+  indexes.array = indexes_array;
+  indexes.used = 5;
+
+  {
+    const f_status_t status = f_random_array_shuffle(0, indexes.used, 0, &cache, (void *) indexes.array);
+    assert_int_equal(status, F_status_set_error(F_too_small));
+  }
+
+  if (cache.string) free(cache.string);
+}
+
+void test__f_random_array_shuffle__works(void **state) {
+
+  mock_unwrap = 1;
+
+  f_string_dynamic_t cache = f_string_dynamic_t_initialize;
+  f_number_unsigneds_t indexes = f_number_unsigneds_t_initialize;
+
+  f_number_unsigned_t indexes_array[5] = { 0, 1, 2, 3, 4 };
+  indexes.array = indexes_array;
+  indexes.used = 5;
+
+  uint8_t unchanged = F_true;
+  const uint8_t tries = 20;
+
+  for (uint8_t i = 0; i < tries; ++i) {
+
+    const f_status_t status = f_random_array_shuffle(0, indexes.used, sizeof(f_number_unsigned_t), &cache, (void *) indexes.array);
+    assert_int_equal(status, F_okay);
+
+    uint8_t founds[] = { F_false, F_false, F_false, F_false, F_false };
+
+    for (uint8_t j = 0; j < indexes.used; ++j) {
+      founds[indexes.array[j]] = F_true;
+
+      if (indexes.array[j] > indexes.used) {
+        printf("[  ERROR   ] --- value (%lu) at index %d is too large.\n", indexes.array[j], j);
+      }
+
+      assert_true(indexes.array[j] <= indexes.used);
+    } // for
+
+    for (uint8_t j = 0; j < indexes.used; ++j) {
+
+      if (!founds[j]) {
+        printf("[  ERROR   ] --- Did not find index %d, value=%lu.\n", j, indexes.array[j]);
+      }
+
+      assert_int_equal(founds[j], F_true);
+    } // for
+
+    // Because things are random, the values could randomly be the same { 0, 1, 2, 3, 4 }.
+    // Try multiple times to get a different value and if a different order is found, then break.
+    for (uint8_t j = 0; j < indexes.used; ++j) {
+
+      if (indexes.array[j] != j) {
+        unchanged = F_false;
+
+        break;
+      }
+    } // for
+  } // for
+
+  if (unchanged) {
+    printf("[  ERROR   ] --- Failed to shuffle to a different result at least once in %d tries.\n", tries);
+  }
+
+  assert_false(unchanged);
+
+  if (cache.string) free(cache.string);
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
diff --git a/level_0/f_random/tests/unit/c/test-random-array_shuffle.h b/level_0/f_random/tests/unit/c/test-random-array_shuffle.h
new file mode 100644 (file)
index 0000000..2a7c61e
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ * FLL - Level 0
+ *
+ * Project: Random
+ * API Version: 0.7
+ * Licenses: lgpl-2.1-or-later
+ *
+ * Test the array shuffle function in the random project.
+ */
+#ifndef _TEST__F_random_array_shuffle_h
+#define _TEST__F_random_array_shuffle_h
+
+/**
+ * Test that the function fails.
+ *
+ * @see f_random_array_shuffle()
+ */
+extern void test__f_random_array_shuffle__fails(void **state);
+
+/**
+ * Test that the function correctly fails on invalid parameter.
+ *
+ * @see f_random_array_shuffle()
+ */
+extern void test__f_random_array_shuffle__parameter_checking(void **state);
+
+/**
+ * Test that the function returns F_data_not.
+ *
+ * @see f_random_array_shuffle()
+ */
+extern void test__f_random_array_shuffle__returns_data_not(void **state);
+
+/**
+ * Test that the function returns F_too_small (with error bit).
+ *
+ * @see f_random_array_shuffle()
+ */
+extern void test__f_random_array_shuffle__returns_too_small(void **state);
+
+/**
+ * Test that the function works.
+ *
+ * @see f_random_array_shuffle()
+ */
+extern void test__f_random_array_shuffle__works(void **state);
+
+#endif // _TEST__F_random_array_shuffle_h
index 3ad505b18a3562ca66424de358d32be41c2d3a8c..62a1d3d37052fd95aace17ea0c70d8226f7efb51 100644 (file)
@@ -1,5 +1,4 @@
 #include "test-random.h"
-#include "test-random-get.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -7,6 +6,8 @@ extern "C" {
 
 void test__f_random_get__works(void **state) {
 
+  mock_unwrap = 0;
+
   const long mocked_data = 1234;
 
   {
@@ -24,6 +25,8 @@ void test__f_random_get__works(void **state) {
 
 void test__f_random_get__parameter_checking(void **state) {
 
+  mock_unwrap = 0;
+
   {
     const f_status_t status = f_random_get(0);
 
index 9c33377e153685b5fa2bfe737ba472ed43b5056c..769c442ffb3401d17b4f632b482518b84cfd58e2 100644 (file)
@@ -1,5 +1,4 @@
 #include "test-random.h"
-#include "test-random-read.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -7,6 +6,8 @@ extern "C" {
 
 void test__f_random_read__fails(void **state) {
 
+  mock_unwrap = 0;
+
   int errnos[] = {
     EAGAIN,
     EFAULT,
@@ -37,6 +38,10 @@ void test__f_random_read__fails(void **state) {
 
     const f_status_t status = f_random_read(0, 4, &buffer_ptr, &total);
 
+    if (status != F_status_set_error(statuss[i])) {
+      printf("[  ERROR   ] --- At index %d, first loop.\n", i);
+    }
+
     assert_int_equal(status, F_status_set_error(statuss[i]));
   } // for
 
@@ -51,12 +56,18 @@ void test__f_random_read__fails(void **state) {
 
     const f_status_t status = f_random_read(0, 4, &buffer_ptr, 0);
 
+    if (status != F_status_set_error(statuss[i])) {
+      printf("[  ERROR   ] --- At index %d, second loop.\n", i);
+    }
+
     assert_int_equal(status, F_status_set_error(statuss[i]));
   } // for
 }
 
 void test__f_random_read__works(void **state) {
 
+  mock_unwrap = 0;
+
   const f_number_unsigned_t length = 4;
 
   {
@@ -103,6 +114,8 @@ void test__f_random_read__works(void **state) {
 
 void test__f_random_read__parameter_checking(void **state) {
 
+  mock_unwrap = 0;
+
   {
     const f_status_t status = f_random_read(0, 0, 0, 0);
 
index 2edf1a34395cedc99dbde0a305d691507627010c..a105f451fa97d8cc82efb76cec9ffada95bc405d 100644 (file)
@@ -1,5 +1,4 @@
 #include "test-random.h"
-#include "test-random-seed.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -7,6 +6,8 @@ extern "C" {
 
 void test__f_random_seed__fails(void **state) {
 
+  mock_unwrap = 0;
+
   int errnos[] = {
     EAGAIN,
     EFAULT,
@@ -33,12 +34,18 @@ void test__f_random_seed__fails(void **state) {
 
     const f_status_t status = f_random_seed(0);
 
+    if (status != F_status_set_error(statuss[i])) {
+      printf("[  ERROR   ] --- At index %d.\n", i);
+    }
+
     assert_int_equal(status, F_status_set_error(statuss[i]));
   } // for
 }
 
 void test__f_random_seed__works(void **state) {
 
+  mock_unwrap = 0;
+
   const f_number_unsigned_t length = 4;
 
   {
index 928c7bf947a6b80122a856b1438761a71646eff4..fffbc454b92b28352ea8a917cf5f469aeb3b3d52 100644 (file)
@@ -1,5 +1,4 @@
 #include "test-random.h"
-#include "test-random-seed_set.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -7,6 +6,8 @@ extern "C" {
 
 void test__f_random_seed_set__works(void **state) {
 
+  mock_unwrap = 0;
+
   {
     const f_status_t status = f_random_seed_set(1);
 
index f5d04b55d2907e40897b76a4c05da6af51b771e7..990dcc8413ad099dbef0aeef6ecfd8bc8cbe873c 100644 (file)
@@ -19,15 +19,20 @@ int setdown(void **state) {
 int main(void) {
 
   const struct CMUnitTest tests[] = {
+    cmocka_unit_test(test__f_random_array_shuffle__fails),
     cmocka_unit_test(test__f_random_read__fails),
     cmocka_unit_test(test__f_random_seed__fails),
 
+    cmocka_unit_test(test__f_random_array_shuffle__returns_data_not),
+    cmocka_unit_test(test__f_random_array_shuffle__returns_too_small),
+    cmocka_unit_test(test__f_random_array_shuffle__works),
     cmocka_unit_test(test__f_random_get__works),
     cmocka_unit_test(test__f_random_read__works),
     cmocka_unit_test(test__f_random_seed__works),
     cmocka_unit_test(test__f_random_seed_set__works),
 
     #ifndef _di_level_0_parameter_checking_
+      cmocka_unit_test(test__f_random_array_shuffle__parameter_checking),
       cmocka_unit_test(test__f_random_get__parameter_checking),
       cmocka_unit_test(test__f_random_read__parameter_checking),
 
index 546118d13e4e8fb9b281f848b50e97d5964e2e0a..35b0e4a70b036cef95c6c903336887e38dc7dafd 100644 (file)
@@ -26,6 +26,7 @@
 #include "mock-random.h"
 
 // Test includes.
+#include "test-random-array_shuffle.h"
 #include "test-random-get.h"
 #include "test-random-read.h"
 #include "test-random-seed.h"