test_storage: further refactoring, added test functionality, and TESTING.md to describe testing approach

This commit is contained in:
Ian Clowes 2020-06-07 16:02:56 +01:00 committed by Polynomialdivision
parent bd35961de8
commit 292ccb01f2
9 changed files with 513 additions and 117 deletions

111
TESTING.md Normal file
View file

@ -0,0 +1,111 @@
# Testing
[NB: The current content is aspirational. Not fully implemented yet.]
## Overview
The core purpose of DAWN is the processing of information relating
to the set of access points (AP) and clients (STA - meaning stations in 802.11
vernacular) that form the wi-fi network under DAWN's management.
The required data storage and processing capabilites are generally held in the 'storage'
part of DAWN's source code tree. The remaining parts are mainly for
interfacing with resources of the AP environment rhat provide updated information and allow
DAWN to indicate to stations what they should do next following processing of the latest data.
Specifically, this means components such as iwinfo, ubus and uci that are commonly found on an
AP running OpenWRT variant of Linux.
## Testing Approach
The principal focus of DAWN's test harness is on these storage and processing components. This is achieved by
having a build target named test_storage which builds these parts
without the environment interaction dependencies. This "test harness" can then be executed on the
build host (e.g. a Linux desktop development environment) or on the target AP.
To configure the imaginary AP (SUT, or "system under test" in testing parlance)
a script that mimics many of the messages from ubus and other sources is created to represent
the simulated network by describing APs, connected STAs and DAWN's own configuration parameters.
The data evaluation algorithms are then executed, and resultant outputs to stations are simulated as
text output.
For example consider a simple network of two AP with 2 stations. The following (simplifed) script is
used to configure what one AP will be aware of:
CONFIG RSSI=10db
SELF 01:01:01:01:01:01
AP 02:02:02:02:02:02
CONNECT 99:99:99:99:99:99 01:01:01:01:01:01 -78dB
HEARING 99:99:99:99:99:99 02:02:02:02:02:02 -65dB
CONNECT 88:88:88:88:88:88 01:01:01:01:01:01 -65dB
HEARING 88:88:88:88:88:88 02:02:02:02:02:02 -65dB
KICK CONSUME
KICK
This means our test AP has the BSSID 01:..., and there is another AP in the network with BSSID 02:....
Two stations with MAC 99:... and 88:... are in the network, both connected to AP01:... but also
able to see AP02:.... The dB values indicate RSSI levels, and will be evalutated to determine if stations
are connected to an appropriate AP. We'ed also configured
AP01:... to have an RSSI transition threshold of 10dB. When "kicking evaluation" is performed
STA99:... can improve its RSSI by over 10dB by switching to AP02:..., so will be instructed to do so,
resulting in the test actions:
REMOVE 99:99:99:99:99:99 01:01:01:01:01:01
CONNECT 99:99:99:99:99:99 02:02:02:02:02:02 -65dB
Note that this is also valid input to the test_harness, and the parameter CONSUME
on the KICK action will cause it to be reinjested for consideration when the
second KICK action is evaluated.
## Types of Testing
Three main areas of testing are performed by the supplied test scripts:
* Data management: Ensuring data is stored correctly and soes not cause buffer overruns, etc
* Algorithm functionality: Review the outcomes of evaluation for somple and complex network data
* Scalability: Evaluate the ability of DAWN to sacle linearly to hundreds and thousands of AP and STA
A number of scenarios are defined for each type of testing, along with scripts to execute them.
### Data Management Scenarios
Data management scenarios excercise DAWN's internal data structures by filling and emptying them to ensure no
overflow conditions occur, and where appropriate that sorting is applied correctly. They are independent
of any functional testing, so each data structure is excercised alone in a way that would never occur in
real usage.
#### Test DM001: AP list
Fill, print, empty and print
#### Test DM002: Client list
Fill, print, empty and print
#### Test DM001: MAC list
Fill, print, empty and print
#### Test DM001: Hearing list
Fill, print, empty and print
### Algorithm Scenarios
Algorithm scenarios are used to ensure that DAWN creates the intended outputs to manage which stations
connect to APs. They require more intricate crafting of the scripted synthetic data to represent the
input to DAWN's evaluation algortithms to blend situation data with decision metrics.
#### Test AL001: 2+2 Stable
Two AP with two stations connected to AP1, and will remain there.
#### Test AL002: 2+2 Cross
Two AP with two stations connected to AP1, both switching to AP2.
#### Test AL003: 2+1+1 Cross
Two AP with one station connected each, and will cross to the other.
#### Test AL003: Load balance
Three AP with 40 stations connected to AP1, all with same metrics. DAWN should balance load to other APs.
### Scalability Scenarios
Scalability scenarios explore how well DAWN can function in environments with many APs and clients.
Using commodity equipment this might mean tens of APs with a total of a thousand or so connected stations.
Subject to use of appropriate hardware (eg small PC rather than SoC platform) there is an aspiration for
DAWN to be stable while managing hundreds of APs each with tens of connected stations, allowing networks
to support 10,000 or more concurrent stations.
Scalability requires a number of design aspects to function together:
* Data storage scalability: To allow efficient use of memory the amount required should be approximately linear, so simple networks on small devices require small amounts of memory while more complex environments can function on moderately scaled hardware.
* Algorithm scalability: Similar to memory usage, ensuring that calculation times are more linear than exponential as numbers of APs and stations grow.
* Algorithm efficiency: As well as being scaleable the processing of data must fit within absolute limits to ensure that APs can keep up with the rate of data arriving from the network.

