From d376ab001e9efddabdabc823721bea46d1c06c22 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Fri, 4 Jun 2021 12:38:10 -0400 Subject: [PATCH 01/42] Add execution screenshots for SunneeD and test Add screenshots showing the exectution of SunneeD and the overlay tester to README --- README.md | 7 ++++++- res/Run_Overlay_LDPRELOAD.JPG | Bin 0 -> 67877 bytes res/Run_SunneeD.JPG | Bin 0 -> 107545 bytes 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 res/Run_Overlay_LDPRELOAD.JPG create mode 100644 res/Run_SunneeD.JPG diff --git a/README.md b/README.md index 3d9e1ea..8c35af8 100644 --- a/README.md +++ b/README.md @@ -47,5 +47,10 @@ You can run this, and then run one of the example programs to test connectivity ## Running with overlay tester -Once you have successfully built sunneed and verified that the `build/sunneed` binary has been made, we can run the overlay tester to verify that sunneed is correctly intercepting `open()` and `write()` requests. *Pro tip: open a second terminal so that you don't have to type around the sunneed log prints.* Execute the sunneed binary by running `sudo ./sunneed &`. Note that the `&` is not necessary if you are running in a second terminal. In your second terminal or after sunneed begins execution, we can run the overlay_tester located in `sunneed/build/test_progs/`. Before running it, we have to `LD_PRELOAD` SunneeD's overlay so that the `open()` and `write()` calls are redirected correctly. You can do all of this with the following command: `LD_PRELOAD= ./build/test_progs/overlay_tester` from the sunneed directory. **Note: the argument to LD_PRELOAD must be the ABSOLUTE path of sunneed_overlay.so, using a relative path will not work.** +Once you have successfully built sunneed and verified that the `build/sunneed` binary has been made, we can run the overlay tester to verify that sunneed is correctly intercepting `open()` and `write()` requests. *Pro tip: open a second terminal so that you don't have to type around the sunneed log prints.* Execute the sunneed binary by running `sudo ./sunneed &`. Note that the `&` is not necessary if you are running in a second terminal. +![Sunneed](./res/Run_SunneeD.JPG) + +In your second terminal or after sunneed begins execution, we can run the overlay_tester located in `sunneed/build/`. Before running it, we have to `LD_PRELOAD` SunneeD's overlay so that the `open()` and `write()` calls are redirected correctly. You can do all of this with the following command: `LD_PRELOAD= ./build/overlay_tester` from the sunneed directory. **Note: the argument to LD_PRELOAD must be the ABSOLUTE path of sunneed_overlay.so, using a relative path will not work.** + +![Overlay_test](./res/Run_Overlay_LDPRELOAD.JPG) diff --git a/res/Run_Overlay_LDPRELOAD.JPG b/res/Run_Overlay_LDPRELOAD.JPG new file mode 100644 index 0000000000000000000000000000000000000000..b70e973a8784fcabc06b5e7c48391e3036ec04c3 GIT binary patch literal 67877 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 k6M?R!z^keHg|+S<{9 literal 0 HcmV?d00001 diff --git a/res/Run_SunneeD.JPG b/res/Run_SunneeD.JPG new file mode 100644 index 0000000000000000000000000000000000000000..65e4745c595f65133f6599afb9641deec1cb2818 GIT binary patch literal 107545 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%0A{youIGIdK35BFkE(jDZB~-IzN){ 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 Date: Wed, 9 Jun 2021 15:59:45 -0400 Subject: [PATCH 02/42] Add files via upload --- PowerModel/tmp.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 PowerModel/tmp.txt diff --git a/PowerModel/tmp.txt b/PowerModel/tmp.txt new file mode 100644 index 0000000..32f95c0 --- /dev/null +++ b/PowerModel/tmp.txt @@ -0,0 +1 @@ +hi \ No newline at end of file From 36d82b179752300e4f3426635987569538a38277 Mon Sep 17 00:00:00 2001 From: jonterry9 <55032391+jonterry9@users.noreply.github.com> Date: Wed, 9 Jun 2021 16:00:05 -0400 Subject: [PATCH 03/42] Delete tmp.txt --- PowerModel/tmp.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 PowerModel/tmp.txt diff --git a/PowerModel/tmp.txt b/PowerModel/tmp.txt deleted file mode 100644 index 32f95c0..0000000 --- a/PowerModel/tmp.txt +++ /dev/null @@ -1 +0,0 @@ -hi \ No newline at end of file From 0779ec5337c5dd52719a0ffd276c3ee2349ec88e Mon Sep 17 00:00:00 2001 From: jonterry9 <55032391+jonterry9@users.noreply.github.com> Date: Wed, 9 Jun 2021 16:00:21 -0400 Subject: [PATCH 04/42] Add files via upload --- PowerModel/tmp.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 PowerModel/tmp.txt diff --git a/PowerModel/tmp.txt b/PowerModel/tmp.txt new file mode 100644 index 0000000..32f95c0 --- /dev/null +++ b/PowerModel/tmp.txt @@ -0,0 +1 @@ +hi \ No newline at end of file From 8091322742569f989239223439351e901c53ba6b Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Wed, 9 Jun 2021 16:09:36 -0400 Subject: [PATCH 05/42] Add remote side of packet size test Packet size test sends UDP packets between the Pi and a remote host message sizes increase from 1 to 64 chars by appending the next ascii value after each send still need to add the raspberry pi side of these tests --- .../recv_test_size_remote.c | 66 +++++++++++++++++++ .../send_test_size_remote.c | 65 ++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 PowerModel/SunneeD_network_tests/recv_test_size_remote.c create mode 100644 PowerModel/SunneeD_network_tests/send_test_size_remote.c diff --git a/PowerModel/SunneeD_network_tests/recv_test_size_remote.c b/PowerModel/SunneeD_network_tests/recv_test_size_remote.c new file mode 100644 index 0000000..4b3a539 --- /dev/null +++ b/PowerModel/SunneeD_network_tests/recv_test_size_remote.c @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +/* + * Send varying sized packets from this host to the Pi + * uses UDP sockets to keep the packet size consistent with what is being sent + * + * to test with TCP, change SOCK_DGRAM to SOCK_STREAM and see the note for testing with + * TCP in recv_test_size.c on the Pi + */ +int +main(void) +{ + int pi_fd, read_val; + struct sockaddr_in pi_addr; + char msg[64]; + msg[0] = 'A'; + + + + char buffer[1024] = {0}; + + if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + fprintf(stderr, "failed to create socket\n"); + return 0; + } + + pi_addr.sin_family = AF_INET; + pi_addr.sin_port = htons(PORT); + + if(inet_pton(AF_INET, "192.168.1.134", &pi_addr.sin_addr)<=0) + { + fprintf(stderr, "invalid address/failed to convert address\n"); + return 0; + } + + if(connect(pi_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) + { + fprintf(stderr, "connection failed\n"); + return 0; + } + + printf("connected to pi\n"); + int i; + /* + * starting with an empty string, add one char at a time up to 64 + * since chars are 1 byte each, this should be roughly equivalent to + * increasing the packet size by one byte for each send + */ + for(i = 0; i < 64; i++) + { + printf("msg to send: %s\n", msg); + send(pi_fd, msg, sizeof(msg), 0); + + sleep(1); + + msg[i + 1] = (char)(msg[0] + 1 + i); + } +} \ No newline at end of file diff --git a/PowerModel/SunneeD_network_tests/send_test_size_remote.c b/PowerModel/SunneeD_network_tests/send_test_size_remote.c new file mode 100644 index 0000000..a554721 --- /dev/null +++ b/PowerModel/SunneeD_network_tests/send_test_size_remote.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +int +main(void) +{ + int pi_fd, read_val; +// int pi_sock; //uncomment this for TCP sockets + struct sockaddr_in pi_addr; + int opt = 1; + int addrlen = sizeof(pi_addr); + char buffer[1024] = {0}; + + //create UDP socket, change from SOCK_DGRAM to SOCK_STREAM for TCP sockets and uncomment the section below + if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) + { + perror("socket failed\n"); + return 0; + } + + if(setsockopt(pi_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) + { + perror("setsockopt failed\n"); + return 0; + } + + pi_addr.sin_family = AF_INET; + pi_addr.sin_addr.s_addr = INADDR_ANY; + pi_addr.sin_port = htons(PORT); + + if(bind(pi_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) + { + perror("bind failed\n"); + return 0; + } + + /* + * Uncomment this section if using TCP sockets, comment out if using UDP sockets + */ + +// if(listen(pi_fd, 3) < 0) +// { +// perror("listen failed\n"); +// return 0; +// } + +// if((pi_sock = accept(pi_fd, (struct sockaddr *)&remote_addr, (socklen_t*)&addrlen)) < 0) +// { +// perror("accept failed\n"); +// return 0; +// } + + printf("connected to pi\n"); + while((read_val = read(pi_fd, buffer, 1024)) > 0) + { + printf("received %s\n", buffer); + sleep(1); + } +} \ No newline at end of file From 929b9c59afa6c60e6d79f62616807a6156660216 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Thu, 10 Jun 2021 15:27:37 -0400 Subject: [PATCH 06/42] Add battery babysitter logging on write functions Add LOG_PWR option to makefile to run a tenant with battery babysitter logging Will use this to generate data for regrassion model --- .vscode/settings.json | 6 ++++++ Makefile | 7 +++++++ locked_H5pUiE | 0 src/log.h | 13 ++++++++++++- src/pip/bq27441.c | 1 + src/shared/sunneed_pip_interface.h | 1 + src/sunneed.h | 7 ++++++- src/sunneed_core.c | 6 ++++++ src/sunneed_listener.c | 24 ++++++++++++++++++++++++ stepper_pwr_log.txt | 0 10 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 locked_H5pUiE create mode 100644 stepper_pwr_log.txt diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..792562d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.associations": { + "sunneed_pip_interface.h": "c", + "sunneed_listener.h": "c" + } +} \ No newline at end of file diff --git a/Makefile b/Makefile index 18fc141..551d570 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,13 @@ device_objs = $(patsubst %.c, %.o, $(wildcard $(src_dir)/device/*.c)) util_objs = $(patsubst %.c, %.o, $(wildcard $(src_dir)/util/*.c)) all: pre-all main overlay util + +log_pwr: pre-all main_log_pwr overlay util + +main_log_pwr: ext protobuf pip devices + $(call section_title,main executable) + $(CC) $(CFLAGS) -DTESTING -DLOG_PWR $(sources) $(protobuf_out_sources) $(cflags_deps) $(pip_obj) -o $(out_dir)/$(bin_file) + pre-all: @echo "Starting all build..." diff --git a/locked_H5pUiE b/locked_H5pUiE new file mode 100644 index 0000000..e69de29 diff --git a/src/log.h b/src/log.h index e166511..9e6d987 100644 --- a/src/log.h +++ b/src/log.h @@ -9,13 +9,23 @@ #include #include -FILE *logfile; +FILE *logfile, *stepper_pwr_logfile; #define LOGL_DEBUG "D\e[38;5;240m" #define LOGL_INFO "I" #define LOGL_WARN "W\e[0;33m" #define LOGL_ERROR "E\e[0;31m" +#ifndef LOG_PWR +#define LOG_PWR(LEVEL, MESSAGE, ...) \ + { \ + FILE *_logfile = stepper_pwr_logfile; \ + if (!logfile) { \ + return; \ + } \ + fprintf(_logfile, MESSAGE) \ + } +#endif #define LOG(LEVEL, MESSAGE, ...) \ { \ FILE *_logfile = logfile; \ @@ -33,5 +43,6 @@ FILE *logfile; #define LOG_I(MESSAGE, ...) LOG(LOGL_INFO, MESSAGE, ##__VA_ARGS__); #define LOG_W(MESSAGE, ...) LOG(LOGL_WARN, MESSAGE, ##__VA_ARGS__); #define LOG_E(MESSAGE, ...) LOG(LOGL_ERROR, MESSAGE, ##__VA_ARGS__); +#define LOG_P(MESSAGE, ...) LOG_PWR(LOGL_INFO, MESSAGE, ##__VA_ARGS__); #endif diff --git a/src/pip/bq27441.c b/src/pip/bq27441.c index c461e40..5cd9f10 100644 --- a/src/pip/bq27441.c +++ b/src/pip/bq27441.c @@ -8,4 +8,5 @@ pip_info() { unsigned int present_power() { return 0; + // return bq27441_nominal_avail_cap(); } diff --git a/src/shared/sunneed_pip_interface.h b/src/shared/sunneed_pip_interface.h index 1c90354..06ddeca 100644 --- a/src/shared/sunneed_pip_interface.h +++ b/src/shared/sunneed_pip_interface.h @@ -1,5 +1,6 @@ #ifndef _SUNNEED_PIP_H_ #define _SUNNEED_PIP_H_ +#include "../../ext/libbq27441/bq27441.h" /* * Describes the interface for a power information provider (a PIP). diff --git a/src/sunneed.h b/src/sunneed.h index cb90389..cdfe09e 100644 --- a/src/sunneed.h +++ b/src/sunneed.h @@ -5,6 +5,11 @@ #define APP_NAME "sunneed" -typedef void* sunneed_worker_thread_result_t; +#ifdef LOG_PWR + #define REQS_PER_LOG 10 + int last_capacity, curr_capacity, reqs_since_last_log; +#endif + +typedef void* sunneed_worker_thread_result_t; #endif diff --git a/src/sunneed_core.c b/src/sunneed_core.c index 0f82000..fff798d 100644 --- a/src/sunneed_core.c +++ b/src/sunneed_core.c @@ -66,12 +66,18 @@ spawn_worker_threads(void) { void sunneed_init(void) { pip = pip_info(); + last_capacity = present_power(); } int main(int argc, char *argv[]) { int opt; extern int optopt; +#ifdef LOG_PWR + LOG_D("Recording power\n"); + stepper_pwr_logfile = fopen("stepper_pwr_log.txt", "w+"); + reqs_since_last_log = 0; +#endif #ifdef TESTING const char *optstring = ":ht:c"; diff --git a/src/sunneed_listener.c b/src/sunneed_listener.c index c58e5dc..bb7cab5 100644 --- a/src/sunneed_listener.c +++ b/src/sunneed_listener.c @@ -254,6 +254,10 @@ int sunneed_listen(void) { SUNNEED_NNG_SET_ERROR_REPORT_FUNC(report_nng_error); + #ifdef LOG_PWR + int capacity_change; + #endif + // Initialize client states. for (int i = 0; i < MAX_TENANTS; i++) { tenant_pipes[i] = (struct tenant_pipe){.tenant = NULL, @@ -306,6 +310,10 @@ sunneed_listen(void) { SunneedResponse resp = SUNNEED_RESPONSE__INIT; int ret = -1; + #ifdef LOG_PWR + reqs_since_last_log++; + #endif + switch (request->message_type_case) { case SUNNEED_REQUEST__MESSAGE_TYPE__NOT_SET: LOG_W("Request from pipe %d has no message type set.", pipe.id); @@ -321,11 +329,27 @@ sunneed_listen(void) { ret = serve_open_file(&resp, sub_resp_buf, tenant, request->open_file); break; case SUNNEED_REQUEST__MESSAGE_TYPE_WRITE: + #ifdef LOG_PWR + if (reqs_since_last_log < REQS_PER_LOG) { + LOG_D("%d, ",reqs_since_last_log); + LOG_I("%d\n",capacity_change); + } else if (reqs_since_last_log > REQS_PER_LOG) { + curr_capacity = present_power(); + capacity_change = last_capacity - curr_capacity; + last_capacity = curr_capacity; + LOG_D("%d\n",capacity_change); + LOG_D("%d, ",reqs_since_last_log); + reqs_since_last_log = 1; + } + #endif ret = serve_write(&resp, sub_resp_buf, tenant, request->write); break; default: LOG_W("Received request with invalid type %d", request->message_type_case); ret = -1; + #ifdef LOG_PWR + reqs_since_last_log--; + #endif break; } diff --git a/stepper_pwr_log.txt b/stepper_pwr_log.txt new file mode 100644 index 0000000..e69de29 From c50840b6b4dc3b9ef362121c799551d1f4da2620 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Thu, 10 Jun 2021 15:35:08 -0400 Subject: [PATCH 07/42] Restructure network tests directory for clarity Test directory contains the programs that need to run on the Pi and on the Remote host The directory is broken down into which test you want to run (packet size or frequency), then has subdirectories containing the Pi code and remote host code --- .../Frequency/Remote/remote_recv.c | 70 ++++++++++ .../Remote/remote_recv.c} | 128 ++++++++--------- .../Remote/remote_send.c} | 130 +++++++++--------- 3 files changed, 199 insertions(+), 129 deletions(-) create mode 100644 PowerModel/SunneeD_network_tests/Frequency/Remote/remote_recv.c rename PowerModel/SunneeD_network_tests/{send_test_size_remote.c => Packet_Size/Remote/remote_recv.c} (96%) rename PowerModel/SunneeD_network_tests/{recv_test_size_remote.c => Packet_Size/Remote/remote_send.c} (96%) diff --git a/PowerModel/SunneeD_network_tests/Frequency/Remote/remote_recv.c b/PowerModel/SunneeD_network_tests/Frequency/Remote/remote_recv.c new file mode 100644 index 0000000..973190f --- /dev/null +++ b/PowerModel/SunneeD_network_tests/Frequency/Remote/remote_recv.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +int +main(void) +{ + int pi_fd, read_val; +// int pi_sock; //uncomment this if using TCP sockets + struct sockaddr_in pi_addr; + int opt = 1; + int addrlen = sizeof(pi_addr); + char buffer[1024] = {0}; + float downtime = 0.5; + + //create UDP socket, change SOCK_DGRAM to SOCK_STREAM for a TCP socket + if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + perror("failed to create socket\n"); + return 0; + } + + if(setsockopt(pi_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) + { + perror("setsockopt failed\n"); + return 0; + } + + pi_addr.sin_family = AF_INET; + pi_addr.sin_addr.s_addr = INADDR_ANY; + pi_addr.sin_port = htons(PORT); + + if(bind(pI_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) + { + perror("bind failed\n"); + return 0; + } + + /* + * Uncomment this section is using TCP sockets + */ + + /* + if(listen(pi_fd, 3) < 0) + { + perror("listen failed\n"); + return 0; + } + + if((pi_sock = accept(pi_fd, (struct sockaddr *)&pi_addr, (socklen_t*)&addrlen)) < 0) + { + perror("accept failed\n"); + return 0; + } + */ + + printf("connected to pi\n"); + while((read_val = read(pi_fd, buffer, 1024)) > 0) + { + printf("received: %s\n", buffer); + sleep(downtime); + + downtime += 0.5; + } +} \ No newline at end of file diff --git a/PowerModel/SunneeD_network_tests/send_test_size_remote.c b/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_recv.c similarity index 96% rename from PowerModel/SunneeD_network_tests/send_test_size_remote.c rename to PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_recv.c index a554721..b3c2423 100644 --- a/PowerModel/SunneeD_network_tests/send_test_size_remote.c +++ b/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_recv.c @@ -1,65 +1,65 @@ -#include -#include -#include -#include -#include -#include - -#define PORT 9999 - -int -main(void) -{ - int pi_fd, read_val; -// int pi_sock; //uncomment this for TCP sockets - struct sockaddr_in pi_addr; - int opt = 1; - int addrlen = sizeof(pi_addr); - char buffer[1024] = {0}; - - //create UDP socket, change from SOCK_DGRAM to SOCK_STREAM for TCP sockets and uncomment the section below - if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) - { - perror("socket failed\n"); - return 0; - } - - if(setsockopt(pi_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) - { - perror("setsockopt failed\n"); - return 0; - } - - pi_addr.sin_family = AF_INET; - pi_addr.sin_addr.s_addr = INADDR_ANY; - pi_addr.sin_port = htons(PORT); - - if(bind(pi_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) - { - perror("bind failed\n"); - return 0; - } - - /* - * Uncomment this section if using TCP sockets, comment out if using UDP sockets - */ - -// if(listen(pi_fd, 3) < 0) -// { -// perror("listen failed\n"); -// return 0; -// } - -// if((pi_sock = accept(pi_fd, (struct sockaddr *)&remote_addr, (socklen_t*)&addrlen)) < 0) -// { -// perror("accept failed\n"); -// return 0; -// } - - printf("connected to pi\n"); - while((read_val = read(pi_fd, buffer, 1024)) > 0) - { - printf("received %s\n", buffer); - sleep(1); - } +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +int +main(void) +{ + int pi_fd, read_val; +// int pi_sock; //uncomment this for TCP sockets + struct sockaddr_in pi_addr; + int opt = 1; + int addrlen = sizeof(pi_addr); + char buffer[1024] = {0}; + + //create UDP socket, change from SOCK_DGRAM to SOCK_STREAM for TCP sockets and uncomment the section below + if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) + { + perror("socket failed\n"); + return 0; + } + + if(setsockopt(pi_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) + { + perror("setsockopt failed\n"); + return 0; + } + + pi_addr.sin_family = AF_INET; + pi_addr.sin_addr.s_addr = INADDR_ANY; + pi_addr.sin_port = htons(PORT); + + if(bind(pi_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) + { + perror("bind failed\n"); + return 0; + } + + /* + * Uncomment this section if using TCP sockets, comment out if using UDP sockets + */ + +// if(listen(pi_fd, 3) < 0) +// { +// perror("listen failed\n"); +// return 0; +// } + +// if((pi_sock = accept(pi_fd, (struct sockaddr *)&remote_addr, (socklen_t*)&addrlen)) < 0) +// { +// perror("accept failed\n"); +// return 0; +// } + + printf("connected to pi\n"); + while((read_val = read(pi_fd, buffer, 1024)) > 0) + { + printf("received %s\n", buffer); + sleep(1); + } } \ No newline at end of file diff --git a/PowerModel/SunneeD_network_tests/recv_test_size_remote.c b/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_send.c similarity index 96% rename from PowerModel/SunneeD_network_tests/recv_test_size_remote.c rename to PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_send.c index 4b3a539..d4591e4 100644 --- a/PowerModel/SunneeD_network_tests/recv_test_size_remote.c +++ b/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_send.c @@ -1,66 +1,66 @@ -#include -#include -#include -#include -#include -#include - -#define PORT 9999 - -/* - * Send varying sized packets from this host to the Pi - * uses UDP sockets to keep the packet size consistent with what is being sent - * - * to test with TCP, change SOCK_DGRAM to SOCK_STREAM and see the note for testing with - * TCP in recv_test_size.c on the Pi - */ -int -main(void) -{ - int pi_fd, read_val; - struct sockaddr_in pi_addr; - char msg[64]; - msg[0] = 'A'; - - - - char buffer[1024] = {0}; - - if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) - { - fprintf(stderr, "failed to create socket\n"); - return 0; - } - - pi_addr.sin_family = AF_INET; - pi_addr.sin_port = htons(PORT); - - if(inet_pton(AF_INET, "192.168.1.134", &pi_addr.sin_addr)<=0) - { - fprintf(stderr, "invalid address/failed to convert address\n"); - return 0; - } - - if(connect(pi_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) - { - fprintf(stderr, "connection failed\n"); - return 0; - } - - printf("connected to pi\n"); - int i; - /* - * starting with an empty string, add one char at a time up to 64 - * since chars are 1 byte each, this should be roughly equivalent to - * increasing the packet size by one byte for each send - */ - for(i = 0; i < 64; i++) - { - printf("msg to send: %s\n", msg); - send(pi_fd, msg, sizeof(msg), 0); - - sleep(1); - - msg[i + 1] = (char)(msg[0] + 1 + i); - } +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +/* + * Send varying sized packets from this host to the Pi + * uses UDP sockets to keep the packet size consistent with what is being sent + * + * to test with TCP, change SOCK_DGRAM to SOCK_STREAM and see the note for testing with + * TCP in recv_test_size.c on the Pi + */ +int +main(void) +{ + int pi_fd, read_val; + struct sockaddr_in pi_addr; + char msg[64]; + msg[0] = 'A'; + + + + char buffer[1024] = {0}; + + if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + fprintf(stderr, "failed to create socket\n"); + return 0; + } + + pi_addr.sin_family = AF_INET; + pi_addr.sin_port = htons(PORT); + + if(inet_pton(AF_INET, "192.168.1.134", &pi_addr.sin_addr)<=0) + { + fprintf(stderr, "invalid address/failed to convert address\n"); + return 0; + } + + if(connect(pi_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) + { + fprintf(stderr, "connection failed\n"); + return 0; + } + + printf("connected to pi\n"); + int i; + /* + * starting with an empty string, add one char at a time up to 64 + * since chars are 1 byte each, this should be roughly equivalent to + * increasing the packet size by one byte for each send + */ + for(i = 0; i < 64; i++) + { + printf("msg to send: %s\n", msg); + send(pi_fd, msg, sizeof(msg), 0); + + sleep(1); + + msg[i + 1] = (char)(msg[0] + 1 + i); + } } \ No newline at end of file From e0e15867cd1b23e6577995ed52301ae39ec59c06 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 10 Jun 2021 16:17:27 -0400 Subject: [PATCH 08/42] Finish packet size tests and add Frequency tests Finished Pi (SunneeD) side of the packet size tests Add both the Remote host and Pi side of the packet frequency test --- .../Frequency/Pi/pi_recv.c | 72 +++++++++++++++++++ .../Frequency/Pi/pi_send.c | 59 +++++++++++++++ .../Packet_Size/Pi/pi_recv.c | 63 ++++++++++++++++ .../Packet_Size/Pi/pi_send.c | 59 +++++++++++++++ 4 files changed, 253 insertions(+) create mode 100644 PowerModel/SunneeD_network_tests/Frequency/Pi/pi_recv.c create mode 100644 PowerModel/SunneeD_network_tests/Frequency/Pi/pi_send.c create mode 100644 PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_recv.c create mode 100644 PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_send.c diff --git a/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_recv.c b/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_recv.c new file mode 100644 index 0000000..15ccc8d --- /dev/null +++ b/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_recv.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +int +main(void) +{ + int remote_fd, read_val; +// int pi_sock; //uncomment for TCP sockets + struct sockaddr_in remote_addr; + int opt = 1; + int addrlen = sizeof(remote_addr); + char buffer[1024] = {0}; + float timeout = 0.5; + + //change from SOCK_DGRAM to SOCK_STREAM for TCP sockets + if((remote_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) + { + perror("socket failed\n"); + exit(0); + } + + if(setsockopt(remote_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) + { + perror("setsockopt failed\n"); + exit(0); + } + + remote_addr.sin_family = AF_INET; + remote_addr.sin_addr.s_addr = INADDR_ANY; + remote_addr.sin_port = htons(PORT); + + if(bind(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + perror("bind failed\n"); + exit(0); + } + + /* + * Uncomment this section if using TCP sockets, comment out for UDP sockets + */ + + /* + if(listen(remote_fd, 3) < 0) + { + perror("listen failed\n"); + exit(0); + } + + if((pi_sock = accept(pi_fd, (struct sockaddr *)&remote_addr, (socklen_t*)&addrlen)) < 0) + { + perror("accept failed\n"); + exit(0); + } + */ + + printf("connected to remote host\n"); + int i; + + while((read_val = read(remote_fd, buffer, 1024)) > 0) + { + printf("received: %s\n", buffer); + sleep(timeout); + + timeout += 0.5; + } +} diff --git a/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_send.c b/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_send.c new file mode 100644 index 0000000..c0bb7f2 --- /dev/null +++ b/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_send.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +/* + * Send packets at various frequencies, allowing the network card to idle for varying times + * starts at 500 ms and increases by 500 ms every iteration + * + * Uses UDP sockets as written, follow the notes in comments below to change to TCP sockets + */ + +int +main(void) +{ + int remote_fd, read_val; + struct sockaddr_in remote_addr; + char msg[64] = {'A'}; + float downtime = 0.5; + + //create UDP socket, change SOCK_DGRAM to SOCK_STREAM to create TCP sockets + if((remote_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + perror("socket failed\n"); + exit(0); + } + + remote_addr.sin_family = AF_INET; + remote_addr.sin_port = htons(PORT); + + if(inet_pton(AF_INET, "192.168.1.214", &remote_addr.sin_addr) <= 0) + { + perror("invalid address/failed to convert\n"); + exit(0); + } + + if(connect(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + perror("failed to connect\n"); + exit(0); + } + + printf("connected to remote host\n"); + + int i; + for (i = 0; i < 20; i++) + { + printf("sending msg\n"); + send(remote_fd, msg, sizeof(msg), 0); + + sleep(downtime); + + downtime += 0.5; + } +} diff --git a/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_recv.c b/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_recv.c new file mode 100644 index 0000000..c2c59ed --- /dev/null +++ b/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_recv.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +int +main(void) +{ + int sock_fd, remote_sock, read_val; + struct sockaddr_in remote_addr; + int opt = 1; + int addrlen = sizeof(remote_addr); + char buffer[1024] = {0}; + + + //create UDP socket, change from SOCK_DGRAM to SOCK_STREAM for a TCP socket + if((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) + { + perror("socket failure\n"); + return 0; + } + + if(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) + { + perror("setsockopt failed\n"); + return 0; + } + + remote_addr.sin_family = AF_INET; + remote_addr.sin_addr.s_addr = INADDR_ANY; + remote_addr.sin_port = htons(PORT); + + if(bind(sock_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + perror("bind failed\n"); + return 0; + } + /* + * Uncomment this section if testing with TCP (make sure to change the socket from + * SOCK_DGRAM to SOCK_STREAM + if(listen(sock_fd, 3) < 0) + { + perror("listen failed\n"); + return 0; + } + + if((remote_sock = accept(sock_fd, (struct sockaddr *)&remote_addr, (socklen_t*)&addrlen)) < 0) + { + perror("accept failed\n"); + return 0; + }*/ + printf("connected to remote host\n"); + + while((read_val = read(sock_fd, buffer, 1024)) != 0) + { + printf("received %s\n", buffer); + sleep(1); + } +} diff --git a/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_send.c b/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_send.c new file mode 100644 index 0000000..b839727 --- /dev/null +++ b/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_send.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +/* + * send varying packet sizes from the Pi to a remote host using UDP sockets + * see comments below for how to convert this code to use TCP sockets + */ + +int +main(void) +{ + int remote_fd, read_val; + struct sockaddr_in remote_addr; + char msg[64]; + char buffer[1024] = {0}; + + msg[0] = 'A'; + + //create UDP socket, change SOCK_DGRAM to SOCK_STREAM for a TCP socket + if((remote_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + perror("failed to create socket\n"); + return 0; + } + + remote_addr.sin_family = AF_INET; + remote_addr.sin_port = htons(PORT); + + if(inet_pton(AF_INET, "192.168.1.214", &remote_addr.sin_addr) <= 0) + { + perror("invalid address/failed to convert address\n"); + return 0; + } + + if(connect(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + perror("failed to connect\n"); + return 0; + } + + printf("connected to remote host\n"); + int i; + + for(i = 0; i < 64; i++) + { + printf("msg to send: %s\n", msg); + send(remote_fd, msg, strlen(msg), 0); + + msg[i + 1] = (char)(msg[0] + i + 1); + + sleep(1); + } +} From 09ba36fb626192e3f99dbdd07bf9e1f16814cdc8 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Thu, 10 Jun 2021 16:22:21 -0400 Subject: [PATCH 09/42] Add remote_send program for the packet frequency tests --- .vscode/settings.json | 10 +- .../Frequency/Remote/remote_recv.c | 138 +++++++++--------- .../Frequency/Remote/remote_send.c | 60 ++++++++ .../Packet_Size/Remote/remote_recv.c | 128 ++++++++-------- .../Packet_Size/Remote/remote_send.c | 130 ++++++++--------- 5 files changed, 263 insertions(+), 203 deletions(-) create mode 100644 PowerModel/SunneeD_network_tests/Frequency/Remote/remote_send.c diff --git a/.vscode/settings.json b/.vscode/settings.json index 792562d..880e35c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ -{ - "files.associations": { - "sunneed_pip_interface.h": "c", - "sunneed_listener.h": "c" - } +{ + "files.associations": { + "sunneed_pip_interface.h": "c", + "sunneed_listener.h": "c" + } } \ No newline at end of file diff --git a/PowerModel/SunneeD_network_tests/Frequency/Remote/remote_recv.c b/PowerModel/SunneeD_network_tests/Frequency/Remote/remote_recv.c index 973190f..bee8cb6 100644 --- a/PowerModel/SunneeD_network_tests/Frequency/Remote/remote_recv.c +++ b/PowerModel/SunneeD_network_tests/Frequency/Remote/remote_recv.c @@ -1,70 +1,70 @@ -#include -#include -#include -#include -#include -#include - -#define PORT 9999 - -int -main(void) -{ - int pi_fd, read_val; -// int pi_sock; //uncomment this if using TCP sockets - struct sockaddr_in pi_addr; - int opt = 1; - int addrlen = sizeof(pi_addr); - char buffer[1024] = {0}; - float downtime = 0.5; - - //create UDP socket, change SOCK_DGRAM to SOCK_STREAM for a TCP socket - if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) - { - perror("failed to create socket\n"); - return 0; - } - - if(setsockopt(pi_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) - { - perror("setsockopt failed\n"); - return 0; - } - - pi_addr.sin_family = AF_INET; - pi_addr.sin_addr.s_addr = INADDR_ANY; - pi_addr.sin_port = htons(PORT); - - if(bind(pI_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) - { - perror("bind failed\n"); - return 0; - } - - /* - * Uncomment this section is using TCP sockets - */ - - /* - if(listen(pi_fd, 3) < 0) - { - perror("listen failed\n"); - return 0; - } - - if((pi_sock = accept(pi_fd, (struct sockaddr *)&pi_addr, (socklen_t*)&addrlen)) < 0) - { - perror("accept failed\n"); - return 0; - } - */ - - printf("connected to pi\n"); - while((read_val = read(pi_fd, buffer, 1024)) > 0) - { - printf("received: %s\n", buffer); - sleep(downtime); - - downtime += 0.5; - } +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +int +main(void) +{ + int pi_fd, read_val; +// int pi_sock; //uncomment this if using TCP sockets + struct sockaddr_in pi_addr; + int opt = 1; + int addrlen = sizeof(pi_addr); + char buffer[1024] = {0}; + float downtime = 0.5; + + //create UDP socket, change SOCK_DGRAM to SOCK_STREAM for a TCP socket + if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + perror("failed to create socket\n"); + return 0; + } + + if(setsockopt(pi_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) + { + perror("setsockopt failed\n"); + return 0; + } + + pi_addr.sin_family = AF_INET; + pi_addr.sin_addr.s_addr = INADDR_ANY; + pi_addr.sin_port = htons(PORT); + + if(bind(pI_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) + { + perror("bind failed\n"); + return 0; + } + + /* + * Uncomment this section is using TCP sockets + */ + + /* + if(listen(pi_fd, 3) < 0) + { + perror("listen failed\n"); + return 0; + } + + if((pi_sock = accept(pi_fd, (struct sockaddr *)&pi_addr, (socklen_t*)&addrlen)) < 0) + { + perror("accept failed\n"); + return 0; + } + */ + + printf("connected to pi\n"); + while((read_val = read(pi_fd, buffer, 1024)) > 0) + { + printf("received: %s\n", buffer); + sleep(downtime); + + downtime += 0.5; + } } \ No newline at end of file diff --git a/PowerModel/SunneeD_network_tests/Frequency/Remote/remote_send.c b/PowerModel/SunneeD_network_tests/Frequency/Remote/remote_send.c new file mode 100644 index 0000000..e315183 --- /dev/null +++ b/PowerModel/SunneeD_network_tests/Frequency/Remote/remote_send.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +/* + * Send packets at various frequencies to the Pi + * + * Uses UDP sockets as written, follow comments below to + * change to TCP sockets + */ + +int +main(void) +{ + int pi_fd, read_val; + struct sockaddr_in pi_addr; + char msg[64]; + msg[0] = 'A'; + float timeout = 0.5; + + //change from SOCK_DGRAM to SOCK_STREAM to create TCP socket + if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + perror("failed to create socket\n"); + exit(0); + } + + pi_addr.sin_family = AF_INET; + pi_addr.sin_port = htons(PORT); + + if(inet_pton(AF_INET, "192.168.1.134", &pi_addr.sin_addr) <= 0) + { + perror("invalid address/failed to convert\n"); + exit(0); + } + + if(connect(pi_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) + { + perror("connection failed\n"); + exit(0); + } + + printf("connected to pi\n"); + int i; + + for(i = 0; i < 20; i++) + { + printf("sending: %s\n", msg); + send(pi_fd, msg, sizeof(msg), 0); + + sleep(timeout); + + timeout += 0.5; + } +} \ No newline at end of file diff --git a/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_recv.c b/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_recv.c index b3c2423..a554721 100644 --- a/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_recv.c +++ b/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_recv.c @@ -1,65 +1,65 @@ -#include -#include -#include -#include -#include -#include - -#define PORT 9999 - -int -main(void) -{ - int pi_fd, read_val; -// int pi_sock; //uncomment this for TCP sockets - struct sockaddr_in pi_addr; - int opt = 1; - int addrlen = sizeof(pi_addr); - char buffer[1024] = {0}; - - //create UDP socket, change from SOCK_DGRAM to SOCK_STREAM for TCP sockets and uncomment the section below - if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) - { - perror("socket failed\n"); - return 0; - } - - if(setsockopt(pi_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) - { - perror("setsockopt failed\n"); - return 0; - } - - pi_addr.sin_family = AF_INET; - pi_addr.sin_addr.s_addr = INADDR_ANY; - pi_addr.sin_port = htons(PORT); - - if(bind(pi_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) - { - perror("bind failed\n"); - return 0; - } - - /* - * Uncomment this section if using TCP sockets, comment out if using UDP sockets - */ - -// if(listen(pi_fd, 3) < 0) -// { -// perror("listen failed\n"); -// return 0; -// } - -// if((pi_sock = accept(pi_fd, (struct sockaddr *)&remote_addr, (socklen_t*)&addrlen)) < 0) -// { -// perror("accept failed\n"); -// return 0; -// } - - printf("connected to pi\n"); - while((read_val = read(pi_fd, buffer, 1024)) > 0) - { - printf("received %s\n", buffer); - sleep(1); - } +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +int +main(void) +{ + int pi_fd, read_val; +// int pi_sock; //uncomment this for TCP sockets + struct sockaddr_in pi_addr; + int opt = 1; + int addrlen = sizeof(pi_addr); + char buffer[1024] = {0}; + + //create UDP socket, change from SOCK_DGRAM to SOCK_STREAM for TCP sockets and uncomment the section below + if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) + { + perror("socket failed\n"); + return 0; + } + + if(setsockopt(pi_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) + { + perror("setsockopt failed\n"); + return 0; + } + + pi_addr.sin_family = AF_INET; + pi_addr.sin_addr.s_addr = INADDR_ANY; + pi_addr.sin_port = htons(PORT); + + if(bind(pi_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) + { + perror("bind failed\n"); + return 0; + } + + /* + * Uncomment this section if using TCP sockets, comment out if using UDP sockets + */ + +// if(listen(pi_fd, 3) < 0) +// { +// perror("listen failed\n"); +// return 0; +// } + +// if((pi_sock = accept(pi_fd, (struct sockaddr *)&remote_addr, (socklen_t*)&addrlen)) < 0) +// { +// perror("accept failed\n"); +// return 0; +// } + + printf("connected to pi\n"); + while((read_val = read(pi_fd, buffer, 1024)) > 0) + { + printf("received %s\n", buffer); + sleep(1); + } } \ No newline at end of file diff --git a/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_send.c b/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_send.c index d4591e4..4b3a539 100644 --- a/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_send.c +++ b/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_send.c @@ -1,66 +1,66 @@ -#include -#include -#include -#include -#include -#include - -#define PORT 9999 - -/* - * Send varying sized packets from this host to the Pi - * uses UDP sockets to keep the packet size consistent with what is being sent - * - * to test with TCP, change SOCK_DGRAM to SOCK_STREAM and see the note for testing with - * TCP in recv_test_size.c on the Pi - */ -int -main(void) -{ - int pi_fd, read_val; - struct sockaddr_in pi_addr; - char msg[64]; - msg[0] = 'A'; - - - - char buffer[1024] = {0}; - - if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) - { - fprintf(stderr, "failed to create socket\n"); - return 0; - } - - pi_addr.sin_family = AF_INET; - pi_addr.sin_port = htons(PORT); - - if(inet_pton(AF_INET, "192.168.1.134", &pi_addr.sin_addr)<=0) - { - fprintf(stderr, "invalid address/failed to convert address\n"); - return 0; - } - - if(connect(pi_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) - { - fprintf(stderr, "connection failed\n"); - return 0; - } - - printf("connected to pi\n"); - int i; - /* - * starting with an empty string, add one char at a time up to 64 - * since chars are 1 byte each, this should be roughly equivalent to - * increasing the packet size by one byte for each send - */ - for(i = 0; i < 64; i++) - { - printf("msg to send: %s\n", msg); - send(pi_fd, msg, sizeof(msg), 0); - - sleep(1); - - msg[i + 1] = (char)(msg[0] + 1 + i); - } +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +/* + * Send varying sized packets from this host to the Pi + * uses UDP sockets to keep the packet size consistent with what is being sent + * + * to test with TCP, change SOCK_DGRAM to SOCK_STREAM and see the note for testing with + * TCP in recv_test_size.c on the Pi + */ +int +main(void) +{ + int pi_fd, read_val; + struct sockaddr_in pi_addr; + char msg[64]; + msg[0] = 'A'; + + + + char buffer[1024] = {0}; + + if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + fprintf(stderr, "failed to create socket\n"); + return 0; + } + + pi_addr.sin_family = AF_INET; + pi_addr.sin_port = htons(PORT); + + if(inet_pton(AF_INET, "192.168.1.134", &pi_addr.sin_addr)<=0) + { + fprintf(stderr, "invalid address/failed to convert address\n"); + return 0; + } + + if(connect(pi_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) + { + fprintf(stderr, "connection failed\n"); + return 0; + } + + printf("connected to pi\n"); + int i; + /* + * starting with an empty string, add one char at a time up to 64 + * since chars are 1 byte each, this should be roughly equivalent to + * increasing the packet size by one byte for each send + */ + for(i = 0; i < 64; i++) + { + printf("msg to send: %s\n", msg); + send(pi_fd, msg, sizeof(msg), 0); + + sleep(1); + + msg[i + 1] = (char)(msg[0] + 1 + i); + } } \ No newline at end of file From 1b2daa1f2726e55360fa07228e4cb2b2ce459e04 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Fri, 18 Jun 2021 14:13:15 -0400 Subject: [PATCH 10/42] Add socket to sunneed overlay Still working on connect, need to push unfinished so that we can reset the pi --- src/client/sunneed_client.c | 130 +++++++++++++++++++++++++++++++++ src/client/sunneed_client.h | 17 +++++ src/overlay/sunneed_overlay.c | 79 ++++++++++++++++++++ src/overlay/sunneed_overlay.h | 13 ++++ src/protobuf/server.proto | 30 ++++++++ src/shared/sunneed_files.h | 1 + src/sunneed_listener.c | 132 ++++++++++++++++++++++++++++++++++ src/sunneed_listener.h | 2 + src/util/pi_send.c | 63 ++++++++++++++++ 9 files changed, 467 insertions(+) create mode 100644 src/util/pi_send.c diff --git a/src/client/sunneed_client.c b/src/client/sunneed_client.c index e5b3149..f2b70bd 100644 --- a/src/client/sunneed_client.c +++ b/src/client/sunneed_client.c @@ -9,6 +9,10 @@ struct { int fd; } locked_paths[MAX_LOCKED_FILES] = { { NULL, 0 } }; +struct { + int dummy_sockfd; +} dummy_sockets[MAX_TENANT_SOCKETS] = { { -1 } }; + nng_socket sunneed_socket; static void @@ -63,6 +67,11 @@ sunneed_client_init(const char *name) { SUNNEED_NNG_TRY(nng_req0_open, != 0, &sunneed_socket); SUNNEED_NNG_TRY(nng_dial, != 0, sunneed_socket, SUNNEED_LISTENER_URL, NULL, 0); + + if(!(client_init)) + { + client_init = 1; + } // Register this client with sunneed. SunneedRequest req = SUNNEED_REQUEST__INIT; req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT; @@ -205,6 +214,127 @@ sunneed_client_remote_write(int fd, const void *data, size_t n_bytes) { return 0; } +int +sunneed_client_socket(int domain, int type, int protocol) +{ + + if(!((domain == AF_INET) || (domain == AF_INET6))) + { + perror("invalid address family, must be ipv4 or ipv6\n"); + exit(0); + } + + if(!((type == 1) || (type == 2))) + { + perror("invalid type, must be SOCK_STREAM(tcp) or SOCK_DGRAM(udp)\n"); + exit(0); + } + + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_SOCKET; + + SocketRequest sock = SOCKET_REQUEST__INIT; + sock.domain = domain; + sock.type = type; + + //TODO: check protocol + sock.protocol = protocol; + + req.socket = &sock; + send_request(&req); + + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_SOCKET); + if(resp == NULL) + { + FATAL(-1, "failed to create socket"); + } + + int i; + for(i = 0; i < MAX_TENANT_SOCKETS; i++) + { + if(dummy_sockets[i].dummy_sockfd == -1) + { + dummy_sockets[i].dummy_sockfd = resp -> socket -> dummy_sockfd; + sunneed_response__free_unpacked(resp, NULL); + printf("added dummy sockfd to table\n"); + return dummy_sockets[i].dummy_sockfd; + } + } + + //TODO: handle not having enough free sockets + return -1; + +} + +int +sunneed_client_is_dummysocket(int sockfd) +{ + int i; + for(i = 0; i < MAX_TENANT_SOCKETS; i++) + { + if(dummy_sockets[i].dummy_sockfd == sockfd) + { + printf("found dummy sockfd\n"); + return 1; + } + } + return 0; +} + +int +sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + printf("SUNNEED CLIENT CONNECT\n\n"); + char address[NI_MAXHOST]; + int port = 0; + printf("get name info was here\n"); + //getnameinfo(addr, addrlen, address, sizeof(address), NULL, 0, 0); + //printf("client connect: got address %s\n", address); + + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_CONNECT; + + ConnectRequest conn = CONNECT_REQUEST__INIT; + conn.port = port; + conn.address = address; + conn.addrlen = (int)addrlen; + + req.connect = &conn; + send_request(&req); + + return 0; +} + +ssize_t +sunneed_client_remote_send(int sockfd, const void *data, size_t len, int flags) +{ + //TODO: check sockfd for real socket, check data, flags, etc + //for now, just tell sunneed to perform a send for us + + if(!sockfd) + { + perror("bad socket fd"); + exit(0); + } + + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_SEND; + + SendRequest send_req = SEND_REQUEST__INIT; + send_req.sockfd = sockfd; + send_req.data.data = malloc(len); + + memcpy(send_req.data.data, data, len); + send_req.data.len = len; + + req.send = &send_req; + send_request(&req); + + free(send_req.data.data); + + return 0; +} + int sunneed_client_disconnect(void) { // TODO Check socket opened. diff --git a/src/client/sunneed_client.h b/src/client/sunneed_client.h index af794dd..9b77315 100644 --- a/src/client/sunneed_client.h +++ b/src/client/sunneed_client.h @@ -5,6 +5,8 @@ #include "../shared/sunneed_files.h" #include +#include +#include #include #include @@ -19,6 +21,7 @@ #define client_printf(FMT, ...) \ printf("\e[38;5;240mclient:\e[0m " FMT, ##__VA_ARGS__) +int client_init = 0; typedef unsigned int sunneed_device_handle_t; int @@ -44,3 +47,17 @@ sunneed_client_on_locked_path_open(int i, char *pathname, int fd); void sunneed_client_debug_print_locked_path_table(void); + +int +sunneed_client_socket(int domain, int type, int protocol); + +int +sunneed_client_is_dummysocket(int sockfd); + +int +sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); + +ssize_t +sunneed_client_remote_send(int sockfd, const void *data, size_t n_bytes, int flags); + + diff --git a/src/overlay/sunneed_overlay.c b/src/overlay/sunneed_overlay.c index ab6a2bf..6cfb09b 100644 --- a/src/overlay/sunneed_overlay.c +++ b/src/overlay/sunneed_overlay.c @@ -53,3 +53,82 @@ write(int fd, const void *buf, size_t count) { return 0; } + +int +socket(int domain, int type, int protocol) +{ + + int sockfd; + + if(!(client_init)) + { + int ret; + SUPER(ret, socket, int, (domain, type, protocol), int, int, int); + return ret; + } + + + + if((domain == AF_INET) || (domain == AF_INET6)) + { + if((type == SOCK_STREAM) || (type == SOCK_DGRAM)) + { + printf("calling sunneed_client_socket\n"); + sockfd = sunneed_client_socket(domain, type, protocol); + printf("got back sockfd %d\n", sockfd); + return sockfd; + + } + } + + printf("calling SUPER for socket\n"); + int ret2; + SUPER(ret2, socket, int, (domain, type, protocol), int, int, int); + return ret2; + +} + +int +connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + char addr_string[1024]; + printf("OVERLAY CONNECT\n"); + if(!(client_init)) + { + printf("client not init-ed\n"); + int fd; + SUPER(fd, connect, int, (sockfd, addr, addrlen), int, const struct sockaddr *, socklen_t); + return fd; + } + + if(sunneed_client_is_dummysocket(sockfd)) + { + //struct sockaddr_in *in_addr = (struct sockaddr_in *)addr; + //inet_pton(AF_INET, in_addr->sin_addr, addr_string); + //printf("overlay connect: destination ip = %s\n",addr_string); + + //getnameinfo(addr, addrlen, addr_string, strlen(addr_string), NULL, 0, 0); + //printf("overlay connect: addr: %s\n", addr_string); + printf("overlay connect: calling sunneed_client_connect\n"); + return sunneed_client_connect(sockfd, addr, addrlen); + }else if(sockfd){ + printf("overlay connect: calling SUPER\n"); + int ret; + SUPER(ret, connect, int, (sockfd, addr, addrlen), int, const struct sockaddr *, socklen_t); + return ret; + }else{ + perror("overlay connect: bad socketfd and not a dummy socket\n"); + return 0; + } + +} + +ssize_t +send(int sockfd, const void *buf, size_t len, int flags) +{ + printf("overlay send %d\n", sockfd); + + sunneed_client_remote_send(sockfd, buf, len, flags); + + return 0; +} diff --git a/src/overlay/sunneed_overlay.h b/src/overlay/sunneed_overlay.h index 0d795ae..eb9c8d7 100644 --- a/src/overlay/sunneed_overlay.h +++ b/src/overlay/sunneed_overlay.h @@ -6,6 +6,9 @@ #include #include #include +#include +#include +#include #include "../client/sunneed_client.h" #include "../shared/sunneed_device_type.h" @@ -38,6 +41,7 @@ RETVAR = (*_base) ARGS; \ } + // This will be run as soon as the library is linked (program start). void __attribute__((constructor)) on_load(); @@ -49,3 +53,12 @@ open(const char *pathname, int flags, mode_t mode); ssize_t write(int fd, const void *buf, size_t count); + +int +socket(int domain, int type, int protocol); + +int +connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); + +ssize_t +send(int sockfd, const void *buf, size_t len, int flags); diff --git a/src/protobuf/server.proto b/src/protobuf/server.proto index eb87459..d5aef21 100644 --- a/src/protobuf/server.proto +++ b/src/protobuf/server.proto @@ -9,6 +9,9 @@ message SunneedRequest { UnregisterClientRequest unregister_client = 2; OpenFileRequest open_file = 3; WriteRequest write = 4; + SocketRequest socket = 5; + ConnectRequest connect = 6; + SendRequest send = 7; } } @@ -28,6 +31,27 @@ message WriteRequest { bytes data = 2; } +message SocketRequest { + int32 domain = 1; + int32 type = 2; + int32 protocol = 3; +} + +message ConnectRequest { + int32 port = 1; + string address = 2; + int32 addrlen = 3; + int32 sockfd = 4; +} + +message SendRequest { + int32 sockfd = 1; + bytes data = 2; + int32 flags = 3; +} + + + // {{{1 Response // Generic container type for messages sent from server to client. @@ -39,6 +63,7 @@ message SunneedResponse { OpenFileResponse open_file = 4; RegisterClientResponse register_client = 5; WriteResponse call_write = 6; + SocketResponse socket = 7; } } @@ -63,6 +88,11 @@ message WriteResponse { uint64 bytes_written = 2; } +message SocketResponse { + //fake socket fd given back by sunneed + int32 dummy_sockfd = 1; +} + import "device.proto"; // Dear whoever is reading this, I hope you like the word "Device"... diff --git a/src/shared/sunneed_files.h b/src/shared/sunneed_files.h index 27fd05d..a9e3ff6 100644 --- a/src/shared/sunneed_files.h +++ b/src/shared/sunneed_files.h @@ -2,5 +2,6 @@ #define _SUNNEED_FILES_H_ #define MAX_LOCKED_FILES 1024 +#define MAX_TENANT_SOCKETS 128 #endif diff --git a/src/sunneed_listener.c b/src/sunneed_listener.c index bb7cab5..ba4118e 100644 --- a/src/sunneed_listener.c +++ b/src/sunneed_listener.c @@ -17,6 +17,12 @@ struct { int fd; } dummy_path_fd_map[MAX_LOCKED_FILES] = { { NULL, 0 } }; +struct { + int id; + int sockfd; + int domain; +} dummy_socket_map[MAX_TENANT_SOCKETS] = { {0, 0, 0} }; + static int get_fd_from_dummy_path(char *path) { for (int i = 0; i < MAX_LOCKED_FILES; i++) @@ -245,6 +251,123 @@ serve_write( return 0; } +static int +serve_socket(SunneedResponse *resp, void *sub_response_buf, SocketRequest *request) +{ + LOG_D("Got request to open a new socket\n"); + + SocketResponse *sock_resp = sub_response_buf; + *sock_resp = (SocketResponse)SOCKET_RESPONSE__INIT; + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_SOCKET; + resp->socket = sock_resp; + //TODO: checks on the request? + + //find open spot in socket table + int i, new_id, sockfd; + for(i = 0; i < MAX_TENANT_SOCKETS; i++) + { + new_id = i; + if(dummy_socket_map[i].id == 0) + { + sockfd = socket(request->domain, request->type, request->protocol); + if(sockfd) + { + dummy_socket_map[i].id = new_id; + dummy_socket_map[i].sockfd = sockfd; + dummy_socket_map[i].domain = request->domain; + LOG_D("Socket created successfully\n"); + break; + }else{ + LOG_E("Failed to create socket. domain %d type %d protocol %d\n", request->domain, request->type, request->protocol); + return 1; + } + } + } + + if(new_id == MAX_TENANT_SOCKETS) + { + LOG_E("no more sockets can be created\n"); + sock_resp->dummy_sockfd = -1; + return 1; + } + + sock_resp->dummy_sockfd = new_id; + return 0; +} + +static int +serve_connect(nng_pipe pipe, ConnectRequest *request) +{ + //lookup real sockfd in dummy_socket_map and create new socket + LOG_D("got connect request\n"); + int i, sockfd, domain; + struct sockaddr_in remote_addr; + sockfd = 0; + for(i = 0; i < MAX_TENANT_SOCKETS; i++) + { + if(dummy_socket_map[i].id == request->sockfd) + { + LOG_D("found socket for pipe %d\n", pipe.id); + sockfd = dummy_socket_map[i].sockfd; + domain = dummy_socket_map[i].domain; + break; + } + } + if(!(sockfd)) + { + LOG_E("failed to find socket for pipe %d\n", pipe.id); + return 1; + } + + remote_addr.sin_family = domain; + remote_addr.sin_port = htons(request->port); + + //TODO: check address/port + if(inet_pton(domain, request->address, &remote_addr.sin_addr) <= 0) + { + LOG_E("invalid address/domain or failed to convert\n"); + return 1; + } + + if(connect(sockfd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + LOG_E("Failed to connect to %s\n", request->address); + return 1; + } + + LOG_D("connected to remote host: %s\n", request->address); + + return 0; + + +} + +static int +serve_send(struct sunneed_tenant *tenant, SendRequest *request) +{ + //TODO: formulate response, for now just log and call send + + LOG_D("Got request from %d to send %ld bytes", tenant->id, sizeof(request->data.len)); + + //TODO: probably want more checks here as well + + int sockfd = request->sockfd; + if(!(sockfd)) + { + LOG_E("Bad socket descriptor: %d\n", sockfd); + return 1; + } + if(!(request->data.data)) + { + LOG_E("couldnt get data from request\n"); + return 1; + } + send(sockfd, request->data.data, request->data.len, request->flags); + + LOG_D("Sent data from tenant %d\n", tenant->id); + + return 0; +} static void report_nng_error(const char *func, int rv) { LOG_E("nng error: (%s) %s", func, nng_strerror(rv)); @@ -344,6 +467,15 @@ sunneed_listen(void) { #endif ret = serve_write(&resp, sub_resp_buf, tenant, request->write); break; + case SUNNEED_REQUEST__MESSAGE_TYPE_SOCKET: + ret = serve_socket(&resp, sub_resp_buf, request->socket); + break; + case SUNNEED_REQUEST__MESSAGE_TYPE_CONNECT: + ret = serve_connect(pipe, request->connect); + break; + case SUNNEED_REQUEST__MESSAGE_TYPE_SEND: + ret = serve_send(tenant, request->send); + break; default: LOG_W("Received request with invalid type %d", request->message_type_case); ret = -1; diff --git a/src/sunneed_listener.h b/src/sunneed_listener.h index d80d974..1dd03b1 100644 --- a/src/sunneed_listener.h +++ b/src/sunneed_listener.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include diff --git a/src/util/pi_send.c b/src/util/pi_send.c new file mode 100644 index 0000000..c1eef55 --- /dev/null +++ b/src/util/pi_send.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +/* + * Send packets at various frequencies, allowing the network card to idle for varying times + * starts at 500 ms and increases by 500 ms every iteration + * + * Uses UDP sockets as written, follow the notes in comments below to change to TCP sockets + */ + +int +main(void) +{ + int remote_fd, read_val; + struct sockaddr_in remote_addr; + char msg[64] = {'A'}; + float downtime = 0.5; + + //create UDP socket, change SOCK_DGRAM to SOCK_STREAM to create TCP sockets + + + //while(1); + if((remote_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + perror("socket failed\n"); + exit(0); + } + + remote_addr.sin_family = AF_INET; + remote_addr.sin_port = htons(PORT); + + if(inet_pton(AF_INET, "192.168.1.214", &remote_addr.sin_addr) <= 0) + { + perror("invalid address/failed to convert\n"); + exit(0); + } + + printf("pi_send: calling connect\n"); + if(connect(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + perror("failed to connect\n"); + exit(0); + } + + printf("connected to remote host\n"); + + int i; + for (i = 0; i < 20; i++) + { + printf("sending msg\n"); + send(remote_fd, msg, sizeof(msg), 0); + + sleep(downtime); + + downtime += 0.5; + } +} From 1f235168fb8ed1b6c1a3396f15b43812e6f4b2b1 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 21 Jun 2021 12:24:23 -0900 Subject: [PATCH 11/42] Fix socket and connect overlays Socket and connect in tandem register a fake socketfd with sunneed and create a real socket within the sunneed process to serve future send requests Need to fix a malloc issue with consecutive sends --- src/client/sunneed_client.c | 48 +++++++++++++++++++++---------- src/client/sunneed_client.h | 3 ++ src/overlay/sunneed_overlay.c | 22 ++++++++------- src/sunneed_listener.c | 53 ++++++++++++++++++++++------------- src/sunneed_listener.h | 1 + src/sunneed_power.c | 3 ++ src/util/pi_send.c | 8 ++---- 7 files changed, 88 insertions(+), 50 deletions(-) diff --git a/src/client/sunneed_client.c b/src/client/sunneed_client.c index f2b70bd..5e1bc0c 100644 --- a/src/client/sunneed_client.c +++ b/src/client/sunneed_client.c @@ -27,7 +27,6 @@ nngfatal(const char *func, int rv) { static void send_request(SunneedRequest *req) { nng_msg *msg; - int req_len = sunneed_request__get_packed_size(req); void *buf = malloc(req_len); if (!buf) @@ -158,6 +157,7 @@ sunneed_client_on_locked_path_open(int i, char *pathname, int fd) { if (fd <= 0) FATAL(-1, "illegal FD"); + //toSend[REQUETS_PER_PWR_LOG * 3] = pwr_change; locked_paths[i].path = pathname; locked_paths[i].fd = fd; @@ -224,7 +224,7 @@ sunneed_client_socket(int domain, int type, int protocol) exit(0); } - if(!((type == 1) || (type == 2))) + if(!((type == SOCK_STREAM) || (type == SOCK_DGRAM))) { perror("invalid type, must be SOCK_STREAM(tcp) or SOCK_DGRAM(udp)\n"); exit(0); @@ -241,6 +241,7 @@ sunneed_client_socket(int domain, int type, int protocol) sock.protocol = protocol; req.socket = &sock; + printf("sending request to sunneed\n"); send_request(&req); SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_SOCKET); @@ -262,6 +263,7 @@ sunneed_client_socket(int domain, int type, int protocol) } //TODO: handle not having enough free sockets + sunneed_response__free_unpacked(resp, NULL); return -1; } @@ -274,7 +276,6 @@ sunneed_client_is_dummysocket(int sockfd) { if(dummy_sockets[i].dummy_sockfd == sockfd) { - printf("found dummy sockfd\n"); return 1; } } @@ -284,12 +285,29 @@ sunneed_client_is_dummysocket(int sockfd) int sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { - printf("SUNNEED CLIENT CONNECT\n\n"); - char address[NI_MAXHOST]; + char host_name[NI_MAXHOST]; + char address[INET_ADDRSTRLEN]; int port = 0; - printf("get name info was here\n"); - //getnameinfo(addr, addrlen, address, sizeof(address), NULL, 0, 0); - //printf("client connect: got address %s\n", address); + struct hostent *requested_host; + char **addr_pointer; + + //get host name from sockaddr struct + getnameinfo(addr, addrlen, host_name, NI_MAXHOST, NULL, 0, 0); + + requested_host = gethostbyname(host_name); + if(requested_host == NULL) + { + fprintf(stderr, "client connect: failed to get host by name errno %d\n", h_errno); + return -1; + } + + //loop through addr list to find a valid address for the host + for(addr_pointer = requested_host->h_addr_list; *addr_pointer; addr_pointer++) + { + inet_ntop(AF_INET, (void *)*addr_pointer, address, sizeof(address)); + printf("client connect: got address %s\n", address); + } + SunneedRequest req = SUNNEED_REQUEST__INIT; req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_CONNECT; @@ -297,12 +315,12 @@ sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrle ConnectRequest conn = CONNECT_REQUEST__INIT; conn.port = port; conn.address = address; - conn.addrlen = (int)addrlen; + conn.addrlen = sizeof(address); req.connect = &conn; send_request(&req); - return 0; + return 1; } ssize_t @@ -311,20 +329,20 @@ sunneed_client_remote_send(int sockfd, const void *data, size_t len, int flags) //TODO: check sockfd for real socket, check data, flags, etc //for now, just tell sunneed to perform a send for us - if(!sockfd) + if(!sunneed_client_is_dummysocket(sockfd)) { - perror("bad socket fd"); + perror("called sunneed send with a non-sunneed socket\n"); exit(0); } - + printf("client send: data %s len %d sizeof data %ld\n", data, len, sizeof(data)); SunneedRequest req = SUNNEED_REQUEST__INIT; req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_SEND; SendRequest send_req = SEND_REQUEST__INIT; send_req.sockfd = sockfd; - send_req.data.data = malloc(len); + send_req.data.data = malloc(sizeof(data)); - memcpy(send_req.data.data, data, len); + memcpy(send_req.data.data, data, sizeof(data)); send_req.data.len = len; req.send = &send_req; diff --git a/src/client/sunneed_client.h b/src/client/sunneed_client.h index 9b77315..e2c28a1 100644 --- a/src/client/sunneed_client.h +++ b/src/client/sunneed_client.h @@ -7,6 +7,9 @@ #include #include #include +#include +#include + #include #include diff --git a/src/overlay/sunneed_overlay.c b/src/overlay/sunneed_overlay.c index 6cfb09b..4deb48d 100644 --- a/src/overlay/sunneed_overlay.c +++ b/src/overlay/sunneed_overlay.c @@ -81,7 +81,6 @@ socket(int domain, int type, int protocol) } } - printf("calling SUPER for socket\n"); int ret2; SUPER(ret2, socket, int, (domain, type, protocol), int, int, int); return ret2; @@ -91,11 +90,8 @@ socket(int domain, int type, int protocol) int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { - char addr_string[1024]; - printf("OVERLAY CONNECT\n"); if(!(client_init)) { - printf("client not init-ed\n"); int fd; SUPER(fd, connect, int, (sockfd, addr, addrlen), int, const struct sockaddr *, socklen_t); return fd; @@ -109,10 +105,8 @@ connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) //getnameinfo(addr, addrlen, addr_string, strlen(addr_string), NULL, 0, 0); //printf("overlay connect: addr: %s\n", addr_string); - printf("overlay connect: calling sunneed_client_connect\n"); return sunneed_client_connect(sockfd, addr, addrlen); }else if(sockfd){ - printf("overlay connect: calling SUPER\n"); int ret; SUPER(ret, connect, int, (sockfd, addr, addrlen), int, const struct sockaddr *, socklen_t); return ret; @@ -126,9 +120,17 @@ connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) ssize_t send(int sockfd, const void *buf, size_t len, int flags) { - printf("overlay send %d\n", sockfd); - - sunneed_client_remote_send(sockfd, buf, len, flags); - + + printf("OVERLAY SEND\n"); + if(sunneed_client_is_dummysocket(sockfd)) + { + printf("sunneed client send\n"); + sunneed_client_remote_send(sockfd, buf, len, flags); + }else if(sockfd){ + printf("SUPER send\n"); + int ret; + SUPER(ret, send, int, (sockfd, buf, len, flags), int, const void *, size_t, int); + return ret; + } return 0; } diff --git a/src/sunneed_listener.c b/src/sunneed_listener.c index ba4118e..f3864ff 100644 --- a/src/sunneed_listener.c +++ b/src/sunneed_listener.c @@ -7,8 +7,8 @@ extern struct sunneed_device devices[]; extern struct sunneed_tenant tenants[]; extern const char *locked_file_paths[]; - -/** +extern int errno; +/*n * Maps dummy paths (typically sent by clients during a read or write) to FDs pointing to the real device, held by * sunneed. */ @@ -21,7 +21,7 @@ struct { int id; int sockfd; int domain; -} dummy_socket_map[MAX_TENANT_SOCKETS] = { {0, 0, 0} }; +} dummy_socket_map[MAX_TENANT_SOCKETS] = { {-1, -1, 0} }; static int get_fd_from_dummy_path(char *path) { @@ -50,6 +50,17 @@ tenant_of_pipe(int pipe_id) { return NULL; } +int +lookup_socket(sockfd) +{ + int i; + for(i = 0; i < MAX_TENANT_SOCKETS; i++) + if(dummy_socket_map[i].id == sockfd) + return dummy_socket_map[i].sockfd; + return 0; + +} + // Get the PID of a pipe and use that to create a new sunneed tenant with that ID. // TODO: This shouldn't always create a new tenant, since we want multiple processes // mapped to one tenant. @@ -208,7 +219,7 @@ serve_open_file( } // TODO Free this - sub_resp->path = malloc(strlen(dummypath)); + sub_resp->path = malloc(strlen(dummypath) + 1); strncpy(sub_resp->path, dummypath, strlen(dummypath) + 1); } else { // They requested a non-dummy file. @@ -267,7 +278,7 @@ serve_socket(SunneedResponse *resp, void *sub_response_buf, SocketRequest *reque for(i = 0; i < MAX_TENANT_SOCKETS; i++) { new_id = i; - if(dummy_socket_map[i].id == 0) + if(dummy_socket_map[i].id == -1) { sockfd = socket(request->domain, request->type, request->protocol); if(sockfd) @@ -276,23 +287,17 @@ serve_socket(SunneedResponse *resp, void *sub_response_buf, SocketRequest *reque dummy_socket_map[i].sockfd = sockfd; dummy_socket_map[i].domain = request->domain; LOG_D("Socket created successfully\n"); - break; + sock_resp->dummy_sockfd = new_id; + return 0; }else{ LOG_E("Failed to create socket. domain %d type %d protocol %d\n", request->domain, request->type, request->protocol); return 1; } } } + LOG_E("Maximum tenant sockets have been created\n"); + return 1; - if(new_id == MAX_TENANT_SOCKETS) - { - LOG_E("no more sockets can be created\n"); - sock_resp->dummy_sockfd = -1; - return 1; - } - - sock_resp->dummy_sockfd = new_id; - return 0; } static int @@ -300,6 +305,9 @@ serve_connect(nng_pipe pipe, ConnectRequest *request) { //lookup real sockfd in dummy_socket_map and create new socket LOG_D("got connect request\n"); + + //TODO: figure out getting the port from the tenant, hardcoded to get data from network + int i, sockfd, domain; struct sockaddr_in remote_addr; sockfd = 0; @@ -320,7 +328,7 @@ serve_connect(nng_pipe pipe, ConnectRequest *request) } remote_addr.sin_family = domain; - remote_addr.sin_port = htons(request->port); + remote_addr.sin_port = htons(9999); //TODO: check address/port if(inet_pton(domain, request->address, &remote_addr.sin_addr) <= 0) @@ -348,10 +356,10 @@ serve_send(struct sunneed_tenant *tenant, SendRequest *request) //TODO: formulate response, for now just log and call send LOG_D("Got request from %d to send %ld bytes", tenant->id, sizeof(request->data.len)); - + LOG_D("Msg to send %s\n", request->data.data); //TODO: probably want more checks here as well - int sockfd = request->sockfd; + int sockfd = lookup_socket(request->sockfd); if(!(sockfd)) { LOG_E("Bad socket descriptor: %d\n", sockfd); @@ -362,9 +370,14 @@ serve_send(struct sunneed_tenant *tenant, SendRequest *request) LOG_E("couldnt get data from request\n"); return 1; } - send(sockfd, request->data.data, request->data.len, request->flags); + if((send(sockfd, request->data.data, request->data.len, request->flags)) < 0) + { + LOG_E("Failed to send data for tenant %d error %d\n", tenant->id, errno); + }else{ + + LOG_D("Sent data from tenant %d\n", tenant->id); + } - LOG_D("Sent data from tenant %d\n", tenant->id); return 0; } diff --git a/src/sunneed_listener.h b/src/sunneed_listener.h index 1dd03b1..bdab472 100644 --- a/src/sunneed_listener.h +++ b/src/sunneed_listener.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include diff --git a/src/sunneed_power.c b/src/sunneed_power.c index d8dea82..e1a74b8 100644 --- a/src/sunneed_power.c +++ b/src/sunneed_power.c @@ -73,6 +73,8 @@ sunneed_quantum_begin(void) { return 1; } + power_usage_evs->next = NULL; + LOG_I("Started quantum %d", current_quantum.id); current_quantum.is_active = true; @@ -131,6 +133,7 @@ sunneed_quantum_end(void) { sunneed_worker_thread_result_t sunneed_quantum_worker(__attribute__((unused)) void *args) { int ret; + power_usage_evs = NULL; while (true) { if ((ret = sunneed_quantum_begin()) != 0) { goto end; diff --git a/src/util/pi_send.c b/src/util/pi_send.c index c1eef55..f9c4769 100644 --- a/src/util/pi_send.c +++ b/src/util/pi_send.c @@ -19,7 +19,7 @@ main(void) { int remote_fd, read_val; struct sockaddr_in remote_addr; - char msg[64] = {'A'}; + char *msg = "heyo"; float downtime = 0.5; //create UDP socket, change SOCK_DGRAM to SOCK_STREAM to create TCP sockets @@ -41,20 +41,18 @@ main(void) exit(0); } - printf("pi_send: calling connect\n"); if(connect(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) { perror("failed to connect\n"); exit(0); } - printf("connected to remote host\n"); int i; for (i = 0; i < 20; i++) { - printf("sending msg\n"); - send(remote_fd, msg, sizeof(msg), 0); + printf("sending msg %s\n", msg); + send(remote_fd, msg, strlen(msg), 0); sleep(downtime); From fa8813eea20eba75b814f2865a11486798a855fe Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Tue, 29 Jun 2021 15:33:13 -0400 Subject: [PATCH 12/42] Fix double free issue and commit working overlay There was some confusion about the use of the nng api, we had some double frees due to nng_sendmsg freeing the message after sending. If the callee frees the message after calling send, it creates this double free issue. --- ext/nng | 2 +- src/client/sunneed_client.c | 11 +++++------ src/overlay/sunneed_overlay.c | 3 --- src/sunneed_listener.c | 17 ++++++++++++----- src/util/pi_send.c | 22 ++++++++++++++-------- 5 files changed, 32 insertions(+), 23 deletions(-) diff --git a/ext/nng b/ext/nng index d0ac83e..2a841af 160000 --- a/ext/nng +++ b/ext/nng @@ -1 +1 @@ -Subproject commit d0ac83e34bde86e1fa888677adf7c99217e3205a +Subproject commit 2a841afb2601e8bc8e2127cb196d64fd5d6fa8a9 diff --git a/src/client/sunneed_client.c b/src/client/sunneed_client.c index 5e1bc0c..8a04a23 100644 --- a/src/client/sunneed_client.c +++ b/src/client/sunneed_client.c @@ -34,11 +34,10 @@ send_request(SunneedRequest *req) { sunneed_request__pack(req, buf); SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &msg, req_len); - SUNNEED_NNG_TRY(nng_msg_insert, != 0, msg, buf, req_len); + SUNNEED_NNG_TRY(nng_msg_insert, != 0, msg, buf, req_len); SUNNEED_NNG_TRY(nng_sendmsg, != 0, sunneed_socket, msg, 0); - free(buf); - nng_msg_free(msg); + free(buf); } static SunneedResponse * @@ -334,15 +333,15 @@ sunneed_client_remote_send(int sockfd, const void *data, size_t len, int flags) perror("called sunneed send with a non-sunneed socket\n"); exit(0); } - printf("client send: data %s len %d sizeof data %ld\n", data, len, sizeof(data)); + printf("client send: len %d\n", len); SunneedRequest req = SUNNEED_REQUEST__INIT; req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_SEND; SendRequest send_req = SEND_REQUEST__INIT; send_req.sockfd = sockfd; - send_req.data.data = malloc(sizeof(data)); + send_req.data.data = malloc(len + 1); - memcpy(send_req.data.data, data, sizeof(data)); + memcpy(send_req.data.data, data, len); send_req.data.len = len; req.send = &send_req; diff --git a/src/overlay/sunneed_overlay.c b/src/overlay/sunneed_overlay.c index 4deb48d..f8c37f0 100644 --- a/src/overlay/sunneed_overlay.c +++ b/src/overlay/sunneed_overlay.c @@ -121,13 +121,10 @@ ssize_t send(int sockfd, const void *buf, size_t len, int flags) { - printf("OVERLAY SEND\n"); if(sunneed_client_is_dummysocket(sockfd)) { - printf("sunneed client send\n"); sunneed_client_remote_send(sockfd, buf, len, flags); }else if(sockfd){ - printf("SUPER send\n"); int ret; SUPER(ret, send, int, (sockfd, buf, len, flags), int, const void *, size_t, int); return ret; diff --git a/src/sunneed_listener.c b/src/sunneed_listener.c index f3864ff..1062892 100644 --- a/src/sunneed_listener.c +++ b/src/sunneed_listener.c @@ -51,7 +51,7 @@ tenant_of_pipe(int pipe_id) { } int -lookup_socket(sockfd) +lookup_socket(int sockfd) { int i; for(i = 0; i < MAX_TENANT_SOCKETS; i++) @@ -355,8 +355,8 @@ serve_send(struct sunneed_tenant *tenant, SendRequest *request) { //TODO: formulate response, for now just log and call send - LOG_D("Got request from %d to send %ld bytes", tenant->id, sizeof(request->data.len)); - LOG_D("Msg to send %s\n", request->data.data); + LOG_D("Got request from %d to send %ld bytes", tenant->id, request->data.len); + //LOG_D("Msg to send %s\n", request->data.data); //TODO: probably want more checks here as well int sockfd = lookup_socket(request->sockfd); @@ -406,9 +406,14 @@ sunneed_listen(void) { LOG_I("Starting listener loop..."); // Make a socket and attach it to the sunneed URL. + SUNNEED_NNG_TRY_RET(nng_rep0_open, != 0, &sock); + SUNNEED_NNG_TRY_RET(nng_listen, < 0, sock, SUNNEED_LISTENER_URL, NULL, 0); + + + // Buffer for `serve_` methods to write their sub-response to. void *sub_resp_buf = malloc(SUB_RESPONSE_BUF_SZ); // TODO Check malloc. @@ -508,11 +513,13 @@ sunneed_listen(void) { SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &resp_msg, resp_len); SUNNEED_NNG_TRY(nng_msg_insert, != 0, resp_msg, resp_buf, resp_len); - SUNNEED_NNG_TRY(nng_sendmsg, != 0, sock, resp_msg, 0); + SUNNEED_NNG_TRY_RET(nng_sendmsg, != 0, sock, resp_msg, 0); + + //nng_msg_free(resp_msg); end: sunneed_request__free_unpacked(request, NULL); - nng_msg_free(resp_msg); + nng_msg_free(msg); } diff --git a/src/util/pi_send.c b/src/util/pi_send.c index f9c4769..f4f00b1 100644 --- a/src/util/pi_send.c +++ b/src/util/pi_send.c @@ -19,8 +19,9 @@ main(void) { int remote_fd, read_val; struct sockaddr_in remote_addr; - char *msg = "heyo"; - float downtime = 0.5; + char *msg; + int packet_sizes[8] = {1, 4, 8, 16, 32, 64, 128, 256}; + int index; //create UDP socket, change SOCK_DGRAM to SOCK_STREAM to create TCP sockets @@ -48,14 +49,19 @@ main(void) } - int i; - for (i = 0; i < 20; i++) + int i, j; + for (i = 0; i < 2000; i++) { - printf("sending msg %s\n", msg); + index = rand() % 8; + msg = (char *) malloc(packet_sizes[index] * sizeof(char)); + for(j = 0; j < packet_sizes[index]-1; j++) + { + msg[j] = 'a'; + } + msg[packet_sizes[index] - 1] = '\0'; send(remote_fd, msg, strlen(msg), 0); - sleep(downtime); - - downtime += 0.5; + free(msg); + sleep(0.5); } } From c94f0a78df1146ac34b108f3a049a444f1fdf9bf Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Tue, 29 Jun 2021 16:14:11 -0400 Subject: [PATCH 13/42] Add battery babysitter logging to send overlay, need to test on the pi --- src/log.h | 8 ++++---- src/sunneed.h | 1 + src/sunneed_core.c | 2 ++ src/sunneed_listener.c | 29 ++++++++++++++++++++++++++++- src/sunneed_power.h | 2 ++ 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/log.h b/src/log.h index 9e6d987..8a811d4 100644 --- a/src/log.h +++ b/src/log.h @@ -16,14 +16,14 @@ FILE *logfile, *stepper_pwr_logfile; #define LOGL_WARN "W\e[0;33m" #define LOGL_ERROR "E\e[0;31m" -#ifndef LOG_PWR -#define LOG_PWR(LEVEL, MESSAGE, ...) \ +#ifndef LOG_PWR_EVENT +#define LOG_PWR_EVENT(LEVEL, MESSAGE, ...) \ { \ FILE *_logfile = stepper_pwr_logfile; \ if (!logfile) { \ return; \ } \ - fprintf(_logfile, MESSAGE) \ + fprintf(_logfile, MESSAGE); \ } #endif #define LOG(LEVEL, MESSAGE, ...) \ @@ -43,6 +43,6 @@ FILE *logfile, *stepper_pwr_logfile; #define LOG_I(MESSAGE, ...) LOG(LOGL_INFO, MESSAGE, ##__VA_ARGS__); #define LOG_W(MESSAGE, ...) LOG(LOGL_WARN, MESSAGE, ##__VA_ARGS__); #define LOG_E(MESSAGE, ...) LOG(LOGL_ERROR, MESSAGE, ##__VA_ARGS__); -#define LOG_P(MESSAGE, ...) LOG_PWR(LOGL_INFO, MESSAGE, ##__VA_ARGS__); +#define LOG_P(MESSAGE, ...) LOG_PWR_EVENT(LOGL_INFO, MESSAGE, ##__VA_ARGS__); #endif diff --git a/src/sunneed.h b/src/sunneed.h index cdfe09e..e88c232 100644 --- a/src/sunneed.h +++ b/src/sunneed.h @@ -8,6 +8,7 @@ #ifdef LOG_PWR #define REQS_PER_LOG 10 int last_capacity, curr_capacity, reqs_since_last_log; + double last_send, time_since_send; #endif typedef void* sunneed_worker_thread_result_t; diff --git a/src/sunneed_core.c b/src/sunneed_core.c index fff798d..b9fec68 100644 --- a/src/sunneed_core.c +++ b/src/sunneed_core.c @@ -66,7 +66,9 @@ spawn_worker_threads(void) { void sunneed_init(void) { pip = pip_info(); + #ifdef LOG_PWR last_capacity = present_power(); + #endif } int diff --git a/src/sunneed_listener.c b/src/sunneed_listener.c index 1062892..9c556a5 100644 --- a/src/sunneed_listener.c +++ b/src/sunneed_listener.c @@ -358,7 +358,7 @@ serve_send(struct sunneed_tenant *tenant, SendRequest *request) LOG_D("Got request from %d to send %ld bytes", tenant->id, request->data.len); //LOG_D("Msg to send %s\n", request->data.data); //TODO: probably want more checks here as well - + int sockfd = lookup_socket(request->sockfd); if(!(sockfd)) { @@ -370,6 +370,21 @@ serve_send(struct sunneed_tenant *tenant, SendRequest *request) LOG_E("couldnt get data from request\n"); return 1; } + + #ifdef LOG_PWR + if(last_send == 0) + { + last_send = clock(); + LOG_P ("%f ", (double) last_send / CLOCKS_PER_SEC); + }else{ + time_since_send = (double)((clock() - last_send)/CLOCKS_PER_SEC); + LOG_P("%f ", time_since_send); + } + + LOG_P("%d ", request->data.len); + + #endif + if((send(sockfd, request->data.data, request->data.len, request->flags)) < 0) { LOG_E("Failed to send data for tenant %d error %d\n", tenant->id, errno); @@ -378,6 +393,16 @@ serve_send(struct sunneed_tenant *tenant, SendRequest *request) LOG_D("Sent data from tenant %d\n", tenant->id); } + #ifdef LOG_PWR + curr_capacity = present_power(); + + double change = ((double) last_capacity - curr_capacity) - (time_since_send * PASSIVE_PWR_PER_SEC); + LOG_P("%f\n", change); + + last_capacity = curr_capacity; + last_send = clock(); + + #endif return 0; } @@ -391,7 +416,9 @@ sunneed_listen(void) { SUNNEED_NNG_SET_ERROR_REPORT_FUNC(report_nng_error); #ifdef LOG_PWR + last_capacity = present_power(); int capacity_change; + last_send = 0; #endif // Initialize client states. diff --git a/src/sunneed_power.h b/src/sunneed_power.h index 73df058..d93c13a 100644 --- a/src/sunneed_power.h +++ b/src/sunneed_power.h @@ -15,6 +15,8 @@ #define QUANTUMS_RINGBUF_SZ 16 +#define PASSIVE_PWR_PER_SEC 0.07667 + // TODO This is waaaaaaaaaaaaaaaaaaaaay too big. #define QUANTUM_DURATION_MS 5000 From 158c264ecb754fe40c1ce49abab8ff4cf409b977 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Thu, 8 Jul 2021 15:22:49 -0400 Subject: [PATCH 14/42] Add most recent version of network overlay Need to reclone on pi to test with Jon's memory fixes --- .gitignore | 110 +- .gitmodules | 24 +- .vscode/settings.json | 7 +- LICENSE | 678 +++++----- Makefile | 374 +++--- .../Frequency/Pi/pi_recv.c | 144 +-- .../Frequency/Pi/pi_send.c | 118 +- .../Packet_Size/Pi/pi_recv.c | 126 +- .../Packet_Size/Pi/pi_send.c | 118 +- README.md | 112 +- azure-pipelines.yml | 118 +- design/Build/Build.md | 78 +- design/scheduling/schedulerModel_draft1.tex | 68 +- ext/Makefile | 38 +- misc/install_dependencies | 8 +- misc/tools/Dockerfile | 72 +- misc/wpa_supplicant_real.service | 20 +- res/logo.svg | 392 +++--- runtime_tests | 38 +- src/.gitignore | 2 +- src/client/sunneed_client.c | 803 ++++++------ src/client/sunneed_client.h | 132 +- src/device/test_broken.c | 30 +- src/device/test_file_lock.c | 54 +- src/log.h | 96 +- src/overlay/sunneed_overlay.c | 270 ++-- src/overlay/sunneed_overlay.h | 130 +- src/pip/bq27441.c | 24 +- src/protobuf/Makefile | 14 +- src/protobuf/device.proto | 10 +- src/protobuf/server.proto | 216 ++-- src/shared/sunneed_device_interface.h | 56 +- src/shared/sunneed_device_type.h | 32 +- src/shared/sunneed_files.h | 14 +- src/shared/sunneed_ipc.h | 168 +-- src/shared/sunneed_pip_interface.h | 72 +- src/shared/sunneed_testing.h | 6 +- src/sunneed.h | 32 +- src/sunneed_core.c | 294 ++--- src/sunneed_core.h | 84 +- src/sunneed_device.c | 132 +- src/sunneed_device.h | 100 +- src/sunneed_listener.c | 1143 +++++++++-------- src/sunneed_listener.h | 66 +- src/sunneed_loader.c | 501 ++++---- src/sunneed_loader.h | 64 +- src/sunneed_power.c | 304 ++--- src/sunneed_power.h | 106 +- src/sunneed_proc.c | 238 ++-- src/sunneed_proc.h | 104 +- src/sunneed_runtime_test_collection.h | 20 +- src/sunneed_test.c | 36 +- src/sunneed_test.h | 46 +- src/util/overlay_tester.c | 64 +- src/util/pi_send.c | 169 ++- test/.gitignore | 6 +- test/Makefile | 38 +- test/nng.c | 86 +- test/scripts/generate-test-header | 156 +-- test/test_main.c | 28 +- 60 files changed, 4353 insertions(+), 4236 deletions(-) diff --git a/.gitignore b/.gitignore index d67fbc6..2424abb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,55 +1,55 @@ -# Prerequisites -*.d - -# Object files -*.o -*.ko -*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf - -build/ -sunneed_log.txt +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +build/ +sunneed_log.txt diff --git a/.gitmodules b/.gitmodules index 804c283..c9c3a70 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,12 @@ -[submodule "ext/libbq27441"] - path = ext/libbq27441 - url = https://github.com/zacharied/libbq27441.git -[submodule "ext/nng"] - path = ext/nng - url = https://github.com/nanomsg/nng.git -[submodule "test/munit"] - path = test/munit - url = https://github.com/nemequ/munit.git -[submodule "ext/SunneeD_dev_drivers"] - path = ext/SunneeD_dev_drivers - url = https://github.com/jonterry9/SunneeD_dev_drivers.git +[submodule "ext/libbq27441"] + path = ext/libbq27441 + url = https://github.com/zacharied/libbq27441.git +[submodule "ext/nng"] + path = ext/nng + url = https://github.com/nanomsg/nng.git +[submodule "test/munit"] + path = test/munit + url = https://github.com/nemequ/munit.git +[submodule "ext/SunneeD_dev_drivers"] + path = ext/SunneeD_dev_drivers + url = https://github.com/jonterry9/SunneeD_dev_drivers.git diff --git a/.vscode/settings.json b/.vscode/settings.json index 880e35c..c2d9d76 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,11 @@ { "files.associations": { "sunneed_pip_interface.h": "c", - "sunneed_listener.h": "c" + "sunneed_listener.h": "c", + "cstring": "c", + "memory": "c", + "xmemory": "c", + "xstring": "c", + "xutility": "c" } } \ No newline at end of file diff --git a/LICENSE b/LICENSE index d159169..89e08fb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,339 +1,339 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Makefile b/Makefile index 551d570..6256e08 100644 --- a/Makefile +++ b/Makefile @@ -1,187 +1,187 @@ -# Builds the main sunneed executable. - -ifeq ($(origin CC),default) - export CC = gcc -endif - -CFLAGS ?= -Wall -Wextra -g -PROTOC ?= protoc-c - -SUNNEED_BUILD_TYPE ?= devel -SUNNEED_BUILD_PIP ?= bq27441 -SUNNEED_BUILD_OUT_DIR ?= build -SUNNEED_BUILD_BIN_FILE ?= sunneed -SUNNEED_BUILD_CLIENT_LIB_NAME ?= libsunneedclient -SUNNEED_BUILD_OVERLAY_LIB_NAME ?= sunneed_overlay - -export SOURCE_FORMATTER = clang-format -style=file -i - -export cflags_deps = -I$(PWD)/$(ext_dir)/nng/include -L$(PWD)/$(ext_dir)/nng/build -lnng -lpthread -ldl -lprotobuf-c -latomic - -ifeq ($(SUNNEED_BUILD_TYPE),devel) - util_cflags = -Wl,-rpath,$(CURDIR)/$(clientlib_out_dir) -endif - -src_dir = src -sources = $(wildcard $(src_dir)/*.c) - -ext_dir = ext - -out_dir = $(SUNNEED_BUILD_OUT_DIR) -bin_file = $(SUNNEED_BUILD_BIN_FILE) - -pip_out_dir = $(out_dir) -pip_obj = $(pip_out_dir)/pip.o -pip_name = $(SUNNEED_BUILD_PIP) - -clientlib_sources = $(wildcard $(src_dir)/client/*.c) -clientlib_out_dir = $(out_dir)/client -clientlib_obj = $(clientlib_out_dir)/$(SUNNEED_BUILD_CLIENT_LIB_NAME).so - -overlay_sources = $(wildcard $(src_dir)/overlay/*.c) -overlay_out_dir = $(out_dir) -overlay_obj = $(overlay_out_dir)/$(SUNNEED_BUILD_OVERLAY_LIB_NAME).so -overlay_testing_obj = $(patsubst %.so,%_testing.so,$(overlay_obj)) -overlay_runner = $(out_dir)/run-with-overlay - -protobuf_dir = $(src_dir)/protobuf -protobuf_sources = $(wildcard $(protobuf_dir)/*.proto) -protobuf_out_files = $(foreach src,$(protobuf_sources),$(subst !!!, ,$(join $(src:.proto=.pb-c.c!!!),$(src:.proto=.pb-c.h)))) -protobuf_out_dir = $(src_dir)/protobuf/c -protobuf_out_sources = $(wildcard $(protobuf_out_dir)/*.c) - -export test_home = test -export test_runner_name = run-tests -test_runner = $(test_home)/$(test_runner_name) -runtime_tests_runner = ./runtime_tests - -device_objs = $(patsubst %.c, %.o, $(wildcard $(src_dir)/device/*.c)) -util_objs = $(patsubst %.c, %.o, $(wildcard $(src_dir)/util/*.c)) - -all: pre-all main overlay util - -log_pwr: pre-all main_log_pwr overlay util - -main_log_pwr: ext protobuf pip devices - $(call section_title,main executable) - $(CC) $(CFLAGS) -DTESTING -DLOG_PWR $(sources) $(protobuf_out_sources) $(cflags_deps) $(pip_obj) -o $(out_dir)/$(bin_file) - -pre-all: - @echo "Starting all build..." - -main: ext protobuf pip devices - $(call section_title,main executable) - $(CC) $(CFLAGS) -DTESTING $(sources) $(protobuf_out_sources) $(cflags_deps) $(pip_obj) -o $(out_dir)/$(bin_file) - -pip: pre-pip $(src_dir)/pip/$(pip_name).c - $(CC) $(CFLAGS) -c $(src_dir)/pip/$(pip_name).c -o $(pip_obj) -pre-pip: - $(call section_title,pip) - -devices: pre-devices ext $(device_objs) -pre-devices: - $(call section_title,devices) - -util: clientlib pre-util $(util_objs) -pre-util: - $(call section_title,utils) - -$(src_dir)/util/%.o: $(src_dir)/util/%.c - $(CC) $(CFLAGS) -o $(patsubst $(src_dir)/util/%.o, $(out_dir)/%, $@) $^ $(protobuf_out_sources) -L$(clientlib_out_dir) -lsunneedclient $(util_cflags) $(cflags_deps) - -$(src_dir)/client/%.o: $(src_dir)/client/%.c - $(CC) $(CFLAGS) -o $(patsubst $(src_dir)/util/%.o, $(out_dir)/%, $@) $^ $(protobuf_out_sources) $(cflags_deps) - -$(src_dir)/device/%.o: $(src_dir)/device/%.c - @if [ ! -d "$(out_dir)/device" ]; then mkdir "$(out_dir)/device"; fi - $(CC) $(CFLAGS) -g -shared -o $(patsubst $(src_dir)/device/%.o, $(out_dir)/device/%.so, $@) -fPIC $^ $(cflags_deps) - -protobuf: pre-protobuf $(protobuf_out_files) - @rm -rf "$(protobuf_out_dir)" && mkdir "$(protobuf_out_dir)" - mv $(protobuf_out_files) $(protobuf_out_dir) -pre-protobuf: - $(call section_title,protobuf) - -$(protobuf_out_files): $(protobuf_sources) - $(MAKE) --no-print-directory -C $(protobuf_dir) - -clientlib: ext - $(call section_title,client library) - @if [ ! -d "$(out_dir)/client" ]; then mkdir "$(out_dir)/client"; fi - $(CC) $(CFLAGS) -c -fPIC -o $(out_dir)/client/clientlib.o $(clientlib_sources) $(cflags_deps) - $(CC) $(CFLAGS) -shared -o $(clientlib_obj) $(out_dir)/client/clientlib.o - -# Run the runtime tests -runtime_test: main - $(runtime_tests_runner) - -test: tests - $(test_runner) - -tests: - make -C $(test_home) - -# Note that we compile two overlay libraries. One is a tester, which contains additional output meant for -# debugging/testing purposes. -overlay: clientlib - $(call section_title,overlay) - $(CC) $(CFLAGS) -g -fPIC -shared $(overlay_sources) -o $(overlay_obj) $(cflags_deps) - $(CC) $(CFLAGS) -g -fPIC -shared -DTESTING $(overlay_sources) -o $(overlay_testing_obj) $(cflags_deps) - @echo Generate overlay runscript at $(overlay_runner) - @echo "#!/usr/bin/env bash\n$(overlay_runscript_content)" > $(overlay_runner) - chmod +x $(overlay_runner) - -clean: - rm -rf "$(out_dir)"/* - rm -rf "$(protobuf_out_dir)"/* - $(MAKE) -C $(test_home) clean - @echo '=============================================================' - @echo '= External library files were not cleaned. =' - @echo '= Please run `make -C ext clean` if you wish to clean them. =' - @echo '=============================================================' - -format: - $(SOURCE_FORMATTER) $(shell find '$(src_dir)' -not -path '$(protobuf_dir)/*' -type f -regex '.*\.[ch]') - $(MAKE) -C $(test_home) format - -tags: - ctags -R src/* - -ext: - $(call section_title,dependencies) - $(MAKE) -C $(ext_dir) - -.PHONY: all pip util test runtime_test clean format ext tags - -LeftParens := ( -RightParens := ) - -# Prints a nice little header graphic in the form: -# -# ======================= -# === Building === -# ======================= -# -# When using in the Makefile, in simple cases you can just call this at the beginning of the target. For more -# complicated targets, however, you will have to make a `pre-` target and run that before resolving the rest -# of the target. Remember to put your `pre-` target *after* the targets that your depends upon, but *before* the list -# of files (if any). See the `main`, `util`, and `devices` targets for examples of usage in different situations. -section_count := 1 -num_sections := $(shell grep -E '^\s*\$$\$(LeftParens)\s*call\s+section_title,' Makefile | wc -l) -define section_title - @echo - $(eval _var := $(section_count)/$(num_sections) $(1)) - $(eval _len := $(shell x="$(_var)"; echo -n $${#x})) - @printf '=%.0s' $(shell seq -16 $(_len)) - @echo - @echo === $(section_count)/$(num_sections) Building $(1) === - @printf '=%.0s' $(shell seq -16 $(_len)) - @echo - $(eval section_count := $(shell expr $(section_count) + 1)) -endef - -ifeq ($(SUNNEED_BUILD_TYPE),devel) - overlay_runscript_content := "gdb --args env LD_PRELOAD=$(abspath $(overlay_testing_obj)) $$\@" -else - overlay_runscript_content := "LD_PRELOAD=$(abspath $(overlay_obj)) $$\@" -endif +# Builds the main sunneed executable. + +ifeq ($(origin CC),default) + export CC = gcc +endif + +CFLAGS ?= -Wall -Wextra -g +PROTOC ?= protoc-c + +SUNNEED_BUILD_TYPE ?= devel +SUNNEED_BUILD_PIP ?= bq27441 +SUNNEED_BUILD_OUT_DIR ?= build +SUNNEED_BUILD_BIN_FILE ?= sunneed +SUNNEED_BUILD_CLIENT_LIB_NAME ?= libsunneedclient +SUNNEED_BUILD_OVERLAY_LIB_NAME ?= sunneed_overlay + +export SOURCE_FORMATTER = clang-format -style=file -i + +export cflags_deps = -I$(PWD)/$(ext_dir)/nng/include -L$(PWD)/$(ext_dir)/nng/build -lnng -lpthread -ldl -lprotobuf-c -latomic + +ifeq ($(SUNNEED_BUILD_TYPE),devel) + util_cflags = -Wl,-rpath,$(CURDIR)/$(clientlib_out_dir) +endif + +src_dir = src +sources = $(wildcard $(src_dir)/*.c) + +ext_dir = ext + +out_dir = $(SUNNEED_BUILD_OUT_DIR) +bin_file = $(SUNNEED_BUILD_BIN_FILE) + +pip_out_dir = $(out_dir) +pip_obj = $(pip_out_dir)/pip.o +pip_name = $(SUNNEED_BUILD_PIP) + +clientlib_sources = $(wildcard $(src_dir)/client/*.c) +clientlib_out_dir = $(out_dir)/client +clientlib_obj = $(clientlib_out_dir)/$(SUNNEED_BUILD_CLIENT_LIB_NAME).so + +overlay_sources = $(wildcard $(src_dir)/overlay/*.c) +overlay_out_dir = $(out_dir) +overlay_obj = $(overlay_out_dir)/$(SUNNEED_BUILD_OVERLAY_LIB_NAME).so +overlay_testing_obj = $(patsubst %.so,%_testing.so,$(overlay_obj)) +overlay_runner = $(out_dir)/run-with-overlay + +protobuf_dir = $(src_dir)/protobuf +protobuf_sources = $(wildcard $(protobuf_dir)/*.proto) +protobuf_out_files = $(foreach src,$(protobuf_sources),$(subst !!!, ,$(join $(src:.proto=.pb-c.c!!!),$(src:.proto=.pb-c.h)))) +protobuf_out_dir = $(src_dir)/protobuf/c +protobuf_out_sources = $(wildcard $(protobuf_out_dir)/*.c) + +export test_home = test +export test_runner_name = run-tests +test_runner = $(test_home)/$(test_runner_name) +runtime_tests_runner = ./runtime_tests + +device_objs = $(patsubst %.c, %.o, $(wildcard $(src_dir)/device/*.c)) +util_objs = $(patsubst %.c, %.o, $(wildcard $(src_dir)/util/*.c)) + +all: pre-all main overlay util + +log_pwr: pre-all main_log_pwr overlay util + +main_log_pwr: ext protobuf pip devices + $(call section_title,main executable) + $(CC) $(CFLAGS) -DTESTING -DLOG_PWR $(sources) $(protobuf_out_sources) $(cflags_deps) $(pip_obj) -o $(out_dir)/$(bin_file) + +pre-all: + @echo "Starting all build..." + +main: ext protobuf pip devices + $(call section_title,main executable) + $(CC) $(CFLAGS) -DTESTING $(sources) $(protobuf_out_sources) $(cflags_deps) $(pip_obj) -o $(out_dir)/$(bin_file) + +pip: pre-pip $(src_dir)/pip/$(pip_name).c + $(CC) $(CFLAGS) -c $(src_dir)/pip/$(pip_name).c -o $(pip_obj) +pre-pip: + $(call section_title,pip) + +devices: pre-devices ext $(device_objs) +pre-devices: + $(call section_title,devices) + +util: clientlib pre-util $(util_objs) +pre-util: + $(call section_title,utils) + +$(src_dir)/util/%.o: $(src_dir)/util/%.c + $(CC) $(CFLAGS) -o $(patsubst $(src_dir)/util/%.o, $(out_dir)/%, $@) $^ $(protobuf_out_sources) -L$(clientlib_out_dir) -lsunneedclient $(util_cflags) $(cflags_deps) + +$(src_dir)/client/%.o: $(src_dir)/client/%.c + $(CC) $(CFLAGS) -o $(patsubst $(src_dir)/util/%.o, $(out_dir)/%, $@) $^ $(protobuf_out_sources) $(cflags_deps) + +$(src_dir)/device/%.o: $(src_dir)/device/%.c + @if [ ! -d "$(out_dir)/device" ]; then mkdir "$(out_dir)/device"; fi + $(CC) $(CFLAGS) -g -shared -o $(patsubst $(src_dir)/device/%.o, $(out_dir)/device/%.so, $@) -fPIC $^ $(cflags_deps) + +protobuf: pre-protobuf $(protobuf_out_files) + @rm -rf "$(protobuf_out_dir)" && mkdir "$(protobuf_out_dir)" + mv $(protobuf_out_files) $(protobuf_out_dir) +pre-protobuf: + $(call section_title,protobuf) + +$(protobuf_out_files): $(protobuf_sources) + $(MAKE) --no-print-directory -C $(protobuf_dir) + +clientlib: ext + $(call section_title,client library) + @if [ ! -d "$(out_dir)/client" ]; then mkdir "$(out_dir)/client"; fi + $(CC) $(CFLAGS) -c -fPIC -o $(out_dir)/client/clientlib.o $(clientlib_sources) $(cflags_deps) + $(CC) $(CFLAGS) -shared -o $(clientlib_obj) $(out_dir)/client/clientlib.o + +# Run the runtime tests +runtime_test: main + $(runtime_tests_runner) + +test: tests + $(test_runner) + +tests: + make -C $(test_home) + +# Note that we compile two overlay libraries. One is a tester, which contains additional output meant for +# debugging/testing purposes. +overlay: clientlib + $(call section_title,overlay) + $(CC) $(CFLAGS) -g -fPIC -shared $(overlay_sources) -o $(overlay_obj) $(cflags_deps) + $(CC) $(CFLAGS) -g -fPIC -shared -DTESTING $(overlay_sources) -o $(overlay_testing_obj) $(cflags_deps) + @echo Generate overlay runscript at $(overlay_runner) + @echo "#!/usr/bin/env bash\n$(overlay_runscript_content)" > $(overlay_runner) + chmod +x $(overlay_runner) + +clean: + rm -rf "$(out_dir)"/* + rm -rf "$(protobuf_out_dir)"/* + $(MAKE) -C $(test_home) clean + @echo '=============================================================' + @echo '= External library files were not cleaned. =' + @echo '= Please run `make -C ext clean` if you wish to clean them. =' + @echo '=============================================================' + +format: + $(SOURCE_FORMATTER) $(shell find '$(src_dir)' -not -path '$(protobuf_dir)/*' -type f -regex '.*\.[ch]') + $(MAKE) -C $(test_home) format + +tags: + ctags -R src/* + +ext: + $(call section_title,dependencies) + $(MAKE) -C $(ext_dir) + +.PHONY: all pip util test runtime_test clean format ext tags + +LeftParens := ( +RightParens := ) + +# Prints a nice little header graphic in the form: +# +# ======================= +# === Building === +# ======================= +# +# When using in the Makefile, in simple cases you can just call this at the beginning of the target. For more +# complicated targets, however, you will have to make a `pre-` target and run that before resolving the rest +# of the target. Remember to put your `pre-` target *after* the targets that your depends upon, but *before* the list +# of files (if any). See the `main`, `util`, and `devices` targets for examples of usage in different situations. +section_count := 1 +num_sections := $(shell grep -E '^\s*\$$\$(LeftParens)\s*call\s+section_title,' Makefile | wc -l) +define section_title + @echo + $(eval _var := $(section_count)/$(num_sections) $(1)) + $(eval _len := $(shell x="$(_var)"; echo -n $${#x})) + @printf '=%.0s' $(shell seq -16 $(_len)) + @echo + @echo === $(section_count)/$(num_sections) Building $(1) === + @printf '=%.0s' $(shell seq -16 $(_len)) + @echo + $(eval section_count := $(shell expr $(section_count) + 1)) +endef + +ifeq ($(SUNNEED_BUILD_TYPE),devel) + overlay_runscript_content := "gdb --args env LD_PRELOAD=$(abspath $(overlay_testing_obj)) $$\@" +else + overlay_runscript_content := "LD_PRELOAD=$(abspath $(overlay_obj)) $$\@" +endif diff --git a/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_recv.c b/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_recv.c index 15ccc8d..0e8017b 100644 --- a/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_recv.c +++ b/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_recv.c @@ -1,72 +1,72 @@ -#include -#include -#include -#include -#include -#include - -#define PORT 9999 - -int -main(void) -{ - int remote_fd, read_val; -// int pi_sock; //uncomment for TCP sockets - struct sockaddr_in remote_addr; - int opt = 1; - int addrlen = sizeof(remote_addr); - char buffer[1024] = {0}; - float timeout = 0.5; - - //change from SOCK_DGRAM to SOCK_STREAM for TCP sockets - if((remote_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) - { - perror("socket failed\n"); - exit(0); - } - - if(setsockopt(remote_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) - { - perror("setsockopt failed\n"); - exit(0); - } - - remote_addr.sin_family = AF_INET; - remote_addr.sin_addr.s_addr = INADDR_ANY; - remote_addr.sin_port = htons(PORT); - - if(bind(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) - { - perror("bind failed\n"); - exit(0); - } - - /* - * Uncomment this section if using TCP sockets, comment out for UDP sockets - */ - - /* - if(listen(remote_fd, 3) < 0) - { - perror("listen failed\n"); - exit(0); - } - - if((pi_sock = accept(pi_fd, (struct sockaddr *)&remote_addr, (socklen_t*)&addrlen)) < 0) - { - perror("accept failed\n"); - exit(0); - } - */ - - printf("connected to remote host\n"); - int i; - - while((read_val = read(remote_fd, buffer, 1024)) > 0) - { - printf("received: %s\n", buffer); - sleep(timeout); - - timeout += 0.5; - } -} +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +int +main(void) +{ + int remote_fd, read_val; +// int pi_sock; //uncomment for TCP sockets + struct sockaddr_in remote_addr; + int opt = 1; + int addrlen = sizeof(remote_addr); + char buffer[1024] = {0}; + float timeout = 0.5; + + //change from SOCK_DGRAM to SOCK_STREAM for TCP sockets + if((remote_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) + { + perror("socket failed\n"); + exit(0); + } + + if(setsockopt(remote_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) + { + perror("setsockopt failed\n"); + exit(0); + } + + remote_addr.sin_family = AF_INET; + remote_addr.sin_addr.s_addr = INADDR_ANY; + remote_addr.sin_port = htons(PORT); + + if(bind(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + perror("bind failed\n"); + exit(0); + } + + /* + * Uncomment this section if using TCP sockets, comment out for UDP sockets + */ + + /* + if(listen(remote_fd, 3) < 0) + { + perror("listen failed\n"); + exit(0); + } + + if((pi_sock = accept(pi_fd, (struct sockaddr *)&remote_addr, (socklen_t*)&addrlen)) < 0) + { + perror("accept failed\n"); + exit(0); + } + */ + + printf("connected to remote host\n"); + int i; + + while((read_val = read(remote_fd, buffer, 1024)) > 0) + { + printf("received: %s\n", buffer); + sleep(timeout); + + timeout += 0.5; + } +} diff --git a/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_send.c b/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_send.c index c0bb7f2..42aca72 100644 --- a/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_send.c +++ b/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_send.c @@ -1,59 +1,59 @@ -#include -#include -#include -#include -#include -#include - -#define PORT 9999 - -/* - * Send packets at various frequencies, allowing the network card to idle for varying times - * starts at 500 ms and increases by 500 ms every iteration - * - * Uses UDP sockets as written, follow the notes in comments below to change to TCP sockets - */ - -int -main(void) -{ - int remote_fd, read_val; - struct sockaddr_in remote_addr; - char msg[64] = {'A'}; - float downtime = 0.5; - - //create UDP socket, change SOCK_DGRAM to SOCK_STREAM to create TCP sockets - if((remote_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) - { - perror("socket failed\n"); - exit(0); - } - - remote_addr.sin_family = AF_INET; - remote_addr.sin_port = htons(PORT); - - if(inet_pton(AF_INET, "192.168.1.214", &remote_addr.sin_addr) <= 0) - { - perror("invalid address/failed to convert\n"); - exit(0); - } - - if(connect(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) - { - perror("failed to connect\n"); - exit(0); - } - - printf("connected to remote host\n"); - - int i; - for (i = 0; i < 20; i++) - { - printf("sending msg\n"); - send(remote_fd, msg, sizeof(msg), 0); - - sleep(downtime); - - downtime += 0.5; - } -} +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +/* + * Send packets at various frequencies, allowing the network card to idle for varying times + * starts at 500 ms and increases by 500 ms every iteration + * + * Uses UDP sockets as written, follow the notes in comments below to change to TCP sockets + */ + +int +main(void) +{ + int remote_fd, read_val; + struct sockaddr_in remote_addr; + char msg[64] = {'A'}; + float downtime = 0.5; + + //create UDP socket, change SOCK_DGRAM to SOCK_STREAM to create TCP sockets + if((remote_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + perror("socket failed\n"); + exit(0); + } + + remote_addr.sin_family = AF_INET; + remote_addr.sin_port = htons(PORT); + + if(inet_pton(AF_INET, "192.168.1.214", &remote_addr.sin_addr) <= 0) + { + perror("invalid address/failed to convert\n"); + exit(0); + } + + if(connect(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + perror("failed to connect\n"); + exit(0); + } + + printf("connected to remote host\n"); + + int i; + for (i = 0; i < 20; i++) + { + printf("sending msg\n"); + send(remote_fd, msg, sizeof(msg), 0); + + sleep(downtime); + + downtime += 0.5; + } +} diff --git a/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_recv.c b/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_recv.c index c2c59ed..8c46589 100644 --- a/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_recv.c +++ b/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_recv.c @@ -1,63 +1,63 @@ -#include -#include -#include -#include -#include -#include - -#define PORT 9999 - -int -main(void) -{ - int sock_fd, remote_sock, read_val; - struct sockaddr_in remote_addr; - int opt = 1; - int addrlen = sizeof(remote_addr); - char buffer[1024] = {0}; - - - //create UDP socket, change from SOCK_DGRAM to SOCK_STREAM for a TCP socket - if((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) - { - perror("socket failure\n"); - return 0; - } - - if(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) - { - perror("setsockopt failed\n"); - return 0; - } - - remote_addr.sin_family = AF_INET; - remote_addr.sin_addr.s_addr = INADDR_ANY; - remote_addr.sin_port = htons(PORT); - - if(bind(sock_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) - { - perror("bind failed\n"); - return 0; - } - /* - * Uncomment this section if testing with TCP (make sure to change the socket from - * SOCK_DGRAM to SOCK_STREAM - if(listen(sock_fd, 3) < 0) - { - perror("listen failed\n"); - return 0; - } - - if((remote_sock = accept(sock_fd, (struct sockaddr *)&remote_addr, (socklen_t*)&addrlen)) < 0) - { - perror("accept failed\n"); - return 0; - }*/ - printf("connected to remote host\n"); - - while((read_val = read(sock_fd, buffer, 1024)) != 0) - { - printf("received %s\n", buffer); - sleep(1); - } -} +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +int +main(void) +{ + int sock_fd, remote_sock, read_val; + struct sockaddr_in remote_addr; + int opt = 1; + int addrlen = sizeof(remote_addr); + char buffer[1024] = {0}; + + + //create UDP socket, change from SOCK_DGRAM to SOCK_STREAM for a TCP socket + if((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) + { + perror("socket failure\n"); + return 0; + } + + if(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) + { + perror("setsockopt failed\n"); + return 0; + } + + remote_addr.sin_family = AF_INET; + remote_addr.sin_addr.s_addr = INADDR_ANY; + remote_addr.sin_port = htons(PORT); + + if(bind(sock_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + perror("bind failed\n"); + return 0; + } + /* + * Uncomment this section if testing with TCP (make sure to change the socket from + * SOCK_DGRAM to SOCK_STREAM + if(listen(sock_fd, 3) < 0) + { + perror("listen failed\n"); + return 0; + } + + if((remote_sock = accept(sock_fd, (struct sockaddr *)&remote_addr, (socklen_t*)&addrlen)) < 0) + { + perror("accept failed\n"); + return 0; + }*/ + printf("connected to remote host\n"); + + while((read_val = read(sock_fd, buffer, 1024)) != 0) + { + printf("received %s\n", buffer); + sleep(1); + } +} diff --git a/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_send.c b/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_send.c index b839727..66ea788 100644 --- a/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_send.c +++ b/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_send.c @@ -1,59 +1,59 @@ -#include -#include -#include -#include -#include -#include - -#define PORT 9999 - -/* - * send varying packet sizes from the Pi to a remote host using UDP sockets - * see comments below for how to convert this code to use TCP sockets - */ - -int -main(void) -{ - int remote_fd, read_val; - struct sockaddr_in remote_addr; - char msg[64]; - char buffer[1024] = {0}; - - msg[0] = 'A'; - - //create UDP socket, change SOCK_DGRAM to SOCK_STREAM for a TCP socket - if((remote_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) - { - perror("failed to create socket\n"); - return 0; - } - - remote_addr.sin_family = AF_INET; - remote_addr.sin_port = htons(PORT); - - if(inet_pton(AF_INET, "192.168.1.214", &remote_addr.sin_addr) <= 0) - { - perror("invalid address/failed to convert address\n"); - return 0; - } - - if(connect(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) - { - perror("failed to connect\n"); - return 0; - } - - printf("connected to remote host\n"); - int i; - - for(i = 0; i < 64; i++) - { - printf("msg to send: %s\n", msg); - send(remote_fd, msg, strlen(msg), 0); - - msg[i + 1] = (char)(msg[0] + i + 1); - - sleep(1); - } -} +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +/* + * send varying packet sizes from the Pi to a remote host using UDP sockets + * see comments below for how to convert this code to use TCP sockets + */ + +int +main(void) +{ + int remote_fd, read_val; + struct sockaddr_in remote_addr; + char msg[64]; + char buffer[1024] = {0}; + + msg[0] = 'A'; + + //create UDP socket, change SOCK_DGRAM to SOCK_STREAM for a TCP socket + if((remote_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + perror("failed to create socket\n"); + return 0; + } + + remote_addr.sin_family = AF_INET; + remote_addr.sin_port = htons(PORT); + + if(inet_pton(AF_INET, "192.168.1.214", &remote_addr.sin_addr) <= 0) + { + perror("invalid address/failed to convert address\n"); + return 0; + } + + if(connect(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + perror("failed to connect\n"); + return 0; + } + + printf("connected to remote host\n"); + int i; + + for(i = 0; i < 64; i++) + { + printf("msg to send: %s\n", msg); + send(remote_fd, msg, strlen(msg), 0); + + msg[i + 1] = (char)(msg[0] + i + 1); + + sleep(1); + } +} diff --git a/README.md b/README.md index 8c35af8..ec82026 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,56 @@ -

- Sunny D logo - -

- -[![Build Status](https://dev.azure.com/gwsystems/sunneed/_apis/build/status/gwsystems.sunneed?branchName=master)](https://dev.azure.com/gwsystems/sunneed/_build/latest?definitionId=3&branchName=master) - -`sunneed` (pronounced "Sunny D") is a framework for tracking and managing the distribution of power consumption of individual -processes in multi-tenant computing environments. In systems with uncertain reserves of power available, such as a computer -powered by a solar battery, it is impossible to guarantee unlimited power to each tenant. The basis of this project is to make -toolset for tenants to run **power-constrained code** on the system, limited by a power budget described by `sunneed`. - -# Installation - -Below is instructions for getting a basic instance of `sunneed` running on a Linux host. Package names are intentionally -left ambiguous due to the variety of naming schemes for packages across distros; see the `misc/install_dependencies` -file for package names on Ubuntu 18.04. - -If you are running Ubuntu 18.04 (and likely other versions of Ubuntu and Debian), you may run`misc/install_dependencies` -(with root privileges) and then skip to [Dependencies of Dependencies](#dependencies-of-dependencies). - -## Getting the dependencies - -`sunneed` uses protobufs for I2C. Specifically, it uses the `protobufs-c` library. Both the `protoc-c` compiler and the -`libprotobuf-c` headers and libraries must be installed. - -### Dependencies of dependencies - -We also need to manually compile some dependencies. Run `git submodule update --init --recursive`. This will download -the code for NNG and libbq27441, which must be compiled manually on common distros. - -For building NNG, you will need CMake > 3.13 and Ninja. These are available as packages on common distros. - -For building libbq27441, you will need the headers for the Linux I2C library. This comes under different names depending -on the distro; you want whichever package gives you the file `/usr/include/linux/i2c-dev.h`. - -## Compilation - -Once you have all the dependencies in place, we can begin compilation. First, create the output directory by running -`mkdir build`. Then, begin compilation by running `make` in the root of the `sunneed` directory. This should compile all -the local dependencies, the `sunneed` runtime overlay, client library, and main executable. - -After all this, there should be a file `build/sunneed` in the `sunneed` directory. This is the core `sunneed` binary. -You can run this, and then run one of the example programs to test connectivity to `sunneed`. - - -## Running with overlay tester - -Once you have successfully built sunneed and verified that the `build/sunneed` binary has been made, we can run the overlay tester to verify that sunneed is correctly intercepting `open()` and `write()` requests. *Pro tip: open a second terminal so that you don't have to type around the sunneed log prints.* Execute the sunneed binary by running `sudo ./sunneed &`. Note that the `&` is not necessary if you are running in a second terminal. -![Sunneed](./res/Run_SunneeD.JPG) - -In your second terminal or after sunneed begins execution, we can run the overlay_tester located in `sunneed/build/`. Before running it, we have to `LD_PRELOAD` SunneeD's overlay so that the `open()` and `write()` calls are redirected correctly. You can do all of this with the following command: `LD_PRELOAD= ./build/overlay_tester` from the sunneed directory. **Note: the argument to LD_PRELOAD must be the ABSOLUTE path of sunneed_overlay.so, using a relative path will not work.** - -![Overlay_test](./res/Run_Overlay_LDPRELOAD.JPG) - +

+ Sunny D logo + +

+ +[![Build Status](https://dev.azure.com/gwsystems/sunneed/_apis/build/status/gwsystems.sunneed?branchName=master)](https://dev.azure.com/gwsystems/sunneed/_build/latest?definitionId=3&branchName=master) + +`sunneed` (pronounced "Sunny D") is a framework for tracking and managing the distribution of power consumption of individual +processes in multi-tenant computing environments. In systems with uncertain reserves of power available, such as a computer +powered by a solar battery, it is impossible to guarantee unlimited power to each tenant. The basis of this project is to make +toolset for tenants to run **power-constrained code** on the system, limited by a power budget described by `sunneed`. + +# Installation + +Below is instructions for getting a basic instance of `sunneed` running on a Linux host. Package names are intentionally +left ambiguous due to the variety of naming schemes for packages across distros; see the `misc/install_dependencies` +file for package names on Ubuntu 18.04. + +If you are running Ubuntu 18.04 (and likely other versions of Ubuntu and Debian), you may run`misc/install_dependencies` +(with root privileges) and then skip to [Dependencies of Dependencies](#dependencies-of-dependencies). + +## Getting the dependencies + +`sunneed` uses protobufs for I2C. Specifically, it uses the `protobufs-c` library. Both the `protoc-c` compiler and the +`libprotobuf-c` headers and libraries must be installed. + +### Dependencies of dependencies + +We also need to manually compile some dependencies. Run `git submodule update --init --recursive`. This will download +the code for NNG and libbq27441, which must be compiled manually on common distros. + +For building NNG, you will need CMake > 3.13 and Ninja. These are available as packages on common distros. + +For building libbq27441, you will need the headers for the Linux I2C library. This comes under different names depending +on the distro; you want whichever package gives you the file `/usr/include/linux/i2c-dev.h`. + +## Compilation + +Once you have all the dependencies in place, we can begin compilation. First, create the output directory by running +`mkdir build`. Then, begin compilation by running `make` in the root of the `sunneed` directory. This should compile all +the local dependencies, the `sunneed` runtime overlay, client library, and main executable. + +After all this, there should be a file `build/sunneed` in the `sunneed` directory. This is the core `sunneed` binary. +You can run this, and then run one of the example programs to test connectivity to `sunneed`. + + +## Running with overlay tester + +Once you have successfully built sunneed and verified that the `build/sunneed` binary has been made, we can run the overlay tester to verify that sunneed is correctly intercepting `open()` and `write()` requests. *Pro tip: open a second terminal so that you don't have to type around the sunneed log prints.* Execute the sunneed binary by running `sudo ./sunneed &`. Note that the `&` is not necessary if you are running in a second terminal. +![Sunneed](./res/Run_SunneeD.JPG) + +In your second terminal or after sunneed begins execution, we can run the overlay_tester located in `sunneed/build/`. Before running it, we have to `LD_PRELOAD` SunneeD's overlay so that the `open()` and `write()` calls are redirected correctly. You can do all of this with the following command: `LD_PRELOAD= ./build/overlay_tester` from the sunneed directory. **Note: the argument to LD_PRELOAD must be the ABSOLUTE path of sunneed_overlay.so, using a relative path will not work.** + +![Overlay_test](./res/Run_Overlay_LDPRELOAD.JPG) + diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d760cb6..9713f3d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,59 +1,59 @@ -# Starter pipeline -# Start with a minimal pipeline that you can customize to build and deploy your code. -# Add steps that build, run tests, deploy, and more: -# https://aka.ms/yaml - -variables: -- name: IS_AZURE_PIPELINE - value: true - -trigger: -- master - -pool: - vmImage: 'ubuntu-18.04' - -container: grahamschock/suneed_env:latest - -steps: -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - git clone https://github.com/gwsystems/sunneed.git - git submodule update --init --recursive - ls -la - displayName: 'Clone system' - -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - mkdir build - make - displayName: 'Build sunneed' - -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - rm /usr/bin/gcc - ln -s /usr/bin/gcc-8 /usr/bin/gcc - make tests - displayName: 'Build unit tests' - -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - ls -la - make test - displayName: 'Run unit tests' - -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - cd src/sunneed - make runtime_test - displayName: 'Run runtime tests' +# Starter pipeline +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + +variables: +- name: IS_AZURE_PIPELINE + value: true + +trigger: +- master + +pool: + vmImage: 'ubuntu-18.04' + +container: grahamschock/suneed_env:latest + +steps: +- task: Bash@3 + inputs: + targetType: 'inline' + script: | + git clone https://github.com/gwsystems/sunneed.git + git submodule update --init --recursive + ls -la + displayName: 'Clone system' + +- task: Bash@3 + inputs: + targetType: 'inline' + script: | + mkdir build + make + displayName: 'Build sunneed' + +- task: Bash@3 + inputs: + targetType: 'inline' + script: | + rm /usr/bin/gcc + ln -s /usr/bin/gcc-8 /usr/bin/gcc + make tests + displayName: 'Build unit tests' + +- task: Bash@3 + inputs: + targetType: 'inline' + script: | + ls -la + make test + displayName: 'Run unit tests' + +- task: Bash@3 + inputs: + targetType: 'inline' + script: | + cd src/sunneed + make runtime_test + displayName: 'Run runtime tests' diff --git a/design/Build/Build.md b/design/Build/Build.md index c41c389..cceb2fb 100644 --- a/design/Build/Build.md +++ b/design/Build/Build.md @@ -1,40 +1,40 @@ -# SunneeD Build - -## Parts - -* Raspberry Pi 0w - -* ULN2003 Stepper Motor and Driver - -* SparkFun Battery Babysitter - -* LiPo 1000mAh battery - -* Pixy2 Camera - -* Breadboard (1/2+) - -## Wiring - -![img](./SunneeD_Wiring_Diagram.png) - -**Note: the system should be powered by the battery babysitter and not a micro-usb connection to the Pi. Otherwise the i2c connection between the babysitter and Pi may not work.** - - -### Raspberry Pi 0w Pinmap -* All pin #'s below refernce this pinmap - -![img](./RPi0w_pinmap.png) - -| Pi pin | Connection | -| :----- | :------- | -| 2 | 5v out (to stepper driver) | -| 3 | SDA (from babysitter) | -| 5 | SCL (from babysitter) | -| 9 | GND (out to stepper driver)| -| 16 | Stepper driver In1 | -| 17 | 3.3v In (battery) | -| 18 | Stepper driver In2 | -| 19 | Stepper driver In4 | -| 20 | GND (battery) | +# SunneeD Build + +## Parts + +* Raspberry Pi 0w + +* ULN2003 Stepper Motor and Driver + +* SparkFun Battery Babysitter + +* LiPo 1000mAh battery + +* Pixy2 Camera + +* Breadboard (1/2+) + +## Wiring + +![img](./SunneeD_Wiring_Diagram.png) + +**Note: the system should be powered by the battery babysitter and not a micro-usb connection to the Pi. Otherwise the i2c connection between the babysitter and Pi may not work.** + + +### Raspberry Pi 0w Pinmap +* All pin #'s below refernce this pinmap + +![img](./RPi0w_pinmap.png) + +| Pi pin | Connection | +| :----- | :------- | +| 2 | 5v out (to stepper driver) | +| 3 | SDA (from babysitter) | +| 5 | SCL (from babysitter) | +| 9 | GND (out to stepper driver)| +| 16 | Stepper driver In1 | +| 17 | 3.3v In (battery) | +| 18 | Stepper driver In2 | +| 19 | Stepper driver In4 | +| 20 | GND (battery) | | 22 | Stepper driver In3 | \ No newline at end of file diff --git a/design/scheduling/schedulerModel_draft1.tex b/design/scheduling/schedulerModel_draft1.tex index 8bd0d34..a39bbda 100644 --- a/design/scheduling/schedulerModel_draft1.tex +++ b/design/scheduling/schedulerModel_draft1.tex @@ -1,35 +1,35 @@ -\documentclass[12pt,english]{amsart} -\usepackage{amsthm} -\usepackage{amssymb} -\usepackage{amsmath} - - -\begin{document} -The basic idea of the scheduler is a round robin scheduler where we introduce power constraints on tenants and the requests tenants can make. If a tenant's request is at the top of the queue and they've reached their power limit for the current power window, the request is put to the back of the queue. In order to prevent a device from monopolizing power in the system, if a request is at the top of the queue and the request has been made too many times in the current power window, the request is put at the back of the queue. - - Each tentant $t$ on the system is allocated a percentage $c_t$ of power in a fixed power window $P.$ For a basic implementation we can set $c_t = \frac{1}{N}$ where $N$ is the number of tenants in the system. Note that we always have $\sum_{t=1}^Nc_t=1.$ We can track the amount of estimated power used by each tenant in power windows $P$ and dynamically update $c_t$ without tracking much additional data as we already need each tenants estimated power usage to charge them. - -For each operation tenants can request from a device, Sunneed intercepts the request and checks the estimated amount of power this operation will consume. This is checked by referencing the data from the regression model we will write to estimate power usage of each possible write to each device in the system. - -Let $X = \{x_1,x_2,\ldots,x_n\}$ be the set of requests Sunneed can intercept. For each $x_i \in X$ let $p_i$ denote the estimated power usage of the corresponding device operation obtained from regression model. - -Let $a_i$ denote the maximum number of times the request $x_i$ can be made in the power window $P.$ We need $a_i$ such that $\sum_{i=1}^na_ip_i \leq P$ so the system will not overdraw power in the power window $P.$ The cap on the number of times each request can be made - $a_i$ - ensures no one device or operation on a device is able to monopolize the power in the system. - -We can start with $a_i = \frac{\frac{P}{f_i}}{p_i}$ where $f_i=|X|,$ or the number of different requests Sunneed can intercept, for all $i,$ giving each request equal percentage of the power in the power window $P.$ - - We can then keep track of the number of times each call is made in power windows of $P.$ If the average number of calls made is lower than the current $a_i$ for any $x_i$ we can increase $f_i$ so $a_i = \text{average number of calls},$ therefore lowering the percentage of power $x_i$ gets in power window $P.$ If any $x_i$ is called $a_i$ number of times frequently and we have $(a_i+1)p_i \leq P - \sum_{j=1}^np_jf_j : j\ne i$ -- in other words, some other call $x_j$ has had the corresponding $f_j$ increased, making it possible to increase the percent of power allocated to $x_i$ -- we can set $f_i = 1-\frac{P}{\sum_{j=1}^np_jf_j : j}.$ This value of $f_i$ gives us $a_i = \frac{P - \sum_{j=1}^np_jf_j : j}{p_i},$ or the number of calls $x_i$ that would put us at $P$ power used in the power window $P.$ - - \textbf{Problems with idea:} - - 1. If a call $x_i$ is made an average of $0$ times in power window $P$ we don't want to set $a_i=0$ as this will prevent the request from being made in the future. - - 2. We'd have to track the number times each request is made to dynamically update the power allocation - this could potentially be a large amount of data to track and therefore take up memory. However, I don't think tracking tenant power usage to update $c_t$ would present an issue as we already need to track tenants power usage and already have to maintain the number of power windows that have occurred to dynamically update $a_i.$ Since the total power consumed by tenant and the number of power windows the tenant has existed for is all we need for an average, there is no additional data tracked. - - \bigskip - - \textbf{Citations:} - Idea inspired by content of this paper (particularly section 2.2) - - Utility Accrual Real-Time Scheduling Under the Unimodal Arbitrary Arrival Model with Energy Bounds(Haisang Wu; Binoy Ravindran; E. Douglas Jensen): - https://ieeexplore-ieee-org.proxygw.wrlc.org/document/4302708 +\documentclass[12pt,english]{amsart} +\usepackage{amsthm} +\usepackage{amssymb} +\usepackage{amsmath} + + +\begin{document} +The basic idea of the scheduler is a round robin scheduler where we introduce power constraints on tenants and the requests tenants can make. If a tenant's request is at the top of the queue and they've reached their power limit for the current power window, the request is put to the back of the queue. In order to prevent a device from monopolizing power in the system, if a request is at the top of the queue and the request has been made too many times in the current power window, the request is put at the back of the queue. + + Each tentant $t$ on the system is allocated a percentage $c_t$ of power in a fixed power window $P.$ For a basic implementation we can set $c_t = \frac{1}{N}$ where $N$ is the number of tenants in the system. Note that we always have $\sum_{t=1}^Nc_t=1.$ We can track the amount of estimated power used by each tenant in power windows $P$ and dynamically update $c_t$ without tracking much additional data as we already need each tenants estimated power usage to charge them. + +For each operation tenants can request from a device, Sunneed intercepts the request and checks the estimated amount of power this operation will consume. This is checked by referencing the data from the regression model we will write to estimate power usage of each possible write to each device in the system. + +Let $X = \{x_1,x_2,\ldots,x_n\}$ be the set of requests Sunneed can intercept. For each $x_i \in X$ let $p_i$ denote the estimated power usage of the corresponding device operation obtained from regression model. + +Let $a_i$ denote the maximum number of times the request $x_i$ can be made in the power window $P.$ We need $a_i$ such that $\sum_{i=1}^na_ip_i \leq P$ so the system will not overdraw power in the power window $P.$ The cap on the number of times each request can be made - $a_i$ - ensures no one device or operation on a device is able to monopolize the power in the system. + +We can start with $a_i = \frac{\frac{P}{f_i}}{p_i}$ where $f_i=|X|,$ or the number of different requests Sunneed can intercept, for all $i,$ giving each request equal percentage of the power in the power window $P.$ + + We can then keep track of the number of times each call is made in power windows of $P.$ If the average number of calls made is lower than the current $a_i$ for any $x_i$ we can increase $f_i$ so $a_i = \text{average number of calls},$ therefore lowering the percentage of power $x_i$ gets in power window $P.$ If any $x_i$ is called $a_i$ number of times frequently and we have $(a_i+1)p_i \leq P - \sum_{j=1}^np_jf_j : j\ne i$ -- in other words, some other call $x_j$ has had the corresponding $f_j$ increased, making it possible to increase the percent of power allocated to $x_i$ -- we can set $f_i = 1-\frac{P}{\sum_{j=1}^np_jf_j : j}.$ This value of $f_i$ gives us $a_i = \frac{P - \sum_{j=1}^np_jf_j : j}{p_i},$ or the number of calls $x_i$ that would put us at $P$ power used in the power window $P.$ + + \textbf{Problems with idea:} + + 1. If a call $x_i$ is made an average of $0$ times in power window $P$ we don't want to set $a_i=0$ as this will prevent the request from being made in the future. + + 2. We'd have to track the number times each request is made to dynamically update the power allocation - this could potentially be a large amount of data to track and therefore take up memory. However, I don't think tracking tenant power usage to update $c_t$ would present an issue as we already need to track tenants power usage and already have to maintain the number of power windows that have occurred to dynamically update $a_i.$ Since the total power consumed by tenant and the number of power windows the tenant has existed for is all we need for an average, there is no additional data tracked. + + \bigskip + + \textbf{Citations:} + Idea inspired by content of this paper (particularly section 2.2) + + Utility Accrual Real-Time Scheduling Under the Unimodal Arbitrary Arrival Model with Energy Bounds(Haisang Wu; Binoy Ravindran; E. Douglas Jensen): + https://ieeexplore-ieee-org.proxygw.wrlc.org/document/4302708 \end{document} \ No newline at end of file diff --git a/ext/Makefile b/ext/Makefile index 78756aa..e301c8e 100644 --- a/ext/Makefile +++ b/ext/Makefile @@ -1,19 +1,19 @@ -.ONESHELL: -dependencies: nng libbq27441 - -nng: - @cd nng - @[ ! -d build ] && mkdir build; cd build - cmake -G Ninja .. - if [ ! -f libnng.a ]; then - ninja - fi - -libbq27441: - make -C libbq27441 - -clean: - rm -rf nng/build - make -C libbq27441 clean - -.PHONY: nng libbq27441 dependencies clean +.ONESHELL: +dependencies: nng libbq27441 + +nng: + @cd nng + @[ ! -d build ] && mkdir build; cd build + cmake -G Ninja .. + if [ ! -f libnng.a ]; then + ninja + fi + +libbq27441: + make -C libbq27441 + +clean: + rm -rf nng/build + make -C libbq27441 clean + +.PHONY: nng libbq27441 dependencies clean diff --git a/misc/install_dependencies b/misc/install_dependencies index 3434d82..0f1cee3 100755 --- a/misc/install_dependencies +++ b/misc/install_dependencies @@ -1,4 +1,4 @@ -apt install -y cmake -apt install -y ninja-build -apt install -y libprotobuf-c-dev protobuf-c-compiler -apt install -y libi2c-dev +apt install -y cmake +apt install -y ninja-build +apt install -y libprotobuf-c-dev protobuf-c-compiler +apt install -y libi2c-dev diff --git a/misc/tools/Dockerfile b/misc/tools/Dockerfile index 2150f9f..2016e18 100644 --- a/misc/tools/Dockerfile +++ b/misc/tools/Dockerfile @@ -1,36 +1,36 @@ -FROM ubuntu:18.04 -RUN apt-get -y update &&\ - apt-get -y install \ - htop \ - ninja-build\ - libprotobuf-c-dev protobuf-c-compiler\ - libi2c-dev\ - zsh \ - python3 \ - git \ - wget \ - bc \ - g++-multilib \ - gcc-8-multilib \ - libssl-dev \ - binutils-dev \ - curl \ - python \ - tmux \ - gpg \ - software-properties-common \ - vim &&\ - chsh -s /bin/zsh root &&\ - wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | zsh || true - -RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null &&\ - apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main'&&\ - apt update -y &&\ - apt install -y cmake - -RUN apt-get -y install \ - ninja-build \ - libprotobuf-c-dev protobuf-c-compiler \ - libi2c-dev -RUN chmod a+rwx -R /usr - +FROM ubuntu:18.04 +RUN apt-get -y update &&\ + apt-get -y install \ + htop \ + ninja-build\ + libprotobuf-c-dev protobuf-c-compiler\ + libi2c-dev\ + zsh \ + python3 \ + git \ + wget \ + bc \ + g++-multilib \ + gcc-8-multilib \ + libssl-dev \ + binutils-dev \ + curl \ + python \ + tmux \ + gpg \ + software-properties-common \ + vim &&\ + chsh -s /bin/zsh root &&\ + wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | zsh || true + +RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null &&\ + apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main'&&\ + apt update -y &&\ + apt install -y cmake + +RUN apt-get -y install \ + ninja-build \ + libprotobuf-c-dev protobuf-c-compiler \ + libi2c-dev +RUN chmod a+rwx -R /usr + diff --git a/misc/wpa_supplicant_real.service b/misc/wpa_supplicant_real.service index f6d5c69..503461c 100644 --- a/misc/wpa_supplicant_real.service +++ b/misc/wpa_supplicant_real.service @@ -1,10 +1,10 @@ -# Symlink this to /etc/systemd/system . - -[Unit] -Description=WPA Supplicant but not broken - -[Service] -ExecStart=/sbin/wpa_supplicant -d -iwlan0 -c/etc/wpa_supplicant/wpa_supplicant.conf - -[Install] -WantedBy=multi-user.target +# Symlink this to /etc/systemd/system . + +[Unit] +Description=WPA Supplicant but not broken + +[Service] +ExecStart=/sbin/wpa_supplicant -d -iwlan0 -c/etc/wpa_supplicant/wpa_supplicant.conf + +[Install] +WantedBy=multi-user.target diff --git a/res/logo.svg b/res/logo.svg index 0c78b64..935b01e 100644 --- a/res/logo.svg +++ b/res/logo.svg @@ -1,196 +1,196 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - Sun vector illustration from: https://www.123freevectors.com/sun-vector-illustrator/ - - - - - - - - - - - - - - - - - SUNNEE D - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Sun vector illustration from: https://www.123freevectors.com/sun-vector-illustrator/ + + + + + + + + + + + + + + + + + SUNNEE D + + diff --git a/runtime_tests b/runtime_tests index f8ab4bd..08eb6c0 100755 --- a/runtime_tests +++ b/runtime_tests @@ -1,19 +1,19 @@ -#!/usr/bin/env bash - -testcount=$(build/sunneed -c) - -if [[ $testcount == 0 ]]; then - echo "No runtime tests available." >&2 - exit -fi - -echo "Found $testcount tests" - -for i in `seq 0 $((testcount - 1))`; do - echo -n "Running test #$i... " - build/sunneed -t $i 2>&1 | sed "s/Failure:.*/$(tput setaf 1)\\0$(tput sgr0)/" - code=${PIPESTATUS[0]} # Gets us the return value of sunneed (since sed is always 0). Pretty cool huh? - if [[ $code == 0 ]]; then - echo "$(tput setaf 2)Success!$(tput sgr0)" - fi -done +#!/usr/bin/env bash + +testcount=$(build/sunneed -c) + +if [[ $testcount == 0 ]]; then + echo "No runtime tests available." >&2 + exit +fi + +echo "Found $testcount tests" + +for i in `seq 0 $((testcount - 1))`; do + echo -n "Running test #$i... " + build/sunneed -t $i 2>&1 | sed "s/Failure:.*/$(tput setaf 1)\\0$(tput sgr0)/" + code=${PIPESTATUS[0]} # Gets us the return value of sunneed (since sed is always 0). Pretty cool huh? + if [[ $code == 0 ]]; then + echo "$(tput setaf 2)Success!$(tput sgr0)" + fi +done diff --git a/src/.gitignore b/src/.gitignore index 1edf03c..a8b2799 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1 +1 @@ -protobuf/c/ +protobuf/c/ diff --git a/src/client/sunneed_client.c b/src/client/sunneed_client.c index 8a04a23..58ee4ce 100644 --- a/src/client/sunneed_client.c +++ b/src/client/sunneed_client.c @@ -1,384 +1,419 @@ -#include "sunneed_client.h" -#include "../log.h" -#include -#include -#include - -struct { - char *path; - int fd; -} locked_paths[MAX_LOCKED_FILES] = { { NULL, 0 } }; - -struct { - int dummy_sockfd; -} dummy_sockets[MAX_TENANT_SOCKETS] = { { -1 } }; - -nng_socket sunneed_socket; - -static void -nngfatal(const char *func, int rv) { - FATAL(rv, "%s: %s\n", func, nng_strerror(rv)); -} - -/** - * Packs the given SunneedRequest into an NNG message and sends it to sunneed. If any failures occur in the packing - * or sending processes, this client will crash with a fatal error. - */ -static void -send_request(SunneedRequest *req) { - nng_msg *msg; - int req_len = sunneed_request__get_packed_size(req); - void *buf = malloc(req_len); - if (!buf) - FATAL(-1, "unable to allocate buffer for request"); - sunneed_request__pack(req, buf); - - SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &msg, req_len); - SUNNEED_NNG_TRY(nng_msg_insert, != 0, msg, buf, req_len); - SUNNEED_NNG_TRY(nng_sendmsg, != 0, sunneed_socket, msg, 0); - - free(buf); -} - -static SunneedResponse * -receive_response(SunneedResponse__MessageTypeCase message_type) { - nng_msg *reply; - SUNNEED_NNG_TRY(nng_recvmsg, != 0, sunneed_socket, &reply, 0); - - size_t msg_len = nng_msg_len(reply); - SUNNEED_NNG_MSG_LEN_FIX(msg_len); - SunneedResponse *resp = sunneed_response__unpack(NULL, msg_len, nng_msg_body(reply)); - - if (resp->status != 0) { - return NULL; - } else if (resp->message_type_case != message_type) { - FATAL(-1, "incorrect message type received (expected %d, got %d)", message_type, resp->status); - } - - nng_msg_free(reply); - return resp; -} - -int -sunneed_client_init(const char *name) { - SUNNEED_NNG_SET_ERROR_REPORT_FUNC(nngfatal); - SUNNEED_NNG_TRY(nng_req0_open, != 0, &sunneed_socket); - SUNNEED_NNG_TRY(nng_dial, != 0, sunneed_socket, SUNNEED_LISTENER_URL, NULL, 0); - - - if(!(client_init)) - { - client_init = 1; - } - // Register this client with sunneed. - SunneedRequest req = SUNNEED_REQUEST__INIT; - req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT; - RegisterClientRequest register_req = REGISTER_CLIENT_REQUEST__INIT; - register_req.name = malloc(strlen(name) + 1); - if (!register_req.name) { - FATAL(-1, "failed to allocate memory for client name"); - return -1; - } - strncpy(register_req.name, name, strlen(name) + 1); - req.register_client = ®ister_req; - send_request(&req); - free(register_req.name); - - // Check the response. - SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_REGISTER_CLIENT); - for (size_t i = 0; i < resp->register_client->n_locked_paths; i++) { - locked_paths[i].path = malloc(strlen(resp->register_client->locked_paths[i]) + 1); - strcpy(locked_paths[i].path, resp->register_client->locked_paths[i]); - client_printf(-1, "Registered locked path '%s'", locked_paths[i].path); - } - sunneed_response__free_unpacked(resp, NULL); - - return 0; -} - -/** Allocate a string containing the path of the dummy file corresponding to the given path. */ -char * -sunneed_client_fetch_locked_file_path(const char *pathname, int flags) { - // TODO Check socket opened. - - SunneedRequest req = SUNNEED_REQUEST__INIT; - req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_OPEN_FILE; - OpenFileRequest open_file_req = OPEN_FILE_REQUEST__INIT; - open_file_req.path = malloc(strlen(pathname) + 1); - if (!open_file_req.path) { - FATAL(-1, "failed to allocated memory for path"); - } - strncpy(open_file_req.path, pathname, strlen(pathname) + 1); - - open_file_req.flags = flags; - - req.open_file = &open_file_req; - - send_request(&req); - free(open_file_req.path); - - // TODO Handle request of a path that isn't locked. - SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_OPEN_FILE); - if (resp == NULL) { - // TODO Gotos - return 0; - } - - client_printf("Opening dummy path '%s'\n", resp->open_file->path); - char *path = malloc(strlen(resp->open_file->path) + 1); - if (!path) - FATAL(-1, "unable to allocate path"); - strcpy(path, resp->open_file->path); - - sunneed_response__free_unpacked(resp, NULL); - - return path; -} - -int -sunneed_client_check_locked_file(const char *pathname) { - for (int i = 0; i < MAX_LOCKED_FILES; i++) { - if (locked_paths[i].path == NULL) - return -1; - else if (strncmp(pathname, locked_paths[i].path, strlen(pathname)) == 0) - return i; - } - - return -1; -} - -int -sunneed_client_on_locked_path_open(int i, char *pathname, int fd) { - if (i < 0 || i >= MAX_LOCKED_FILES) - FATAL(-1, "locked-file array index out of bounds"); - if (pathname == NULL) - FATAL(-1, "pathname is null"); - if (fd <= 0) - FATAL(-1, "illegal FD"); - - //toSend[REQUETS_PER_PWR_LOG * 3] = pwr_change; - locked_paths[i].path = pathname; - locked_paths[i].fd = fd; - - return 0; -} - -bool -sunneed_client_fd_is_locked(int fd) { - for (int i = 0; i < MAX_LOCKED_FILES; i++) - if (locked_paths[i].fd == fd) - return true; - return false; -} - -ssize_t -sunneed_client_remote_write(int fd, const void *data, size_t n_bytes) { - // Get the dummy path corresponding to the FD. - int locked_file_i; - char *dummy_path = NULL; - for (locked_file_i = 0; locked_file_i < MAX_LOCKED_FILES; locked_file_i++) - if (locked_paths[locked_file_i].fd == fd) { - dummy_path = locked_paths[locked_file_i].path; - break; - } - - if (dummy_path == NULL) - FATAL(-1, "cannot remote write a non-dummy file"); - - SunneedRequest req = SUNNEED_REQUEST__INIT; - req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_WRITE; - - WriteRequest write_req = WRITE_REQUEST__INIT; - write_req.dummy_path = malloc(strlen(dummy_path) + 1); - write_req.data.data = malloc(n_bytes); - if (!write_req.dummy_path || !write_req.data.data) - FATAL(-1, "failed to allocate write request data"); - strncpy((char *)write_req.data.data, data, n_bytes); - strncpy(write_req.dummy_path, dummy_path, strlen(dummy_path) + 1); - write_req.data.len = n_bytes; - - req.write = &write_req; - send_request(&req); - - free(write_req.dummy_path); - free(write_req.data.data); - - SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_CALL_WRITE); - if (resp == NULL) { - // TODO Handle - FATAL(-1, "write response was NULL"); - } - sunneed_response__free_unpacked(resp, NULL); - - return 0; -} - -int -sunneed_client_socket(int domain, int type, int protocol) -{ - - if(!((domain == AF_INET) || (domain == AF_INET6))) - { - perror("invalid address family, must be ipv4 or ipv6\n"); - exit(0); - } - - if(!((type == SOCK_STREAM) || (type == SOCK_DGRAM))) - { - perror("invalid type, must be SOCK_STREAM(tcp) or SOCK_DGRAM(udp)\n"); - exit(0); - } - - SunneedRequest req = SUNNEED_REQUEST__INIT; - req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_SOCKET; - - SocketRequest sock = SOCKET_REQUEST__INIT; - sock.domain = domain; - sock.type = type; - - //TODO: check protocol - sock.protocol = protocol; - - req.socket = &sock; - printf("sending request to sunneed\n"); - send_request(&req); - - SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_SOCKET); - if(resp == NULL) - { - FATAL(-1, "failed to create socket"); - } - - int i; - for(i = 0; i < MAX_TENANT_SOCKETS; i++) - { - if(dummy_sockets[i].dummy_sockfd == -1) - { - dummy_sockets[i].dummy_sockfd = resp -> socket -> dummy_sockfd; - sunneed_response__free_unpacked(resp, NULL); - printf("added dummy sockfd to table\n"); - return dummy_sockets[i].dummy_sockfd; - } - } - - //TODO: handle not having enough free sockets - sunneed_response__free_unpacked(resp, NULL); - return -1; - -} - -int -sunneed_client_is_dummysocket(int sockfd) -{ - int i; - for(i = 0; i < MAX_TENANT_SOCKETS; i++) - { - if(dummy_sockets[i].dummy_sockfd == sockfd) - { - return 1; - } - } - return 0; -} - -int -sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) -{ - char host_name[NI_MAXHOST]; - char address[INET_ADDRSTRLEN]; - int port = 0; - struct hostent *requested_host; - char **addr_pointer; - - //get host name from sockaddr struct - getnameinfo(addr, addrlen, host_name, NI_MAXHOST, NULL, 0, 0); - - requested_host = gethostbyname(host_name); - if(requested_host == NULL) - { - fprintf(stderr, "client connect: failed to get host by name errno %d\n", h_errno); - return -1; - } - - //loop through addr list to find a valid address for the host - for(addr_pointer = requested_host->h_addr_list; *addr_pointer; addr_pointer++) - { - inet_ntop(AF_INET, (void *)*addr_pointer, address, sizeof(address)); - printf("client connect: got address %s\n", address); - } - - - SunneedRequest req = SUNNEED_REQUEST__INIT; - req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_CONNECT; - - ConnectRequest conn = CONNECT_REQUEST__INIT; - conn.port = port; - conn.address = address; - conn.addrlen = sizeof(address); - - req.connect = &conn; - send_request(&req); - - return 1; -} - -ssize_t -sunneed_client_remote_send(int sockfd, const void *data, size_t len, int flags) -{ - //TODO: check sockfd for real socket, check data, flags, etc - //for now, just tell sunneed to perform a send for us - - if(!sunneed_client_is_dummysocket(sockfd)) - { - perror("called sunneed send with a non-sunneed socket\n"); - exit(0); - } - printf("client send: len %d\n", len); - SunneedRequest req = SUNNEED_REQUEST__INIT; - req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_SEND; - - SendRequest send_req = SEND_REQUEST__INIT; - send_req.sockfd = sockfd; - send_req.data.data = malloc(len + 1); - - memcpy(send_req.data.data, data, len); - send_req.data.len = len; - - req.send = &send_req; - send_request(&req); - - free(send_req.data.data); - - return 0; -} - -int -sunneed_client_disconnect(void) { - // TODO Check socket opened. - - SunneedRequest req = SUNNEED_REQUEST__INIT; - req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_UNREGISTER_CLIENT; - UnregisterClientRequest unregister_req = UNREGISTER_CLIENT_REQUEST__INIT; - req.unregister_client = &unregister_req; - send_request(&req); - - SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC); - if (resp == NULL) { - // TODO Handle - FATAL(-1, "Disconnect response was NULL\n"); - } - sunneed_response__free_unpacked(resp, NULL); - - client_printf("Unregistered.\n"); - return 0; -} - -void -sunneed_client_debug_print_locked_path_table(void) { - client_printf("locked files: [\n"); - for (int i = 0; i < MAX_LOCKED_FILES; i++) { - if (locked_paths[i].path != NULL) - client_printf(" FD %d : '%s'\n", locked_paths[i].fd, locked_paths[i].path); - } - client_printf("]\n"); -} +#include "sunneed_client.h" +#include "../log.h" +#include +#include +#include + +struct { + char *path; + int fd; +} locked_paths[MAX_LOCKED_FILES] = { { NULL, 0 } }; + +struct { + int dummy_sockfd; +} dummy_sockets[MAX_TENANT_SOCKETS] = { { -1 } }; + +nng_socket sunneed_socket; +nng_msg *msg; +static void +nngfatal(const char *func, int rv) { + FATAL(rv, "%s: %s\n", func, nng_strerror(rv)); +} + +/** + * Packs the given SunneedRequest into an NNG message and sends it to sunneed. If any failures occur in the packing + * or sending processes, this client will crash with a fatal error. + */ +static void +send_request(SunneedRequest *req) { + //nng_msg *msg; + int req_len = sunneed_request__get_packed_size(req); + void *buf = malloc(req_len); + if (!buf) + FATAL(-1, "unable to allocate buffer for request"); + sunneed_request__pack(req, buf); + printf("malloced %d bytes for buf\n", req_len); + int ret = -1; + //int ret = nng_msg_alloc(&msg, req_len); + SUNNEED_NNG_TRY_SET(nng_msg_alloc, ret, != 0, &msg, req_len); + //ret = nng_msg_insert(msg, buf, req_len); + SUNNEED_NNG_TRY_SET(nng_msg_insert, ret, != 0, msg, buf, req_len); + printf("nng_msg_insert: ret = %d\n", ret); + //ret = nng_sendmsg(sunneed_socket, msg, 0); + SUNNEED_NNG_TRY_SET(nng_sendmsg, ret, != 0, sunneed_socket, msg, 0); + printf("nng_sendmsg: ret = %d\n", ret); + + if(ret > 0) nng_msg_free(msg); + + free(buf); + printf("end of send_request\n"); +} + +static SunneedResponse * +receive_response(SunneedResponse__MessageTypeCase message_type) { + nng_msg *reply; + printf("nng_recvmsg from socket %d\n", sunneed_socket); + SUNNEED_NNG_TRY(nng_recvmsg, != 0, sunneed_socket, &reply, 0); + + size_t msg_len = nng_msg_len(reply); + printf("response size: %d\n", msg_len); + SUNNEED_NNG_MSG_LEN_FIX(msg_len); + printf("unpacking response\n"); + SunneedResponse *resp = sunneed_response__unpack(NULL, msg_len, nng_msg_body(reply)); + + if (resp->status != 0) { + printf("resp status > 0\n"); + return NULL; + } else if (resp->message_type_case != message_type) { + FATAL(-1, "incorrect message type received (expected %d, got %d)", message_type, resp->status); + } + + nng_msg_free(reply); + return resp; +} + +int +sunneed_client_init(const char *name) { + SUNNEED_NNG_SET_ERROR_REPORT_FUNC(nngfatal); + SUNNEED_NNG_TRY(nng_req0_open, != 0, &sunneed_socket); + SUNNEED_NNG_TRY(nng_dial, != 0, sunneed_socket, SUNNEED_LISTENER_URL, NULL, 0); + + + if(!(client_init)) + { + client_init = 1; + } + // Register this client with sunneed. + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT; + RegisterClientRequest register_req = REGISTER_CLIENT_REQUEST__INIT; + register_req.name = malloc(strlen(name) + 1); + if (!register_req.name) { + FATAL(-1, "failed to allocate memory for client name"); + return -1; + } + strncpy(register_req.name, name, strlen(name) + 1); + req.register_client = ®ister_req; + send_request(&req); + free(register_req.name); + + // Check the response. + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_REGISTER_CLIENT); + for (size_t i = 0; i < resp->register_client->n_locked_paths; i++) { + locked_paths[i].path = malloc(strlen(resp->register_client->locked_paths[i]) + 1); + strcpy(locked_paths[i].path, resp->register_client->locked_paths[i]); + client_printf(-1, "Registered locked path '%s'", locked_paths[i].path); + } + sunneed_response__free_unpacked(resp, NULL); + + return 0; +} + +/** Allocate a string containing the path of the dummy file corresponding to the given path. */ +char * +sunneed_client_fetch_locked_file_path(const char *pathname, int flags) { + // TODO Check socket opened. + + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_OPEN_FILE; + OpenFileRequest open_file_req = OPEN_FILE_REQUEST__INIT; + open_file_req.path = malloc(strlen(pathname) + 1); + if (!open_file_req.path) { + FATAL(-1, "failed to allocated memory for path"); + } + strncpy(open_file_req.path, pathname, strlen(pathname) + 1); + + open_file_req.flags = flags; + + req.open_file = &open_file_req; + + send_request(&req); + free(open_file_req.path); + + // TODO Handle request of a path that isn't locked. + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_OPEN_FILE); + if (resp == NULL) { + // TODO Gotos + return 0; + } + + client_printf("Opening dummy path '%s'\n", resp->open_file->path); + char *path = malloc(strlen(resp->open_file->path) + 1); + if (!path) + FATAL(-1, "unable to allocate path"); + strcpy(path, resp->open_file->path); + + sunneed_response__free_unpacked(resp, NULL); + + return path; +} + +int +sunneed_client_check_locked_file(const char *pathname) { + for (int i = 0; i < MAX_LOCKED_FILES; i++) { + if (locked_paths[i].path == NULL) + return -1; + else if (strncmp(pathname, locked_paths[i].path, strlen(pathname)) == 0) + return i; + } + + return -1; +} + +int +sunneed_client_on_locked_path_open(int i, char *pathname, int fd) { + if (i < 0 || i >= MAX_LOCKED_FILES) + FATAL(-1, "locked-file array index out of bounds"); + if (pathname == NULL) + FATAL(-1, "pathname is null"); + if (fd <= 0) + FATAL(-1, "illegal FD"); + + //toSend[REQUETS_PER_PWR_LOG * 3] = pwr_change; + locked_paths[i].path = pathname; + locked_paths[i].fd = fd; + + return 0; +} + +bool +sunneed_client_fd_is_locked(int fd) { + for (int i = 0; i < MAX_LOCKED_FILES; i++) + if (locked_paths[i].fd == fd) + return true; + return false; +} + +ssize_t +sunneed_client_remote_write(int fd, const void *data, size_t n_bytes) { + // Get the dummy path corresponding to the FD. + int locked_file_i; + char *dummy_path = NULL; + for (locked_file_i = 0; locked_file_i < MAX_LOCKED_FILES; locked_file_i++) + if (locked_paths[locked_file_i].fd == fd) { + dummy_path = locked_paths[locked_file_i].path; + break; + } + + if (dummy_path == NULL) + FATAL(-1, "cannot remote write a non-dummy file"); + + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_WRITE; + + WriteRequest write_req = WRITE_REQUEST__INIT; + write_req.dummy_path = malloc(strlen(dummy_path) + 1); + write_req.data.data = malloc(n_bytes); + if (!write_req.dummy_path || !write_req.data.data) + FATAL(-1, "failed to allocate write request data"); + strncpy((char *)write_req.data.data, data, n_bytes); + strncpy(write_req.dummy_path, dummy_path, strlen(dummy_path) + 1); + write_req.data.len = n_bytes; + + req.write = &write_req; + send_request(&req); + + free(write_req.dummy_path); + free(write_req.data.data); + + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_CALL_WRITE); + if (resp == NULL) { + // TODO Handle + FATAL(-1, "write response was NULL"); + } + sunneed_response__free_unpacked(resp, NULL); + + return 0; +} + +int +sunneed_client_socket(int domain, int type, int protocol) +{ + + if(!((domain == AF_INET) || (domain == AF_INET6))) + { + perror("invalid address family, must be ipv4 or ipv6\n"); + exit(0); + } + + if(!((type == SOCK_STREAM) || (type == SOCK_DGRAM))) + { + perror("invalid type, must be SOCK_STREAM(tcp) or SOCK_DGRAM(udp)\n"); + exit(0); + } + + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_SOCKET; + + SocketRequest sock = SOCKET_REQUEST__INIT; + sock.domain = domain; + sock.type = type; + + //TODO: check protocol + sock.protocol = protocol; + + req.socket = &sock; + printf("sending request to sunneed\n"); + send_request(&req); + + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_SOCKET); + if(resp == NULL) + { + FATAL(-1, "failed to create socket"); + } + + int i; + for(i = 0; i < MAX_TENANT_SOCKETS; i++) + { + if(dummy_sockets[i].dummy_sockfd == -1) + { + dummy_sockets[i].dummy_sockfd = resp -> socket -> dummy_sockfd; + sunneed_response__free_unpacked(resp, NULL); + printf("added dummy sockfd to table\n"); + return dummy_sockets[i].dummy_sockfd; + } + } + + //TODO: handle not having enough free sockets + sunneed_response__free_unpacked(resp, NULL); + return -1; + +} + +int +sunneed_client_is_dummysocket(int sockfd) +{ + int i; + for(i = 0; i < MAX_TENANT_SOCKETS; i++) + { + if(dummy_sockets[i].dummy_sockfd == sockfd) + { + return 1; + } + } + return 0; +} + +int +sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + char host_name[NI_MAXHOST]; + char address[INET_ADDRSTRLEN]; + int port = 0; + struct hostent *requested_host; + char **addr_pointer; + + //get host name from sockaddr struct + getnameinfo(addr, addrlen, host_name, NI_MAXHOST, NULL, 0, 0); + + requested_host = gethostbyname(host_name); + if(requested_host == NULL) + { + fprintf(stderr, "client connect: failed to get host by name errno %d\n", h_errno); + return -1; + } + + //loop through addr list to find a valid address for the host + for(addr_pointer = requested_host->h_addr_list; *addr_pointer; addr_pointer++) + { + inet_ntop(AF_INET, (void *)*addr_pointer, address, sizeof(address)); + printf("client connect: got address %s\n", address); + } + + + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_CONNECT; + + ConnectRequest conn = CONNECT_REQUEST__INIT; + conn.port = port; + conn.address = address; + conn.addrlen = sizeof(address); + + req.connect = &conn; + send_request(&req); + + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC); + if(resp == NULL) + { + FATAL(-1, "connect response was null\n"); + return -1; + } + + sunneed_response__free_unpacked(resp, NULL); + + return 1; +} + +ssize_t +sunneed_client_remote_send(int sockfd, const void *data, size_t len, int flags) +{ + //TODO: check sockfd for real socket, check data, flags, etc + //for now, just tell sunneed to perform a send for us + + if(!sunneed_client_is_dummysocket(sockfd)) + { + perror("called sunneed send with a non-sunneed socket\n"); + exit(0); + } + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_SEND; + + SendRequest send_req = SEND_REQUEST__INIT; + send_req.sockfd = sockfd; + send_req.data.data = malloc(len); + + if(!(send_req.data.data)) + { + printf("failed to malloc request data\n"); + return -1; + } + + strncpy(send_req.data.data, data, len); + send_req.data.len = len; + + req.send = &send_req; + send_request(&req); + + + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC); + if(resp == NULL) + { + FATAL(-1, "send response was null\n"); + return -1; + } + free(send_req.data.data); + sunneed_response__free_unpacked(resp, NULL); + + return 0; +} + +int +sunneed_client_disconnect(void) { + // TODO Check socket opened. + + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_UNREGISTER_CLIENT; + UnregisterClientRequest unregister_req = UNREGISTER_CLIENT_REQUEST__INIT; + req.unregister_client = &unregister_req; + send_request(&req); + + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC); + if (resp == NULL) { + // TODO Handle + FATAL(-1, "Disconnect response was NULL\n"); + } + sunneed_response__free_unpacked(resp, NULL); + + client_printf("Unregistered.\n"); + return 0; +} + +void +sunneed_client_debug_print_locked_path_table(void) { + client_printf("locked files: [\n"); + for (int i = 0; i < MAX_LOCKED_FILES; i++) { + if (locked_paths[i].path != NULL) + client_printf(" FD %d : '%s'\n", locked_paths[i].fd, locked_paths[i].path); + } + client_printf("]\n"); +} diff --git a/src/client/sunneed_client.h b/src/client/sunneed_client.h index e2c28a1..0ccb9cc 100644 --- a/src/client/sunneed_client.h +++ b/src/client/sunneed_client.h @@ -1,66 +1,66 @@ -#include "../protobuf/c/device.pb-c.h" - -#include "../protobuf/c/server.pb-c.h" -#include "../shared/sunneed_ipc.h" -#include "../shared/sunneed_files.h" - -#include -#include -#include -#include -#include - - -#include -#include -#include - -#define FATAL(CODE, FMT, ...) \ - { \ - fprintf(stderr, "fatal (%d): " FMT "\n", CODE, ##__VA_ARGS__); \ - exit(CODE); \ - } - -#define client_printf(FMT, ...) \ - printf("\e[38;5;240mclient:\e[0m " FMT, ##__VA_ARGS__) - -int client_init = 0; -typedef unsigned int sunneed_device_handle_t; - -int -sunneed_client_init(const char *name); - -char * -sunneed_client_fetch_locked_file_path(const char *pathname, int flags); - -int -sunneed_client_check_locked_file(const char *pathname); - -bool -sunneed_client_fd_is_locked(int fd); - -ssize_t -sunneed_client_remote_write(int fd, const void *data, size_t n_bytes); - -int -sunneed_client_disconnect(void); - -int -sunneed_client_on_locked_path_open(int i, char *pathname, int fd); - -void -sunneed_client_debug_print_locked_path_table(void); - -int -sunneed_client_socket(int domain, int type, int protocol); - -int -sunneed_client_is_dummysocket(int sockfd); - -int -sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); - -ssize_t -sunneed_client_remote_send(int sockfd, const void *data, size_t n_bytes, int flags); - - +#include "../protobuf/c/device.pb-c.h" + +#include "../protobuf/c/server.pb-c.h" +#include "../shared/sunneed_ipc.h" +#include "../shared/sunneed_files.h" + +#include +#include +#include +#include +#include + + +#include +#include +#include + +#define FATAL(CODE, FMT, ...) \ + { \ + fprintf(stderr, "fatal (%d): " FMT "\n", CODE, ##__VA_ARGS__); \ + exit(CODE); \ + } + +#define client_printf(FMT, ...) \ + printf("\e[38;5;240mclient:\e[0m " FMT, ##__VA_ARGS__) + +int client_init = 0; +typedef unsigned int sunneed_device_handle_t; + +int +sunneed_client_init(const char *name); + +char * +sunneed_client_fetch_locked_file_path(const char *pathname, int flags); + +int +sunneed_client_check_locked_file(const char *pathname); + +bool +sunneed_client_fd_is_locked(int fd); + +ssize_t +sunneed_client_remote_write(int fd, const void *data, size_t n_bytes); + +int +sunneed_client_disconnect(void); + +int +sunneed_client_on_locked_path_open(int i, char *pathname, int fd); + +void +sunneed_client_debug_print_locked_path_table(void); + +int +sunneed_client_socket(int domain, int type, int protocol); + +int +sunneed_client_is_dummysocket(int sockfd); + +int +sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); + +ssize_t +sunneed_client_remote_send(int sockfd, const void *data, size_t n_bytes, int flags); + + diff --git a/src/device/test_broken.c b/src/device/test_broken.c index 88e483b..4dcf422 100644 --- a/src/device/test_broken.c +++ b/src/device/test_broken.c @@ -1,15 +1,15 @@ -// An example of a broken device -- it does not define a `get_device_type_data`. - -#include "../shared/sunneed_device_interface.h" -#include "../shared/sunneed_testing.h" -#include -#include - -int -init(void) { - return 0; -} - -enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; - -unsigned int device_flags = SUNNEED_DEVICE_FLAG_SILENT_FAIL; +// An example of a broken device -- it does not define a `get_device_type_data`. + +#include "../shared/sunneed_device_interface.h" +#include "../shared/sunneed_testing.h" +#include +#include + +int +init(void) { + return 0; +} + +enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; + +unsigned int device_flags = SUNNEED_DEVICE_FLAG_SILENT_FAIL; diff --git a/src/device/test_file_lock.c b/src/device/test_file_lock.c index 3bfc115..92f7b0b 100644 --- a/src/device/test_file_lock.c +++ b/src/device/test_file_lock.c @@ -1,27 +1,27 @@ -// An example device that implements the bare minimum. - -#include "../shared/sunneed_device_interface.h" -#include "../shared/sunneed_testing.h" -#include -#include - -struct sunneed_device_type_file_lock data = { - .len = 1, - .paths = { TEST_FILE_LOCK_FILE_PATH } -}; - -int -init(void) { - return 0; -} - -enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; - -void * -get_device_type_data(void) { - return &data; -} - -unsigned int device_flags = 0; - -// TODO Device type. +// An example device that implements the bare minimum. + +#include "../shared/sunneed_device_interface.h" +#include "../shared/sunneed_testing.h" +#include +#include + +struct sunneed_device_type_file_lock data = { + .len = 1, + .paths = { TEST_FILE_LOCK_FILE_PATH } +}; + +int +init(void) { + return 0; +} + +enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; + +void * +get_device_type_data(void) { + return &data; +} + +unsigned int device_flags = 0; + +// TODO Device type. diff --git a/src/log.h b/src/log.h index 8a811d4..8936481 100644 --- a/src/log.h +++ b/src/log.h @@ -1,48 +1,48 @@ -#ifndef _LOG_H_ -#define _LOG_H_ - -/* - * Defines a variety of macros to be used for logging information. - * Assign a FILE* to `logfile` during runtime to redirect the log to that file. - */ - -#include -#include - -FILE *logfile, *stepper_pwr_logfile; - -#define LOGL_DEBUG "D\e[38;5;240m" -#define LOGL_INFO "I" -#define LOGL_WARN "W\e[0;33m" -#define LOGL_ERROR "E\e[0;31m" - -#ifndef LOG_PWR_EVENT -#define LOG_PWR_EVENT(LEVEL, MESSAGE, ...) \ - { \ - FILE *_logfile = stepper_pwr_logfile; \ - if (!logfile) { \ - return; \ - } \ - fprintf(_logfile, MESSAGE); \ - } -#endif -#define LOG(LEVEL, MESSAGE, ...) \ - { \ - FILE *_logfile = logfile; \ - if (!logfile) { \ - _logfile = stdout; \ - } \ - time_t _now = time(NULL); \ - struct tm *_time = localtime(&_now); \ - char _time_str[21]; \ - strftime(_time_str, 21, "%Y-%m-%d %H:%M:%S", _time); \ - fprintf(_logfile, "%s[%s] " MESSAGE "\e[0m\n", LEVEL, _time_str, ##__VA_ARGS__); \ - } - -#define LOG_D(MESSAGE, ...) LOG(LOGL_DEBUG, MESSAGE, ##__VA_ARGS__); -#define LOG_I(MESSAGE, ...) LOG(LOGL_INFO, MESSAGE, ##__VA_ARGS__); -#define LOG_W(MESSAGE, ...) LOG(LOGL_WARN, MESSAGE, ##__VA_ARGS__); -#define LOG_E(MESSAGE, ...) LOG(LOGL_ERROR, MESSAGE, ##__VA_ARGS__); -#define LOG_P(MESSAGE, ...) LOG_PWR_EVENT(LOGL_INFO, MESSAGE, ##__VA_ARGS__); - -#endif +#ifndef _LOG_H_ +#define _LOG_H_ + +/* + * Defines a variety of macros to be used for logging information. + * Assign a FILE* to `logfile` during runtime to redirect the log to that file. + */ + +#include +#include + +FILE *logfile, *stepper_pwr_logfile; + +#define LOGL_DEBUG "D\e[38;5;240m" +#define LOGL_INFO "I" +#define LOGL_WARN "W\e[0;33m" +#define LOGL_ERROR "E\e[0;31m" + +#ifndef LOG_PWR_EVENT +#define LOG_PWR_EVENT(LEVEL, MESSAGE, ...) \ + { \ + FILE *_logfile = stepper_pwr_logfile; \ + if (!logfile) { \ + return; \ + } \ + fprintf(_logfile, MESSAGE); \ + } +#endif +#define LOG(LEVEL, MESSAGE, ...) \ + { \ + FILE *_logfile = logfile; \ + if (!logfile) { \ + _logfile = stdout; \ + } \ + time_t _now = time(NULL); \ + struct tm *_time = localtime(&_now); \ + char _time_str[21]; \ + strftime(_time_str, 21, "%Y-%m-%d %H:%M:%S", _time); \ + fprintf(_logfile, "%s[%s] " MESSAGE "\e[0m\n", LEVEL, _time_str, ##__VA_ARGS__); \ + } + +#define LOG_D(MESSAGE, ...) LOG(LOGL_DEBUG, MESSAGE, ##__VA_ARGS__); +#define LOG_I(MESSAGE, ...) LOG(LOGL_INFO, MESSAGE, ##__VA_ARGS__); +#define LOG_W(MESSAGE, ...) LOG(LOGL_WARN, MESSAGE, ##__VA_ARGS__); +#define LOG_E(MESSAGE, ...) LOG(LOGL_ERROR, MESSAGE, ##__VA_ARGS__); +#define LOG_P(MESSAGE, ...) LOG_PWR_EVENT(LOGL_INFO, MESSAGE, ##__VA_ARGS__); + +#endif diff --git a/src/overlay/sunneed_overlay.c b/src/overlay/sunneed_overlay.c index f8c37f0..e4bb5f2 100644 --- a/src/overlay/sunneed_overlay.c +++ b/src/overlay/sunneed_overlay.c @@ -1,133 +1,137 @@ -#include "sunneed_overlay.h" - -void -on_load() { - sunneed_client_init("TODO"); - - printf("Overlay: Client init\n"); -} - -void -on_unload() { - sunneed_client_disconnect(); -} - -int -open(const char *pathname, int flags, mode_t mode) { - printf("Opening file %s\n", pathname); - - int locked = sunneed_client_check_locked_file(pathname); - if (locked < 0) { - printf("'%s' is not locked; opening normally\n", pathname); - } else { - printf("'%s' is locked; opening via dummy\n", pathname); - char *dummy_path = sunneed_client_fetch_locked_file_path(pathname, flags); - pathname = dummy_path; - } - - int fd; - SUPER(fd, open, int, (pathname, flags, mode), const char *, int, mode_t); - - // TODO Handle errors from open - - sunneed_client_on_locked_path_open(locked, (char *)pathname, fd); - - sunneed_client_debug_print_locked_path_table(); - - return fd; -} - -ssize_t -write(int fd, const void *buf, size_t count) { - printf("Overlay write %d\n", fd); - int ret; - - if (!sunneed_client_fd_is_locked(fd)) { - // Perform the write as normal. - SUPER(ret, write, int, (fd, buf, count), int, const void *, size_t); - return ret; - } - - // Ask sunneed to do the write for us. - sunneed_client_remote_write(fd, buf, count); - - return 0; -} - -int -socket(int domain, int type, int protocol) -{ - - int sockfd; - - if(!(client_init)) - { - int ret; - SUPER(ret, socket, int, (domain, type, protocol), int, int, int); - return ret; - } - - - - if((domain == AF_INET) || (domain == AF_INET6)) - { - if((type == SOCK_STREAM) || (type == SOCK_DGRAM)) - { - printf("calling sunneed_client_socket\n"); - sockfd = sunneed_client_socket(domain, type, protocol); - printf("got back sockfd %d\n", sockfd); - return sockfd; - - } - } - - int ret2; - SUPER(ret2, socket, int, (domain, type, protocol), int, int, int); - return ret2; - -} - -int -connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) -{ - if(!(client_init)) - { - int fd; - SUPER(fd, connect, int, (sockfd, addr, addrlen), int, const struct sockaddr *, socklen_t); - return fd; - } - - if(sunneed_client_is_dummysocket(sockfd)) - { - //struct sockaddr_in *in_addr = (struct sockaddr_in *)addr; - //inet_pton(AF_INET, in_addr->sin_addr, addr_string); - //printf("overlay connect: destination ip = %s\n",addr_string); - - //getnameinfo(addr, addrlen, addr_string, strlen(addr_string), NULL, 0, 0); - //printf("overlay connect: addr: %s\n", addr_string); - return sunneed_client_connect(sockfd, addr, addrlen); - }else if(sockfd){ - int ret; - SUPER(ret, connect, int, (sockfd, addr, addrlen), int, const struct sockaddr *, socklen_t); - return ret; - }else{ - perror("overlay connect: bad socketfd and not a dummy socket\n"); - return 0; - } - -} - -ssize_t -send(int sockfd, const void *buf, size_t len, int flags) -{ - - if(sunneed_client_is_dummysocket(sockfd)) - { - sunneed_client_remote_send(sockfd, buf, len, flags); - }else if(sockfd){ - int ret; - SUPER(ret, send, int, (sockfd, buf, len, flags), int, const void *, size_t, int); - return ret; - } - return 0; -} +#include "sunneed_overlay.h" + +void +on_load() { + sunneed_client_init("TODO"); + + printf("Overlay: Client init\n"); +} + +void +on_unload() { + sunneed_client_disconnect(); +} + +int +open(const char *pathname, int flags, mode_t mode) { + printf("Opening file %s\n", pathname); + + int locked = sunneed_client_check_locked_file(pathname); + if (locked < 0) { + printf("'%s' is not locked; opening normally\n", pathname); + } else { + printf("'%s' is locked; opening via dummy\n", pathname); + char *dummy_path = sunneed_client_fetch_locked_file_path(pathname, flags); + pathname = dummy_path; + } + + int fd; + SUPER(fd, open, int, (pathname, flags, mode), const char *, int, mode_t); + + // TODO Handle errors from open + + if(locked > 0) sunneed_client_on_locked_path_open(locked, (char *)pathname, fd); + + sunneed_client_debug_print_locked_path_table(); + + + return fd; +} + +ssize_t +write(int fd, const void *buf, size_t count) { + printf("Overlay write %d\n", fd); + int ret; + + if (!sunneed_client_fd_is_locked(fd)) { + // Perform the write as normal. + SUPER(ret, write, int, (fd, buf, count), int, const void *, size_t); + return ret; + } + + // Ask sunneed to do the write for us. + sunneed_client_remote_write(fd, buf, count); + + return 0; +} + +int +socket(int domain, int type, int protocol) +{ + + int sockfd; + + if(!(client_init)) + { + int ret; + SUPER(ret, socket, int, (domain, type, protocol), int, int, int); + return ret; + } + + + + if((domain == AF_INET) || (domain == AF_INET6)) + { + if((type == SOCK_STREAM) || (type == SOCK_DGRAM)) + { + printf("calling sunneed_client_socket\n"); + sockfd = sunneed_client_socket(domain, type, protocol); + printf("got back sockfd %d\n", sockfd); + return sockfd; + + } + } + + int ret2; + SUPER(ret2, socket, int, (domain, type, protocol), int, int, int); + return ret2; + +} + +int +connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + if(!(client_init)) + { + int fd; + SUPER(fd, connect, int, (sockfd, addr, addrlen), int, const struct sockaddr *, socklen_t); + return fd; + } + + if(sunneed_client_is_dummysocket(sockfd)) + { + //struct sockaddr_in *in_addr = (struct sockaddr_in *)addr; + //inet_pton(AF_INET, in_addr->sin_addr, addr_string); + //printf("overlay connect: destination ip = %s\n",addr_string); + + //getnameinfo(addr, addrlen, addr_string, strlen(addr_string), NULL, 0, 0); + //printf("overlay connect: addr: %s\n", addr_string); + return sunneed_client_connect(sockfd, addr, addrlen); + }else if(sockfd){ + int ret; + SUPER(ret, connect, int, (sockfd, addr, addrlen), int, const struct sockaddr *, socklen_t); + return ret; + }else{ + perror("overlay connect: bad socketfd and not a dummy socket\n"); + return 0; + } + +} + +ssize_t +send(int sockfd, const void *buf, size_t len, int flags) +{ + if(sunneed_client_is_dummysocket(sockfd)) + { + sunneed_client_remote_send(sockfd, buf, len, flags); + }else if(sockfd){ + int ret; + SUPER(ret, send, int, (sockfd, buf, len, flags), int, const void *, size_t, int); + return ret; + } + return 0; +} + + + + diff --git a/src/overlay/sunneed_overlay.h b/src/overlay/sunneed_overlay.h index eb9c8d7..95ba2b7 100644 --- a/src/overlay/sunneed_overlay.h +++ b/src/overlay/sunneed_overlay.h @@ -1,64 +1,66 @@ -// Symbols to overlay on top of existing programs via LD_PRELOAD. - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include - -#include "../client/sunneed_client.h" -#include "../shared/sunneed_device_type.h" - -/** - * Call the overridden function from within an overlay function. This is done via the RTLD_NEXT flag, which instructs - * dlsym to find the next match for the symbol in libraries loaded after the current one. - * - * Arguments: - * RETVAR is a variable to assign the return value of the function to. - * NAME is the name of the function (not in a string). - * RETURN_TYPE is the type signature for the return value of the function. - * ARGS should be the argument names passed to the current overlay function, wrapped in parentheses. For example, - * calling the base `open(const char *path, int flags)`, the ARGS should be in the form `(path, flags)`. - * Finally, the varargs is the type signatures of the arguments, comma-separated. These should end up lining up with - * the types of the args passed as ARGS. - * - * To illustrate, let's return to our example using `open`. We'll declare an `int` to hold the return value, and then - * invoke `SUPER`. - * - * int ret; - * SUPER(ret, open, int, (path, flags), const char *, int); - * - * TODO The potential pitfall here is that we perform a `dlsym` lookup every single time we use `SUPER`. We should - * either implement some caching system, or make sure that `dlsym` is caching these values under the hood. - */ -#define SUPER(RETVAR, NAME, RETURN_TYPE, ARGS, ...) { \ - RETURN_TYPE (*_base)(__VA_ARGS__); \ - _base = dlsym(RTLD_NEXT, # NAME); \ - RETVAR = (*_base) ARGS; \ -} - - -// This will be run as soon as the library is linked (program start). -void __attribute__((constructor)) on_load(); - -// ...and likewise, this one when unlinked (program end). -void __attribute__((destructor)) on_unload(); - -int -open(const char *pathname, int flags, mode_t mode); - -ssize_t -write(int fd, const void *buf, size_t count); - -int -socket(int domain, int type, int protocol); - -int -connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); - -ssize_t -send(int sockfd, const void *buf, size_t len, int flags); +// Symbols to overlay on top of existing programs via LD_PRELOAD. + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include "../client/sunneed_client.h" +#include "../shared/sunneed_device_type.h" + +/** + * Call the overridden function from within an overlay function. This is done via the RTLD_NEXT flag, which instructs + * dlsym to find the next match for the symbol in libraries loaded after the current one. + * + * Arguments: + * RETVAR is a variable to assign the return value of the function to. + * NAME is the name of the function (not in a string). + * RETURN_TYPE is the type signature for the return value of the function. + * ARGS should be the argument names passed to the current overlay function, wrapped in parentheses. For example, + * calling the base `open(const char *path, int flags)`, the ARGS should be in the form `(path, flags)`. + * Finally, the varargs is the type signatures of the arguments, comma-separated. These should end up lining up with + * the types of the args passed as ARGS. + * + * To illustrate, let's return to our example using `open`. We'll declare an `int` to hold the return value, and then + * invoke `SUPER`. + * + * int ret; + * SUPER(ret, open, int, (path, flags), const char *, int); + * + * TODO The potential pitfall here is that we perform a `dlsym` lookup every single time we use `SUPER`. We should + * either implement some caching system, or make sure that `dlsym` is caching these values under the hood. + */ +#define SUPER(RETVAR, NAME, RETURN_TYPE, ARGS, ...) { \ + RETURN_TYPE (*_base)(__VA_ARGS__); \ + _base = dlsym(RTLD_NEXT, # NAME); \ + RETVAR = (*_base) ARGS; \ +} + + +// This will be run as soon as the library is linked (program start). +void __attribute__((constructor)) on_load(); + +// ...and likewise, this one when unlinked (program end). +void __attribute__((destructor)) on_unload(); + +int +open(const char *pathname, int flags, mode_t mode); + +ssize_t +write(int fd, const void *buf, size_t count); + +int +socket(int domain, int type, int protocol); + +int +connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); + +ssize_t +send(int sockfd, const void *buf, size_t len, int flags); + + diff --git a/src/pip/bq27441.c b/src/pip/bq27441.c index 5cd9f10..133a75b 100644 --- a/src/pip/bq27441.c +++ b/src/pip/bq27441.c @@ -1,12 +1,12 @@ -#include "../shared/sunneed_pip_interface.h" - -struct sunneed_pip -pip_info() { - return (struct sunneed_pip){"bq27441", 1000, 50}; -} - -unsigned int -present_power() { - return 0; - // return bq27441_nominal_avail_cap(); -} +#include "../shared/sunneed_pip_interface.h" + +struct sunneed_pip +pip_info() { + return (struct sunneed_pip){"bq27441", 1000, 50}; +} + +unsigned int +present_power() { + return 0; + // return bq27441_nominal_avail_cap(); +} diff --git a/src/protobuf/Makefile b/src/protobuf/Makefile index d05c606..587c252 100644 --- a/src/protobuf/Makefile +++ b/src/protobuf/Makefile @@ -1,7 +1,7 @@ -PROTOC ?= protoc-c - -protobuf_sources = $(wildcard *.proto) -protobuf_out_files = $(foreach src,$(protobuf_sources),$(subst !!!, ,$(join $(src:.proto=.pb-c.c!!!),$(src:.proto=.pb-c.h)))) - -$(protobuf_out_files): $(protobuf_sources) - $(PROTOC) --c_out=. $(patsubst $(protobuf_dir)/%,./%,$^) +PROTOC ?= protoc-c + +protobuf_sources = $(wildcard *.proto) +protobuf_out_files = $(foreach src,$(protobuf_sources),$(subst !!!, ,$(join $(src:.proto=.pb-c.c!!!),$(src:.proto=.pb-c.h)))) + +$(protobuf_out_files): $(protobuf_sources) + $(PROTOC) --c_out=. $(patsubst $(protobuf_dir)/%,./%,$^) diff --git a/src/protobuf/device.proto b/src/protobuf/device.proto index ac4efef..9a0c18f 100644 --- a/src/protobuf/device.proto +++ b/src/protobuf/device.proto @@ -1,5 +1,5 @@ -syntax = "proto3"; - -message DeviceRandomResponse { - int32 val = 1; -} +syntax = "proto3"; + +message DeviceRandomResponse { + int32 val = 1; +} diff --git a/src/protobuf/server.proto b/src/protobuf/server.proto index d5aef21..6369ac1 100644 --- a/src/protobuf/server.proto +++ b/src/protobuf/server.proto @@ -1,108 +1,108 @@ -syntax = "proto3"; - -// {{{1 Request - -// Generic container type for messages sent from client to server. -message SunneedRequest { - oneof message_type { - RegisterClientRequest register_client = 1; - UnregisterClientRequest unregister_client = 2; - OpenFileRequest open_file = 3; - WriteRequest write = 4; - SocketRequest socket = 5; - ConnectRequest connect = 6; - SendRequest send = 7; - } -} - -message RegisterClientRequest { - string name = 1; -} - -message UnregisterClientRequest {} - -message OpenFileRequest { - string path = 1; - int32 flags = 2; -} - -message WriteRequest { - string dummy_path = 1; - bytes data = 2; -} - -message SocketRequest { - int32 domain = 1; - int32 type = 2; - int32 protocol = 3; -} - -message ConnectRequest { - int32 port = 1; - string address = 2; - int32 addrlen = 3; - int32 sockfd = 4; -} - -message SendRequest { - int32 sockfd = 1; - bytes data = 2; - int32 flags = 3; -} - - - -// {{{1 Response - -// Generic container type for messages sent from server to client. -message SunneedResponse { - int32 status = 1; - oneof message_type { - GenericResponse generic = 2; - DeviceResponse device = 3; - OpenFileResponse open_file = 4; - RegisterClientResponse register_client = 5; - WriteResponse call_write = 6; - SocketResponse socket = 7; - } -} - -message RegisterClientResponse { - repeated string locked_paths = 1; -} - -message GenericResponse { - bytes data = 1; -} - -message OpenFileResponse { - string path = 1; -} - -message WriteResponse { - // Zero if no error. - int32 errno_value = 1; - - // This is only set from values of type `size_t`, so don't worry about data loss when converting it back from - // uint64. - uint64 bytes_written = 2; -} - -message SocketResponse { - //fake socket fd given back by sunneed - int32 dummy_sockfd = 1; -} - -import "device.proto"; - -// Dear whoever is reading this, I hope you like the word "Device"... -message DeviceResponse { - - oneof message_type { - DeviceRandomResponse random = 1; - } -}; - -// }}} - -// vim: fdm=marker : +syntax = "proto3"; + +// {{{1 Request + +// Generic container type for messages sent from client to server. +message SunneedRequest { + oneof message_type { + RegisterClientRequest register_client = 1; + UnregisterClientRequest unregister_client = 2; + OpenFileRequest open_file = 3; + WriteRequest write = 4; + SocketRequest socket = 5; + ConnectRequest connect = 6; + SendRequest send = 7; + } +} + +message RegisterClientRequest { + string name = 1; +} + +message UnregisterClientRequest {} + +message OpenFileRequest { + string path = 1; + int32 flags = 2; +} + +message WriteRequest { + string dummy_path = 1; + bytes data = 2; +} + +message SocketRequest { + int32 domain = 1; + int32 type = 2; + int32 protocol = 3; +} + +message ConnectRequest { + int32 port = 1; + string address = 2; + int32 addrlen = 3; + int32 sockfd = 4; +} + +message SendRequest { + int32 sockfd = 1; + bytes data = 2; + int32 flags = 3; +} + + + +// {{{1 Response + +// Generic container type for messages sent from server to client. +message SunneedResponse { + int32 status = 1; + oneof message_type { + GenericResponse generic = 2; + DeviceResponse device = 3; + OpenFileResponse open_file = 4; + RegisterClientResponse register_client = 5; + WriteResponse call_write = 6; + SocketResponse socket = 7; + } +} + +message RegisterClientResponse { + repeated string locked_paths = 1; +} + +message GenericResponse { + bytes data = 1; +} + +message OpenFileResponse { + string path = 1; +} + +message WriteResponse { + // Zero if no error. + int32 errno_value = 1; + + // This is only set from values of type `size_t`, so don't worry about data loss when converting it back from + // uint64. + uint64 bytes_written = 2; +} + +message SocketResponse { + //fake socket fd given back by sunneed + int32 dummy_sockfd = 1; +} + +import "device.proto"; + +// Dear whoever is reading this, I hope you like the word "Device"... +message DeviceResponse { + + oneof message_type { + DeviceRandomResponse random = 1; + } +}; + +// }}} + +// vim: fdm=marker : diff --git a/src/shared/sunneed_device_interface.h b/src/shared/sunneed_device_interface.h index c2c4a92..fdeb3ca 100644 --- a/src/shared/sunneed_device_interface.h +++ b/src/shared/sunneed_device_interface.h @@ -1,28 +1,28 @@ -#ifndef _SUNNEED_DEVICE_INTERFACE_H_ -#define _SUNNEED_DEVICE_INTERFACE_H_ - -#include "sunneed_device_type.h" -#include "../protobuf/c/device.pb-c.h" - -/** - * Runs once when module is loaded. - * If the return value is greater than zero, loading will be aborted with an error. - * If the return value is less than zero, loading will be silently aborted. - */ -extern int -init(void); - -/** - * The type of the implementing device. - * Under the hood, defines the union member of `device_type_data` to write to. - */ -extern enum sunneed_device_type device_type_kind; - -/** Should return a pointer to a struct, the type of which corresponds with the `device_type_kind` of this. */ -extern void * -get_device_type_data(void); - -/** A set of bitflags as defined in `sunneed_device_interface.h`. */ -extern unsigned int device_flags; - -#endif +#ifndef _SUNNEED_DEVICE_INTERFACE_H_ +#define _SUNNEED_DEVICE_INTERFACE_H_ + +#include "sunneed_device_type.h" +#include "../protobuf/c/device.pb-c.h" + +/** + * Runs once when module is loaded. + * If the return value is greater than zero, loading will be aborted with an error. + * If the return value is less than zero, loading will be silently aborted. + */ +extern int +init(void); + +/** + * The type of the implementing device. + * Under the hood, defines the union member of `device_type_data` to write to. + */ +extern enum sunneed_device_type device_type_kind; + +/** Should return a pointer to a struct, the type of which corresponds with the `device_type_kind` of this. */ +extern void * +get_device_type_data(void); + +/** A set of bitflags as defined in `sunneed_device_interface.h`. */ +extern unsigned int device_flags; + +#endif diff --git a/src/shared/sunneed_device_type.h b/src/shared/sunneed_device_type.h index 5f6cfcc..74dab6f 100644 --- a/src/shared/sunneed_device_type.h +++ b/src/shared/sunneed_device_type.h @@ -1,16 +1,16 @@ -// For testing purposes. -#define CAMERA_PATH "camera" - -#define SUNNEED_DEVICE_FLAG_SILENT_FAIL (1 << 0) - -enum sunneed_device_type { - DEVICE_TYPE_FILE_LOCK = 1 -}; - -struct sunneed_device_type_file_lock { - /** The number of elements in `paths`. */ - unsigned int len; - - /** An array of strings; these are the paths to lock. */ - char *paths[]; -}; +// For testing purposes. +#define CAMERA_PATH "camera" + +#define SUNNEED_DEVICE_FLAG_SILENT_FAIL (1 << 0) + +enum sunneed_device_type { + DEVICE_TYPE_FILE_LOCK = 1 +}; + +struct sunneed_device_type_file_lock { + /** The number of elements in `paths`. */ + unsigned int len; + + /** An array of strings; these are the paths to lock. */ + char *paths[]; +}; diff --git a/src/shared/sunneed_files.h b/src/shared/sunneed_files.h index a9e3ff6..759fa6b 100644 --- a/src/shared/sunneed_files.h +++ b/src/shared/sunneed_files.h @@ -1,7 +1,7 @@ -#ifndef _SUNNEED_FILES_H_ -#define _SUNNEED_FILES_H_ - -#define MAX_LOCKED_FILES 1024 -#define MAX_TENANT_SOCKETS 128 - -#endif +#ifndef _SUNNEED_FILES_H_ +#define _SUNNEED_FILES_H_ + +#define MAX_LOCKED_FILES 1024 +#define MAX_TENANT_SOCKETS 128 + +#endif diff --git a/src/shared/sunneed_ipc.h b/src/shared/sunneed_ipc.h index 2c5105f..1987b6c 100644 --- a/src/shared/sunneed_ipc.h +++ b/src/shared/sunneed_ipc.h @@ -1,84 +1,84 @@ -#ifndef _SUNNEED_IPC_H_ -#define _SUNNEED_IPC_H_ - -#include -#include - -#define SUNNEED_LISTENER_URL "ipc:///tmp/sunneed.ipc" - -void (*_sunneed_nng_error_func)(const char *nng_call_name, int rv); - -/** - * Sets the function to be called when an error is encountered during a SUNNEED_NNG_TRY_* macro. - * This function takes two arguments: a string representing the name of the function, and an integer containing the - * return value of the failed NNG function call. - * Usually this function should somehow call `nng_strerror` to report NNG's description of the error. - */ -#define SUNNEED_NNG_SET_ERROR_REPORT_FUNC(FUNCNAME) _sunneed_nng_error_func = FUNCNAME; - -/** Try to perform an NNG function, expanding to returning 1 if the NNG call fails. */ -#define SUNNEED_NNG_TRY_RET(CALL, ERROR_COND, ARGS...) \ - { \ - int _rv; \ - SUNNEED_NNG_TRY_RET_SET(CALL, _rv, ERROR_COND, ##ARGS); \ - } - -/** - * Try to perform an NNG function, expanding to returning 1 if the NNG call fails. - * In the process of calling the function, the variable TARGET will be assigned its return value. - */ -#define SUNNEED_NNG_TRY_RET_SET(CALL, TARGET, ERROR_COND, ARGS...) \ - { _SUNNEED_NNG_TRY_CONTAINER(CALL, TARGET, ERROR_COND, return 1, ##ARGS); } - -/** Try to perform an NNG function. */ -#define SUNNEED_NNG_TRY(CALL, ERROR_COND, ARGS...) \ - { \ - int _rv; \ - SUNNEED_NNG_TRY_SET(CALL, _rv, ERROR_COND, ##ARGS); \ - } - -/** - * Try to perform an NNG function. - * In the process of calling the function, the variable TARGET will be assigned its return value. - */ -#define SUNNEED_NNG_TRY_SET(CALL, TARGET, ERROR_COND, ARGS...) \ - { _SUNNEED_NNG_TRY_CONTAINER(CALL, TARGET, ERROR_COND, , ##ARGS); } - -#ifdef SUNNEED_BUILD_TYPE_PRODUCTION -#define _SUNNEED_NNG_TRY_CONTAINER(CALL, TARGET, ERROR_COND, ON_ERROR, ARGS...) \ - { \ - if ((TARGET = CALL(ARGS)) ERROR_COND) { \ - _sunneed_nng_error_func(#CALL, TARGET); \ - ON_ERROR; \ - } \ - } -#else -#define _SUNNEED_NNG_TRY_CONTAINER(CALL, TARGET, ERROR_COND, ON_ERROR, ARGS...) \ - { \ - if ((TARGET = CALL(ARGS)) ERROR_COND) { \ - if (!_sunneed_nng_error_func) { \ - fprintf(stderr, \ - "An NNG error was encountered but there is no error handler.\n" \ - "Error details: " #CALL ": %s\n", \ - nng_strerror(TARGET)); \ - exit(1); \ - } \ - _sunneed_nng_error_func(#CALL, TARGET); \ - ON_ERROR; \ - } \ - } -#endif - -#define SUNNEED_NNG_MSG_LEN_FIX(LEN_VAR) \ - if ((LEN_VAR / 2) % 2 == 1) \ - LEN_VAR++; - - -/**** - * IPC helpers - ***/ - -int -sunneed_ipc_register_self(void); - -#endif +#ifndef _SUNNEED_IPC_H_ +#define _SUNNEED_IPC_H_ + +#include +#include + +#define SUNNEED_LISTENER_URL "ipc:///tmp/sunneed.ipc" + +void (*_sunneed_nng_error_func)(const char *nng_call_name, int rv); + +/** + * Sets the function to be called when an error is encountered during a SUNNEED_NNG_TRY_* macro. + * This function takes two arguments: a string representing the name of the function, and an integer containing the + * return value of the failed NNG function call. + * Usually this function should somehow call `nng_strerror` to report NNG's description of the error. + */ +#define SUNNEED_NNG_SET_ERROR_REPORT_FUNC(FUNCNAME) _sunneed_nng_error_func = FUNCNAME; + +/** Try to perform an NNG function, expanding to returning 1 if the NNG call fails. */ +#define SUNNEED_NNG_TRY_RET(CALL, ERROR_COND, ARGS...) \ + { \ + int _rv; \ + SUNNEED_NNG_TRY_RET_SET(CALL, _rv, ERROR_COND, ##ARGS); \ + } + +/** + * Try to perform an NNG function, expanding to returning 1 if the NNG call fails. + * In the process of calling the function, the variable TARGET will be assigned its return value. + */ +#define SUNNEED_NNG_TRY_RET_SET(CALL, TARGET, ERROR_COND, ARGS...) \ + { _SUNNEED_NNG_TRY_CONTAINER(CALL, TARGET, ERROR_COND, return 1, ##ARGS); } + +/** Try to perform an NNG function. */ +#define SUNNEED_NNG_TRY(CALL, ERROR_COND, ARGS...) \ + { \ + int _rv; \ + SUNNEED_NNG_TRY_SET(CALL, _rv, ERROR_COND, ##ARGS); \ + } + +/** + * Try to perform an NNG function. + * In the process of calling the function, the variable TARGET will be assigned its return value. + */ +#define SUNNEED_NNG_TRY_SET(CALL, TARGET, ERROR_COND, ARGS...) \ + { _SUNNEED_NNG_TRY_CONTAINER(CALL, TARGET, ERROR_COND, , ##ARGS); } + +#ifdef SUNNEED_BUILD_TYPE_PRODUCTION +#define _SUNNEED_NNG_TRY_CONTAINER(CALL, TARGET, ERROR_COND, ON_ERROR, ARGS...) \ + { \ + if ((TARGET = CALL(ARGS)) ERROR_COND) { \ + _sunneed_nng_error_func(#CALL, TARGET); \ + ON_ERROR; \ + } \ + } +#else +#define _SUNNEED_NNG_TRY_CONTAINER(CALL, TARGET, ERROR_COND, ON_ERROR, ARGS...) \ + { \ + if ((TARGET = CALL(ARGS)) ERROR_COND) { \ + if (!_sunneed_nng_error_func) { \ + fprintf(stderr, \ + "An NNG error was encountered but there is no error handler.\n" \ + "Error details: " #CALL ": %s\n", \ + nng_strerror(TARGET)); \ + exit(1); \ + } \ + _sunneed_nng_error_func(#CALL, TARGET); \ + ON_ERROR; \ + } \ + } +#endif + +#define SUNNEED_NNG_MSG_LEN_FIX(LEN_VAR) \ + if ((LEN_VAR / 2) % 2 == 1) \ + LEN_VAR++; + + +/**** + * IPC helpers + ***/ + +int +sunneed_ipc_register_self(void); + +#endif diff --git a/src/shared/sunneed_pip_interface.h b/src/shared/sunneed_pip_interface.h index 06ddeca..b6c01dd 100644 --- a/src/shared/sunneed_pip_interface.h +++ b/src/shared/sunneed_pip_interface.h @@ -1,36 +1,36 @@ -#ifndef _SUNNEED_PIP_H_ -#define _SUNNEED_PIP_H_ -#include "../../ext/libbq27441/bq27441.h" - -/* - * Describes the interface for a power information provider (a PIP). - * A PIP is what tells sunneed statistics about the battery, such as power and - * electrical current usage. - * This API is unstable and new things will definitely be added as we progress - * in sunneed's development. - */ - -struct sunneed_pip { - // A textual name to identify the PIP by. Probably won't be used in any - // actual logic. - const char *name; - - // The maximum power the system can have. Units are arbitrary, as this - // value will only be used in comparison to the present power. - unsigned int max_power; - - // The interval, in milliseconds, that the device can update its power - // statistics. This helps reduce unnecessary device accesses by - // sunneed. - // TODO: Some devices may have different updatee intervals across their - // components; should we account for that? - unsigned int update_interval; -}; - -struct sunneed_pip -pip_info(); - -unsigned int -present_power(); - -#endif +#ifndef _SUNNEED_PIP_H_ +#define _SUNNEED_PIP_H_ +#include "../../ext/libbq27441/bq27441.h" + +/* + * Describes the interface for a power information provider (a PIP). + * A PIP is what tells sunneed statistics about the battery, such as power and + * electrical current usage. + * This API is unstable and new things will definitely be added as we progress + * in sunneed's development. + */ + +struct sunneed_pip { + // A textual name to identify the PIP by. Probably won't be used in any + // actual logic. + const char *name; + + // The maximum power the system can have. Units are arbitrary, as this + // value will only be used in comparison to the present power. + unsigned int max_power; + + // The interval, in milliseconds, that the device can update its power + // statistics. This helps reduce unnecessary device accesses by + // sunneed. + // TODO: Some devices may have different updatee intervals across their + // components; should we account for that? + unsigned int update_interval; +}; + +struct sunneed_pip +pip_info(); + +unsigned int +present_power(); + +#endif diff --git a/src/shared/sunneed_testing.h b/src/shared/sunneed_testing.h index b1f16e5..a96a72e 100644 --- a/src/shared/sunneed_testing.h +++ b/src/shared/sunneed_testing.h @@ -1,3 +1,3 @@ -#define TEST_DEVICE_OUTPUT "TEST" - -#define TEST_FILE_LOCK_FILE_PATH "/tmp/test" +#define TEST_DEVICE_OUTPUT "TEST" + +#define TEST_FILE_LOCK_FILE_PATH "/tmp/test" diff --git a/src/sunneed.h b/src/sunneed.h index e88c232..7238456 100644 --- a/src/sunneed.h +++ b/src/sunneed.h @@ -1,16 +1,16 @@ -#ifndef _SUNNEED_H_ -#define _SUNNEED_H_ - -#include - -#define APP_NAME "sunneed" - -#ifdef LOG_PWR - #define REQS_PER_LOG 10 - int last_capacity, curr_capacity, reqs_since_last_log; - double last_send, time_since_send; -#endif - -typedef void* sunneed_worker_thread_result_t; - -#endif +#ifndef _SUNNEED_H_ +#define _SUNNEED_H_ + +#include + +#define APP_NAME "sunneed" + +#ifdef LOG_PWR + #define REQS_PER_LOG 10 + int last_capacity, curr_capacity, reqs_since_last_log; + double last_send, time_since_send; +#endif + +typedef void* sunneed_worker_thread_result_t; + +#endif diff --git a/src/sunneed_core.c b/src/sunneed_core.c index b9fec68..5514926 100644 --- a/src/sunneed_core.c +++ b/src/sunneed_core.c @@ -1,147 +1,147 @@ -#include "sunneed_core.h" - -extern struct sunneed_device devices[MAX_DEVICES]; - -struct sunneed_pip pip; - -sunneed_worker_thread_result_t (*worker_thread_functions[])(void *) = {sunneed_proc_monitor, sunneed_quantum_worker, NULL}; - -#ifdef TESTING - -#include "sunneed_runtime_test_collection.h" - -int (*runtime_tests[])(void) = RUNTIME_TESTS; - -static unsigned int -testcase_count(void) { - unsigned int testcases = 0; - for (int (**cur)(void) = runtime_tests; *cur != NULL; cur++) - testcases++; - // TODO Why minus one... - return testcases - 1; -} - -static int -run_testcase(unsigned int testcase) { - if (testcase >= testcase_count()) { - LOG_E("Cannot run testcase #%d because it does not exist", testcase); - return 1; - } - - int ret = runtime_tests[testcase](); - if (ret != 0) { - fprintf(stderr, "Failure: %s (%d)\n", sunneed_runtime_test_error, ret); - return ret; - } - - return 0; -} -#endif - -static int -spawn_worker_threads(void) { - int ret; - int worker_thread_count = 0; - for (void *(**cur)(void *) = worker_thread_functions; *cur != NULL; cur++) - worker_thread_count++; - - pthread_t worker_threads[worker_thread_count]; - - LOG_I("Launching %d worker threads", worker_thread_count); - - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - for (int i = 0; i < worker_thread_count; i++) { - if ((ret = pthread_create(&worker_threads[i], &attr, worker_thread_functions[i], NULL)) != 0) { - LOG_E("Failed to launch worker thread %d (error %d)", i, ret); - return 1; - }; - } - - return 0; -} - - -void -sunneed_init(void) { - pip = pip_info(); - #ifdef LOG_PWR - last_capacity = present_power(); - #endif -} - -int -main(int argc, char *argv[]) { - int opt; - extern int optopt; -#ifdef LOG_PWR - LOG_D("Recording power\n"); - stepper_pwr_logfile = fopen("stepper_pwr_log.txt", "w+"); - reqs_since_last_log = 0; -#endif - -#ifdef TESTING - const char *optstring = ":ht:c"; -#else - const char *optstring = ":h"; -#endif - - // TODO Long-form getopts. - while ((opt = getopt(argc, argv, optstring)) != -1) { - switch (opt) { - case 'h': - printf(HELP_TEXT, argv[0]); - exit(0); -#ifdef TESTING - case 't': ; - logfile = fopen("sunneed_log.txt", "w+"); - int testcase = strtol(optarg, NULL, 10); - if (errno) { - LOG_E("Failed to parse testcase index: %s", strerror(errno)); - return 1; - } - return run_testcase(testcase); - case 'c': - printf("%d\n", testcase_count()); - exit(0); -#endif - case '?': - fprintf(stderr, "%s: illegal option -%c\n", APP_NAME, optopt); - exit(1); - case ':': - fprintf(stderr, "%s: expected argument for option -%c\n", APP_NAME, optopt); - exit(1); - } - } - - int ret = 0; - - LOG_I("sunneed is initializing..."); - - sunneed_init(); - - LOG_I("Acquired PIP: %s", pip.name); - - LOG_I("Loading devices..."); - if ((ret = sunneed_load_devices(devices)) != 0) { - LOG_E("Failed to load devices"); - ret = 1; - goto end; - } - - if ((ret = spawn_worker_threads()) != 0) { - LOG_E("Error occurred while spawning worker threads"); - ret = 1; - goto end; - } - - if ((ret = sunneed_listen()) != 0) { - LOG_E("sunneed listener encountered a fatal error. Exiting."); - ret = 1; - goto end; - } - -end: - return 0; -} +#include "sunneed_core.h" + +extern struct sunneed_device devices[MAX_DEVICES]; + +struct sunneed_pip pip; + +sunneed_worker_thread_result_t (*worker_thread_functions[])(void *) = {sunneed_proc_monitor, sunneed_quantum_worker, NULL}; + +#ifdef TESTING + +#include "sunneed_runtime_test_collection.h" + +int (*runtime_tests[])(void) = RUNTIME_TESTS; + +static unsigned int +testcase_count(void) { + unsigned int testcases = 0; + for (int (**cur)(void) = runtime_tests; *cur != NULL; cur++) + testcases++; + // TODO Why minus one... + return testcases - 1; +} + +static int +run_testcase(unsigned int testcase) { + if (testcase >= testcase_count()) { + LOG_E("Cannot run testcase #%d because it does not exist", testcase); + return 1; + } + + int ret = runtime_tests[testcase](); + if (ret != 0) { + fprintf(stderr, "Failure: %s (%d)\n", sunneed_runtime_test_error, ret); + return ret; + } + + return 0; +} +#endif + +static int +spawn_worker_threads(void) { + int ret; + int worker_thread_count = 0; + for (void *(**cur)(void *) = worker_thread_functions; *cur != NULL; cur++) + worker_thread_count++; + + pthread_t worker_threads[worker_thread_count]; + + LOG_I("Launching %d worker threads", worker_thread_count); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + for (int i = 0; i < worker_thread_count; i++) { + if ((ret = pthread_create(&worker_threads[i], &attr, worker_thread_functions[i], NULL)) != 0) { + LOG_E("Failed to launch worker thread %d (error %d)", i, ret); + return 1; + }; + } + + return 0; +} + + +void +sunneed_init(void) { + pip = pip_info(); + #ifdef LOG_PWR + last_capacity = present_power(); + #endif +} + +int +main(int argc, char *argv[]) { + int opt; + extern int optopt; +#ifdef LOG_PWR + LOG_D("Recording power\n"); + stepper_pwr_logfile = fopen("stepper_pwr_log.txt", "w+"); + reqs_since_last_log = 0; +#endif + +#ifdef TESTING + const char *optstring = ":ht:c"; +#else + const char *optstring = ":h"; +#endif + + // TODO Long-form getopts. + while ((opt = getopt(argc, argv, optstring)) != -1) { + switch (opt) { + case 'h': + printf(HELP_TEXT, argv[0]); + exit(0); +#ifdef TESTING + case 't': ; + logfile = fopen("sunneed_log.txt", "w+"); + int testcase = strtol(optarg, NULL, 10); + if (errno) { + LOG_E("Failed to parse testcase index: %s", strerror(errno)); + return 1; + } + return run_testcase(testcase); + case 'c': + printf("%d\n", testcase_count()); + exit(0); +#endif + case '?': + fprintf(stderr, "%s: illegal option -%c\n", APP_NAME, optopt); + exit(1); + case ':': + fprintf(stderr, "%s: expected argument for option -%c\n", APP_NAME, optopt); + exit(1); + } + } + + int ret = 0; + + LOG_I("sunneed is initializing..."); + + sunneed_init(); + + LOG_I("Acquired PIP: %s", pip.name); + + LOG_I("Loading devices..."); + if ((ret = sunneed_load_devices(devices)) != 0) { + LOG_E("Failed to load devices"); + ret = 1; + goto end; + } + + if ((ret = spawn_worker_threads()) != 0) { + LOG_E("Error occurred while spawning worker threads"); + ret = 1; + goto end; + } + + if ((ret = sunneed_listen()) != 0) { + LOG_E("sunneed listener encountered a fatal error. Exiting."); + ret = 1; + goto end; + } + +end: + return 0; +} diff --git a/src/sunneed_core.h b/src/sunneed_core.h index bd74ad9..5fd284f 100644 --- a/src/sunneed_core.h +++ b/src/sunneed_core.h @@ -1,42 +1,42 @@ -#ifndef _SUNNEED_CORE_H_ -#define _SUNNEED_CORE_H_ - -#include -#include -#include -#include -#include - -#include "log.h" -#include "shared/sunneed_pip_interface.h" -#include "sunneed.h" -#include "sunneed_listener.h" -#include "sunneed_proc.h" -#include "sunneed_loader.h" -#include "sunneed_device.h" - -#define _HELP_TEXT_HEAD \ - APP_NAME ": enforce power usage policies\n" \ - "\nUSAGE\n" \ - "%s OPTIONS\n" \ - "\nOPTIONS\n" \ - "\t-h --help Show this help.\n" -#define _HELP_TEXT_TAIL \ - "\n" - -#ifdef TESTING -#define HELP_TEXT _HELP_TEXT_HEAD \ - "\t-c --testcase-count Print out the number of runtime tests.\n" \ - "\t-t --run-test TEST Run a runtime test by given its numerical ID.\n" \ - _HELP_TEXT_TAIL -#else -#define HELP_TEXT _HELP_TEXT_HEAD _HELP_TEXT_TAIL -#endif - -#ifdef TESTING - -#define MAX_TESTS_PER_SUITE 64 - -#endif - -#endif +#ifndef _SUNNEED_CORE_H_ +#define _SUNNEED_CORE_H_ + +#include +#include +#include +#include +#include + +#include "log.h" +#include "shared/sunneed_pip_interface.h" +#include "sunneed.h" +#include "sunneed_listener.h" +#include "sunneed_proc.h" +#include "sunneed_loader.h" +#include "sunneed_device.h" + +#define _HELP_TEXT_HEAD \ + APP_NAME ": enforce power usage policies\n" \ + "\nUSAGE\n" \ + "%s OPTIONS\n" \ + "\nOPTIONS\n" \ + "\t-h --help Show this help.\n" +#define _HELP_TEXT_TAIL \ + "\n" + +#ifdef TESTING +#define HELP_TEXT _HELP_TEXT_HEAD \ + "\t-c --testcase-count Print out the number of runtime tests.\n" \ + "\t-t --run-test TEST Run a runtime test by given its numerical ID.\n" \ + _HELP_TEXT_TAIL +#else +#define HELP_TEXT _HELP_TEXT_HEAD _HELP_TEXT_TAIL +#endif + +#ifdef TESTING + +#define MAX_TESTS_PER_SUITE 64 + +#endif + +#endif diff --git a/src/sunneed_device.c b/src/sunneed_device.c index ecf702f..2477a6b 100644 --- a/src/sunneed_device.c +++ b/src/sunneed_device.c @@ -1,66 +1,66 @@ -#include "sunneed_device.h" - -struct sunneed_device devices[MAX_DEVICES] = { { .is_ready = false } }; -struct { - bool init; - char pathname[DUMMY_FILE_PATH_LEN]; // TODO Don't hardcode. - char dummypath[DUMMY_FILE_PATH_LEN]; -} dummy_map[MAX_LOCKED_FILES] = { { false, { '\0' }, { '\0' } } }; - -/** - * Check for devices locking the specified pathname. - * Returns the device specifying the lock if one is found, otherwise returns NULL. - */ -struct sunneed_device * -sunneed_device_file_locker(const char *pathname) { - for (int i = 0; i < MAX_DEVICES; i++) { - // Find devices of type FILE_LOCK. - if (!devices[i].is_ready || devices[i].device_type_kind != DEVICE_TYPE_FILE_LOCK) - continue; - - // Compare to each locked file specified by the device. - for (unsigned int s = 0; s < devices[i].device_type_data.file_lock->len; s++) { - LOG_D("Comparing %s to %s", pathname, devices[i].device_type_data.file_lock->paths[s]); - - // TODO Treat locked filepaths as a list of paths. - if (!strncmp(pathname, devices[i].device_type_data.file_lock->paths[s], strlen(pathname))) { - return &devices[i]; - } - } - } - - return NULL; -} - -char * -sunneed_device_get_dummy_file(const char *orig_path) { - // Try to find the already-created locked file with that name. - for (int i = 0; i < MAX_LOCKED_FILES; i++) { - if (dummy_map[i].init) - if (strncmp(orig_path, dummy_map[i].pathname, sizeof(dummy_map[i].pathname)) == 0) - return dummy_map[i].dummypath; - } - - // We haven't made a dummy file for this yet. - char template[] = "locked_XXXXXX"; - int dummy; - if ((dummy = mkstemp(template)) == -1) { - LOG_E("Error creating temp dummy file"); - return NULL; - } - - LOG_I("Created dummy file '%s'", template); - - for (int i = 0; i < MAX_LOCKED_FILES; i++) { - if (!dummy_map[i].init) { - dummy_map[i].init = dummy; - strncpy(dummy_map[i].pathname, orig_path, DUMMY_FILE_PATH_LEN); - strncpy(dummy_map[i].dummypath, template, DUMMY_FILE_PATH_LEN); - return dummy_map[i].dummypath; - } - } - - // Out of entires in dummy file table. - LOG_E("Cannot create dummy file mapping for '%s'", orig_path); - return NULL; -} +#include "sunneed_device.h" + +struct sunneed_device devices[MAX_DEVICES] = { { .is_ready = false } }; +struct { + bool init; + char pathname[DUMMY_FILE_PATH_LEN]; // TODO Don't hardcode. + char dummypath[DUMMY_FILE_PATH_LEN]; +} dummy_map[MAX_LOCKED_FILES] = { { false, { '\0' }, { '\0' } } }; + +/** + * Check for devices locking the specified pathname. + * Returns the device specifying the lock if one is found, otherwise returns NULL. + */ +struct sunneed_device * +sunneed_device_file_locker(const char *pathname) { + for (int i = 0; i < MAX_DEVICES; i++) { + // Find devices of type FILE_LOCK. + if (!devices[i].is_ready || devices[i].device_type_kind != DEVICE_TYPE_FILE_LOCK) + continue; + + // Compare to each locked file specified by the device. + for (unsigned int s = 0; s < devices[i].device_type_data.file_lock->len; s++) { + LOG_D("Comparing %s to %s", pathname, devices[i].device_type_data.file_lock->paths[s]); + + // TODO Treat locked filepaths as a list of paths. + if (!strncmp(pathname, devices[i].device_type_data.file_lock->paths[s], strlen(pathname))) { + return &devices[i]; + } + } + } + + return NULL; +} + +char * +sunneed_device_get_dummy_file(const char *orig_path) { + // Try to find the already-created locked file with that name. + for (int i = 0; i < MAX_LOCKED_FILES; i++) { + if (dummy_map[i].init) + if (strncmp(orig_path, dummy_map[i].pathname, sizeof(dummy_map[i].pathname)) == 0) + return dummy_map[i].dummypath; + } + + // We haven't made a dummy file for this yet. + char template[] = "locked_XXXXXX"; + int dummy; + if ((dummy = mkstemp(template)) == -1) { + LOG_E("Error creating temp dummy file"); + return NULL; + } + + LOG_I("Created dummy file '%s'", template); + + for (int i = 0; i < MAX_LOCKED_FILES; i++) { + if (!dummy_map[i].init) { + dummy_map[i].init = dummy; + strncpy(dummy_map[i].pathname, orig_path, DUMMY_FILE_PATH_LEN); + strncpy(dummy_map[i].dummypath, template, DUMMY_FILE_PATH_LEN); + return dummy_map[i].dummypath; + } + } + + // Out of entires in dummy file table. + LOG_E("Cannot create dummy file mapping for '%s'", orig_path); + return NULL; +} diff --git a/src/sunneed_device.h b/src/sunneed_device.h index 9493f90..8c70875 100644 --- a/src/sunneed_device.h +++ b/src/sunneed_device.h @@ -1,50 +1,50 @@ -#ifndef _SUNNEED_DEVICE_H_ -#define _SUNNEED_DEVICE_H_ - -#include "sunneed.h" -#include "log.h" - -#include "shared/sunneed_device_type.h" -#include "shared/sunneed_files.h" -#include "protobuf/c/device.pb-c.h" - -#include -#include -#include - -#define DEVICE_IDENTIFIER_LEN 32 - -#define MAX_DEVICES 64 - -#define DUMMY_FILE_PATH_LEN 128 - -#define SUNNEED_DEVICE_FLAG_SILENT_FAIL (1 << 0) - -struct sunneed_device { - /** Set to true when all data has been prepared in this struct. */ - bool is_ready; - - /** Numerical identifier of the device. */ - int handle; - - /** Human-readable name for the device (e.g. "camera"). */ - char *identifier; - - /** Specifices the union member to write to in `device_type_data`. */ - enum sunneed_device_type device_type_kind; - - /** One of many different structs representing arbitrary data of the device interface. */ - union { - struct sunneed_device_type_file_lock *file_lock; - } device_type_data; -}; - -struct sunneed_device * -sunneed_device_file_locker(const char *pathname); - -char * -sunneed_device_get_dummy_file(const char *orig_path); - -extern struct sunneed_device devices[]; - -#endif +#ifndef _SUNNEED_DEVICE_H_ +#define _SUNNEED_DEVICE_H_ + +#include "sunneed.h" +#include "log.h" + +#include "shared/sunneed_device_type.h" +#include "shared/sunneed_files.h" +#include "protobuf/c/device.pb-c.h" + +#include +#include +#include + +#define DEVICE_IDENTIFIER_LEN 32 + +#define MAX_DEVICES 64 + +#define DUMMY_FILE_PATH_LEN 128 + +#define SUNNEED_DEVICE_FLAG_SILENT_FAIL (1 << 0) + +struct sunneed_device { + /** Set to true when all data has been prepared in this struct. */ + bool is_ready; + + /** Numerical identifier of the device. */ + int handle; + + /** Human-readable name for the device (e.g. "camera"). */ + char *identifier; + + /** Specifices the union member to write to in `device_type_data`. */ + enum sunneed_device_type device_type_kind; + + /** One of many different structs representing arbitrary data of the device interface. */ + union { + struct sunneed_device_type_file_lock *file_lock; + } device_type_data; +}; + +struct sunneed_device * +sunneed_device_file_locker(const char *pathname); + +char * +sunneed_device_get_dummy_file(const char *orig_path); + +extern struct sunneed_device devices[]; + +#endif diff --git a/src/sunneed_listener.c b/src/sunneed_listener.c index 9c556a5..83af81c 100644 --- a/src/sunneed_listener.c +++ b/src/sunneed_listener.c @@ -1,554 +1,589 @@ -#include "sunneed_listener.h" - -#include "protobuf/c/server.pb-c.h" - -#define SUB_RESPONSE_BUF_SZ 4096 - -extern struct sunneed_device devices[]; -extern struct sunneed_tenant tenants[]; -extern const char *locked_file_paths[]; -extern int errno; -/*n - * Maps dummy paths (typically sent by clients during a read or write) to FDs pointing to the real device, held by - * sunneed. - */ -struct { - char *path; - int fd; -} dummy_path_fd_map[MAX_LOCKED_FILES] = { { NULL, 0 } }; - -struct { - int id; - int sockfd; - int domain; -} dummy_socket_map[MAX_TENANT_SOCKETS] = { {-1, -1, 0} }; - -static int -get_fd_from_dummy_path(char *path) { - for (int i = 0; i < MAX_LOCKED_FILES; i++) - if (dummy_path_fd_map[i].path && strncmp(dummy_path_fd_map[i].path, path, strlen(path)) == 0) - return dummy_path_fd_map[i].fd; - return -1; -} - -// Control flow: -// When a new pipe connects, we use this struct to make a mapping of its pipe ID to a tenant. Then, when further -// requests are made, the pipe ID is used to identify a tenant to the request. -// The client will have to send some notification in order to unregister; I don't think we can tell if a pipe -// closed. -struct tenant_pipe { - struct sunneed_tenant *tenant; - nng_pipe pipe; -} tenant_pipes[SUNNEED_MAX_IPC_CLIENTS]; - -// TODO This is probably slow -- O(n) lookup for every request made. -static struct sunneed_tenant * -tenant_of_pipe(int pipe_id) { - for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) - if (nng_pipe_id(tenant_pipes[i].pipe) == pipe_id) - return tenant_pipes[i].tenant; - return NULL; -} - -int -lookup_socket(int sockfd) -{ - int i; - for(i = 0; i < MAX_TENANT_SOCKETS; i++) - if(dummy_socket_map[i].id == sockfd) - return dummy_socket_map[i].sockfd; - return 0; - -} - -// Get the PID of a pipe and use that to create a new sunneed tenant with that ID. -// TODO: This shouldn't always create a new tenant, since we want multiple processes -// mapped to one tenant. -static struct sunneed_tenant * -register_client(nng_pipe pipe) { - struct sunneed_tenant *tenant; - - // Get PID of pipe. - uint64_t pid_int; - SUNNEED_NNG_TRY(nng_pipe_get_uint64, != 0, pipe, NNG_OPT_IPC_PEER_PID, &pid_int); - pid_t pid = (pid_t)pid_int; - - if ((tenant = sunneed_tenant_register(pid)) == NULL) { - LOG_E("Failed to initialize tenant from PID %d", pid); - return NULL; - } - - for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) { - if (tenant_pipes[i].tenant == NULL) { - tenant_pipes[i].tenant = tenant; - tenant_pipes[i].pipe = pipe; - break; - } - } - - return tenant; -} - -// The `serve_*` methods take a `sub_resp_buf` parameter. This is a pointer to a buffer in which the client -// can store their sub-response (the message in the oneof field of the SunneedResponse). Example: -// -// GetDeviceHandleResponse *sub_resp = sub_resp_buf; -// *sub_resp = (GetDeviceHandleResponse)GET_DEVICE_HANDLE_RESPONSE__INIT; -// -// This example writes the initializer for the `GetDeviceHandleResponse` to the address pointed to by -// `sub_resp_buf`. -// The rationale for this whole process comes next: once the `serve_*` function returns, its sub-response -// data is contained within the buffer, to which a pointer is in scope in the main request listening -// loop. - -// Create a mapping between this pipe and a sunneed tenant. -// TODO Currently, this just spawns a new tenant for each different pipe. We want tenants to be able to have multiple -// pipes to sunneed open. -static int -serve_register_client(SunneedResponse *resp, void *sub_resp_buf, nng_pipe pipe) { - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_REGISTER_CLIENT; - RegisterClientResponse *sub_resp = sub_resp_buf; - *sub_resp = (RegisterClientResponse)REGISTER_CLIENT_RESPONSE__INIT; - resp->register_client = sub_resp; - - struct sunneed_tenant *tenant = NULL; - - // Register as a new client. - if ((tenant = register_client(pipe)) == NULL) { - LOG_W("Registration failed for pipe %d", pipe.id); - return 1; - } - - // Construct the list of locked file paths to send to the client. - size_t locked_paths_len = 0; - for (locked_paths_len = 0; locked_file_paths[locked_paths_len] != NULL; locked_paths_len++) ; - sub_resp->n_locked_paths = locked_paths_len; - sub_resp->locked_paths = malloc(sizeof(char *) * sub_resp->n_locked_paths); - for (size_t i = 0; i < sub_resp->n_locked_paths; i++) - sub_resp->locked_paths[i] = (char *)locked_file_paths[i]; - - LOG_D("Registered pipe %d with tenant %d", pipe.id, tenant->id); - - return 0; -} - -static int -serve_unregister_client(SunneedResponse *resp, void *sub_resp_buf, nng_pipe pipe, struct sunneed_tenant *tenant) { - int retval; - - LOG_D("Unregistering tenant %d", tenant->id); - - // Find the entry in the tenant pipe mappings. - bool cleared = false; - for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) - if (tenant_pipes[i].pipe.id == pipe.id) { - LOG_D("Removing mapping from pipe %d to tenant %d", pipe.id, tenant->id); - tenant_pipes[i].tenant = NULL; - tenant_pipes[i].pipe = (nng_pipe)NNG_PIPE_INITIALIZER; - cleared = true; - break; - } - - if (!cleared) { - LOG_E("No mapping cleared when unregistering pipe %d; something is wrong with the pipe->tenant table", pipe.id); - return 1; - } - - if ((retval = sunneed_tenant_unregister(tenant)) != 0) - // TODO Handle (follow the pattern of the `register_client` stuff by making a secondary `unregister` function - // that handles interacting with tenants). - return 1; - - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; - GenericResponse *sub_resp = sub_resp_buf; - *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; - resp->generic = sub_resp; - - return 0; -} - -static int -serve_open_file( - SunneedResponse *resp, - void *sub_resp_buf, - __attribute__((unused)) struct sunneed_tenant *tenant, - OpenFileRequest *request) { - LOG_D("Got request to open file '%s'", request->path); - - OpenFileResponse *sub_resp = sub_resp_buf; - *sub_resp = (OpenFileResponse)OPEN_FILE_RESPONSE__INIT; - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_OPEN_FILE; - resp->open_file = sub_resp; - - // TODO Take flags!! - - - struct sunneed_device *locker; - if ((locker = sunneed_device_file_locker(request->path)) != NULL) { - // TODO Wait for availability, perform power calcs, etc. - - // Open the real file and save its FD. -// int real_fd = open(request->path, O_RDWR); // TODO Use flags given by client. - int real_fd = open(request->path, request->flags); - - if (real_fd == -1) { - LOG_E("Failed to open file '%s'", request->path); - return 1; - } - - char *dummypath = sunneed_device_get_dummy_file(request->path); - - int i; - for (i = 0; i < MAX_LOCKED_FILES; i++) { - // Find open slot. - if (dummy_path_fd_map[i].path == NULL) { - dummy_path_fd_map[i].path = malloc(strlen(dummypath) + 1); - strncpy(dummy_path_fd_map[i].path, dummypath, strlen(dummypath) + 1); - dummy_path_fd_map[i].fd = real_fd; - - LOG_I("Opened locked path '%s' as '%s' (FD %d)", request->path, dummypath, real_fd); - - break; - } - } - if (i == MAX_LOCKED_FILES) { - // Theoretically this should never happen (since MAX_LOCKED_FILES also bounds the number of possible locked - // paths) but good to check. - LOG_E("No slots remaining in dummy_path_fd_map"); - return 1; - } - - // TODO Free this - sub_resp->path = malloc(strlen(dummypath) + 1); - strncpy(sub_resp->path, dummypath, strlen(dummypath) + 1); - } else { - // They requested a non-dummy file. - return 1; - } - - return 0; -} - -static int -serve_write( - SunneedResponse *resp, - void *sub_resp_buf, - struct sunneed_tenant *tenant, - WriteRequest *request) { - LOG_D("Got request from %d to write %ld bytes to '%s' (real file FD %d)", tenant->id, request->data.len, request->dummy_path, get_fd_from_dummy_path(request->dummy_path)); - - WriteResponse *sub_resp = sub_resp_buf; - *sub_resp = (WriteResponse)WRITE_RESPONSE__INIT; - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_CALL_WRITE; - resp->call_write = sub_resp; - - // Perform the write. - ssize_t bytes_written; - if ((bytes_written = write(get_fd_from_dummy_path(request->dummy_path), request->data.data, request->data.len)) - < 0) { - int errno_val = errno; - - sub_resp->errno_value = errno_val; - sub_resp->bytes_written = bytes_written; - - LOG_E("`write` for client %d failed with: %s", tenant->id, strerror(errno_val)); - - return 1; - } - - sub_resp->bytes_written = bytes_written; - sub_resp->errno_value = 0; - - return 0; -} - -static int -serve_socket(SunneedResponse *resp, void *sub_response_buf, SocketRequest *request) -{ - LOG_D("Got request to open a new socket\n"); - - SocketResponse *sock_resp = sub_response_buf; - *sock_resp = (SocketResponse)SOCKET_RESPONSE__INIT; - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_SOCKET; - resp->socket = sock_resp; - //TODO: checks on the request? - - //find open spot in socket table - int i, new_id, sockfd; - for(i = 0; i < MAX_TENANT_SOCKETS; i++) - { - new_id = i; - if(dummy_socket_map[i].id == -1) - { - sockfd = socket(request->domain, request->type, request->protocol); - if(sockfd) - { - dummy_socket_map[i].id = new_id; - dummy_socket_map[i].sockfd = sockfd; - dummy_socket_map[i].domain = request->domain; - LOG_D("Socket created successfully\n"); - sock_resp->dummy_sockfd = new_id; - return 0; - }else{ - LOG_E("Failed to create socket. domain %d type %d protocol %d\n", request->domain, request->type, request->protocol); - return 1; - } - } - } - LOG_E("Maximum tenant sockets have been created\n"); - return 1; - -} - -static int -serve_connect(nng_pipe pipe, ConnectRequest *request) -{ - //lookup real sockfd in dummy_socket_map and create new socket - LOG_D("got connect request\n"); - - //TODO: figure out getting the port from the tenant, hardcoded to get data from network - - int i, sockfd, domain; - struct sockaddr_in remote_addr; - sockfd = 0; - for(i = 0; i < MAX_TENANT_SOCKETS; i++) - { - if(dummy_socket_map[i].id == request->sockfd) - { - LOG_D("found socket for pipe %d\n", pipe.id); - sockfd = dummy_socket_map[i].sockfd; - domain = dummy_socket_map[i].domain; - break; - } - } - if(!(sockfd)) - { - LOG_E("failed to find socket for pipe %d\n", pipe.id); - return 1; - } - - remote_addr.sin_family = domain; - remote_addr.sin_port = htons(9999); - - //TODO: check address/port - if(inet_pton(domain, request->address, &remote_addr.sin_addr) <= 0) - { - LOG_E("invalid address/domain or failed to convert\n"); - return 1; - } - - if(connect(sockfd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) - { - LOG_E("Failed to connect to %s\n", request->address); - return 1; - } - - LOG_D("connected to remote host: %s\n", request->address); - - return 0; - - -} - -static int -serve_send(struct sunneed_tenant *tenant, SendRequest *request) -{ - //TODO: formulate response, for now just log and call send - - LOG_D("Got request from %d to send %ld bytes", tenant->id, request->data.len); - //LOG_D("Msg to send %s\n", request->data.data); - //TODO: probably want more checks here as well - - int sockfd = lookup_socket(request->sockfd); - if(!(sockfd)) - { - LOG_E("Bad socket descriptor: %d\n", sockfd); - return 1; - } - if(!(request->data.data)) - { - LOG_E("couldnt get data from request\n"); - return 1; - } - - #ifdef LOG_PWR - if(last_send == 0) - { - last_send = clock(); - LOG_P ("%f ", (double) last_send / CLOCKS_PER_SEC); - }else{ - time_since_send = (double)((clock() - last_send)/CLOCKS_PER_SEC); - LOG_P("%f ", time_since_send); - } - - LOG_P("%d ", request->data.len); - - #endif - - if((send(sockfd, request->data.data, request->data.len, request->flags)) < 0) - { - LOG_E("Failed to send data for tenant %d error %d\n", tenant->id, errno); - }else{ - - LOG_D("Sent data from tenant %d\n", tenant->id); - } - - #ifdef LOG_PWR - curr_capacity = present_power(); - - double change = ((double) last_capacity - curr_capacity) - (time_since_send * PASSIVE_PWR_PER_SEC); - LOG_P("%f\n", change); - - last_capacity = curr_capacity; - last_send = clock(); - - #endif - - return 0; -} -static void -report_nng_error(const char *func, int rv) { - LOG_E("nng error: (%s) %s", func, nng_strerror(rv)); -} - -int -sunneed_listen(void) { - SUNNEED_NNG_SET_ERROR_REPORT_FUNC(report_nng_error); - - #ifdef LOG_PWR - last_capacity = present_power(); - int capacity_change; - last_send = 0; - #endif - - // Initialize client states. - for (int i = 0; i < MAX_TENANTS; i++) { - tenant_pipes[i] = (struct tenant_pipe){.tenant = NULL, - // TODO Why do I need to cast this... - .pipe = (nng_pipe)NNG_PIPE_INITIALIZER}; - } - - nng_socket sock; - - LOG_I("Starting listener loop..."); - - // Make a socket and attach it to the sunneed URL. - - SUNNEED_NNG_TRY_RET(nng_rep0_open, != 0, &sock); - - SUNNEED_NNG_TRY_RET(nng_listen, < 0, sock, SUNNEED_LISTENER_URL, NULL, 0); - - - - - // Buffer for `serve_` methods to write their sub-response to. - void *sub_resp_buf = malloc(SUB_RESPONSE_BUF_SZ); - // TODO Check malloc. - - // Await messages. - for (;;) { - nng_msg *msg; - - SUNNEED_NNG_TRY_RET(nng_recvmsg, != 0, sock, &msg, NNG_FLAG_ALLOC); - - // TODO They claim nng_msg_get_pipe() returns -1 on error, but its return type is nng_pipe, which can't - // be compared to an integer. - nng_pipe pipe = nng_msg_get_pipe(msg); - - // Get contents of message. - size_t msg_len = nng_msg_len(msg); - SUNNEED_NNG_MSG_LEN_FIX(msg_len); - SunneedRequest *request = sunneed_request__unpack(NULL, msg_len, nng_msg_body(msg)); - - if (request == NULL) { - LOG_W("Received null request from %d", pipe.id); - goto end; - } - - struct sunneed_tenant *tenant = NULL; - - // Find the pipe's associated tenant. If we can't find it, we error out unless the message is of type REGISTER_CLIENT. - if ((tenant = tenant_of_pipe(pipe.id)) == NULL && request->message_type_case != SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT) { - // This client has not registered! - LOG_W("Received message from %d, who is not registered.", pipe.id); - goto end; - } - - // Begin setting up our response. - SunneedResponse resp = SUNNEED_RESPONSE__INIT; - int ret = -1; - - #ifdef LOG_PWR - reqs_since_last_log++; - #endif - - switch (request->message_type_case) { - case SUNNEED_REQUEST__MESSAGE_TYPE__NOT_SET: - LOG_W("Request from pipe %d has no message type set.", pipe.id); - ret = -1; - break; - case SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT: - ret = serve_register_client(&resp, sub_resp_buf, pipe); - break; - case SUNNEED_REQUEST__MESSAGE_TYPE_UNREGISTER_CLIENT: - ret = serve_unregister_client(&resp, sub_resp_buf, pipe, tenant); - break; - case SUNNEED_REQUEST__MESSAGE_TYPE_OPEN_FILE: - ret = serve_open_file(&resp, sub_resp_buf, tenant, request->open_file); - break; - case SUNNEED_REQUEST__MESSAGE_TYPE_WRITE: - #ifdef LOG_PWR - if (reqs_since_last_log < REQS_PER_LOG) { - LOG_D("%d, ",reqs_since_last_log); - LOG_I("%d\n",capacity_change); - } else if (reqs_since_last_log > REQS_PER_LOG) { - curr_capacity = present_power(); - capacity_change = last_capacity - curr_capacity; - last_capacity = curr_capacity; - LOG_D("%d\n",capacity_change); - LOG_D("%d, ",reqs_since_last_log); - reqs_since_last_log = 1; - } - #endif - ret = serve_write(&resp, sub_resp_buf, tenant, request->write); - break; - case SUNNEED_REQUEST__MESSAGE_TYPE_SOCKET: - ret = serve_socket(&resp, sub_resp_buf, request->socket); - break; - case SUNNEED_REQUEST__MESSAGE_TYPE_CONNECT: - ret = serve_connect(pipe, request->connect); - break; - case SUNNEED_REQUEST__MESSAGE_TYPE_SEND: - ret = serve_send(tenant, request->send); - break; - default: - LOG_W("Received request with invalid type %d", request->message_type_case); - ret = -1; - #ifdef LOG_PWR - reqs_since_last_log--; - #endif - break; - } - - resp.status = ret; - - // Create and send the response message. - nng_msg *resp_msg; - int resp_len = sunneed_response__get_packed_size(&resp); - void *resp_buf = malloc(resp_len); - sunneed_response__pack(&resp, resp_buf); - - SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &resp_msg, resp_len); - SUNNEED_NNG_TRY(nng_msg_insert, != 0, resp_msg, resp_buf, resp_len); - SUNNEED_NNG_TRY_RET(nng_sendmsg, != 0, sock, resp_msg, 0); - - //nng_msg_free(resp_msg); - - end: - sunneed_request__free_unpacked(request, NULL); - - nng_msg_free(msg); - } - - free(sub_resp_buf); -} +#include "sunneed_listener.h" + +#include "protobuf/c/server.pb-c.h" + +#define SUB_RESPONSE_BUF_SZ 4096 + +extern struct sunneed_device devices[]; +extern struct sunneed_tenant tenants[]; +extern const char *locked_file_paths[]; +extern int errno; +/*n + * Maps dummy paths (typically sent by clients during a read or write) to FDs pointing to the real device, held by + * sunneed. + */ +struct { + char *path; + int fd; +} dummy_path_fd_map[MAX_LOCKED_FILES] = { { NULL, 0 } }; + +struct { + int id; + int sockfd; + int domain; +} dummy_socket_map[MAX_TENANT_SOCKETS] = { {-1, -1, 0} }; + +static int +get_fd_from_dummy_path(char *path) { + for (int i = 0; i < MAX_LOCKED_FILES; i++) + if (dummy_path_fd_map[i].path && strncmp(dummy_path_fd_map[i].path, path, strlen(path)) == 0) + return dummy_path_fd_map[i].fd; + return -1; +} + +// Control flow: +// When a new pipe connects, we use this struct to make a mapping of its pipe ID to a tenant. Then, when further +// requests are made, the pipe ID is used to identify a tenant to the request. +// The client will have to send some notification in order to unregister; I don't think we can tell if a pipe +// closed. +struct tenant_pipe { + struct sunneed_tenant *tenant; + nng_pipe pipe; +} tenant_pipes[SUNNEED_MAX_IPC_CLIENTS]; + +// TODO This is probably slow -- O(n) lookup for every request made. +static struct sunneed_tenant * +tenant_of_pipe(int pipe_id) { + for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) + if (nng_pipe_id(tenant_pipes[i].pipe) == pipe_id) + return tenant_pipes[i].tenant; + return NULL; +} + +int +lookup_socket(int sockfd) +{ + int i; + for(i = 0; i < MAX_TENANT_SOCKETS; i++) + if(dummy_socket_map[i].id == sockfd) + return dummy_socket_map[i].sockfd; + return 0; + +} + +// Get the PID of a pipe and use that to create a new sunneed tenant with that ID. +// TODO: This shouldn't always create a new tenant, since we want multiple processes +// mapped to one tenant. +static struct sunneed_tenant * +register_client(nng_pipe pipe) { + struct sunneed_tenant *tenant; + + // Get PID of pipe. + uint64_t pid_int; + SUNNEED_NNG_TRY(nng_pipe_get_uint64, != 0, pipe, NNG_OPT_IPC_PEER_PID, &pid_int); + pid_t pid = (pid_t)pid_int; + + if ((tenant = sunneed_tenant_register(pid)) == NULL) { + LOG_E("Failed to initialize tenant from PID %d", pid); + return NULL; + } + + for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) { + if (tenant_pipes[i].tenant == NULL) { + tenant_pipes[i].tenant = tenant; + tenant_pipes[i].pipe = pipe; + break; + } + } + + return tenant; +} + +// The `serve_*` methods take a `sub_resp_buf` parameter. This is a pointer to a buffer in which the client +// can store their sub-response (the message in the oneof field of the SunneedResponse). Example: +// +// GetDeviceHandleResponse *sub_resp = sub_resp_buf; +// *sub_resp = (GetDeviceHandleResponse)GET_DEVICE_HANDLE_RESPONSE__INIT; +// +// This example writes the initializer for the `GetDeviceHandleResponse` to the address pointed to by +// `sub_resp_buf`. +// The rationale for this whole process comes next: once the `serve_*` function returns, its sub-response +// data is contained within the buffer, to which a pointer is in scope in the main request listening +// loop. + +// Create a mapping between this pipe and a sunneed tenant. +// TODO Currently, this just spawns a new tenant for each different pipe. We want tenants to be able to have multiple +// pipes to sunneed open. +static int +serve_register_client(SunneedResponse *resp, void *sub_resp_buf, nng_pipe pipe) { + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_REGISTER_CLIENT; + RegisterClientResponse *sub_resp = sub_resp_buf; + *sub_resp = (RegisterClientResponse)REGISTER_CLIENT_RESPONSE__INIT; + resp->register_client = sub_resp; + + struct sunneed_tenant *tenant = NULL; + + // Register as a new client. + if ((tenant = register_client(pipe)) == NULL) { + LOG_W("Registration failed for pipe %d", pipe.id); + return 1; + } + + // Construct the list of locked file paths to send to the client. + size_t locked_paths_len = 0; + for (locked_paths_len = 0; locked_file_paths[locked_paths_len] != NULL; locked_paths_len++) ; + sub_resp->n_locked_paths = locked_paths_len; + sub_resp->locked_paths = malloc(sizeof(char *) * sub_resp->n_locked_paths); + for (size_t i = 0; i < sub_resp->n_locked_paths; i++) + sub_resp->locked_paths[i] = (char *)locked_file_paths[i]; + + LOG_D("Registered pipe %d with tenant %d", pipe.id, tenant->id); + + return 0; +} + +static int +serve_unregister_client(SunneedResponse *resp, void *sub_resp_buf, nng_pipe pipe, struct sunneed_tenant *tenant) { + int retval; + + LOG_D("Unregistering tenant %d", tenant->id); + + // Find the entry in the tenant pipe mappings. + bool cleared = false; + for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) + if (tenant_pipes[i].pipe.id == pipe.id) { + LOG_D("Removing mapping from pipe %d to tenant %d", pipe.id, tenant->id); + tenant_pipes[i].tenant = NULL; + tenant_pipes[i].pipe = (nng_pipe)NNG_PIPE_INITIALIZER; + cleared = true; + break; + } + + if (!cleared) { + LOG_E("No mapping cleared when unregistering pipe %d; something is wrong with the pipe->tenant table", pipe.id); + return 1; + } + + if ((retval = sunneed_tenant_unregister(tenant)) != 0) + // TODO Handle (follow the pattern of the `register_client` stuff by making a secondary `unregister` function + // that handles interacting with tenants). + return 1; + + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; + GenericResponse *sub_resp = sub_resp_buf; + *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; + resp->generic = sub_resp; + + return 0; +} + +static int +serve_open_file( + SunneedResponse *resp, + void *sub_resp_buf, + __attribute__((unused)) struct sunneed_tenant *tenant, + OpenFileRequest *request) { + LOG_D("Got request to open file '%s'", request->path); + + OpenFileResponse *sub_resp = sub_resp_buf; + *sub_resp = (OpenFileResponse)OPEN_FILE_RESPONSE__INIT; + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_OPEN_FILE; + resp->open_file = sub_resp; + + // TODO Take flags!! + + + struct sunneed_device *locker; + if ((locker = sunneed_device_file_locker(request->path)) != NULL) { + // TODO Wait for availability, perform power calcs, etc. + + // Open the real file and save its FD. +// int real_fd = open(request->path, O_RDWR); // TODO Use flags given by client. + int real_fd = open(request->path, request->flags); + + if (real_fd == -1) { + LOG_E("Failed to open file '%s'", request->path); + return 1; + } + + char *dummypath = sunneed_device_get_dummy_file(request->path); + + int i; + for (i = 0; i < MAX_LOCKED_FILES; i++) { + // Find open slot. + if (dummy_path_fd_map[i].path == NULL) { + dummy_path_fd_map[i].path = malloc(strlen(dummypath) + 1); + strncpy(dummy_path_fd_map[i].path, dummypath, strlen(dummypath) + 1); + dummy_path_fd_map[i].fd = real_fd; + + LOG_I("Opened locked path '%s' as '%s' (FD %d)", request->path, dummypath, real_fd); + + break; + } + } + if (i == MAX_LOCKED_FILES) { + // Theoretically this should never happen (since MAX_LOCKED_FILES also bounds the number of possible locked + // paths) but good to check. + LOG_E("No slots remaining in dummy_path_fd_map"); + return 1; + } + + // TODO Free this + sub_resp->path = malloc(strlen(dummypath) + 1); + strncpy(sub_resp->path, dummypath, strlen(dummypath) + 1); + } else { + // They requested a non-dummy file. + return 1; + } + + return 0; +} + +static int +serve_write( + SunneedResponse *resp, + void *sub_resp_buf, + struct sunneed_tenant *tenant, + WriteRequest *request) { + LOG_D("Got request from %d to write %ld bytes to '%s' (real file FD %d)", tenant->id, request->data.len, request->dummy_path, get_fd_from_dummy_path(request->dummy_path)); + + WriteResponse *sub_resp = sub_resp_buf; + *sub_resp = (WriteResponse)WRITE_RESPONSE__INIT; + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_CALL_WRITE; + resp->call_write = sub_resp; + + // Perform the write. + ssize_t bytes_written; + if ((bytes_written = write(get_fd_from_dummy_path(request->dummy_path), request->data.data, request->data.len)) + < 0) { + int errno_val = errno; + + sub_resp->errno_value = errno_val; + sub_resp->bytes_written = bytes_written; + + LOG_E("`write` for client %d failed with: %s", tenant->id, strerror(errno_val)); + + return 1; + } + + sub_resp->bytes_written = bytes_written; + sub_resp->errno_value = 0; + + return 0; +} + +static int +serve_socket(SunneedResponse *resp, void *sub_response_buf, SocketRequest *request) +{ + LOG_D("Got request to open a new socket\n"); + + SocketResponse *sock_resp = sub_response_buf; + *sock_resp = (SocketResponse)SOCKET_RESPONSE__INIT; + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_SOCKET; + resp->socket = sock_resp; + //TODO: checks on the request? + + //find open spot in socket table + int i, new_id, sockfd; + for(i = 0; i < MAX_TENANT_SOCKETS; i++) + { + new_id = i; + if(dummy_socket_map[i].id == -1) + { + sockfd = socket(request->domain, request->type, request->protocol); + if(sockfd) + { + dummy_socket_map[i].id = new_id; + dummy_socket_map[i].sockfd = sockfd; + dummy_socket_map[i].domain = request->domain; + LOG_D("Socket created successfully\n"); + sock_resp->dummy_sockfd = new_id; + return 0; + }else{ + LOG_E("Failed to create socket. domain %d type %d protocol %d\n", request->domain, request->type, request->protocol); + return 1; + } + } + } + LOG_E("Maximum tenant sockets have been created\n"); + return 1; + +} + +static int +serve_connect(SunneedResponse *resp, void* sub_resp_buf, nng_pipe pipe, ConnectRequest *request) +{ + //lookup real sockfd in dummy_socket_map and create new socket + LOG_D("got connect request\n"); + + //TODO: figure out getting the port from the tenant, hardcoded to get data from network + + int i, sockfd, domain; + struct sockaddr_in remote_addr; + sockfd = 0; + for(i = 0; i < MAX_TENANT_SOCKETS; i++) + { + if(dummy_socket_map[i].id == request->sockfd) + { + LOG_D("found socket for pipe %d\n", pipe.id); + sockfd = dummy_socket_map[i].sockfd; + domain = dummy_socket_map[i].domain; + break; + } + } + if(!(sockfd)) + { + LOG_E("failed to find socket for pipe %d\n", pipe.id); + return 1; + } + + remote_addr.sin_family = domain; + remote_addr.sin_port = htons(9999); + + //TODO: check address/port + if(inet_pton(domain, request->address, &remote_addr.sin_addr) <= 0) + { + LOG_E("invalid address/domain or failed to convert\n"); + return 1; + } + + if(connect(sockfd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + LOG_E("Failed to connect to %s\n", request->address); + return 1; + } + + LOG_D("connected to remote host: %s\n", request->address); + + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; + GenericResponse *sub_resp = sub_resp_buf; + *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; + resp->generic = sub_resp; + + return 0; + + +} + +static int +serve_send(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant *tenant, SendRequest *request) +{ + //TODO: formulate response, for now just log and call send + + LOG_D("Got request from %d to send %ld bytes", tenant->id, request->data.len); + //LOG_D("Msg to send %s\n", request->data.data); + //TODO: probably want more checks here as well + + int sockfd = lookup_socket(request->sockfd); + if(!(sockfd)) + { + LOG_E("Bad socket descriptor: %d\n", sockfd); + return 1; + } + if(!(request->data.data)) + { + LOG_E("couldnt get data from request\n"); + return 1; + } + + #ifdef LOG_PWR + if(last_send == 0) + { + last_send = clock(); + LOG_P ("%f ", (double) last_send / CLOCKS_PER_SEC); + }else{ + time_since_send = (double)((clock() - last_send)/CLOCKS_PER_SEC); + LOG_P("%f ", time_since_send); + } + + LOG_P("%d ", request->data.len); + + #endif + + if((send(sockfd, request->data.data, request->data.len, request->flags)) < 0) + { + LOG_E("Failed to send data for tenant %d error %d\n", tenant->id, errno); + }else{ + + LOG_D("Sent data from tenant %d\n", tenant->id); + } + + #ifdef LOG_PWR + curr_capacity = present_power(); + + double change = ((double) last_capacity - curr_capacity) - (time_since_send * PASSIVE_PWR_PER_SEC); + LOG_P("%f\n", change); + + last_capacity = curr_capacity; + last_send = clock(); + + #endif + + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; + GenericResponse *sub_resp = sub_resp_buf; + *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; + resp->generic = sub_resp; + + return 0; +} +static void +report_nng_error(const char *func, int rv) { + LOG_E("nng error: (%s) %s", func, nng_strerror(rv)); +} + +int +sunneed_listen(void) { + SUNNEED_NNG_SET_ERROR_REPORT_FUNC(report_nng_error); + + #ifdef LOG_PWR + last_capacity = present_power(); + int capacity_change; + last_send = 0; + #endif + + // Initialize client states. + for (int i = 0; i < MAX_TENANTS; i++) { + tenant_pipes[i] = (struct tenant_pipe){.tenant = NULL, + // TODO Why do I need to cast this... + .pipe = (nng_pipe)NNG_PIPE_INITIALIZER}; + } + + nng_socket sock; + + LOG_I("Starting listener loop..."); + + // Make a socket and attach it to the sunneed URL. + + SUNNEED_NNG_TRY_RET(nng_rep0_open, != 0, &sock); + + SUNNEED_NNG_TRY_RET(nng_listen, < 0, sock, SUNNEED_LISTENER_URL, NULL, 0); + + + + + // Buffer for `serve_` methods to write their sub-response to. + void *sub_resp_buf = malloc(SUB_RESPONSE_BUF_SZ); + // TODO Check malloc. + if(sub_resp_buf == NULL) + { + LOG_E ("ERROR, failed to malloc sub_resp_buf in sunneed_listener"); + } + int ret = -1; + // Await messages. + for (;;) { + nng_msg *msg = NULL; + + + SUNNEED_NNG_TRY_SET(nng_recvmsg, ret, != 0, sock, &msg, 0); + if(ret) + { + LOG_E("nng_recvmsg failed with error %d\n", ret); + } + // TODO They claim nng_msg_get_pipe() returns -1 on error, but its return type is nng_pipe, which can't + // be compared to an integer. + if(!(msg)) + { + LOG_E("recv msg couldn't get the request"); + } + nng_pipe pipe = nng_msg_get_pipe(msg); + + // Get contents of message. + size_t msg_len = nng_msg_len(msg); + SUNNEED_NNG_MSG_LEN_FIX(msg_len); + SunneedRequest *request = sunneed_request__unpack(NULL, msg_len, nng_msg_body(msg)); + + if (request == NULL) { + LOG_W("Received null request from %d", pipe.id); + goto end; + } + + struct sunneed_tenant *tenant = NULL; + + // Find the pipe's associated tenant. If we can't find it, we error out unless the message is of type REGISTER_CLIENT. + if ((tenant = tenant_of_pipe(pipe.id)) == NULL && request->message_type_case != SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT) { + // This client has not registered! + LOG_W("Received message from %d, who is not registered.", pipe.id); + goto end; + } + + // Begin setting up our response. + SunneedResponse resp = SUNNEED_RESPONSE__INIT; + int ret = -1; + + #ifdef LOG_PWR + reqs_since_last_log++; + #endif + + switch (request->message_type_case) { + case SUNNEED_REQUEST__MESSAGE_TYPE__NOT_SET: + LOG_W("Request from pipe %d has no message type set.", pipe.id); + ret = -1; + break; + case SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT: + ret = serve_register_client(&resp, sub_resp_buf, pipe); + break; + case SUNNEED_REQUEST__MESSAGE_TYPE_UNREGISTER_CLIENT: + ret = serve_unregister_client(&resp, sub_resp_buf, pipe, tenant); + break; + case SUNNEED_REQUEST__MESSAGE_TYPE_OPEN_FILE: + ret = serve_open_file(&resp, sub_resp_buf, tenant, request->open_file); + break; + case SUNNEED_REQUEST__MESSAGE_TYPE_WRITE: + #ifdef LOG_PWR + if (reqs_since_last_log < REQS_PER_LOG) { + LOG_D("%d, ",reqs_since_last_log); + LOG_I("%d\n",capacity_change); + } else if (reqs_since_last_log > REQS_PER_LOG) { + curr_capacity = present_power(); + capacity_change = last_capacity - curr_capacity; + last_capacity = curr_capacity; + LOG_D("%d\n",capacity_change); + LOG_D("%d, ",reqs_since_last_log); + reqs_since_last_log = 1; + } + #endif + ret = serve_write(&resp, sub_resp_buf, tenant, request->write); + break; + case SUNNEED_REQUEST__MESSAGE_TYPE_SOCKET: + ret = serve_socket(&resp, sub_resp_buf, request->socket); + break; + case SUNNEED_REQUEST__MESSAGE_TYPE_CONNECT: + ret = serve_connect(&resp, sub_resp_buf, pipe, request->connect); + break; + case SUNNEED_REQUEST__MESSAGE_TYPE_SEND: + ret = serve_send(&resp, sub_resp_buf, tenant, request->send); + break; + default: + LOG_W("Received request with invalid type %d", request->message_type_case); + ret = -1; + #ifdef LOG_PWR + reqs_since_last_log--; + #endif + break; + } + + resp.status = ret; + + // Create and send the response message. + nng_msg *resp_msg; + int ret2 = -2; + int resp_len = sunneed_response__get_packed_size(&resp); + void *resp_buf = malloc(resp_len); + sunneed_response__pack(&resp, resp_buf); + + SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &resp_msg, resp_len); + SUNNEED_NNG_TRY(nng_msg_insert, != 0, resp_msg, resp_buf, resp_len); + SUNNEED_NNG_TRY_SET(nng_sendmsg, ret2, != 0, sock, resp_msg, 0); + + if(resp.message_type_case == SUNNEED_RESPONSE__MESSAGE_TYPE_REGISTER_CLIENT) + { + //need to free the locked paths array after we send the message + free(resp.register_client->locked_paths); + } + + //nng_msg_free(resp_msg); + memset(resp_buf, '\0', resp_len); + memset(sub_resp_buf, '\0', SUB_RESPONSE_BUF_SZ); + + end: + if(request != NULL) sunneed_request__free_unpacked(request, NULL); + + free(resp_buf); + //in theory nng_sendmsg frees this for us, but it may be casuing the memory bug + if(ret2) nng_msg_free(resp_msg); + + } + free(sub_resp_buf); + + +} diff --git a/src/sunneed_listener.h b/src/sunneed_listener.h index bdab472..7f11fa9 100644 --- a/src/sunneed_listener.h +++ b/src/sunneed_listener.h @@ -1,33 +1,33 @@ -#ifndef _SUNNEED_LISTENER_H_ -#define _SUNNEED_LISTENER_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "log.h" -#include "shared/sunneed_ipc.h" -#include "sunneed.h" -#include "sunneed_power.h" -#include "sunneed_proc.h" - -#define SUNNEED_MESSAGE_DEFAULT_BODY_SZ 64 -#define SUNNEED_MAX_IPC_CLIENTS 512 -#define SUNNEED_DEVICE_PATH_MAX_LEN 64 - -int -sunneed_listen(void); - -#endif +#ifndef _SUNNEED_LISTENER_H_ +#define _SUNNEED_LISTENER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "log.h" +#include "shared/sunneed_ipc.h" +#include "sunneed.h" +#include "sunneed_power.h" +#include "sunneed_proc.h" + +#define SUNNEED_MESSAGE_DEFAULT_BODY_SZ 64 +#define SUNNEED_MAX_IPC_CLIENTS 512 +#define SUNNEED_DEVICE_PATH_MAX_LEN 64 + +int +sunneed_listen(void); + +#endif diff --git a/src/sunneed_loader.c b/src/sunneed_loader.c index 8339b0f..4616356 100644 --- a/src/sunneed_loader.c +++ b/src/sunneed_loader.c @@ -1,250 +1,251 @@ -#include "sunneed_loader.h" - -/* Convenience array containing pointers to all locked file paths. It is generated as devices are loaded. */ -const char *locked_file_paths[MAX_LOCKED_FILES] = { NULL }; - -static void -add_locked_file_path(const char *path) { - for (int i = 0; i < MAX_LOCKED_FILES; i++) { - if (locked_file_paths[i] == NULL) { - LOG_I("Adding locked file path %d: '%s'", i, path); - locked_file_paths[i] = path; - return; - } - } - - LOG_E("No space left in locked file paths array."); - // TODO Real exit function. - exit(1); -} - -/** - * Assign to the `device_type_data` member of a device struct, choosing the correct union member based on what has been assigned - * to `device_type_kind`. - */ -static int -assign_device_type_data_field(struct sunneed_device *dev, void *data) { - // We're gonna dereference a void pointer. Try not to worry about it. - switch (dev->device_type_kind) { - case DEVICE_TYPE_FILE_LOCK: ; - // Copy each string from the data's `paths`. - struct sunneed_device_type_file_lock *file_lock = (struct sunneed_device_type_file_lock *)data; - dev->device_type_data.file_lock = (struct sunneed_device_type_file_lock *)malloc(sizeof(struct sunneed_device_type_file_lock) + sizeof(char *) * file_lock->len); - dev->device_type_data.file_lock->len = file_lock->len; - for (unsigned int i = 0; i < file_lock->len; i++) { - // Copy the string, adding a null terminator. - size_t len = strlen(file_lock->paths[i]); - dev->device_type_data.file_lock->paths[i] = malloc(len + 1); - strncpy(dev->device_type_data.file_lock->paths[i], file_lock->paths[i], len); - dev->device_type_data.file_lock->paths[i][len] = '\0'; - - add_locked_file_path(dev->device_type_data.file_lock->paths[i]); - } - break; - - default: - LOG_E("Invalid device kind %d", dev->device_type_kind); - return 1; - } - - return 0; -} - -static bool -is_object_file(char *path) { - size_t len = strlen(path); - if (strncmp(path + len - OBJ_EXTENSION_LEN, OBJ_EXTENSION, len) == 0) - return true; - else - return false; -} - -static int -load_device(const char *device_path, const char *device_name, int handle, struct sunneed_device *dev) { - int retval = 0; - - void *sym; - - // Load the object. - void *dlhandle = dlopen(device_path, RTLD_LAZY | RTLD_LOCAL); - if (!dlhandle) { - LOG_E("Error loading device from '%s': %s", device_name, dlerror()); - return 1; - } - - sym = dlsym(dlhandle, "device_flags"); - if (!sym) { - LOG_E("Failed to load device flags: %s", dlerror()); - retval = 1; - goto end; - } - unsigned int flags = *(unsigned int *)sym; - - // Set up device flags. - bool silent_fail = false; - if (flags) { - silent_fail = flags & SUNNEED_DEVICE_FLAG_SILENT_FAIL; - } - - char *identifier = malloc(DEVICE_IDENTIFIER_LEN); - if (!identifier) { - if (!silent_fail) LOG_E("Failed to allocate memory for device name."); - retval = 1; - goto end; - } - - sym = dlsym(dlhandle, "device_type_kind"); - if (!sym) { - if (!silent_fail) LOG_E("Getting `device_type_kind` failed: %s", dlerror()); - retval = 1; - goto end; - } - enum sunneed_device_type kind = *(enum sunneed_device_type *)sym; - - // Write to struct. - *dev = (struct sunneed_device) { - .is_ready = false, - .handle = handle, - .identifier = identifier, - .device_type_kind = kind - }; - strncpy(dev->identifier, device_name, DEVICE_IDENTIFIER_LEN); - dev->identifier[DEVICE_IDENTIFIER_LEN - 1] = '\0'; - - sym = dlsym(dlhandle, "init"); - if (!sym) { - if (!silent_fail) LOG_E("Getting `init` failed: %s", dlerror()); - retval = 1; - goto end; - } - int (*init)(void) = (int (*)(void))sym; - - // Call `init`, failing on nonzero. - int init_val = init(); - if (init_val != 0) { - if (!silent_fail && init_val > 0) LOG_E("`init` failed with %d", init_val); - retval = 1; - goto end; - } - - // Set up device type data. - sym = dlsym(dlhandle, "get_device_type_data"); - if (!sym) { - if (!silent_fail) LOG_E("Getting `get_device_type_data` failed: %s", dlerror()); - retval = 1; - goto end; - } - - // Obtain the pointer to the device type data. - void *data = ((void *(*)(void))sym)(); - if (!data) { - if (!silent_fail) LOG_E("No device type data returned!"); - retval = 1; - goto end; - } - - // Write that data to the appropriate union field. - if (assign_device_type_data_field(dev, data)) { - if (!silent_fail) LOG_E("Failed to assign to device type data field."); - retval = 1; - goto end; - } - - // Hooray! - dev->is_ready = true; - -end: - dlclose(dlhandle); - - // Clean up if error. - if (retval != 0) { - if (identifier) free(identifier); - } - - return retval; -} - -/* Loads all objects in the device directory as sunneed devices, storing them in the `target` array. */ -int -sunneed_load_devices(struct sunneed_device *target) { - int ret = 0, res; - - DIR *dir = opendir("build/device"); - struct dirent *ent; - - if (!dir) { - LOG_E("Failed to open devices directory"); - ret = 1; - goto end; - } - - unsigned int device_count = 0; - while ((ent = readdir(dir)) != NULL) { - if (strlen(ent->d_name) <= OBJ_EXTENSION_LEN) - // Can't be a real filename. - continue; - - // Strip the `.so` from the path to get the device name. - char device_name[DEVICE_PATH_LEN]; - strncpy(device_name, ent->d_name, strlen(ent->d_name) - OBJ_EXTENSION_LEN); - device_name[strlen(ent->d_name) - OBJ_EXTENSION_LEN] = '\0'; - - char device_path[DEVICE_PATH_LEN] = "build/device/"; - strncat(device_path, ent->d_name, DEVICE_PATH_LEN); - - if (is_object_file(device_path)) { - if ((res = load_device(device_path, device_name, device_count++, target)) != 0) - continue; - - LOG_I("Loaded device '%s'", device_name); - - target++; - } - } - - - -end: - closedir(dir); - - return ret; -} - -#ifdef TESTING - -int -TEST_load_device(void) { - int res; - - struct sunneed_device dev; - if ((res = load_device("build/device/test_file_lock.so", "test", 0, &dev)) != 0) - return set_sunneed_error(1, "`load_device` failed: %d", res); - - if (dev.handle != 0) - return set_sunneed_error(2, "invalid handle %d", dev.handle); - - if (strcmp(dev.identifier, "test") != 0) - return set_sunneed_error(3, "incorrect identifier '%s'", dev.identifier); - - if (strcmp(dev.device_type_data.file_lock->paths[0], TEST_FILE_LOCK_FILE_PATH) != 0) - return set_sunneed_error(4, "wrong file lock path '%s'", dev.device_type_data.file_lock->paths[0]); - - if (dev.device_type_data.file_lock->len != 1) - return set_sunneed_error(5, "wrong number of locked files %d", dev.device_type_data.file_lock->len); - - return 0; -} - -int -TEST_load_broken_device(void) { - int res; - - struct sunneed_device dev; - if ((res = load_device("build/device/test_broken.so", "test", 0, &dev)) == 0) - // Should fail. - return set_sunneed_error(1, "loading broken device didn't fail"); - - return 0; -} - -#endif +#include "sunneed_loader.h" + +/* Convenience array containing pointers to all locked file paths. It is generated as devices are loaded. */ +const char *locked_file_paths[MAX_LOCKED_FILES] = { NULL }; + +static void +add_locked_file_path(const char *path) { + for (int i = 0; i < MAX_LOCKED_FILES; i++) { + if (locked_file_paths[i] == NULL) { + LOG_I("Adding locked file path %d: '%s'", i, path); + locked_file_paths[i] = path; + return; + } + } + + LOG_E("No space left in locked file paths array."); + // TODO Real exit function. + exit(1); +} + +/** + * Assign to the `device_type_data` member of a device struct, choosing the correct union member based on what has been assigned + * to `device_type_kind`. + */ +static int +assign_device_type_data_field(struct sunneed_device *dev, void *data) { + // We're gonna dereference a void pointer. Try not to worry about it. + switch (dev->device_type_kind) { + case DEVICE_TYPE_FILE_LOCK: ; + // Copy each string from the data's `paths`. + struct sunneed_device_type_file_lock *file_lock = (struct sunneed_device_type_file_lock *)data; + dev->device_type_data.file_lock = (struct sunneed_device_type_file_lock *)malloc(sizeof(struct sunneed_device_type_file_lock) + sizeof(char *) * file_lock->len); + dev->device_type_data.file_lock->len = file_lock->len; + for (unsigned int i = 0; i < file_lock->len; i++) { + // Copy the string, adding a null terminator. + size_t len = strlen(file_lock->paths[i]); + dev->device_type_data.file_lock->paths[i] = malloc(len + 1); + strncpy(dev->device_type_data.file_lock->paths[i], file_lock->paths[i], len); + dev->device_type_data.file_lock->paths[i][len] = '\0'; + + add_locked_file_path(dev->device_type_data.file_lock->paths[i]); + } + break; + + default: + LOG_E("Invalid device kind %d", dev->device_type_kind); + return 1; + } + + return 0; +} + +static bool +is_object_file(char *path) { + size_t len = strlen(path); + if (strncmp(path + len - OBJ_EXTENSION_LEN, OBJ_EXTENSION, len) == 0) + return true; + else + return false; +} + +static int +load_device(const char *device_path, const char *device_name, int handle, struct sunneed_device *dev) { + int retval = 0; + + void *sym; + + // Load the object. + void *dlhandle = dlopen(device_path, RTLD_LAZY | RTLD_LOCAL); + if (!dlhandle) { + LOG_E("Error loading device from '%s': %s", device_name, dlerror()); + return 1; + } + + sym = dlsym(dlhandle, "device_flags"); + if (!sym) { + LOG_E("Failed to load device flags: %s", dlerror()); + retval = 1; + goto end; + } + unsigned int flags = *(unsigned int *)sym; + + // Set up device flags. + bool silent_fail = false; + if (flags) { + silent_fail = flags & SUNNEED_DEVICE_FLAG_SILENT_FAIL; + } + + char *identifier = malloc(DEVICE_IDENTIFIER_LEN); + if (!identifier) { + if (!silent_fail) LOG_E("Failed to allocate memory for device name."); + retval = 1; + goto end; + } + + sym = dlsym(dlhandle, "device_type_kind"); + if (!sym) { + if (!silent_fail) LOG_E("Getting `device_type_kind` failed: %s", dlerror()); + retval = 1; + goto end; + } + enum sunneed_device_type kind = *(enum sunneed_device_type *)sym; + + // Write to struct. + *dev = (struct sunneed_device) { + .is_ready = false, + .handle = handle, + .identifier = identifier, + .device_type_kind = kind + }; + strncpy(dev->identifier, device_name, DEVICE_IDENTIFIER_LEN); + dev->identifier[DEVICE_IDENTIFIER_LEN - 1] = '\0'; + + sym = dlsym(dlhandle, "init"); + if (!sym) { + if (!silent_fail) LOG_E("Getting `init` failed: %s", dlerror()); + retval = 1; + goto end; + } + int (*init)(void) = (int (*)(void))sym; + + // Call `init`, failing on nonzero. + int init_val = init(); + if (init_val != 0) { + if (!silent_fail && init_val > 0) LOG_E("`init` failed with %d", init_val); + retval = 1; + goto end; + } + + // Set up device type data. + sym = dlsym(dlhandle, "get_device_type_data"); + if (!sym) { + if (!silent_fail) LOG_E("Getting `get_device_type_data` failed: %s", dlerror()); + retval = 1; + goto end; + } + + // Obtain the pointer to the device type data. + void *data = ((void *(*)(void))sym)(); + if (!data) { + if (!silent_fail) LOG_E("No device type data returned!"); + retval = 1; + goto end; + } + + // Write that data to the appropriate union field. + if (assign_device_type_data_field(dev, data)) { + if (!silent_fail) LOG_E("Failed to assign to device type data field."); + retval = 1; + goto end; + } + + // Hooray! + dev->is_ready = true; + +end: + dlclose(dlhandle); + + // Clean up if error. + if (retval != 0) { + if (identifier) free(identifier); + }else if (identifier) free(identifier); + + + return retval; +} + +/* Loads all objects in the device directory as sunneed devices, storing them in the `target` array. */ +int +sunneed_load_devices(struct sunneed_device *target) { + int ret = 0, res; + + DIR *dir = opendir("build/device"); + struct dirent *ent; + + if (!dir) { + LOG_E("Failed to open devices directory"); + ret = 1; + goto end; + } + + unsigned int device_count = 0; + while ((ent = readdir(dir)) != NULL) { + if (strlen(ent->d_name) <= OBJ_EXTENSION_LEN) + // Can't be a real filename. + continue; + + // Strip the `.so` from the path to get the device name. + char device_name[DEVICE_PATH_LEN]; + strncpy(device_name, ent->d_name, strlen(ent->d_name) - OBJ_EXTENSION_LEN); + device_name[strlen(ent->d_name) - OBJ_EXTENSION_LEN] = '\0'; + + char device_path[DEVICE_PATH_LEN] = "build/device/"; + strncat(device_path, ent->d_name, DEVICE_PATH_LEN); + + if (is_object_file(device_path)) { + if ((res = load_device(device_path, device_name, device_count++, target)) != 0) + continue; + + LOG_I("Loaded device '%s'", device_name); + + target++; + } + } + + + +end: + closedir(dir); + + return ret; +} + +#ifdef TESTING + +int +TEST_load_device(void) { + int res; + + struct sunneed_device dev; + if ((res = load_device("build/device/test_file_lock.so", "test", 0, &dev)) != 0) + return set_sunneed_error(1, "`load_device` failed: %d", res); + + if (dev.handle != 0) + return set_sunneed_error(2, "invalid handle %d", dev.handle); + + if (strcmp(dev.identifier, "test") != 0) + return set_sunneed_error(3, "incorrect identifier '%s'", dev.identifier); + + if (strcmp(dev.device_type_data.file_lock->paths[0], TEST_FILE_LOCK_FILE_PATH) != 0) + return set_sunneed_error(4, "wrong file lock path '%s'", dev.device_type_data.file_lock->paths[0]); + + if (dev.device_type_data.file_lock->len != 1) + return set_sunneed_error(5, "wrong number of locked files %d", dev.device_type_data.file_lock->len); + + return 0; +} + +int +TEST_load_broken_device(void) { + int res; + + struct sunneed_device dev; + if ((res = load_device("build/device/test_broken.so", "test", 0, &dev)) == 0) + // Should fail. + return set_sunneed_error(1, "loading broken device didn't fail"); + + return 0; +} + +#endif diff --git a/src/sunneed_loader.h b/src/sunneed_loader.h index 7775414..79786e7 100644 --- a/src/sunneed_loader.h +++ b/src/sunneed_loader.h @@ -1,32 +1,32 @@ -#ifndef _SUNNEED_LOADER_H_ -#define _SUNNEED_LOADER_H_ - -#include -#include -#include - -#include "sunneed_device.h" - -#define DEVICE_PATH_LEN 64 - -#define OBJ_EXTENSION ".so" -#define OBJ_EXTENSION_LEN 3 - -int -sunneed_load_devices(struct sunneed_device *target); - -#ifdef TESTING - -#include "shared/sunneed_testing.h" -#include "sunneed_test.h" - -int TEST_load_device(void); -int TEST_load_broken_device(void); - -#define SUNNEED_RUNTIME_TESTS_LOADER \ - TEST_load_device, \ - TEST_load_broken_device - -#endif - -#endif +#ifndef _SUNNEED_LOADER_H_ +#define _SUNNEED_LOADER_H_ + +#include +#include +#include + +#include "sunneed_device.h" + +#define DEVICE_PATH_LEN 64 + +#define OBJ_EXTENSION ".so" +#define OBJ_EXTENSION_LEN 3 + +int +sunneed_load_devices(struct sunneed_device *target); + +#ifdef TESTING + +#include "shared/sunneed_testing.h" +#include "sunneed_test.h" + +int TEST_load_device(void); +int TEST_load_broken_device(void); + +#define SUNNEED_RUNTIME_TESTS_LOADER \ + TEST_load_device, \ + TEST_load_broken_device + +#endif + +#endif diff --git a/src/sunneed_power.c b/src/sunneed_power.c index e1a74b8..b0d32e1 100644 --- a/src/sunneed_power.c +++ b/src/sunneed_power.c @@ -1,152 +1,152 @@ -#include "sunneed_power.h" - -extern struct sunneed_tenant tenants[]; - -static struct { - // Index. - int id; - - // Power at the start of the quantum. - int present_power; - - // Timestamp for the start of the quantum. - struct timespec begin_time; - - // Whether events can be recorded to this quantum. - bool is_active; - - // If a power event has occurred in this quantum. - bool has_power_event; -} current_quantum = {-1, 0.0, {0}, false, false}; - -int -sunneed_record_power_usage_event(struct sunneed_power_usage_event ev) { - if (!current_quantum.is_active) { - LOG_E("Cannot record a power event outside of an active quantum"); - return 1; - } - - struct sunneed_power_usage_event *cur = power_usage_evs; - if (cur == NULL) { - LOG_E("Power usage events head node is unallocated!"); - return 1; - } - - // Find tail of events list. - while (cur->next != NULL) { - cur = cur->next; - } - - // Copy given event to heap and attach to end of events list. - cur->next = malloc(sizeof(struct sunneed_power_usage_event)); - if (!cur->next) { - LOG_E("Failed to allocate space for power usage event."); - return 1; - } - *cur->next = ev; - - current_quantum.has_power_event = true; - - return 0; -} - -int -sunneed_quantum_begin(void) { - LOG_D("Begin start procedure for quantum %d", current_quantum.id + 1); - - // Set quantum metadata. - current_quantum.id++; - current_quantum.present_power = present_power(); - timespec_get(¤t_quantum.begin_time, TIME_UTC); - - // Reset power events array. - struct sunneed_power_usage_event *cur = power_usage_evs; - while (cur != NULL) { - struct sunneed_power_usage_event *next = cur->next; - free(cur); - cur = next; - } - - power_usage_evs = malloc(sizeof(struct sunneed_power_usage_event)); - if (!power_usage_evs) { - LOG_E("Failed to allocate space for power usage events!"); - return 1; - } - - power_usage_evs->next = NULL; - - LOG_I("Started quantum %d", current_quantum.id); - current_quantum.is_active = true; - - return 0; -} - -int -sunneed_quantum_end(void) { - LOG_I("Ending quantum %d", current_quantum.id); - - current_quantum.is_active = false; - - double power_consumed[MAX_TENANTS] = {0.0}; - - // Add up power used in this quantum by each tenant. - struct sunneed_power_usage_event *ev = power_usage_evs; - while (ev != NULL) { - if (!current_quantum.has_power_event) - // No power events to add to tenant. - break; - - if (ev->ev.device == NULL) { - // This is a CPU usage digest. - // TODO Check CPU usage lol. - power_consumed[ev->ev.tenant->id] += 0; - } else { - // TODO Get power from event. - power_consumed[ev->ev.tenant->id] += 0; - } - ev = ev->next; - } - - float unscaled_proportions[MAX_TENANTS] = {0.0}; - float unscaled_sum = 0.0; - - // Update power proportions for tenants. - - // First, get the percentage of their given power that each tenant used. - for (int i = 0; i < MAX_TENANTS; i++) { - unscaled_proportions[i] - = 1.0 - power_consumed[i] / (current_quantum.present_power * tenants[i].power_proportion); - unscaled_sum += unscaled_proportions[i]; - } - - // Multiply the total percentage by a scaling factor such that the sum of new proportions adds up to 1. - float scale_factor = 1.0 / unscaled_sum; - for (int i = 0; i < MAX_TENANTS; i++) { - tenants[i].power_proportion = unscaled_proportions[i] * scale_factor; - } - - LOG_D("Finished ending quantum %d", current_quantum.id); - - return 0; -} - -sunneed_worker_thread_result_t -sunneed_quantum_worker(__attribute__((unused)) void *args) { - int ret; - power_usage_evs = NULL; - while (true) { - if ((ret = sunneed_quantum_begin()) != 0) { - goto end; - } - - usleep(QUANTUM_DURATION_MS * 1000); - - if ((ret = sunneed_quantum_end()) != 0) { - goto end; - } - } - -end: - LOG_E("Error with quantum; quantum thread stopping"); - return NULL; -} +#include "sunneed_power.h" + +extern struct sunneed_tenant tenants[]; + +static struct { + // Index. + int id; + + // Power at the start of the quantum. + int present_power; + + // Timestamp for the start of the quantum. + struct timespec begin_time; + + // Whether events can be recorded to this quantum. + bool is_active; + + // If a power event has occurred in this quantum. + bool has_power_event; +} current_quantum = {-1, 0.0, {0}, false, false}; + +int +sunneed_record_power_usage_event(struct sunneed_power_usage_event ev) { + if (!current_quantum.is_active) { + LOG_E("Cannot record a power event outside of an active quantum"); + return 1; + } + + struct sunneed_power_usage_event *cur = power_usage_evs; + if (cur == NULL) { + LOG_E("Power usage events head node is unallocated!"); + return 1; + } + + // Find tail of events list. + while (cur->next != NULL) { + cur = cur->next; + } + + // Copy given event to heap and attach to end of events list. + cur->next = malloc(sizeof(struct sunneed_power_usage_event)); + if (!cur->next) { + LOG_E("Failed to allocate space for power usage event."); + return 1; + } + *cur->next = ev; + + current_quantum.has_power_event = true; + + return 0; +} + +int +sunneed_quantum_begin(void) { + LOG_D("Begin start procedure for quantum %d", current_quantum.id + 1); + + // Set quantum metadata. + current_quantum.id++; + current_quantum.present_power = present_power(); + timespec_get(¤t_quantum.begin_time, TIME_UTC); + + // Reset power events array. + struct sunneed_power_usage_event *cur = power_usage_evs; + while (cur != NULL) { + struct sunneed_power_usage_event *next = cur->next; + free(cur); + cur = next; + } + + power_usage_evs = malloc(sizeof(struct sunneed_power_usage_event)); + if (!power_usage_evs) { + LOG_E("Failed to allocate space for power usage events!"); + return 1; + } + + power_usage_evs->next = NULL; + + LOG_I("Started quantum %d", current_quantum.id); + current_quantum.is_active = true; + + return 0; +} + +int +sunneed_quantum_end(void) { + LOG_I("Ending quantum %d", current_quantum.id); + + current_quantum.is_active = false; + + double power_consumed[MAX_TENANTS] = {0.0}; + + // Add up power used in this quantum by each tenant. + struct sunneed_power_usage_event *ev = power_usage_evs; + while (ev != NULL) { + if (!current_quantum.has_power_event) + // No power events to add to tenant. + break; + + if (ev->ev.device == NULL) { + // This is a CPU usage digest. + // TODO Check CPU usage lol. + power_consumed[ev->ev.tenant->id] += 0; + } else { + // TODO Get power from event. + power_consumed[ev->ev.tenant->id] += 0; + } + ev = ev->next; + } + + float unscaled_proportions[MAX_TENANTS] = {0.0}; + float unscaled_sum = 0.0; + + // Update power proportions for tenants. + + // First, get the percentage of their given power that each tenant used. + for (int i = 0; i < MAX_TENANTS; i++) { + unscaled_proportions[i] + = 1.0 - power_consumed[i] / (current_quantum.present_power * tenants[i].power_proportion); + unscaled_sum += unscaled_proportions[i]; + } + + // Multiply the total percentage by a scaling factor such that the sum of new proportions adds up to 1. + float scale_factor = 1.0 / unscaled_sum; + for (int i = 0; i < MAX_TENANTS; i++) { + tenants[i].power_proportion = unscaled_proportions[i] * scale_factor; + } + + LOG_D("Finished ending quantum %d", current_quantum.id); + + return 0; +} + +sunneed_worker_thread_result_t +sunneed_quantum_worker(__attribute__((unused)) void *args) { + int ret; + power_usage_evs = NULL; + while (true) { + if ((ret = sunneed_quantum_begin()) != 0) { + goto end; + } + + usleep(QUANTUM_DURATION_MS * 1000); + + if ((ret = sunneed_quantum_end()) != 0) { + goto end; + } + } + +end: + LOG_E("Error with quantum; quantum thread stopping"); + return NULL; +} diff --git a/src/sunneed_power.h b/src/sunneed_power.h index d93c13a..1cc5730 100644 --- a/src/sunneed_power.h +++ b/src/sunneed_power.h @@ -1,53 +1,53 @@ -#ifndef _SUNNEED_POWER_H_ -#define _SUNNEED_POWER_H_ - -#include -#include -#include - -#include "log.h" -#include "shared/sunneed_pip_interface.h" -#include "sunneed.h" -#include "sunneed_device.h" -#include "sunneed_proc.h" - -#define MAX_DEVICES 64 - -#define QUANTUMS_RINGBUF_SZ 16 - -#define PASSIVE_PWR_PER_SEC 0.07667 - -// TODO This is waaaaaaaaaaaaaaaaaaaaay too big. -#define QUANTUM_DURATION_MS 5000 - -struct sunneed_power_usage_event { - struct { - // The moment the power event occurred. - struct timespec timestamp; - - struct sunneed_tenant *tenant; - - // If NULL, then the power event is a CPU usage digest. - struct sunneed_device *device; - - // Unused for now, can configure parameters of the device interaction. - void *args; - } ev; - struct sunneed_power_usage_event *next; -}; - -int -sunneed_record_power_usage_event(struct sunneed_power_usage_event ev); -int -sunneed_quantum_begin(void); -int -sunneed_quantum_end(void); - -sunneed_worker_thread_result_t -sunneed_quantum_worker(void *args); - -struct sunneed_power_usage_event *power_usage_evs; - -struct sunneed_device devices[MAX_DEVICES]; - -#endif +#ifndef _SUNNEED_POWER_H_ +#define _SUNNEED_POWER_H_ + +#include +#include +#include + +#include "log.h" +#include "shared/sunneed_pip_interface.h" +#include "sunneed.h" +#include "sunneed_device.h" +#include "sunneed_proc.h" + +#define MAX_DEVICES 64 + +#define QUANTUMS_RINGBUF_SZ 16 + +#define PASSIVE_PWR_PER_SEC 0.07667 + +// TODO This is waaaaaaaaaaaaaaaaaaaaay too big. +#define QUANTUM_DURATION_MS 5000 + +struct sunneed_power_usage_event { + struct { + // The moment the power event occurred. + struct timespec timestamp; + + struct sunneed_tenant *tenant; + + // If NULL, then the power event is a CPU usage digest. + struct sunneed_device *device; + + // Unused for now, can configure parameters of the device interaction. + void *args; + } ev; + struct sunneed_power_usage_event *next; +}; + +int +sunneed_record_power_usage_event(struct sunneed_power_usage_event ev); +int +sunneed_quantum_begin(void); +int +sunneed_quantum_end(void); + +sunneed_worker_thread_result_t +sunneed_quantum_worker(void *args); + +struct sunneed_power_usage_event *power_usage_evs; + +struct sunneed_device devices[MAX_DEVICES]; + +#endif diff --git a/src/sunneed_proc.c b/src/sunneed_proc.c index d510869..0b6daa0 100644 --- a/src/sunneed_proc.c +++ b/src/sunneed_proc.c @@ -1,119 +1,119 @@ -#include "sunneed_proc.h" - -struct sunneed_tenant tenants[MAX_TENANTS]; - -int -sunneed_init_tenants(void) { - for (int i = 0; i < MAX_TENANTS; i++) { - tenants[i] = (struct sunneed_tenant){.id = i, .pid = 0, .power_proportion = 0.0, .is_active = false}; - } - - return 0; -} - -// Find an unused spot for a tenant and register ourself there. -struct sunneed_tenant * -sunneed_tenant_register(pid_t pid) { - struct sunneed_tenant *tenant = NULL; - for (int i = 0; i < MAX_TENANTS; i++) { - if (!tenants[i].is_active) { - tenant = &tenants[i]; - break; - } - } - - if (tenant == NULL) { - LOG_E("Sorry PID %d, can't spawn any more tenants!", pid); - return NULL; - } - - tenant->pid = pid; - tenant->is_active = true; - - return tenant; -} - -int -sunneed_tenant_unregister(struct sunneed_tenant *tenant) { - if (tenant == NULL || !tenant->is_active) { - LOG_W("Cannot deactivate an inactive tenant"); - return 1; - } - - tenant->is_active = false; - - return 0; -} - -unsigned int -sunneed_get_num_tenants(void) { - unsigned int num_tenants = 0; - - for (int i = 0; i < MAX_TENANTS; i++) { - if (tenants[i].is_active) - num_tenants++; - } - - return num_tenants; -} - -int -sunneed_update_tenant_cpu_usage(void) { - // TODO Verify that the pipe and PID match up. It is entirely possible for the tenant process to die and a new - // process with the same PID as the tenant's to start up. - FILE *file; - char filepath[FILENAME_MAX] = "/proc/stat"; - - file = fopen(filepath, "r"); - fscanf(file, "%*s %llu %llu %llu %llu", &cpu_usage.user, &cpu_usage.nice, &cpu_usage.sys, &cpu_usage.idle); - fclose(file); - - for (struct sunneed_tenant *tenant = tenants; tenant < tenants + MAX_TENANTS; tenant++) { - if (!tenant->is_active) - continue; - - snprintf(filepath, FILENAME_MAX, "/proc/%d/stat", tenant->pid); - if (access(filepath, F_OK) != 0) { - LOG_E("Unable to find procfs file for PID %d; most likely a tenant ended but forgot to tell us", - tenant->pid); - continue; - } - - file = fopen(filepath, "r"); - // Read CPU consumption from this tenant's PID. - fscanf(file, - "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u" // 13 things we don't care about - " %llu %llu" // usertime, systemtime - " %*d %*d %*d %*d %*d %*d %*u %*u", // 8 things we don't care about - &cpu_usage.tenants[tenant->id].user, &cpu_usage.tenants[tenant->id].sys); - fclose(file); - } - - return 0; -} - -int -sunneed_get_tenant_cpu_usage(sunneed_tenant_id_t tenant_id) { - if (!tenants[tenant_id].is_active) { - LOG_E("Attempt to get CPU usage of inactive tenant %d", tenant_id); - return -1; - } - - // TODO Shit - - return 0; -} - -sunneed_worker_thread_result_t -sunneed_proc_monitor(__attribute__((unused)) void *args) { - int ret; - while (true) { - LOG_D("Updating process CPU usage"); - if ((ret = sunneed_update_tenant_cpu_usage()) != 0) { - LOG_E("Error updating CPU usage; monitor thread stopping"); - return NULL; - } - - sleep(5); - } -} +#include "sunneed_proc.h" + +struct sunneed_tenant tenants[MAX_TENANTS]; + +int +sunneed_init_tenants(void) { + for (int i = 0; i < MAX_TENANTS; i++) { + tenants[i] = (struct sunneed_tenant){.id = i, .pid = 0, .power_proportion = 0.0, .is_active = false}; + } + + return 0; +} + +// Find an unused spot for a tenant and register ourself there. +struct sunneed_tenant * +sunneed_tenant_register(pid_t pid) { + struct sunneed_tenant *tenant = NULL; + for (int i = 0; i < MAX_TENANTS; i++) { + if (!tenants[i].is_active) { + tenant = &tenants[i]; + break; + } + } + + if (tenant == NULL) { + LOG_E("Sorry PID %d, can't spawn any more tenants!", pid); + return NULL; + } + + tenant->pid = pid; + tenant->is_active = true; + + return tenant; +} + +int +sunneed_tenant_unregister(struct sunneed_tenant *tenant) { + if (tenant == NULL || !tenant->is_active) { + LOG_W("Cannot deactivate an inactive tenant"); + return 1; + } + + tenant->is_active = false; + + return 0; +} + +unsigned int +sunneed_get_num_tenants(void) { + unsigned int num_tenants = 0; + + for (int i = 0; i < MAX_TENANTS; i++) { + if (tenants[i].is_active) + num_tenants++; + } + + return num_tenants; +} + +int +sunneed_update_tenant_cpu_usage(void) { + // TODO Verify that the pipe and PID match up. It is entirely possible for the tenant process to die and a new + // process with the same PID as the tenant's to start up. + FILE *file; + char filepath[FILENAME_MAX] = "/proc/stat"; + + file = fopen(filepath, "r"); + fscanf(file, "%*s %llu %llu %llu %llu", &cpu_usage.user, &cpu_usage.nice, &cpu_usage.sys, &cpu_usage.idle); + fclose(file); + + for (struct sunneed_tenant *tenant = tenants; tenant < tenants + MAX_TENANTS; tenant++) { + if (!tenant->is_active) + continue; + + snprintf(filepath, FILENAME_MAX, "/proc/%d/stat", tenant->pid); + if (access(filepath, F_OK) != 0) { + LOG_E("Unable to find procfs file for PID %d; most likely a tenant ended but forgot to tell us", + tenant->pid); + continue; + } + + file = fopen(filepath, "r"); + // Read CPU consumption from this tenant's PID. + fscanf(file, + "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u" // 13 things we don't care about + " %llu %llu" // usertime, systemtime + " %*d %*d %*d %*d %*d %*d %*u %*u", // 8 things we don't care about + &cpu_usage.tenants[tenant->id].user, &cpu_usage.tenants[tenant->id].sys); + fclose(file); + } + + return 0; +} + +int +sunneed_get_tenant_cpu_usage(sunneed_tenant_id_t tenant_id) { + if (!tenants[tenant_id].is_active) { + LOG_E("Attempt to get CPU usage of inactive tenant %d", tenant_id); + return -1; + } + + // TODO Shit + + return 0; +} + +sunneed_worker_thread_result_t +sunneed_proc_monitor(__attribute__((unused)) void *args) { + int ret; + while (true) { + LOG_D("Updating process CPU usage"); + if ((ret = sunneed_update_tenant_cpu_usage()) != 0) { + LOG_E("Error updating CPU usage; monitor thread stopping"); + return NULL; + } + + sleep(5); + } +} diff --git a/src/sunneed_proc.h b/src/sunneed_proc.h index 08a612c..c740b7c 100644 --- a/src/sunneed_proc.h +++ b/src/sunneed_proc.h @@ -1,52 +1,52 @@ -#ifndef _SUNNEED_PROC_H_ -#define _SUNNEED_PROC_H_ - -#include -#include - -#include "log.h" -#include "sunneed.h" - -#define MAX_TENANTS 2 - -typedef unsigned int sunneed_tenant_id_t; - -// TODO Separate tenants from processes. -struct sunneed_tenant { - sunneed_tenant_id_t id; - pid_t pid; - float power_proportion; - bool is_active; -}; - -struct tenant_cpu_usage { - unsigned long long user, sys; -}; - -struct { - unsigned long long user, nice, sys, idle; - struct tenant_cpu_usage tenants[MAX_TENANTS]; -} cpu_usage; - -int -sunneed_update_tenant_cpu_usage(void); - -int -sunneed_init_tenants(void); - -struct sunneed_tenant * -sunneed_tenant_register(pid_t pid); - -int -sunneed_tenant_unregister(struct sunneed_tenant *tenant); - -unsigned int -sunneed_get_num_tenants(void); - -int -sunneed_get_tenant_cpu_usage(sunneed_tenant_id_t tenant_id); - -sunneed_worker_thread_result_t -sunneed_proc_monitor(void *args); - -#endif +#ifndef _SUNNEED_PROC_H_ +#define _SUNNEED_PROC_H_ + +#include +#include + +#include "log.h" +#include "sunneed.h" + +#define MAX_TENANTS 2 + +typedef unsigned int sunneed_tenant_id_t; + +// TODO Separate tenants from processes. +struct sunneed_tenant { + sunneed_tenant_id_t id; + pid_t pid; + float power_proportion; + bool is_active; +}; + +struct tenant_cpu_usage { + unsigned long long user, sys; +}; + +struct { + unsigned long long user, nice, sys, idle; + struct tenant_cpu_usage tenants[MAX_TENANTS]; +} cpu_usage; + +int +sunneed_update_tenant_cpu_usage(void); + +int +sunneed_init_tenants(void); + +struct sunneed_tenant * +sunneed_tenant_register(pid_t pid); + +int +sunneed_tenant_unregister(struct sunneed_tenant *tenant); + +unsigned int +sunneed_get_num_tenants(void); + +int +sunneed_get_tenant_cpu_usage(sunneed_tenant_id_t tenant_id); + +sunneed_worker_thread_result_t +sunneed_proc_monitor(void *args); + +#endif diff --git a/src/sunneed_runtime_test_collection.h b/src/sunneed_runtime_test_collection.h index 405714e..bf95377 100644 --- a/src/sunneed_runtime_test_collection.h +++ b/src/sunneed_runtime_test_collection.h @@ -1,10 +1,10 @@ -// This is here so `sunneed_core` can access it. -extern char sunneed_runtime_test_error[]; - -/* - ********************************************************************* - * Place your collection of tests to run in the RUNTIME_TESTS macro. * - ********************************************************************* - */ -#include "sunneed_loader.h" -#define RUNTIME_TESTS { SUNNEED_RUNTIME_TESTS_LOADER } +// This is here so `sunneed_core` can access it. +extern char sunneed_runtime_test_error[]; + +/* + ********************************************************************* + * Place your collection of tests to run in the RUNTIME_TESTS macro. * + ********************************************************************* + */ +#include "sunneed_loader.h" +#define RUNTIME_TESTS { SUNNEED_RUNTIME_TESTS_LOADER } diff --git a/src/sunneed_test.c b/src/sunneed_test.c index c796ad3..eba20a1 100644 --- a/src/sunneed_test.c +++ b/src/sunneed_test.c @@ -1,18 +1,18 @@ -#include "sunneed_test.h" - -char sunneed_runtime_test_error[SUNNEED_RUNTIME_TEST_ERROR_DESC_BUFFER_LENGTH] = { '\0' }; - -int set_sunneed_error(int ret, const char *format, ...) { - if (sunneed_runtime_test_error[0] != '\0') { - fprintf(stderr, "The test failure message buffer has already been modified.\n" - "Something is messed up with the tests!\n"); - exit(1); - } - - va_list ap; - va_start(ap, format); - vsnprintf(sunneed_runtime_test_error, SUNNEED_RUNTIME_TEST_ERROR_DESC_BUFFER_LENGTH, format, ap); - va_end(ap); - - return ret; -} +#include "sunneed_test.h" + +char sunneed_runtime_test_error[SUNNEED_RUNTIME_TEST_ERROR_DESC_BUFFER_LENGTH] = { '\0' }; + +int set_sunneed_error(int ret, const char *format, ...) { + if (sunneed_runtime_test_error[0] != '\0') { + fprintf(stderr, "The test failure message buffer has already been modified.\n" + "Something is messed up with the tests!\n"); + exit(1); + } + + va_list ap; + va_start(ap, format); + vsnprintf(sunneed_runtime_test_error, SUNNEED_RUNTIME_TEST_ERROR_DESC_BUFFER_LENGTH, format, ap); + va_end(ap); + + return ret; +} diff --git a/src/sunneed_test.h b/src/sunneed_test.h index 22841f1..6bd22c9 100644 --- a/src/sunneed_test.h +++ b/src/sunneed_test.h @@ -1,23 +1,23 @@ -#ifndef _SUNNEED_TEST_H_ -#define _SUNNEED_TEST_H_ - -#include -#include -#include - -// Imagine using non-"power of 2" numbers lmao. -#define SUNNEED_RUNTIME_TEST_ERROR_DESC_BUFFER_LENGTH 1024 - -/** - * A convenience method for testing. - * Writes the given message to the error description buffer and returns the given value. - * - * Example usage: - * - * if (my_test_variable != 0) - * return set_sunneed_error(1, "test condition failed, variable has value %d", my_test_variable); - * - */ -int set_sunneed_error(int ret, const char *format, ...); - -#endif +#ifndef _SUNNEED_TEST_H_ +#define _SUNNEED_TEST_H_ + +#include +#include +#include + +// Imagine using non-"power of 2" numbers lmao. +#define SUNNEED_RUNTIME_TEST_ERROR_DESC_BUFFER_LENGTH 1024 + +/** + * A convenience method for testing. + * Writes the given message to the error description buffer and returns the given value. + * + * Example usage: + * + * if (my_test_variable != 0) + * return set_sunneed_error(1, "test condition failed, variable has value %d", my_test_variable); + * + */ +int set_sunneed_error(int ret, const char *format, ...); + +#endif diff --git a/src/util/overlay_tester.c b/src/util/overlay_tester.c index a30ccce..a5ec521 100644 --- a/src/util/overlay_tester.c +++ b/src/util/overlay_tester.c @@ -1,32 +1,32 @@ -// Example of a program that would run on a sunneed system. This program should be run with `LD_PRELOAD` overlaying -// our custom functions. This program doesn't reference sunneed in any way; the design is that communication -// should be automatic. - -#include -#include -#include -#include -#include - -// Just to get the CAMERA_PATH. -#include "../shared/sunneed_device_type.h" -#include "../shared/sunneed_testing.h" - -int -main(void) { - printf("Starting main overlay tester\n"); - - int fd = open("/tmp/test", O_CREAT | O_RDWR, 0666); - if (fd == -1) { - fprintf(stderr, "Failed to open file\n"); - return -1; - } - - write(fd, "foo", 3); - - close(fd); - - printf("Opened and closed file\n"); - - return 0; -} +// Example of a program that would run on a sunneed system. This program should be run with `LD_PRELOAD` overlaying +// our custom functions. This program doesn't reference sunneed in any way; the design is that communication +// should be automatic. + +#include +#include +#include +#include +#include + +// Just to get the CAMERA_PATH. +#include "../shared/sunneed_device_type.h" +#include "../shared/sunneed_testing.h" + +int +main(void) { + printf("Starting main overlay tester\n"); + + int fd = open("/tmp/test", O_CREAT | O_RDWR, 0666); + if (fd == -1) { + fprintf(stderr, "Failed to open file\n"); + return -1; + } + + write(fd, "foo", 3); + + close(fd); + + printf("Opened and closed file\n"); + + return 0; +} diff --git a/src/util/pi_send.c b/src/util/pi_send.c index f4f00b1..9000dc2 100644 --- a/src/util/pi_send.c +++ b/src/util/pi_send.c @@ -1,67 +1,102 @@ -#include -#include -#include -#include -#include -#include - -#define PORT 9999 - -/* - * Send packets at various frequencies, allowing the network card to idle for varying times - * starts at 500 ms and increases by 500 ms every iteration - * - * Uses UDP sockets as written, follow the notes in comments below to change to TCP sockets - */ - -int -main(void) -{ - int remote_fd, read_val; - struct sockaddr_in remote_addr; - char *msg; - int packet_sizes[8] = {1, 4, 8, 16, 32, 64, 128, 256}; - int index; - - //create UDP socket, change SOCK_DGRAM to SOCK_STREAM to create TCP sockets - - - //while(1); - if((remote_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) - { - perror("socket failed\n"); - exit(0); - } - - remote_addr.sin_family = AF_INET; - remote_addr.sin_port = htons(PORT); - - if(inet_pton(AF_INET, "192.168.1.214", &remote_addr.sin_addr) <= 0) - { - perror("invalid address/failed to convert\n"); - exit(0); - } - - if(connect(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) - { - perror("failed to connect\n"); - exit(0); - } - - - int i, j; - for (i = 0; i < 2000; i++) - { - index = rand() % 8; - msg = (char *) malloc(packet_sizes[index] * sizeof(char)); - for(j = 0; j < packet_sizes[index]-1; j++) - { - msg[j] = 'a'; - } - msg[packet_sizes[index] - 1] = '\0'; - send(remote_fd, msg, strlen(msg), 0); - - free(msg); - sleep(0.5); - } -} +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +/* + * Send packets at various frequencies, allowing the network card to idle for varying times + * starts at 500 ms and increases by 500 ms every iteration + * + * Uses UDP sockets as written, follow the notes in comments below to change to TCP sockets + */ + +int +main(void) +{ + int remote_fd, read_val; + struct sockaddr_in remote_addr; + char *packets[8]; + + char packet_size_1 = 'A'; + packets[0] = &packet_size_1; + + char packet_size_4[4]; + memset(packet_size_4, 'a', 4); + packets[1] = packet_size_4; + + char packet_size_8[8]; + memset(packet_size_8, 'B', 8); + packets[2] = packet_size_8; + + char packet_size_16[16]; + memset(packet_size_16, 'b', 16); + packets[3] = packet_size_16; + + char packet_size_32[32]; + memset(packet_size_32, 'C', 32); + packets[4] = packet_size_32; + + char packet_size_64[64]; + memset(packet_size_64, 'c', 64); + packets[5] = packet_size_64; + + char packet_size_128[128]; + memset(packet_size_128, 'D', 128); + packets[6] = packet_size_128; + + char packet_size_256[256]; + memset(packet_size_256, 'd', 256); + packets[7] = packet_size_256; + + int index; + int packet_sizes[8] = {1, 4, 8, 16, 32, 64, 128, 256}; + + + //create UDP socket, change SOCK_DGRAM to SOCK_STREAM to create TCP sockets + + + //while(1); + if((remote_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + perror("socket failed\n"); + exit(0); + } + + remote_addr.sin_family = AF_INET; + remote_addr.sin_port = htons(PORT); + + if(inet_pton(AF_INET, "192.168.1.214", &remote_addr.sin_addr) <= 0) + { + perror("invalid address/failed to convert\n"); + exit(0); + } + + if(connect(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + perror("failed to connect\n"); + exit(0); + } + + + int i, j; + for (i = 0; i < 2000; i++) + { + index = rand() % 8; + //msg = (char *) malloc(packet_sizes[index] * sizeof(char)); + /*for(j = 0; j < packet_sizes[index]-1; j++) + { + msg[j] = 'a'; + }*/ + //msg[packet_sizes[index] - 1] = '\0'; + printf("msg size: %d\n", packet_sizes[index]); + send(remote_fd, packets[index], packet_sizes[index], 0); + + //free(msg); + sleep(0.5); + } + +} diff --git a/test/.gitignore b/test/.gitignore index ae3b222..7269d7d 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,3 +1,3 @@ -*.h - -run-tests +*.h + +run-tests diff --git a/test/Makefile b/test/Makefile index 5f67ebe..a4208f0 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,19 +1,19 @@ -test_sources = $(wildcard *.c) munit/munit.c -suite_sources = $(filter-out test_main.c, $(wildcard *.c)) -suites = $(patsubst %.c,%,$(suite_sources)) - -glue_header = test.gen.h - -all: $(glue_header) - $(CC) $(CFLAGS) -o $(test_runner_name) $(test_sources) $(cflags_deps) - -$(glue_header): - ./scripts/generate-test-header $(suites) > $@ - -format: - $(SOURCE_FORMATTER) $(shell find . -not -path './munit/*' -type f -regex '.*\.c') - -clean: - rm -f $(glue_header) $(test_runner_name) - -.PHONY: all clean $(glue_header) +test_sources = $(wildcard *.c) munit/munit.c +suite_sources = $(filter-out test_main.c, $(wildcard *.c)) +suites = $(patsubst %.c,%,$(suite_sources)) + +glue_header = test.gen.h + +all: $(glue_header) + $(CC) $(CFLAGS) -o $(test_runner_name) $(test_sources) $(cflags_deps) + +$(glue_header): + ./scripts/generate-test-header $(suites) > $@ + +format: + $(SOURCE_FORMATTER) $(shell find . -not -path './munit/*' -type f -regex '.*\.c') + +clean: + rm -f $(glue_header) $(test_runner_name) + +.PHONY: all clean $(glue_header) diff --git a/test/nng.c b/test/nng.c index 9175cbc..0aeec28 100644 --- a/test/nng.c +++ b/test/nng.c @@ -1,43 +1,43 @@ -#include "../src/shared/sunneed_ipc.h" -#include "munit/munit.h" -#include - -#include - -static void -nng_fatal(const char *func, int rv) { - fprintf(stderr, "%s: %s\n", func, nng_strerror(rv)); -} - -MunitResult -test_nng_try_macro(const MunitParameter params[], void *data) { - const size_t msg_sz = 16; - - SUNNEED_NNG_SET_ERROR_REPORT_FUNC(nng_fatal); - - nng_msg *msg; - SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &msg, msg_sz); - munit_assert_size(nng_msg_len(msg), ==, msg_sz); - - nng_msg_free(msg); - - return MUNIT_OK; -} - -MunitResult -test_nng_try_set_macro(const MunitParameter params[], void *data) { - const size_t msg_sz = 16; - - SUNNEED_NNG_SET_ERROR_REPORT_FUNC(nng_fatal); - - nng_msg *msg; - int ret = 1; - SUNNEED_NNG_TRY_SET(nng_msg_alloc, ret, != 0, &msg, msg_sz); - munit_assert_size(nng_msg_len(msg), ==, msg_sz); - - munit_assert_int(ret, ==, 0); - - nng_msg_free(msg); - - return MUNIT_OK; -} +#include "../src/shared/sunneed_ipc.h" +#include "munit/munit.h" +#include + +#include + +static void +nng_fatal(const char *func, int rv) { + fprintf(stderr, "%s: %s\n", func, nng_strerror(rv)); +} + +MunitResult +test_nng_try_macro(const MunitParameter params[], void *data) { + const size_t msg_sz = 16; + + SUNNEED_NNG_SET_ERROR_REPORT_FUNC(nng_fatal); + + nng_msg *msg; + SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &msg, msg_sz); + munit_assert_size(nng_msg_len(msg), ==, msg_sz); + + nng_msg_free(msg); + + return MUNIT_OK; +} + +MunitResult +test_nng_try_set_macro(const MunitParameter params[], void *data) { + const size_t msg_sz = 16; + + SUNNEED_NNG_SET_ERROR_REPORT_FUNC(nng_fatal); + + nng_msg *msg; + int ret = 1; + SUNNEED_NNG_TRY_SET(nng_msg_alloc, ret, != 0, &msg, msg_sz); + munit_assert_size(nng_msg_len(msg), ==, msg_sz); + + munit_assert_int(ret, ==, 0); + + nng_msg_free(msg); + + return MUNIT_OK; +} diff --git a/test/scripts/generate-test-header b/test/scripts/generate-test-header index 3a5177a..f5a315a 100755 --- a/test/scripts/generate-test-header +++ b/test/scripts/generate-test-header @@ -1,78 +1,78 @@ -#!/usr/bin/env bash -# -# generate-test-header: Outputs the contents of a header file which presents the user with a macro SUITES_ARRAY, which -# will in turn generate a static MunitSuite array containing each provided test file as a suite. The output of this -# should be redirected to a file to be included in the main test runner file. Just call `SUITES_ARRAY(arrayname)` -# and then pass `arrayname` as the `suites` field (the third field) in your main MunitSuite. -# Arguments: suite filenames - -err() { - echo "$(tput setaf 1)Error: $@$(tput sgr0)" >&2 -} - -# Generate the MunitSuite for a given test suite. -# Arguments: suite filename -suite_from_file() { - if [[ ! -f "$1" ]]; then - err "invalid test file '$1'" - return 1 - fi - - local filename=$1 - local testname=${1%.c} - local header_file=${testname}.h - - functions=$(grep -x 'MunitResult' -A1 "$filename" | grep -xv -- '--' | grep -xv 'MunitResult' | sed -r 's/^([[:alnum:]_]+)\(.*$/\1/') - - echo "// Generated from: ${filename}" - - for f in ${functions[@]}; do - echo "MunitResult ${f}(const MunitParameter params[], void *data);" - done - - echo "static MunitTest ${testname}_tests[] = {" - for f in ${functions[@]}; do - echo "{ (char*) (\"/${f}\"), $f, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }," - done - echo "{ NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL } };" - - echo "static const MunitSuite ${testname}_suite = { \"/$testname\", ${testname}_tests, NULL, 1, MUNIT_SUITE_OPTION_NONE };" -} - -# Generate a macro that allows the user to create an array containing all of our MunitSuites. -# Arguments: suite names -suites_macro() { - if [[ $# -lt 1 ]]; then - err "suites required" - return 1 - fi - - local suites=$@ - - echo -n "#define SUITES_ARRAY(NAME) static MunitSuite NAME[] = {" - for suite in ${suites[@]}; do - echo -n "${suite}_suite," - done - echo "{ NULL, NULL, NULL, 1, MUNIT_SUITE_OPTION_NONE }}" -} - -main() { - if [[ $# -lt 1 ]]; then - err "input files required" - return 1 - fi - - local files=${@:1} - local suites=${files[@]/.c/} - - echo "// Generated by ${0##*/}. DO NOT EDIT THIS FILE." - echo '#include "munit/munit.h"' - - for suite in ${suites[@]}; do - suite_from_file "${suite}.c" || return $? - done - - suites_macro ${suites[@]} -} - -main "$@" || exit $? +#!/usr/bin/env bash +# +# generate-test-header: Outputs the contents of a header file which presents the user with a macro SUITES_ARRAY, which +# will in turn generate a static MunitSuite array containing each provided test file as a suite. The output of this +# should be redirected to a file to be included in the main test runner file. Just call `SUITES_ARRAY(arrayname)` +# and then pass `arrayname` as the `suites` field (the third field) in your main MunitSuite. +# Arguments: suite filenames + +err() { + echo "$(tput setaf 1)Error: $@$(tput sgr0)" >&2 +} + +# Generate the MunitSuite for a given test suite. +# Arguments: suite filename +suite_from_file() { + if [[ ! -f "$1" ]]; then + err "invalid test file '$1'" + return 1 + fi + + local filename=$1 + local testname=${1%.c} + local header_file=${testname}.h + + functions=$(grep -x 'MunitResult' -A1 "$filename" | grep -xv -- '--' | grep -xv 'MunitResult' | sed -r 's/^([[:alnum:]_]+)\(.*$/\1/') + + echo "// Generated from: ${filename}" + + for f in ${functions[@]}; do + echo "MunitResult ${f}(const MunitParameter params[], void *data);" + done + + echo "static MunitTest ${testname}_tests[] = {" + for f in ${functions[@]}; do + echo "{ (char*) (\"/${f}\"), $f, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }," + done + echo "{ NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL } };" + + echo "static const MunitSuite ${testname}_suite = { \"/$testname\", ${testname}_tests, NULL, 1, MUNIT_SUITE_OPTION_NONE };" +} + +# Generate a macro that allows the user to create an array containing all of our MunitSuites. +# Arguments: suite names +suites_macro() { + if [[ $# -lt 1 ]]; then + err "suites required" + return 1 + fi + + local suites=$@ + + echo -n "#define SUITES_ARRAY(NAME) static MunitSuite NAME[] = {" + for suite in ${suites[@]}; do + echo -n "${suite}_suite," + done + echo "{ NULL, NULL, NULL, 1, MUNIT_SUITE_OPTION_NONE }}" +} + +main() { + if [[ $# -lt 1 ]]; then + err "input files required" + return 1 + fi + + local files=${@:1} + local suites=${files[@]/.c/} + + echo "// Generated by ${0##*/}. DO NOT EDIT THIS FILE." + echo '#include "munit/munit.h"' + + for suite in ${suites[@]}; do + suite_from_file "${suite}.c" || return $? + done + + suites_macro ${suites[@]} +} + +main "$@" || exit $? diff --git a/test/test_main.c b/test/test_main.c index 8e3dab7..778e85e 100644 --- a/test/test_main.c +++ b/test/test_main.c @@ -1,14 +1,14 @@ -#include "munit/munit.h" -#include "test.gen.h" - -SUITES_ARRAY(sub_suites); - -static const MunitSuite main_suite = {(char *)"/sunneed", NULL, sub_suites, 1, MUNIT_SUITE_OPTION_NONE}; - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wincompatible-pointer-types" -int -main(int argc, const char *argv[]) { - return munit_suite_main(&main_suite, NULL, argc, argv); -} -#pragma GCC diagnostic pop +#include "munit/munit.h" +#include "test.gen.h" + +SUITES_ARRAY(sub_suites); + +static const MunitSuite main_suite = {(char *)"/sunneed", NULL, sub_suites, 1, MUNIT_SUITE_OPTION_NONE}; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wincompatible-pointer-types" +int +main(int argc, const char *argv[]) { + return munit_suite_main(&main_suite, NULL, argc, argv); +} +#pragma GCC diagnostic pop From 1e15427a15010649479a5d8cce53ca081829e077 Mon Sep 17 00:00:00 2001 From: Ryan Fisk Date: Tue, 13 Jul 2021 10:15:10 -0900 Subject: [PATCH 15/42] Add time delay and randomize size and delay for network tests --- src/client/sunneed_client.c | 47 ++++++++-------------- src/log.h | 17 ++++---- src/overlay/sunneed_overlay.c | 2 - src/sunneed.h | 3 +- src/sunneed_listener.c | 74 +++++++++++++++++------------------ src/sunneed_power.h | 6 +++ src/util/pi_send.c | 34 +++++++++------- 7 files changed, 91 insertions(+), 92 deletions(-) diff --git a/src/client/sunneed_client.c b/src/client/sunneed_client.c index 58ee4ce..61671d5 100644 --- a/src/client/sunneed_client.c +++ b/src/client/sunneed_client.c @@ -32,37 +32,25 @@ send_request(SunneedRequest *req) { if (!buf) FATAL(-1, "unable to allocate buffer for request"); sunneed_request__pack(req, buf); - printf("malloced %d bytes for buf\n", req_len); int ret = -1; - //int ret = nng_msg_alloc(&msg, req_len); - SUNNEED_NNG_TRY_SET(nng_msg_alloc, ret, != 0, &msg, req_len); - //ret = nng_msg_insert(msg, buf, req_len); - SUNNEED_NNG_TRY_SET(nng_msg_insert, ret, != 0, msg, buf, req_len); - printf("nng_msg_insert: ret = %d\n", ret); - //ret = nng_sendmsg(sunneed_socket, msg, 0); - SUNNEED_NNG_TRY_SET(nng_sendmsg, ret, != 0, sunneed_socket, msg, 0); - printf("nng_sendmsg: ret = %d\n", ret); - - if(ret > 0) nng_msg_free(msg); - + SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &msg, 0); + SUNNEED_NNG_TRY(nng_msg_append, != 0, msg, buf, req_len); + SUNNEED_NNG_TRY(nng_sendmsg, != 0, sunneed_socket, msg, 0); + + free(buf); - printf("end of send_request\n"); } static SunneedResponse * receive_response(SunneedResponse__MessageTypeCase message_type) { nng_msg *reply; - printf("nng_recvmsg from socket %d\n", sunneed_socket); SUNNEED_NNG_TRY(nng_recvmsg, != 0, sunneed_socket, &reply, 0); size_t msg_len = nng_msg_len(reply); - printf("response size: %d\n", msg_len); - SUNNEED_NNG_MSG_LEN_FIX(msg_len); - printf("unpacking response\n"); + //SUNNEED_NNG_MSG_LEN_FIX(msg_len); SunneedResponse *resp = sunneed_response__unpack(NULL, msg_len, nng_msg_body(reply)); if (resp->status != 0) { - printf("resp status > 0\n"); return NULL; } else if (resp->message_type_case != message_type) { FATAL(-1, "incorrect message type received (expected %d, got %d)", message_type, resp->status); @@ -253,13 +241,12 @@ sunneed_client_socket(int domain, int type, int protocol) sock.protocol = protocol; req.socket = &sock; - printf("sending request to sunneed\n"); send_request(&req); SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_SOCKET); if(resp == NULL) { - FATAL(-1, "failed to create socket"); + FATAL(-1, "failed to create socket, no response"); } int i; @@ -269,7 +256,6 @@ sunneed_client_socket(int domain, int type, int protocol) { dummy_sockets[i].dummy_sockfd = resp -> socket -> dummy_sockfd; sunneed_response__free_unpacked(resp, NULL); - printf("added dummy sockfd to table\n"); return dummy_sockets[i].dummy_sockfd; } } @@ -317,7 +303,6 @@ sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrle for(addr_pointer = requested_host->h_addr_list; *addr_pointer; addr_pointer++) { inet_ntop(AF_INET, (void *)*addr_pointer, address, sizeof(address)); - printf("client connect: got address %s\n", address); } @@ -332,16 +317,16 @@ sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrle req.connect = &conn; send_request(&req); - SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC); - if(resp == NULL) - { - FATAL(-1, "connect response was null\n"); - return -1; - } + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC); + if(resp == NULL) + { + FATAL(-1, "connect response was null\n"); + return -1; + } - sunneed_response__free_unpacked(resp, NULL); + sunneed_response__free_unpacked(resp, NULL); - return 1; + return 1; } ssize_t @@ -384,7 +369,7 @@ sunneed_client_remote_send(int sockfd, const void *data, size_t len, int flags) free(send_req.data.data); sunneed_response__free_unpacked(resp, NULL); - return 0; + return 0; } int diff --git a/src/log.h b/src/log.h index 8936481..6a16192 100644 --- a/src/log.h +++ b/src/log.h @@ -11,19 +11,22 @@ FILE *logfile, *stepper_pwr_logfile; + #define LOGL_DEBUG "D\e[38;5;240m" #define LOGL_INFO "I" #define LOGL_WARN "W\e[0;33m" #define LOGL_ERROR "E\e[0;31m" #ifndef LOG_PWR_EVENT -#define LOG_PWR_EVENT(LEVEL, MESSAGE, ...) \ - { \ - FILE *_logfile = stepper_pwr_logfile; \ - if (!logfile) { \ - return; \ - } \ - fprintf(_logfile, MESSAGE); \ +#define LOG_PWR_EVENT(LEVEL, MESSAGE, ...) \ + { \ + FILE *_logfile = fopen("sunneed_network_pwr_log.txt", "w+"); \ + if (!_logfile) { \ + return; \ + } \ + \ + fprintf(_logfile, MESSAGE); \ + fflush(_logfile); \ } #endif #define LOG(LEVEL, MESSAGE, ...) \ diff --git a/src/overlay/sunneed_overlay.c b/src/overlay/sunneed_overlay.c index e4bb5f2..f2933dd 100644 --- a/src/overlay/sunneed_overlay.c +++ b/src/overlay/sunneed_overlay.c @@ -74,9 +74,7 @@ socket(int domain, int type, int protocol) { if((type == SOCK_STREAM) || (type == SOCK_DGRAM)) { - printf("calling sunneed_client_socket\n"); sockfd = sunneed_client_socket(domain, type, protocol); - printf("got back sockfd %d\n", sockfd); return sockfd; } diff --git a/src/sunneed.h b/src/sunneed.h index 7238456..58d26d6 100644 --- a/src/sunneed.h +++ b/src/sunneed.h @@ -2,13 +2,14 @@ #define _SUNNEED_H_ #include +#include #define APP_NAME "sunneed" #ifdef LOG_PWR #define REQS_PER_LOG 10 int last_capacity, curr_capacity, reqs_since_last_log; - double last_send, time_since_send; + clock_t last_send, time_since_send; #endif typedef void* sunneed_worker_thread_result_t; diff --git a/src/sunneed_listener.c b/src/sunneed_listener.c index 83af81c..d2d98c8 100644 --- a/src/sunneed_listener.c +++ b/src/sunneed_listener.c @@ -315,7 +315,6 @@ serve_connect(SunneedResponse *resp, void* sub_resp_buf, nng_pipe pipe, ConnectR { if(dummy_socket_map[i].id == request->sockfd) { - LOG_D("found socket for pipe %d\n", pipe.id); sockfd = dummy_socket_map[i].sockfd; domain = dummy_socket_map[i].domain; break; @@ -345,10 +344,10 @@ serve_connect(SunneedResponse *resp, void* sub_resp_buf, nng_pipe pipe, ConnectR LOG_D("connected to remote host: %s\n", request->address); - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; - GenericResponse *sub_resp = sub_resp_buf; - *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; - resp->generic = sub_resp; + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; + GenericResponse *sub_resp = sub_resp_buf; + *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; + resp->generic = sub_resp; return 0; @@ -361,7 +360,6 @@ serve_send(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant *ten //TODO: formulate response, for now just log and call send LOG_D("Got request from %d to send %ld bytes", tenant->id, request->data.len); - //LOG_D("Msg to send %s\n", request->data.data); //TODO: probably want more checks here as well int sockfd = lookup_socket(request->sockfd); @@ -376,43 +374,51 @@ serve_send(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant *ten return 1; } - #ifdef LOG_PWR - if(last_send == 0) - { - last_send = clock(); - LOG_P ("%f ", (double) last_send / CLOCKS_PER_SEC); - }else{ - time_since_send = (double)((clock() - last_send)/CLOCKS_PER_SEC); - LOG_P("%f ", time_since_send); - } +#ifdef LOG_PWR + if(last_send == 0) + { + last_send = clock(); + printf("last send = %ld\n", last_send); + LOG_P ("%f ", (((double)(last_send))/CLOCKS_PER_SEC)); + LOG_D ("first send %f ", (((double) (last_send))/CLOCKS_PER_SEC)); + }else{ + printf("clock() - last_send = %ld\n", clock()-last_send); + time_since_send = (double)(clock() - last_send) / (double)CLOCKS_PER_SEC; + LOG_P("%f ", time_since_send); + LOG_D("%f since last send", time_since_send); + } - LOG_P("%d ", request->data.len); + LOG_P("%d ", request->data.len); + LOG_D("msg size %d\n", request->data.len); - #endif +#endif if((send(sockfd, request->data.data, request->data.len, request->flags)) < 0) { LOG_E("Failed to send data for tenant %d error %d\n", tenant->id, errno); + return 1; }else{ LOG_D("Sent data from tenant %d\n", tenant->id); } - #ifdef LOG_PWR - curr_capacity = present_power(); +#ifdef LOG_PWR + curr_capacity = present_power(); - double change = ((double) last_capacity - curr_capacity) - (time_since_send * PASSIVE_PWR_PER_SEC); - LOG_P("%f\n", change); + double change = ((double) last_capacity - curr_capacity) - (time_since_send * PASSIVE_PWR_PER_SEC); + LOG_P("%f\n", change); + LOG_D("%f\n", change); - last_capacity = curr_capacity; - last_send = clock(); + last_capacity = curr_capacity; + last_send = clock(); - #endif +#endif + + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; + GenericResponse *sub_resp = sub_resp_buf; + *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; + resp->generic = sub_resp; - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; - GenericResponse *sub_resp = sub_resp_buf; - *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; - resp->generic = sub_resp; return 0; } @@ -479,7 +485,7 @@ sunneed_listen(void) { // Get contents of message. size_t msg_len = nng_msg_len(msg); - SUNNEED_NNG_MSG_LEN_FIX(msg_len); + //SUNNEED_NNG_MSG_LEN_FIX(msg_len); SunneedRequest *request = sunneed_request__unpack(NULL, msg_len, nng_msg_body(msg)); if (request == NULL) { @@ -553,17 +559,15 @@ sunneed_listen(void) { } resp.status = ret; - // Create and send the response message. nng_msg *resp_msg; - int ret2 = -2; int resp_len = sunneed_response__get_packed_size(&resp); void *resp_buf = malloc(resp_len); sunneed_response__pack(&resp, resp_buf); - SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &resp_msg, resp_len); + SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &resp_msg, 0); SUNNEED_NNG_TRY(nng_msg_insert, != 0, resp_msg, resp_buf, resp_len); - SUNNEED_NNG_TRY_SET(nng_sendmsg, ret2, != 0, sock, resp_msg, 0); + SUNNEED_NNG_TRY(nng_sendmsg, != 0, sock, resp_msg, 0); if(resp.message_type_case == SUNNEED_RESPONSE__MESSAGE_TYPE_REGISTER_CLIENT) { @@ -572,15 +576,11 @@ sunneed_listen(void) { } //nng_msg_free(resp_msg); - memset(resp_buf, '\0', resp_len); - memset(sub_resp_buf, '\0', SUB_RESPONSE_BUF_SZ); end: if(request != NULL) sunneed_request__free_unpacked(request, NULL); free(resp_buf); - //in theory nng_sendmsg frees this for us, but it may be casuing the memory bug - if(ret2) nng_msg_free(resp_msg); } free(sub_resp_buf); diff --git a/src/sunneed_power.h b/src/sunneed_power.h index 1cc5730..2f75b0c 100644 --- a/src/sunneed_power.h +++ b/src/sunneed_power.h @@ -17,6 +17,12 @@ #define PASSIVE_PWR_PER_SEC 0.07667 +#define PASSIVE_PWR_PER_MIN PASSIVE_PWR_PER_SEC * 60 + +#ifdef LOG_PWR +#define REQUESTS_PER_PWR_LOG 20 +#endif + // TODO This is waaaaaaaaaaaaaaaaaaaaay too big. #define QUANTUM_DURATION_MS 5000 diff --git a/src/util/pi_send.c b/src/util/pi_send.c index 9000dc2..9ebde1c 100644 --- a/src/util/pi_send.c +++ b/src/util/pi_send.c @@ -4,6 +4,7 @@ #include #include #include +#include #define PORT 9999 @@ -52,8 +53,9 @@ main(void) memset(packet_size_256, 'd', 256); packets[7] = packet_size_256; - int index; + int size_index, time_index; int packet_sizes[8] = {1, 4, 8, 16, 32, 64, 128, 256}; + double delay_times[14] = {0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 2.5, 5.0, 7.5, 10.0, 12.5, 15.0, 17.5, 20.0}; //create UDP socket, change SOCK_DGRAM to SOCK_STREAM to create TCP sockets @@ -82,21 +84,25 @@ main(void) } - int i, j; - for (i = 0; i < 2000; i++) + int i; + clock_t delay_start; + double curr_delay; + for (i = 0; i < 200; i++) { - index = rand() % 8; - //msg = (char *) malloc(packet_sizes[index] * sizeof(char)); - /*for(j = 0; j < packet_sizes[index]-1; j++) + size_index = rand() % 8; + time_index = rand() % 14; + + printf("msg size: %d\n", packet_sizes[size_index]); + + send(remote_fd, packets[size_index], packet_sizes[size_index], 0); + + delay_start = clock(); + curr_delay = 0; + while(curr_delay < delay_times[time_index]) { - msg[j] = 'a'; - }*/ - //msg[packet_sizes[index] - 1] = '\0'; - printf("msg size: %d\n", packet_sizes[index]); - send(remote_fd, packets[index], packet_sizes[index], 0); - - //free(msg); - sleep(0.5); + curr_delay = (double)(clock() - delay_start) / CLOCKS_PER_SEC; + } + } } From cb3d2278aa9a6248b7e5ed91149e8a3e936c445a Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Thu, 15 Jul 2021 14:14:35 -0400 Subject: [PATCH 16/42] Fix connect overlay to remove hardcoded port I originally hardcoded the port so we could get data logged, but since we're delayed on that I took the chance to remove the hardcoding --- src/client/sunneed_client.c | 7 +++++-- src/sunneed_listener.c | 23 +++++++++++++---------- src/sunneed_power.c | 9 +++++++++ 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/client/sunneed_client.c b/src/client/sunneed_client.c index 61671d5..895369e 100644 --- a/src/client/sunneed_client.c +++ b/src/client/sunneed_client.c @@ -285,8 +285,10 @@ sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrle { char host_name[NI_MAXHOST]; char address[INET_ADDRSTRLEN]; + char addr2[INET_ADDRSTRLEN]; int port = 0; struct hostent *requested_host; + struct sockaddr_in *addr_info; char **addr_pointer; //get host name from sockaddr struct @@ -305,6 +307,8 @@ sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrle inet_ntop(AF_INET, (void *)*addr_pointer, address, sizeof(address)); } + addr_info = (struct sockaddr_in*) addr; + port = htons(addr_info->sin_port); SunneedRequest req = SUNNEED_REQUEST__INIT; req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_CONNECT; @@ -332,9 +336,8 @@ sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrle ssize_t sunneed_client_remote_send(int sockfd, const void *data, size_t len, int flags) { - //TODO: check sockfd for real socket, check data, flags, etc - //for now, just tell sunneed to perform a send for us + //TODO: handle flags if(!sunneed_client_is_dummysocket(sockfd)) { perror("called sunneed send with a non-sunneed socket\n"); diff --git a/src/sunneed_listener.c b/src/sunneed_listener.c index d2d98c8..bc49ffb 100644 --- a/src/sunneed_listener.c +++ b/src/sunneed_listener.c @@ -301,13 +301,10 @@ serve_socket(SunneedResponse *resp, void *sub_response_buf, SocketRequest *reque } static int -serve_connect(SunneedResponse *resp, void* sub_resp_buf, nng_pipe pipe, ConnectRequest *request) +serve_connect(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant *tenant, ConnectRequest *request) { //lookup real sockfd in dummy_socket_map and create new socket LOG_D("got connect request\n"); - - //TODO: figure out getting the port from the tenant, hardcoded to get data from network - int i, sockfd, domain; struct sockaddr_in remote_addr; sockfd = 0; @@ -320,14 +317,21 @@ serve_connect(SunneedResponse *resp, void* sub_resp_buf, nng_pipe pipe, ConnectR break; } } + //check socket and domain if(!(sockfd)) { - LOG_E("failed to find socket for pipe %d\n", pipe.id); + LOG_E("failed to find socket for tenant %d\n", tenant->id); return 1; } + if(!((domain == AF_INET) || (domain == AF_INET6))) + { + LOG_E("tenant passed invalid domain %d\n"); + return 1; + } + remote_addr.sin_family = domain; - remote_addr.sin_port = htons(9999); + remote_addr.sin_port = htons(request->port); //TODO: check address/port if(inet_pton(domain, request->address, &remote_addr.sin_addr) <= 0) @@ -342,7 +346,7 @@ serve_connect(SunneedResponse *resp, void* sub_resp_buf, nng_pipe pipe, ConnectR return 1; } - LOG_D("connected to remote host: %s\n", request->address); + LOG_D("connected to remote host %s on port %d\n", request->address, request->port); resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; GenericResponse *sub_resp = sub_resp_buf; @@ -357,8 +361,7 @@ serve_connect(SunneedResponse *resp, void* sub_resp_buf, nng_pipe pipe, ConnectR static int serve_send(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant *tenant, SendRequest *request) { - //TODO: formulate response, for now just log and call send - + LOG_D("Got request from %d to send %ld bytes", tenant->id, request->data.len); //TODO: probably want more checks here as well @@ -544,7 +547,7 @@ sunneed_listen(void) { ret = serve_socket(&resp, sub_resp_buf, request->socket); break; case SUNNEED_REQUEST__MESSAGE_TYPE_CONNECT: - ret = serve_connect(&resp, sub_resp_buf, pipe, request->connect); + ret = serve_connect(&resp, sub_resp_buf, tenant, request->connect); break; case SUNNEED_REQUEST__MESSAGE_TYPE_SEND: ret = serve_send(&resp, sub_resp_buf, tenant, request->send); diff --git a/src/sunneed_power.c b/src/sunneed_power.c index b0d32e1..d2665bb 100644 --- a/src/sunneed_power.c +++ b/src/sunneed_power.c @@ -139,7 +139,16 @@ sunneed_quantum_worker(__attribute__((unused)) void *args) { goto end; } +//cant have sleeps when trying to log power usage - will mess with clock() function +#ifndef LOG_PWR usleep(QUANTUM_DURATION_MS * 1000); +#endif + +#ifdef LOG_PWR + clock_t delay_start = clock(); + + while(((double)(clock() - delay_start) / CLOCKS_PER_SEC) < QUANTUM_DURATION_MS); +#endif if ((ret = sunneed_quantum_end()) != 0) { goto end; From b2d8adf1d733d5b832b6a7b2b4149ab03a5cdc91 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 12:55:51 -0400 Subject: [PATCH 17/42] Fix battery babysitter library types and improve socket lookup The voltage and current measurement functions in the bq27441 library return signed ints, however the library functions returned unsigned ints. Removed the lookup function for the dummy socket map since the dummy sockfd given back is just the index into that table, so we can use it for O(1) lookup --- src/client/sunneed_client.c | 118 +++++++++++++++++++++------------- src/client/sunneed_client.h | 3 +- src/overlay/sunneed_overlay.c | 57 +++++++--------- src/pip/bq27441.c | 2 +- src/sunneed_listener.c | 108 ++++++++++++++++--------------- src/util/pi_send.c | 2 +- 6 files changed, 156 insertions(+), 134 deletions(-) diff --git a/src/client/sunneed_client.c b/src/client/sunneed_client.c index 895369e..651f0e3 100644 --- a/src/client/sunneed_client.c +++ b/src/client/sunneed_client.c @@ -14,6 +14,7 @@ struct { } dummy_sockets[MAX_TENANT_SOCKETS] = { { -1 } }; nng_socket sunneed_socket; +nng_dialer sunneed_socket_dialer; nng_msg *msg; static void nngfatal(const char *func, int rv) { @@ -62,16 +63,12 @@ receive_response(SunneedResponse__MessageTypeCase message_type) { int sunneed_client_init(const char *name) { + SUNNEED_NNG_SET_ERROR_REPORT_FUNC(nngfatal); SUNNEED_NNG_TRY(nng_req0_open, != 0, &sunneed_socket); - SUNNEED_NNG_TRY(nng_dial, != 0, sunneed_socket, SUNNEED_LISTENER_URL, NULL, 0); + SUNNEED_NNG_TRY(nng_dial, != 0, sunneed_socket, SUNNEED_LISTENER_URL, &sunneed_socket_dialer, 0); + - - if(!(client_init)) - { - client_init = 1; - } - // Register this client with sunneed. SunneedRequest req = SUNNEED_REQUEST__INIT; req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT; RegisterClientRequest register_req = REGISTER_CLIENT_REQUEST__INIT; @@ -218,16 +215,30 @@ int sunneed_client_socket(int domain, int type, int protocol) { - if(!((domain == AF_INET) || (domain == AF_INET6))) + /* + * sunneed_socket_dialer points to the nng_dialer associated with the tenant <--> suneeed nng socket + * since the dialer is the last part of the socket set up, this should indicate whether the nng socket is set up or not + * if not, nng_dialer_id will return -1 to signal an invalid dialer, therefore we need to call SUPER to create the nng socket + */ + if(nng_dialer_id(sunneed_socket_dialer) == -1) + { + return -1; + } + + /* + * currently only support IPv4 UDP packets for power logging + * can extend for TCP and IPv6 later if needed + */ + if(!(domain == AF_INET)) { - perror("invalid address family, must be ipv4 or ipv6\n"); - exit(0); + FATAL(-1, "must be IPv4 socket\n"); } - if(!((type == SOCK_STREAM) || (type == SOCK_DGRAM))) + if(!(type == SOCK_DGRAM)) { - perror("invalid type, must be SOCK_STREAM(tcp) or SOCK_DGRAM(udp)\n"); - exit(0); + //TODO: determine type of gethostbyname and getaddr info packets, both end up triggering this return case + //should error out if not one of those, but for now just return -1 to signal a call to SUPER + return -1; } SunneedRequest req = SUNNEED_REQUEST__INIT; @@ -237,7 +248,7 @@ sunneed_client_socket(int domain, int type, int protocol) sock.domain = domain; sock.type = type; - //TODO: check protocol + sock.protocol = protocol; req.socket = &sock; @@ -249,6 +260,7 @@ sunneed_client_socket(int domain, int type, int protocol) FATAL(-1, "failed to create socket, no response"); } + //add the new fake socket from sunneed to the table, future sends to this fd will be caught by the send overlay int i; for(i = 0; i < MAX_TENANT_SOCKETS; i++) { @@ -266,6 +278,10 @@ sunneed_client_socket(int domain, int type, int protocol) } +/* + * dummy socket table lookup function for convenience + * TODO: could probably speed this up a little bit (currently O(n)) + */ int sunneed_client_is_dummysocket(int sockfd) { @@ -283,29 +299,52 @@ sunneed_client_is_dummysocket(int sockfd) int sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { + char host_name[NI_MAXHOST]; char address[INET_ADDRSTRLEN]; - char addr2[INET_ADDRSTRLEN]; int port = 0; struct hostent *requested_host; struct sockaddr_in *addr_info; - char **addr_pointer; + struct addrinfo hints; + struct addrinfo *result, *rp; + void *addr_ptr; + + //need to check that nng is set up via dialer here as well + if(nng_dialer_id(sunneed_socket_dialer) == -1) + { + return -1; + } + + + /* + * get remote address and port from given sockaddr struct + * uses AF_INET since we assume IPv4 packets, change to AF_UNSPEC when IPv6 support is added + */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; + hints.ai_protocol = 0; + hints.ai_addr = NULL; + hints.ai_next = NULL; - //get host name from sockaddr struct getnameinfo(addr, addrlen, host_name, NI_MAXHOST, NULL, 0, 0); + if(getaddrinfo(host_name, NULL, &hints, &result)) + { + FATAL(-1, "getaddrinfo\n"); + } - requested_host = gethostbyname(host_name); - if(requested_host == NULL) - { - fprintf(stderr, "client connect: failed to get host by name errno %d\n", h_errno); - return -1; - } + //getaddrinfo() returns a list of socket structs, since we only have one socket per address this will give us the address the tenant requested + while(result) + { + inet_ntop(result->ai_family, result->ai_addr->sa_data, address, NI_MAXHOST); + addr_ptr = &((struct sockaddr_in*)result->ai_addr)->sin_addr; - //loop through addr list to find a valid address for the host - for(addr_pointer = requested_host->h_addr_list; *addr_pointer; addr_pointer++) - { - inet_ntop(AF_INET, (void *)*addr_pointer, address, sizeof(address)); - } + inet_ntop(result->ai_family, addr_ptr, address, NI_MAXHOST); + + + result = result->ai_next; + } addr_info = (struct sockaddr_in*) addr; port = htons(addr_info->sin_port); @@ -321,28 +360,22 @@ sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrle req.connect = &conn; send_request(&req); - SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC); - if(resp == NULL) - { - FATAL(-1, "connect response was null\n"); - return -1; - } + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC); + if(resp == NULL) + { + FATAL(-1, "connect response was null\n"); + return -1; + } - sunneed_response__free_unpacked(resp, NULL); + sunneed_response__free_unpacked(resp, NULL); - return 1; + return 1; } ssize_t sunneed_client_remote_send(int sockfd, const void *data, size_t len, int flags) { - //TODO: handle flags - if(!sunneed_client_is_dummysocket(sockfd)) - { - perror("called sunneed send with a non-sunneed socket\n"); - exit(0); - } SunneedRequest req = SUNNEED_REQUEST__INIT; req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_SEND; @@ -362,7 +395,6 @@ sunneed_client_remote_send(int sockfd, const void *data, size_t len, int flags) req.send = &send_req; send_request(&req); - SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC); if(resp == NULL) { diff --git a/src/client/sunneed_client.h b/src/client/sunneed_client.h index 0ccb9cc..3444c93 100644 --- a/src/client/sunneed_client.h +++ b/src/client/sunneed_client.h @@ -24,7 +24,8 @@ #define client_printf(FMT, ...) \ printf("\e[38;5;240mclient:\e[0m " FMT, ##__VA_ARGS__) -int client_init = 0; + + typedef unsigned int sunneed_device_handle_t; int diff --git a/src/overlay/sunneed_overlay.c b/src/overlay/sunneed_overlay.c index f2933dd..9691ad6 100644 --- a/src/overlay/sunneed_overlay.c +++ b/src/overlay/sunneed_overlay.c @@ -2,9 +2,10 @@ void on_load() { - sunneed_client_init("TODO"); + sunneed_client_init("TODO"); printf("Overlay: Client init\n"); + } void @@ -58,55 +59,41 @@ write(int fd, const void *buf, size_t count) { int socket(int domain, int type, int protocol) { + int ret; - int sockfd; - - if(!(client_init)) + ret = sunneed_client_socket(domain, type, protocol); + + if(ret == -1) { - int ret; + //nng socket is not open (caught in sunneed_client_socket), call SUPER SUPER(ret, socket, int, (domain, type, protocol), int, int, int); return ret; } - - - if((domain == AF_INET) || (domain == AF_INET6)) - { - if((type == SOCK_STREAM) || (type == SOCK_DGRAM)) - { - sockfd = sunneed_client_socket(domain, type, protocol); - return sockfd; - - } - } - - int ret2; - SUPER(ret2, socket, int, (domain, type, protocol), int, int, int); - return ret2; + return ret; } int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { - if(!(client_init)) - { - int fd; - SUPER(fd, connect, int, (sockfd, addr, addrlen), int, const struct sockaddr *, socklen_t); - return fd; - } + printf("overlay connect\n"); + + int ret; if(sunneed_client_is_dummysocket(sockfd)) - { - //struct sockaddr_in *in_addr = (struct sockaddr_in *)addr; - //inet_pton(AF_INET, in_addr->sin_addr, addr_string); - //printf("overlay connect: destination ip = %s\n",addr_string); + { + ret = sunneed_client_connect(sockfd, addr, addrlen); + if(ret == -1) + { + //NNG dialer wasn't set up or the socket type was wrong + SUPER(ret, connect, int, (sockfd, addr, addrlen), int, const struct sockaddr *, socklen_t); + return ret; + } - //getnameinfo(addr, addrlen, addr_string, strlen(addr_string), NULL, 0, 0); - //printf("overlay connect: addr: %s\n", addr_string); - return sunneed_client_connect(sockfd, addr, addrlen); + return ret; }else if(sockfd){ - int ret; + //non-sunneed socket (maybe dont need this? Need to think of a case where we'd want to allow a connection to a non sunneed socket) SUPER(ret, connect, int, (sockfd, addr, addrlen), int, const struct sockaddr *, socklen_t); return ret; }else{ @@ -121,7 +108,7 @@ send(int sockfd, const void *buf, size_t len, int flags) { if(sunneed_client_is_dummysocket(sockfd)) { - sunneed_client_remote_send(sockfd, buf, len, flags); + return sunneed_client_remote_send(sockfd, buf, len, flags); }else if(sockfd){ int ret; SUPER(ret, send, int, (sockfd, buf, len, flags), int, const void *, size_t, int); diff --git a/src/pip/bq27441.c b/src/pip/bq27441.c index 133a75b..e98a9ae 100644 --- a/src/pip/bq27441.c +++ b/src/pip/bq27441.c @@ -8,5 +8,5 @@ pip_info() { unsigned int present_power() { return 0; - // return bq27441_nominal_avail_cap(); + //return bq27441_nominal_avail_cap(); } diff --git a/src/sunneed_listener.c b/src/sunneed_listener.c index bc49ffb..10f201f 100644 --- a/src/sunneed_listener.c +++ b/src/sunneed_listener.c @@ -50,16 +50,6 @@ tenant_of_pipe(int pipe_id) { return NULL; } -int -lookup_socket(int sockfd) -{ - int i; - for(i = 0; i < MAX_TENANT_SOCKETS; i++) - if(dummy_socket_map[i].id == sockfd) - return dummy_socket_map[i].sockfd; - return 0; - -} // Get the PID of a pipe and use that to create a new sunneed tenant with that ID. // TODO: This shouldn't always create a new tenant, since we want multiple processes @@ -271,9 +261,13 @@ serve_socket(SunneedResponse *resp, void *sub_response_buf, SocketRequest *reque *sock_resp = (SocketResponse)SOCKET_RESPONSE__INIT; resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_SOCKET; resp->socket = sock_resp; - //TODO: checks on the request? - //find open spot in socket table + /* + * add domain and real socket file descriptor to dummy socket map + * need to store domain for use in connect + * + * the tenant will receive the index into the table of the socket fd and their dummy sockfd + */ int i, new_id, sockfd; for(i = 0; i < MAX_TENANT_SOCKETS; i++) { @@ -285,8 +279,10 @@ serve_socket(SunneedResponse *resp, void *sub_response_buf, SocketRequest *reque { dummy_socket_map[i].id = new_id; dummy_socket_map[i].sockfd = sockfd; + + //need to store the domain (AF_INET) to use in connect dummy_socket_map[i].domain = request->domain; - LOG_D("Socket created successfully\n"); + LOG_D("Socket %d created successfully\n", new_id); sock_resp->dummy_sockfd = new_id; return 0; }else{ @@ -303,37 +299,39 @@ serve_socket(SunneedResponse *resp, void *sub_response_buf, SocketRequest *reque static int serve_connect(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant *tenant, ConnectRequest *request) { - //lookup real sockfd in dummy_socket_map and create new socket LOG_D("got connect request\n"); int i, sockfd, domain; struct sockaddr_in remote_addr; - sockfd = 0; - for(i = 0; i < MAX_TENANT_SOCKETS; i++) - { - if(dummy_socket_map[i].id == request->sockfd) - { - sockfd = dummy_socket_map[i].sockfd; - domain = dummy_socket_map[i].domain; - break; - } - } - //check socket and domain - if(!(sockfd)) + sockfd = domain = 0; + + /* + * the fake sockfd passed by the request is the index into the dummy socket map of the tenants socket + * can take advantage of this for O(1) retrieval of the real socket fd and domain + */ + sockfd = dummy_socket_map[request->sockfd].sockfd; + domain = dummy_socket_map[request->sockfd].domain; + + //check sockfd and domain + if(sockfd < 0) { LOG_E("failed to find socket for tenant %d\n", tenant->id); return 1; } - if(!((domain == AF_INET) || (domain == AF_INET6))) + /* + * should have errored out in socket if the tenant passed an IPv6 domain + * but worth the check here in case the dummy socket table got messed with + */ + if(!(domain == AF_INET)) { - LOG_E("tenant passed invalid domain %d\n"); + LOG_E("tenant %d tried to create a non-IPv4 socket\n", tenant->id); return 1; } remote_addr.sin_family = domain; remote_addr.sin_port = htons(request->port); - //TODO: check address/port + if(inet_pton(domain, request->address, &remote_addr.sin_addr) <= 0) { LOG_E("invalid address/domain or failed to convert\n"); @@ -343,15 +341,16 @@ serve_connect(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant * if(connect(sockfd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) { LOG_E("Failed to connect to %s\n", request->address); - return 1; + return 1; } + //connection successful, send generic response to the tenant LOG_D("connected to remote host %s on port %d\n", request->address, request->port); - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; - GenericResponse *sub_resp = sub_resp_buf; - *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; - resp->generic = sub_resp; + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; + GenericResponse *sub_resp = sub_resp_buf; + *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; + resp->generic = sub_resp; return 0; @@ -363,10 +362,11 @@ serve_send(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant *ten { LOG_D("Got request from %d to send %ld bytes", tenant->id, request->data.len); - //TODO: probably want more checks here as well - int sockfd = lookup_socket(request->sockfd); - if(!(sockfd)) + //same lookup as serve_connect, use the requested sockfd as the index for O(1) lookup + int sockfd = dummy_socket_map[request->sockfd].sockfd; + + if(sockfd < 0) { LOG_E("Bad socket descriptor: %d\n", sockfd); return 1; @@ -378,20 +378,21 @@ serve_send(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant *ten } #ifdef LOG_PWR - if(last_send == 0) - { - last_send = clock(); + // log time since last sent packet and size of the current packet + if(last_send == 0) + { + last_send = clock(); printf("last send = %ld\n", last_send); - LOG_P ("%f ", (((double)(last_send))/CLOCKS_PER_SEC)); + LOG_P ("%f ", (((double)(last_send))/CLOCKS_PER_SEC)); LOG_D ("first send %f ", (((double) (last_send))/CLOCKS_PER_SEC)); - }else{ + }else{ printf("clock() - last_send = %ld\n", clock()-last_send); - time_since_send = (double)(clock() - last_send) / (double)CLOCKS_PER_SEC; - LOG_P("%f ", time_since_send); + time_since_send = (double)(clock() - last_send) / (double)CLOCKS_PER_SEC; + LOG_P("%f ", time_since_send); LOG_D("%f since last send", time_since_send); - } + } - LOG_P("%d ", request->data.len); + LOG_P("%d ", request->data.len); LOG_D("msg size %d\n", request->data.len); #endif @@ -406,21 +407,22 @@ serve_send(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant *ten } #ifdef LOG_PWR + //log power change from send and reset last_capacity and last_send curr_capacity = present_power(); - double change = ((double) last_capacity - curr_capacity) - (time_since_send * PASSIVE_PWR_PER_SEC); - LOG_P("%f\n", change); + double change = ((double) last_capacity - curr_capacity) - (time_since_send * PASSIVE_PWR_PER_SEC); + LOG_P("%f\n", change); LOG_D("%f\n", change); last_capacity = curr_capacity; - last_send = clock(); + last_send = clock(); #endif - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; - GenericResponse *sub_resp = sub_resp_buf; - *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; - resp->generic = sub_resp; + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; + GenericResponse *sub_resp = sub_resp_buf; + *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; + resp->generic = sub_resp; return 0; diff --git a/src/util/pi_send.c b/src/util/pi_send.c index 9ebde1c..61f1579 100644 --- a/src/util/pi_send.c +++ b/src/util/pi_send.c @@ -71,7 +71,7 @@ main(void) remote_addr.sin_family = AF_INET; remote_addr.sin_port = htons(PORT); - if(inet_pton(AF_INET, "192.168.1.214", &remote_addr.sin_addr) <= 0) + if(inet_pton(AF_INET, "127.0.0.1", &remote_addr.sin_addr) <= 0) { perror("invalid address/failed to convert\n"); exit(0); From 6ac792755399a43c791bb6398d17fbfbd44c1f35 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 16:04:03 -0400 Subject: [PATCH 18/42] add install_dependencies script to pipeline --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 9713f3d..63fb145 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -29,6 +29,7 @@ steps: inputs: targetType: 'inline' script: | + sudo ./misc/install_dependencies mkdir build make displayName: 'Build sunneed' From 7af2984aed365bfa2dedd66cc01c2b571160d6f6 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 16:07:29 -0400 Subject: [PATCH 19/42] move install_dependecies to 'Clone system' script --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 63fb145..a340c00 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,6 +22,7 @@ steps: script: | git clone https://github.com/gwsystems/sunneed.git git submodule update --init --recursive + sudo ./misc/install_dependencies ls -la displayName: 'Clone system' @@ -29,7 +30,6 @@ steps: inputs: targetType: 'inline' script: | - sudo ./misc/install_dependencies mkdir build make displayName: 'Build sunneed' From c67786fbb80fa626a7893ea66205c4bb8595e25c Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 16:09:54 -0400 Subject: [PATCH 20/42] Add this branch to pipeline triggers for debugging --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a340c00..db65166 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,6 +9,7 @@ variables: trigger: - master +- Ryan pool: vmImage: 'ubuntu-18.04' From a7595510d61f863185af618d28746e82f5ed34fd Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 16:14:25 -0400 Subject: [PATCH 21/42] remove sudo from install dependencies command --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index db65166..df598ac 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,7 +23,7 @@ steps: script: | git clone https://github.com/gwsystems/sunneed.git git submodule update --init --recursive - sudo ./misc/install_dependencies + ./misc/install_dependencies ls -la displayName: 'Clone system' From dcfacf2ed32488bd2e1dbc7ab5024c8b4893726a Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 16:17:24 -0400 Subject: [PATCH 22/42] give install dependencies its own task --- azure-pipelines.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index df598ac..600efba 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,10 +23,16 @@ steps: script: | git clone https://github.com/gwsystems/sunneed.git git submodule update --init --recursive - ./misc/install_dependencies ls -la displayName: 'Clone system' +- task: Bash@3 + inputs: + targetType: 'inline' + script: | + sudo $PWD/misc/install_dependencies + displayName: 'Install deps' + - task: Bash@3 inputs: targetType: 'inline' From 44a9d3f60890055fb7c164384517e08139080b56 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 16:30:24 -0400 Subject: [PATCH 23/42] Clean up overlay functions and add comments for review --- locked_H5pUiE | 0 src/client/sunneed_client.c | 7 ++++--- src/overlay/sunneed_overlay.c | 4 +--- src/sunneed_listener.c | 15 ++++++++++----- 4 files changed, 15 insertions(+), 11 deletions(-) delete mode 100644 locked_H5pUiE diff --git a/locked_H5pUiE b/locked_H5pUiE deleted file mode 100644 index e69de29..0000000 diff --git a/src/client/sunneed_client.c b/src/client/sunneed_client.c index 651f0e3..e0fd20f 100644 --- a/src/client/sunneed_client.c +++ b/src/client/sunneed_client.c @@ -229,14 +229,14 @@ sunneed_client_socket(int domain, int type, int protocol) * currently only support IPv4 UDP packets for power logging * can extend for TCP and IPv6 later if needed */ - if(!(domain == AF_INET)) + if(domain != AF_INET) { FATAL(-1, "must be IPv4 socket\n"); } - if(!(type == SOCK_DGRAM)) + if(type != SOCK_DGRAM) { - //TODO: determine type of gethostbyname and getaddr info packets, both end up triggering this return case + //TODO: determine type of getnameinfo and getaddrinfo packets, both end up triggering this return case //should error out if not one of those, but for now just return -1 to signal a call to SUPER return -1; } @@ -356,6 +356,7 @@ sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrle conn.port = port; conn.address = address; conn.addrlen = sizeof(address); + conn.sockfd = sockfd; req.connect = &conn; send_request(&req); diff --git a/src/overlay/sunneed_overlay.c b/src/overlay/sunneed_overlay.c index 9691ad6..03a5fbb 100644 --- a/src/overlay/sunneed_overlay.c +++ b/src/overlay/sunneed_overlay.c @@ -59,9 +59,8 @@ write(int fd, const void *buf, size_t count) { int socket(int domain, int type, int protocol) { - int ret; - ret = sunneed_client_socket(domain, type, protocol); + int ret = sunneed_client_socket(domain, type, protocol); if(ret == -1) { @@ -77,7 +76,6 @@ socket(int domain, int type, int protocol) int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { - printf("overlay connect\n"); int ret; diff --git a/src/sunneed_listener.c b/src/sunneed_listener.c index 10f201f..3ef9b00 100644 --- a/src/sunneed_listener.c +++ b/src/sunneed_listener.c @@ -283,7 +283,12 @@ serve_socket(SunneedResponse *resp, void *sub_response_buf, SocketRequest *reque //need to store the domain (AF_INET) to use in connect dummy_socket_map[i].domain = request->domain; LOG_D("Socket %d created successfully\n", new_id); - sock_resp->dummy_sockfd = new_id; + + /* + * add 3 to the dummy sockfd to be returned to avoid overwriting STDIN, STDOUT, or STDERR + * using (request->sockfd - 3) in connect and send should give us O(1) lookup for this table + */ + sock_resp->dummy_sockfd = new_id + 3; return 0; }else{ LOG_E("Failed to create socket. domain %d type %d protocol %d\n", request->domain, request->type, request->protocol); @@ -308,8 +313,8 @@ serve_connect(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant * * the fake sockfd passed by the request is the index into the dummy socket map of the tenants socket * can take advantage of this for O(1) retrieval of the real socket fd and domain */ - sockfd = dummy_socket_map[request->sockfd].sockfd; - domain = dummy_socket_map[request->sockfd].domain; + sockfd = dummy_socket_map[request->sockfd - 3].sockfd; + domain = dummy_socket_map[request->sockfd - 3].domain; //check sockfd and domain if(sockfd < 0) @@ -324,7 +329,7 @@ serve_connect(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant * */ if(!(domain == AF_INET)) { - LOG_E("tenant %d tried to create a non-IPv4 socket\n", tenant->id); + LOG_E("tenant %d tried to create a non-IPv4 socket %d\n", tenant->id, domain); return 1; } @@ -364,7 +369,7 @@ serve_send(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant *ten LOG_D("Got request from %d to send %ld bytes", tenant->id, request->data.len); //same lookup as serve_connect, use the requested sockfd as the index for O(1) lookup - int sockfd = dummy_socket_map[request->sockfd].sockfd; + int sockfd = dummy_socket_map[request->sockfd - 3].sockfd; if(sockfd < 0) { From 88b395e938829fa5031f309b9b4cdb981cb43a9a Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 16:32:52 -0400 Subject: [PATCH 24/42] remove sudo from install_dependencies task --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 600efba..dd03d65 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -30,9 +30,9 @@ steps: inputs: targetType: 'inline' script: | - sudo $PWD/misc/install_dependencies + $PWD/misc/install_dependencies displayName: 'Install deps' - + - task: Bash@3 inputs: targetType: 'inline' From d655ba7621c609149820c91e3f6c6700d7329f76 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 16:37:26 -0400 Subject: [PATCH 25/42] add --user root option to pipeline --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index dd03d65..5135a5c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -15,6 +15,7 @@ pool: vmImage: 'ubuntu-18.04' container: grahamschock/suneed_env:latest +options: --user root steps: - task: Bash@3 From a66ebfe00659ff6f7cebfc0e8da098449c3ffb53 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 16:40:22 -0400 Subject: [PATCH 26/42] remove options from pipeline, rerun with bash --- azure-pipelines.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5135a5c..7e67dd0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -15,7 +15,7 @@ pool: vmImage: 'ubuntu-18.04' container: grahamschock/suneed_env:latest -options: --user root + steps: - task: Bash@3 @@ -30,8 +30,8 @@ steps: - task: Bash@3 inputs: targetType: 'inline' - script: | - $PWD/misc/install_dependencies + bash: | + sudo $PWD/misc/install_dependencies displayName: 'Install deps' - task: Bash@3 From 0258bcdb0ab16fae51227624cd3f3cbfcf022bc6 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 16:44:13 -0400 Subject: [PATCH 27/42] fix execution of install_dependencies command --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7e67dd0..a8e6594 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -31,7 +31,7 @@ steps: inputs: targetType: 'inline' bash: | - sudo $PWD/misc/install_dependencies + sudo ./misc/install_dependencies displayName: 'Install deps' - task: Bash@3 From 49672f74498569cd663b14c8febf8d448e5767ed Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 16:49:48 -0400 Subject: [PATCH 28/42] add echo to install_dependencies script for debugging --- misc/install_dependencies | 1 + 1 file changed, 1 insertion(+) diff --git a/misc/install_dependencies b/misc/install_dependencies index 0f1cee3..d392cf1 100755 --- a/misc/install_dependencies +++ b/misc/install_dependencies @@ -2,3 +2,4 @@ apt install -y cmake apt install -y ninja-build apt install -y libprotobuf-c-dev protobuf-c-compiler apt install -y libi2c-dev +echo "done installing dependencies" From 3e3d53367bcc04dcde3b0f4ba163e7c3a5d04586 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 16:52:42 -0400 Subject: [PATCH 29/42] edit pipeline so it will rerun --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a8e6594..c7630a9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -32,6 +32,7 @@ steps: targetType: 'inline' bash: | sudo ./misc/install_dependencies + echo ':)' displayName: 'Install deps' - task: Bash@3 From b0968b00a5f2e0dee2e56ce9452858bcc96a6f52 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 17:02:37 -0400 Subject: [PATCH 30/42] trying to run pipeline as sudo --- azure-pipelines.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c7630a9..89afa21 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -13,6 +13,7 @@ trigger: pool: vmImage: 'ubuntu-18.04' + options: --user root container: grahamschock/suneed_env:latest @@ -31,7 +32,7 @@ steps: inputs: targetType: 'inline' bash: | - sudo ./misc/install_dependencies + ./misc/install_dependencies echo ':)' displayName: 'Install deps' From f21607b53ca3f243b0d1d4922862cd0ae7cd48c5 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 17:05:56 -0400 Subject: [PATCH 31/42] trying to run pipeline as sudo --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 89afa21..17f798c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -32,8 +32,8 @@ steps: inputs: targetType: 'inline' bash: | + ls ./misc/install_dependencies - echo ':)' displayName: 'Install deps' - task: Bash@3 From e668dbe087ce8999fc1f35d8f3ee368567d51e91 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 17:08:07 -0400 Subject: [PATCH 32/42] change bash option to script now that pipeline is executing as root --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 17f798c..98c01c5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -31,7 +31,7 @@ steps: - task: Bash@3 inputs: targetType: 'inline' - bash: | + script: | ls ./misc/install_dependencies displayName: 'Install deps' From 05271a15724df66dfaccc60be52c09e0776a0b3a Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 17:10:32 -0400 Subject: [PATCH 33/42] add sudo back to script maybe --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 98c01c5..98d8ffa 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -33,7 +33,7 @@ steps: targetType: 'inline' script: | ls - ./misc/install_dependencies + sudo ./misc/install_dependencies displayName: 'Install deps' - task: Bash@3 From 005fdd4c4b22c5b901a0ba71734b3b5347075684 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Mon, 26 Jul 2021 17:13:38 -0400 Subject: [PATCH 34/42] install sudo from script to see if that fixes the command not found issue --- azure-pipelines.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 98d8ffa..e70c5f9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -32,6 +32,8 @@ steps: inputs: targetType: 'inline' script: | + su - + apt-get install sudo -y ls sudo ./misc/install_dependencies displayName: 'Install deps' From 17ad5e88c41763d6f578f460d98a00ddddecd389 Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Tue, 27 Jul 2021 17:25:42 -0400 Subject: [PATCH 35/42] remove unused loop from connect --- src/sunneed_listener.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sunneed_listener.c b/src/sunneed_listener.c index 3ef9b00..2ddf855 100644 --- a/src/sunneed_listener.c +++ b/src/sunneed_listener.c @@ -305,7 +305,7 @@ static int serve_connect(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant *tenant, ConnectRequest *request) { LOG_D("got connect request\n"); - int i, sockfd, domain; + int sockfd, domain; struct sockaddr_in remote_addr; sockfd = domain = 0; From f9a934b29a1e4e137be2f10d33fe771838363381 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Wed, 28 Jul 2021 12:54:15 -0400 Subject: [PATCH 36/42] fix conflicts from human error --- Makefile | 199 +++++++++++++++++++ azure-pipelines.yml | 77 +------- src/client/sunneed_client.c | 247 ----------------------- src/client/sunneed_client.h | 49 ----- src/log.h | 54 ----- src/overlay/sunneed_overlay.c | 58 ------ src/pip/bq27441.c | 17 +- src/protobuf/server.proto | 82 -------- src/sunneed.h | 20 -- src/sunneed_core.c | 150 -------------- src/sunneed_core.h | 45 ----- src/sunneed_listener.c | 361 ---------------------------------- src/sunneed_listener.h | 32 --- src/sunneed_power.c | 158 +-------------- src/sunneed_power.h | 56 ------ src/sunneed_proc.c | 122 ------------ src/sunneed_proc.h | 55 ------ 17 files changed, 206 insertions(+), 1576 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..46188fc --- /dev/null +++ b/Makefile @@ -0,0 +1,199 @@ +# Builds the main sunneed executable. + +ifeq ($(origin CC),default) + export CC = gcc +endif + +CFLAGS ?= -Wall -Wextra -g +PROTOC ?= protoc-c + +SUNNEED_BUILD_TYPE ?= devel +SUNNEED_BUILD_PIP ?= bq27441 +SUNNEED_BUILD_OUT_DIR ?= build +SUNNEED_BUILD_BIN_FILE ?= sunneed +SUNNEED_BUILD_CLIENT_LIB_NAME ?= libsunneedclient +SUNNEED_BUILD_OVERLAY_LIB_NAME ?= sunneed_overlay + +export SOURCE_FORMATTER = clang-format -style=file -i + +export cflags_deps = -I$(PWD)/$(ext_dir)/nng/include -L$(PWD)/$(ext_dir)/nng/build -L$(PWD)/$(ext_dir)/libbq27441 -lnng -lpthread -ldl -lprotobuf-c -latomic -I$(PWD)/$(ext_dir)/libbq27441 -lbq27441 -li2c + +ifeq ($(SUNNEED_BUILD_TYPE),devel) + util_cflags = -Wl,-rpath,$(CURDIR)/$(clientlib_out_dir) +endif + +src_dir = src +sources = $(wildcard $(src_dir)/*.c) + +ext_dir = ext + +out_dir = $(SUNNEED_BUILD_OUT_DIR) +bin_file = $(SUNNEED_BUILD_BIN_FILE) + +pip_out_dir = $(out_dir) +pip_obj = $(pip_out_dir)/pip.o +pip_name = $(SUNNEED_BUILD_PIP) + +clientlib_sources = $(wildcard $(src_dir)/client/*.c) +clientlib_out_dir = $(out_dir)/client +clientlib_obj = $(clientlib_out_dir)/$(SUNNEED_BUILD_CLIENT_LIB_NAME).so + +overlay_sources = $(wildcard $(src_dir)/overlay/*.c) +overlay_out_dir = $(out_dir) +overlay_obj = $(overlay_out_dir)/$(SUNNEED_BUILD_OVERLAY_LIB_NAME).so +overlay_testing_obj = $(patsubst %.so,%_testing.so,$(overlay_obj)) +overlay_runner = $(out_dir)/run-with-overlay + +protobuf_dir = $(src_dir)/protobuf +protobuf_sources = $(wildcard $(protobuf_dir)/*.proto) +protobuf_out_files = $(foreach src,$(protobuf_sources),$(subst !!!, ,$(join $(src:.proto=.pb-c.c!!!),$(src:.proto=.pb-c.h)))) +protobuf_out_dir = $(src_dir)/protobuf/c +protobuf_out_sources = $(wildcard $(protobuf_out_dir)/*.c) + +export test_home = test +export test_runner_name = run-tests +test_runner = $(test_home)/$(test_runner_name) +runtime_tests_runner = ./runtime_tests + +device_objs = $(patsubst %.c, %.o, $(wildcard $(src_dir)/device/*.c)) +util_objs = $(patsubst %.c, %.o, $(wildcard $(src_dir)/util/*.c)) + +all: pre-all main overlay util + +run_valgrind: pre-all main overlay util + valgrind ./build/sunneed + +run_ASAN: pre-all main_ASAN overlay util + ./build/sunneed + + +pre-all: + @echo "Starting all build..." + +log_pwr: pre-all main_pwr_data overlay util + + +main: ext protobuf pip devices + $(call section_title,main executable) + $(CC) $(CFLAGS) -DTESTING $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) + +main_pwr_data: ext protobuf pip devices + $(call section_title, main executable) + $(CC) $(CFLAGS) -DTESTING -DLOG_PWR $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) + +main_ASAN: ext protobuf pip devices + $(call section_title,main executable) + $(CC) -fsanitize=address $(CFLAGS) -DTESTING $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) + +pip: pre-pip $(src_dir)/pip/$(pip_name).c + $(CC) $(CFLAGS) -o $(pip_obj) -c $(src_dir)/pip/$(pip_name).c +pre-pip: + $(call section_title,pip) + +devices: pre-devices ext $(device_objs) +pre-devices: + $(call section_title,devices) + +util: clientlib pre-util $(util_objs) +pre-util: + $(call section_title,utils) + +$(src_dir)/util/%.o: $(src_dir)/util/%.c + $(CC) $(CFLAGS) -o $(patsubst $(src_dir)/util/%.o, $(out_dir)/%, $@) $^ $(protobuf_out_sources) -L$(clientlib_out_dir) -lsunneedclient $(util_cflags) $(cflags_deps) + +$(src_dir)/client/%.o: $(src_dir)/client/%.c + $(CC) $(CFLAGS) -o $(patsubst $(src_dir)/util/%.o, $(out_dir)/%, $@) $^ $(protobuf_out_sources) $(cflags_deps) + +$(src_dir)/device/%.o: $(src_dir)/device/%.c + @if [ ! -d "$(out_dir)/device" ]; then mkdir "$(out_dir)/device"; fi + $(CC) $(CFLAGS) -g -shared -o $(patsubst $(src_dir)/device/%.o, $(out_dir)/device/%.so, $@) -fPIC $^ $(cflags_deps) + +protobuf: pre-protobuf $(protobuf_out_files) + @rm -rf "$(protobuf_out_dir)" && mkdir "$(protobuf_out_dir)" + mv $(protobuf_out_files) $(protobuf_out_dir) +pre-protobuf: + $(call section_title,protobuf) + +$(protobuf_out_files): $(protobuf_sources) + $(MAKE) --no-print-directory -C $(protobuf_dir) + +clientlib: ext + $(call section_title,client library) + @if [ ! -d "$(out_dir)/client" ]; then mkdir "$(out_dir)/client"; fi + $(CC) $(CFLAGS) -c -fPIC -o $(out_dir)/client/clientlib.o $(clientlib_sources) $(cflags_deps) + $(CC) $(CFLAGS) -shared -o $(clientlib_obj) $(out_dir)/client/clientlib.o + +# Run the runtime tests +runtime_test: main + $(runtime_tests_runner) + +test: tests + $(test_runner) + +tests: + make -C $(test_home) + +# Note that we compile two overlay libraries. One is a tester, which contains additional output meant for +# debugging/testing purposes. +overlay: clientlib + $(call section_title,overlay) + $(CC) $(CFLAGS) -g -fPIC -shared $(overlay_sources) -o $(overlay_obj) $(cflags_deps) + $(CC) $(CFLAGS) -g -fPIC -shared -DTESTING $(overlay_sources) -o $(overlay_testing_obj) $(cflags_deps) + @echo Generate overlay runscript at $(overlay_runner) + @echo "#!/usr/bin/env bash\n$(overlay_runscript_content)" > $(overlay_runner) + chmod +x $(overlay_runner) + +clean: + rm -rf "$(out_dir)"/* + rm -rf "$(protobuf_out_dir)"/* + $(MAKE) -C $(test_home) clean + @echo '=============================================================' + @echo '= External library files were not cleaned. =' + @echo '= Please run `make -C ext clean` if you wish to clean them. =' + @echo '=============================================================' + +format: + $(SOURCE_FORMATTER) $(shell find '$(src_dir)' -not -path '$(protobuf_dir)/*' -type f -regex '.*\.[ch]') + $(MAKE) -C $(test_home) format + +tags: + ctags -R src/* + +ext: + $(call section_title,dependencies) + $(MAKE) -C $(ext_dir) + +.PHONY: all pip util test runtime_test clean format ext tags + +LeftParens := ( +RightParens := ) + +# Prints a nice little header graphic in the form: +# +# ======================= +# === Building === +# ======================= +# +# When using in the Makefile, in simple cases you can just call this at the beginning of the target. For more +# complicated targets, however, you will have to make a `pre-` target and run that before resolving the rest +# of the target. Remember to put your `pre-` target *after* the targets that your depends upon, but *before* the list +# of files (if any). See the `main`, `util`, and `devices` targets for examples of usage in different situations. +section_count := 1 +num_sections := $(shell grep -E '^\s*\$$\$(LeftParens)\s*call\s+section_title,' Makefile | wc -l) +define section_title + @echo + $(eval _var := $(section_count)/$(num_sections) $(1)) + $(eval _len := $(shell x="$(_var)"; echo -n $${#x})) + @printf '=%.0s' $(shell seq -16 $(_len)) + @echo + @echo === $(section_count)/$(num_sections) Building $(1) === + @printf '=%.0s' $(shell seq -16 $(_len)) + @echo + $(eval section_count := $(shell expr $(section_count) + 1)) +endef + +ifeq ($(SUNNEED_BUILD_TYPE),devel) + overlay_runscript_content := "gdb --args env LD_PRELOAD=$(abspath $(overlay_testing_obj)) $$\@" +else + overlay_runscript_content := "LD_PRELOAD=$(abspath $(overlay_obj)) $$\@" +endif diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bab3748..ee63b87 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,4 +1,3 @@ -<<<<<<< HEAD # Starter pipeline # Start with a minimal pipeline that you can customize to build and deploy your code. # Add steps that build, run tests, deploy, and more: @@ -10,35 +9,23 @@ variables: trigger: - master -- Ryan pool: vmImage: 'ubuntu-18.04' - options: --user root container: grahamschock/suneed_env:latest - steps: - task: Bash@3 inputs: targetType: 'inline' script: | + sudo ./misc/install_dependencies git clone https://github.com/gwsystems/sunneed.git git submodule update --init --recursive ls -la displayName: 'Clone system' -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - su - - apt-get install sudo -y - ls - sudo ./misc/install_dependencies - displayName: 'Install deps' - - task: Bash@3 inputs: targetType: 'inline' @@ -71,65 +58,3 @@ steps: cd src/sunneed make runtime_test displayName: 'Run runtime tests' -======= -# Starter pipeline -# Start with a minimal pipeline that you can customize to build and deploy your code. -# Add steps that build, run tests, deploy, and more: -# https://aka.ms/yaml - -variables: -- name: IS_AZURE_PIPELINE - value: true - -trigger: -- master - -pool: - vmImage: 'ubuntu-18.04' - -container: grahamschock/suneed_env:latest - -steps: -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - sudo ./misc/install_dependencies - git clone https://github.com/gwsystems/sunneed.git - git submodule update --init --recursive - ls -la - displayName: 'Clone system' - -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - mkdir build - make - displayName: 'Build sunneed' - -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - rm /usr/bin/gcc - ln -s /usr/bin/gcc-8 /usr/bin/gcc - make tests - displayName: 'Build unit tests' - -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - ls -la - make test - displayName: 'Run unit tests' - -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - cd src/sunneed - make runtime_test - displayName: 'Run runtime tests' ->>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 diff --git a/src/client/sunneed_client.c b/src/client/sunneed_client.c index aed691a..e0fd20f 100644 --- a/src/client/sunneed_client.c +++ b/src/client/sunneed_client.c @@ -1,4 +1,3 @@ -<<<<<<< HEAD #include "sunneed_client.h" #include "../log.h" #include @@ -439,249 +438,3 @@ sunneed_client_debug_print_locked_path_table(void) { } client_printf("]\n"); } -======= -#include "sunneed_client.h" -#include "../log.h" -#include -#include -#include - -struct { - char *path; - int fd; -} locked_paths[MAX_LOCKED_FILES] = { { NULL, 0 } }; - -nng_socket sunneed_socket; - -static void -nngfatal(const char *func, int rv) { - FATAL(rv, "%s: %s\n", func, nng_strerror(rv)); -} - -/** - * Packs the given SunneedRequest into an NNG message and sends it to sunneed. If any failures occur in the packing - * or sending processes, this client will crash with a fatal error. - */ -static void -send_request(SunneedRequest *req) { - nng_msg *msg; - - int req_len = sunneed_request__get_packed_size(req); - void *buf = malloc(req_len); - if (!buf) - FATAL(-1, "unable to allocate buffer for request"); - sunneed_request__pack(req, buf); - -// SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &msg, req_len); -// SUNNEED_NNG_TRY(nng_msg_insert, != 0, msg, buf, req_len); - SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &msg, 0); - SUNNEED_NNG_TRY(nng_msg_append, != 0, msg, buf, req_len); - SUNNEED_NNG_TRY(nng_sendmsg, != 0, sunneed_socket, msg, 0); - - free(buf); -// nng_msg_free(msg); -} - -static SunneedResponse * -receive_response(SunneedResponse__MessageTypeCase message_type) { - nng_msg *reply; - SUNNEED_NNG_TRY(nng_recvmsg, != 0, sunneed_socket, &reply, 0); - - size_t msg_len = nng_msg_len(reply); -// SUNNEED_NNG_MSG_LEN_FIX(msg_len); - SunneedResponse *resp = sunneed_response__unpack(NULL, msg_len, nng_msg_body(reply)); - - if (resp->status != 0) { - return NULL; - } else if (resp->message_type_case != message_type) { - FATAL(-1, "incorrect message type received (expected %d, got %d)", message_type, resp->status); - } - - nng_msg_free(reply); - return resp; -} - -int -sunneed_client_init(const char *name) { - SUNNEED_NNG_SET_ERROR_REPORT_FUNC(nngfatal); - SUNNEED_NNG_TRY(nng_req0_open, != 0, &sunneed_socket); - SUNNEED_NNG_TRY(nng_dial, != 0, sunneed_socket, SUNNEED_LISTENER_URL, NULL, 0); - - // Register this client with sunneed. - SunneedRequest req = SUNNEED_REQUEST__INIT; - req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT; - RegisterClientRequest register_req = REGISTER_CLIENT_REQUEST__INIT; - register_req.name = malloc(strlen(name) + 1); - if (!register_req.name) { - FATAL(-1, "failed to allocate memory for client name"); - return -1; - } - strncpy(register_req.name, name, strlen(name) + 1); - req.register_client = ®ister_req; - send_request(&req); - free(register_req.name); - - // Check the response. - SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_REGISTER_CLIENT); - for (size_t i = 0; i < resp->register_client->n_locked_paths; i++) { - locked_paths[i].path = malloc(strlen(resp->register_client->locked_paths[i]) + 1); - strcpy(locked_paths[i].path, resp->register_client->locked_paths[i]); - client_printf(-1, "Registered locked path '%s'", locked_paths[i].path); - } - sunneed_response__free_unpacked(resp, NULL); - - return 0; -} - -/** Allocate a string containing the path of the dummy file corresponding to the given path. */ -char * - -sunneed_client_fetch_locked_file_path(const char *pathname, int flags, int mode) { - // TODO Check socket opened. - - SunneedRequest req = SUNNEED_REQUEST__INIT; - req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_OPEN_FILE; - OpenFileRequest open_file_req = OPEN_FILE_REQUEST__INIT; - open_file_req.path = malloc(strlen(pathname) + 1); - if (!open_file_req.path) { - FATAL(-1, "failed to allocated memory for path"); - } - strncpy(open_file_req.path, pathname, strlen(pathname) + 1); - - open_file_req.flags = flags; - - req.open_file = &open_file_req; - - open_file_req.flags = flags; - open_file_req.mode = mode; - - - send_request(&req); - free(open_file_req.path); - - // TODO Handle request of a path that isn't locked. - SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_OPEN_FILE); - if (resp == NULL) { - // TODO Gotos - return 0; - } - - client_printf("Opening dummy path '%s'\n", resp->open_file->path); - char *path = malloc(strlen(resp->open_file->path) + 1); - if (!path) - FATAL(-1, "unable to allocate path"); - strcpy(path, resp->open_file->path); - - sunneed_response__free_unpacked(resp, NULL); - - return path; -} - -int -sunneed_client_check_locked_file(const char *pathname) { - for (int i = 0; i < MAX_LOCKED_FILES; i++) { - if (locked_paths[i].path == NULL) - return -1; - else if (strncmp(pathname, locked_paths[i].path, strlen(pathname)) == 0) - return i; - } - - return -1; -} - -int -sunneed_client_on_locked_path_open(int i, char *pathname, int fd) { - if (i < 0 || i >= MAX_LOCKED_FILES) - FATAL(-1, "locked-file array index out of bounds"); - if (pathname == NULL) - FATAL(-1, "pathname is null"); - if (fd <= 0) - FATAL(-1, "illegal FD"); - - locked_paths[i].path = pathname; - locked_paths[i].fd = fd; - - return 0; -} - -bool -sunneed_client_fd_is_locked(int fd) { - for (int i = 0; i < MAX_LOCKED_FILES; i++) - if (locked_paths[i].fd == fd) - return true; - return false; -} - -ssize_t -sunneed_client_remote_write(int fd, const void *data, size_t n_bytes) { - // Get the dummy path corresponding to the FD. - int locked_file_i; - char *dummy_path = NULL; - for (locked_file_i = 0; locked_file_i < MAX_LOCKED_FILES; locked_file_i++) - if (locked_paths[locked_file_i].fd == fd) { - dummy_path = locked_paths[locked_file_i].path; - break; - } - - if (dummy_path == NULL) - FATAL(-1, "cannot remote write a non-dummy file"); - - SunneedRequest req = SUNNEED_REQUEST__INIT; - req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_WRITE; - - WriteRequest write_req = WRITE_REQUEST__INIT; - write_req.dummy_path = malloc(strlen(dummy_path) + 1); - write_req.data.data = malloc(n_bytes); - if (!write_req.dummy_path || !write_req.data.data) - FATAL(-1, "failed to allocate write request data"); - strncpy((char *)write_req.data.data, data, n_bytes); - strncpy(write_req.dummy_path, dummy_path, strlen(dummy_path) + 1); - write_req.data.len = n_bytes; - - req.write = &write_req; - send_request(&req); - - free(write_req.dummy_path); - free(write_req.data.data); - - SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_CALL_WRITE); - if (resp == NULL) { - // TODO Handle - FATAL(-1, "write response was NULL"); - } - sunneed_response__free_unpacked(resp, NULL); - - return 0; -} - -int -sunneed_client_disconnect(void) { - // TODO Check socket opened. - - SunneedRequest req = SUNNEED_REQUEST__INIT; - req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_UNREGISTER_CLIENT; - UnregisterClientRequest unregister_req = UNREGISTER_CLIENT_REQUEST__INIT; - req.unregister_client = &unregister_req; - send_request(&req); - - SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC); - if (resp == NULL) { - // TODO Handle - FATAL(-1, "Disconnect response was NULL\n"); - } - sunneed_response__free_unpacked(resp, NULL); - - client_printf("Unregistered.\n"); - return 0; -} - -void -sunneed_client_debug_print_locked_path_table(void) { - client_printf("locked files: [\n"); - for (int i = 0; i < MAX_LOCKED_FILES; i++) { - if (locked_paths[i].path != NULL) - client_printf(" FD %d : '%s'\n", locked_paths[i].fd, locked_paths[i].path); - } - client_printf("]\n"); -} ->>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 diff --git a/src/client/sunneed_client.h b/src/client/sunneed_client.h index 56b0d71..3444c93 100644 --- a/src/client/sunneed_client.h +++ b/src/client/sunneed_client.h @@ -1,4 +1,3 @@ -<<<<<<< HEAD #include "../protobuf/c/device.pb-c.h" #include "../protobuf/c/server.pb-c.h" @@ -66,51 +65,3 @@ ssize_t sunneed_client_remote_send(int sockfd, const void *data, size_t n_bytes, int flags); -======= -#include "../protobuf/c/device.pb-c.h" - -#include "../protobuf/c/server.pb-c.h" -#include "../shared/sunneed_ipc.h" -#include "../shared/sunneed_files.h" - -#include - -#include -#include -#include - -#define FATAL(CODE, FMT, ...) \ - { \ - fprintf(stderr, "fatal (%d): " FMT "\n", CODE, ##__VA_ARGS__); \ - exit(CODE); \ - } - -#define client_printf(FMT, ...) \ - printf("\e[38;5;240mclient:\e[0m " FMT, ##__VA_ARGS__) - -typedef unsigned int sunneed_device_handle_t; - -int -sunneed_client_init(const char *name); - -char * -sunneed_client_fetch_locked_file_path(const char *pathname, int flags, int mode); - -int -sunneed_client_check_locked_file(const char *pathname); - -bool -sunneed_client_fd_is_locked(int fd); - -ssize_t -sunneed_client_remote_write(int fd, const void *data, size_t n_bytes); - -int -sunneed_client_disconnect(void); - -int -sunneed_client_on_locked_path_open(int i, char *pathname, int fd); - -void -sunneed_client_debug_print_locked_path_table(void); ->>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 diff --git a/src/log.h b/src/log.h index ddd4535..f56495d 100644 --- a/src/log.h +++ b/src/log.h @@ -1,56 +1,3 @@ -<<<<<<< HEAD -#ifndef _LOG_H_ -#define _LOG_H_ - -/* - * Defines a variety of macros to be used for logging information. - * Assign a FILE* to `logfile` during runtime to redirect the log to that file. - */ - -#include -#include - -FILE *logfile, *stepper_pwr_logfile; - - -#define LOGL_DEBUG "D\e[38;5;240m" -#define LOGL_INFO "I" -#define LOGL_WARN "W\e[0;33m" -#define LOGL_ERROR "E\e[0;31m" - -#ifndef LOG_PWR_EVENT -#define LOG_PWR_EVENT(LEVEL, MESSAGE, ...) \ - { \ - FILE *_logfile = fopen("sunneed_network_pwr_log.txt", "w+"); \ - if (!_logfile) { \ - return; \ - } \ - \ - fprintf(_logfile, MESSAGE); \ - fflush(_logfile); \ - } -#endif -#define LOG(LEVEL, MESSAGE, ...) \ - { \ - FILE *_logfile = logfile; \ - if (!logfile) { \ - _logfile = stdout; \ - } \ - time_t _now = time(NULL); \ - struct tm *_time = localtime(&_now); \ - char _time_str[21]; \ - strftime(_time_str, 21, "%Y-%m-%d %H:%M:%S", _time); \ - fprintf(_logfile, "%s[%s] " MESSAGE "\e[0m\n", LEVEL, _time_str, ##__VA_ARGS__); \ - } - -#define LOG_D(MESSAGE, ...) LOG(LOGL_DEBUG, MESSAGE, ##__VA_ARGS__); -#define LOG_I(MESSAGE, ...) LOG(LOGL_INFO, MESSAGE, ##__VA_ARGS__); -#define LOG_W(MESSAGE, ...) LOG(LOGL_WARN, MESSAGE, ##__VA_ARGS__); -#define LOG_E(MESSAGE, ...) LOG(LOGL_ERROR, MESSAGE, ##__VA_ARGS__); -#define LOG_P(MESSAGE, ...) LOG_PWR_EVENT(LOGL_INFO, MESSAGE, ##__VA_ARGS__); - -#endif -======= #ifndef _LOG_H_ #define _LOG_H_ @@ -103,4 +50,3 @@ FILE *logfile, *logfile_pwr; #define LOG_E(MESSAGE, ...) LOG(LOGL_ERROR, MESSAGE, ##__VA_ARGS__); #define LOG_P(MESSAGE, ...) LOG_PWR(LOGL_INFO, MESSAGE, ##__VA_ARGS__); #endif ->>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 diff --git a/src/overlay/sunneed_overlay.c b/src/overlay/sunneed_overlay.c index cdfb789..03a5fbb 100644 --- a/src/overlay/sunneed_overlay.c +++ b/src/overlay/sunneed_overlay.c @@ -1,4 +1,3 @@ -<<<<<<< HEAD #include "sunneed_overlay.h" void @@ -119,60 +118,3 @@ send(int sockfd, const void *buf, size_t len, int flags) -======= -#include "sunneed_overlay.h" - -void -on_load() { - sunneed_client_init("TODO"); - - printf("Overlay: Client init\n"); -} - -void -on_unload() { - sunneed_client_disconnect(); -} - -int -open(const char *pathname, int flags, mode_t mode) { - printf("Opening file %s\n", pathname); - - int locked = sunneed_client_check_locked_file(pathname); - if (locked < 0) { - printf("'%s' is not locked; opening normally\n", pathname); - } else { - printf("'%s' is locked; opening via dummy\n", pathname); - char *dummy_path = sunneed_client_fetch_locked_file_path(pathname, flags, mode); - pathname = dummy_path; - } - - int fd; - SUPER(fd, open, int, (pathname, flags, mode), const char *, int, mode_t); - - // TODO Handle errors from open - - sunneed_client_on_locked_path_open(locked, (char *)pathname, fd); - - sunneed_client_debug_print_locked_path_table(); - - return fd; -} - -ssize_t -write(int fd, const void *buf, size_t count) { - printf("Overlay write %d\n", fd); - int ret; - - if (!sunneed_client_fd_is_locked(fd)) { - // Perform the write as normal. - SUPER(ret, write, int, (fd, buf, count), int, const void *, size_t); - return ret; - } - - // Ask sunneed to do the write for us. - sunneed_client_remote_write(fd, buf, count); - - return 0; -} ->>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 diff --git a/src/pip/bq27441.c b/src/pip/bq27441.c index 39f4f8d..8ee6fd6 100644 --- a/src/pip/bq27441.c +++ b/src/pip/bq27441.c @@ -1,17 +1,3 @@ -<<<<<<< HEAD -#include "../shared/sunneed_pip_interface.h" - -struct sunneed_pip -pip_info() { - return (struct sunneed_pip){"bq27441", 1000, 50}; -} - -unsigned int -present_power() { - return 0; - //return bq27441_nominal_avail_cap(); -} -======= #include "../shared/sunneed_pip_interface.h" #include "../../ext/libbq27441/bq27441.c" @@ -20,9 +6,10 @@ pip_info() { return (struct sunneed_pip){"bq27441", 1000, 50}; } +#ifdef LOG_PWR unsigned int present_power() { bq27441_init(1); return bq27441_nominal_avail_cap(); } ->>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 +#ifdef LOG_PWR \ No newline at end of file diff --git a/src/protobuf/server.proto b/src/protobuf/server.proto index bf9a412..6369ac1 100644 --- a/src/protobuf/server.proto +++ b/src/protobuf/server.proto @@ -1,4 +1,3 @@ -<<<<<<< HEAD syntax = "proto3"; // {{{1 Request @@ -107,84 +106,3 @@ message DeviceResponse { // }}} // vim: fdm=marker : -======= -syntax = "proto3"; - -// {{{1 Request - -// Generic container type for messages sent from client to server. -message SunneedRequest { - oneof message_type { - RegisterClientRequest register_client = 1; - UnregisterClientRequest unregister_client = 2; - OpenFileRequest open_file = 3; - WriteRequest write = 4; - } -} - -message RegisterClientRequest { - string name = 1; -} - -message UnregisterClientRequest {} - -message OpenFileRequest { - string path = 1; - int32 flags = 2; - int32 mode = 3; -} - -message WriteRequest { - string dummy_path = 1; - bytes data = 2; -} - -// {{{1 Response - -// Generic container type for messages sent from server to client. -message SunneedResponse { - int32 status = 1; - oneof message_type { - GenericResponse generic = 2; - DeviceResponse device = 3; - OpenFileResponse open_file = 4; - RegisterClientResponse register_client = 5; - WriteResponse call_write = 6; - } -} - -message RegisterClientResponse { - repeated string locked_paths = 1; -} - -message GenericResponse { - bytes data = 1; -} - -message OpenFileResponse { - string path = 1; -} - -message WriteResponse { - // Zero if no error. - int32 errno_value = 1; - - // This is only set from values of type `size_t`, so don't worry about data loss when converting it back from - // uint64. - uint64 bytes_written = 2; -} - -import "device.proto"; - -// Dear whoever is reading this, I hope you like the word "Device"... -message DeviceResponse { - - oneof message_type { - DeviceRandomResponse random = 1; - } -}; - -// }}} - -// vim: fdm=marker : ->>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 diff --git a/src/sunneed.h b/src/sunneed.h index 13c09d1..aa9c816 100644 --- a/src/sunneed.h +++ b/src/sunneed.h @@ -1,22 +1,3 @@ -<<<<<<< HEAD -#ifndef _SUNNEED_H_ -#define _SUNNEED_H_ - -#include -#include - -#define APP_NAME "sunneed" - -#ifdef LOG_PWR - #define REQS_PER_LOG 10 - int last_capacity, curr_capacity, reqs_since_last_log; - clock_t last_send, time_since_send; -#endif - -typedef void* sunneed_worker_thread_result_t; - -#endif -======= #ifndef _SUNNEED_H_ #define _SUNNEED_H_ @@ -41,4 +22,3 @@ struct tenant_pipe { nng_pipe pipe; } tenant_pipes[SUNNEED_MAX_IPC_CLIENTS]; #endif ->>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 diff --git a/src/sunneed_core.c b/src/sunneed_core.c index 5045af2..9825262 100644 --- a/src/sunneed_core.c +++ b/src/sunneed_core.c @@ -1,152 +1,3 @@ -<<<<<<< HEAD -#include "sunneed_core.h" - -extern struct sunneed_device devices[MAX_DEVICES]; - -struct sunneed_pip pip; - -sunneed_worker_thread_result_t (*worker_thread_functions[])(void *) = {sunneed_proc_monitor, sunneed_quantum_worker, NULL}; - -#ifdef TESTING - -#include "sunneed_runtime_test_collection.h" - -int (*runtime_tests[])(void) = RUNTIME_TESTS; - -static unsigned int -testcase_count(void) { - unsigned int testcases = 0; - for (int (**cur)(void) = runtime_tests; *cur != NULL; cur++) - testcases++; - // TODO Why minus one... - return testcases - 1; -} - -static int -run_testcase(unsigned int testcase) { - if (testcase >= testcase_count()) { - LOG_E("Cannot run testcase #%d because it does not exist", testcase); - return 1; - } - - int ret = runtime_tests[testcase](); - if (ret != 0) { - fprintf(stderr, "Failure: %s (%d)\n", sunneed_runtime_test_error, ret); - return ret; - } - - return 0; -} -#endif - -static int -spawn_worker_threads(void) { - int ret; - int worker_thread_count = 0; - for (void *(**cur)(void *) = worker_thread_functions; *cur != NULL; cur++) - worker_thread_count++; - - pthread_t worker_threads[worker_thread_count]; - - LOG_I("Launching %d worker threads", worker_thread_count); - - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - for (int i = 0; i < worker_thread_count; i++) { - if ((ret = pthread_create(&worker_threads[i], &attr, worker_thread_functions[i], NULL)) != 0) { - LOG_E("Failed to launch worker thread %d (error %d)", i, ret); - return 1; - }; - } - - return 0; -} - - -void -sunneed_init(void) { - pip = pip_info(); - #ifdef LOG_PWR - last_capacity = present_power(); - #endif -} - -int -main(int argc, char *argv[]) { - int opt; - extern int optopt; -#ifdef LOG_PWR - LOG_D("Recording power\n"); - stepper_pwr_logfile = fopen("stepper_pwr_log.txt", "w+"); - reqs_since_last_log = 0; -#endif - -#ifdef TESTING - const char *optstring = ":ht:c"; -#else - const char *optstring = ":h"; -#endif - - // TODO Long-form getopts. - while ((opt = getopt(argc, argv, optstring)) != -1) { - switch (opt) { - case 'h': - printf(HELP_TEXT, argv[0]); - exit(0); -#ifdef TESTING - case 't': ; - logfile = fopen("sunneed_log.txt", "w+"); - int testcase = strtol(optarg, NULL, 10); - if (errno) { - LOG_E("Failed to parse testcase index: %s", strerror(errno)); - return 1; - } - return run_testcase(testcase); - case 'c': - printf("%d\n", testcase_count()); - exit(0); -#endif - case '?': - fprintf(stderr, "%s: illegal option -%c\n", APP_NAME, optopt); - exit(1); - case ':': - fprintf(stderr, "%s: expected argument for option -%c\n", APP_NAME, optopt); - exit(1); - } - } - - int ret = 0; - - LOG_I("sunneed is initializing..."); - - sunneed_init(); - - LOG_I("Acquired PIP: %s", pip.name); - - LOG_I("Loading devices..."); - if ((ret = sunneed_load_devices(devices)) != 0) { - LOG_E("Failed to load devices"); - ret = 1; - goto end; - } - - if ((ret = spawn_worker_threads()) != 0) { - LOG_E("Error occurred while spawning worker threads"); - ret = 1; - goto end; - } - - if ((ret = sunneed_listen()) != 0) { - LOG_E("sunneed listener encountered a fatal error. Exiting."); - ret = 1; - goto end; - } - -end: - return 0; -} -======= #include "sunneed_core.h" extern struct sunneed_device devices[MAX_DEVICES]; @@ -298,4 +149,3 @@ main(int argc, char *argv[]) { end: return 0; } ->>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 diff --git a/src/sunneed_core.h b/src/sunneed_core.h index beba6d0..fa6b7f6 100644 --- a/src/sunneed_core.h +++ b/src/sunneed_core.h @@ -1,47 +1,3 @@ -<<<<<<< HEAD -#ifndef _SUNNEED_CORE_H_ -#define _SUNNEED_CORE_H_ - -#include -#include -#include -#include -#include - -#include "log.h" -#include "shared/sunneed_pip_interface.h" -#include "sunneed.h" -#include "sunneed_listener.h" -#include "sunneed_proc.h" -#include "sunneed_loader.h" -#include "sunneed_device.h" - -#define _HELP_TEXT_HEAD \ - APP_NAME ": enforce power usage policies\n" \ - "\nUSAGE\n" \ - "%s OPTIONS\n" \ - "\nOPTIONS\n" \ - "\t-h --help Show this help.\n" -#define _HELP_TEXT_TAIL \ - "\n" - -#ifdef TESTING -#define HELP_TEXT _HELP_TEXT_HEAD \ - "\t-c --testcase-count Print out the number of runtime tests.\n" \ - "\t-t --run-test TEST Run a runtime test by given its numerical ID.\n" \ - _HELP_TEXT_TAIL -#else -#define HELP_TEXT _HELP_TEXT_HEAD _HELP_TEXT_TAIL -#endif - -#ifdef TESTING - -#define MAX_TESTS_PER_SUITE 64 - -#endif - -#endif -======= #ifndef _SUNNEED_CORE_H_ #define _SUNNEED_CORE_H_ @@ -85,4 +41,3 @@ #endif #endif ->>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 diff --git a/src/sunneed_listener.c b/src/sunneed_listener.c index 7ee2dec..2ddf855 100644 --- a/src/sunneed_listener.c +++ b/src/sunneed_listener.c @@ -1,4 +1,3 @@ -<<<<<<< HEAD #include "sunneed_listener.h" #include "protobuf/c/server.pb-c.h" @@ -598,363 +597,3 @@ sunneed_listen(void) { } -======= -#include "sunneed_listener.h" - -#include "protobuf/c/server.pb-c.h" - -#define SUB_RESPONSE_BUF_SZ 4096 - -extern struct sunneed_device devices[]; -extern struct sunneed_tenant tenants[]; -extern const char *locked_file_paths[]; - -/** - * Maps dummy paths (typically sent by clients during a read or write) to FDs pointing to the real device, held by - * sunneed. - */ -struct { - char *path; - int fd; -} dummy_path_fd_map[MAX_LOCKED_FILES] = { { NULL, 0 } }; - -static int -get_fd_from_dummy_path(char *path) { - for (int i = 0; i < MAX_LOCKED_FILES; i++) - if (dummy_path_fd_map[i].path && strncmp(dummy_path_fd_map[i].path, path, strlen(path)) == 0) - return dummy_path_fd_map[i].fd; - return -1; -} - -// TODO This is probably slow -- O(n) lookup for every request made. -static struct sunneed_tenant * -tenant_of_pipe(int pipe_id) { - for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) - if (nng_pipe_id(tenant_pipes[i].pipe) == pipe_id) - return tenant_pipes[i].tenant; - return NULL; -} - -// Get the PID of a pipe and use that to create a new sunneed tenant with that ID. -// TODO: This shouldn't always create a new tenant, since we want multiple processes -// mapped to one tenant. -static struct sunneed_tenant * -register_client(nng_pipe pipe) { - struct sunneed_tenant *tenant; - - // Get PID of pipe. - uint64_t pid_int; - SUNNEED_NNG_TRY(nng_pipe_get_uint64, != 0, pipe, NNG_OPT_IPC_PEER_PID, &pid_int); - pid_t pid = (pid_t)pid_int; - - if ((tenant = sunneed_tenant_register(pid)) == NULL) { - LOG_E("Failed to initialize tenant from PID %d", pid); - return NULL; - } - - for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) { - if (tenant_pipes[i].tenant == NULL) { - tenant_pipes[i].tenant = tenant; - tenant_pipes[i].pipe = pipe; - break; - } - } - - return tenant; -} - -// The `serve_*` methods take a `sub_resp_buf` parameter. This is a pointer to a buffer in which the client -// can store their sub-response (the message in the oneof field of the SunneedResponse). Example: -// -// GetDeviceHandleResponse *sub_resp = sub_resp_buf; -// *sub_resp = (GetDeviceHandleResponse)GET_DEVICE_HANDLE_RESPONSE__INIT; -// -// This example writes the initializer for the `GetDeviceHandleResponse` to the address pointed to by -// `sub_resp_buf`. -// The rationale for this whole process comes next: once the `serve_*` function returns, its sub-response -// data is contained within the buffer, to which a pointer is in scope in the main request listening -// loop. - -// Create a mapping between this pipe and a sunneed tenant. -// TODO Currently, this just spawns a new tenant for each different pipe. We want tenants to be able to have multiple -// pipes to sunneed open. -static int -serve_register_client(SunneedResponse *resp, void *sub_resp_buf, nng_pipe pipe) { - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_REGISTER_CLIENT; - RegisterClientResponse *sub_resp = sub_resp_buf; - *sub_resp = (RegisterClientResponse)REGISTER_CLIENT_RESPONSE__INIT; - resp->register_client = sub_resp; - - struct sunneed_tenant *tenant = NULL; - - // Register as a new client. - if ((tenant = register_client(pipe)) == NULL) { - LOG_W("Registration failed for pipe %d", pipe.id); - return 1; - } - - // Construct the list of locked file paths to send to the client. - size_t locked_paths_len = 0; - for (locked_paths_len = 0; locked_file_paths[locked_paths_len] != NULL; locked_paths_len++) ; - sub_resp->n_locked_paths = locked_paths_len; - sub_resp->locked_paths = malloc(sizeof(char *) * sub_resp->n_locked_paths); - for (size_t i = 0; i < sub_resp->n_locked_paths; i++) - sub_resp->locked_paths[i] = (char *)locked_file_paths[i]; - - LOG_D("Registered pipe %d with tenant %d", pipe.id, tenant->id); - - return 0; -} - -static int -serve_unregister_client(SunneedResponse *resp, void *sub_resp_buf, nng_pipe pipe, struct sunneed_tenant *tenant) { - int retval; - - LOG_D("Unregistering tenant %d", tenant->id); - - // Find the entry in the tenant pipe mappings. - bool cleared = false; - for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) - if (tenant_pipes[i].pipe.id == pipe.id) { - LOG_D("Removing mapping from pipe %d to tenant %d", pipe.id, tenant->id); - tenant_pipes[i].tenant = NULL; - tenant_pipes[i].pipe = (nng_pipe)NNG_PIPE_INITIALIZER; - cleared = true; - break; - } - - if (!cleared) { - LOG_E("No mapping cleared when unregistering pipe %d; something is wrong with the pipe->tenant table", pipe.id); - return 1; - } - - if ((retval = sunneed_tenant_unregister(tenant)) != 0) - // TODO Handle (follow the pattern of the `register_client` stuff by making a secondary `unregister` function - // that handles interacting with tenants). - return 1; - - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; - GenericResponse *sub_resp = sub_resp_buf; - *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; - resp->generic = sub_resp; - - return 0; -} - -static int -serve_open_file( - SunneedResponse *resp, - void *sub_resp_buf, - __attribute__((unused)) struct sunneed_tenant *tenant, - OpenFileRequest *request) { - LOG_D("Got request to open file '%s'", request->path); - - OpenFileResponse *sub_resp = sub_resp_buf; - *sub_resp = (OpenFileResponse)OPEN_FILE_RESPONSE__INIT; - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_OPEN_FILE; - resp->open_file = sub_resp; - - // TODO Take flags!! - - - struct sunneed_device *locker; - if ((locker = sunneed_device_file_locker(request->path)) != NULL) { - // TODO Wait for availability, perform power calcs, etc. - - // Open the real file and save its FD. - int real_fd = open(request->path, request->flags, request->mode); - - if (real_fd == -1) { - LOG_E("Failed to open file '%s'", request->path); - return 1; - } - - char *dummypath = sunneed_device_get_dummy_file(request->path); - - int i; - for (i = 0; i < MAX_LOCKED_FILES; i++) { - // Find open slot. - if (dummy_path_fd_map[i].path == NULL) { - dummy_path_fd_map[i].path = malloc(strlen(dummypath) + 1); - strncpy(dummy_path_fd_map[i].path, dummypath, strlen(dummypath) + 1); - dummy_path_fd_map[i].fd = real_fd; - - LOG_I("Opened locked path '%s' as '%s' (FD %d)", request->path, dummypath, real_fd); - - break; - } - } - if (i == MAX_LOCKED_FILES) { - // Theoretically this should never happen (since MAX_LOCKED_FILES also bounds the number of possible locked - // paths) but good to check. - LOG_E("No slots remaining in dummy_path_fd_map"); - return 1; - } - - // TODO Free this - sub_resp->path = malloc(strlen(dummypath) + 1); - strncpy(sub_resp->path, dummypath, strlen(dummypath) + 1); - } else { - // They requested a non-dummy file. - return 1; - } - - return 0; -} - -static int -serve_write( - SunneedResponse *resp, - void *sub_resp_buf, - struct sunneed_tenant *tenant, - WriteRequest *request) { - LOG_D("Got request from %d to write %ld bytes to '%s' (real file FD %d)", tenant->id, request->data.len, request->dummy_path, get_fd_from_dummy_path(request->dummy_path)); - - WriteResponse *sub_resp = sub_resp_buf; - *sub_resp = (WriteResponse)WRITE_RESPONSE__INIT; - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_CALL_WRITE; - resp->call_write = sub_resp; - - - #ifdef LOG_PWR - char buf[1024]; - char real_path[1024]; - - sprintf(buf, "/proc/self/fd/%d", get_fd_from_dummy_path(request->dummy_path)); - memset(real_path, 0, sizeof(real_path)); - readlink(buf, real_path, sizeof(real_path)); - printf("real path: %s\n", real_path); - if (strcmp(real_path, "/dev/stepper") == 0) { - LOG_D("writing to stepper motor driver"); - } - #endif - - // Perform the write. - ssize_t bytes_written; - if ((bytes_written = write(get_fd_from_dummy_path(request->dummy_path), request->data.data, request->data.len)) - < 0) { - int errno_val = errno; - - sub_resp->errno_value = errno_val; - sub_resp->bytes_written = bytes_written; - - LOG_E("`write` for client %d failed with: %s", tenant->id, strerror(errno_val)); - - return 1; - } - - sub_resp->bytes_written = bytes_written; - sub_resp->errno_value = 0; - - return 0; -} - -static void -report_nng_error(const char *func, int rv) { - LOG_E("nng error: (%s) %s", func, nng_strerror(rv)); -} - -int -sunneed_listen(void) { - SUNNEED_NNG_SET_ERROR_REPORT_FUNC(report_nng_error); - - // Initialize client states. - for (int i = 0; i < MAX_TENANTS; i++) { - tenant_pipes[i] = (struct tenant_pipe){.tenant = NULL, - // TODO Why do I need to cast this... - .pipe = (nng_pipe)NNG_PIPE_INITIALIZER}; - } - - nng_socket sock; - - LOG_I("Starting listener loop..."); - - // Make a socket and attach it to the sunneed URL. - SUNNEED_NNG_TRY_RET(nng_rep0_open, != 0, &sock); - SUNNEED_NNG_TRY_RET(nng_listen, < 0, sock, SUNNEED_LISTENER_URL, NULL, 0); - - // Buffer for `serve_` methods to write their sub-response to. - void *sub_resp_buf = malloc(SUB_RESPONSE_BUF_SZ); - // TODO Check malloc. - - // Await messages. - for (;;) { - nng_msg *msg; - - SUNNEED_NNG_TRY_RET(nng_recvmsg, != 0, sock, &msg, NNG_FLAG_ALLOC); - - // TODO They claim nng_msg_get_pipe() returns -1 on error, but its return type is nng_pipe, which can't - // be compared to an integer. - nng_pipe pipe = nng_msg_get_pipe(msg); - - // Get contents of message. - size_t msg_len = nng_msg_len(msg); - -// SUNNEED_NNG_MSG_LEN_FIX(msg_len); - - SunneedRequest *request = sunneed_request__unpack(NULL, msg_len, nng_msg_body(msg)); - - if (request == NULL) { - LOG_W("Received null request from %d", pipe.id); - goto end; - } - - struct sunneed_tenant *tenant = NULL; - - // Find the pipe's associated tenant. If we can't find it, we error out unless the message is of type REGISTER_CLIENT. - if ((tenant = tenant_of_pipe(pipe.id)) == NULL && request->message_type_case != SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT) { - // This client has not registered! - LOG_W("Received message from %d, who is not registered.", pipe.id); - goto end; - } - - // Begin setting up our response. - SunneedResponse resp = SUNNEED_RESPONSE__INIT; - int ret = -1; - - switch (request->message_type_case) { - case SUNNEED_REQUEST__MESSAGE_TYPE__NOT_SET: - LOG_W("Request from pipe %d has no message type set.", pipe.id); - ret = -1; - break; - case SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT: - ret = serve_register_client(&resp, sub_resp_buf, pipe); - break; - case SUNNEED_REQUEST__MESSAGE_TYPE_UNREGISTER_CLIENT: - ret = serve_unregister_client(&resp, sub_resp_buf, pipe, tenant); - break; - case SUNNEED_REQUEST__MESSAGE_TYPE_OPEN_FILE: - ret = serve_open_file(&resp, sub_resp_buf, tenant, request->open_file); - break; - case SUNNEED_REQUEST__MESSAGE_TYPE_WRITE: - ret = serve_write(&resp, sub_resp_buf, tenant, request->write); - break; - default: - LOG_W("Received request with invalid type %d", request->message_type_case); - ret = -1; - break; - } - - resp.status = ret; - - // Create and send the response message. - nng_msg *resp_msg; - int resp_len = sunneed_response__get_packed_size(&resp); - void *resp_buf = malloc(resp_len); - sunneed_response__pack(&resp, resp_buf); - - SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &resp_msg, 0); - // SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &resp_msg, resp_len); - SUNNEED_NNG_TRY(nng_msg_append, != 0, resp_msg, resp_buf, resp_len); - // SUNNEED_NNG_TRY(nng_msg_insert, != 0, resp_msg, resp_buf, resp_len); - SUNNEED_NNG_TRY(nng_sendmsg, != 0, sock, resp_msg, 0); - - end: - sunneed_request__free_unpacked(request, NULL); - - nng_msg_free(msg); - } - - free(sub_resp_buf); -} ->>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 diff --git a/src/sunneed_listener.h b/src/sunneed_listener.h index f050203..7f11fa9 100644 --- a/src/sunneed_listener.h +++ b/src/sunneed_listener.h @@ -1,4 +1,3 @@ -<<<<<<< HEAD #ifndef _SUNNEED_LISTENER_H_ #define _SUNNEED_LISTENER_H_ @@ -32,34 +31,3 @@ int sunneed_listen(void); #endif -======= -#ifndef _SUNNEED_LISTENER_H_ -#define _SUNNEED_LISTENER_H_ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "log.h" -#include "shared/sunneed_ipc.h" -#include "sunneed.h" -#include "sunneed_power.h" -#include "sunneed_proc.h" - -#define SUNNEED_MESSAGE_DEFAULT_BODY_SZ 64 -#define SUNNEED_DEVICE_PATH_MAX_LEN 64 - -int -sunneed_listen(void); - -#endif ->>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 diff --git a/src/sunneed_power.c b/src/sunneed_power.c index 9ecd8f6..79e73e6 100644 --- a/src/sunneed_power.c +++ b/src/sunneed_power.c @@ -1,4 +1,3 @@ -<<<<<<< HEAD #include "sunneed_power.h" extern struct sunneed_tenant tenants[]; @@ -57,7 +56,11 @@ sunneed_quantum_begin(void) { // Set quantum metadata. current_quantum.id++; + +#ifdef LOG_PWR current_quantum.present_power = present_power(); +#endif + timespec_get(¤t_quantum.begin_time, TIME_UTC); // Reset power events array. @@ -160,156 +163,3 @@ sunneed_quantum_worker(__attribute__((unused)) void *args) { LOG_E("Error with quantum; quantum thread stopping"); return NULL; } -======= -#include "sunneed_power.h" - -extern struct sunneed_tenant tenants[]; - -static struct { - // Index. - int id; - - // Power at the start of the quantum. - int present_power; - - // Timestamp for the start of the quantum. - struct timespec begin_time; - - // Whether events can be recorded to this quantum. - bool is_active; - - // If a power event has occurred in this quantum. - bool has_power_event; -} current_quantum = {-1, 0.0, {0}, false, false}; - -int -sunneed_record_power_usage_event(struct sunneed_power_usage_event ev) { - if (!current_quantum.is_active) { - LOG_E("Cannot record a power event outside of an active quantum"); - return 1; - } - - struct sunneed_power_usage_event *cur = power_usage_evs; - if (cur == NULL) { - LOG_E("Power usage events head node is unallocated!"); - return 1; - } - - // Find tail of events list. - while (cur->next != NULL) { - cur = cur->next; - } - - // Copy given event to heap and attach to end of events list. - cur->next = malloc(sizeof(struct sunneed_power_usage_event)); - if (!cur->next) { - LOG_E("Failed to allocate space for power usage event."); - return 1; - } - - *cur->next = ev; - - current_quantum.has_power_event = true; - - return 0; -} - -int -sunneed_quantum_begin(void) { - LOG_D("Begin start procedure for quantum %d", current_quantum.id + 1); - - // Set quantum metadata. - current_quantum.id++; - current_quantum.present_power = present_power(); - timespec_get(¤t_quantum.begin_time, TIME_UTC); - - // Reset power events array. - struct sunneed_power_usage_event *cur = power_usage_evs; - while (cur != NULL) { - struct sunneed_power_usage_event *next = cur->next; - free(cur); - cur = next; - } - - power_usage_evs = (struct sunneed_power_usage_event*) malloc(sizeof(struct sunneed_power_usage_event)); - - if (!power_usage_evs) { - LOG_E("Failed to allocate space for power usage events!"); - return 1; - } - power_usage_evs->next = NULL; - - LOG_I("Started quantum %d", current_quantum.id); - current_quantum.is_active = true; - - return 0; -} - -int -sunneed_quantum_end(void) { - LOG_I("Ending quantum %d", current_quantum.id); - - current_quantum.is_active = false; - - double power_consumed[MAX_TENANTS] = {0.0}; - - // Add up power used in this quantum by each tenant. - struct sunneed_power_usage_event *ev = power_usage_evs; - while (ev != NULL) { - if (!current_quantum.has_power_event) - // No power events to add to tenant. - break; - - if (ev->ev.device == NULL) { - // This is a CPU usage digest. - // TODO Check CPU usage lol. - power_consumed[ev->ev.tenant->id] += 0; - } else { - // TODO Get power from event. - power_consumed[ev->ev.tenant->id] += 0; - } - ev = ev->next; - } - - float unscaled_proportions[MAX_TENANTS] = {0.0}; - float unscaled_sum = 0.0; - - // Update power proportions for tenants. - - // First, get the percentage of their given power that each tenant used. - for (int i = 0; i < MAX_TENANTS; i++) { - unscaled_proportions[i] - = 1.0 - power_consumed[i] / (current_quantum.present_power * tenants[i].power_proportion); - unscaled_sum += unscaled_proportions[i]; - } - - // Multiply the total percentage by a scaling factor such that the sum of new proportions adds up to 1. - float scale_factor = 1.0 / unscaled_sum; - for (int i = 0; i < MAX_TENANTS; i++) { - tenants[i].power_proportion = unscaled_proportions[i] * scale_factor; - } - - LOG_D("Finished ending quantum %d", current_quantum.id); - - return 0; -} - -sunneed_worker_thread_result_t -sunneed_quantum_worker(__attribute__((unused)) void *args) { - int ret; - power_usage_evs = NULL; - while (true) { - if ((ret = sunneed_quantum_begin()) != 0) { - goto end; - } - usleep(QUANTUM_DURATION_MS * 1000); - if ((ret = sunneed_quantum_end()) != 0) { - goto end; - } - } - -end: - LOG_E("Error with quantum; quantum thread stopping"); - return NULL; -} ->>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 diff --git a/src/sunneed_power.h b/src/sunneed_power.h index f992804..2f75b0c 100644 --- a/src/sunneed_power.h +++ b/src/sunneed_power.h @@ -1,4 +1,3 @@ -<<<<<<< HEAD #ifndef _SUNNEED_POWER_H_ #define _SUNNEED_POWER_H_ @@ -58,58 +57,3 @@ struct sunneed_power_usage_event *power_usage_evs; struct sunneed_device devices[MAX_DEVICES]; #endif -======= -#ifndef _SUNNEED_POWER_H_ -#define _SUNNEED_POWER_H_ - -#include -#include -#include -#include -#include - -#include "log.h" -#include "shared/sunneed_pip_interface.h" -#include "sunneed.h" -#include "sunneed_device.h" -#include "sunneed_proc.h" - -#define MAX_DEVICES 64 - -#define QUANTUMS_RINGBUF_SZ 16 - -// TODO This is waaaaaaaaaaaaaaaaaaaaay too big. -#define QUANTUM_DURATION_MS 5000 - -struct sunneed_power_usage_event { - struct { - // The moment the power event occurred. - struct timespec timestamp; - - struct sunneed_tenant *tenant; - - // If NULL, then the power event is a CPU usage digest. - struct sunneed_device *device; - - // Unused for now, can configure parameters of the device interaction. - void *args; - } ev; - struct sunneed_power_usage_event *next; -}; - -int -sunneed_record_power_usage_event(struct sunneed_power_usage_event ev); -int -sunneed_quantum_begin(void); -int -sunneed_quantum_end(void); - -sunneed_worker_thread_result_t -sunneed_quantum_worker(void *args); - -struct sunneed_power_usage_event *power_usage_evs; - -struct sunneed_device devices[MAX_DEVICES]; - -#endif ->>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 diff --git a/src/sunneed_proc.c b/src/sunneed_proc.c index 96a306f..e0a4003 100644 --- a/src/sunneed_proc.c +++ b/src/sunneed_proc.c @@ -1,124 +1,3 @@ -<<<<<<< HEAD -#include "sunneed_proc.h" - -struct sunneed_tenant tenants[MAX_TENANTS]; - -int -sunneed_init_tenants(void) { - for (int i = 0; i < MAX_TENANTS; i++) { - tenants[i] = (struct sunneed_tenant){.id = i, .pid = 0, .power_proportion = 0.0, .is_active = false}; - } - - return 0; -} - -// Find an unused spot for a tenant and register ourself there. -struct sunneed_tenant * -sunneed_tenant_register(pid_t pid) { - struct sunneed_tenant *tenant = NULL; - for (int i = 0; i < MAX_TENANTS; i++) { - if (!tenants[i].is_active) { - tenant = &tenants[i]; - break; - } - } - - if (tenant == NULL) { - LOG_E("Sorry PID %d, can't spawn any more tenants!", pid); - return NULL; - } - - tenant->pid = pid; - tenant->is_active = true; - - return tenant; -} - -int -sunneed_tenant_unregister(struct sunneed_tenant *tenant) { - if (tenant == NULL || !tenant->is_active) { - LOG_W("Cannot deactivate an inactive tenant"); - return 1; - } - - tenant->is_active = false; - - return 0; -} - -unsigned int -sunneed_get_num_tenants(void) { - unsigned int num_tenants = 0; - - for (int i = 0; i < MAX_TENANTS; i++) { - if (tenants[i].is_active) - num_tenants++; - } - - return num_tenants; -} - -int -sunneed_update_tenant_cpu_usage(void) { - // TODO Verify that the pipe and PID match up. It is entirely possible for the tenant process to die and a new - // process with the same PID as the tenant's to start up. - FILE *file; - char filepath[FILENAME_MAX] = "/proc/stat"; - - file = fopen(filepath, "r"); - fscanf(file, "%*s %llu %llu %llu %llu", &cpu_usage.user, &cpu_usage.nice, &cpu_usage.sys, &cpu_usage.idle); - fclose(file); - - for (struct sunneed_tenant *tenant = tenants; tenant < tenants + MAX_TENANTS; tenant++) { - if (!tenant->is_active) - continue; - - snprintf(filepath, FILENAME_MAX, "/proc/%d/stat", tenant->pid); - if (access(filepath, F_OK) != 0) { - LOG_E("Unable to find procfs file for PID %d; most likely a tenant ended but forgot to tell us", - tenant->pid); - continue; - } - - file = fopen(filepath, "r"); - // Read CPU consumption from this tenant's PID. - fscanf(file, - "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u" // 13 things we don't care about - " %llu %llu" // usertime, systemtime - " %*d %*d %*d %*d %*d %*d %*u %*u", // 8 things we don't care about - &cpu_usage.tenants[tenant->id].user, &cpu_usage.tenants[tenant->id].sys); - fclose(file); - } - - return 0; -} - -int -sunneed_get_tenant_cpu_usage(sunneed_tenant_id_t tenant_id) { - if (!tenants[tenant_id].is_active) { - LOG_E("Attempt to get CPU usage of inactive tenant %d", tenant_id); - return -1; - } - - // TODO Shit - - return 0; -} - -sunneed_worker_thread_result_t -sunneed_proc_monitor(__attribute__((unused)) void *args) { - int ret; - while (true) { - LOG_D("Updating process CPU usage"); - if ((ret = sunneed_update_tenant_cpu_usage()) != 0) { - LOG_E("Error updating CPU usage; monitor thread stopping"); - return NULL; - } - - sleep(5); - } -} -======= #include "sunneed_proc.h" struct sunneed_tenant tenants[MAX_TENANTS]; @@ -345,4 +224,3 @@ sunneed_camera_driver(__attribute__((unused)) void *args) { return NULL; } } ->>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 diff --git a/src/sunneed_proc.h b/src/sunneed_proc.h index 57b375b..b5614bb 100644 --- a/src/sunneed_proc.h +++ b/src/sunneed_proc.h @@ -1,57 +1,3 @@ -<<<<<<< HEAD -#ifndef _SUNNEED_PROC_H_ -#define _SUNNEED_PROC_H_ - -#include -#include - -#include "log.h" -#include "sunneed.h" - -#define MAX_TENANTS 2 - -typedef unsigned int sunneed_tenant_id_t; - -// TODO Separate tenants from processes. -struct sunneed_tenant { - sunneed_tenant_id_t id; - pid_t pid; - float power_proportion; - bool is_active; -}; - -struct tenant_cpu_usage { - unsigned long long user, sys; -}; - -struct { - unsigned long long user, nice, sys, idle; - struct tenant_cpu_usage tenants[MAX_TENANTS]; -} cpu_usage; - -int -sunneed_update_tenant_cpu_usage(void); - -int -sunneed_init_tenants(void); - -struct sunneed_tenant * -sunneed_tenant_register(pid_t pid); - -int -sunneed_tenant_unregister(struct sunneed_tenant *tenant); - -unsigned int -sunneed_get_num_tenants(void); - -int -sunneed_get_tenant_cpu_usage(sunneed_tenant_id_t tenant_id); - -sunneed_worker_thread_result_t -sunneed_proc_monitor(void *args); - -#endif -======= #ifndef _SUNNEED_PROC_H_ #define _SUNNEED_PROC_H_ @@ -118,4 +64,3 @@ sunneed_worker_thread_result_t sunneed_camera_driver(void *args); #endif ->>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 From 5cd222e7bcdcc11a71dc9f07ce43a73deaa96f0d Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Wed, 28 Jul 2021 13:33:37 -0400 Subject: [PATCH 37/42] Fix Merge Conflicts with master and commit working result for review --- Makefile | 2 +- src/client/sunneed_client.c | 2 +- src/log.h | 4 ++-- src/pip/bq27441.c | 9 ++++++--- src/shared/sunneed_pip_interface.h | 2 +- src/sunneed_listener.c | 25 ++++++------------------- src/sunneed_power.h | 5 ++++- 7 files changed, 21 insertions(+), 28 deletions(-) diff --git a/Makefile b/Makefile index 46188fc..98b98ee 100644 --- a/Makefile +++ b/Makefile @@ -75,7 +75,7 @@ log_pwr: pre-all main_pwr_data overlay util main: ext protobuf pip devices $(call section_title,main executable) - $(CC) $(CFLAGS) -DTESTING $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) + $(CC) $(CFLAGS) -DTESTING -ULOG_PWR $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) main_pwr_data: ext protobuf pip devices $(call section_title, main executable) diff --git a/src/client/sunneed_client.c b/src/client/sunneed_client.c index e0fd20f..dfa1111 100644 --- a/src/client/sunneed_client.c +++ b/src/client/sunneed_client.c @@ -231,7 +231,7 @@ sunneed_client_socket(int domain, int type, int protocol) */ if(domain != AF_INET) { - FATAL(-1, "must be IPv4 socket\n"); + return -1; } if(type != SOCK_DGRAM) diff --git a/src/log.h b/src/log.h index f56495d..39fede9 100644 --- a/src/log.h +++ b/src/log.h @@ -17,7 +17,7 @@ FILE *logfile, *logfile_pwr; #define LOGL_ERROR "E\e[0;31m" -#define LOG_PWR(LEVEL, MESSAGE, ...) \ +#define LOG_PWR_EVENT(LEVEL, MESSAGE, ...) \ { \ FILE *_logfile = logfile_pwr; \ if (logfile_pwr) { \ @@ -48,5 +48,5 @@ FILE *logfile, *logfile_pwr; #define LOG_I(MESSAGE, ...) LOG(LOGL_INFO, MESSAGE, ##__VA_ARGS__); #define LOG_W(MESSAGE, ...) LOG(LOGL_WARN, MESSAGE, ##__VA_ARGS__); #define LOG_E(MESSAGE, ...) LOG(LOGL_ERROR, MESSAGE, ##__VA_ARGS__); -#define LOG_P(MESSAGE, ...) LOG_PWR(LOGL_INFO, MESSAGE, ##__VA_ARGS__); +#define LOG_P(MESSAGE, ...) LOG_PWR_EVENT(LOGL_INFO, MESSAGE, ##__VA_ARGS__); #endif diff --git a/src/pip/bq27441.c b/src/pip/bq27441.c index 8ee6fd6..ad1a923 100644 --- a/src/pip/bq27441.c +++ b/src/pip/bq27441.c @@ -6,10 +6,13 @@ pip_info() { return (struct sunneed_pip){"bq27441", 1000, 50}; } -#ifdef LOG_PWR -unsigned int +signed int present_power() { + +#ifdef log_pwr bq27441_init(1); return bq27441_nominal_avail_cap(); +#endif + + return 0; } -#ifdef LOG_PWR \ No newline at end of file diff --git a/src/shared/sunneed_pip_interface.h b/src/shared/sunneed_pip_interface.h index b6c01dd..ae2a04c 100644 --- a/src/shared/sunneed_pip_interface.h +++ b/src/shared/sunneed_pip_interface.h @@ -30,7 +30,7 @@ struct sunneed_pip { struct sunneed_pip pip_info(); -unsigned int +signed int present_power(); #endif diff --git a/src/sunneed_listener.c b/src/sunneed_listener.c index 2ddf855..2ae77c6 100644 --- a/src/sunneed_listener.c +++ b/src/sunneed_listener.c @@ -31,16 +31,6 @@ get_fd_from_dummy_path(char *path) { return -1; } -// Control flow: -// When a new pipe connects, we use this struct to make a mapping of its pipe ID to a tenant. Then, when further -// requests are made, the pipe ID is used to identify a tenant to the request. -// The client will have to send some notification in order to unregister; I don't think we can tell if a pipe -// closed. -struct tenant_pipe { - struct sunneed_tenant *tenant; - nng_pipe pipe; -} tenant_pipes[SUNNEED_MAX_IPC_CLIENTS]; - // TODO This is probably slow -- O(n) lookup for every request made. static struct sunneed_tenant * tenant_of_pipe(int pipe_id) { @@ -387,11 +377,9 @@ serve_send(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant *ten if(last_send == 0) { last_send = clock(); - printf("last send = %ld\n", last_send); LOG_P ("%f ", (((double)(last_send))/CLOCKS_PER_SEC)); LOG_D ("first send %f ", (((double) (last_send))/CLOCKS_PER_SEC)); }else{ - printf("clock() - last_send = %ld\n", clock()-last_send); time_since_send = (double)(clock() - last_send) / (double)CLOCKS_PER_SEC; LOG_P("%f ", time_since_send); LOG_D("%f since last send", time_since_send); @@ -413,13 +401,12 @@ serve_send(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant *ten #ifdef LOG_PWR //log power change from send and reset last_capacity and last_send - curr_capacity = present_power(); + curr_read = present_power(); - double change = ((double) last_capacity - curr_capacity) - (time_since_send * PASSIVE_PWR_PER_SEC); - LOG_P("%f\n", change); - LOG_D("%f\n", change); + LOG_P("%d\n", curr_read); + LOG_D("%d\n", curr_read); - last_capacity = curr_capacity; + last_read = curr_read; last_send = clock(); #endif @@ -442,7 +429,7 @@ sunneed_listen(void) { SUNNEED_NNG_SET_ERROR_REPORT_FUNC(report_nng_error); #ifdef LOG_PWR - last_capacity = present_power(); + last_read = present_power(); int capacity_change; last_send = 0; #endif @@ -576,7 +563,7 @@ sunneed_listen(void) { sunneed_response__pack(&resp, resp_buf); SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &resp_msg, 0); - SUNNEED_NNG_TRY(nng_msg_insert, != 0, resp_msg, resp_buf, resp_len); + SUNNEED_NNG_TRY(nng_msg_append, != 0, resp_msg, resp_buf, resp_len); SUNNEED_NNG_TRY(nng_sendmsg, != 0, sock, resp_msg, 0); if(resp.message_type_case == SUNNEED_RESPONSE__MESSAGE_TYPE_REGISTER_CLIENT) diff --git a/src/sunneed_power.h b/src/sunneed_power.h index 2f75b0c..a59ec88 100644 --- a/src/sunneed_power.h +++ b/src/sunneed_power.h @@ -20,9 +20,12 @@ #define PASSIVE_PWR_PER_MIN PASSIVE_PWR_PER_SEC * 60 #ifdef LOG_PWR -#define REQUESTS_PER_PWR_LOG 20 +#define REQS_PER_LOG 10 +int16_t last_read, curr_read, last_capacity, curr_capacity, reqs_since_last_log; +clock_t last_send, time_since_send; #endif + // TODO This is waaaaaaaaaaaaaaaaaaaaay too big. #define QUANTUM_DURATION_MS 5000 From b2fdaae9351ad40c5c9b97928be6d85894e4529e Mon Sep 17 00:00:00 2001 From: RyanFisk2 Date: Fri, 13 Aug 2021 14:16:36 -0400 Subject: [PATCH 38/42] Add battery state enum to sunneed proc monitor --- Makefile | 398 +++++++++++++++---------------- run_PyDriver.c | 106 ++++----- src/device/camera.c | 54 ++--- src/device/stepperMotor.c | 54 ++--- src/device/stepper_motor.c | 46 ++-- src/log.h | 104 ++++---- src/pip/bq27441.c | 36 +-- src/sunneed.h | 48 ++-- src/sunneed_core.c | 302 +++++++++++------------ src/sunneed_core.h | 86 +++---- src/sunneed_proc.c | 475 +++++++++++++++++++------------------ src/sunneed_proc.h | 141 +++++------ src/util/stepper_test.c | 44 ++-- 13 files changed, 963 insertions(+), 931 deletions(-) diff --git a/Makefile b/Makefile index 98b98ee..35bd648 100644 --- a/Makefile +++ b/Makefile @@ -1,199 +1,199 @@ -# Builds the main sunneed executable. - -ifeq ($(origin CC),default) - export CC = gcc -endif - -CFLAGS ?= -Wall -Wextra -g -PROTOC ?= protoc-c - -SUNNEED_BUILD_TYPE ?= devel -SUNNEED_BUILD_PIP ?= bq27441 -SUNNEED_BUILD_OUT_DIR ?= build -SUNNEED_BUILD_BIN_FILE ?= sunneed -SUNNEED_BUILD_CLIENT_LIB_NAME ?= libsunneedclient -SUNNEED_BUILD_OVERLAY_LIB_NAME ?= sunneed_overlay - -export SOURCE_FORMATTER = clang-format -style=file -i - -export cflags_deps = -I$(PWD)/$(ext_dir)/nng/include -L$(PWD)/$(ext_dir)/nng/build -L$(PWD)/$(ext_dir)/libbq27441 -lnng -lpthread -ldl -lprotobuf-c -latomic -I$(PWD)/$(ext_dir)/libbq27441 -lbq27441 -li2c - -ifeq ($(SUNNEED_BUILD_TYPE),devel) - util_cflags = -Wl,-rpath,$(CURDIR)/$(clientlib_out_dir) -endif - -src_dir = src -sources = $(wildcard $(src_dir)/*.c) - -ext_dir = ext - -out_dir = $(SUNNEED_BUILD_OUT_DIR) -bin_file = $(SUNNEED_BUILD_BIN_FILE) - -pip_out_dir = $(out_dir) -pip_obj = $(pip_out_dir)/pip.o -pip_name = $(SUNNEED_BUILD_PIP) - -clientlib_sources = $(wildcard $(src_dir)/client/*.c) -clientlib_out_dir = $(out_dir)/client -clientlib_obj = $(clientlib_out_dir)/$(SUNNEED_BUILD_CLIENT_LIB_NAME).so - -overlay_sources = $(wildcard $(src_dir)/overlay/*.c) -overlay_out_dir = $(out_dir) -overlay_obj = $(overlay_out_dir)/$(SUNNEED_BUILD_OVERLAY_LIB_NAME).so -overlay_testing_obj = $(patsubst %.so,%_testing.so,$(overlay_obj)) -overlay_runner = $(out_dir)/run-with-overlay - -protobuf_dir = $(src_dir)/protobuf -protobuf_sources = $(wildcard $(protobuf_dir)/*.proto) -protobuf_out_files = $(foreach src,$(protobuf_sources),$(subst !!!, ,$(join $(src:.proto=.pb-c.c!!!),$(src:.proto=.pb-c.h)))) -protobuf_out_dir = $(src_dir)/protobuf/c -protobuf_out_sources = $(wildcard $(protobuf_out_dir)/*.c) - -export test_home = test -export test_runner_name = run-tests -test_runner = $(test_home)/$(test_runner_name) -runtime_tests_runner = ./runtime_tests - -device_objs = $(patsubst %.c, %.o, $(wildcard $(src_dir)/device/*.c)) -util_objs = $(patsubst %.c, %.o, $(wildcard $(src_dir)/util/*.c)) - -all: pre-all main overlay util - -run_valgrind: pre-all main overlay util - valgrind ./build/sunneed - -run_ASAN: pre-all main_ASAN overlay util - ./build/sunneed - - -pre-all: - @echo "Starting all build..." - -log_pwr: pre-all main_pwr_data overlay util - - -main: ext protobuf pip devices - $(call section_title,main executable) - $(CC) $(CFLAGS) -DTESTING -ULOG_PWR $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) - -main_pwr_data: ext protobuf pip devices - $(call section_title, main executable) - $(CC) $(CFLAGS) -DTESTING -DLOG_PWR $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) - -main_ASAN: ext protobuf pip devices - $(call section_title,main executable) - $(CC) -fsanitize=address $(CFLAGS) -DTESTING $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) - -pip: pre-pip $(src_dir)/pip/$(pip_name).c - $(CC) $(CFLAGS) -o $(pip_obj) -c $(src_dir)/pip/$(pip_name).c -pre-pip: - $(call section_title,pip) - -devices: pre-devices ext $(device_objs) -pre-devices: - $(call section_title,devices) - -util: clientlib pre-util $(util_objs) -pre-util: - $(call section_title,utils) - -$(src_dir)/util/%.o: $(src_dir)/util/%.c - $(CC) $(CFLAGS) -o $(patsubst $(src_dir)/util/%.o, $(out_dir)/%, $@) $^ $(protobuf_out_sources) -L$(clientlib_out_dir) -lsunneedclient $(util_cflags) $(cflags_deps) - -$(src_dir)/client/%.o: $(src_dir)/client/%.c - $(CC) $(CFLAGS) -o $(patsubst $(src_dir)/util/%.o, $(out_dir)/%, $@) $^ $(protobuf_out_sources) $(cflags_deps) - -$(src_dir)/device/%.o: $(src_dir)/device/%.c - @if [ ! -d "$(out_dir)/device" ]; then mkdir "$(out_dir)/device"; fi - $(CC) $(CFLAGS) -g -shared -o $(patsubst $(src_dir)/device/%.o, $(out_dir)/device/%.so, $@) -fPIC $^ $(cflags_deps) - -protobuf: pre-protobuf $(protobuf_out_files) - @rm -rf "$(protobuf_out_dir)" && mkdir "$(protobuf_out_dir)" - mv $(protobuf_out_files) $(protobuf_out_dir) -pre-protobuf: - $(call section_title,protobuf) - -$(protobuf_out_files): $(protobuf_sources) - $(MAKE) --no-print-directory -C $(protobuf_dir) - -clientlib: ext - $(call section_title,client library) - @if [ ! -d "$(out_dir)/client" ]; then mkdir "$(out_dir)/client"; fi - $(CC) $(CFLAGS) -c -fPIC -o $(out_dir)/client/clientlib.o $(clientlib_sources) $(cflags_deps) - $(CC) $(CFLAGS) -shared -o $(clientlib_obj) $(out_dir)/client/clientlib.o - -# Run the runtime tests -runtime_test: main - $(runtime_tests_runner) - -test: tests - $(test_runner) - -tests: - make -C $(test_home) - -# Note that we compile two overlay libraries. One is a tester, which contains additional output meant for -# debugging/testing purposes. -overlay: clientlib - $(call section_title,overlay) - $(CC) $(CFLAGS) -g -fPIC -shared $(overlay_sources) -o $(overlay_obj) $(cflags_deps) - $(CC) $(CFLAGS) -g -fPIC -shared -DTESTING $(overlay_sources) -o $(overlay_testing_obj) $(cflags_deps) - @echo Generate overlay runscript at $(overlay_runner) - @echo "#!/usr/bin/env bash\n$(overlay_runscript_content)" > $(overlay_runner) - chmod +x $(overlay_runner) - -clean: - rm -rf "$(out_dir)"/* - rm -rf "$(protobuf_out_dir)"/* - $(MAKE) -C $(test_home) clean - @echo '=============================================================' - @echo '= External library files were not cleaned. =' - @echo '= Please run `make -C ext clean` if you wish to clean them. =' - @echo '=============================================================' - -format: - $(SOURCE_FORMATTER) $(shell find '$(src_dir)' -not -path '$(protobuf_dir)/*' -type f -regex '.*\.[ch]') - $(MAKE) -C $(test_home) format - -tags: - ctags -R src/* - -ext: - $(call section_title,dependencies) - $(MAKE) -C $(ext_dir) - -.PHONY: all pip util test runtime_test clean format ext tags - -LeftParens := ( -RightParens := ) - -# Prints a nice little header graphic in the form: -# -# ======================= -# === Building === -# ======================= -# -# When using in the Makefile, in simple cases you can just call this at the beginning of the target. For more -# complicated targets, however, you will have to make a `pre-` target and run that before resolving the rest -# of the target. Remember to put your `pre-` target *after* the targets that your depends upon, but *before* the list -# of files (if any). See the `main`, `util`, and `devices` targets for examples of usage in different situations. -section_count := 1 -num_sections := $(shell grep -E '^\s*\$$\$(LeftParens)\s*call\s+section_title,' Makefile | wc -l) -define section_title - @echo - $(eval _var := $(section_count)/$(num_sections) $(1)) - $(eval _len := $(shell x="$(_var)"; echo -n $${#x})) - @printf '=%.0s' $(shell seq -16 $(_len)) - @echo - @echo === $(section_count)/$(num_sections) Building $(1) === - @printf '=%.0s' $(shell seq -16 $(_len)) - @echo - $(eval section_count := $(shell expr $(section_count) + 1)) -endef - -ifeq ($(SUNNEED_BUILD_TYPE),devel) - overlay_runscript_content := "gdb --args env LD_PRELOAD=$(abspath $(overlay_testing_obj)) $$\@" -else - overlay_runscript_content := "LD_PRELOAD=$(abspath $(overlay_obj)) $$\@" -endif +# Builds the main sunneed executable. + +ifeq ($(origin CC),default) + export CC = gcc +endif + +CFLAGS ?= -Wall -Wextra -g +PROTOC ?= protoc-c + +SUNNEED_BUILD_TYPE ?= devel +SUNNEED_BUILD_PIP ?= bq27441 +SUNNEED_BUILD_OUT_DIR ?= build +SUNNEED_BUILD_BIN_FILE ?= sunneed +SUNNEED_BUILD_CLIENT_LIB_NAME ?= libsunneedclient +SUNNEED_BUILD_OVERLAY_LIB_NAME ?= sunneed_overlay + +export SOURCE_FORMATTER = clang-format -style=file -i + +export cflags_deps = -I$(PWD)/$(ext_dir)/nng/include -L$(PWD)/$(ext_dir)/nng/build -L$(PWD)/$(ext_dir)/libbq27441 -lnng -lpthread -ldl -lprotobuf-c -latomic -I$(PWD)/$(ext_dir)/libbq27441 -lbq27441 -li2c + +ifeq ($(SUNNEED_BUILD_TYPE),devel) + util_cflags = -Wl,-rpath,$(CURDIR)/$(clientlib_out_dir) +endif + +src_dir = src +sources = $(wildcard $(src_dir)/*.c) + +ext_dir = ext + +out_dir = $(SUNNEED_BUILD_OUT_DIR) +bin_file = $(SUNNEED_BUILD_BIN_FILE) + +pip_out_dir = $(out_dir) +pip_obj = $(pip_out_dir)/pip.o +pip_name = $(SUNNEED_BUILD_PIP) + +clientlib_sources = $(wildcard $(src_dir)/client/*.c) +clientlib_out_dir = $(out_dir)/client +clientlib_obj = $(clientlib_out_dir)/$(SUNNEED_BUILD_CLIENT_LIB_NAME).so + +overlay_sources = $(wildcard $(src_dir)/overlay/*.c) +overlay_out_dir = $(out_dir) +overlay_obj = $(overlay_out_dir)/$(SUNNEED_BUILD_OVERLAY_LIB_NAME).so +overlay_testing_obj = $(patsubst %.so,%_testing.so,$(overlay_obj)) +overlay_runner = $(out_dir)/run-with-overlay + +protobuf_dir = $(src_dir)/protobuf +protobuf_sources = $(wildcard $(protobuf_dir)/*.proto) +protobuf_out_files = $(foreach src,$(protobuf_sources),$(subst !!!, ,$(join $(src:.proto=.pb-c.c!!!),$(src:.proto=.pb-c.h)))) +protobuf_out_dir = $(src_dir)/protobuf/c +protobuf_out_sources = $(wildcard $(protobuf_out_dir)/*.c) + +export test_home = test +export test_runner_name = run-tests +test_runner = $(test_home)/$(test_runner_name) +runtime_tests_runner = ./runtime_tests + +device_objs = $(patsubst %.c, %.o, $(wildcard $(src_dir)/device/*.c)) +util_objs = $(patsubst %.c, %.o, $(wildcard $(src_dir)/util/*.c)) + +all: pre-all main overlay util + +run_valgrind: pre-all main overlay util + valgrind ./build/sunneed + +run_ASAN: pre-all main_ASAN overlay util + ./build/sunneed + + +pre-all: + @echo "Starting all build..." + +log_pwr: pre-all main_pwr_data overlay util + + +main: ext protobuf pip devices + $(call section_title,main executable) + $(CC) $(CFLAGS) -DTESTING -ULOG_PWR $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) + +main_pwr_data: ext protobuf pip devices + $(call section_title, main executable) + $(CC) $(CFLAGS) -DTESTING -DLOG_PWR $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) + +main_ASAN: ext protobuf pip devices + $(call section_title,main executable) + $(CC) -fsanitize=address $(CFLAGS) -DTESTING $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) + +pip: pre-pip $(src_dir)/pip/$(pip_name).c + $(CC) $(CFLAGS) -o $(pip_obj) -c $(src_dir)/pip/$(pip_name).c +pre-pip: + $(call section_title,pip) + +devices: pre-devices ext $(device_objs) +pre-devices: + $(call section_title,devices) + +util: clientlib pre-util $(util_objs) +pre-util: + $(call section_title,utils) + +$(src_dir)/util/%.o: $(src_dir)/util/%.c + $(CC) $(CFLAGS) -o $(patsubst $(src_dir)/util/%.o, $(out_dir)/%, $@) $^ $(protobuf_out_sources) -L$(clientlib_out_dir) -lsunneedclient $(util_cflags) $(cflags_deps) + +$(src_dir)/client/%.o: $(src_dir)/client/%.c + $(CC) $(CFLAGS) -o $(patsubst $(src_dir)/util/%.o, $(out_dir)/%, $@) $^ $(protobuf_out_sources) $(cflags_deps) + +$(src_dir)/device/%.o: $(src_dir)/device/%.c + @if [ ! -d "$(out_dir)/device" ]; then mkdir "$(out_dir)/device"; fi + $(CC) $(CFLAGS) -g -shared -o $(patsubst $(src_dir)/device/%.o, $(out_dir)/device/%.so, $@) -fPIC $^ $(cflags_deps) + +protobuf: pre-protobuf $(protobuf_out_files) + @rm -rf "$(protobuf_out_dir)" && mkdir "$(protobuf_out_dir)" + mv $(protobuf_out_files) $(protobuf_out_dir) +pre-protobuf: + $(call section_title,protobuf) + +$(protobuf_out_files): $(protobuf_sources) + $(MAKE) --no-print-directory -C $(protobuf_dir) + +clientlib: ext + $(call section_title,client library) + @if [ ! -d "$(out_dir)/client" ]; then mkdir "$(out_dir)/client"; fi + $(CC) $(CFLAGS) -c -fPIC -o $(out_dir)/client/clientlib.o $(clientlib_sources) $(cflags_deps) + $(CC) $(CFLAGS) -shared -o $(clientlib_obj) $(out_dir)/client/clientlib.o + +# Run the runtime tests +runtime_test: main + $(runtime_tests_runner) + +test: tests + $(test_runner) + +tests: + make -C $(test_home) + +# Note that we compile two overlay libraries. One is a tester, which contains additional output meant for +# debugging/testing purposes. +overlay: clientlib + $(call section_title,overlay) + $(CC) $(CFLAGS) -g -fPIC -shared $(overlay_sources) -o $(overlay_obj) $(cflags_deps) + $(CC) $(CFLAGS) -g -fPIC -shared -DTESTING $(overlay_sources) -o $(overlay_testing_obj) $(cflags_deps) + @echo Generate overlay runscript at $(overlay_runner) + @echo "#!/usr/bin/env bash\n$(overlay_runscript_content)" > $(overlay_runner) + chmod +x $(overlay_runner) + +clean: + rm -rf "$(out_dir)"/* + rm -rf "$(protobuf_out_dir)"/* + $(MAKE) -C $(test_home) clean + @echo '=============================================================' + @echo '= External library files were not cleaned. =' + @echo '= Please run `make -C ext clean` if you wish to clean them. =' + @echo '=============================================================' + +format: + $(SOURCE_FORMATTER) $(shell find '$(src_dir)' -not -path '$(protobuf_dir)/*' -type f -regex '.*\.[ch]') + $(MAKE) -C $(test_home) format + +tags: + ctags -R src/* + +ext: + $(call section_title,dependencies) + $(MAKE) -C $(ext_dir) + +.PHONY: all pip util test runtime_test clean format ext tags + +LeftParens := ( +RightParens := ) + +# Prints a nice little header graphic in the form: +# +# ======================= +# === Building === +# ======================= +# +# When using in the Makefile, in simple cases you can just call this at the beginning of the target. For more +# complicated targets, however, you will have to make a `pre-` target and run that before resolving the rest +# of the target. Remember to put your `pre-` target *after* the targets that your depends upon, but *before* the list +# of files (if any). See the `main`, `util`, and `devices` targets for examples of usage in different situations. +section_count := 1 +num_sections := $(shell grep -E '^\s*\$$\$(LeftParens)\s*call\s+section_title,' Makefile | wc -l) +define section_title + @echo + $(eval _var := $(section_count)/$(num_sections) $(1)) + $(eval _len := $(shell x="$(_var)"; echo -n $${#x})) + @printf '=%.0s' $(shell seq -16 $(_len)) + @echo + @echo === $(section_count)/$(num_sections) Building $(1) === + @printf '=%.0s' $(shell seq -16 $(_len)) + @echo + $(eval section_count := $(shell expr $(section_count) + 1)) +endef + +ifeq ($(SUNNEED_BUILD_TYPE),devel) + overlay_runscript_content := "gdb --args env LD_PRELOAD=$(abspath $(overlay_testing_obj)) $$\@" +else + overlay_runscript_content := "LD_PRELOAD=$(abspath $(overlay_obj)) $$\@" +endif diff --git a/run_PyDriver.c b/run_PyDriver.c index d82e7ef..bc8e619 100644 --- a/run_PyDriver.c +++ b/run_PyDriver.c @@ -1,54 +1,54 @@ -#include -#include -#include -#include -#include - -int -main(void) -{ - PyObject *module, *func, *py_args, *py_kwargs; - PyGILState_STATE state; - - Py_Initialize(); - PyRun_SimpleString("import sys"); - PyRun_SimpleString("import os"); - PyRun_SimpleString("sys.path.append(os.getcwd()+'/ext/SunneeD_dev_drivers/PiCamDriver')"); - - if ( (module = PyImport_Import(PyUnicode_FromString("picam_driver"))) == NULL) { - PyErr_Print(); - abort(); - } - func = PyObject_GetAttrString(module, "read_pipe"); - - state = PyGILState_Ensure(); - - if (!PyCallable_Check(func)) { - fprintf(stderr, "Function not callable\n"); - goto fail; - } - - py_args = NULL; - py_kwargs = NULL; - - printf("calling function\n"); - PyObject_Call(func, py_args, py_kwargs); - - Py_DECREF(py_args); - Py_XDECREF(py_kwargs); - - if(PyErr_Occurred()) { - PyErr_Print(); - goto fail; - } - - PyGILState_Release(state); - - Py_Finalize(); - - return 0; - -fail: - PyGILState_Release(state); - abort(); +#include +#include +#include +#include +#include + +int +main(void) +{ + PyObject *module, *func, *py_args, *py_kwargs; + PyGILState_STATE state; + + Py_Initialize(); + PyRun_SimpleString("import sys"); + PyRun_SimpleString("import os"); + PyRun_SimpleString("sys.path.append(os.getcwd()+'/ext/SunneeD_dev_drivers/PiCamDriver')"); + + if ( (module = PyImport_Import(PyUnicode_FromString("picam_driver"))) == NULL) { + PyErr_Print(); + abort(); + } + func = PyObject_GetAttrString(module, "read_pipe"); + + state = PyGILState_Ensure(); + + if (!PyCallable_Check(func)) { + fprintf(stderr, "Function not callable\n"); + goto fail; + } + + py_args = NULL; + py_kwargs = NULL; + + printf("calling function\n"); + PyObject_Call(func, py_args, py_kwargs); + + Py_DECREF(py_args); + Py_XDECREF(py_kwargs); + + if(PyErr_Occurred()) { + PyErr_Print(); + goto fail; + } + + PyGILState_Release(state); + + Py_Finalize(); + + return 0; + +fail: + PyGILState_Release(state); + abort(); } \ No newline at end of file diff --git a/src/device/camera.c b/src/device/camera.c index 7d20c0f..ddac51f 100644 --- a/src/device/camera.c +++ b/src/device/camera.c @@ -1,27 +1,27 @@ -// An example device that implements the bare minimum. - -#include "../shared/sunneed_device_interface.h" -#include "../shared/sunneed_testing.h" -#include -#include - -struct sunneed_device_type_file_lock data = { - .len = 1, - .paths = { "/tmp/camera" } -}; - -int -init(void) { - return 0; -} - -enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; - -void * -get_device_type_data(void) { - return &data; -} - -unsigned int device_flags = 0; - -// TODO Device type. +// An example device that implements the bare minimum. + +#include "../shared/sunneed_device_interface.h" +#include "../shared/sunneed_testing.h" +#include +#include + +struct sunneed_device_type_file_lock data = { + .len = 1, + .paths = { "/tmp/camera" } +}; + +int +init(void) { + return 0; +} + +enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; + +void * +get_device_type_data(void) { + return &data; +} + +unsigned int device_flags = 0; + +// TODO Device type. diff --git a/src/device/stepperMotor.c b/src/device/stepperMotor.c index a3ac94b..daef753 100644 --- a/src/device/stepperMotor.c +++ b/src/device/stepperMotor.c @@ -1,27 +1,27 @@ -// An example device that implements the bare minimum. - -#include "../shared/sunneed_device_interface.h" -#include "../shared/sunneed_testing.h" -#include -#include - -struct sunneed_device_type_file_lock data = { - .len = 1, - .paths = { "/tmp/stepper" } -}; - -int -init(void) { - return 0; -} - -enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; - -void * -get_device_type_data(void) { - return &data; -} - -unsigned int device_flags = 0; - -// TODO Device type. +// An example device that implements the bare minimum. + +#include "../shared/sunneed_device_interface.h" +#include "../shared/sunneed_testing.h" +#include +#include + +struct sunneed_device_type_file_lock data = { + .len = 1, + .paths = { "/tmp/stepper" } +}; + +int +init(void) { + return 0; +} + +enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; + +void * +get_device_type_data(void) { + return &data; +} + +unsigned int device_flags = 0; + +// TODO Device type. diff --git a/src/device/stepper_motor.c b/src/device/stepper_motor.c index d4d9c90..f421a1e 100644 --- a/src/device/stepper_motor.c +++ b/src/device/stepper_motor.c @@ -1,23 +1,23 @@ -#include "../shared/sunneed_device_interface.h" -#include "../shared/sunneed_testing.h" -#include -#include - -struct sunneed_device_type_file_lock data = { - .len = 1, - .paths = {"/dev/stepper"} -}; - -int -init(void) { - return 0; -} - -enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; - -void * -get_device_type_data(void) { - return &data; -} - -unsigned int device_flags = 0; +#include "../shared/sunneed_device_interface.h" +#include "../shared/sunneed_testing.h" +#include +#include + +struct sunneed_device_type_file_lock data = { + .len = 1, + .paths = {"/dev/stepper"} +}; + +int +init(void) { + return 0; +} + +enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; + +void * +get_device_type_data(void) { + return &data; +} + +unsigned int device_flags = 0; diff --git a/src/log.h b/src/log.h index 39fede9..52ba438 100644 --- a/src/log.h +++ b/src/log.h @@ -1,52 +1,52 @@ -#ifndef _LOG_H_ -#define _LOG_H_ - -/* - * Defines a variety of macros to be used for logging information. - * Assign a FILE* to `logfile` during runtime to redirect the log to that file. - */ - -#include -#include - -FILE *logfile, *logfile_pwr; - -#define LOGL_DEBUG "D\e[38;5;240m" -#define LOGL_INFO "I" -#define LOGL_WARN "W\e[0;33m" -#define LOGL_ERROR "E\e[0;31m" - - -#define LOG_PWR_EVENT(LEVEL, MESSAGE, ...) \ - { \ - FILE *_logfile = logfile_pwr; \ - if (logfile_pwr) { \ - _logfile = fopen("sunneed_pwr_log.txt", "w+"); \ - } \ - time_t _now = time(NULL); \ - struct tm *_time = localtime(&_now); \ - char _time_str[21]; \ - strftime(_time_str, 21, "%Y-%m-%d %H:%M:%S", _time); \ - fprintf(_logfile, "%s[%s] " MESSAGE "\e[0m\n", LEVEL, _time_str, ##__VA_ARGS__); \ - fflush(_logfile); \ - } - -#define LOG(LEVEL, MESSAGE, ...) \ - { \ - FILE *_logfile = logfile; \ - if (!logfile) { \ - _logfile = stdout; \ - } \ - time_t _now = time(NULL); \ - struct tm *_time = localtime(&_now); \ - char _time_str[21]; \ - strftime(_time_str, 21, "%Y-%m-%d %H:%M:%S", _time); \ - fprintf(_logfile, "%s[%s] " MESSAGE "\e[0m\n", LEVEL, _time_str, ##__VA_ARGS__); \ - } - -#define LOG_D(MESSAGE, ...) LOG(LOGL_DEBUG, MESSAGE, ##__VA_ARGS__); -#define LOG_I(MESSAGE, ...) LOG(LOGL_INFO, MESSAGE, ##__VA_ARGS__); -#define LOG_W(MESSAGE, ...) LOG(LOGL_WARN, MESSAGE, ##__VA_ARGS__); -#define LOG_E(MESSAGE, ...) LOG(LOGL_ERROR, MESSAGE, ##__VA_ARGS__); -#define LOG_P(MESSAGE, ...) LOG_PWR_EVENT(LOGL_INFO, MESSAGE, ##__VA_ARGS__); -#endif +#ifndef _LOG_H_ +#define _LOG_H_ + +/* + * Defines a variety of macros to be used for logging information. + * Assign a FILE* to `logfile` during runtime to redirect the log to that file. + */ + +#include +#include + +FILE *logfile, *logfile_pwr; + +#define LOGL_DEBUG "D\e[38;5;240m" +#define LOGL_INFO "I" +#define LOGL_WARN "W\e[0;33m" +#define LOGL_ERROR "E\e[0;31m" + + +#define LOG_PWR_EVENT(LEVEL, MESSAGE, ...) \ + { \ + FILE *_logfile = logfile_pwr; \ + if (logfile_pwr) { \ + _logfile = fopen("sunneed_pwr_log.txt", "w+"); \ + } \ + time_t _now = time(NULL); \ + struct tm *_time = localtime(&_now); \ + char _time_str[21]; \ + strftime(_time_str, 21, "%Y-%m-%d %H:%M:%S", _time); \ + fprintf(_logfile, "%s[%s] " MESSAGE "\e[0m\n", LEVEL, _time_str, ##__VA_ARGS__); \ + fflush(_logfile); \ + } + +#define LOG(LEVEL, MESSAGE, ...) \ + { \ + FILE *_logfile = logfile; \ + if (!logfile) { \ + _logfile = stdout; \ + } \ + time_t _now = time(NULL); \ + struct tm *_time = localtime(&_now); \ + char _time_str[21]; \ + strftime(_time_str, 21, "%Y-%m-%d %H:%M:%S", _time); \ + fprintf(_logfile, "%s[%s] " MESSAGE "\e[0m\n", LEVEL, _time_str, ##__VA_ARGS__); \ + } + +#define LOG_D(MESSAGE, ...) LOG(LOGL_DEBUG, MESSAGE, ##__VA_ARGS__); +#define LOG_I(MESSAGE, ...) LOG(LOGL_INFO, MESSAGE, ##__VA_ARGS__); +#define LOG_W(MESSAGE, ...) LOG(LOGL_WARN, MESSAGE, ##__VA_ARGS__); +#define LOG_E(MESSAGE, ...) LOG(LOGL_ERROR, MESSAGE, ##__VA_ARGS__); +#define LOG_P(MESSAGE, ...) LOG_PWR_EVENT(LOGL_INFO, MESSAGE, ##__VA_ARGS__); +#endif diff --git a/src/pip/bq27441.c b/src/pip/bq27441.c index ad1a923..88f73d0 100644 --- a/src/pip/bq27441.c +++ b/src/pip/bq27441.c @@ -1,18 +1,18 @@ -#include "../shared/sunneed_pip_interface.h" -#include "../../ext/libbq27441/bq27441.c" - -struct sunneed_pip -pip_info() { - return (struct sunneed_pip){"bq27441", 1000, 50}; -} - -signed int -present_power() { - -#ifdef log_pwr - bq27441_init(1); - return bq27441_nominal_avail_cap(); -#endif - - return 0; -} +#include "../shared/sunneed_pip_interface.h" +#include "../../ext/libbq27441/bq27441.c" + +struct sunneed_pip +pip_info() { + return (struct sunneed_pip){"bq27441", 1000, 50}; +} + +signed int +present_power() { + +#ifdef log_pwr + bq27441_init(1); + return bq27441_average_power(); +#endif + + return 0; +} diff --git a/src/sunneed.h b/src/sunneed.h index aa9c816..51c0401 100644 --- a/src/sunneed.h +++ b/src/sunneed.h @@ -1,24 +1,24 @@ -#ifndef _SUNNEED_H_ -#define _SUNNEED_H_ - -#include -#include -#include -#include - -#define APP_NAME "sunneed" - -typedef void* sunneed_worker_thread_result_t; - -#define SUNNEED_MAX_IPC_CLIENTS 512 - -// Control flow: -// When a new pipe connects, we use this struct to make a mapping of its pipe ID to a tenant. Then, when further -// requests are made, the pipe ID is used to identify a tenant to the request. -// The client will have to send some notification in order to unregister; I don't think we can tell if a pipe -// closed. -struct tenant_pipe { - struct sunneed_tenant *tenant; - nng_pipe pipe; -} tenant_pipes[SUNNEED_MAX_IPC_CLIENTS]; -#endif +#ifndef _SUNNEED_H_ +#define _SUNNEED_H_ + +#include +#include +#include +#include + +#define APP_NAME "sunneed" + +typedef void* sunneed_worker_thread_result_t; + +#define SUNNEED_MAX_IPC_CLIENTS 512 + +// Control flow: +// When a new pipe connects, we use this struct to make a mapping of its pipe ID to a tenant. Then, when further +// requests are made, the pipe ID is used to identify a tenant to the request. +// The client will have to send some notification in order to unregister; I don't think we can tell if a pipe +// closed. +struct tenant_pipe { + struct sunneed_tenant *tenant; + nng_pipe pipe; +} tenant_pipes[SUNNEED_MAX_IPC_CLIENTS]; +#endif diff --git a/src/sunneed_core.c b/src/sunneed_core.c index 9825262..0ebf9c9 100644 --- a/src/sunneed_core.c +++ b/src/sunneed_core.c @@ -1,151 +1,151 @@ -#include "sunneed_core.h" - -extern struct sunneed_device devices[MAX_DEVICES]; - -struct sunneed_pip pip; - -sunneed_worker_thread_result_t (*worker_thread_functions[])(void *) = {sunneed_proc_monitor, sunneed_quantum_worker, sunneed_stepperMotor_driver, sunneed_camera_driver, NULL}; - -void -handle_exit(void) { - LOG_I("Sunneed exiting"); - LOG_I("\tKilling stepper motor"); - kill(sunneed_stepper_driver_pid, SIGTERM); - LOG_I("\tKilling camera driver"); - kill(sunneed_camera_driver_pid, SIGTERM); -} - -#ifdef TESTING - -#include "sunneed_runtime_test_collection.h" - -int (*runtime_tests[])(void) = RUNTIME_TESTS; - -static unsigned int -testcase_count(void) { - unsigned int testcases = 0; - for (int (**cur)(void) = runtime_tests; *cur != NULL; cur++) - testcases++; - // TODO Why minus one... - return testcases - 1; -} - -static int -run_testcase(unsigned int testcase) { - if (testcase >= testcase_count()) { - LOG_E("Cannot run testcase #%d because it does not exist", testcase); - return 1; - } - - int ret = runtime_tests[testcase](); - if (ret != 0) { - fprintf(stderr, "Failure: %s (%d)\n", sunneed_runtime_test_error, ret); - return ret; - } - - return 0; -} -#endif - -static int -spawn_worker_threads(void) { - int ret; - int worker_thread_count = 0; - for (void *(**cur)(void *) = worker_thread_functions; *cur != NULL; cur++) - worker_thread_count++; - - pthread_t worker_threads[worker_thread_count]; - - LOG_I("Launching %d worker threads", worker_thread_count); - - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - for (int i = 0; i < worker_thread_count; i++) { - if ((ret = pthread_create(&worker_threads[i], &attr, worker_thread_functions[i], NULL)) != 0) { - LOG_E("Failed to launch worker thread %d (error %d)", i, ret); - return 1; - }; - } - - return 0; -} - - -void -sunneed_init(void) { - atexit(handle_exit); - pip = pip_info(); -} - -int -main(int argc, char *argv[]) { - int opt; - extern int optopt; - -#ifdef LOG_PWR - logfile_pwr = fopen("sunneed_pwr_log.txt", "w+"); -#endif - -#ifdef TESTING - const char *optstring = ":ht:c"; -#else - const char *optstring = ":h"; -#endif - // TODO Long-form getopts. - while ((opt = getopt(argc, argv, optstring)) != -1) { - switch (opt) { - case 'h': - printf(HELP_TEXT, argv[0]); - exit(0); -#ifdef TESTING - case 't': ; - logfile = fopen("sunneed_log.txt", "w+"); - int testcase = strtol(optarg, NULL, 10); - if (errno) { - LOG_E("Failed to parse testcase index: %s", strerror(errno)); - return 1; - } - return run_testcase(testcase); - case 'c': - printf("%d\n", testcase_count()); - exit(0); -#endif - case '?': - fprintf(stderr, "%s: illegal option -%c\n", APP_NAME, optopt); - exit(1); - case ':': - fprintf(stderr, "%s: expected argument for option -%c\n", APP_NAME, optopt); - exit(1); - } - } - - int ret = 0; - - LOG_I("sunneed is initializing..."); - - sunneed_init(); - - LOG_I("Acquired PIP: %s", pip.name); - - LOG_I("Loading devices..."); - if ((ret = sunneed_load_devices(devices)) != 0) { - LOG_E("Failed to load devices"); - ret = 1; - goto end; - } - - if ((ret = spawn_worker_threads()) != 0) { - LOG_E("Error occurred while spawning worker threads"); - ret = 1; - goto end; - } - if ((ret = sunneed_listen()) != 0) { - LOG_E("sunneed listener encountered a fatal error. Exiting."); - ret = 1; - goto end; - } - -end: - return 0; -} +#include "sunneed_core.h" + +extern struct sunneed_device devices[MAX_DEVICES]; + +struct sunneed_pip pip; + +sunneed_worker_thread_result_t (*worker_thread_functions[])(void *) = {sunneed_proc_monitor, sunneed_quantum_worker, sunneed_stepperMotor_driver, sunneed_camera_driver, NULL}; + +void +handle_exit(void) { + LOG_I("Sunneed exiting"); + LOG_I("\tKilling stepper motor"); + kill(sunneed_stepper_driver_pid, SIGTERM); + LOG_I("\tKilling camera driver"); + kill(sunneed_camera_driver_pid, SIGTERM); +} + +#ifdef TESTING + +#include "sunneed_runtime_test_collection.h" + +int (*runtime_tests[])(void) = RUNTIME_TESTS; + +static unsigned int +testcase_count(void) { + unsigned int testcases = 0; + for (int (**cur)(void) = runtime_tests; *cur != NULL; cur++) + testcases++; + // TODO Why minus one... + return testcases - 1; +} + +static int +run_testcase(unsigned int testcase) { + if (testcase >= testcase_count()) { + LOG_E("Cannot run testcase #%d because it does not exist", testcase); + return 1; + } + + int ret = runtime_tests[testcase](); + if (ret != 0) { + fprintf(stderr, "Failure: %s (%d)\n", sunneed_runtime_test_error, ret); + return ret; + } + + return 0; +} +#endif + +static int +spawn_worker_threads(void) { + int ret; + int worker_thread_count = 0; + for (void *(**cur)(void *) = worker_thread_functions; *cur != NULL; cur++) + worker_thread_count++; + + pthread_t worker_threads[worker_thread_count]; + + LOG_I("Launching %d worker threads", worker_thread_count); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + for (int i = 0; i < worker_thread_count; i++) { + if ((ret = pthread_create(&worker_threads[i], &attr, worker_thread_functions[i], NULL)) != 0) { + LOG_E("Failed to launch worker thread %d (error %d)", i, ret); + return 1; + }; + } + + return 0; +} + + +void +sunneed_init(void) { + atexit(handle_exit); + pip = pip_info(); +} + +int +main(int argc, char *argv[]) { + int opt; + extern int optopt; + +#ifdef LOG_PWR + logfile_pwr = fopen("sunneed_pwr_log.txt", "w+"); +#endif + +#ifdef TESTING + const char *optstring = ":ht:c"; +#else + const char *optstring = ":h"; +#endif + // TODO Long-form getopts. + while ((opt = getopt(argc, argv, optstring)) != -1) { + switch (opt) { + case 'h': + printf(HELP_TEXT, argv[0]); + exit(0); +#ifdef TESTING + case 't': ; + logfile = fopen("sunneed_log.txt", "w+"); + int testcase = strtol(optarg, NULL, 10); + if (errno) { + LOG_E("Failed to parse testcase index: %s", strerror(errno)); + return 1; + } + return run_testcase(testcase); + case 'c': + printf("%d\n", testcase_count()); + exit(0); +#endif + case '?': + fprintf(stderr, "%s: illegal option -%c\n", APP_NAME, optopt); + exit(1); + case ':': + fprintf(stderr, "%s: expected argument for option -%c\n", APP_NAME, optopt); + exit(1); + } + } + + int ret = 0; + + LOG_I("sunneed is initializing..."); + + sunneed_init(); + + LOG_I("Acquired PIP: %s", pip.name); + + LOG_I("Loading devices..."); + if ((ret = sunneed_load_devices(devices)) != 0) { + LOG_E("Failed to load devices"); + ret = 1; + goto end; + } + + if ((ret = spawn_worker_threads()) != 0) { + LOG_E("Error occurred while spawning worker threads"); + ret = 1; + goto end; + } + if ((ret = sunneed_listen()) != 0) { + LOG_E("sunneed listener encountered a fatal error. Exiting."); + ret = 1; + goto end; + } + +end: + return 0; +} diff --git a/src/sunneed_core.h b/src/sunneed_core.h index fa6b7f6..7dcf211 100644 --- a/src/sunneed_core.h +++ b/src/sunneed_core.h @@ -1,43 +1,43 @@ -#ifndef _SUNNEED_CORE_H_ -#define _SUNNEED_CORE_H_ - -#include -#include -#include -#include -#include -#include - -#include "log.h" -#include "shared/sunneed_pip_interface.h" -#include "sunneed.h" -#include "sunneed_listener.h" -#include "sunneed_proc.h" -#include "sunneed_loader.h" -#include "sunneed_device.h" - -#define _HELP_TEXT_HEAD \ - APP_NAME ": enforce power usage policies\n" \ - "\nUSAGE\n" \ - "%s OPTIONS\n" \ - "\nOPTIONS\n" \ - "\t-h --help Show this help.\n" -#define _HELP_TEXT_TAIL \ - "\n" - -#ifdef TESTING -#define HELP_TEXT _HELP_TEXT_HEAD \ - "\t-c --testcase-count Print out the number of runtime tests.\n" \ - "\t-t --run-test TEST Run a runtime test by given its numerical ID.\n" \ - _HELP_TEXT_TAIL -#else -#define HELP_TEXT _HELP_TEXT_HEAD _HELP_TEXT_TAIL -#endif - -#ifdef TESTING - -#define MAX_TESTS_PER_SUITE 64 - -#endif - -#endif +#ifndef _SUNNEED_CORE_H_ +#define _SUNNEED_CORE_H_ + +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "shared/sunneed_pip_interface.h" +#include "sunneed.h" +#include "sunneed_listener.h" +#include "sunneed_proc.h" +#include "sunneed_loader.h" +#include "sunneed_device.h" + +#define _HELP_TEXT_HEAD \ + APP_NAME ": enforce power usage policies\n" \ + "\nUSAGE\n" \ + "%s OPTIONS\n" \ + "\nOPTIONS\n" \ + "\t-h --help Show this help.\n" +#define _HELP_TEXT_TAIL \ + "\n" + +#ifdef TESTING +#define HELP_TEXT _HELP_TEXT_HEAD \ + "\t-c --testcase-count Print out the number of runtime tests.\n" \ + "\t-t --run-test TEST Run a runtime test by given its numerical ID.\n" \ + _HELP_TEXT_TAIL +#else +#define HELP_TEXT _HELP_TEXT_HEAD _HELP_TEXT_TAIL +#endif + +#ifdef TESTING + +#define MAX_TESTS_PER_SUITE 64 + +#endif + +#endif diff --git a/src/sunneed_proc.c b/src/sunneed_proc.c index e0a4003..b14686c 100644 --- a/src/sunneed_proc.c +++ b/src/sunneed_proc.c @@ -1,226 +1,249 @@ -#include "sunneed_proc.h" - -struct sunneed_tenant tenants[MAX_TENANTS]; - -int -sunneed_init_tenants(void) { - for (int i = 0; i < MAX_TENANTS; i++) { - tenants[i] = (struct sunneed_tenant){.id = i, .pid = 0, .power_proportion = 0.0, .is_active = false}; - } - - return 0; -} - -// Find an unused spot for a tenant and register ourself there. -struct sunneed_tenant * -sunneed_tenant_register(pid_t pid) { - struct sunneed_tenant *tenant = NULL; - for (int i = 0; i < MAX_TENANTS; i++) { - if (!tenants[i].is_active) { - tenant = &tenants[i]; - break; - } - } - - if (tenant == NULL) { - LOG_E("Sorry PID %d, can't spawn any more tenants!", pid); - return NULL; - } - - tenant->pid = pid; - tenant->is_active = true; - - return tenant; -} - -int -sunneed_tenant_unregister(struct sunneed_tenant *tenant) { - if (tenant == NULL || !tenant->is_active) { - LOG_W("Cannot deactivate an inactive tenant"); - return 1; - } - - tenant->is_active = false; - return 0; -} - -void -unregister_lost_tenant(struct sunneed_tenant* tenant) -{ - bool cleared = false; - for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) { - if (tenant_pipes[i].tenant->id == tenant->id) { - LOG_D("Removing mapping from pipe %d to tenant %d", tenant_pipes[i].pipe.id, tenant->id); - tenant_pipes[i].tenant = NULL; - tenant_pipes[i].pipe = (nng_pipe)NNG_PIPE_INITIALIZER; - cleared = true; - break; - } - } - if (!cleared) { - LOG_E("No mapping cleared when unregistering tenant %d", tenant->id); - return; - } - if (sunneed_tenant_unregister(tenant) != 0) { - LOG_E("Failed to unregister tenant %d", tenant->id); - } -} - -unsigned int -sunneed_get_num_tenants(void) { - unsigned int num_tenants = 0; - - for (int i = 0; i < MAX_TENANTS; i++) { - if (tenants[i].is_active) - num_tenants++; - } - - return num_tenants; -} - -int -sunneed_update_tenant_cpu_usage(void) { - // TODO Verify that the pipe and PID match up. It is entirely possible for the tenant process to die and a new - // process with the same PID as the tenant's to start up. - FILE *file; - char filepath[FILENAME_MAX] = "/proc/stat"; - - file = fopen(filepath, "r"); - fscanf(file, "%*s %llu %llu %llu %llu", &cpu_usage.user, &cpu_usage.nice, &cpu_usage.sys, &cpu_usage.idle); - fclose(file); - - for (struct sunneed_tenant *tenant = tenants; tenant < tenants + MAX_TENANTS; tenant++) { - if (!tenant->is_active) - continue; - - snprintf(filepath, FILENAME_MAX, "/proc/%d/stat", tenant->pid); - if (access(filepath, F_OK) != 0) { - LOG_E("Unable to find procfs file for PID %d; most likely a tenant ended but forgot to tell us", - tenant->pid); - LOG_E("Unregistering tenant %d (PID %d)",tenant->id, tenant->pid); - unregister_lost_tenant(tenant); - continue; - } - - file = fopen(filepath, "r"); - // Read CPU consumption from this tenant's PID. - fscanf(file, - "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u" // 13 things we don't care about - " %llu %llu" // usertime, systemtime - " %*d %*d %*d %*d %*d %*d %*u %*u", // 8 things we don't care about - &cpu_usage.tenants[tenant->id].user, &cpu_usage.tenants[tenant->id].sys); - fclose(file); - } - - return 0; -} - -int -sunneed_get_tenant_cpu_usage(sunneed_tenant_id_t tenant_id) { - if (!tenants[tenant_id].is_active) { - LOG_E("Attempt to get CPU usage of inactive tenant %d", tenant_id); - return -1; - } - - // TODO Shit - - return 0; -} - -sunneed_worker_thread_result_t -sunneed_proc_monitor(__attribute__((unused)) void *args) { - int ret; - while (true) { - LOG_D("Updating process CPU usage"); - if ((ret = sunneed_update_tenant_cpu_usage()) != 0) { - LOG_E("Error updating CPU usage; monitor thread stopping"); - return NULL; - } - - sleep(5); - } -} - -sunneed_worker_thread_result_t -sunneed_stepperMotor_driver(__attribute__((unused)) void *args) { - int status, stepper_driver_new_stdin; - const char *path = "/tmp/stepper"; - const char *executable_path = "ext/SunneeD_dev_drivers/StepperDriver/stepper_driver"; - - if ( (sunneed_stepper_driver_pid = fork()) == 0) { - if (mkfifo(path, S_IRUSR | S_IWUSR | S_IWOTH) == -1) { - /* check if pipe already exists */ - if ((stepper_driver_new_stdin = open(path, O_RDONLY)) == -1) { - /* cound't create pipe & it doesn't already exist */ - LOG_E("Error creating pipe to stepper driver"); - exit(1); - } - } else { - if ((stepper_driver_new_stdin = open(path, O_RDONLY)) == -1) { - /* pipe exists, couldn't open it */ - LOG_E("Error opening pipe to stepper driver"); - exit(1); - } - } - - if (close(0) == -1) { - LOG_E("error closing stdin for stepper driver"); - exit(1); - } - if (dup2(stepper_driver_new_stdin, 0) == -1) { - LOG_E("error duping new stdin for stepper driver"); - exit(1); - } - if (close(stepper_driver_new_stdin) == -1) { - LOG_E("error closing stepper driver's 2nd ref to new stdin after dup"); - exit(1); - } - execl(executable_path, executable_path, NULL); - /* should never get here -- stepper driver runs on loop */ - exit(1); - } else { - wait(&status); - LOG_E("Stepper driver exited - status=%d, errno=%s", status, strerror(errno)); - return NULL; - } -} - -sunneed_worker_thread_result_t -sunneed_camera_driver(__attribute__((unused)) void *args) { - int wait_status, cam_driver_new_stdin; - const char *path = "/tmp/camera"; - const char *executable_path = "run_PyDriver"; - - if ( (sunneed_camera_driver_pid = fork()) == 0) { - if ( (cam_driver_new_stdin = open(path, O_RDONLY)) == -1) { - if (mkfifo(path, S_IRUSR | S_IWUSR | S_IWOTH) == -1) { - LOG_E("Could not create camera device fd"); - exit(1); - } - if (chmod(path, S_IRUSR | S_IWUSR | S_IWOTH) == -1) { - LOG_E("Could not set permissions for camera device fd"); - exit(1); - } - cam_driver_new_stdin = open(path, O_RDONLY); - } - if (close(0) == -1) { - LOG_E("Error closing camera driver stdin"); - exit(1); - } - if (dup2(cam_driver_new_stdin, 0) == -1) { - LOG_E("Error duping camera device fd to camera driver stdin"); - exit(1); - } - if (close(cam_driver_new_stdin) == -1) { - LOG_E("Error closing camera driver's unused reference to camera fd"); - exit(1); - } - execl(executable_path, executable_path, NULL); - - exit(1); - } else { - wait(&wait_status); - LOG_E("Error executing camera driver: status=%d\n",wait_status); - return NULL; - } -} +#include "sunneed_proc.h" + + +struct sunneed_tenant tenants[MAX_TENANTS]; + +static enum battery_state battery_state; + + +int +sunneed_init_tenants(void) { + for (int i = 0; i < MAX_TENANTS; i++) { + tenants[i] = (struct sunneed_tenant){.id = i, .pid = 0, .power_proportion = 0.0, .is_active = false}; + } + + return 0; +} + +// Find an unused spot for a tenant and register ourself there. +struct sunneed_tenant * +sunneed_tenant_register(pid_t pid) { + struct sunneed_tenant *tenant = NULL; + for (int i = 0; i < MAX_TENANTS; i++) { + if (!tenants[i].is_active) { + tenant = &tenants[i]; + break; + } + } + + if (tenant == NULL) { + LOG_E("Sorry PID %d, can't spawn any more tenants!", pid); + return NULL; + } + + tenant->pid = pid; + tenant->is_active = true; + + return tenant; +} + +int +sunneed_tenant_unregister(struct sunneed_tenant *tenant) { + if (tenant == NULL || !tenant->is_active) { + LOG_W("Cannot deactivate an inactive tenant"); + return 1; + } + + tenant->is_active = false; + return 0; +} + +void +unregister_lost_tenant(struct sunneed_tenant* tenant) +{ + bool cleared = false; + for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) { + if (tenant_pipes[i].tenant->id == tenant->id) { + LOG_D("Removing mapping from pipe %d to tenant %d", tenant_pipes[i].pipe.id, tenant->id); + tenant_pipes[i].tenant = NULL; + tenant_pipes[i].pipe = (nng_pipe)NNG_PIPE_INITIALIZER; + cleared = true; + break; + } + } + if (!cleared) { + LOG_E("No mapping cleared when unregistering tenant %d", tenant->id); + return; + } + if (sunneed_tenant_unregister(tenant) != 0) { + LOG_E("Failed to unregister tenant %d", tenant->id); + } +} + +unsigned int +sunneed_get_num_tenants(void) { + unsigned int num_tenants = 0; + + for (int i = 0; i < MAX_TENANTS; i++) { + if (tenants[i].is_active) + num_tenants++; + } + + return num_tenants; +} + +int +sunneed_update_tenant_cpu_usage(void) { + // TODO Verify that the pipe and PID match up. It is entirely possible for the tenant process to die and a new + // process with the same PID as the tenant's to start up. + FILE *file; + char filepath[FILENAME_MAX] = "/proc/stat"; + + file = fopen(filepath, "r"); + fscanf(file, "%*s %llu %llu %llu %llu", &cpu_usage.user, &cpu_usage.nice, &cpu_usage.sys, &cpu_usage.idle); + fclose(file); + + for (struct sunneed_tenant *tenant = tenants; tenant < tenants + MAX_TENANTS; tenant++) { + if (!tenant->is_active) + continue; + + snprintf(filepath, FILENAME_MAX, "/proc/%d/stat", tenant->pid); + if (access(filepath, F_OK) != 0) { + LOG_E("Unable to find procfs file for PID %d; most likely a tenant ended but forgot to tell us", + tenant->pid); + LOG_E("Unregistering tenant %d (PID %d)",tenant->id, tenant->pid); + unregister_lost_tenant(tenant); + continue; + } + + file = fopen(filepath, "r"); + // Read CPU consumption from this tenant's PID. + fscanf(file, + "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u" // 13 things we don't care about + " %llu %llu" // usertime, systemtime + " %*d %*d %*d %*d %*d %*d %*u %*u", // 8 things we don't care about + &cpu_usage.tenants[tenant->id].user, &cpu_usage.tenants[tenant->id].sys); + fclose(file); + } + + return 0; +} + +int +sunneed_get_tenant_cpu_usage(sunneed_tenant_id_t tenant_id) { + if (!tenants[tenant_id].is_active) { + LOG_E("Attempt to get CPU usage of inactive tenant %d", tenant_id); + return -1; + } + + // TODO Shit + + return 0; +} + +sunneed_worker_thread_result_t +sunneed_proc_monitor(__attribute__((unused)) void *args) { + int ret, last_soc, curr_soc; + last_soc = bq27441_soc_unfiltered(); + while (true) { + LOG_D("Updating process CPU usage"); + if ((ret = sunneed_update_tenant_cpu_usage()) != 0) { + LOG_E("Error updating CPU usage; monitor thread stopping"); + return NULL; + } + + //check and update battery state + curr_soc = bq27441_soc_unfiltered(); + if(curr_soc > 90) + { + battery_state = FULL; + }else if(curr_soc > last_soc){ + battery_state = CHARGING; + }else if(curr_soc > 50){ + battery_state = DISCHARGING; + }else if(curr_soc > 20){ + battery_state = HALF; + }else if(curr_soc){ + battery_state = LOW; + }else{ + battery_state = EMPTY; + } + LOG_D("battery state: %s\n", battery_state); + + sleep(5); + } +} + +sunneed_worker_thread_result_t +sunneed_stepperMotor_driver(__attribute__((unused)) void *args) { + int status, stepper_driver_new_stdin; + const char *path = "/tmp/stepper"; + const char *executable_path = "ext/SunneeD_dev_drivers/StepperDriver/stepper_driver"; + + if ( (sunneed_stepper_driver_pid = fork()) == 0) { + if (mkfifo(path, S_IRUSR | S_IWUSR | S_IWOTH) == -1) { + /* check if pipe already exists */ + if ((stepper_driver_new_stdin = open(path, O_RDONLY)) == -1) { + /* cound't create pipe & it doesn't already exist */ + LOG_E("Error creating pipe to stepper driver"); + exit(1); + } + } else { + if ((stepper_driver_new_stdin = open(path, O_RDONLY)) == -1) { + /* pipe exists, couldn't open it */ + LOG_E("Error opening pipe to stepper driver"); + exit(1); + } + } + + if (close(0) == -1) { + LOG_E("error closing stdin for stepper driver"); + exit(1); + } + if (dup2(stepper_driver_new_stdin, 0) == -1) { + LOG_E("error duping new stdin for stepper driver"); + exit(1); + } + if (close(stepper_driver_new_stdin) == -1) { + LOG_E("error closing stepper driver's 2nd ref to new stdin after dup"); + exit(1); + } + execl(executable_path, executable_path, NULL); + /* should never get here -- stepper driver runs on loop */ + exit(1); + } else { + wait(&status); + LOG_E("Stepper driver exited - status=%d, errno=%s", status, strerror(errno)); + return NULL; + } +} + +sunneed_worker_thread_result_t +sunneed_camera_driver(__attribute__((unused)) void *args) { + int wait_status, cam_driver_new_stdin; + const char *path = "/tmp/camera"; + const char *executable_path = "run_PyDriver"; + + if ( (sunneed_camera_driver_pid = fork()) == 0) { + if ( (cam_driver_new_stdin = open(path, O_RDONLY)) == -1) { + if (mkfifo(path, S_IRUSR | S_IWUSR | S_IWOTH) == -1) { + LOG_E("Could not create camera device fd"); + exit(1); + } + if (chmod(path, S_IRUSR | S_IWUSR | S_IWOTH) == -1) { + LOG_E("Could not set permissions for camera device fd"); + exit(1); + } + cam_driver_new_stdin = open(path, O_RDONLY); + } + if (close(0) == -1) { + LOG_E("Error closing camera driver stdin"); + exit(1); + } + if (dup2(cam_driver_new_stdin, 0) == -1) { + LOG_E("Error duping camera device fd to camera driver stdin"); + exit(1); + } + if (close(cam_driver_new_stdin) == -1) { + LOG_E("Error closing camera driver's unused reference to camera fd"); + exit(1); + } + execl(executable_path, executable_path, NULL); + + exit(1); + } else { + wait(&wait_status); + LOG_E("Error executing camera driver: status=%d\n",wait_status); + return NULL; + } +} diff --git a/src/sunneed_proc.h b/src/sunneed_proc.h index b5614bb..c39785f 100644 --- a/src/sunneed_proc.h +++ b/src/sunneed_proc.h @@ -1,66 +1,75 @@ -#ifndef _SUNNEED_PROC_H_ -#define _SUNNEED_PROC_H_ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "log.h" -#include "sunneed.h" - -#define MAX_TENANTS 4 - -typedef unsigned int sunneed_tenant_id_t; - -// TODO Separate tenants from processes. -struct sunneed_tenant { - sunneed_tenant_id_t id; - pid_t pid; - float power_proportion; - bool is_active; -}; - -struct tenant_cpu_usage { - unsigned long long user, sys; -}; - -struct { - unsigned long long user, nice, sys, idle; - struct tenant_cpu_usage tenants[MAX_TENANTS]; -} cpu_usage; - -int sunneed_stepper_driver_pid, sunneed_camera_driver_pid; - -int -sunneed_update_tenant_cpu_usage(void); - -int -sunneed_init_tenants(void); - -struct sunneed_tenant * -sunneed_tenant_register(pid_t pid); - -int -sunneed_tenant_unregister(struct sunneed_tenant *tenant); - -unsigned int -sunneed_get_num_tenants(void); - -int -sunneed_get_tenant_cpu_usage(sunneed_tenant_id_t tenant_id); - -sunneed_worker_thread_result_t -sunneed_proc_monitor(void *args); - -sunneed_worker_thread_result_t -sunneed_stepperMotor_driver(void *args); - -sunneed_worker_thread_result_t -sunneed_camera_driver(void *args); - -#endif +#ifndef _SUNNEED_PROC_H_ +#define _SUNNEED_PROC_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "sunneed.h" + +#define MAX_TENANTS 4 + +typedef unsigned int sunneed_tenant_id_t; + +// TODO Separate tenants from processes. +struct sunneed_tenant { + sunneed_tenant_id_t id; + pid_t pid; + float power_proportion; + bool is_active; +}; + +struct tenant_cpu_usage { + unsigned long long user, sys; +}; + +struct { + unsigned long long user, nice, sys, idle; + struct tenant_cpu_usage tenants[MAX_TENANTS]; +} cpu_usage; + +enum battery_state { + FULL, + CHARGING, + DISCHARGING, + HALF, + LOW, + EMPTY +}; + +int sunneed_stepper_driver_pid, sunneed_camera_driver_pid; + +int +sunneed_update_tenant_cpu_usage(void); + +int +sunneed_init_tenants(void); + +struct sunneed_tenant * +sunneed_tenant_register(pid_t pid); + +int +sunneed_tenant_unregister(struct sunneed_tenant *tenant); + +unsigned int +sunneed_get_num_tenants(void); + +int +sunneed_get_tenant_cpu_usage(sunneed_tenant_id_t tenant_id); + +sunneed_worker_thread_result_t +sunneed_proc_monitor(void *args); + +sunneed_worker_thread_result_t +sunneed_stepperMotor_driver(void *args); + +sunneed_worker_thread_result_t +sunneed_camera_driver(void *args); + +#endif diff --git a/src/util/stepper_test.c b/src/util/stepper_test.c index 4afc172..1b5afbe 100644 --- a/src/util/stepper_test.c +++ b/src/util/stepper_test.c @@ -1,22 +1,22 @@ -#include -#include -#include -#include -#include - -int -main(void) -{ - int fd = open("/dev/stepper", O_WRONLY); - if (fd == -1) { - fprintf(stderr, "couldn't open file\n"); - return -1; - } - int dir = 90; - write(fd, &dir, sizeof(dir)); - - close(fd); - - return 0; -} - +#include +#include +#include +#include +#include + +int +main(void) +{ + int fd = open("/dev/stepper", O_WRONLY); + if (fd == -1) { + fprintf(stderr, "couldn't open file\n"); + return -1; + } + int dir = 90; + write(fd, &dir, sizeof(dir)); + + close(fd); + + return 0; +} + From fbdc4e175fd4f13256c0e7edde416969a32b09df Mon Sep 17 00:00:00 2001 From: BuildTools Date: Mon, 16 Aug 2021 16:28:02 -0400 Subject: [PATCH 39/42] update battery states --- src/sunneed_proc.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/sunneed_proc.c b/src/sunneed_proc.c index b14686c..0adf15b 100644 --- a/src/sunneed_proc.c +++ b/src/sunneed_proc.c @@ -150,12 +150,16 @@ sunneed_proc_monitor(__attribute__((unused)) void *args) { }else if(curr_soc > last_soc){ battery_state = CHARGING; }else if(curr_soc > 50){ + //51 - 90% charge using power battery_state = DISCHARGING; - }else if(curr_soc > 20){ + }else if(curr_soc >= 20){ + //20 - 49% charge battery_state = HALF; - }else if(curr_soc){ + }else if(curr_soc >= 5){ + //5 - 19% charge battery_state = LOW; }else{ + //less than 5 % charge battery_state = EMPTY; } LOG_D("battery state: %s\n", battery_state); From d526829f8dab30ef934474b058fd8ddb9837589f Mon Sep 17 00:00:00 2001 From: Jon Terry Date: Mon, 16 Aug 2021 16:40:15 -0400 Subject: [PATCH 40/42] update submodule versions to latest --- ext/SunneeD_dev_drivers | 2 +- ext/nng | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/SunneeD_dev_drivers b/ext/SunneeD_dev_drivers index f7b2df6..e95e131 160000 --- a/ext/SunneeD_dev_drivers +++ b/ext/SunneeD_dev_drivers @@ -1 +1 @@ -Subproject commit f7b2df6bf09d8d82750d63321eb58539c2a88c77 +Subproject commit e95e131ba934d0a69365a00ba446cdb4abf99350 diff --git a/ext/nng b/ext/nng index 2a841af..169221d 160000 --- a/ext/nng +++ b/ext/nng @@ -1 +1 @@ -Subproject commit 2a841afb2601e8bc8e2127cb196d64fd5d6fa8a9 +Subproject commit 169221da8d53b2ca4fda76f894bee8505887a7c6 From 21e79f1f1ae5a981214d8eb1a67d839fa417983e Mon Sep 17 00:00:00 2001 From: jonterry9 Date: Mon, 16 Aug 2021 17:07:31 -0400 Subject: [PATCH 41/42] Update nng --- ext/nng | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/nng b/ext/nng index 169221d..04cf36a 160000 --- a/ext/nng +++ b/ext/nng @@ -1 +1 @@ -Subproject commit 169221da8d53b2ca4fda76f894bee8505887a7c6 +Subproject commit 04cf36a355ac40a26bbdac3e4d9e10c258a7ea0d From 6adc4aa5db25d83ce55570ec3f760973def45c48 Mon Sep 17 00:00:00 2001 From: jonterry9 Date: Mon, 16 Aug 2021 17:07:35 -0400 Subject: [PATCH 42/42] Update SunneeD_dev_drivers --- ext/SunneeD_dev_drivers | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/SunneeD_dev_drivers b/ext/SunneeD_dev_drivers index e95e131..71bb9ed 160000 --- a/ext/SunneeD_dev_drivers +++ b/ext/SunneeD_dev_drivers @@ -1 +1 @@ -Subproject commit e95e131ba934d0a69365a00ba446cdb4abf99350 +Subproject commit 71bb9ed034a7b8f408012f0a90bca13df1aca29b