{"action":"created","comment":{"url":"https://api.github.com/repos/kaaproject/kaa/pulls/comments/28599807","id":28599807,"diff_hunk":"@@ -0,0 +1,1001 @@\n+\n+/*\n+ * Copyright 2014-2015 CyberVision, Inc.\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at\n+ *\n+ *      http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+#ifndef KAA_DISABLE_FEATURE_NOTIFICATION\n+\n+#include \"kaa_notification_manager.h\"\n+#include \"platform/sock.h\"\n+\n+\n+extern kaa_transport_channel_interface_t *kaa_channel_manager_get_transport_channel(kaa_channel_manager_t *self\n+                                                                           , kaa_service_t service_type);\n+\n+struct kaa_notification_manager_t {\n+    kaa_list_t                 *mandatory_listeners;\n+    kaa_list_t                 *topics_listeners;\n+    kaa_list_t                 *optional_listeners;\n+    kaa_list_t                 *subscriptions;\n+    kaa_list_t                 *unsubscriptions;\n+    kaa_list_t                 *topics;\n+    kaa_list_t                 *uids;\n+    kaa_list_t                 *topic_states;\n+    size_t                      notification_sequence_number;\n+\n+    kaa_status_t               *status;\n+    kaa_channel_manager_t      *channel_manager;\n+    kaa_logger_t               *logger;\n+};\n+\n+\n+typedef struct {\n+    uint16_t length;\n+    void *data;\n+} kaa_notifications_uid_t;\n+\n+typedef struct {\n+    uint64_t topic_id;\n+    uint32_t sqn_number;\n+} kaa_topic_state_t;\n+\n+typedef struct {\n+    uint32_t seq_num;\n+    kaa_topic_subscription_type_t type;\n+    uint32_t size;\n+    uint64_t topic_id;\n+    kaa_notification_t *notification_body;\n+} kaa_notification_wrapper_t;\n+\n+\n+\n+#define   TOPICS                 0x0\n+#define   NOTIFICATIONS          0x1\n+#define   TOPICS_STATE_ID        0x0\n+#define   UID_ID                 0x1\n+#define   SUBSCRIPTION_ID        0x2\n+#define   UNSUBSCRIPTION_ID      0x3\n+\n+\n+typedef enum {\n+    KAA_CLIENT_WANTS_TO_RECEIVE_NOTIFICATIONS = 0x01,\n+    KAA_SUBSCRIBED_TOPIC_HASH_IS_PRESENT = 0x02\n+} kaa_notification_extension_flags_t;\n+\n+typedef struct {\n+    uint64_t topic_id;\n+    kaa_list_t *listeners;\n+} kaa_optional_notification_listeners_wrapper_t ;\n+\n+typedef struct {\n+    uint32_t id;\n+    kaa_notification_listener_t listener;\n+} kaa_notification_listener_wrapper_t;\n+\n+typedef struct {\n+    uint32_t id;\n+    kaa_topic_listener_t listener;\n+} kaa_topic_listener_wrapper_t;\n+\n+static kaa_service_t notification_sync_services[1] = {KAA_SERVICE_NOTIFICATION};\n+\n+static bool kaa_find_notification_listener_by_id(void *listener, void *context)\n+{\n+    KAA_RETURN_IF_NIL2(listener, context, KAA_ERR_NONE);\n+    return ((kaa_notification_listener_wrapper_t *) listener)->id == *(uint32_t *)context;\n+}\n+static bool kaa_find_topic_listener_by_id(void *listener, void *context)\n+{\n+    KAA_RETURN_IF_NIL2(listener, context, KAA_ERR_NONE);\n+    return ((kaa_topic_listener_wrapper_t *) listener)->id == *(uint32_t *)context;\n+}\n+\n+static bool kaa_find_optional_notification_listener_by_id (void *optional_listener_list, void *topic_id)\n+{\n+    KAA_RETURN_IF_NIL2(optional_listener_list, topic_id, KAA_ERR_NONE);\n+    kaa_optional_notification_listeners_wrapper_t *optional_listener_node = (kaa_optional_notification_listeners_wrapper_t *) optional_listener_list;\n+    return optional_listener_node->topic_id == *(uint64_t *)topic_id;\n+}\n+\n+static bool kaa_find_topic_by_id(void *topic, void *id)\n+{\n+    KAA_RETURN_IF_NIL2(topic, id, KAA_ERR_NONE);\n+    kaa_topic_t* notification_topic = (kaa_topic_t *) topic;\n+    return notification_topic->id == *(uint64_t *)id;\n+\n+}\n+static void kaa_optional_notification_wrapper_destroy(void *data)\n+{\n+    KAA_RETURN_IF_NIL(data,);\n+    kaa_optional_notification_listeners_wrapper_t * wrapper = (kaa_optional_notification_listeners_wrapper_t *) data;\n+    KAA_FREE(wrapper);\n+}\n+static void kaa_notification_listener_destroy(void *data)\n+{\n+    KAA_RETURN_IF_NIL(data,);\n+    kaa_notification_listener_wrapper_t * wrapper = (kaa_notification_listener_wrapper_t *) data;\n+    KAA_FREE(wrapper);\n+}\n+\n+static void kaa_topic_listener_destroy(void *data)\n+{\n+    KAA_RETURN_IF_NIL(data,);\n+    kaa_topic_listener_wrapper_t * listener = (kaa_topic_listener_wrapper_t  *) data;\n+    KAA_FREE(listener);\n+}\n+\n+static void kaa_topic_destroy(void *data)\n+{\n+    KAA_RETURN_IF_NIL(data,);\n+    kaa_topic_t * topic = (kaa_topic_t *) data;\n+    KAA_FREE(topic);\n+}\n+\n+static void kaa_notification_wrapper_destroy(void *data)\n+{\n+    kaa_notification_wrapper_t *wrapper = (kaa_notification_wrapper_t *) data;\n+    wrapper->notification_body->destroy(wrapper->notification_body);\n+    KAA_FREE(wrapper);\n+}\n+\n+static void kaa_topic_states_destroy(void *data )\n+{\n+    kaa_topic_state_t *state = (kaa_topic_state_t *)data;\n+    KAA_FREE(state);\n+}\n+\n+static void kaa_uid_destroy(void *data)\n+{\n+    kaa_notifications_uid_t * uid = (kaa_notifications_uid_t *)data;\n+    KAA_FREE(uid);\n+}\n+\n+static void kaa_subscription_unsubscription_destroy(void *data)\n+{\n+    uint64_t *topic_id = (uint64_t *)data;\n+    KAA_FREE(topic_id);\n+}\n+\n+static kaa_error_t kaa_find_topic(kaa_notification_manager_t *self, kaa_topic_t **topic, uint64_t *topic_id)\n+{\n+    KAA_RETURN_IF_NIL2(topic_id, topic, KAA_ERR_BADPARAM);\n+    kaa_list_t *topic_node = kaa_list_find_next(self->topics, &kaa_find_topic_by_id, topic_id);\n+    if (!topic_node) {\n+        KAA_LOG_ERROR(self->logger, KAA_ERR_NOT_FOUND, \"Topic with id = %d not found.\", *topic_id);\n+        return KAA_ERR_NOT_FOUND;\n+    }\n+    *topic = kaa_list_get_data(topic_node);\n+    return KAA_ERR_NONE;\n+}\n+\n+static bool kaa_find_topic_state_by_id(void *topic_state, void *id)\n+{\n+    kaa_topic_state_t *state = (kaa_topic_state_t *)topic_state;\n+    return state->topic_id == *(uint64_t *)id;\n+}\n+\n+kaa_error_t kaa_calculate_topic_listener_id(kaa_topic_listener_t *listener, uint32_t *listener_id)\n+{\n+    KAA_RETURN_IF_NIL2(listener, listener_id, KAA_ERR_BADPARAM);\n+\n+    const uint32_t prime = 31;\n+\n+    *listener_id = 1;\n+    *listener_id = prime * (*listener_id) + (ptrdiff_t)listener->context;\n+    *listener_id = prime * (*listener_id) + (ptrdiff_t)listener->callback;\n+     return KAA_ERR_NONE;\n+}\n+\n+kaa_error_t kaa_calculate_notification_listener_id(kaa_notification_listener_t *listener, uint32_t *listener_id)\n+{\n+    KAA_RETURN_IF_NIL2(listener, listener_id, KAA_ERR_BADPARAM);\n+\n+    const uint32_t prime = 31;\n+\n+    *listener_id = 1;\n+    *listener_id = prime * (*listener_id) + (ptrdiff_t)listener->context;\n+    *listener_id = prime * (*listener_id) + (ptrdiff_t)listener->callback;\n+    return KAA_ERR_NONE;\n+}\n+\n+kaa_error_t kaa_notification_manager_get_size(kaa_notification_manager_t *self, size_t *expected_size)\n+{\n+    KAA_RETURN_IF_NIL2(self, expected_size, KAA_ERR_BADPARAM);\n+    *expected_size = KAA_EXTENSION_HEADER_SIZE;\n+    *expected_size += sizeof(uint32_t); //state sqn size\n+\n+    if (self->topic_states) {\n+        *expected_size += sizeof(uint32_t); //field id + reserved + count\n+        *expected_size += (sizeof(uint64_t) + sizeof(uint32_t)) * kaa_list_get_size(self->topic_states);\n+    }\n+\n+    if (self->uids) {\n+        *expected_size += sizeof(uint32_t); //field id + reserved + count\n+        kaa_list_t *uid_node = self->uids;\n+        size_t uids_size = sizeof(uint32_t) * kaa_list_get_size(self->uids);\n+        while (uid_node) {\n+            kaa_notifications_uid_t *uid = (kaa_notifications_uid_t *)kaa_list_get_data(uid_node);\n+            uids_size += kaa_aligned_size_get(uid->length);\n+            uid_node = kaa_list_next(uid_node);\n+        }\n+        *expected_size += uids_size;\n+    }\n+\n+    if (self->subscriptions) {\n+        *expected_size += sizeof(uint32_t); //meta data for subscribe commands\n+        *expected_size += kaa_list_get_size(self->subscriptions) * sizeof(uint64_t);\n+    }\n+    if (self->unsubscriptions) {\n+        *expected_size += sizeof(uint32_t); //meta data for unsubscribe commands\n+        *expected_size += kaa_list_get_size(self->unsubscriptions) * sizeof(uint64_t);\n+    }\n+    return KAA_ERR_NONE;\n+}\n+\n+kaa_error_t kaa_notification_manager_request_serialize(kaa_notification_manager_t *self, kaa_platform_message_writer_t *writer)\n+{\n+    KAA_RETURN_IF_NIL2(self, writer, KAA_ERR_BADPARAM);\n+\n+    size_t size = 0;\n+    kaa_error_t err = kaa_notification_manager_get_size(self, &size);\n+    if (err) {\n+        KAA_LOG_ERROR(self->logger, KAA_ERR_BADPARAM,\"Failed to get extensions size.\");\n+        return KAA_ERR_BADPARAM;\n+    }\n+    uint32_t payload_size = size - KAA_EXTENSION_HEADER_SIZE;\n+    err = kaa_platform_message_write_extension_header(writer\n+                                                    , KAA_NOTIFICATION_EXTENSION_TYPE\n+                                                    , KAA_CLIENT_WANTS_TO_RECEIVE_NOTIFICATIONS\n+                                                    , payload_size);\n+    if (err) {\n+        KAA_LOG_ERROR(self->logger, KAA_ERR_BADPARAM,\"Failed to write notification header.\");\n+        return KAA_ERR_BADPARAM;\n+    }\n+    *(uint32_t *)writer->current = KAA_HTONL((uint32_t)self->status->notification_seq_n);\n+    writer->current += sizeof(uint32_t);\n+\n+    if (self->topic_states) {\n+        *(uint8_t *)writer->current = (uint8_t)TOPICS_STATE_ID;\n+        writer->current += sizeof(uint16_t);\n+        *(uint16_t *)writer->current = KAA_HTONS((uint16_t)kaa_list_get_size(self->topic_states));\n+        writer->current += sizeof(uint16_t);\n+        kaa_list_t *topic_state_node = self->topic_states;\n+        while (topic_state_node) {\n+            kaa_topic_state_t *state = (kaa_topic_state_t *)kaa_list_get_data(topic_state_node);\n+            KAA_RETURN_IF_NIL(state, KAA_ERR_BADDATA);\n+            *(uint64_t *)writer->current = KAA_HTONLL(state->topic_id);\n+            writer->current += sizeof(uint64_t);\n+            *(uint32_t *)writer->current = KAA_HTONL((uint32_t)state->sqn_number);\n+            writer->current += sizeof(uint32_t);\n+            topic_state_node = kaa_list_next(topic_state_node);\n+        }\n+    }\n+    if (self->uids) {\n+        *(uint8_t *)writer->current = (uint8_t)UID_ID;\n+        writer->current += sizeof(uint16_t);\n+        *(uint16_t *)writer->current = KAA_HTONS(kaa_list_get_size(self->uids));\n+        writer->current += sizeof(uint16_t);\n+        kaa_list_t *uid_node = self->uids;\n+        while (uid_node) {\n+            kaa_notifications_uid_t * uid = (kaa_notifications_uid_t *)kaa_list_get_data(uid_node);\n+            KAA_RETURN_IF_NIL(uid, KAA_ERR_BAD_STATE);\n+            *(uint32_t *)writer->current = KAA_HTONL(uid->length);\n+            writer->current += sizeof(uint32_t);\n+            err = kaa_platform_message_write_aligned(writer, uid->data, uid->length);\n+            uid_node = kaa_list_next(uid_node);\n+        }\n+    }\n+\n+    if (self->subscriptions) {\n+        *(uint8_t *)writer->current = (uint8_t)SUBSCRIPTION_ID;\n+        writer->current += sizeof(uint16_t);\n+        *(uint16_t *)writer->current = KAA_HTONS(kaa_list_get_size(self->subscriptions));\n+        writer->current += sizeof(uint16_t);\n+        kaa_list_t *subscription_node = self->subscriptions;\n+        while (subscription_node) {\n+            *(uint64_t *)writer->current = KAA_HTONLL(*(uint64_t *)kaa_list_get_data(subscription_node));\n+            subscription_node = kaa_list_next(subscription_node);\n+        }\n+    }\n+\n+    if (self->unsubscriptions) {\n+        *(uint8_t *)writer->current = (uint8_t)UNSUBSCRIPTION_ID;\n+        writer->current += sizeof(uint16_t);\n+        *(uint16_t *)writer->current = KAA_HTONS(kaa_list_get_size(self->unsubscriptions));\n+        writer->current += sizeof(uint16_t);\n+        kaa_list_t *subscription_node = self->unsubscriptions;\n+        while (subscription_node) {\n+            *(uint64_t *)writer->current = KAA_HTONLL(*(uint64_t *)kaa_list_get_data(subscription_node));\n+            subscription_node = kaa_list_next(subscription_node);\n+        }\n+    }\n+    return KAA_ERR_NONE;\n+}\n+\n+kaa_error_t kaa_notification_manager_create(kaa_notification_manager_t **self, kaa_status_t *status\n+                                          , kaa_channel_manager_t *channel_manager\n+                                          , kaa_logger_t *logger)\n+{\n+    KAA_RETURN_IF_NIL4(self, status, channel_manager, logger, KAA_ERR_BADPARAM);\n+\n+    *self = (kaa_notification_manager_t *) KAA_MALLOC( sizeof(kaa_notification_manager_t) );\n+    KAA_RETURN_IF_NIL(self,KAA_ERR_NOMEM);\n+\n+    (*self)->mandatory_listeners =  NULL;\n+    (*self)->topics_listeners    =  NULL;\n+    (*self)->optional_listeners  =  NULL;\n+    (*self)->subscriptions       =  NULL;\n+    (*self)->unsubscriptions     =  NULL;\n+    (*self)->topics              =  NULL;\n+    (*self)->topic_states        =  NULL;\n+\n+    (*self)->status              =  status;\n+    (*self)->channel_manager     =  channel_manager;\n+    (*self)->logger              =  logger;\n+\n+    return KAA_ERR_NONE;\n+}\n+\n+void kaa_notification_manager_destroy(kaa_notification_manager_t *self)\n+{\n+    KAA_RETURN_IF_NIL(self,);\n+    if (self->mandatory_listeners) {\n+        kaa_list_destroy(self->mandatory_listeners, &kaa_notification_listener_destroy);\n+    }\n+    if (self->topics_listeners) {\n+        kaa_list_destroy(self->topics_listeners, &kaa_topic_listener_destroy);\n+    }\n+    if(self->subscriptions) {\n+        kaa_list_destroy(self->subscriptions, &kaa_subscription_unsubscription_destroy);\n+    }\n+    if(self->unsubscriptions) {\n+        kaa_list_destroy(self->unsubscriptions, &kaa_subscription_unsubscription_destroy);\n+    }\n+    if (self->optional_listeners) {\n+        kaa_list_t* wrappers = self->optional_listeners;\n+        while (wrappers) {\n+             kaa_optional_notification_listeners_wrapper_t* wrapper = (kaa_optional_notification_listeners_wrapper_t *) kaa_list_get_data(wrappers);\n+             kaa_list_destroy(wrapper->listeners, &kaa_notification_listener_destroy);\n+             wrappers = kaa_list_next(wrappers);\n+        }\n+        kaa_list_destroy(self->optional_listeners, &kaa_optional_notification_wrapper_destroy);\n+    }\n+    if (self->topics) {\n+        kaa_list_destroy(self->topics, &kaa_topic_destroy);\n+    }\n+    if (self->topic_states) {\n+        kaa_list_destroy(self->topic_states, &kaa_topic_states_destroy);\n+    }\n+    KAA_FREE(self);\n+}\n+\n+kaa_error_t kaa_add_notification_listener(kaa_notification_manager_t *self, kaa_notification_listener_t *listener, uint32_t* listener_id)\n+{\n+    KAA_RETURN_IF_NIL2(self, listener, KAA_ERR_BADPARAM);\n+    uint32_t id;\n+    kaa_error_t err = kaa_calculate_notification_listener_id(listener, &id);\n+    if (err) {\n+        KAA_LOG_WARN(self->logger, err, \"Failed to calculate mandatory listener id.\");\n+        return err;\n+    }\n+    if (kaa_list_find_next(self->mandatory_listeners, &kaa_find_notification_listener_by_id, &id)) {\n+        KAA_LOG_WARN(self->logger, KAA_ERR_ALREADY_EXISTS, \"Failed to add mandatory listener: listener is already subscribed.\");\n+        return KAA_ERR_ALREADY_EXISTS;\n+    }\n+    kaa_notification_listener_wrapper_t* wrapper = (kaa_notification_listener_wrapper_t *) KAA_MALLOC(sizeof(kaa_notification_listener_wrapper_t));\n+    KAA_RETURN_IF_NIL(wrapper, KAA_ERR_NOMEM);\n+    wrapper->listener = *listener;\n+    if (!self->mandatory_listeners) {\n+        self->mandatory_listeners = kaa_list_create(wrapper);\n+        KAA_RETURN_IF_NIL(self->mandatory_listeners, KAA_ERR_NOMEM);\n+    } else {\n+        KAA_RETURN_IF_NIL((kaa_list_push_back(self->mandatory_listeners, wrapper)), KAA_ERR_NOMEM);\n+    }\n+    *listener_id = wrapper->id = id;; // for user to have convenient way to address notification listener\n+    return KAA_ERR_NONE;\n+}\n+\n+kaa_error_t kaa_remove_notification_listener(kaa_notification_manager_t *self, uint32_t *listener_id)\n+{\n+    KAA_RETURN_IF_NIL2(self, listener_id, KAA_ERR_BADPARAM);\n+    kaa_error_t err = kaa_list_remove_first(&self->mandatory_listeners, &kaa_find_notification_listener_by_id, listener_id, &kaa_notification_listener_destroy);\n+    if (err) {\n+        KAA_LOG_WARN(self->logger, err, \"Failed to remove mandatory listener: listener with id=%d is not found.\", *listener_id);\n+    }\n+    if (!kaa_list_get_size(self->mandatory_listeners)) {\n+        self->mandatory_listeners = NULL;\n+        KAA_LOG_TRACE(self->logger, KAA_ERR_NONE, \"Mandatory listeners list is empty now.\");\n+    }\n+    return err;\n+}\n+\n+kaa_error_t kaa_add_optional_notification_listener(kaa_notification_manager_t *self, kaa_notification_listener_t *listener\n+                                                 , uint64_t *topic_id, uint32_t *listener_id)\n+{\n+    KAA_RETURN_IF_NIL4(self, listener, topic_id, listener_id, KAA_ERR_BADPARAM);\n+    kaa_list_t *topic = kaa_list_find_next(self->topics, &kaa_find_topic_by_id, topic_id);\n+    if (!topic) {\n+        KAA_LOG_ERROR(self->logger, KAA_ERR_NOT_FOUND, \"Topic with id = %d not found.\", *topic_id);\n+        return KAA_ERR_NOT_FOUND;\n+    }\n+    uint32_t id;\n+    kaa_error_t err = kaa_calculate_notification_listener_id(listener, &id);\n+    if (err) {\n+        KAA_LOG_WARN(self->logger, err, \"Failed to calculate optional listener id.\");\n+        return err;\n+    }\n+       if (!self->optional_listeners) {\n+           kaa_notification_listener_wrapper_t *wrapper = (kaa_notification_listener_wrapper_t *) KAA_MALLOC(sizeof(kaa_notification_listener_wrapper_t));\n+           KAA_RETURN_IF_NIL(wrapper, KAA_ERR_NOMEM);\n+           wrapper->listener = *listener;\n+           kaa_optional_notification_listeners_wrapper_t* optional_wrapper = (kaa_optional_notification_listeners_wrapper_t *)\n+                                                                     KAA_MALLOC(sizeof(kaa_optional_notification_listeners_wrapper_t));\n+           KAA_RETURN_IF_NIL(optional_wrapper, KAA_ERR_NOMEM);\n+           self->optional_listeners = kaa_list_create(optional_wrapper);\n+           KAA_RETURN_IF_NIL(self->optional_listeners, KAA_ERR_NOMEM);\n+           optional_wrapper->listeners = kaa_list_create(wrapper);\n+           KAA_RETURN_IF_NIL(optional_wrapper->listeners, KAA_ERR_NOMEM);\n+           optional_wrapper->topic_id = *topic_id;\n+           *listener_id = wrapper->id = id; // for user to have convenient way to address notification listener\n+       } else {\n+           kaa_list_t *opt_listeners_node  = kaa_list_find_next(self->optional_listeners, &kaa_find_optional_notification_listener_by_id, topic_id);\n+           if (!opt_listeners_node) {\n+               kaa_optional_notification_listeners_wrapper_t* optional_wrapper = (kaa_optional_notification_listeners_wrapper_t *)\n+                                                                         KAA_MALLOC(sizeof(kaa_optional_notification_listeners_wrapper_t));\n+               KAA_RETURN_IF_NIL(optional_wrapper, KAA_ERR_NOMEM);\n+               kaa_list_t * new_opt_topic_listener_list = kaa_list_push_back(self->optional_listeners, optional_wrapper);\n+               KAA_RETURN_IF_NIL(new_opt_topic_listener_list, KAA_ERR_NOMEM);\n+               kaa_notification_listener_wrapper_t *wrapper = (kaa_notification_listener_wrapper_t *) KAA_MALLOC(sizeof(kaa_notification_listener_wrapper_t));\n+               KAA_RETURN_IF_NIL(wrapper, KAA_ERR_NOMEM);\n+               wrapper->listener = *listener;\n+               optional_wrapper->listeners  = kaa_list_create(wrapper);\n+               KAA_RETURN_IF_NIL(optional_wrapper->listeners, KAA_ERR_NOMEM);\n+               optional_wrapper->topic_id = *topic_id;\n+               *listener_id = wrapper->id = id; // for user to have convenient way to address notification listener\n+           } else {\n+               kaa_optional_notification_listeners_wrapper_t* optional_wrapper = (kaa_optional_notification_listeners_wrapper_t *) kaa_list_get_data(opt_listeners_node);\n+               if (kaa_list_find_next(optional_wrapper->listeners, &kaa_find_notification_listener_by_id, &id)) {\n+                   KAA_LOG_WARN(self->logger, KAA_ERR_ALREADY_EXISTS, \"Failed to add the optional listener: the listener is already subscribed.\");\n+                   return KAA_ERR_ALREADY_EXISTS;\n+               }\n+               kaa_notification_listener_wrapper_t *wrapper = (kaa_notification_listener_wrapper_t *) KAA_MALLOC(sizeof(kaa_notification_listener_wrapper_t));\n+               KAA_RETURN_IF_NIL(wrapper, KAA_ERR_NOMEM);\n+               wrapper->listener = *listener;\n+               kaa_list_t *inserted_listener = kaa_list_push_back(optional_wrapper->listeners, wrapper);\n+               KAA_RETURN_IF_NIL(inserted_listener, KAA_ERR_NOMEM);\n+               *listener_id = wrapper->id = id; // for user to have convenient way to address notification listener\n+           }\n+       }\n+       KAA_LOG_TRACE(self->logger, KAA_ERR_NONE, \"The optional listener with id = %d has been added.\", *listener_id);\n+       return KAA_ERR_NONE;\n+}\n+\n+kaa_error_t kaa_remove_optional_notification_listener(kaa_notification_manager_t *self, uint64_t *topic_id, uint32_t *listener_id)\n+{\n+    KAA_RETURN_IF_NIL3(self, topic_id, listener_id, KAA_ERR_BADPARAM);\n+    kaa_list_t *topic = kaa_list_find_next(self->topics, &kaa_find_topic_by_id, topic_id);\n+    if (!topic) {\n+        KAA_LOG_ERROR(self->logger, KAA_ERR_NOT_FOUND, \"Topic with id = %d not found.\", *topic_id);\n+        return KAA_ERR_NOT_FOUND;\n+    }\n+    kaa_list_t *opt_listeners_node = kaa_list_find_next(self->optional_listeners, &kaa_find_optional_notification_listener_by_id, topic_id);\n+    if (!opt_listeners_node) {\n+        KAA_LOG_WARN(self->logger, KAA_ERR_NOT_FOUND, \"Failed to remove the optional listener: there is no listeners subscribed on this topic (topic id = %d).\", *topic_id);\n+        return KAA_ERR_NOT_FOUND;\n+    }\n+    kaa_optional_notification_listeners_wrapper_t* optional_wrapper = (kaa_optional_notification_listeners_wrapper_t *) kaa_list_get_data(opt_listeners_node);\n+    kaa_error_t error = kaa_list_remove_first(&optional_wrapper->listeners, &kaa_find_notification_listener_by_id, listener_id, &kaa_notification_listener_destroy);\n+    if (error) {\n+        KAA_LOG_WARN(self->logger, KAA_ERR_NOT_FOUND, \"Failed to remove the optional listener: the listener with id=%d is not found.\", *listener_id);\n+        return error;\n+    } else {\n+        KAA_LOG_TRACE(self->logger, KAA_ERR_NONE, \"The optional listener with id = %d has been removed.\", *listener_id);\n+        if (!kaa_list_get_size(optional_wrapper->listeners)) {\n+            if (!kaa_list_remove_at(&self->optional_listeners, opt_listeners_node, &kaa_optional_notification_wrapper_destroy)) {\n+                KAA_LOG_TRACE(self->logger, KAA_ERR_NONE, \"The optional listeners list is empty.\");\n+                self->optional_listeners = NULL;\n+                return KAA_ERR_NONE;\n+            }\n+        }\n+        return KAA_ERR_NONE;\n+    }\n+}\n+\n+kaa_error_t kaa_add_topic_list_listener(kaa_notification_manager_t *self, kaa_topic_listener_t *listener, uint32_t *topic_listener_id)\n+{\n+    KAA_RETURN_IF_NIL2(self, listener, KAA_ERR_BADPARAM);\n+    uint32_t id;\n+    kaa_error_t err = kaa_calculate_topic_listener_id(listener, &id);\n+    if (err) {\n+        KAA_LOG_WARN(self->logger, err, \"Failed to calculate mandatory listener's id.\");\n+        return err;\n+    }\n+    kaa_topic_listener_wrapper_t *wrapper = NULL;\n+    if (!self->topics_listeners) {\n+        wrapper = (kaa_topic_listener_wrapper_t *) KAA_MALLOC(sizeof(kaa_topic_listener_wrapper_t));\n+        KAA_RETURN_IF_NIL(wrapper, KAA_ERR_NOMEM);\n+        wrapper->listener = *listener;\n+        self->topics_listeners = kaa_list_create(wrapper);\n+        if (!self->topics_listeners) {\n+            KAA_LOG_TRACE(self->logger, KAA_ERR_NOMEM, \"Failed to create storage for topic listeners.\");\n+            return KAA_ERR_NOMEM;\n+        }\n+    } else {\n+        if (kaa_list_find_next(self->topics_listeners, &kaa_find_topic_listener_by_id, &id)) {\n+            KAA_LOG_WARN(self->logger, KAA_ERR_ALREADY_EXISTS, \"Failed to add topic the listener: the listener is already subscribed.\");\n+            return KAA_ERR_ALREADY_EXISTS;\n+        }\n+        wrapper = (kaa_topic_listener_wrapper_t *) KAA_MALLOC(sizeof(kaa_topic_listener_wrapper_t));\n+        KAA_RETURN_IF_NIL(wrapper, KAA_ERR_NOMEM);\n+        wrapper->listener = *listener;\n+        if (!kaa_list_push_back(self->topics_listeners, wrapper)) {\n+            KAA_LOG_ERROR(self->logger, KAA_ERR_NOMEM, \"Failed to add the topic listener.\");\n+            return KAA_ERR_NOMEM;\n+        }\n+    }\n+    *topic_listener_id = wrapper->id = id;// for user to have convenient way to address notification listener\n+    return KAA_ERR_NONE;\n+}\n+\n+kaa_error_t kaa_remove_topic_list_listener(kaa_notification_manager_t *self, uint32_t *topic_listener_id)\n+{\n+    KAA_RETURN_IF_NIL2(self, topic_listener_id, KAA_ERR_BADPARAM);\n+    kaa_error_t err = kaa_list_remove_first(&self->topics_listeners, &kaa_find_topic_listener_by_id, topic_listener_id, &kaa_topic_listener_destroy);\n+    if (err) {\n+        KAA_LOG_WARN(self->logger, err, \"Failed to remove the topic listener: the listener is not found\");\n+    }\n+    if (!kaa_list_get_size(self->topics_listeners)) {\n+        KAA_LOG_TRACE(self->logger, err, \"Topic listeners list is empty now.\");\n+        self->topics_listeners = NULL;\n+    }\n+    return err;\n+}\n+\n+kaa_error_t kaa_get_topics(kaa_notification_manager_t *self, kaa_list_t **topics)\n+{\n+    KAA_RETURN_IF_NIL2(self, topics, KAA_ERR_BADPARAM);\n+    if (!self->topics) {\n+        *topics = NULL;\n+        KAA_LOG_WARN(self->logger, KAA_ERR_BAD_STATE, \"Failed retrieve the topic list.The topic list is empty.\");\n+        return KAA_ERR_BAD_STATE;\n+    }\n+    *topics = self->topics;\n+    return KAA_ERR_NONE;\n+}\n+\n+static kaa_error_t kaa_notify_topic_update_subscribers(kaa_notification_manager_t *self, kaa_list_t *topics)\n+{\n+    KAA_RETURN_IF_NIL2(self, topics, KAA_ERR_BADPARAM);\n+    kaa_list_t *current_listener_node = NULL;\n+    if (!self->topics_listeners) {\n+        KAA_LOG_TRACE(self->logger, KAA_ERR_NONE, \"There is no topic listeners to be notified\");\n+        return KAA_ERR_NONE;\n+    } else {\n+        current_listener_node = self->topics_listeners;\n+    }\n+    while (current_listener_node) {\n+        kaa_topic_listener_wrapper_t *wrapper = (kaa_topic_listener_wrapper_t *)kaa_list_get_data(current_listener_node);\n+        if (wrapper->listener.callback) {\n+            wrapper->listener.callback(wrapper->listener.context, topics);\n+        } else {\n+            KAA_LOG_WARN(self->logger, KAA_ERR_BADDATA, \"Get NULL, expected callback, listener's id = %d.\", wrapper->id);\n+        }\n+        current_listener_node = kaa_list_next(current_listener_node);\n+    }\n+    return KAA_ERR_NONE;\n+}\n+\n+static kaa_error_t kaa_notify_mandatory_notification_subscribers(kaa_notification_manager_t *self, uint64_t *topic_id, kaa_notification_t *notification)\n+{\n+    KAA_RETURN_IF_NIL2(self, notification, KAA_ERR_BADPARAM);\n+    kaa_list_t *current_listener_node = self->mandatory_listeners;\n+    KAA_RETURN_IF_NIL(current_listener_node,KAA_ERR_BAD_STATE);\n+    kaa_notification_listener_wrapper_t *wrapper = NULL;\n+    while (current_listener_node) {\n+         wrapper = (kaa_notification_listener_wrapper_t *) kaa_list_get_data(current_listener_node);\n+        if (wrapper->listener.callback) {\n+            wrapper->listener.callback(wrapper->listener.context, topic_id, notification);\n+        } else {\n+            KAA_LOG_ERROR(self->logger, KAA_ERR_BADDATA, \"Get NULL, expected callback, listener's id = %d.\", wrapper->id);\n+        }\n+        current_listener_node = kaa_list_next(current_listener_node);\n+    }\n+    return KAA_ERR_NONE;\n+}\n+\n+static kaa_error_t kaa_notify_optional_notification_subscribers(kaa_notification_manager_t *self, uint64_t *topic_id, const kaa_notification_t *notification)\n+{\n+    KAA_RETURN_IF_NIL3(self, topic_id, notification, KAA_ERR_BADPARAM);\n+    kaa_list_t *opt_listeners_node = kaa_list_find_next(self->optional_listeners, &kaa_find_optional_notification_listener_by_id, topic_id);\n+\n+    if (!opt_listeners_node) {\n+        KAA_LOG_WARN(self->logger, KAA_ERR_NOT_FOUND, \"No listeners were notified, because nobody's subscribed on this topic (topic id = %d).\", *topic_id);\n+        return KAA_ERR_NOT_FOUND;\n+    }\n+    KAA_RETURN_IF_NIL(opt_listeners_node, KAA_ERR_BADPARAM);\n+    kaa_optional_notification_listeners_wrapper_t* optional_wrapper = (kaa_optional_notification_listeners_wrapper_t *) kaa_list_get_data(opt_listeners_node);\n+    kaa_list_t *optional_listener_node = optional_wrapper->listeners;\n+    kaa_notification_listener_wrapper_t *wrapper = NULL;\n+    while (optional_listener_node) {\n+        wrapper = (kaa_notification_listener_wrapper_t *) kaa_list_get_data(optional_listener_node);\n+        if (wrapper->listener.callback) {\n+            wrapper->listener.callback(wrapper->listener.context, topic_id, notification);\n+        } else {\n+            KAA_LOG_ERROR(self->logger, KAA_ERR_BADDATA, \"Get NULL, expected callback, listener's id = %d.\", wrapper->id);\n+        }\n+        optional_listener_node = kaa_list_next(optional_listener_node);\n+    }\n+    return KAA_ERR_NONE;\n+}\n+\n+kaa_error_t kaa_subscribe_to_topic(kaa_notification_manager_t *self, uint64_t *topic_id, bool force_sync)\n+{\n+    KAA_RETURN_IF_NIL2(self, topic_id, KAA_ERR_BADPARAM);\n+    kaa_topic_t* topic = NULL;\n+    kaa_error_t err = kaa_find_topic(self, &topic, topic_id);\n+    if (err) {\n+        KAA_LOG_WARN(self->logger, KAA_ERR_BADPARAM, \"Failed to subscribe to the topic with id = %d.\", *topic_id);\n+        return err;\n+    }\n+    if (topic->subscription_type == MANDATORY) {\n+        KAA_LOG_WARN(self->logger, KAA_ERR_BADPARAM, \"Failed to subscribe to the topic with id = %d. Topic isn't optional.\", *topic_id);\n+        return KAA_ERR_BADPARAM;\n+    }\n+    uint64_t *subs_topic_id = (uint64_t *)KAA_MALLOC(sizeof(uint64_t));\n+    KAA_RETURN_IF_NIL(subs_topic_id, KAA_ERR_NOMEM);\n+    *subs_topic_id = *topic_id;\n+    if (!self->subscriptions) {\n+        self->subscriptions = kaa_list_create(subs_topic_id);\n+        KAA_RETURN_IF_NIL(self->subscriptions, KAA_ERR_NOMEM);\n+    } else {\n+        KAA_RETURN_IF_NIL(kaa_list_push_back(self->subscriptions, subs_topic_id), KAA_ERR_NOMEM);\n+    }\n+\n+    if (force_sync) {\n+        KAA_LOG_INFO(self->logger, KAA_ERR_NONE, \"Going to subscribe to topic %d\", *topic_id);","path":"client/client-multi/client-c/src/kaa/kaa_notification_manager.c","position":666,"original_position":666,"commit_id":"ed46f136c9253747694d0bfee126b288d9b74d28","original_commit_id":"ed46f136c9253747694d0bfee126b288d9b74d28","user":{"login":"dyosick","id":712449,"avatar_url":"https://avatars.githubusercontent.com/u/712449?v=3","gravatar_id":"","url":"https://api.github.com/users/dyosick","html_url":"https://github.com/dyosick","followers_url":"https://api.github.com/users/dyosick/followers","following_url":"https://api.github.com/users/dyosick/following{/other_user}","gists_url":"https://api.github.com/users/dyosick/gists{/gist_id}","starred_url":"https://api.github.com/users/dyosick/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dyosick/subscriptions","organizations_url":"https://api.github.com/users/dyosick/orgs","repos_url":"https://api.github.com/users/dyosick/repos","events_url":"https://api.github.com/users/dyosick/events{/privacy}","received_events_url":"https://api.github.com/users/dyosick/received_events","type":"User","site_admin":false},"body":"Wrong specifier %d. Review all file.","created_at":"2015-04-17T14:43:53Z","updated_at":"2015-04-17T14:43:53Z","html_url":"https://github.com/kaaproject/kaa/pull/198#discussion_r28599807","pull_request_url":"https://api.github.com/repos/kaaproject/kaa/pulls/198","_links":{"self":{"href":"https://api.github.com/repos/kaaproject/kaa/pulls/comments/28599807"},"html":{"href":"https://github.com/kaaproject/kaa/pull/198#discussion_r28599807"},"pull_request":{"href":"https://api.github.com/repos/kaaproject/kaa/pulls/198"}}},"pull_request":{"url":"https://api.github.com/repos/kaaproject/kaa/pulls/198","id":33543671,"html_url":"https://github.com/kaaproject/kaa/pull/198","diff_url":"https://github.com/kaaproject/kaa/pull/198.diff","patch_url":"https://github.com/kaaproject/kaa/pull/198.patch","issue_url":"https://api.github.com/repos/kaaproject/kaa/issues/198","number":198,"state":"open","locked":false,"title":"KAA-295: Implement notifications subsystem for C EP SDK","user":{"login":"GamovCoder","id":9300235,"avatar_url":"https://avatars.githubusercontent.com/u/9300235?v=3","gravatar_id":"","url":"https://api.github.com/users/GamovCoder","html_url":"https://github.com/GamovCoder","followers_url":"https://api.github.com/users/GamovCoder/followers","following_url":"https://api.github.com/users/GamovCoder/following{/other_user}","gists_url":"https://api.github.com/users/GamovCoder/gists{/gist_id}","starred_url":"https://api.github.com/users/GamovCoder/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GamovCoder/subscriptions","organizations_url":"https://api.github.com/users/GamovCoder/orgs","repos_url":"https://api.github.com/users/GamovCoder/repos","events_url":"https://api.github.com/users/GamovCoder/events{/privacy}","received_events_url":"https://api.github.com/users/GamovCoder/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-04-17T13:25:49Z","updated_at":"2015-04-17T14:43:53Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2c70151169c82dd1a24071d85ef32a57d3dcb8f3","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/kaaproject/kaa/pulls/198/commits","review_comments_url":"https://api.github.com/repos/kaaproject/kaa/pulls/198/comments","review_comment_url":"https://api.github.com/repos/kaaproject/kaa/pulls/comments{/number}","comments_url":"https://api.github.com/repos/kaaproject/kaa/issues/198/comments","statuses_url":"https://api.github.com/repos/kaaproject/kaa/statuses/ed46f136c9253747694d0bfee126b288d9b74d28","head":{"label":"kaaproject:KAA-295/475","ref":"KAA-295/475","sha":"ed46f136c9253747694d0bfee126b288d9b74d28","user":{"login":"kaaproject","id":6984613,"avatar_url":"https://avatars.githubusercontent.com/u/6984613?v=3","gravatar_id":"","url":"https://api.github.com/users/kaaproject","html_url":"https://github.com/kaaproject","followers_url":"https://api.github.com/users/kaaproject/followers","following_url":"https://api.github.com/users/kaaproject/following{/other_user}","gists_url":"https://api.github.com/users/kaaproject/gists{/gist_id}","starred_url":"https://api.github.com/users/kaaproject/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaaproject/subscriptions","organizations_url":"https://api.github.com/users/kaaproject/orgs","repos_url":"https://api.github.com/users/kaaproject/repos","events_url":"https://api.github.com/users/kaaproject/events{/privacy}","received_events_url":"https://api.github.com/users/kaaproject/received_events","type":"Organization","site_admin":false},"repo":{"id":17839346,"name":"kaa","full_name":"kaaproject/kaa","owner":{"login":"kaaproject","id":6984613,"avatar_url":"https://avatars.githubusercontent.com/u/6984613?v=3","gravatar_id":"","url":"https://api.github.com/users/kaaproject","html_url":"https://github.com/kaaproject","followers_url":"https://api.github.com/users/kaaproject/followers","following_url":"https://api.github.com/users/kaaproject/following{/other_user}","gists_url":"https://api.github.com/users/kaaproject/gists{/gist_id}","starred_url":"https://api.github.com/users/kaaproject/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaaproject/subscriptions","organizations_url":"https://api.github.com/users/kaaproject/orgs","repos_url":"https://api.github.com/users/kaaproject/repos","events_url":"https://api.github.com/users/kaaproject/events{/privacy}","received_events_url":"https://api.github.com/users/kaaproject/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/kaaproject/kaa","description":"Kaa open-source middleware platform for building, managing, and integrating connected products with the Internet of Everything.","fork":false,"url":"https://api.github.com/repos/kaaproject/kaa","forks_url":"https://api.github.com/repos/kaaproject/kaa/forks","keys_url":"https://api.github.com/repos/kaaproject/kaa/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kaaproject/kaa/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kaaproject/kaa/teams","hooks_url":"https://api.github.com/repos/kaaproject/kaa/hooks","issue_events_url":"https://api.github.com/repos/kaaproject/kaa/issues/events{/number}","events_url":"https://api.github.com/repos/kaaproject/kaa/events","assignees_url":"https://api.github.com/repos/kaaproject/kaa/assignees{/user}","branches_url":"https://api.github.com/repos/kaaproject/kaa/branches{/branch}","tags_url":"https://api.github.com/repos/kaaproject/kaa/tags","blobs_url":"https://api.github.com/repos/kaaproject/kaa/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kaaproject/kaa/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kaaproject/kaa/git/refs{/sha}","trees_url":"https://api.github.com/repos/kaaproject/kaa/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kaaproject/kaa/statuses/{sha}","languages_url":"https://api.github.com/repos/kaaproject/kaa/languages","stargazers_url":"https://api.github.com/repos/kaaproject/kaa/stargazers","contributors_url":"https://api.github.com/repos/kaaproject/kaa/contributors","subscribers_url":"https://api.github.com/repos/kaaproject/kaa/subscribers","subscription_url":"https://api.github.com/repos/kaaproject/kaa/subscription","commits_url":"https://api.github.com/repos/kaaproject/kaa/commits{/sha}","git_commits_url":"https://api.github.com/repos/kaaproject/kaa/git/commits{/sha}","comments_url":"https://api.github.com/repos/kaaproject/kaa/comments{/number}","issue_comment_url":"https://api.github.com/repos/kaaproject/kaa/issues/comments{/number}","contents_url":"https://api.github.com/repos/kaaproject/kaa/contents/{+path}","compare_url":"https://api.github.com/repos/kaaproject/kaa/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kaaproject/kaa/merges","archive_url":"https://api.github.com/repos/kaaproject/kaa/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kaaproject/kaa/downloads","issues_url":"https://api.github.com/repos/kaaproject/kaa/issues{/number}","pulls_url":"https://api.github.com/repos/kaaproject/kaa/pulls{/number}","milestones_url":"https://api.github.com/repos/kaaproject/kaa/milestones{/number}","notifications_url":"https://api.github.com/repos/kaaproject/kaa/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kaaproject/kaa/labels{/name}","releases_url":"https://api.github.com/repos/kaaproject/kaa/releases{/id}","created_at":"2014-03-17T18:48:31Z","updated_at":"2015-04-17T12:24:05Z","pushed_at":"2015-04-17T14:15:06Z","git_url":"git://github.com/kaaproject/kaa.git","ssh_url":"git@github.com:kaaproject/kaa.git","clone_url":"https://github.com/kaaproject/kaa.git","svn_url":"https://github.com/kaaproject/kaa","homepage":"http://www.kaaproject.org","size":109026,"stargazers_count":81,"watchers_count":81,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":10,"mirror_url":null,"open_issues_count":7,"forks":10,"open_issues":7,"watchers":81,"default_branch":"master"}},"base":{"label":"kaaproject:feature/KAA-295","ref":"feature/KAA-295","sha":"0645fe70a14a78f9d03d7ed60424f7a32709ff14","user":{"login":"kaaproject","id":6984613,"avatar_url":"https://avatars.githubusercontent.com/u/6984613?v=3","gravatar_id":"","url":"https://api.github.com/users/kaaproject","html_url":"https://github.com/kaaproject","followers_url":"https://api.github.com/users/kaaproject/followers","following_url":"https://api.github.com/users/kaaproject/following{/other_user}","gists_url":"https://api.github.com/users/kaaproject/gists{/gist_id}","starred_url":"https://api.github.com/users/kaaproject/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaaproject/subscriptions","organizations_url":"https://api.github.com/users/kaaproject/orgs","repos_url":"https://api.github.com/users/kaaproject/repos","events_url":"https://api.github.com/users/kaaproject/events{/privacy}","received_events_url":"https://api.github.com/users/kaaproject/received_events","type":"Organization","site_admin":false},"repo":{"id":17839346,"name":"kaa","full_name":"kaaproject/kaa","owner":{"login":"kaaproject","id":6984613,"avatar_url":"https://avatars.githubusercontent.com/u/6984613?v=3","gravatar_id":"","url":"https://api.github.com/users/kaaproject","html_url":"https://github.com/kaaproject","followers_url":"https://api.github.com/users/kaaproject/followers","following_url":"https://api.github.com/users/kaaproject/following{/other_user}","gists_url":"https://api.github.com/users/kaaproject/gists{/gist_id}","starred_url":"https://api.github.com/users/kaaproject/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaaproject/subscriptions","organizations_url":"https://api.github.com/users/kaaproject/orgs","repos_url":"https://api.github.com/users/kaaproject/repos","events_url":"https://api.github.com/users/kaaproject/events{/privacy}","received_events_url":"https://api.github.com/users/kaaproject/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/kaaproject/kaa","description":"Kaa open-source middleware platform for building, managing, and integrating connected products with the Internet of Everything.","fork":false,"url":"https://api.github.com/repos/kaaproject/kaa","forks_url":"https://api.github.com/repos/kaaproject/kaa/forks","keys_url":"https://api.github.com/repos/kaaproject/kaa/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kaaproject/kaa/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kaaproject/kaa/teams","hooks_url":"https://api.github.com/repos/kaaproject/kaa/hooks","issue_events_url":"https://api.github.com/repos/kaaproject/kaa/issues/events{/number}","events_url":"https://api.github.com/repos/kaaproject/kaa/events","assignees_url":"https://api.github.com/repos/kaaproject/kaa/assignees{/user}","branches_url":"https://api.github.com/repos/kaaproject/kaa/branches{/branch}","tags_url":"https://api.github.com/repos/kaaproject/kaa/tags","blobs_url":"https://api.github.com/repos/kaaproject/kaa/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kaaproject/kaa/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kaaproject/kaa/git/refs{/sha}","trees_url":"https://api.github.com/repos/kaaproject/kaa/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kaaproject/kaa/statuses/{sha}","languages_url":"https://api.github.com/repos/kaaproject/kaa/languages","stargazers_url":"https://api.github.com/repos/kaaproject/kaa/stargazers","contributors_url":"https://api.github.com/repos/kaaproject/kaa/contributors","subscribers_url":"https://api.github.com/repos/kaaproject/kaa/subscribers","subscription_url":"https://api.github.com/repos/kaaproject/kaa/subscription","commits_url":"https://api.github.com/repos/kaaproject/kaa/commits{/sha}","git_commits_url":"https://api.github.com/repos/kaaproject/kaa/git/commits{/sha}","comments_url":"https://api.github.com/repos/kaaproject/kaa/comments{/number}","issue_comment_url":"https://api.github.com/repos/kaaproject/kaa/issues/comments{/number}","contents_url":"https://api.github.com/repos/kaaproject/kaa/contents/{+path}","compare_url":"https://api.github.com/repos/kaaproject/kaa/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kaaproject/kaa/merges","archive_url":"https://api.github.com/repos/kaaproject/kaa/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kaaproject/kaa/downloads","issues_url":"https://api.github.com/repos/kaaproject/kaa/issues{/number}","pulls_url":"https://api.github.com/repos/kaaproject/kaa/pulls{/number}","milestones_url":"https://api.github.com/repos/kaaproject/kaa/milestones{/number}","notifications_url":"https://api.github.com/repos/kaaproject/kaa/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kaaproject/kaa/labels{/name}","releases_url":"https://api.github.com/repos/kaaproject/kaa/releases{/id}","created_at":"2014-03-17T18:48:31Z","updated_at":"2015-04-17T12:24:05Z","pushed_at":"2015-04-17T14:15:06Z","git_url":"git://github.com/kaaproject/kaa.git","ssh_url":"git@github.com:kaaproject/kaa.git","clone_url":"https://github.com/kaaproject/kaa.git","svn_url":"https://github.com/kaaproject/kaa","homepage":"http://www.kaaproject.org","size":109026,"stargazers_count":81,"watchers_count":81,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":10,"mirror_url":null,"open_issues_count":7,"forks":10,"open_issues":7,"watchers":81,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/kaaproject/kaa/pulls/198"},"html":{"href":"https://github.com/kaaproject/kaa/pull/198"},"issue":{"href":"https://api.github.com/repos/kaaproject/kaa/issues/198"},"comments":{"href":"https://api.github.com/repos/kaaproject/kaa/issues/198/comments"},"review_comments":{"href":"https://api.github.com/repos/kaaproject/kaa/pulls/198/comments"},"review_comment":{"href":"https://api.github.com/repos/kaaproject/kaa/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/kaaproject/kaa/pulls/198/commits"},"statuses":{"href":"https://api.github.com/repos/kaaproject/kaa/statuses/ed46f136c9253747694d0bfee126b288d9b74d28"}}}}