View file

@ -175,6 +175,8 @@ void print_probe_entry(probe_entry entry);
int eval_probe_metric(struct probe_entry_s probe_entry);
void denied_req_array_insert(auth_entry entry);
auth_entry denied_req_array_delete(auth_entry entry);
auth_entry insert_to_denied_req_array(auth_entry entry, int inc_counter);

View file

@ -43,8 +43,6 @@ int compare_station_count(uint8_t *bssid_addr_own, uint8_t *bssid_addr_to_compar
int compare_ssid(uint8_t *bssid_addr_own, uint8_t *bssid_addr_to_compare);
void denied_req_array_insert(auth_entry entry);
int denied_req_array_go_next(char sort_order[], int i, auth_entry entry,
auth_entry next_entry);

5
src/test/ap_auto.script Normal file
View file

@ -0,0 +1,5 @@
# Basic test of array entry handling
ap_add_auto 10 20
ap_show
ap_del_auto 10 20
ap_show

View file

@ -0,0 +1,5 @@
# Basic test of array entry handling
auth_entry_add_auto 10 20
auth_entry_show
auth_entry_del_auto 10 20
auth_entry_show

View file

@ -0,0 +1,5 @@
# Basic test of array entry handling
client_add_auto 10 20
client_show
client_del_auto 10 20
client_show

View file

@ -0,0 +1,6 @@
# Basic test of array entry handling
probe_sort bcfs
probe_add_auto 10 20
probe_show
probe_del_auto 10 20
probe_show

View file

@ -1,6 +1,7 @@
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include "dawn_iwinfo.h"
#include "utils.h"
@ -9,126 +10,21 @@
#include "datastorage.h"
#include "uface.h"
/*** External functions ***/
/*** SUT functions we use that are not in header files (like "friend" functions) ***/
void ap_array_insert(ap entry);
ap ap_array_delete(ap entry);
/*** Testing structures, etc ***/
union __attribute__((__packed__)) mac_mangler
union __attribute__((__packed__)) pac_a_mac
{
struct {
uint8_t b[6];
uint8_t pos[6];
uint8_t packing[2];
} u8;
uint64_t u64;
};
/*** Test code */
int ap_array_helper_auto(int action, int i0, int i1);
int ap_array_helper_auto(int action, int i0, int i1)
{
int m;
int step = (i0 > i1) ? -1 : 1;
int ret = 0;
switch (action)
{
case 0:
case 1:
m = i0;
int cont = 1;
while (cont) {
union mac_mangler this_mac;
ap ap0;
this_mac.u64 = m;
memcpy(ap0.bssid_addr, this_mac.u8.b, sizeof(ap0.bssid_addr));
if (action == 0)
ap_array_insert(ap0);
else
ap_array_delete(ap0);
if (m == i1)
cont = 0;
else
m += step;
}
break;
default:
ret = 1;
break;
}
return ret;
}
int main(int argc, char** argv)
{
int ret = 0;
int args_ok = 1;
int arg_consumed = 0;
printf("DAWN datastorage.c test harness. Ready for commands...\n");
int this_arg = 1;
argv++;
while (args_ok)
{
if (strcmp(*argv, "help") == 0)
{
arg_consumed = 1;
if (this_arg + arg_consumed > argc) goto next_command;
printf("Help is on its way...\n");
}
else if (strcmp(*argv, "ap_show") == 0)
{
arg_consumed = 1;
if (this_arg + arg_consumed > argc) goto next_command;
print_ap_array();
}
else if (strcmp(*argv, "ap_add_auto") == 0)
{
arg_consumed = 3;
if (this_arg + arg_consumed > argc) goto next_command;
ap_array_helper_auto(0, atoi(*(argv + 1)), atoi(*(argv + 2)));
}
else if (strcmp(*argv, "ap_del_auto") == 0)
{
arg_consumed = 3;
if (this_arg + arg_consumed > argc) goto next_command;
ap_array_helper_auto(1, atoi(*(argv + 1)), atoi(*(argv + 2)));
}
else
{
arg_consumed = 1;
if (this_arg + arg_consumed > argc) goto next_command;
printf("COMMAND \"%s\": Unknown - skipping!\n", *argv);
}
next_command:
this_arg += arg_consumed;
if (this_arg > argc)
{
printf("Commands are mangled at: \"%s\"!\n", *argv);
args_ok = 0;
}
else if (this_arg == argc)
args_ok = 0;
else
argv += arg_consumed;
}
printf("\n\nDAWN datastorage.c test harness - finshed. \n");
return ret;
}
/*** Test Stub Functions - Called by SUT ***/
void ubus_send_beacon_report(uint8_t client[], int id)
{
printf("send_beacon_report() was called...\n");
@ -140,7 +36,7 @@ int send_set_probe(uint8_t client_addr[])
return 0;
}
void wnm_disassoc_imminent(uint32_t id, const uint8_t *client_addr, char* dest_ap, uint32_t duration)
void wnm_disassoc_imminent(uint32_t id, const uint8_t* client_addr, char* dest_ap, uint32_t duration)
{
printf("wnm_disassoc_imminent() was called...\n");
}
@ -150,7 +46,7 @@ void add_client_update_timer(time_t time)
printf("add_client_update_timer() was called...\n");
}
void del_client_interface(uint32_t id, const uint8_t *client_addr, uint32_t reason, uint8_t deauth, uint32_t ban_time)
void del_client_interface(uint32_t id, const uint8_t* client_addr, uint32_t reason, uint8_t deauth, uint32_t ban_time)
{
printf("del_client_interface() was called...\n");
}
@ -161,21 +57,389 @@ int ubus_send_probe_via_network(struct probe_entry_s probe_entry)
return 0;
}
int get_rssi_iwinfo(uint8_t *client_addr)
int get_rssi_iwinfo(uint8_t* client_addr)
{
printf("get_rssi_iwinfo() was called...\n");
return 0;
}
int get_expected_throughput_iwinfo(uint8_t *client_addr)
int get_expected_throughput_iwinfo(uint8_t* client_addr)
{
printf("get_expected_throughput_iwinfo() was called...\n");
return 0;
}
int get_bandwidth_iwinfo(uint8_t *client_addr, float *rx_rate, float *tx_rate)
int get_bandwidth_iwinfo(uint8_t* client_addr, float* rx_rate, float* tx_rate)
{
printf("get_bandwidth_iwinfo() was called...\n");
return 0;
}
/*** Local Function Prototypes ***/
#define HELPER_ACTION_ADD 0x0000
#define HELPER_ACTION_DEL 0x1000
#define HELPER_ACTION_MASK 0x1000
#define HELPER_AP 0x0001
#define HELPER_CLIENT 0x0002
#define HELPER_AUTH_ENTRY 0x0004
#define HELPER_PROBE_ARRAY 0x0008
int array_auto_helper(int action, int i0, int i1);
int client_array_auto_helper(int action, int i0, int i1);
int auth_entry_array_auto_helper(int action, int i0, int i1);
int probe_array_auto_helper(int action, int i0, int i1);
/*** Test narness code */
int array_auto_helper(int action, int i0, int i1)
{
int m = i0;
int step = (i0 > i1) ? -1 : 1;
int ret = 0;
int cont = 1;
while (cont) {
union pac_a_mac this_mac;
this_mac.u64 = m;
switch (action & ~HELPER_ACTION_MASK)
{
case HELPER_AP:
; // Empty statement to allow label before declaration
ap ap0;
memcpy(ap0.bssid_addr, &this_mac.u8.pos[0], sizeof(ap0.bssid_addr));
if ((action & HELPER_ACTION_MASK) == HELPER_ACTION_ADD)
ap_array_insert(ap0);
else
ap_array_delete(ap0);
break;
case HELPER_CLIENT:
; // Empty statement to allow label before declaration
client client0;
memcpy(client0.bssid_addr, &this_mac.u8.pos[0], sizeof(client0.bssid_addr));
memcpy(client0.client_addr, &this_mac.u8.pos[0], sizeof(client0.client_addr));
if ((action & HELPER_ACTION_MASK) == HELPER_ACTION_ADD)
client_array_insert(client0);
else
client_array_delete(client0);
break;
case HELPER_PROBE_ARRAY:
; // Empty statement to allow label before declaration
probe_entry probe0;
memcpy(probe0.bssid_addr, &this_mac.u8.pos[0], sizeof(probe0.bssid_addr));
memcpy(probe0.client_addr, &this_mac.u8.pos[0], sizeof(probe0.client_addr));
if ((action & HELPER_ACTION_MASK) == HELPER_ACTION_ADD)
probe_array_insert(probe0);
else
probe_array_delete(probe0);
break;
case HELPER_AUTH_ENTRY:
; // Empty statement to allow label before declaration
auth_entry auth_entry0;
memcpy(auth_entry0.bssid_addr, &this_mac.u8.pos[0], sizeof(auth_entry0.bssid_addr));
memcpy(auth_entry0.client_addr, &this_mac.u8.pos[0], sizeof(auth_entry0.client_addr));
if ((action & HELPER_ACTION_MASK) == HELPER_ACTION_ADD)
denied_req_array_insert(auth_entry0);
else
denied_req_array_delete(auth_entry0);
break;
default:
printf("HELPER error - which entity?\n");
ret = -1;
}
if (m == i1)
cont = 0;
else
m += step;
}
return ret;
}
int consume_actions(int argc, char* argv[]);
int consume_actions(int argc, char* argv[])
{
int ret = 0;
int args_required = 0; // Suppress compiler warming by assigning initial value
int curr_arg = 0;
while (curr_arg < argc && ret == 0)
{
if (strcmp(*argv, "probe_sort") == 0)
{
args_required = 2;
if (curr_arg + args_required <= argc)
{
strcpy(sort_string, argv[1]);
}
}
else if (strcmp(*argv, "ap_show") == 0)
{
args_required = 1;
print_ap_array();
}
else if (strcmp(*argv, "probe_show") == 0)
{
args_required = 1;
print_probe_array();
}
else if (strcmp(*argv, "client_show") == 0)
{
args_required = 1;
print_client_array();
}
else if (strcmp(*argv, "auth_entry_show") == 0)
{
args_required = 1;
printf("--------APs------\n");
for (int i = 0; i <= denied_req_last; i++) {
print_auth_entry(denied_req_array[i]);
}
printf("------------------\n");
}
else if (strcmp(*argv, "ap_add_auto") == 0)
{
args_required = 3;
if (curr_arg + args_required <= argc)
{
ret = array_auto_helper(HELPER_AP | HELPER_ACTION_ADD, atoi(*(argv + 1)), atoi(*(argv + 2)));
}
}
else if (strcmp(*argv, "ap_del_auto") == 0)
{
args_required = 3;
if (curr_arg + args_required <= argc)
{
ret = array_auto_helper(HELPER_AP | HELPER_ACTION_DEL, atoi(*(argv + 1)), atoi(*(argv + 2)));
}
}
else if (strcmp(*argv, "probe_add_auto") == 0)
{
args_required = 3;
if (curr_arg + args_required <= argc)
{
ret = array_auto_helper(HELPER_PROBE_ARRAY | HELPER_ACTION_ADD, atoi(*(argv + 1)), atoi(*(argv + 2)));
}
}
else if (strcmp(*argv, "probe_del_auto") == 0)
{
args_required = 3;
if (curr_arg + args_required <= argc)
{
ret = array_auto_helper(HELPER_PROBE_ARRAY | HELPER_ACTION_DEL, atoi(*(argv + 1)), atoi(*(argv + 2)));
}
}
else if (strcmp(*argv, "client_add_auto") == 0)
{
args_required = 3;
if (curr_arg + args_required <= argc)
{
ret = array_auto_helper(HELPER_CLIENT | HELPER_ACTION_ADD, atoi(*(argv + 1)), atoi(*(argv + 2)));
}
}
else if (strcmp(*argv, "client_del_auto") == 0)
{
args_required = 3;
if (curr_arg + args_required <= argc)
{
ret = array_auto_helper(HELPER_CLIENT | HELPER_ACTION_DEL, atoi(*(argv + 1)), atoi(*(argv + 2)));
}
}
else if (strcmp(*argv, "auth_entry_add_auto") == 0)
{
args_required = 3;
if (curr_arg + args_required <= argc)
{
ret = array_auto_helper(HELPER_AUTH_ENTRY | HELPER_ACTION_ADD, atoi(*(argv + 1)), atoi(*(argv + 2)));
}
}
else if (strcmp(*argv, "auth_entry_del_auto") == 0)
{
args_required = 3;
if (curr_arg + args_required <= argc)
{
ret = array_auto_helper(HELPER_AUTH_ENTRY | HELPER_ACTION_DEL, atoi(*(argv + 1)), atoi(*(argv + 2)));
}
}
else
{
args_required = 1;
printf("COMMAND \"%s\": Unknown - stopping!\n", *argv);
ret = -1;
}
curr_arg += args_required;
if (curr_arg <= argc)
{
// Still need to continue consuming args
argv += args_required;
}
else
{
// There aren't enough args left to give the parameters of the current action
printf("Commands are mangled at: \"%s\"!\n", *argv);
ret = -1;
}
}
return ret;
}
int process_script_line(char* line, size_t len);
#define MAX_LINE_ARGS 5
int process_script_line(char* line, size_t len)
{
int argc = 0;
char* argv[MAX_LINE_ARGS];
bool in_white = true;
bool force_blank = false;
int ret = 0;
//printf("%lu: \"%s\"\n", len, line);
while (len > 0 && !ret)
{
if (isblank(*line) || (*line == '\n') || (*line == '\r') || (*line == '#') || force_blank)
{
if (*line == '#')
{
//printf("Blanking 0x%02X...\n", *line);
force_blank = true;
}
//printf("Zapping 0x%02X...\n", *line);
*line = '\0';
in_white = true;
}
else
{
if (in_white)
{
//printf("Marking 0x%02X...\n", *line);
if (argc == MAX_LINE_ARGS)
{
printf("ERROR: Script line exceeds permitted arg count!\n");
ret = -1;
}
else
{
argv[argc] = line;
argc++;
in_white = false;
}
}
}
len--;
line++;
}
if (!ret)
ret = consume_actions(argc, argv);
return ret;
}
int main(int argc, char* argv[])
{
FILE* fp;
char* line = NULL;
size_t len = 0;
ssize_t read;
int ret = 0;
printf("DAWN datastorage.c test harness...\n\n");
if ((argc == 1) || !strcmp(*(argv + 1), "help") || !strcmp(*(argv + 1), "--help") || !strcmp(*(argv + 1), "-h"))
{
printf("Usage: %s [commands]\n\n", *argv);
printf(" [action [arg...]]... : Read test actions from command line\n");
printf(" --script [file]... : Read test script from file(s) (NB: \"-\" is a valid name\n");
printf(" indicating STDIN) {-s}\n");
printf(" - : Read test script from STDIN (and remaining arguments\n");
printf(" as script file names)\n");
printf(" --help : This help message {-h, help}\n");
printf("NB: Contents of {braces} indicate equivalent command\n");
}
else
{
// Step past command name on args, ie argv[0]
argc--;
argv++;
if (!strcmp(*argv, "--script") || !strcmp(*argv, "-s") || !strcmp(*argv, "-"))
{
if (!strcmp(*argv, "--script") || !strcmp(*argv, "-s"))
{
argc--;
argv++;
}
// Read script from file[s]
while (argc > 0 && ret == 0)
{
if (!strcmp(*argv, "-"))
{
fp = stdin;
printf("Consuming script from STDIN\n");
}
else
{
fp = fopen(*argv, "r");
if (fp == NULL)
{
printf("Error opening script file: %s\n", *argv);
ret = -1;
}
else
{
printf("Consuming script file: %s\n", *argv);
}
}
if (ret == 0)
{
read = getline(&line, &len, fp);
while (!ret && read != -1)
{
printf("Processing: %s\n", line);
ret = process_script_line(line, read);
if (!ret)
read = getline(&line, &len, fp);
}
if (fp != stdin)
fclose(fp);
if (line)
free(line);
}
argc--;
argv++;
}
}
else
{
// Take direct input on command line
ret = consume_actions(argc, argv);
}
}
printf("\nDAWN datastorage.c test harness - finshed. \n");
return ret;
}

View file

@ -42,7 +42,7 @@ int hwaddr_aton(const char *txt, uint8_t *addr) {
b = hex_to_bin(*txt++);
if (b < 0) return -1;
*addr++ = (a << 4) | b;
// TODO: Should NUL terminator be checked for? Is aa:bb:cc:dd:ee:ff00 valid input?
// TODO: Should NUL terminator be checked for? Is aa:bb:cc:dd:ee:ff00 valid input?
if (i < (ETH_ALEN - 1) && *txt++ != ':') return -1;
}