@cryptotaxi247 / netdata-1 / commits / f9fbde67a

ACLK Passwd endpoint update (#10859)

Updates ACLK-NG to properly handle new response of password payload as defined in New Cloud Architecture

Timotej S committed Apr 21, 2021 at 14:23 UTC f9fbde67a4fad334806e78866acd6a22ad1f350b
6 files changed +319 -76
aclk/aclk.c
+26 -11
@@ -201,6 +201,11 @@ static void msg_callback(const char *topic, const void *msg, size_t msglen, int
201 {
202 char cmsg[RX_MSGLEN_MAX];
203 size_t len = (msglen < RX_MSGLEN_MAX - 1) ? msglen : (RX_MSGLEN_MAX - 1);
204 + const char *cmd_topic = aclk_get_topic(ACLK_TOPICID_COMMAND);
205 + if (!cmd_topic) {
206 + error("Error retrieving command topic");
207 + return;
208 + }
209
210 if (msglen > RX_MSGLEN_MAX - 1)
211 error("Incoming ACLK message was bigger than MAX of %d and got truncated.", RX_MSGLEN_MAX);
@@ -224,7 +229,7 @@ static void msg_callback(const char *topic, const void *msg, size_t msglen, int
229
230 debug(D_ACLK, "Got Message From Broker Topic \"%s\" QOS %d MSG: \"%s\"", topic, qos, cmsg);
231
227 - if (strcmp(aclk_get_topic(ACLK_TOPICID_COMMAND), topic))
232 + if (strcmp(cmd_topic, topic))
233 error("Received message on unexpected topic %s", topic);
234
235 if (aclk_shared_state.mqtt_shutdown_msg_id > 0) {
@@ -323,7 +328,12 @@ static inline void mqtt_connected_actions(mqtt_wss_client client)
328 aclk_session_sec = now / USEC_PER_SEC;
329 aclk_session_us = now % USEC_PER_SEC;
330
326 - mqtt_wss_subscribe(client, aclk_get_topic(ACLK_TOPICID_COMMAND), 1);
331 + const char *topic = aclk_get_topic(ACLK_TOPICID_COMMAND);
332 +
333 + if (!topic)
334 + error("Unable to fetch topic for COMMAND (to subscribe)");
335 + else
336 + mqtt_wss_subscribe(client, topic, 1);
337
338 aclk_stats_upd_online(1);
339 aclk_connected = 1;
@@ -331,7 +341,7 @@ static inline void mqtt_connected_actions(mqtt_wss_client client)
341 aclk_hello_msg(client);
342 ACLK_SHARED_STATE_LOCK;
343 if (aclk_shared_state.agent_state != AGENT_INITIALIZING) {
334 - error("Sending `connect` payload immediatelly as popcorning was finished already.");
344 + error("Sending `connect` payload immediately as popcorning was finished already.");
345 queue_connect_payloads();
346 }
347 ACLK_SHARED_STATE_UNLOCK;
@@ -461,9 +471,6 @@ static int aclk_attempt_to_connect(mqtt_wss_client client)
471 #ifndef ACLK_DISABLE_CHALLENGE
472 url_t auth_url;
473 url_t mqtt_url;
464 -
465 - char *mqtt_otp_user = NULL;
466 - char *mqtt_otp_pass = NULL;
474 #endif
475
476 json_object *lwt;
@@ -494,7 +501,7 @@ static int aclk_attempt_to_connect(mqtt_wss_client client)
501 .clientid = "anon",
502 .username = "anon",
503 .password = "anon",
497 - .will_topic = aclk_get_topic(ACLK_TOPICID_METADATA),
504 + .will_topic = "lwt",
505 .will_msg = NULL,
506 .will_flags = MQTT_WSS_PUB_QOS2,
507 .keep_alive = 60
@@ -522,16 +529,20 @@ static int aclk_attempt_to_connect(mqtt_wss_client client)
529 continue;
530 }
531
525 - ret = aclk_get_mqtt_otp(aclk_private_key, &mqtt_otp_user, &mqtt_otp_pass, &auth_url);
532 + ret = aclk_get_mqtt_otp(aclk_private_key, (char **)&mqtt_conn_params.clientid, (char **)&mqtt_conn_params.username, (char **)&mqtt_conn_params.password, &auth_url);
533 url_t_destroy(&auth_url);
534 if (ret) {
535 error("Error passing Challenge/Response to get OTP");
536 continue;
537 }
538
532 - mqtt_conn_params.clientid = mqtt_otp_user;
533 - mqtt_conn_params.username = mqtt_otp_user;
534 - mqtt_conn_params.password = mqtt_otp_pass;
539 + // aclk_get_topic moved here as during OTP we
540 + // generate the topic cache
541 + mqtt_conn_params.will_topic = aclk_get_topic(ACLK_TOPICID_METADATA);
542 + if (!mqtt_conn_params.will_topic) {
543 + error("Couldn't get LWT topic. Will not send LWT.");
544 + continue;
545 + }
546
547 // Do the MQTT connection
548 ret = aclk_get_transport_idx(aclk_env);
@@ -558,6 +569,10 @@ static int aclk_attempt_to_connect(mqtt_wss_client client)
569 #else
570 ret = mqtt_wss_connect(client, mqtt_url.host, mqtt_url.port, &mqtt_conn_params, ACLK_SSL_FLAGS, &proxy_conf);
571 url_t_destroy(&mqtt_url);
572 +
573 + freez((char*)mqtt_conn_params.clientid);
574 + freez((char*)mqtt_conn_params.password);
575 + freez((char*)mqtt_conn_params.username);
576 #endif
577
578 json_object_put(lwt);
aclk/aclk_otp.c
+96 -26
@@ -188,8 +188,95 @@ static int aclk_https_request(https_req_t *request, https_req_response_t *respon
188 return rc;
189 }
190
191 +struct auth_data {
192 + char *client_id;
193 + char *username;
194 + char *passwd;
195 +};
196 +
197 +#define PARSE_ENV_JSON_CHK_TYPE(it, type, name) \
198 + if (json_object_get_type(json_object_iter_peek_value(it)) != type) { \
199 + error("value of key \"%s\" should be %s", name, #type); \
200 + goto exit; \
201 + }
202 +
203 +#define JSON_KEY_CLIENTID "clientID"
204 +#define JSON_KEY_USER "username"
205 +#define JSON_KEY_PASS "password"
206 +#define JSON_KEY_TOPICS "topics"
207 +
208 +static int parse_passwd_response(const char *json_str, struct auth_data *auth) {
209 + int rc = 1;
210 + json_object *json;
211 + struct json_object_iterator it;
212 + struct json_object_iterator itEnd;
213 +
214 + json = json_tokener_parse(json_str);
215 + if (!json) {
216 + error("JSON-C failed to parse the payload of http respons of /env endpoint");
217 + return 1;
218 + }
219 +
220 + it = json_object_iter_begin(json);
221 + itEnd = json_object_iter_end(json);
222 +
223 + while (!json_object_iter_equal(&it, &itEnd)) {
224 + if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_CLIENTID)) {
225 + PARSE_ENV_JSON_CHK_TYPE(&it, json_type_string, JSON_KEY_CLIENTID)
226 +
227 + auth->client_id = strdupz(json_object_get_string(json_object_iter_peek_value(&it)));
228 + json_object_iter_next(&it);
229 + continue;
230 + }
231 + if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_USER)) {
232 + PARSE_ENV_JSON_CHK_TYPE(&it, json_type_string, JSON_KEY_USER)
233 +
234 + auth->username = strdupz(json_object_get_string(json_object_iter_peek_value(&it)));
235 + json_object_iter_next(&it);
236 + continue;
237 + }
238 + if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_PASS)) {
239 + PARSE_ENV_JSON_CHK_TYPE(&it, json_type_string, JSON_KEY_PASS)
240 +
241 + auth->passwd = strdupz(json_object_get_string(json_object_iter_peek_value(&it)));
242 + json_object_iter_next(&it);
243 + continue;
244 + }
245 + if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_TOPICS)) {
246 + PARSE_ENV_JSON_CHK_TYPE(&it, json_type_array, JSON_KEY_TOPICS)
247 +
248 + if (aclk_generate_topic_cache(json_object_iter_peek_value(&it))) {
249 + error("Failed to generate topic cache!");
250 + goto exit;
251 + }
252 + json_object_iter_next(&it);
253 + continue;
254 + }
255 + error("Unknown key \"%s\" in passwd response payload. Ignoring", json_object_iter_peek_name(&it));
256 + json_object_iter_next(&it);
257 + }
258 +
259 + if (!auth->client_id) {
260 + error(JSON_KEY_CLIENTID " is compulsory key in /password response");
261 + goto exit;
262 + }
263 + if (!auth->passwd) {
264 + error(JSON_KEY_PASS " is compulsory in /password response");
265 + goto exit;
266 + }
267 + if (!auth->username) {
268 + error(JSON_KEY_USER " is compulsory in /password response");
269 + goto exit;
270 + }
271 +
272 + rc = 0;
273 +exit:
274 + json_object_put(json);
275 + return rc;
276 +}
277 +
278 #define OTP_URL_PREFIX "/api/v1/auth/node/"
192 -int aclk_get_mqtt_otp(RSA *p_key, char **mqtt_usr, char **mqtt_pass, url_t *target) {
279 +int aclk_get_mqtt_otp(RSA *p_key, char **mqtt_id, char **mqtt_usr, char **mqtt_pass, url_t *target) {
280 // TODO this fnc will be rewritten and simplified in following PRs
281 // still carries lot of baggage from ACLK Legacy
282 int rc = 1;
@@ -272,43 +359,26 @@ int aclk_get_mqtt_otp(RSA *p_key, char **mqtt_usr, char **mqtt_pass, url_t *targ
359 }
360 info ("ACLK_OTP Got Password from Cloud");
361
275 - struct dictionary_singleton password = { .key = "password", .result = NULL };
276 - if (json_parse(resp.payload, &password, json_extract_singleton) != JSON_OK)
277 - {
278 - freez(password.result);
279 - error("Could not parse the json response with the password");
362 + struct auth_data data = { .client_id = NULL, .passwd = NULL, .username = NULL };
363 +
364 + if (parse_passwd_response(resp.payload, &data)){
365 + error("Error parsing response of password endpoint");
366 goto cleanup_resp;
367 }
368
283 - if (password.result == NULL ) {
284 - error("Could not retrieve password from auth response");
285 - goto cleanup_resp;
286 - }
287 - if (*mqtt_pass != NULL )
288 - freez(*mqtt_pass);
289 - *mqtt_pass = password.result;
290 - if (*mqtt_usr != NULL)
291 - freez(*mqtt_usr);
292 - *mqtt_usr = agent_id;
293 - agent_id = NULL;
369 + *mqtt_pass = data.passwd;
370 + *mqtt_usr = data.username;
371 + *mqtt_id = data.client_id;
372
373 rc = 0;
296 -
374 cleanup_resp:
375 https_req_response_free(&resp);
376 cleanup:
300 - if (agent_id != NULL)
301 - freez(agent_id);
377 + freez(agent_id);
378 buffer_free(url);
379 return rc;
380 }
381
306 -#define PARSE_ENV_JSON_CHK_TYPE(it, type, name) \
307 - if (json_object_get_type(json_object_iter_peek_value(it)) != type) { \
308 - error("value of key \"%s\" should be %s", name, #type); \
309 - goto exit; \
310 - }
311 -
382 #define JSON_KEY_ENC "encoding"
383 #define JSON_KEY_AUTH_ENDPOINT "authEndpoint"
384 #define JSON_KEY_TRP "transports"
aclk/aclk_otp.h
+1 -1
@@ -7,7 +7,7 @@
7
8 #include "https_client.h"
9
10 -int aclk_get_mqtt_otp(RSA *p_key, char **mqtt_usr, char **mqtt_pass, url_t *target);
10 +int aclk_get_mqtt_otp(RSA *p_key, char **mqtt_id, char **mqtt_usr, char **mqtt_pass, url_t *target);
11 int aclk_get_env(aclk_env_t *env, const char *aclk_hostname, int aclk_port);
12
13 #endif /* ACLK_OTP_H */
aclk/aclk_tx_msgs.c
+14 -2
@@ -13,8 +13,14 @@ static void aclk_send_message_subtopic(mqtt_wss_client client, json_object *msg,
13 {
14 uint16_t packet_id;
15 const char *str = json_object_to_json_string_ext(msg, JSON_C_TO_STRING_PLAIN);
16 + const char *topic = aclk_get_topic(subtopic);
17
17 - mqtt_wss_publish_pid(client, aclk_get_topic(subtopic), str, strlen(str), MQTT_WSS_PUB_QOS1, &packet_id);
18 + if (unlikely(!topic)) {
19 + error("Couldn't get topic. Aborting mesage send");
20 + return;
21 + }
22 +
23 + mqtt_wss_publish_pid(client, topic, str, strlen(str), MQTT_WSS_PUB_QOS1, &packet_id);
24 #ifdef NETDATA_INTERNAL_CHECKS
25 aclk_stats_msg_published(packet_id);
26 #endif
@@ -30,8 +36,14 @@ static uint16_t aclk_send_message_subtopic_pid(mqtt_wss_client client, json_obje
36 {
37 uint16_t packet_id;
38 const char *str = json_object_to_json_string_ext(msg, JSON_C_TO_STRING_PLAIN);
39 + const char *topic = aclk_get_topic(subtopic);
40 +
41 + if (unlikely(!topic)) {
42 + error("Couldn't get topic. Aborting mesage send");
43 + return 0;
44 + }
45
34 - mqtt_wss_publish_pid(client, aclk_get_topic(subtopic), str, strlen(str), MQTT_WSS_PUB_QOS1, &packet_id);
46 + mqtt_wss_publish_pid(client, topic, str, strlen(str), MQTT_WSS_PUB_QOS1, &packet_id);
47 #ifdef NETDATA_INTERNAL_CHECKS
48 aclk_stats_msg_published(packet_id);
49 #endif
aclk/aclk_util.c
+176 -32
@@ -72,52 +72,188 @@ int aclk_get_conv_log_next()
72 #define ACLK_TOPIC_PREFIX "/agent/"
73
74 struct aclk_topic {
75 - const char *topic_suffix;
75 + enum aclk_topics topic_id;
76 + // as received from cloud - we keep this for
77 + // eventual topic list update when claim_id changes
78 + char *topic_recvd;
79 + // constructed topic
80 char *topic;
81 };
82
83 // This helps to cache finalized topics (assembled with claim_id)
84 // to not have to alloc or create buffer and construct topic every
85 // time message is sent as in old ACLK
82 -static struct aclk_topic aclk_topic_cache[] = {
83 - { .topic_suffix = "outbound/meta", .topic = NULL }, // ACLK_TOPICID_CHART
84 - { .topic_suffix = "outbound/alarms", .topic = NULL }, // ACLK_TOPICID_ALARMS
85 - { .topic_suffix = "outbound/meta", .topic = NULL }, // ACLK_TOPICID_METADATA
86 - { .topic_suffix = "inbound/cmd", .topic = NULL }, // ACLK_TOPICID_COMMAND
87 - { .topic_suffix = NULL, .topic = NULL }
88 -};
86 +static struct aclk_topic **aclk_topic_cache = NULL;
87 +static size_t aclk_topic_cache_items = 0;
88
89 void free_topic_cache(void)
90 {
92 - struct aclk_topic *tc = aclk_topic_cache;
93 - while (tc->topic_suffix) {
94 - if (tc->topic) {
95 - freez(tc->topic);
96 - tc->topic = NULL;
91 + if (aclk_topic_cache) {
92 + for (size_t i = 0; i < aclk_topic_cache_items; i++) {
93 + freez(aclk_topic_cache[i]->topic);
94 + freez(aclk_topic_cache[i]->topic_recvd);
95 + freez(aclk_topic_cache[i]);
96 }
98 - tc++;
97 + freez(aclk_topic_cache);
98 + aclk_topic_cache = NULL;
99 + aclk_topic_cache_items = 0;
100 }
101 }
102
102 -static inline void generate_topic_cache(void)
103 -{
104 - struct aclk_topic *tc = aclk_topic_cache;
105 - char *ptr;
106 - if (unlikely(!tc->topic)) {
107 - rrdhost_aclk_state_lock(localhost);
108 - while(tc->topic_suffix) {
109 - tc->topic = mallocz(strlen(ACLK_TOPIC_PREFIX) + (UUID_STR_LEN - 1) + 2 /* '/' and \0 */ + strlen(tc->topic_suffix));
110 - ptr = tc->topic;
111 - strcpy(ptr, ACLK_TOPIC_PREFIX);
112 - ptr += strlen(ACLK_TOPIC_PREFIX);
113 - strcpy(ptr, localhost->aclk_state.claimed_id);
114 - ptr += (UUID_STR_LEN - 1);
115 - *ptr++ = '/';
116 - strcpy(ptr, tc->topic_suffix);
117 - tc++;
103 +#define JSON_TOPIC_KEY_TOPIC "topic"
104 +#define JSON_TOPIC_KEY_NAME "name"
105 +
106 +struct topic_name {
107 + enum aclk_topics id;
108 + // cloud name - how is it called
109 + // in answer to /password endpoint
110 + const char *name;
111 +} topic_names[] = {
112 + { .id = ACLK_TOPICID_CHART, .name = "chart" },
113 + { .id = ACLK_TOPICID_ALARMS, .name = "alarms" },
114 + { .id = ACLK_TOPICID_METADATA, .name = "meta" },
115 + { .id = ACLK_TOPICID_COMMAND, .name = "inbox-cmd" },
116 + { .id = ACLK_TOPICID_UNKNOWN, .name = NULL }
117 +};
118 +
119 +enum aclk_topics compulsory_topics[] = {
120 + ACLK_TOPICID_CHART,
121 + ACLK_TOPICID_ALARMS,
122 + ACLK_TOPICID_METADATA,
123 + ACLK_TOPICID_COMMAND,
124 + ACLK_TOPICID_UNKNOWN
125 +};
126 +
127 +static enum aclk_topics topic_name_to_id(const char *name) {
128 + struct topic_name *topic = topic_names;
129 + while (topic->name) {
130 + if (!strcmp(topic->name, name)) {
131 + return topic->id;
132 }
133 + topic++;
134 + }
135 + return ACLK_TOPICID_UNKNOWN;
136 +}
137 +
138 +static const char *topic_id_to_name(enum aclk_topics tid) {
139 + struct topic_name *topic = topic_names;
140 + while (topic->name) {
141 + if (topic->id == tid)
142 + return topic->name;
143 + topic++;
144 + }
145 + return "unknown";
146 +}
147 +
148 +#define CLAIM_ID_REPLACE_TAG "#{claim_id}"
149 +static void topic_generate_final(struct aclk_topic *t) {
150 + char *dest;
151 + char *replace_tag = strstr(t->topic_recvd, CLAIM_ID_REPLACE_TAG);
152 + if (!replace_tag)
153 + return;
154 +
155 + rrdhost_aclk_state_lock(localhost);
156 + if (unlikely(!localhost->aclk_state.claimed_id)) {
157 + error("This should never be called if agent not claimed");
158 rrdhost_aclk_state_unlock(localhost);
159 + return;
160 }
161 +
162 + t->topic = mallocz(strlen(t->topic_recvd) + 1 - strlen(CLAIM_ID_REPLACE_TAG) + strlen(localhost->aclk_state.claimed_id));
163 + memcpy(t->topic, t->topic_recvd, replace_tag - t->topic_recvd);
164 + dest = t->topic + (replace_tag - t->topic_recvd);
165 +
166 + memcpy(dest, localhost->aclk_state.claimed_id, strlen(localhost->aclk_state.claimed_id));
167 + dest += strlen(localhost->aclk_state.claimed_id);
168 + rrdhost_aclk_state_unlock(localhost);
169 + replace_tag += strlen(CLAIM_ID_REPLACE_TAG);
170 + strcpy(dest, replace_tag);
171 + dest += strlen(replace_tag);
172 + *dest = 0;
173 +}
174 +
175 +static int topic_cache_add_topic(struct json_object *json, struct aclk_topic *topic)
176 +{
177 + struct json_object_iterator it;
178 + struct json_object_iterator itEnd;
179 +
180 + it = json_object_iter_begin(json);
181 + itEnd = json_object_iter_end(json);
182 +
183 + while (!json_object_iter_equal(&it, &itEnd)) {
184 + if (!strcmp(json_object_iter_peek_name(&it), JSON_TOPIC_KEY_NAME)) {
185 + if (json_object_get_type(json_object_iter_peek_value(&it)) != json_type_string) {
186 + error("topic dictionary key \"" JSON_TOPIC_KEY_NAME "\" is expected to be json_type_string");
187 + return 1;
188 + }
189 + topic->topic_id = topic_name_to_id(json_object_get_string(json_object_iter_peek_value(&it)));
190 + if (topic->topic_id == ACLK_TOPICID_UNKNOWN) {
191 + info("topic dictionary has unkown topic name \"%s\"", json_object_get_string(json_object_iter_peek_value(&it)));
192 + }
193 + json_object_iter_next(&it);
194 + continue;
195 + }
196 + if (!strcmp(json_object_iter_peek_name(&it), JSON_TOPIC_KEY_TOPIC)) {
197 + if (json_object_get_type(json_object_iter_peek_value(&it)) != json_type_string) {
198 + error("topic dictionary key \"" JSON_TOPIC_KEY_TOPIC "\" is expected to be json_type_string");
199 + return 1;
200 + }
201 + topic->topic_recvd = strdupz(json_object_get_string(json_object_iter_peek_value(&it)));
202 + json_object_iter_next(&it);
203 + continue;
204 + }
205 +
206 + error("topic dictionary has Unknown/Unexpected key \"%s\" in topic description. Ignoring!", json_object_iter_peek_name(&it));
207 + json_object_iter_next(&it);
208 + }
209 +
210 + if (!topic->topic_recvd) {
211 + error("topic dictionary Missig compulsory key %s", JSON_TOPIC_KEY_TOPIC);
212 + return 1;
213 + }
214 +
215 + topic_generate_final(topic);
216 + aclk_topic_cache_items++;
217 +
218 + return 0;
219 +}
220 +
221 +int aclk_generate_topic_cache(struct json_object *json)
222 +{
223 + json_object *obj;
224 +
225 + size_t array_size = json_object_array_length(json);
226 + if (!array_size) {
227 + error("Empty topic list!");
228 + return 1;
229 + }
230 +
231 + if (aclk_topic_cache)
232 + free_topic_cache();
233 +
234 + aclk_topic_cache = callocz(array_size, sizeof(struct aclk_topic *));
235 +
236 + for (size_t i = 0; i < array_size; i++) {
237 + obj = json_object_array_get_idx(json, i);
238 + if (json_object_get_type(obj) != json_type_object) {
239 + error("expected json_type_object");
240 + return 1;
241 + }
242 + aclk_topic_cache[i] = callocz(1, sizeof(struct aclk_topic));
243 + if (topic_cache_add_topic(obj, aclk_topic_cache[i])) {
244 + error("failed to parse topic @idx=%d", (int)i);
245 + return 1;
246 + }
247 + }
248 +
249 + for (int i = 0; compulsory_topics[i] != ACLK_TOPICID_UNKNOWN; i++) {
250 + if (!aclk_get_topic(compulsory_topics[i])) {
251 + error("missing compulsory topic \"%s\" in password response from cloud", topic_id_to_name(compulsory_topics[i]));
252 + return 1;
253 + }
254 + }
255 +
256 + return 0;
257 }
258
259 /*
@@ -127,9 +263,17 @@ static inline void generate_topic_cache(void)
263 */
264 const char *aclk_get_topic(enum aclk_topics topic)
265 {
130 - generate_topic_cache();
266 + if (!aclk_topic_cache) {
267 + error("Topic cache not initialized");
268 + return NULL;
269 + }
270
132 - return aclk_topic_cache[topic].topic;
271 + for (size_t i = 0; i < aclk_topic_cache_items; i++) {
272 + if (aclk_topic_cache[i]->topic_id == topic)
273 + return aclk_topic_cache[i]->topic;
274 + }
275 + error("Unknown topic");
276 + return NULL;
277 }
278
279 /*
aclk/aclk_util.h
+6 -4
@@ -51,13 +51,15 @@ void aclk_transport_desc_t_destroy(aclk_transport_desc_t *trp_desc);
51 void aclk_env_t_destroy(aclk_env_t *env);
52
53 enum aclk_topics {
54 - ACLK_TOPICID_CHART = 0,
55 - ACLK_TOPICID_ALARMS = 1,
56 - ACLK_TOPICID_METADATA = 2,
57 - ACLK_TOPICID_COMMAND = 3,
54 + ACLK_TOPICID_UNKNOWN = 0,
55 + ACLK_TOPICID_CHART = 1,
56 + ACLK_TOPICID_ALARMS = 2,
57 + ACLK_TOPICID_METADATA = 3,
58 + ACLK_TOPICID_COMMAND = 4
59 };
60
61 const char *aclk_get_topic(enum aclk_topics topic);
62 +int aclk_generate_topic_cache(struct json_object *json);
63 void free_topic_cache(void);
64 // TODO
65 // aclk_topics_reload //when claim id changes