diff --git a/bind/v4k.lua b/bind/v4k.lua index 2df2d3a..025fac0 100644 --- a/bind/v4k.lua +++ b/bind/v4k.lua @@ -2415,6 +2415,16 @@ GAMEPAD_GUID, GAMEPAD_NAME, char* tcp_port(int); int tcp_close(int); int tcp_debug(int); + int track_init(char const *host, char const *port); + int track_destroy(void); + int track_event(char const *event_id, char const *user_id, char const *json_payload); + int track_ident(char const *user_id, char const *traits); + int track_group(char const *user_id, char const *group_id, char const *traits); +typedef struct { +char const *key; +char const *val; +} track_prop; + int track_event_props(char const *event_id, char const *user_id, const track_prop *props); enum { NETWORK_BIND = 2, NETWORK_CONNECT = 4, NETWORK_NOFAIL = 8 }; void network_create(unsigned max_clients, const char *ip, const char *port, unsigned flags); enum { NETWORK_SEND = 2, NETWORK_RECV = 4 }; diff --git a/engine/joint/v4k.h b/engine/joint/v4k.h index 0ec0e4c..80ae694 100644 --- a/engine/joint/v4k.h +++ b/engine/joint/v4k.h @@ -16490,6 +16490,67 @@ API int tcp_debug(int); // toggle traffic monitoring on/off for given socket //API int tcp_crypt(int,uint64_t); // set shared secret #line 0 +#line 1 "engine/split/v4k_track.h" +#ifndef TRACK_SEND_BUFSIZE +#define TRACK_SEND_BUFSIZE 576 +#endif + + //~ Errors +#define TRACK_ERROR_INIT_FAIL 1 +#define TRACK_ERROR_SOCKET_FAIL 2 +#define TRACK_ERROR_SOCKET_INVALID 3 +#define TRACK_ERROR_BUFFER_FULL 4 +#define TRACK_ERROR_SEND_FAIL 5 +#define TRACK_ERROR_INPUT_INVALID 6 + +/// Initialises telemetry and connects to the specified endpoint. +/// return: error code +/// host: IP address / domain of the endpoint +/// port: service name / port +/// see: track_event, track_ident, track_group +API int track_init(char const *host, char const *port); + +/// Destroys the currently established telemetry socket. +/// return: error code +/// No parameters needed for this function. +API int track_destroy(void); + +/// Sends an EVENT message to the server. +/// return: error code +/// event_id: Identifier for the event type. +/// user_id: Identifier for the user. +/// json_payload: JSON-formatted metadata for the event. +API int track_event(char const *event_id, char const *user_id, char const *json_payload); + +/// Sends user identification to the server. +/// return: error code +/// user_id: Identifier for the user. +/// traits: JSON-formatted traits or attributes of the user. +API int track_ident(char const *user_id, char const *traits); + +/// Associates a user to a group. +/// return: error code +/// user_id: Identifier for the user. +/// group_id: Identifier for the group. +/// traits: JSON-formatted traits or attributes of the group. +API int track_group(char const *user_id, char const *group_id, char const *traits); + +//~ Event utilities + +/// Structure to represent key-value pairs for event properties. +typedef struct { + char const *key; + char const *val; +} track_prop; + +/// Sends an EVENT message with custom properties. +/// return: error code +/// event_id: Identifier for the event type. +/// user_id: Identifier for the user. +/// props: Array of key-value pairs. Terminates when key is set to NULL. +API int track_event_props(char const *event_id, char const *user_id, const track_prop *props); +#line 0 + #line 1 "engine/split/v4k_netsync.h" // high-level, socket-less networking api. inspired by Quake, MPI and RenderBuckets theories. // - rlyeh, public domain @@ -342467,6 +342528,143 @@ static void network_init() { } #line 0 +#line 1 "engine/split/v4k_track.c" +static __thread int track__sock = -1; + +//~ Lifecycle methods +int track_init(char const *host, char const *port) { + if (track__sock > -1) { + swrapClose(track__sock); + track__sock = -1; + } + + track__sock = swrapSocket(SWRAP_UDP, SWRAP_CONNECT, SWRAP_DEFAULT, host, port); + + if (track__sock == -1) { + return -TRACK_ERROR_SOCKET_FAIL; + } + + return 0; +} +int track_destroy(void) { + if (track__sock > -1) swrapClose(track__sock); + track__sock = -1; + return 0; +} + +//~ Buffer utilities +static __thread char track__buffer[TRACK_SEND_BUFSIZE+1]; +static __thread int track__buffer_len = 0; +static __thread int track__errno = 0; + +static void track__buffer_flush(void) { + track__buffer_len = 0; +} + +static int track__buffer_appendc(char *buf, int *len, char const *str) { + int size = (int)strlen(str); + if (*len+size > TRACK_SEND_BUFSIZE) + return -TRACK_ERROR_BUFFER_FULL; + memcpy(buf+*len, str, size); + *len += size; + return 0; +} + +#define TRACK__APPEND_SAFE_EX(buf, len, xx)\ + track__errno = track__buffer_appendc(buf, len, xx);\ + if (track__errno) return track__errno; + + +#define TRACK__APPEND_SAFE(xx)\ + TRACK__APPEND_SAFE_EX(track__buffer, &track__buffer_len, xx); + +//~ Event tracking +int track_event(char const *event_id, char const *user_id, char const *json_payload) { + if (track__sock == -1) + return -TRACK_ERROR_SOCKET_INVALID; + if (!event_id || !user_id || !json_payload) + return -TRACK_ERROR_INPUT_INVALID; + track__buffer_flush(); + + TRACK__APPEND_SAFE("{\"userId\":\""); + TRACK__APPEND_SAFE(user_id); + TRACK__APPEND_SAFE("\",\"event\":\""); + TRACK__APPEND_SAFE(event_id); + TRACK__APPEND_SAFE("\",\"properties\":"); + TRACK__APPEND_SAFE(json_payload); + TRACK__APPEND_SAFE("}"); + + if (!swrapSend(track__sock, track__buffer, track__buffer_len)) + return -TRACK_ERROR_SEND_FAIL; + return 0; +} + +int track_ident(char const *user_id, char const *traits) { + if (track__sock == -1) + return -TRACK_ERROR_SOCKET_INVALID; + if (!user_id || !traits) + return -TRACK_ERROR_INPUT_INVALID; + track__buffer_flush(); + + TRACK__APPEND_SAFE("{\"userId\":\""); + TRACK__APPEND_SAFE(user_id); + TRACK__APPEND_SAFE("\",\"traits\":"); + TRACK__APPEND_SAFE(traits); + TRACK__APPEND_SAFE("}"); + + if (!swrapSend(track__sock, track__buffer, track__buffer_len)) + return -TRACK_ERROR_SEND_FAIL; + return 0; +} + +int track_group(char const *user_id, char const *group_id, char const *traits) { + if (track__sock == -1) + return -TRACK_ERROR_SOCKET_INVALID; + if (!user_id || !group_id || !traits) + return -TRACK_ERROR_INPUT_INVALID; + track__buffer_flush(); + + TRACK__APPEND_SAFE("{\"userId\":\""); + TRACK__APPEND_SAFE(user_id); + TRACK__APPEND_SAFE("\",\"groupId\":\""); + TRACK__APPEND_SAFE(group_id); + TRACK__APPEND_SAFE("\",\"traits\":"); + TRACK__APPEND_SAFE(traits); + TRACK__APPEND_SAFE("}"); + + if (!swrapSend(track__sock, track__buffer, track__buffer_len)) + return -TRACK_ERROR_SEND_FAIL; + return 0; +} + +int track_event_props(char const *event_id, char const *user_id, const track_prop *props) { + static char buf[TRACK_SEND_BUFSIZE+1] = {0}; + int len = 0; + + + if (!props) + return track_event(event_id, user_id, ""); + + TRACK__APPEND_SAFE_EX(buf, &len, "{"); + while (props->key) { + TRACK__APPEND_SAFE_EX(buf, &len, "\""); + TRACK__APPEND_SAFE_EX(buf, &len, props->key); + TRACK__APPEND_SAFE_EX(buf, &len, "\":"); + TRACK__APPEND_SAFE_EX(buf, &len, props->val); + ++props; + if (props->key) { + TRACK__APPEND_SAFE_EX(buf, &len, ","); + } + } + TRACK__APPEND_SAFE_EX(buf, &len, "}"); + + return track_event(event_id, user_id, buf); +} + +#undef TRACK__APPEND_SAFE +#undef TRACK__APPEND_SAFE_EX +#line 0 + #line 1 "engine/split/v4k_netsync.c" typedef void* (*rpc_function)(); diff --git a/engine/split/v4k.c.inl b/engine/split/v4k.c.inl index 4adfe3e..0cf62e4 100644 --- a/engine/split/v4k.c.inl +++ b/engine/split/v4k.c.inl @@ -143,6 +143,8 @@ API void *ui_handle(); {{FILE:v4k_network.c}} +{{FILE:v4k_track.c}} + {{FILE:v4k_netsync.c}} {{FILE:v4k_pack.c}} diff --git a/engine/split/v4k.h.inl b/engine/split/v4k.h.inl index 2a1d9c3..1770504 100644 --- a/engine/split/v4k.h.inl +++ b/engine/split/v4k.h.inl @@ -133,6 +133,8 @@ extern "C" { {{FILE:v4k_network.h}} +{{FILE:v4k_track.h}} + {{FILE:v4k_netsync.h}} {{FILE:v4k_obj.h}} diff --git a/engine/split/v4k_track.c b/engine/split/v4k_track.c new file mode 100644 index 0000000..d0cce32 --- /dev/null +++ b/engine/split/v4k_track.c @@ -0,0 +1,134 @@ +static __thread int track__sock = -1; + +//~ Lifecycle methods +int track_init(char const *host, char const *port) { + if (track__sock > -1) { + swrapClose(track__sock); + track__sock = -1; + } + + track__sock = swrapSocket(SWRAP_UDP, SWRAP_CONNECT, SWRAP_DEFAULT, host, port); + + if (track__sock == -1) { + return -TRACK_ERROR_SOCKET_FAIL; + } + + return 0; +} +int track_destroy(void) { + if (track__sock > -1) swrapClose(track__sock); + track__sock = -1; + return 0; +} + +//~ Buffer utilities +static __thread char track__buffer[TRACK_SEND_BUFSIZE+1]; +static __thread int track__buffer_len = 0; +static __thread int track__errno = 0; + +static void track__buffer_flush(void) { + track__buffer_len = 0; +} + +static int track__buffer_appendc(char *buf, int *len, char const *str) { + int size = (int)strlen(str); + if (*len+size > TRACK_SEND_BUFSIZE) + return -TRACK_ERROR_BUFFER_FULL; + memcpy(buf+*len, str, size); + *len += size; + return 0; +} + +#define TRACK__APPEND_SAFE_EX(buf, len, xx)\ + track__errno = track__buffer_appendc(buf, len, xx);\ + if (track__errno) return track__errno; + + +#define TRACK__APPEND_SAFE(xx)\ + TRACK__APPEND_SAFE_EX(track__buffer, &track__buffer_len, xx); + +//~ Event tracking +int track_event(char const *event_id, char const *user_id, char const *json_payload) { + if (track__sock == -1) + return -TRACK_ERROR_SOCKET_INVALID; + if (!event_id || !user_id || !json_payload) + return -TRACK_ERROR_INPUT_INVALID; + track__buffer_flush(); + + TRACK__APPEND_SAFE("{\"userId\":\""); + TRACK__APPEND_SAFE(user_id); + TRACK__APPEND_SAFE("\",\"event\":\""); + TRACK__APPEND_SAFE(event_id); + TRACK__APPEND_SAFE("\",\"properties\":"); + TRACK__APPEND_SAFE(json_payload); + TRACK__APPEND_SAFE("}"); + + if (!swrapSend(track__sock, track__buffer, track__buffer_len)) + return -TRACK_ERROR_SEND_FAIL; + return 0; +} + +int track_ident(char const *user_id, char const *traits) { + if (track__sock == -1) + return -TRACK_ERROR_SOCKET_INVALID; + if (!user_id || !traits) + return -TRACK_ERROR_INPUT_INVALID; + track__buffer_flush(); + + TRACK__APPEND_SAFE("{\"userId\":\""); + TRACK__APPEND_SAFE(user_id); + TRACK__APPEND_SAFE("\",\"traits\":"); + TRACK__APPEND_SAFE(traits); + TRACK__APPEND_SAFE("}"); + + if (!swrapSend(track__sock, track__buffer, track__buffer_len)) + return -TRACK_ERROR_SEND_FAIL; + return 0; +} + +int track_group(char const *user_id, char const *group_id, char const *traits) { + if (track__sock == -1) + return -TRACK_ERROR_SOCKET_INVALID; + if (!user_id || !group_id || !traits) + return -TRACK_ERROR_INPUT_INVALID; + track__buffer_flush(); + + TRACK__APPEND_SAFE("{\"userId\":\""); + TRACK__APPEND_SAFE(user_id); + TRACK__APPEND_SAFE("\",\"groupId\":\""); + TRACK__APPEND_SAFE(group_id); + TRACK__APPEND_SAFE("\",\"traits\":"); + TRACK__APPEND_SAFE(traits); + TRACK__APPEND_SAFE("}"); + + if (!swrapSend(track__sock, track__buffer, track__buffer_len)) + return -TRACK_ERROR_SEND_FAIL; + return 0; +} + +int track_event_props(char const *event_id, char const *user_id, const track_prop *props) { + static char buf[TRACK_SEND_BUFSIZE+1] = {0}; + int len = 0; + + + if (!props) + return track_event(event_id, user_id, ""); + + TRACK__APPEND_SAFE_EX(buf, &len, "{"); + while (props->key) { + TRACK__APPEND_SAFE_EX(buf, &len, "\""); + TRACK__APPEND_SAFE_EX(buf, &len, props->key); + TRACK__APPEND_SAFE_EX(buf, &len, "\":"); + TRACK__APPEND_SAFE_EX(buf, &len, props->val); + ++props; + if (props->key) { + TRACK__APPEND_SAFE_EX(buf, &len, ","); + } + } + TRACK__APPEND_SAFE_EX(buf, &len, "}"); + + return track_event(event_id, user_id, buf); +} + +#undef TRACK__APPEND_SAFE +#undef TRACK__APPEND_SAFE_EX diff --git a/engine/split/v4k_track.h b/engine/split/v4k_track.h new file mode 100644 index 0000000..8153b10 --- /dev/null +++ b/engine/split/v4k_track.h @@ -0,0 +1,58 @@ +#ifndef TRACK_SEND_BUFSIZE +#define TRACK_SEND_BUFSIZE 576 +#endif + + //~ Errors +#define TRACK_ERROR_INIT_FAIL 1 +#define TRACK_ERROR_SOCKET_FAIL 2 +#define TRACK_ERROR_SOCKET_INVALID 3 +#define TRACK_ERROR_BUFFER_FULL 4 +#define TRACK_ERROR_SEND_FAIL 5 +#define TRACK_ERROR_INPUT_INVALID 6 + +/// Initialises telemetry and connects to the specified endpoint. +/// return: error code +/// host: IP address / domain of the endpoint +/// port: service name / port +/// see: track_event, track_ident, track_group +API int track_init(char const *host, char const *port); + +/// Destroys the currently established telemetry socket. +/// return: error code +/// No parameters needed for this function. +API int track_destroy(void); + +/// Sends an EVENT message to the server. +/// return: error code +/// event_id: Identifier for the event type. +/// user_id: Identifier for the user. +/// json_payload: JSON-formatted metadata for the event. +API int track_event(char const *event_id, char const *user_id, char const *json_payload); + +/// Sends user identification to the server. +/// return: error code +/// user_id: Identifier for the user. +/// traits: JSON-formatted traits or attributes of the user. +API int track_ident(char const *user_id, char const *traits); + +/// Associates a user to a group. +/// return: error code +/// user_id: Identifier for the user. +/// group_id: Identifier for the group. +/// traits: JSON-formatted traits or attributes of the group. +API int track_group(char const *user_id, char const *group_id, char const *traits); + +//~ Event utilities + +/// Structure to represent key-value pairs for event properties. +typedef struct { + char const *key; + char const *val; +} track_prop; + +/// Sends an EVENT message with custom properties. +/// return: error code +/// event_id: Identifier for the event type. +/// user_id: Identifier for the user. +/// props: Array of key-value pairs. Terminates when key is set to NULL. +API int track_event_props(char const *event_id, char const *user_id, const track_prop *props); diff --git a/engine/v4k.c b/engine/v4k.c index c6253dc..f3fb7d3 100644 --- a/engine/v4k.c +++ b/engine/v4k.c @@ -10579,6 +10579,143 @@ static void network_init() { } #line 0 +#line 1 "engine/split/v4k_track.c" +static __thread int track__sock = -1; + +//~ Lifecycle methods +int track_init(char const *host, char const *port) { + if (track__sock > -1) { + swrapClose(track__sock); + track__sock = -1; + } + + track__sock = swrapSocket(SWRAP_UDP, SWRAP_CONNECT, SWRAP_DEFAULT, host, port); + + if (track__sock == -1) { + return -TRACK_ERROR_SOCKET_FAIL; + } + + return 0; +} +int track_destroy(void) { + if (track__sock > -1) swrapClose(track__sock); + track__sock = -1; + return 0; +} + +//~ Buffer utilities +static __thread char track__buffer[TRACK_SEND_BUFSIZE+1]; +static __thread int track__buffer_len = 0; +static __thread int track__errno = 0; + +static void track__buffer_flush(void) { + track__buffer_len = 0; +} + +static int track__buffer_appendc(char *buf, int *len, char const *str) { + int size = (int)strlen(str); + if (*len+size > TRACK_SEND_BUFSIZE) + return -TRACK_ERROR_BUFFER_FULL; + memcpy(buf+*len, str, size); + *len += size; + return 0; +} + +#define TRACK__APPEND_SAFE_EX(buf, len, xx)\ + track__errno = track__buffer_appendc(buf, len, xx);\ + if (track__errno) return track__errno; + + +#define TRACK__APPEND_SAFE(xx)\ + TRACK__APPEND_SAFE_EX(track__buffer, &track__buffer_len, xx); + +//~ Event tracking +int track_event(char const *event_id, char const *user_id, char const *json_payload) { + if (track__sock == -1) + return -TRACK_ERROR_SOCKET_INVALID; + if (!event_id || !user_id || !json_payload) + return -TRACK_ERROR_INPUT_INVALID; + track__buffer_flush(); + + TRACK__APPEND_SAFE("{\"userId\":\""); + TRACK__APPEND_SAFE(user_id); + TRACK__APPEND_SAFE("\",\"event\":\""); + TRACK__APPEND_SAFE(event_id); + TRACK__APPEND_SAFE("\",\"properties\":"); + TRACK__APPEND_SAFE(json_payload); + TRACK__APPEND_SAFE("}"); + + if (!swrapSend(track__sock, track__buffer, track__buffer_len)) + return -TRACK_ERROR_SEND_FAIL; + return 0; +} + +int track_ident(char const *user_id, char const *traits) { + if (track__sock == -1) + return -TRACK_ERROR_SOCKET_INVALID; + if (!user_id || !traits) + return -TRACK_ERROR_INPUT_INVALID; + track__buffer_flush(); + + TRACK__APPEND_SAFE("{\"userId\":\""); + TRACK__APPEND_SAFE(user_id); + TRACK__APPEND_SAFE("\",\"traits\":"); + TRACK__APPEND_SAFE(traits); + TRACK__APPEND_SAFE("}"); + + if (!swrapSend(track__sock, track__buffer, track__buffer_len)) + return -TRACK_ERROR_SEND_FAIL; + return 0; +} + +int track_group(char const *user_id, char const *group_id, char const *traits) { + if (track__sock == -1) + return -TRACK_ERROR_SOCKET_INVALID; + if (!user_id || !group_id || !traits) + return -TRACK_ERROR_INPUT_INVALID; + track__buffer_flush(); + + TRACK__APPEND_SAFE("{\"userId\":\""); + TRACK__APPEND_SAFE(user_id); + TRACK__APPEND_SAFE("\",\"groupId\":\""); + TRACK__APPEND_SAFE(group_id); + TRACK__APPEND_SAFE("\",\"traits\":"); + TRACK__APPEND_SAFE(traits); + TRACK__APPEND_SAFE("}"); + + if (!swrapSend(track__sock, track__buffer, track__buffer_len)) + return -TRACK_ERROR_SEND_FAIL; + return 0; +} + +int track_event_props(char const *event_id, char const *user_id, const track_prop *props) { + static char buf[TRACK_SEND_BUFSIZE+1] = {0}; + int len = 0; + + + if (!props) + return track_event(event_id, user_id, ""); + + TRACK__APPEND_SAFE_EX(buf, &len, "{"); + while (props->key) { + TRACK__APPEND_SAFE_EX(buf, &len, "\""); + TRACK__APPEND_SAFE_EX(buf, &len, props->key); + TRACK__APPEND_SAFE_EX(buf, &len, "\":"); + TRACK__APPEND_SAFE_EX(buf, &len, props->val); + ++props; + if (props->key) { + TRACK__APPEND_SAFE_EX(buf, &len, ","); + } + } + TRACK__APPEND_SAFE_EX(buf, &len, "}"); + + return track_event(event_id, user_id, buf); +} + +#undef TRACK__APPEND_SAFE +#undef TRACK__APPEND_SAFE_EX +#line 0 + #line 1 "engine/split/v4k_netsync.c" typedef void* (*rpc_function)(); diff --git a/engine/v4k.h b/engine/v4k.h index 6925c85..917cd07 100644 --- a/engine/v4k.h +++ b/engine/v4k.h @@ -2557,6 +2557,67 @@ API int tcp_debug(int); // toggle traffic monitoring on/off for given socket //API int tcp_crypt(int,uint64_t); // set shared secret #line 0 +#line 1 "engine/split/v4k_track.h" +#ifndef TRACK_SEND_BUFSIZE +#define TRACK_SEND_BUFSIZE 576 +#endif + + //~ Errors +#define TRACK_ERROR_INIT_FAIL 1 +#define TRACK_ERROR_SOCKET_FAIL 2 +#define TRACK_ERROR_SOCKET_INVALID 3 +#define TRACK_ERROR_BUFFER_FULL 4 +#define TRACK_ERROR_SEND_FAIL 5 +#define TRACK_ERROR_INPUT_INVALID 6 + +/// Initialises telemetry and connects to the specified endpoint. +/// return: error code +/// host: IP address / domain of the endpoint +/// port: service name / port +/// see: track_event, track_ident, track_group +API int track_init(char const *host, char const *port); + +/// Destroys the currently established telemetry socket. +/// return: error code +/// No parameters needed for this function. +API int track_destroy(void); + +/// Sends an EVENT message to the server. +/// return: error code +/// event_id: Identifier for the event type. +/// user_id: Identifier for the user. +/// json_payload: JSON-formatted metadata for the event. +API int track_event(char const *event_id, char const *user_id, char const *json_payload); + +/// Sends user identification to the server. +/// return: error code +/// user_id: Identifier for the user. +/// traits: JSON-formatted traits or attributes of the user. +API int track_ident(char const *user_id, char const *traits); + +/// Associates a user to a group. +/// return: error code +/// user_id: Identifier for the user. +/// group_id: Identifier for the group. +/// traits: JSON-formatted traits or attributes of the group. +API int track_group(char const *user_id, char const *group_id, char const *traits); + +//~ Event utilities + +/// Structure to represent key-value pairs for event properties. +typedef struct { + char const *key; + char const *val; +} track_prop; + +/// Sends an EVENT message with custom properties. +/// return: error code +/// event_id: Identifier for the event type. +/// user_id: Identifier for the user. +/// props: Array of key-value pairs. Terminates when key is set to NULL. +API int track_event_props(char const *event_id, char const *user_id, const track_prop *props); +#line 0 + #line 1 "engine/split/v4k_netsync.h" // high-level, socket-less networking api. inspired by Quake, MPI and RenderBuckets theories. // - rlyeh, public domain