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
pubnub_ccore.cpp
- Committer:
- sveljko
- Date:
- 2016-11-10
- Revision:
- 0:d13755cfb705
File content as of revision 0:d13755cfb705:
/* -*- 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; }