]> 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 7fda850..7d13ef9 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 cdb7c99..08a7c6a 100644 (file)
 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.
  *
@@ -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 773a147..f02f552 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 34eb91c..368ba55 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 ec2dc0c..d14599a 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 205f366..c87ca77 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 3ad505b..62a1d3d 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 9c33377..769c442 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 2edf1a3..a105f45 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 928c7bf..fffbc45 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 f5d04b5..990dcc8 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 546118d..35b0e4a 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"