The Pubnub C-core library. It's home is on https://github.com/pubnub/c_core, this is a copy
Dependents: Pubnub_c_core_mbed2_pal Pubnub_c_core_mbed2_pal Pubnub_c_core_mbed2_pal2
Diff: pubnub_ccore.cpp
- Revision:
- 0:d13755cfb705
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pubnub_ccore.cpp Thu Nov 10 22:20:11 2016 +0000 @@ -0,0 +1,748 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pubnub_ccore.h" +#include "pubnub_version.h" +#include "pubnub_assert.h" +#include "pubnub_internal.h" +#include "pubnub_json_parse.h" +#include "pubnub_log.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + + +void pbcc_init(struct pbcc_context *p, const char *publish_key, const char *subscribe_key) +{ + p->publish_key = publish_key; + p->subscribe_key = subscribe_key; + p->timetoken[0] = '0'; + p->timetoken[1] = '\0'; + p->uuid = p->auth = NULL; + p->msg_ofs = p->msg_end = 0; + if (PUBNUB_DYNAMIC_REPLY_BUFFER) { + p->http_reply = NULL; + } + +#if PUBNUB_CRYPTO_API + p->secret_key = NULL; +#endif +} + + +void pbcc_deinit(struct pbcc_context *p) +{ + if (PUBNUB_DYNAMIC_REPLY_BUFFER) { + if (p->http_reply != NULL) { + free(p->http_reply); + p->http_reply = NULL; + } + } +} + + +int pbcc_realloc_reply_buffer(struct pbcc_context *p, unsigned bytes) +{ +#if PUBNUB_DYNAMIC_REPLY_BUFFER + char *newbuf = (char*)realloc(p->http_reply, bytes + 1); + if (NULL == newbuf) { + return -1; + } + p->http_reply = newbuf; + return 0; +#else + if (bytes < sizeof p->http_reply / sizeof p->http_reply[0]) { + return 0; + } + return -1; +#endif +} + + +char const *pbcc_get_msg(struct pbcc_context *pb) +{ + if (pb->msg_ofs < pb->msg_end) { + char const *rslt = pb->http_reply + pb->msg_ofs; + pb->msg_ofs += strlen(rslt); + if (pb->msg_ofs++ <= pb->msg_end) { + return rslt; + } + } + + return NULL; +} + + +char const *pbcc_get_channel(struct pbcc_context *pb) +{ + if (pb->chan_ofs < pb->chan_end) { + char const* rslt = pb->http_reply + pb->chan_ofs; + pb->chan_ofs += strlen(rslt); + if (pb->chan_ofs++ <= pb->chan_end) { + return rslt; + } + } + + return NULL; +} + + +void pbcc_set_uuid(struct pbcc_context *pb, const char *uuid) +{ + pb->uuid = uuid; +} + + +void pbcc_set_auth(struct pbcc_context *pb, const char *auth) +{ + pb->auth = auth; +} + + +/* Find the beginning of a JSON string that comes after comma and ends + * at @c &buf[len]. + * @return position (index) of the found start or -1 on error. */ +static int find_string_start(char const *buf, int len) +{ + int i; + for (i = len-1; i > 0; --i) { + if (buf[i] == '"') { + return (buf[i-1] == ',') ? i : -1; + } + } + return -1; +} + + +/** Split @p buf string containing a JSON array (with arbitrary + * contents) to multiple NUL-terminated C strings, in-place. + */ +static bool split_array(char *buf) +{ + bool escaped = false; + bool in_string = false; + int bracket_level = 0; + + for (; *buf != '\0'; ++buf) { + if (escaped) { + escaped = false; + } + else if ('"' == *buf) { + in_string = !in_string; + } + else if (in_string) { + escaped = ('\\' == *buf); + } + else { + switch (*buf) { + case '[': case '{': bracket_level++; break; + case ']': case '}': bracket_level--; break; + /* if at root, split! */ + case ',': if (bracket_level == 0) { *buf = '\0'; } break; + default: break; + } + } + } + + PUBNUB_LOG_TRACE("escaped = %d, in_string = %d, bracket_level = %d\n", escaped, in_string, bracket_level); + return !(escaped || in_string || (bracket_level > 0)); +} + + +static int simple_parse_response(struct pbcc_context *p) +{ + char *reply = p->http_reply; + int replylen = p->http_buf_len; + if (replylen < 2) { + return -1; + } + if ((reply[0] != '[') || (reply[replylen-1] != ']')) { + return -1; + } + + p->chan_ofs = 0; + p->chan_end = 0; + + p->msg_ofs = 1; + p->msg_end = replylen - 1; + reply[replylen-1] = '\0'; + + return split_array(reply + p->msg_ofs) ? 0 : -1; +} + + +enum pubnub_res pbcc_parse_publish_response(struct pbcc_context *p) +{ + char *reply = p->http_reply; + int replylen = p->http_buf_len; + if (replylen < 2) { + return PNR_FORMAT_ERROR; + } + if ((reply[0] != '[') || (reply[replylen-1] != ']')) { + return PNR_FORMAT_ERROR; + } + + p->chan_ofs = p->chan_end = 0; + p->msg_ofs = p->msg_end = 0; + + reply[replylen-1] = '\0'; + + if (split_array(reply + 1)) { + if (1 != strtol(reply+1, NULL, 10)) { + return PNR_PUBLISH_FAILED; + } + return PNR_OK; + } + else { + return PNR_FORMAT_ERROR; + } +} + +int pbcc_parse_time_response(struct pbcc_context *p) +{ + return simple_parse_response(p); +} + + +int pbcc_parse_history_response(struct pbcc_context *p) +{ + return simple_parse_response(p); +} + + +int pbcc_parse_presence_response(struct pbcc_context *p) +{ + char *reply = p->http_reply; + int replylen = p->http_buf_len; + if (replylen < 2) { + return -1; + } + if ((reply[0] != '{') || (reply[replylen-1] != '}')) { + return -1; + } + + p->chan_ofs = p->chan_end = 0; + + p->msg_ofs = 0; + p->msg_end = replylen; + + return 0; +} + + +enum pubnub_res pbcc_parse_channel_registry_response(struct pbcc_context *p) +{ + enum pbjson_object_name_parse_result result; + struct pbjson_elem el; + struct pbjson_elem found; + + el.start = p->http_reply; + el.end = p->http_reply + p->http_buf_len; + p->chan_ofs = 0; + p->chan_end = p->http_buf_len; + + p->msg_ofs = p->msg_end = 0; + + /* We should probably also check that there is a key "service" + with value "channel-registry". Maybe even that there is a key + "status" (with value 200). + */ + result = pbjson_get_object_value(&el, "error", &found); + if (jonmpOK == result) { + if (pbjson_elem_equals_string(&found, "false")) { + return PNR_OK; + } + else { + return PNR_CHANNEL_REGISTRY_ERROR; + } + } + else { + return PNR_FORMAT_ERROR; + } +} + + +int pbcc_parse_subscribe_response(struct pbcc_context *p) +{ + int i; + int previous_i; + unsigned time_token_length; + char *reply = p->http_reply; + int replylen = p->http_buf_len; + if (replylen < 2) { + return -1; + } + if (reply[replylen-1] != ']' && replylen > 2) { + replylen -= 2; /* XXX: this seems required by Manxiang */ + } + if ((reply[0] != '[') || (reply[replylen-1] != ']') || (reply[replylen-2] != '"')) { + return -1; + } + + /* Extract the last argument. */ + previous_i = replylen - 2; + i = find_string_start(reply, previous_i); + if (i < 0) { + return -1; + } + reply[replylen - 2] = 0; + + /* Now, the last argument may either be a timetoken, a channel group list + or a channel list. */ + if (reply[i-2] == '"') { + int k; + /* It is a channel list, there is another string argument in front + * of us. Process the channel list ... */ + for (k = replylen - 2; k > i+1; --k) { + if (reply[k] == ',') { + reply[k] = '\0'; + } + } + + /* The previous argument is either a timetoken or a channel group + list. */ + reply[i-2] = '\0'; + p->chan_ofs = i+1; + p->chan_end = replylen - 1; + previous_i = i-2; + i = find_string_start(reply, previous_i); + if (i < 0) { + p->chan_ofs = 0; + p->chan_end = 0; + return -1; + } + if (reply[i-2] == '"') { + /* It is a channel group list. For now, we shall skip it. In + the future, we may process it like we do the channel list. + */ + reply[i-2] = '\0'; + previous_i = i-2; + i = find_string_start(reply, previous_i); + if (i < 0) { + return -1; + } + } + } + else { + p->chan_ofs = 0; + p->chan_end = 0; + } + + /* Now, `i` points to: + * [[1,2,3],"5678"] + * [[1,2,3],"5678","a,b,c"] + * [[1,2,3],"5678","gr-a,gr-b,gr-c","a,b,c"] + * ^-- here */ + + /* Setup timetoken. */ + time_token_length = previous_i - (i+1); + if (time_token_length >= sizeof p->timetoken) { + p->timetoken[0] = '\0'; + return -1; + } + memcpy(p->timetoken, reply + i+1, time_token_length+1); + + /* terminate the [] message array (before the `]`!) */ + reply[i-2] = 0; + + /* Set up the message list - offset, length and NUL-characters + * splitting the messages. */ + p->msg_ofs = 2; + p->msg_end = i-2; + + return split_array(reply + p->msg_ofs) ? 0 : -1; +} + + +static enum pubnub_res append_url_param(struct pbcc_context *pb, char const *param_name, size_t param_name_len, char const *param_val, char separator) +{ + size_t param_val_len = strlen(param_val); + if (pb->http_buf_len + 1 + param_name_len + 1 + param_val_len > sizeof pb->http_buf) { + return PNR_TX_BUFF_TOO_SMALL; + } + + pb->http_buf[pb->http_buf_len++] = separator; + memcpy(pb->http_buf + pb->http_buf_len, param_name, param_name_len); + pb->http_buf_len += param_name_len; + pb->http_buf[pb->http_buf_len++] = '='; + memcpy(pb->http_buf + pb->http_buf_len, param_val, param_val_len+1); + pb->http_buf_len += param_val_len; + + return PNR_OK; +} + + +#define APPEND_URL_PARAM_M(pbc, name, var, separator) \ + if ((var) != NULL) { \ + const char param_[] = name; \ + enum pubnub_res rslt_ = append_url_param((pbc), param_, sizeof param_ - 1, (var), (separator)); \ + if (rslt_ != PNR_OK) { \ + return rslt_; \ + } \ + } + + +#define APPEND_URL_PARAM_INT_M(pbc, name, var, separator) \ + do { char v_[20]; \ + snprintf(v_, sizeof v_, "%d", (var)); \ + APPEND_URL_PARAM_M(pbc, name, v_, separator); \ + } while (0) + + +#define APPEND_URL_PARAM_UNSIGNED_M(pbc, name, var, separator) \ + do { char v_[20]; \ + snprintf(v_, sizeof v_, "%u", (var)); \ + APPEND_URL_PARAM_M(pbc, name, v_, separator); \ + } while (0) + +#define APPEND_URL_OPT_PARAM_UNSIGNED_M(pbc, name, var, separator) \ + if ((var) != NULL) { char v_[20]; \ + snprintf(v_, sizeof v_, "%u", *(var)); \ + APPEND_URL_PARAM_M(pbc, name, v_, separator); \ + } while (0) + + +#define APPEND_URL_PARAM_BOOL_M(pbc, name, var, separator) \ + do { char const *v_ = (var) ? "true" : "false"; \ + APPEND_URL_PARAM_M(pbc, name, v_, separator); \ + } while (0) + +#define APPEND_URL_PARAM_TRIBOOL_M(pbc, name, var, separator) \ + if ((var) != pbccNotSet) { \ + char const *v_ = (var) ? "1" : "0"; \ + APPEND_URL_PARAM_M(pbc, name, v_, separator); \ + } + + +enum pubnub_res pbcc_publish_prep(struct pbcc_context *pb, const char *channel, const char *message, bool store_in_history, bool eat_after_reading) +{ + char const *const uname = pubnub_uname(); + char const *pmessage = message; + pb->http_content_len = 0; + + pb->http_buf_len = snprintf( + pb->http_buf, sizeof pb->http_buf, + "/publish/%s/%s/0/%s/0/", + pb->publish_key, pb->subscribe_key, channel + ); + + + while (pmessage[0]) { + /* RFC 3986 Unreserved characters plus few + * safe reserved ones. */ + size_t okspan = strspn(pmessage, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~" ",=:;@[]"); + if (okspan > 0) { + if (okspan > sizeof(pb->http_buf)-1 - pb->http_buf_len) { + pb->http_buf_len = 0; + return PNR_TX_BUFF_TOO_SMALL; + } + memcpy(pb->http_buf + pb->http_buf_len, pmessage, okspan); + pb->http_buf_len += okspan; + pb->http_buf[pb->http_buf_len] = 0; + pmessage += okspan; + } + if (pmessage[0]) { + /* %-encode a non-ok character. */ + char enc[4] = {'%'}; + enc[1] = "0123456789ABCDEF"[pmessage[0] / 16]; + enc[2] = "0123456789ABCDEF"[pmessage[0] % 16]; + if (3 > sizeof pb->http_buf - 1 - pb->http_buf_len) { + pb->http_buf_len = 0; + return PNR_TX_BUFF_TOO_SMALL; + } + memcpy(pb->http_buf + pb->http_buf_len, enc, 4); + pb->http_buf_len += 3; + ++pmessage; + } + } + APPEND_URL_PARAM_M(pb, "pnsdk", uname, '?'); + APPEND_URL_PARAM_M(pb, "uuid", pb->uuid, '&'); + APPEND_URL_PARAM_M(pb, "auth", pb->auth, '&'); + if (!store_in_history) { + APPEND_URL_PARAM_BOOL_M(pb, "store", store_in_history, '?'); + } + if (eat_after_reading) { + APPEND_URL_PARAM_BOOL_M(pb, "ear", eat_after_reading, '?'); + } + + return PNR_STARTED; +} + + +enum pubnub_res pbcc_subscribe_prep(struct pbcc_context *p, const char *channel, const char *channel_group, unsigned *heartbeat) +{ + if (NULL == channel) { + if (NULL == channel_group) { + return PNR_INVALID_CHANNEL; + } + channel = ","; + } + if (p->msg_ofs < p->msg_end) { + return PNR_RX_BUFF_NOT_EMPTY; + } + + p->http_content_len = 0; + p->msg_ofs = p->msg_end = 0; + + p->http_buf_len = snprintf( + p->http_buf, sizeof(p->http_buf), + "/subscribe/%s/%s/0/%s?pnsdk=%s", + p->subscribe_key, channel, p->timetoken, + pubnub_uname() + ); + APPEND_URL_PARAM_M(p, "channel-group", channel_group, '&'); + APPEND_URL_PARAM_M(p, "uuid", p->uuid, '&'); + APPEND_URL_PARAM_M(p, "auth", p->auth, '&'); + APPEND_URL_OPT_PARAM_UNSIGNED_M(p, "heartbeat", heartbeat, '&'); + + return PNR_STARTED; +} + + +enum pubnub_res pbcc_leave_prep(struct pbcc_context *pb, const char *channel, const char *channel_group) +{ + if (NULL == channel) { + if (NULL == channel_group) { + return PNR_INVALID_CHANNEL; + } + channel = ","; + } + pb->http_content_len = 0; + + /* Make sure next subscribe() will be a join. */ + pb->timetoken[0] = '0'; + pb->timetoken[1] = '\0'; + + pb->http_buf_len = snprintf( + pb->http_buf, sizeof pb->http_buf, + "/v2/presence/sub-key/%s/channel/%s/leave?pnsdk=%s", + pb->subscribe_key, channel, + pubnub_uname() + ); + APPEND_URL_PARAM_M(pb, "channel-group", channel_group, '&'); + APPEND_URL_PARAM_M(pb, "uuid", pb->uuid, '&'); + APPEND_URL_PARAM_M(pb, "auth", pb->auth, '&'); + + return PNR_STARTED; +} + + +enum pubnub_res pbcc_time_prep(struct pbcc_context *pb) +{ + if (pb->msg_ofs < pb->msg_end) { + return PNR_RX_BUFF_NOT_EMPTY; + } + + pb->http_content_len = 0; + pb->msg_ofs = pb->msg_end = 0; + + pb->http_buf_len = snprintf( + pb->http_buf, sizeof pb->http_buf, + "/time/0?pnsdk=%s", + pubnub_uname() + ); + APPEND_URL_PARAM_M(pb, "uuid", pb->uuid, '&'); + APPEND_URL_PARAM_M(pb, "auth", pb->auth, '&'); + + return PNR_STARTED; +} + + + +enum pubnub_res pbcc_history_prep(struct pbcc_context *pb, const char *channel, unsigned count, bool include_token) +{ + if (pb->msg_ofs < pb->msg_end) { + return PNR_RX_BUFF_NOT_EMPTY; + } + + pb->http_content_len = 0; + pb->msg_ofs = pb->msg_end = 0; + + pb->http_buf_len = snprintf( + pb->http_buf, sizeof pb->http_buf, + "/v2/history/sub-key/%s/channel/%s?pnsdk=%s", + pb->subscribe_key, channel, + pubnub_uname() + ); + APPEND_URL_PARAM_M(pb, "auth", pb->auth, '&'); + APPEND_URL_PARAM_UNSIGNED_M(pb, "count", count, '&'); + APPEND_URL_PARAM_BOOL_M(pb, "include_token", include_token, '&'); + + return PNR_STARTED; +} + + +enum pubnub_res pbcc_heartbeat_prep(struct pbcc_context *pb, const char *channel, const char *channel_group) +{ + if (NULL == channel) { + if (NULL == channel_group) { + return PNR_INVALID_CHANNEL; + } + channel = ","; + } + if (pb->msg_ofs < pb->msg_end) { + return PNR_RX_BUFF_NOT_EMPTY; + } + + pb->http_content_len = 0; + pb->msg_ofs = pb->msg_end = 0; + + pb->http_buf_len = snprintf( + pb->http_buf, sizeof pb->http_buf, + "/v2/presence/sub-key/%s/channel/%s/heartbeat?pnsdk=%s", + pb->subscribe_key, + channel, + pubnub_uname() + ); + APPEND_URL_PARAM_M(pb, "channel-group", channel_group, '&'); + APPEND_URL_PARAM_M(pb, "auth", pb->auth, '&'); + APPEND_URL_PARAM_M(pb, "uuid", pb->uuid, '&'); + + return PNR_STARTED; +} + + +enum pubnub_res pbcc_here_now_prep(struct pbcc_context *pb, const char *channel, const char *channel_group, enum pbcc_tribool disable_uuids, enum pbcc_tribool state) +{ + if (NULL == channel) { + if (channel_group != NULL) { + channel = ","; + } + } + if (pb->msg_ofs < pb->msg_end) { + return PNR_RX_BUFF_NOT_EMPTY; + } + + pb->http_content_len = 0; + pb->msg_ofs = pb->msg_end = 0; + + pb->http_buf_len = snprintf( + pb->http_buf, sizeof pb->http_buf, + "/v2/presence/sub-key/%s%s%s?pnsdk=%s", + pb->subscribe_key, + channel ? "/channel/" : "", + channel ? channel : "", + pubnub_uname() + ); + APPEND_URL_PARAM_M(pb, "channel-group", channel_group, '&'); + APPEND_URL_PARAM_M(pb, "auth", pb->auth, '&'); + APPEND_URL_PARAM_M(pb, "uuid", pb->uuid, '&'); + APPEND_URL_PARAM_TRIBOOL_M(pb, "disable_uuids", disable_uuids, '&'); + APPEND_URL_PARAM_TRIBOOL_M(pb, "state", state, '&'); + + return PNR_STARTED; +} + + +enum pubnub_res pbcc_where_now_prep(struct pbcc_context *pb, const char *uuid) +{ + PUBNUB_ASSERT_OPT(uuid != NULL); + + if (pb->msg_ofs < pb->msg_end) { + return PNR_RX_BUFF_NOT_EMPTY; + } + + pb->http_content_len = 0; + pb->msg_ofs = pb->msg_end = 0; + + pb->http_buf_len = snprintf( + pb->http_buf, sizeof pb->http_buf, + "/v2/presence/sub-key/%s/uuid/%s?pnsdk=%s", + pb->subscribe_key, uuid, + pubnub_uname() + ); + APPEND_URL_PARAM_M(pb, "auth", pb->auth, '&'); + return PNR_STARTED; +} + + +enum pubnub_res pbcc_set_state_prep(struct pbcc_context *pb, char const *channel, char const *channel_group, const char *uuid, char const *state) +{ + PUBNUB_ASSERT_OPT(uuid != NULL); + PUBNUB_ASSERT_OPT(state != NULL); + + if (NULL == channel) { + if (NULL == channel_group) { + return PNR_INVALID_CHANNEL; + } + channel = ","; + } + if (pb->msg_ofs < pb->msg_end) { + return PNR_RX_BUFF_NOT_EMPTY; + } + + pb->http_buf_len = snprintf( + pb->http_buf, sizeof pb->http_buf, + "/v2/presence/sub-key/%s/channel/%s/uuid/%s/data?pnsdk=%s&state=%s", + pb->subscribe_key, channel, uuid, + pubnub_uname(), state + ); + APPEND_URL_PARAM_M(pb, "channel-group", channel_group, '&'); + APPEND_URL_PARAM_M(pb, "auth", pb->auth, '&'); + + return PNR_STARTED; +} + + +enum pubnub_res pbcc_state_get_prep(struct pbcc_context *pb, char const *channel, char const *channel_group, const char *uuid) +{ + PUBNUB_ASSERT_OPT(uuid != NULL); + + if (NULL == channel) { + if (NULL == channel_group) { + return PNR_INVALID_CHANNEL; + } + channel = ","; + } + if (pb->msg_ofs < pb->msg_end) { + return PNR_RX_BUFF_NOT_EMPTY; + } + + pb->http_buf_len = snprintf( + pb->http_buf, sizeof pb->http_buf, + "/v2/presence/sub-key/%s/channel/%s/uuid/%s?pnsdk=%s", + pb->subscribe_key, channel, uuid, + pubnub_uname() + ); + APPEND_URL_PARAM_M(pb, "channel-group", channel_group, '&'); + APPEND_URL_PARAM_M(pb, "auth", pb->auth, '&'); + + return PNR_STARTED; +} + + +enum pubnub_res pbcc_remove_channel_group_prep(struct pbcc_context *pb, char const *channel_group) +{ + PUBNUB_ASSERT_OPT(channel_group != NULL); + + pb->http_buf_len = snprintf( + pb->http_buf, sizeof pb->http_buf, + "/v1/channel-registration/sub-key/%s/channel-group/%s/remove?pnsdk=%s", + pb->subscribe_key, channel_group, pubnub_uname() + ); + APPEND_URL_PARAM_M(pb, "auth", pb->auth, '&'); + + return PNR_STARTED; +} + + +enum pubnub_res pbcc_channel_registry_prep(struct pbcc_context *pb, char const *channel_group, char const *param, char const *channel) +{ + PUBNUB_ASSERT_OPT(channel_group != NULL); + + pb->http_buf_len = snprintf( + pb->http_buf, sizeof pb->http_buf, + "/v1/channel-registration/sub-key/%s/channel-group/%s?pnsdk=%s", + pb->subscribe_key, channel_group, pubnub_uname() + ); + if (NULL != param) { + enum pubnub_res rslt; + PUBNUB_ASSERT_OPT(channel != NULL); + rslt = append_url_param(pb, param, strlen(param), channel, '&'); + if (rslt != PNR_OK) { + return rslt; + } + } + APPEND_URL_PARAM_M(pb, "auth", pb->auth, '&'); + + return PNR_STARTED; +}