From c764b8cd459d5f76d05cf8e40d5f775371f5f141 Mon Sep 17 00:00:00 2001 From: abelino Date: Fri, 4 Oct 2024 21:01:49 -0700 Subject: [PATCH 1/3] Add erlang term based port --- lib/bacnet.ex | 71 ++++++++-- lib/bacnet/application.ex | 24 ---- mix.exs | 1 - src/arg.c | 31 ----- src/arg.h | 13 -- src/ei_client.c | 120 ----------------- src/ei_client.h | 12 -- src/ei_log.c | 52 -------- src/ei_log.h | 26 ---- src/log.c | 66 ++++++++++ src/log.h | 26 ++++ src/main.c | 120 ++--------------- src/port.c | 264 ++++++++++++++++++++++++++++++++++++++ src/port.h | 13 ++ 14 files changed, 441 insertions(+), 398 deletions(-) delete mode 100644 lib/bacnet/application.ex delete mode 100644 src/arg.c delete mode 100644 src/arg.h delete mode 100644 src/ei_client.c delete mode 100644 src/ei_client.h delete mode 100644 src/ei_log.c delete mode 100644 src/ei_log.h create mode 100644 src/log.c create mode 100644 src/log.h create mode 100644 src/port.c create mode 100644 src/port.h diff --git a/lib/bacnet.ex b/lib/bacnet.ex index c105233..fdc93f7 100644 --- a/lib/bacnet.ex +++ b/lib/bacnet.ex @@ -3,18 +3,73 @@ defmodule BACNet do BACNet client. """ + use GenServer + require Logger - defmodule Device do - @typedoc "Placeholder" - @type t :: term + defmodule State do + @doc false + + defstruct [:port] + end + + @doc """ + Start the BACnet client. + """ + @spec start_link(any, GenServer.options()) :: GenServer.on_start() + def start_link(args, opts \\ []) do + GenServer.start_link(__MODULE__, args, opts) end - @spec add_device(device :: Device.t) :: :ok | {:error, term} - def add_device(device) do - GenServer.call({:global, :bacnetd}, {:add_device, device}) + @impl GenServer + def init(_args) do + bacnetd_exe = "#{:code.priv_dir(:bacnet)}/bacnetd" + + port = Port.open( + {:spawn, bacnetd_exe}, + [ + :binary, + :use_stdio, + packet: 4, + env: [], + ] + ) + + state = %State{port: port} + + {:ok, state} end - @doc false - defdelegate ei_log(level, term), to: Logger, as: :log + @impl GenServer + def handle_call(cmd, from, state) do + encoded_term = :erlang.term_to_binary({:"$gen_call", from, cmd}) + Port.command(state.port, encoded_term) + + {:noreply, state} + end + + @impl GenServer + def handle_info({_port, {:data, data}}, state) do + try do + data + |> :erlang.binary_to_term + |> process_message + catch + _ -> nil + end + + {:noreply, state} + end + + defp process_message({:log, level, message}) do + Logger.log(level, message) + end + + defp process_message({:"$gen_reply", to, result}) do + GenServer.reply(to, result) + end + + defp process_message(unknown) do + Logger.warning("Unknown message received #{inspect(unknown)}") + end end diff --git a/lib/bacnet/application.ex b/lib/bacnet/application.ex deleted file mode 100644 index 9af3622..0000000 --- a/lib/bacnet/application.ex +++ /dev/null @@ -1,24 +0,0 @@ -defmodule BACNet.Application do - @moduledoc false - - use Application - - @impl true - def start(_type, _args) do - bacnetd_exe = "#{:code.priv_dir(:bacnet)}/bacnetd" - bacnetd_args = [ - "--cookie", Node.get_cookie |> to_string, - "--nodename", Node.self |> to_string, - ] - - children = [ - {MuonTrap.Daemon, [bacnetd_exe, bacnetd_args, []]} - ] - - Supervisor.start_link( - children, - strategy: :one_for_one, - name: BACNet.Supervisor - ) - end -end diff --git a/mix.exs b/mix.exs index 0485980..f4ff529 100644 --- a/mix.exs +++ b/mix.exs @@ -33,7 +33,6 @@ defmodule BACNet.MixProject do def application do [ extra_applications: [:logger], - mod: {BACNet.Application, []} ] end diff --git a/src/arg.c b/src/arg.c deleted file mode 100644 index eea3286..0000000 --- a/src/arg.c +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include - -#include "arg.h" - -static const struct option options[] = { - {"nodename", required_argument, 0, 'n'}, - {"cookie", required_argument, 0, 'c'}, - {0, 0, 0, 0}, -}; - -void args_parse(arg_t *args, int argc, char **argv) -{ - int opt; - int index = 0; - - while ((opt = getopt_long(argc, argv, "n:c:", options, &index)) != -1) { - switch (opt) { - case 'n': - strncpy(args->nodename, optarg, sizeof(args->nodename)); - break; - - case 'c': - strncpy(args->cookie, optarg, sizeof(args->cookie)); - break; - - default: - break; - } - } -} diff --git a/src/arg.h b/src/arg.h deleted file mode 100644 index 8d3a556..0000000 --- a/src/arg.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef ARG_H -#define ARG_H - -#include - -typedef struct { - char nodename[MAXNODELEN + 1]; - char cookie[MAXATOMLEN + 1]; -} arg_t; - -void args_parse(arg_t *args, int argc, char **argv); - -#endif /* ARG_H */ diff --git a/src/ei_client.c b/src/ei_client.c deleted file mode 100644 index ae09245..0000000 --- a/src/ei_client.c +++ /dev/null @@ -1,120 +0,0 @@ -#include -#include -#include -#include - -#include "ei_client.h" - -#define OTP_COMPAT_VER 24 -#define CNODE_NAME "bacnetd" - -struct ei_client { - bool ready; - pthread_mutex_t lock; - struct ei_cnode_s cnode; - uint32_t creation; - int port; - int fd; - char nodename[MAXNODELEN + 1]; - char cookie[MAXATOMLEN + 1]; -}; - -static struct ei_client client = { 0 }; -static void ei_free(); - -bool ei_client_config(const char *nodename, const char *cookie) -{ - if (client.ready) - return true; - - if (atexit(ei_free) != 0) - return false; - - strncpy(client.nodename, nodename, sizeof(client.nodename)); - strncpy(client.cookie, cookie, sizeof(client.cookie)); - - ei_init(); - ei_set_compat_rel(OTP_COMPAT_VER); - - pthread_mutex_init(&client.lock, NULL); - client.creation = 0; - - if (ei_connect_init(&client.cnode, CNODE_NAME, cookie, client.creation) < 0) - return false; - - client.creation++; - - client.fd = ei_connect(&client.cnode, client.nodename); - if (client.fd < 0) - return false; - - erlang_pid *pid = ei_self(&client.cnode); - if (ei_global_register(client.fd, CNODE_NAME, pid) == -1) - return false; - - client.ready = true; - - return true; -} - -bool ei_client_send(char *process_name, ei_x_buff *message) -{ - if (!client.ready) - return false; - - pthread_mutex_lock(&client.lock); - int ret = ei_reg_send( - &client.cnode, - client.fd, - process_name, - message->buff, - message->index - ); - pthread_mutex_unlock(&client.lock); - - return ret == 0 ? true : false; -} - -bool ei_client_send_to(erlang_pid *pid, ei_x_buff *message) -{ - if (!client.ready) - return false; - - pthread_mutex_lock(&client.lock); - int ret = ei_send(client.fd, pid, message->buff, message->index); - pthread_mutex_unlock(&client.lock); - - return ret == 0 ? true : false; -} - -bool ei_client_call(char *module, char *func, ei_x_buff *args, ei_x_buff *result) -{ - pthread_mutex_lock(&client.lock); - int ret = ei_rpc( - &client.cnode, - client.fd, - module, - func, - args->buff, - args->index, - result - ); - pthread_mutex_unlock(&client.lock); - - return ret == -1 ? false : true; -} - -bool ei_client_recv(erlang_msg *meta, ei_x_buff *message) -{ - pthread_mutex_lock(&client.lock); - int ret = ei_xreceive_msg(client.fd, meta, message); - pthread_mutex_unlock(&client.lock); - - return ret == ERL_ERROR ? false : true; -} - -static void ei_free() -{ - if (client.fd > 0) - ei_close_connection(client.fd); -} diff --git a/src/ei_client.h b/src/ei_client.h deleted file mode 100644 index 54fca47..0000000 --- a/src/ei_client.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef EI_CLIENT_H -#define EI_CLIENT_H - -#include - -bool ei_client_config(const char *nodename, const char *cookie); -bool ei_client_send(char *process_name, ei_x_buff *message); -bool ei_client_send_to(erlang_pid *pid, ei_x_buff *messageg); -bool ei_client_call(char *module, char *func, ei_x_buff *message, ei_x_buff *out); -bool ei_client_recv(erlang_msg *meta, ei_x_buff *message); - -#endif /* EI_CLIENT_H */ diff --git a/src/ei_log.c b/src/ei_log.c deleted file mode 100644 index 9709cc0..0000000 --- a/src/ei_log.c +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include - -#include "ei_client.h" -#include "ei_log.h" - -#define MAX_LOG_LENGTH 1024 - -static const char *level_to_str(log_level_t level); - -void ei_log(log_level_t level, const char *format, ...) -{ - va_list vargs; - va_start(vargs, format); - - char log_buffer[MAX_LOG_LENGTH]; - int log_length = vsnprintf(log_buffer, sizeof(log_buffer), format, vargs); - - ei_x_buff out; - ei_x_new(&out); - - ei_x_buff args; - ei_x_new(&args); - ei_x_encode_list_header(&args, 2); - ei_x_encode_atom(&args, level_to_str(level)); - ei_x_encode_binary(&args, log_buffer, log_length); - ei_x_encode_empty_list(&args); - - if (!ei_client_call("Elixir.BACNet", "ei_log", &args, &out)) { - char new_format[MAX_LOG_LENGTH]; - snprintf(new_format, sizeof(new_format), "%s\n", format); - vprintf(new_format, vargs); - } - - ei_x_free(&out); - ei_x_free(&args); - va_end(vargs); -} - -static const char *level_to_str(log_level_t level) -{ - switch (level) { - case EMERGENCY: return "emergency"; - case ALERT: return "alert"; - case CRITICAL: return "critical"; - case ERROR: return "error"; - case WARNING: return "warning"; - case NOTICE: return "notice"; - case INFO: return "info"; - case DEBUG: return "debug"; - } -} diff --git a/src/ei_log.h b/src/ei_log.h deleted file mode 100644 index a3b81f3..0000000 --- a/src/ei_log.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef EI_LOG_H -#define EI_LOG_H - -#define LOG_EMERGENCY(format, ...) ei_log(EMERGENCY, format, ##__VA_ARGS__) -#define LOG_ALERT(format, ...) ei_log(ALERT, format, ##__VA_ARGS__) -#define LOG_CRITICAL(format, ...) ei_log(CRITICAL, format, ##__VA_ARGS__) -#define LOG_ERROR(format, ...) ei_log(ERROR, format, ##__VA_ARGS__) -#define LOG_WARNING(format, ...) ei_log(WARNING, format, ##__VA_ARGS__) -#define LOG_NOTICE(format, ...) ei_log(NOTICE, format, ##__VA_ARGS__) -#define LOG_INFO(format, ...) ei_log(INFO, format, ##__VA_ARGS__) -#define LOG_DEBUG(format, ...) ei_log(DEBUG, format, ##__VA_ARGS__) - -typedef enum { - EMERGENCY, - ALERT, - CRITICAL, - ERROR, - WARNING, - NOTICE, - INFO, - DEBUG, -} log_level_t; - -void ei_log(log_level_t level, const char *format, ...); - -#endif /* EI_LOG_H */ diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..de0aa47 --- /dev/null +++ b/src/log.c @@ -0,0 +1,66 @@ +#include + +#include "log.h" +#include "port.h" + +#define MAX_LOG_LEN 1024 + +static const char* level_to_str(log_level_t level); + +/** + * @brief Sends a log message at a specified log level. + * + * Prepares a log message based on the provided format and arguments, and + * dispatches it through the port. If transmission fails, then the an attempt + * is made to print to stderr. + * + * @param level The log level indicating the severity of the message. + * @param format The format string for the log message. + * @param ... Additional arguments for the format string. + * + * @return Returns 0 on success, or -1 if message formatting fails. + */ +int send_log(log_level_t level, const char* format, ...) +{ + va_list args; + va_start(args, format); + + char buffer[MAX_LOG_LEN]; + int length = vsnprintf(buffer, sizeof(buffer), format, args); + if (length < 0) { + va_end(args); + return -1; + } + + ei_x_buff message; + ei_x_new_with_version(&message); + ei_x_encode_tuple_header(&message, 3); + ei_x_encode_atom(&message, "log"); + ei_x_encode_atom(&message, level_to_str(level)); + ei_x_encode_string(&message, buffer); + + if (port_send(&message) == -1) { + fprintf(stderr, "failed to send log %d\n", errno); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + } + + ei_x_free(&message); + va_end(args); + + return 0; +} + +static const char* level_to_str(log_level_t level) +{ + switch (level) { + case EMERGENCY: return "emergency"; + case ALERT: return "alert"; + case CRITICAL: return "critical"; + case ERROR: return "error"; + case WARNING: return "warning"; + case NOTICE: return "notice"; + case INFO: return "info"; + case DEBUG: return "debug"; + } +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..1a4871d --- /dev/null +++ b/src/log.h @@ -0,0 +1,26 @@ +#ifndef LOG_H +#define LOG_H + +#define LOG_EMERGENCY(format, ...) send_log(EMERGENCY, format, ##__VA_ARGS__) +#define LOG_ALERT(format, ...) send_log(ALERT, format, ##__VA_ARGS__) +#define LOG_CRITICAL(format, ...) send_log(CRITICAL, format, ##__VA_ARGS__) +#define LOG_ERROR(format, ...) send_log(ERROR, format, ##__VA_ARGS__) +#define LOG_WARNING(format, ...) send_log(WARNING, format, ##__VA_ARGS__) +#define LOG_NOTICE(format, ...) send_log(NOTICE, format, ##__VA_ARGS__) +#define LOG_INFO(format, ...) send_log(INFO, format, ##__VA_ARGS__) +#define LOG_DEBUG(format, ...) send_log(DEBUG, format, ##__VA_ARGS__) + +typedef enum { + EMERGENCY, + ALERT, + CRITICAL, + ERROR, + WARNING, + NOTICE, + INFO, + DEBUG, +} log_level_t; + +int send_log(log_level_t level, const char* format, ...); + +#endif /* LOG_H */ diff --git a/src/main.c b/src/main.c index 5f08867..ec73df0 100644 --- a/src/main.c +++ b/src/main.c @@ -1,128 +1,26 @@ -#include +#include -#include "arg.h" -#include "bacnet/datalink/dlenv.h" -#include "ei_client.h" -#include "ei_log.h" +#include "log.h" +#include "port.h" -#define STRINGIFY(x) #x -#define TOSTR(x) STRINGIFY(x) -#define ERL_TUPLE TOSTR(ERL_SMALL_TUPLE_EXT) TOSTR(ERL_SMALL_TUPLE_EXT) +void handle_request(char* buffer, int* index, ei_x_buff* reply); -static arg_t args; -static void handle_call(char *buffer, int *index, ei_x_buff *reply); - -int main(int argc, char **argv) +int main(int argc, char** argv) { - dlenv_init(); - args_parse(&args, argc, argv); - - if (!ei_client_config(args.nodename, args.cookie)) { - LOG_ERROR("bacnetd: unable to establish connection"); + if (port_start(handle_request) == -1) { + LOG_ERROR("bacnetd: failed to start port thread"); return -1; } LOG_DEBUG("bacnetd: process started"); - - while (1) { - erlang_msg meta; - ei_x_buff message; - ei_x_new(&message); - - if (!ei_client_recv(&meta, &message)) - goto cleanup; - - int index = 0; - int version = 0; - ei_term term = { 0 }; - char message_type[MAXATOMLEN] = { 0 }; - - bool is_bad_message = - ei_decode_version(message.buff, &index, &version) - || ei_decode_tuple_header(message.buff, &index, &term.size) - || (term.size < 2) - || ei_decode_atom(message.buff, &index, message_type); - - if (is_bad_message) - goto cleanup; - - if (strcmp(message_type, "$gen_call") == 0) { - erlang_pid from_pid; - erlang_ref from_ref; - - // request {:"$gen_call", {PID, [:alias | REF]}, TUPLE}} - bool is_bad_message = - ei_decode_tuple_header(message.buff, &index, &term.size) - || (term.size != 2) - || ei_decode_pid(message.buff, &index, &from_pid) - || ei_decode_list_header(message.buff, &index, &term.size) - || (term.size != 1) - || ei_decode_atom(message.buff, &index, term.value.atom_name) - || strcmp(term.value.atom_name, "alias") - || ei_decode_ref(message.buff, &index, &from_ref) - || ei_get_type(message.buff, &index, (int *)&term.ei_type, &term.size) - || memchr(ERL_TUPLE, term.ei_type, sizeof(ERL_TUPLE)) == NULL; - - if (is_bad_message) { - LOG_ERROR("Failed decoding message"); - goto cleanup; - } - - // reply {[:alias | REF], REPLY} - ei_x_buff reply; - ei_x_new_with_version(&reply); - ei_x_encode_tuple_header(&reply, 2); - ei_x_encode_list_header(&reply, 1); - ei_x_encode_atom(&reply, "alias"); - ei_x_encode_ref(&reply, &from_ref); - - handle_call(message.buff, &index, &reply); - - if (!ei_client_send_to(&from_pid, &reply)) - LOG_ERROR("Unable to send reply"); - - ei_x_free(&reply); - } - else { - LOG_WARNING("bacnetd: unknown message type %s", message_type); - } - - cleanup: - ei_x_free(&message); - } + port_wait_until_done(); return 0; } -static void add_device(char *buffer, int *index, ei_x_buff *reply) +void handle_request(char* buffer, int* index, ei_x_buff* reply) { ei_x_encode_tuple_header(reply, 2); ei_x_encode_atom(reply, "error"); ei_x_encode_atom(reply, "unimplemented"); } - -static void handle_call(char *buffer, int *index, ei_x_buff *reply) -{ - int size = 0; - char call_type[MAXATOMLEN] = { 0 }; - - bool is_bad_message = - ei_decode_tuple_header(buffer, index, &size) - || ei_decode_atom(buffer, index, call_type); - - if (is_bad_message) { - ei_x_encode_tuple_header(reply, 2); - ei_x_encode_atom(reply, "error"); - ei_x_encode_atom(reply, "bad_request"); - return; - } - - if (strcmp(call_type, "add_device") == 0) { - add_device(buffer, index, reply); - } - else { - ei_x_encode_tuple_header(reply, 2); - ei_x_encode_atom(reply, "error"); - ei_x_encode_atom(reply, "unimplemented"); - } -} diff --git a/src/port.c b/src/port.c new file mode 100644 index 0000000..72f59bf --- /dev/null +++ b/src/port.c @@ -0,0 +1,264 @@ +#include +#include +#include +#include +#include + +#include "log.h" +#include "port.h" + +#define STRINGIFY(x) #x +#define TOSTR(x) STRINGIFY(x) +#define ERL_TUPLE TOSTR(ERL_SMALL_TUPLE_EXT) TOSTR(ERL_SMALL_TUPLE_EXT) + +typedef struct { + erlang_ref from_ref; + erlang_pid from_pid; + ei_x_buff request; +} gen_call_t; + +static pthread_t read_thread_id; +static pthread_mutex_t write_lock; +static handle_request_t handle_request; + +static int read_exact(uint8_t* buffer, size_t length); +static int read_u32(uint32_t* value); +static void* read_loop(void* arg); +static int decode_gen_call(char* buffer, int* index, gen_call_t* command); + +/** + * @brief Starts the port operations and initializes resources. + * + * Creates a new thread for handling incoming requests using the specified + * callback function. + * + * @param handle_request_cb A callback function to handle incoming requests. + * + * @return Returns 0 on success, or -1 if thread creation fails. + */ +int port_start(handle_request_t handle_request_cb) +{ + if (pthread_create(&read_thread_id, NULL, &read_loop, NULL) != 0) { + LOG_DEBUG("bacnetd: failed to create port read thread"); + return -1; + } + + pthread_mutex_init(&write_lock, NULL); + handle_request = handle_request_cb; + + return 0; +} + +/** + * @brief Waits for the port operations to complete. + * + * Blocks the calling thread until the port gracefully shutsdown. + * + * @return Returns 0 on success, or -1 if stopping the thread fails. + */ +int port_wait_until_done() +{ + if (pthread_join(read_thread_id, NULL) != 0) { + LOG_DEBUG("bacnetd: failed to join thread"); + return -1; + } + + return 0; +} + +/** + * @brief Sends a message through the port. + * + * Writes the specified message to stdout. + * + * @param message A pointer to an `ei_x_buff` structure containing the message + * to be sent. + * + * @return Returns 0 on success, or -1 if an error occurs during the write + * operation. + */ +int port_send(ei_x_buff* message) +{ + uint32_t total_bytes = htonl(message->index); + size_t sent_bytes = 0; + + pthread_mutex_lock(&write_lock); + int rt = write(STDOUT_FILENO, &total_bytes, sizeof(total_bytes)); + if (rt != 4) { + goto error; + } + + while (sent_bytes < message->index) { + size_t sent = write( + STDOUT_FILENO, + message->buff + sent_bytes, + message->index - sent_bytes + ); + + if (sent < 0) { + goto error; + } + + sent_bytes += sent; + } + + pthread_mutex_unlock(&write_lock); + return 0; + +error: + pthread_mutex_unlock(&write_lock); + return -1; +} + +/** + * @brief Reads a message from the port. + * + * Reads the total byte count of an incoming message and allocates sufficient + * memory to store the message, expanding as needed. + * + * @param message A pointer to an `ei_x_buff` structure where the + * incoming message will be stored. + * + * @return Returns 0 on success, or a non-zero error code if an + * error occurs during reading or memory allocation. + */ +int port_read(ei_x_buff* message) +{ + int result_code = 0; + uint32_t total_bytes = 0; + + result_code = read_u32(&total_bytes); + if (result_code != 0) + return result_code; + + message->index = total_bytes; + if (message->index > message->buffsz) { + uint8_t* expanded_buffer = realloc(message->buff, message->index); + if (expanded_buffer == NULL) + return ENOMEM; + + message->buff = expanded_buffer; + message->buffsz = message->index; + } + + result_code = read_exact(message->buff, message->index); + if (result_code != 0) + return result_code; + + return 0; +} + +static void* read_loop(void* arg) +{ + LOG_DEBUG("bacnetd: starting read loop"); + + while (true) { + ei_x_buff message; + ei_x_new(&message); + + int return_code = port_read(&message); + if (return_code == EBADF) + break; + else if (return_code != 0) + goto cleanup; + + int index = 0; + int version = 0; + ei_term term = { 0 }; + char message_type[MAXATOMLEN] = { 0 }; + + gen_call_t call_command = { 0 }; + if (decode_gen_call(message.buff, &index, &call_command) == -1) + goto cleanup; + + // reply {:"$gen_reply", {PID, [:alias | REF]}, RESULT} + ei_x_buff reply; + ei_x_new_with_version(&reply); + ei_x_encode_tuple_header(&reply, 3); + ei_x_encode_atom(&reply, "$gen_reply"); + + ei_x_encode_tuple_header(&reply, 2); + ei_x_encode_pid(&reply, &call_command.from_pid); + ei_x_encode_list_header(&reply, 1); + ei_x_encode_atom(&reply, "alias"); + ei_x_encode_ref(&reply, &call_command.from_ref); + + ei_x_buff* request = &call_command.request; + handle_request(request->buff, &request->index, &reply); + + if (port_send(&reply) == -1) + LOG_ERROR("bacnetd: unable to send reply"); + + ei_x_free(&reply); + + cleanup: + ei_x_free(&message); + } + + pthread_exit(NULL); +} + +static int decode_gen_call(char* buffer, int* index, gen_call_t* command) +{ + int size = 0; + int term_type = 0; + char atom[MAXATOMLEN] = { 0 }; + + int version = 0; + char message_type[MAXATOMLEN] = { 0 }; + + bool is_bad_message = + ei_decode_version(buffer, index, &version) + || ei_decode_tuple_header(buffer, index, &size) + || (size < 2) + || ei_decode_atom(buffer, index, message_type) + || strcmp(message_type, "$gen_call") + || ei_decode_tuple_header(buffer, index, &size) + || (size != 2) + || ei_decode_pid(buffer, index, &command->from_pid) + || ei_decode_list_header(buffer, index, &size) + || (size != 1) + || ei_decode_atom(buffer, index, atom) + || strcmp(atom, "alias") + || ei_decode_ref(buffer, index, &command->from_ref) + || ei_get_type(buffer, index, &term_type, &size) + || memchr(ERL_TUPLE, term_type, sizeof(ERL_TUPLE)) == NULL; + + if (is_bad_message) + return -1; + + command->request.buff = buffer; + command->request.index = *index; + + return 0; +} + +static int read_exact(uint8_t* buffer, size_t length) +{ + size_t read_bytes = 0; + + while (read_bytes < length) { + size_t sent = read(STDIN_FILENO, buffer + read_bytes, length - read_bytes); + switch(sent) { + case 0: return EBADF; + case -1: return errno; + } + + read_bytes += sent; + } + + return 0; +} + +static int read_u32(uint32_t* value) +{ + uint8_t buffer[4] = { 0 }; + + int result_code = read_exact(buffer, sizeof(buffer)); + if (result_code != 0) + return result_code; + + *value = ntohl(*(uint32_t*)buffer); + + return 0; +} diff --git a/src/port.h b/src/port.h new file mode 100644 index 0000000..257ef8d --- /dev/null +++ b/src/port.h @@ -0,0 +1,13 @@ +#ifndef PORT_H +#define PORT_H + +#include + +typedef void (*handle_request_t)(char* buffer, int* index, ei_x_buff* reply); + +int port_start(handle_request_t handle_request); +int port_wait_until_done(); +int port_send(ei_x_buff* message); +int port_read(ei_x_buff* message); + +#endif /* PORT_H */ From fc413cb13849de7fb640a57b60b4fdfb4fb8f758 Mon Sep 17 00:00:00 2001 From: abelino Date: Sat, 5 Oct 2024 00:48:45 -0700 Subject: [PATCH 2/3] Update license type in mix.exs --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index f4ff529..77204fa 100644 --- a/mix.exs +++ b/mix.exs @@ -70,7 +70,7 @@ defmodule BACNet.MixProject do defp package do [ - licenses: ["Apache-2.0"], + licenses: ["MIT"], links: %{"GitHub" => "https://github.com/redwirelabs/bacnet_ex"}, maintainers: ["Abelino Romo"], files: [ From 28b3a668cc755fdf91061a1451cd63c6356e324b Mon Sep 17 00:00:00 2001 From: abelino Date: Sat, 5 Oct 2024 00:49:14 -0700 Subject: [PATCH 3/3] Workaround for broken dialyxir --- dialyzer.ignore.exs => .dialyzer_ignore.exs | 0 mix.exs | 1 - 2 files changed, 1 deletion(-) rename dialyzer.ignore.exs => .dialyzer_ignore.exs (100%) diff --git a/dialyzer.ignore.exs b/.dialyzer_ignore.exs similarity index 100% rename from dialyzer.ignore.exs rename to .dialyzer_ignore.exs diff --git a/mix.exs b/mix.exs index 77204fa..fb85a58 100644 --- a/mix.exs +++ b/mix.exs @@ -15,7 +15,6 @@ defmodule BACNet.MixProject do start_permanent: Mix.env() == :prod, test_coverage: [tool: ExCoveralls, test_task: "espec"], dialyzer: [ - ignore_warnings: "dialyzer.ignore.exs", list_unused_filters: true, plt_file: {:no_warn, plt_file_path()}, ],