// Copyright 2021-2022 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "natsp.h" #include "mem.h" #include "util.h" #include "js.h" typedef enum { jsStreamActionCreate = 1, jsStreamActionUpdate, jsStreamActionGet, } jsStreamAction; typedef struct apiPaged { int64_t total; int64_t offset; int64_t limit; } apiPaged; static natsStatus _marshalTimeUTC(natsBuffer *buf, const char *fieldName, int64_t timeUTC) { natsStatus s = NATS_OK; char dbuf[36] = {'\0'}; s = nats_EncodeTimeUTC(dbuf, sizeof(dbuf), timeUTC); if (s != NATS_OK) return nats_setError(NATS_ERR, "unable to encode data for field '%s' value %" PRId64, fieldName, timeUTC); s = natsBuf_Append(buf, ",\"", -1); IFOK(s, natsBuf_Append(buf, fieldName, -1)); IFOK(s, natsBuf_Append(buf, "\":\"", -1)); IFOK(s, natsBuf_Append(buf, dbuf, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); return NATS_UPDATE_ERR_STACK(s); } // // Stream related functions // static natsStatus _checkStreamName(const char *stream) { if (nats_IsStringEmpty(stream)) return nats_setError(NATS_INVALID_ARG, "%s", jsErrStreamNameRequired); if (strchr(stream, '.') != NULL) return nats_setError(NATS_INVALID_ARG, "%s '%s' (cannot contain '.')", jsErrInvalidStreamName, stream); return NATS_OK; } static void _destroyPlacement(jsPlacement *placement) { int i; if (placement == NULL) return; NATS_FREE((char*)placement->Cluster); for (i=0; iTagsLen; i++) NATS_FREE((char*) placement->Tags[i]); NATS_FREE((char**) placement->Tags); NATS_FREE(placement); } static void _destroyExternalStream(jsExternalStream *external) { if (external == NULL) return; NATS_FREE((char*) external->APIPrefix); NATS_FREE((char*) external->DeliverPrefix); NATS_FREE(external); } static void _destroyStreamSource(jsStreamSource *source) { if (source == NULL) return; NATS_FREE((char*) source->Name); NATS_FREE((char*) source->FilterSubject); _destroyExternalStream(source->External); NATS_FREE(source); } static void _destroyRePublish(jsRePublish *rp) { if (rp == NULL) return; NATS_FREE((char*) rp->Source); NATS_FREE((char*) rp->Destination); NATS_FREE(rp); } void js_destroyStreamConfig(jsStreamConfig *cfg) { int i; if (cfg == NULL) return; NATS_FREE((char*) cfg->Name); NATS_FREE((char*) cfg->Description); for (i=0; iSubjectsLen; i++) NATS_FREE((char*) cfg->Subjects[i]); NATS_FREE((char**) cfg->Subjects); NATS_FREE((char*) cfg->Template); _destroyPlacement(cfg->Placement); _destroyStreamSource(cfg->Mirror); for (i=0; iSourcesLen; i++) _destroyStreamSource(cfg->Sources[i]); NATS_FREE(cfg->Sources); _destroyRePublish(cfg->RePublish); NATS_FREE(cfg); } static void _destroyPeerInfo(jsPeerInfo *peer) { if (peer == NULL) return; NATS_FREE(peer->Name); NATS_FREE(peer); } static void _destroyClusterInfo(jsClusterInfo *cluster) { int i; if (cluster == NULL) return; NATS_FREE(cluster->Name); NATS_FREE(cluster->Leader); for (i=0; iReplicasLen; i++) _destroyPeerInfo(cluster->Replicas[i]); NATS_FREE(cluster->Replicas); NATS_FREE(cluster); } static void _destroyStreamSourceInfo(jsStreamSourceInfo *info) { if (info == NULL) return; NATS_FREE(info->Name); _destroyExternalStream(info->External); NATS_FREE(info); } static void _destroyLostStreamData(jsLostStreamData *lost) { if (lost == NULL) return; NATS_FREE(lost->Msgs); NATS_FREE(lost); } static void _destroyStreamStateSubjects(jsStreamStateSubjects *subjects) { int i; if (subjects == NULL) return; for (i=0; iCount; i++) NATS_FREE((char*) subjects->List[i].Subject); NATS_FREE(subjects->List); NATS_FREE(subjects); } void js_cleanStreamState(jsStreamState *state) { if (state == NULL) return; NATS_FREE(state->Deleted); _destroyLostStreamData(state->Lost); _destroyStreamStateSubjects(state->Subjects); } static void _destroyStreamAlternate(jsStreamAlternate *sa) { if (sa == NULL) return; NATS_FREE((char*) sa->Name); NATS_FREE((char*) sa->Domain); NATS_FREE((char*) sa->Cluster); NATS_FREE(sa); } void jsStreamInfo_Destroy(jsStreamInfo *si) { int i; if (si == NULL) return; js_destroyStreamConfig(si->Config); _destroyClusterInfo(si->Cluster); js_cleanStreamState(&(si->State)); _destroyStreamSourceInfo(si->Mirror); for (i=0; iSourcesLen; i++) _destroyStreamSourceInfo(si->Sources[i]); NATS_FREE(si->Sources); for (i=0; iAlternatesLen; i++) _destroyStreamAlternate(si->Alternates[i]); NATS_FREE(si->Alternates); NATS_FREE(si); } static natsStatus _unmarshalExternalStream(nats_JSON *json, const char *fieldName, jsExternalStream **new_external) { jsExternalStream *external = NULL; nats_JSON *obj = NULL; natsStatus s; s = nats_JSONGetObject(json, fieldName, &obj); if (obj == NULL) return NATS_UPDATE_ERR_STACK(s); external = (jsExternalStream*) NATS_CALLOC(1, sizeof(jsExternalStream)); if (external == NULL) return nats_setDefaultError(NATS_NO_MEMORY); s = nats_JSONGetStr(obj, "api", (char**) &(external->APIPrefix)); IFOK(s, nats_JSONGetStr(obj, "deliver", (char**) &(external->DeliverPrefix))); if (s == NATS_OK) *new_external = external; else _destroyExternalStream(external); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _marshalExternalStream(jsExternalStream *external, const char *fieldName, natsBuffer *buf) { natsStatus s = NATS_OK; IFOK(s, natsBuf_Append(buf, ",\"", -1)); IFOK(s, natsBuf_Append(buf, fieldName, -1)); IFOK(s, natsBuf_Append(buf, "\":{\"api\":\"", -1)); IFOK(s, natsBuf_Append(buf, external->APIPrefix, -1)); if ((s == NATS_OK) && !nats_IsStringEmpty(external->DeliverPrefix)) { IFOK(s, natsBuf_Append(buf, "\",\"deliver\":\"", -1)); IFOK(s, natsBuf_Append(buf, external->DeliverPrefix, -1)); } IFOK(s, natsBuf_Append(buf, "\"}", -1)); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalStreamSource(nats_JSON *json, const char *fieldName, jsStreamSource **new_source) { jsStreamSource *source = NULL; nats_JSON *obj = NULL; natsStatus s; if (fieldName != NULL) { s = nats_JSONGetObject(json, fieldName, &obj); if (obj == NULL) return NATS_UPDATE_ERR_STACK(s); } else { obj = json; } source = (jsStreamSource*) NATS_CALLOC(1, sizeof(jsStreamSource)); if (source == NULL) return nats_setDefaultError(NATS_NO_MEMORY); s = nats_JSONGetStr(obj, "name", (char**) &(source->Name)); IFOK(s, nats_JSONGetULong(obj, "opt_start_seq", &(source->OptStartSeq))); IFOK(s, nats_JSONGetTime(obj, "opt_start_time", &(source->OptStartTime))); IFOK(s, nats_JSONGetStr(obj, "filter_subject", (char**) &(source->FilterSubject))); IFOK(s, _unmarshalExternalStream(obj, "external", &(source->External))); if (s == NATS_OK) *new_source = source; else _destroyStreamSource(source); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _marshalStreamSource(jsStreamSource *source, const char *fieldName, natsBuffer *buf) { natsStatus s = NATS_OK; if (fieldName != NULL) { IFOK(s, natsBuf_Append(buf, ",\"", -1)); IFOK(s, natsBuf_Append(buf, fieldName, -1)); IFOK(s, natsBuf_Append(buf, "\":", -1)); } IFOK(s, natsBuf_Append(buf, "{\"name\":\"", -1)); IFOK(s, natsBuf_Append(buf, source->Name, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); if ((s == NATS_OK) && (source->OptStartSeq > 0)) s = nats_marshalLong(buf, true, "opt_start_seq", source->OptStartSeq); if ((s == NATS_OK) && (source->OptStartTime > 0)) IFOK(s, _marshalTimeUTC(buf, "opt_start_time", source->OptStartTime)); if (source->FilterSubject != NULL) { IFOK(s, natsBuf_Append(buf, ",\"filter_subject\":\"", -1)); IFOK(s, natsBuf_Append(buf, source->FilterSubject, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); } if (source->External != NULL) IFOK(s, _marshalExternalStream(source->External, "external", buf)); IFOK(s, natsBuf_AppendByte(buf, '}')); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalPlacement(nats_JSON *json, const char *fieldName, jsPlacement **new_placement) { jsPlacement *placement = NULL; nats_JSON *jpl = NULL; natsStatus s; s = nats_JSONGetObject(json, fieldName, &jpl); if (jpl == NULL) return NATS_UPDATE_ERR_STACK(s); placement = (jsPlacement*) NATS_CALLOC(1, sizeof(jsPlacement)); if (placement == NULL) return nats_setDefaultError(NATS_NO_MEMORY); s = nats_JSONGetStr(jpl, "cluster", (char**) &(placement->Cluster)); IFOK(s, nats_JSONGetArrayStr(jpl, "tags", (char***) &(placement->Tags), &(placement->TagsLen))); if (s == NATS_OK) *new_placement = placement; else _destroyPlacement(placement); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _marshalPlacement(jsPlacement *placement, natsBuffer *buf) { natsStatus s; s = natsBuf_Append(buf, ",\"placement\":{\"cluster\":\"", -1); IFOK(s, natsBuf_Append(buf, placement->Cluster, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); if (placement->TagsLen > 0) { int i; IFOK(s, natsBuf_Append(buf, ",\"tags\":[\"", -1)); for (i=0; (s == NATS_OK) && (iTagsLen); i++) { IFOK(s, natsBuf_Append(buf, placement->Tags[i], -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); if (i < placement->TagsLen-1) IFOK(s, natsBuf_Append(buf, ",\"", -1)); } IFOK(s, natsBuf_AppendByte(buf, ']')); } IFOK(s, natsBuf_AppendByte(buf, '}')); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalRetentionPolicy(nats_JSON *json, const char *fieldName, jsRetentionPolicy *policy) { natsStatus s = NATS_OK; char *str = NULL; s = nats_JSONGetStr(json, fieldName, &str); if (str == NULL) return NATS_UPDATE_ERR_STACK(s); if (strcmp(str, jsRetPolicyLimitsStr) == 0) *policy = js_LimitsPolicy; else if (strcmp(str, jsRetPolicyInterestStr) == 0) *policy = js_InterestPolicy; else if (strcmp(str, jsRetPolicyWorkQueueStr) == 0) *policy = js_WorkQueuePolicy; else s = nats_setError(NATS_ERR, "unable to unmarshal retention policy '%s'", str); NATS_FREE(str); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _marshalRetentionPolicy(jsRetentionPolicy policy, natsBuffer *buf) { natsStatus s; const char *rp = NULL; s = natsBuf_Append(buf, ",\"retention\":\"", -1); switch (policy) { case js_LimitsPolicy: rp = jsRetPolicyLimitsStr; break; case js_InterestPolicy: rp = jsRetPolicyInterestStr; break; case js_WorkQueuePolicy: rp = jsRetPolicyWorkQueueStr; break; default: return nats_setError(NATS_INVALID_ARG, "invalid retention policy %d", (int) policy); } IFOK(s, natsBuf_Append(buf, rp, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalDiscardPolicy(nats_JSON *json, const char *fieldName, jsDiscardPolicy *policy) { natsStatus s = NATS_OK; char *str = NULL; s = nats_JSONGetStr(json, fieldName, &str); if (str == NULL) return NATS_UPDATE_ERR_STACK(s); if (strcmp(str, jsDiscardPolicyOldStr) == 0) *policy = js_DiscardOld; else if (strcmp(str, jsDiscardPolicyNewStr) == 0) *policy = js_DiscardNew; else s = nats_setError(NATS_ERR, "unable to unmarshal discard policy '%s'", str); NATS_FREE(str); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _marshalDiscardPolicy(jsDiscardPolicy policy, natsBuffer *buf) { natsStatus s; const char *dp = NULL; s = natsBuf_Append(buf, ",\"discard\":\"", -1); switch (policy) { case js_DiscardOld: dp = jsDiscardPolicyOldStr; break; case js_DiscardNew: dp = jsDiscardPolicyNewStr; break; default: return nats_setError(NATS_INVALID_ARG, "invalid discard policy %d", (int) policy); } IFOK(s, natsBuf_Append(buf, dp, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalStorageType(nats_JSON *json, const char *fieldName, jsStorageType *storage) { natsStatus s = NATS_OK; char *str = NULL; s = nats_JSONGetStr(json, "storage", &str); if (str == NULL) return NATS_UPDATE_ERR_STACK(s); if (strcmp(str, jsStorageTypeFileStr) == 0) *storage = js_FileStorage; else if (strcmp(str, jsStorageTypeMemStr) == 0) *storage = js_MemoryStorage; else s = nats_setError(NATS_ERR, "unable to unmarshal storage type '%s'", str); NATS_FREE(str); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _marshalStorageType(jsStorageType storage, natsBuffer *buf) { natsStatus s; const char *st = NULL; s = natsBuf_Append(buf, ",\"storage\":\"", -1); switch (storage) { case js_FileStorage: st = jsStorageTypeFileStr; break; case js_MemoryStorage: st = jsStorageTypeMemStr; break; default: return nats_setError(NATS_INVALID_ARG, "invalid storage type %d", (int) storage); } IFOK(s, natsBuf_Append(buf, st, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalRePublish(nats_JSON *json, const char *fieldName, jsRePublish **new_republish) { jsRePublish *rp = NULL; nats_JSON *jsm = NULL; natsStatus s; s = nats_JSONGetObject(json, fieldName, &jsm); if (jsm == NULL) return NATS_UPDATE_ERR_STACK(s); rp = (jsRePublish*) NATS_CALLOC(1, sizeof(jsRePublish)); if (rp == NULL) return nats_setDefaultError(NATS_NO_MEMORY); s = nats_JSONGetStr(jsm, "src", (char**) &(rp->Source)); IFOK(s, nats_JSONGetStr(jsm, "dest", (char**) &(rp->Destination))); IFOK(s, nats_JSONGetBool(jsm, "headers_only", &(rp->HeadersOnly))); if (s == NATS_OK) *new_republish = rp; else _destroyRePublish(rp); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_unmarshalStreamConfig(nats_JSON *json, const char *fieldName, jsStreamConfig **new_cfg) { nats_JSON *jcfg = NULL; jsStreamConfig *cfg = NULL; nats_JSON **sources = NULL; int sourcesLen = 0; natsStatus s; if (fieldName != NULL) { s = nats_JSONGetObject(json, fieldName, &jcfg); if (jcfg == NULL) return NATS_UPDATE_ERR_STACK(s); } else { jcfg = json; } cfg = (jsStreamConfig*) NATS_CALLOC(1, sizeof(jsStreamConfig)); if (cfg == NULL) return nats_setDefaultError(NATS_NO_MEMORY); s = nats_JSONGetStr(jcfg, "name", (char**) &(cfg->Name)); IFOK(s, nats_JSONGetStr(jcfg, "description", (char**) &(cfg->Description))); IFOK(s, nats_JSONGetArrayStr(jcfg, "subjects", (char***) &(cfg->Subjects), &(cfg->SubjectsLen))); IFOK(s, _unmarshalRetentionPolicy(jcfg, "retention", &(cfg->Retention))); IFOK(s, nats_JSONGetLong(jcfg, "max_consumers", &(cfg->MaxConsumers))); IFOK(s, nats_JSONGetLong(jcfg, "max_msgs", &(cfg->MaxMsgs))); IFOK(s, nats_JSONGetLong(jcfg, "max_bytes", &(cfg->MaxBytes))); IFOK(s, nats_JSONGetLong(jcfg, "max_age", &(cfg->MaxAge))); IFOK(s, nats_JSONGetLong(jcfg, "max_msgs_per_subject", &(cfg->MaxMsgsPerSubject))); IFOK(s, nats_JSONGetInt32(jcfg, "max_msg_size", &(cfg->MaxMsgSize))); IFOK(s, _unmarshalDiscardPolicy(jcfg, "discard", &(cfg->Discard))); IFOK(s, _unmarshalStorageType(jcfg, "storage", &(cfg->Storage))); IFOK(s, nats_JSONGetLong(jcfg, "num_replicas", &(cfg->Replicas))); IFOK(s, nats_JSONGetBool(jcfg, "no_ack", &(cfg->NoAck))); IFOK(s, nats_JSONGetStr(jcfg, "template_owner", (char**) &(cfg->Template))); IFOK(s, nats_JSONGetLong(jcfg, "duplicate_window", &(cfg->Duplicates))); IFOK(s, _unmarshalPlacement(jcfg, "placement", &(cfg->Placement))); IFOK(s, _unmarshalStreamSource(jcfg, "mirror", &(cfg->Mirror))); // Get the sources and unmarshal if present IFOK(s, nats_JSONGetArrayObject(jcfg, "sources", &sources, &sourcesLen)); if ((s == NATS_OK) && (sources != NULL)) { int i; cfg->Sources = (jsStreamSource**) NATS_CALLOC(sourcesLen, sizeof(jsStreamSource*)); if (cfg->Sources == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); for (i=0; (s == NATS_OK) && (iSources[i])); if (s == NATS_OK) cfg->SourcesLen++; } // Free the array of JSON objects that was allocated by nats_JSONGetArrayObject. NATS_FREE(sources); } IFOK(s, nats_JSONGetBool(jcfg, "sealed", &(cfg->Sealed))); IFOK(s, nats_JSONGetBool(jcfg, "deny_delete", &(cfg->DenyDelete))); IFOK(s, nats_JSONGetBool(jcfg, "deny_purge", &(cfg->DenyPurge))); IFOK(s, nats_JSONGetBool(jcfg, "allow_rollup_hdrs", &(cfg->AllowRollup))); IFOK(s, _unmarshalRePublish(jcfg, "republish", &(cfg->RePublish))); IFOK(s, nats_JSONGetBool(jcfg, "allow_direct", &(cfg->AllowDirect))); IFOK(s, nats_JSONGetBool(jcfg, "mirror_direct", &(cfg->MirrorDirect))); IFOK(s, nats_JSONGetBool(jcfg, "discard_new_per_subject", &(cfg->DiscardNewPerSubject))); if (s == NATS_OK) *new_cfg = cfg; else js_destroyStreamConfig(cfg); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_marshalStreamConfig(natsBuffer **new_buf, jsStreamConfig *cfg) { natsBuffer *buf = NULL; natsStatus s; s = natsBuf_Create(&buf, 256); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); s = natsBuf_Append(buf, "{\"name\":\"", -1); IFOK(s, natsBuf_Append(buf, cfg->Name, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); if ((s == NATS_OK) && !nats_IsStringEmpty(cfg->Description)) { IFOK(s, natsBuf_Append(buf, ",\"description\":\"", -1)); IFOK(s, natsBuf_Append(buf, cfg->Description, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); } if ((s == NATS_OK) && (cfg->SubjectsLen > 0)) { int i; IFOK(s, natsBuf_Append(buf, ",\"subjects\":[\"", -1)); for (i=0; (s == NATS_OK) && (iSubjectsLen); i++) { IFOK(s, natsBuf_Append(buf, cfg->Subjects[i], -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); if (i < cfg->SubjectsLen - 1) IFOK(s, natsBuf_Append(buf, ",\"", -1)); } IFOK(s, natsBuf_AppendByte(buf, ']')); } IFOK(s, _marshalRetentionPolicy(cfg->Retention, buf)); IFOK(s, nats_marshalLong(buf, true, "max_consumers", cfg->MaxConsumers)); IFOK(s, nats_marshalLong(buf, true, "max_msgs", cfg->MaxMsgs)); IFOK(s, nats_marshalLong(buf, true, "max_bytes", cfg->MaxBytes)); IFOK(s, nats_marshalLong(buf, true, "max_age", cfg->MaxAge)); IFOK(s, nats_marshalLong(buf, true, "max_msg_size", (int64_t) cfg->MaxMsgSize)); IFOK(s, nats_marshalLong(buf, true, "max_msgs_per_subject", cfg->MaxMsgsPerSubject)); IFOK(s, _marshalDiscardPolicy(cfg->Discard, buf)); IFOK(s, _marshalStorageType(cfg->Storage, buf)); IFOK(s, nats_marshalLong(buf, true, "num_replicas", cfg->Replicas)); if ((s == NATS_OK) && cfg->NoAck) IFOK(s, natsBuf_Append(buf, ",\"no_ack\":true", -1)); if ((s == NATS_OK) && (cfg->Template != NULL)) { IFOK(s, natsBuf_Append(buf, ",\"template_owner\":\"", -1)); IFOK(s, natsBuf_Append(buf, cfg->Template, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); } if ((s == NATS_OK) && (cfg->Duplicates != 0)) IFOK(s, nats_marshalLong(buf, true, "duplicate_window", cfg->Duplicates)); if ((s == NATS_OK) && (cfg->Placement != NULL)) IFOK(s, _marshalPlacement(cfg->Placement, buf)); if ((s == NATS_OK) && (cfg->Mirror != NULL)) IFOK(s, _marshalStreamSource(cfg->Mirror, "mirror", buf)); if ((s == NATS_OK) && (cfg->SourcesLen > 0)) { int i; IFOK(s, natsBuf_Append(buf, ",\"sources\":[", -1)); for (i=0; (s == NATS_OK) && (i < cfg->SourcesLen); i++) { IFOK(s, _marshalStreamSource(cfg->Sources[i], NULL, buf)); if ((s == NATS_OK) && (i < cfg->SourcesLen-1)) IFOK(s, natsBuf_AppendByte(buf, ',')); } IFOK(s, natsBuf_AppendByte(buf, ']')); } if ((s == NATS_OK) && cfg->Sealed) IFOK(s, natsBuf_Append(buf, ",\"sealed\":true", -1)); if ((s == NATS_OK) && cfg->DenyDelete) IFOK(s, natsBuf_Append(buf, ",\"deny_delete\":true", -1)); if ((s == NATS_OK) && cfg->DenyPurge) IFOK(s, natsBuf_Append(buf, ",\"deny_purge\":true", -1)); if ((s == NATS_OK) && cfg->AllowRollup) IFOK(s, natsBuf_Append(buf, ",\"allow_rollup_hdrs\":true", -1)); if ((s == NATS_OK) && (cfg->RePublish != NULL) && !nats_IsStringEmpty(cfg->RePublish->Destination)) { // "dest" is not omitempty, in that the field will always be present. IFOK(s, natsBuf_Append(buf, ",\"republish\":{\"dest\":\"", -1)); IFOK(s, natsBuf_Append(buf, cfg->RePublish->Destination, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); // Now the source... if (!nats_IsStringEmpty(cfg->RePublish->Source)) { IFOK(s, natsBuf_Append(buf, ",\"src\":\"", -1)) IFOK(s, natsBuf_Append(buf, cfg->RePublish->Source, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); } if (cfg->RePublish->HeadersOnly) IFOK(s, natsBuf_Append(buf, ",\"headers_only\":true", -1)); IFOK(s, natsBuf_AppendByte(buf, '}')); } if ((s == NATS_OK) && cfg->AllowDirect) IFOK(s, natsBuf_Append(buf, ",\"allow_direct\":true", -1)); if ((s == NATS_OK) && cfg->MirrorDirect) IFOK(s, natsBuf_Append(buf, ",\"mirror_direct\":true", -1)); if ((s == NATS_OK) && cfg->DiscardNewPerSubject) IFOK(s, natsBuf_Append(buf, ",\"discard_new_per_subject\":true", -1)); IFOK(s, natsBuf_AppendByte(buf, '}')); if (s == NATS_OK) *new_buf = buf; else natsBuf_Destroy(buf); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalLostStreamData(nats_JSON *pjson, const char *fieldName, jsLostStreamData **new_lost) { natsStatus s = NATS_OK; jsLostStreamData *lost = NULL; nats_JSON *json = NULL; s = nats_JSONGetObject(pjson, fieldName, &json); if (json == NULL) return NATS_UPDATE_ERR_STACK(s); lost = (jsLostStreamData*) NATS_CALLOC(1, sizeof(jsLostStreamData)); if (lost == NULL) return nats_setDefaultError(NATS_NO_MEMORY); IFOK(s, nats_JSONGetArrayULong(json, "msgs", &(lost->Msgs), &(lost->MsgsLen))); IFOK(s, nats_JSONGetULong(json, "bytes", &(lost->Bytes))); if (s == NATS_OK) *new_lost = lost; else _destroyLostStreamData(lost); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _fillSubjectsList(void *userInfo, const char *subject, nats_JSONField *f) { jsStreamStateSubjects *subjs = (jsStreamStateSubjects*) userInfo; natsStatus s = NATS_OK; int i = subjs->Count; DUP_STRING(s, subjs->List[i].Subject, subject); if (s == NATS_OK) { subjs->List[i].Msgs = f->value.vuint; subjs->Count = i+1; } return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalStreamStateSubjects(nats_JSON *pjson, const char *fieldName, jsStreamStateSubjects **subjects) { nats_JSON *json = NULL; natsStatus s = NATS_OK; jsStreamStateSubjects *subjs= NULL; int n; s = nats_JSONGetObject(pjson, fieldName, &json); if (json == NULL) return NATS_UPDATE_ERR_STACK(s); if ((n = natsStrHash_Count(json->fields)) > 0) { subjs = NATS_CALLOC(1, sizeof(jsStreamStateSubjects)); if (subjs == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); if (s == NATS_OK) { subjs->List = NATS_CALLOC(n, sizeof(jsStreamStateSubject)); if (subjs->List == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); } IFOK(s, nats_JSONRange(json, TYPE_NUM, TYPE_UINT, _fillSubjectsList, (void*) subjs)); if (s == NATS_OK) *subjects = subjs; else _destroyStreamStateSubjects(subjs); } return NATS_UPDATE_ERR_STACK(s); } natsStatus js_unmarshalStreamState(nats_JSON *pjson, const char *fieldName, jsStreamState *state) { nats_JSON *json = NULL; natsStatus s; s = nats_JSONGetObject(pjson, fieldName, &json); if (json == NULL) return NATS_UPDATE_ERR_STACK(s); s = nats_JSONGetULong(json, "messages", &(state->Msgs)); IFOK(s, nats_JSONGetULong(json, "bytes", &(state->Bytes))); IFOK(s, nats_JSONGetULong(json, "first_seq", &(state->FirstSeq))); IFOK(s, nats_JSONGetTime(json, "first_ts", &(state->FirstTime))); IFOK(s, nats_JSONGetULong(json, "last_seq", &(state->LastSeq))); IFOK(s, nats_JSONGetTime(json, "last_ts", &(state->LastTime))); IFOK(s, nats_JSONGetULong(json, "num_deleted", &(state->NumDeleted))); IFOK(s, nats_JSONGetArrayULong(json, "deleted", &(state->Deleted), &(state->DeletedLen))); IFOK(s, _unmarshalLostStreamData(json, "lost", &(state->Lost))); IFOK(s, nats_JSONGetLong(json, "consumer_count", &(state->Consumers))); IFOK(s, nats_JSONGetLong(json, "num_subjects", &(state->NumSubjects))); IFOK(s, _unmarshalStreamStateSubjects(json, "subjects", &(state->Subjects))); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalPeerInfo(nats_JSON *json, jsPeerInfo **new_pi) { jsPeerInfo *pi = NULL; natsStatus s; pi = (jsPeerInfo*) NATS_CALLOC(1, sizeof(jsPeerInfo)); if (pi == NULL) return nats_setDefaultError(NATS_NO_MEMORY); s = nats_JSONGetStr(json, "name", &(pi->Name)); IFOK(s, nats_JSONGetBool(json, "current", &(pi->Current))); IFOK(s, nats_JSONGetBool(json, "offline", &(pi->Offline))); IFOK(s, nats_JSONGetLong(json, "active", &(pi->Active))); IFOK(s, nats_JSONGetULong(json, "lag", &(pi->Lag))); if (s == NATS_OK) *new_pi = pi; else _destroyPeerInfo(pi); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalClusterInfo(nats_JSON *pjson, const char *fieldName, jsClusterInfo **new_ci) { jsClusterInfo *ci = NULL; nats_JSON *json = NULL; nats_JSON **replicas = NULL; int replicasLen = 0; natsStatus s; s = nats_JSONGetObject(pjson, fieldName, &json); if (json == NULL) return NATS_UPDATE_ERR_STACK(s); ci = (jsClusterInfo*) NATS_CALLOC(1, sizeof(jsClusterInfo)); if (ci == NULL) return nats_setDefaultError(NATS_NO_MEMORY); s = nats_JSONGetStr(json, "name", &(ci->Name)); IFOK(s, nats_JSONGetStr(json, "leader", &(ci->Leader))); IFOK(s, nats_JSONGetArrayObject(json, "replicas", &replicas, &replicasLen)); if ((s == NATS_OK) && (replicas != NULL)) { int i; ci->Replicas = (jsPeerInfo**) NATS_CALLOC(replicasLen, sizeof(jsPeerInfo*)); if (ci->Replicas == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); for (i=0; (s == NATS_OK) && (iReplicas[i])); if (s == NATS_OK) ci->ReplicasLen++; } // Free the array of JSON objects that was allocated by nats_JSONGetArrayObject. NATS_FREE(replicas); } if (s == NATS_OK) *new_ci = ci; else _destroyClusterInfo(ci); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalStreamSourceInfo(nats_JSON *pjson, const char *fieldName, jsStreamSourceInfo **new_src) { nats_JSON *json = NULL; jsStreamSourceInfo *ssi = NULL; natsStatus s; if (fieldName != NULL) { s = nats_JSONGetObject(pjson, fieldName, &json); if (json == NULL) return NATS_UPDATE_ERR_STACK(s); } else { json = pjson; } ssi = (jsStreamSourceInfo*) NATS_CALLOC(1, sizeof(jsStreamSourceInfo)); if (ssi == NULL) return nats_setDefaultError(NATS_NO_MEMORY); s = nats_JSONGetStr(json, "name", &(ssi->Name)); IFOK(s, _unmarshalExternalStream(json, "external", &(ssi->External))); IFOK(s, nats_JSONGetULong(json, "lag", &(ssi->Lag))); IFOK(s, nats_JSONGetLong(json, "active", &(ssi->Active))); if (s == NATS_OK) *new_src = ssi; else _destroyStreamSourceInfo(ssi); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalStreamAlternate(nats_JSON *json, jsStreamAlternate **new_alt) { jsStreamAlternate *sa = NULL; natsStatus s = NATS_OK; sa = (jsStreamAlternate*) NATS_CALLOC(1, sizeof(jsStreamAlternate)); if (sa == NULL) return nats_setDefaultError(NATS_NO_MEMORY); s = nats_JSONGetStr(json, "name", (char**) &(sa->Name)); IFOK(s, nats_JSONGetStr(json, "domain", (char**) &(sa->Domain))); IFOK(s, nats_JSONGetStr(json, "cluster", (char**) &(sa->Cluster))); if (s == NATS_OK) *new_alt = sa; else _destroyStreamAlternate(sa); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalStreamInfoPaged(nats_JSON *json, jsStreamInfo **new_si, apiPaged *page) { jsStreamInfo *si = NULL; nats_JSON **sources = NULL; int sourcesLen = 0; nats_JSON **alts = NULL; int altsLen = 0; natsStatus s; si = (jsStreamInfo*) NATS_CALLOC(1, sizeof(jsStreamInfo)); if (si == NULL) return nats_setDefaultError(NATS_NO_MEMORY); // Get the config object s = js_unmarshalStreamConfig(json, "config", &(si->Config)); IFOK(s, nats_JSONGetTime(json, "created", &(si->Created))); IFOK(s, js_unmarshalStreamState(json, "state", &(si->State))); IFOK(s, _unmarshalClusterInfo(json, "cluster", &(si->Cluster))); IFOK(s, _unmarshalStreamSourceInfo(json, "mirror", &(si->Mirror))); IFOK(s, nats_JSONGetArrayObject(json, "sources", &sources, &sourcesLen)); if ((s == NATS_OK) && (sources != NULL)) { int i; si->Sources = (jsStreamSourceInfo**) NATS_CALLOC(sourcesLen, sizeof(jsStreamSourceInfo*)); if (si->Sources == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); for (i=0; (s == NATS_OK) && (iSources[i])); if (s == NATS_OK) si->SourcesLen++; } // Free the array of JSON objects that was allocated by nats_JSONGetArrayObject. NATS_FREE(sources); } IFOK(s, nats_JSONGetArrayObject(json, "alternates", &alts, &altsLen)); if ((s == NATS_OK) && (alts != NULL)) { int i; si->Alternates = (jsStreamAlternate**) NATS_CALLOC(altsLen, sizeof(jsStreamAlternate*)); if (si->Alternates == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); for (i=0; (s == NATS_OK) && (iAlternates[i])); if (s == NATS_OK) si->AlternatesLen++; } // Free the array of JSON objects that was allocated by nats_JSONGetArrayObject. NATS_FREE(alts); } if ((s == NATS_OK) && (page != NULL)) { IFOK(s, nats_JSONGetLong(json, "total", &page->total)); IFOK(s, nats_JSONGetLong(json, "offset", &page->offset)); IFOK(s, nats_JSONGetLong(json, "limit", &page->limit)); } if (s == NATS_OK) *new_si = si; else jsStreamInfo_Destroy(si); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_unmarshalStreamInfo(nats_JSON *json, jsStreamInfo **new_si) { natsStatus s = _unmarshalStreamInfoPaged(json, new_si, NULL); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalStreamCreateResp(jsStreamInfo **new_si, apiPaged *page, natsMsg *resp, jsErrCode *errCode) { nats_JSON *json = NULL; jsApiResponse ar; natsStatus s; s = js_unmarshalResponse(&ar, &json, resp); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); if (js_apiResponseIsErr(&ar)) { if (errCode != NULL) *errCode = (int) ar.Error.ErrCode; // If the error code is JSStreamNotFoundErr then pick NATS_NOT_FOUND. if (ar.Error.ErrCode == JSStreamNotFoundErr) s = NATS_NOT_FOUND; else s = nats_setError(NATS_ERR, "%s", ar.Error.Description); } else if (new_si != NULL) { // At this point we need to unmarshal the stream info itself. s = _unmarshalStreamInfoPaged(json, new_si, page); } js_freeApiRespContent(&ar); nats_JSONDestroy(json); return NATS_UPDATE_ERR_STACK(s); } natsStatus jsStreamConfig_Init(jsStreamConfig *cfg) { if (cfg == NULL) return nats_setDefaultError(NATS_INVALID_ARG); memset(cfg, 0, sizeof(jsStreamConfig)); cfg->Retention = js_LimitsPolicy; cfg->MaxConsumers = -1; cfg->MaxMsgs = -1; cfg->MaxBytes = -1; cfg->MaxMsgSize = -1; cfg->Storage = js_FileStorage; cfg->Discard = js_DiscardOld; cfg->Replicas = 1; return NATS_OK; } static void _restoreMirrorAndSourcesExternal(jsStreamConfig *cfg) { int i; // We are guaranteed that if a source's Domain is set, there was originally // no External value. So free any External value and reset to NULL to // restore the original setting. if ((cfg->Mirror != NULL) && !nats_IsStringEmpty(cfg->Mirror->Domain)) { _destroyExternalStream(cfg->Mirror->External); cfg->Mirror->External = NULL; } for (i=0; iSourcesLen; i++) { jsStreamSource *src = cfg->Sources[i]; if ((src != NULL) && !nats_IsStringEmpty(src->Domain)) { _destroyExternalStream(src->External); src->External = NULL; } } } static natsStatus _convertDomain(jsStreamSource *src) { jsExternalStream *e = NULL; e = (jsExternalStream*) NATS_CALLOC(1, sizeof(jsExternalStream)); if (e == NULL) return nats_setDefaultError(NATS_NO_MEMORY); if (nats_asprintf((char**) &(e->APIPrefix), jsExtDomainT, src->Domain) < 0) { NATS_FREE(e); return nats_setDefaultError(NATS_NO_MEMORY); } src->External = e; return NATS_OK; } static natsStatus _convertMirrorAndSourcesDomain(bool *converted, jsStreamConfig *cfg) { natsStatus s = NATS_OK; bool cm = false; bool cs = false; int i; *converted = false; if ((cfg->Mirror != NULL) && !nats_IsStringEmpty(cfg->Mirror->Domain)) { if (cfg->Mirror->External != NULL) return nats_setError(NATS_INVALID_ARG, "%s", "mirror's domain and external are both set"); cm = true; } for (i=0; iSourcesLen; i++) { jsStreamSource *src = cfg->Sources[i]; if ((src != NULL) && !nats_IsStringEmpty(src->Domain)) { if (src->External != NULL) return nats_setError(NATS_INVALID_ARG, "%s", "source's domain and external are both set"); cs = true; } } if (!cm && !cs) return NATS_OK; if (cm) s = _convertDomain(cfg->Mirror); if ((s == NATS_OK) && cs) { for (i=0; (s == NATS_OK) && (iSourcesLen); i++) { jsStreamSource *src = cfg->Sources[i]; if ((src != NULL) && !nats_IsStringEmpty(src->Domain)) s = _convertDomain(src); } } if (s == NATS_OK) *converted = true; else _restoreMirrorAndSourcesExternal(cfg); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _addOrUpdate(jsStreamInfo **new_si, jsStreamAction action, jsCtx *js, jsStreamConfig *cfg, jsOptions *opts, jsErrCode *errCode) { natsStatus s = NATS_OK; natsBuffer *buf = NULL; char *subj = NULL; natsMsg *resp = NULL; natsConnection *nc = NULL; const char *apiT = NULL; bool freePfx = false; bool msc = false; jsOptions o; if (errCode != NULL) *errCode = 0; if (js == NULL) return nats_setDefaultError(NATS_INVALID_ARG); if (cfg == NULL) return nats_setError(NATS_INVALID_ARG, "%s", jsErrStreamConfigRequired); s = _checkStreamName(cfg->Name); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); switch (action) { case jsStreamActionCreate: apiT = jsApiStreamCreateT; break; case jsStreamActionUpdate: apiT = jsApiStreamUpdateT; break; default: abort(); } s = js_setOpts(&nc, &freePfx, js, opts, &o); if (s == NATS_OK) { if (nats_asprintf(&subj, apiT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, cfg->Name) < 0) s = nats_setDefaultError(NATS_NO_MEMORY); if (freePfx) NATS_FREE((char*) o.Prefix); } if ((s == NATS_OK) && (action == jsStreamActionCreate)) s = _convertMirrorAndSourcesDomain(&msc, cfg); // Marshal the stream create/update request IFOK(s, js_marshalStreamConfig(&buf, cfg)); // Send the request IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, natsBuf_Data(buf), natsBuf_Len(buf), o.Wait)); // If we got a response, check for error or return the stream info result. IFOK(s, _unmarshalStreamCreateResp(new_si, NULL, resp, errCode)); // If mirror and/or sources were converted for the domain, then we need // to restore the original values (which will free the memory that was // allocated for the conversion). if (msc) _restoreMirrorAndSourcesExternal(cfg); natsBuf_Destroy(buf); natsMsg_Destroy(resp); NATS_FREE(subj); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_AddStream(jsStreamInfo **new_si, jsCtx *js, jsStreamConfig *cfg, jsOptions *opts, jsErrCode *errCode) { natsStatus s = _addOrUpdate(new_si, jsStreamActionCreate, js, cfg, opts, errCode); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_UpdateStream(jsStreamInfo **new_si, jsCtx *js, jsStreamConfig *cfg, jsOptions *opts, jsErrCode *errCode) { natsStatus s = _addOrUpdate(new_si, jsStreamActionUpdate, js, cfg, opts, errCode); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _marshalStreamInfoReq(natsBuffer **new_buf, struct jsOptionsStreamInfo *o, int64_t offset) { natsBuffer *buf = *new_buf; natsStatus s = NATS_OK; if (!o->DeletedDetails && nats_IsStringEmpty(o->SubjectsFilter)) return NATS_OK; if (buf == NULL) s = natsBuf_Create(&buf, 30); IFOK(s, natsBuf_AppendByte(buf, '{')); if ((s == NATS_OK) && o->DeletedDetails) s = natsBuf_Append(buf, "\"deleted_details\":true", -1); if ((s == NATS_OK) && !nats_IsStringEmpty(o->SubjectsFilter)) { if (o->DeletedDetails) s = natsBuf_AppendByte(buf, ','); IFOK(s, natsBuf_Append(buf, "\"subjects_filter\":\"", -1)); IFOK(s, natsBuf_Append(buf, o->SubjectsFilter, -1)); IFOK(s, natsBuf_AppendByte(buf, '\"')); if ((s == NATS_OK) && (offset > 0)) IFOK(s, nats_marshalLong(buf, true, "offset", offset)); } IFOK(s, natsBuf_AppendByte(buf, '}')); if (*new_buf == NULL) *new_buf = buf; return NATS_UPDATE_ERR_STACK(s); } natsStatus js_GetStreamInfo(jsStreamInfo **new_si, jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode) { natsStatus s; natsBuffer *buf = NULL; char *subj = NULL; char *req = NULL; int reqLen = 0; natsMsg *resp = NULL; natsConnection *nc = NULL; bool freePfx = false; jsOptions o; // For subjects pagination int64_t offset = 0; bool doPage = false; bool done = false; jsStreamInfo *si = NULL; jsStreamStateSubjects *subjects = NULL; apiPaged page; if (errCode != NULL) *errCode = 0; if ((js == NULL) || (new_si == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); s = _checkStreamName(stream); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); s = js_setOpts(&nc, &freePfx, js, opts, &o); if (s == NATS_OK) { if (nats_asprintf(&subj, jsApiStreamInfoT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream) < 0) s = nats_setDefaultError(NATS_NO_MEMORY); if (freePfx) NATS_FREE((char*) o.Prefix); } if (!nats_IsStringEmpty(o.Stream.Info.SubjectsFilter)) { doPage = true; memset(&page, 0, sizeof(apiPaged)); } do { // This will return a buffer if the request was marshal'ed // (due to presence of options) IFOK(s, _marshalStreamInfoReq(&buf, &(o.Stream.Info), offset)); if ((s == NATS_OK) && (buf != NULL) && (natsBuf_Len(buf) > 0)) { req = natsBuf_Data(buf); reqLen = natsBuf_Len(buf); } // Send the request IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, req, reqLen, o.Wait)); // If we got a response, check for error or return the stream info result. IFOK(s, _unmarshalStreamCreateResp(&si, &page, resp, errCode)); // If there was paging, we need to collect subjects until we get them all. if ((s == NATS_OK) && doPage) { if (si->State.Subjects != NULL) { int sc = si->State.Subjects->Count; offset += (int64_t) sc; if (subjects == NULL) subjects = si->State.Subjects; else { jsStreamStateSubject *list = subjects->List; int prev = subjects->Count; list = (jsStreamStateSubject*) NATS_REALLOC(list, (prev + sc) * sizeof(jsStreamStateSubject)); if (list == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); else { int i; for (i=0; iState.Subjects->List[i].Subject; list[prev+i].Msgs = si->State.Subjects->List[i].Msgs; } NATS_FREE(si->State.Subjects->List); NATS_FREE(si->State.Subjects); subjects->List = list; subjects->Count += sc; } } if (s == NATS_OK) si->State.Subjects = NULL; } done = offset >= page.total; if (!done) { jsStreamInfo_Destroy(si); si = NULL; // Reset the request buffer, we may be able to reuse. natsBuf_Reset(buf); } } natsMsg_Destroy(resp); resp = NULL; } while ((s == NATS_OK) && doPage && !done); natsBuf_Destroy(buf); NATS_FREE(subj); if (s == NATS_OK) { if (doPage && (subjects != NULL)) si->State.Subjects = subjects; *new_si = si; } else { if (subjects != NULL) _destroyStreamStateSubjects(subjects); if (s == NATS_NOT_FOUND) { nats_clearLastError(); return s; } } return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalSuccessResp(bool *success, natsMsg *resp, jsErrCode *errCode) { nats_JSON *json = NULL; jsApiResponse ar; natsStatus s; *success = false; s = js_unmarshalResponse(&ar, &json, resp); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); if (js_apiResponseIsErr(&ar)) { if (errCode != NULL) *errCode = (int) ar.Error.ErrCode; // For stream or consumer not found, return NATS_NOT_FOUND instead of NATS_ERR. if ((ar.Error.ErrCode == JSStreamNotFoundErr) || (ar.Error.ErrCode == JSConsumerNotFoundErr)) { s = NATS_NOT_FOUND; } else s = nats_setError(NATS_ERR, "%s", ar.Error.Description); } else { s = nats_JSONGetBool(json, "success", success); } js_freeApiRespContent(&ar); nats_JSONDestroy(json); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _marshalPurgeRequest(natsBuffer **new_buf, struct jsOptionsStreamPurge *opts) { natsStatus s = NATS_OK; natsBuffer *buf = NULL; bool comma = false; if (nats_IsStringEmpty(opts->Subject) && (opts->Sequence == 0) && (opts->Keep == 0)) return NATS_OK; if ((opts->Sequence > 0) && (opts->Keep > 0)) return nats_setError(NATS_INVALID_ARG, "Sequence (%" PRIu64 ") and Keep (%" PRIu64 " are mutually exclusive", opts->Sequence, opts->Keep); s = natsBuf_Create(&buf, 128); IFOK(s, natsBuf_AppendByte(buf, '{')); if ((s == NATS_OK) && !nats_IsStringEmpty(opts->Subject)) { s = natsBuf_Append(buf, "\"filter\":\"", -1); IFOK(s, natsBuf_Append(buf, opts->Subject, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); comma = true; } if ((s == NATS_OK) && (opts->Sequence > 0)) s = nats_marshalULong(buf, comma, "seq", opts->Sequence); if ((s == NATS_OK) && (opts->Keep > 0)) s = nats_marshalULong(buf, comma, "keep", opts->Keep); IFOK(s, natsBuf_AppendByte(buf, '}')); if (s == NATS_OK) *new_buf = buf; else natsBuf_Destroy(buf); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _purgeOrDelete(bool purge, jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode) { natsStatus s = NATS_OK; char *subj = NULL; natsMsg *resp = NULL; natsConnection *nc = NULL; const char *apiT = (purge ? jsApiStreamPurgeT : jsApiStreamDeleteT); bool freePfx = false; natsBuffer *buf = NULL; const void *data = NULL; int dlen = 0; bool success = false; jsOptions o; if (errCode != NULL) *errCode = 0; if (js == NULL) return nats_setDefaultError(NATS_INVALID_ARG); s = _checkStreamName(stream); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); s = js_setOpts(&nc, &freePfx, js, opts, &o); if (s == NATS_OK) { if (nats_asprintf(&subj, apiT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream) < 0) s = nats_setDefaultError(NATS_NO_MEMORY); if (freePfx) NATS_FREE((char*) o.Prefix); } if ((s == NATS_OK) && purge) { s = _marshalPurgeRequest(&buf, &(o.Stream.Purge)); if ((s == NATS_OK) && (buf != NULL)) { data = (const void*) natsBuf_Data(buf); dlen = natsBuf_Len(buf); } } // Send the request IFOK_JSR(s, natsConnection_Request(&resp, js->nc, subj, data, dlen, o.Wait)); IFOK(s, _unmarshalSuccessResp(&success, resp, errCode)); if ((s == NATS_OK) && !success) s = nats_setError(NATS_ERR, "failed to %s stream '%s'", (purge ? "purge" : "delete"), stream); natsBuf_Destroy(buf); natsMsg_Destroy(resp); NATS_FREE(subj); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_PurgeStream(jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode) { natsStatus s = _purgeOrDelete(true, js, stream, opts, errCode); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_DeleteStream(jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode) { natsStatus s = _purgeOrDelete(false, js, stream, opts, errCode); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _decodeBytesLen(nats_JSON *json, const char *field, const char **str, int *strLen, int *decodedLen) { natsStatus s = NATS_OK; s = nats_JSONGetStrPtr(json, field, str); if ((s == NATS_OK) && (*str != NULL)) s = nats_Base64_DecodeLen(*str, strLen, decodedLen); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalStoredMsg(nats_JSON *json, natsMsg **new_msg) { natsStatus s; natsMsg *msg = NULL; const char *subject= NULL; const char *hdrs = NULL; const char *data = NULL; int hdrsl = 0; int dhdrsl = 0; int datal = 0; int ddatal = 0; s = nats_JSONGetStrPtr(json, "subject", &subject); IFOK(s, _decodeBytesLen(json, "hdrs", &hdrs, &hdrsl, &dhdrsl)); IFOK(s, _decodeBytesLen(json, "data", &data, &datal, &ddatal)); if ((s == NATS_OK) && (subject != NULL)) { s = natsMsg_create(&msg, subject, (int) strlen(subject), NULL, 0, NULL, dhdrsl+ddatal, dhdrsl); if (s == NATS_OK) { if ((hdrs != NULL) && (dhdrsl > 0)) nats_Base64_DecodeInPlace(hdrs, hdrsl, (unsigned char*) msg->hdr); if ((data != NULL) && (ddatal > 0)) nats_Base64_DecodeInPlace(data, datal, (unsigned char*) msg->data); } IFOK(s, nats_JSONGetULong(json, "seq", &(msg->seq))); IFOK(s, nats_JSONGetTime(json, "time", &(msg->time))); } if (s == NATS_OK) *new_msg = msg; return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalGetMsgResp(natsMsg **msg, natsMsg *resp, jsErrCode *errCode) { nats_JSON *json = NULL; jsApiResponse ar; natsStatus s; s = js_unmarshalResponse(&ar, &json, resp); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); if (js_apiResponseIsErr(&ar)) { if (errCode != NULL) *errCode = (int) ar.Error.ErrCode; // If the error code is JSNoMessageFoundErr then pick NATS_NOT_FOUND. if (ar.Error.ErrCode == JSNoMessageFoundErr) s = NATS_NOT_FOUND; else s = nats_setError(NATS_ERR, "%s", ar.Error.Description); } else { nats_JSON *mjson = NULL; s = nats_JSONGetObject(json, "message", &mjson); if ((s == NATS_OK) && (mjson == NULL)) s = nats_setError(NATS_NOT_FOUND, "%s", "message content not found"); else s = _unmarshalStoredMsg(mjson, msg); } js_freeApiRespContent(&ar); nats_JSONDestroy(json); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _getMsg(natsMsg **msg, jsCtx *js, const char *stream, uint64_t seq, const char *subject, jsOptions *opts, jsErrCode *errCode) { natsStatus s = NATS_OK; char *subj = NULL; natsMsg *resp = NULL; natsConnection *nc = NULL; bool freePfx = false; jsOptions o; char buffer[64]; natsBuffer buf; if ((msg == NULL) || (js == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); if (nats_IsStringEmpty(stream)) return nats_setError(NATS_INVALID_ARG, "%s", jsErrStreamNameRequired); s = js_setOpts(&nc, &freePfx, js, opts, &o); if (s == NATS_OK) { if (nats_asprintf(&subj, jsApiMsgGetT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream) < 0) s = nats_setDefaultError(NATS_NO_MEMORY); if (freePfx) NATS_FREE((char*) o.Prefix); } IFOK(s, natsBuf_InitWithBackend(&buf, buffer, 0, sizeof(buffer))); IFOK(s, natsBuf_AppendByte(&buf, '{')); if ((s == NATS_OK) && (seq > 0)) { s = nats_marshalULong(&buf, false, "seq", seq); } else { IFOK(s, natsBuf_Append(&buf, "\"last_by_subj\":\"", -1)); IFOK(s, natsBuf_Append(&buf, subject, -1)); IFOK(s, natsBuf_AppendByte(&buf, '"')); } IFOK(s, natsBuf_AppendByte(&buf, '}')); // Send the request IFOK_JSR(s, natsConnection_Request(&resp, js->nc, subj, natsBuf_Data(&buf), natsBuf_Len(&buf), o.Wait)); // Unmarshal response IFOK(s, _unmarshalGetMsgResp(msg, resp, errCode)); natsBuf_Cleanup(&buf); natsMsg_Destroy(resp); NATS_FREE(subj); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_GetMsg(natsMsg **msg, jsCtx *js, const char *stream, uint64_t seq, jsOptions *opts, jsErrCode *errCode) { natsStatus s; if (errCode != NULL) *errCode = 0; if (seq < 1) return nats_setDefaultError(NATS_INVALID_ARG); s = _getMsg(msg, js, stream, seq, NULL, opts, errCode); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_GetLastMsg(natsMsg **msg, jsCtx *js, const char *stream, const char *subject, jsOptions *opts, jsErrCode *errCode) { natsStatus s; if (errCode != NULL) *errCode = 0; if (nats_IsStringEmpty(subject)) return nats_setDefaultError(NATS_INVALID_ARG); s = _getMsg(msg, js, stream, 0, subject, opts, errCode); return NATS_UPDATE_ERR_STACK(s); } natsStatus jsDirectGetMsgOptions_Init(jsDirectGetMsgOptions *opts) { if (opts == NULL) return nats_setDefaultError(NATS_INVALID_ARG); memset(opts, 0, sizeof(jsDirectGetMsgOptions)); return NATS_OK; } natsStatus js_directGetMsgToJSMsg(const char *stream, natsMsg *msg) { natsStatus s; const char *val = NULL; int64_t seq = 0; int64_t tm = 0; if ((msg->hdrLen == 0) && (msg->headers == NULL)) return nats_setError(NATS_ERR, "%s", "direct get message response should have headers"); // If the server returns an error (not found/bad request), we would receive // an empty body message with the Status header. Check for that. if ((natsMsg_GetDataLength(msg) == 0) && (natsMsgHeader_Get(msg, STATUS_HDR, &val) == NATS_OK)) { if (strcmp(val, NOT_FOUND_STATUS) == 0) return nats_setDefaultError(NATS_NOT_FOUND); else { natsMsgHeader_Get(msg, DESCRIPTION_HDR, &val); return nats_setError(NATS_ERR, "error getting message: %s", val); } } s = natsMsgHeader_Get(msg, JSStream, &val); if ((s != NATS_OK) || nats_IsStringEmpty(val)) return nats_setError(NATS_ERR, "missing stream name '%s'", val); val = NULL; s = natsMsgHeader_Get(msg, JSSequence, &val); if ((s != NATS_OK) || ((seq = nats_ParseInt64(val, (int) strlen(val))) == -1)) return nats_setError(NATS_ERR, "missing or invalid sequence '%s'", val); val = NULL; s = natsMsgHeader_Get(msg, JSTimeStamp, &val); if ((s == NATS_OK) && !nats_IsStringEmpty(val)) s = nats_parseTime((char*) val, &tm); if ((s != NATS_OK) || (tm == 0)) return nats_setError(NATS_ERR, "missing or invalid timestamp '%s'", val); val = NULL; s = natsMsgHeader_Get(msg, JSSubject, &val); if ((s != NATS_OK) || nats_IsStringEmpty(val)) return nats_setError(NATS_ERR, "missing or invalid subject '%s'", val); // Will point the message subject to the JSSubject header value. // This will remain in the message memory allocated block, even // if later the user changes the JSSubject header. msg->subject = val; msg->seq = seq; msg->time = tm; return NATS_OK; } natsStatus js_DirectGetMsg(natsMsg **msg, jsCtx *js, const char *stream, jsOptions *opts, jsDirectGetMsgOptions *dgOpts) { natsStatus s = NATS_OK; char *subj = NULL; natsMsg *resp = NULL; natsConnection *nc = NULL; bool freePfx = false; bool comma = false; bool doLBS = false; jsOptions o; char buffer[64]; natsBuffer buf; if ((msg == NULL) || (js == NULL) || (dgOpts == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); if (nats_IsStringEmpty(stream)) return nats_setError(NATS_INVALID_ARG, "%s", jsErrStreamNameRequired); doLBS = !nats_IsStringEmpty(dgOpts->LastBySubject); s = js_setOpts(&nc, &freePfx, js, opts, &o); if (s == NATS_OK) { if (doLBS) { if (nats_asprintf(&subj, jsApiDirectMsgGetLastBySubjectT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream, dgOpts->LastBySubject) < 0) s = nats_setDefaultError(NATS_NO_MEMORY); } else { if (nats_asprintf(&subj, jsApiDirectMsgGetT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream) < 0) s = nats_setDefaultError(NATS_NO_MEMORY); } if (freePfx) NATS_FREE((char*) o.Prefix); } if ((s == NATS_OK) && doLBS) { IFOK(s, natsConnection_Request(&resp, js->nc, subj, NULL, 0, o.Wait)); } else if (s == NATS_OK) { IFOK(s, natsBuf_InitWithBackend(&buf, buffer, 0, sizeof(buffer))); IFOK(s, natsBuf_AppendByte(&buf, '{')); if ((s == NATS_OK) && (dgOpts->Sequence > 0)) { comma = true; s = nats_marshalULong(&buf, false, "seq", dgOpts->Sequence); } if ((s == NATS_OK) && !nats_IsStringEmpty(dgOpts->NextBySubject)) { if (comma) s = natsBuf_AppendByte(&buf, ','); comma = true; IFOK(s, natsBuf_Append(&buf, "\"next_by_subj\":\"", -1)); IFOK(s, natsBuf_Append(&buf, dgOpts->NextBySubject, -1)); IFOK(s, natsBuf_AppendByte(&buf, '"')); } IFOK(s, natsBuf_AppendByte(&buf, '}')); // Send the request IFOK(s, natsConnection_Request(&resp, js->nc, subj, natsBuf_Data(&buf), natsBuf_Len(&buf), o.Wait)); natsBuf_Cleanup(&buf); } // Convert the response to a JS message returned to the user. IFOK(s, js_directGetMsgToJSMsg(stream, resp)); NATS_FREE(subj); if (s == NATS_OK) *msg = resp; else natsMsg_Destroy(resp); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _deleteMsg(jsCtx *js, bool noErase, const char *stream, uint64_t seq, jsOptions *opts, jsErrCode *errCode) { natsStatus s = NATS_OK; char *subj = NULL; natsMsg *resp = NULL; natsConnection *nc = NULL; bool freePfx = false; bool success = false; jsOptions o; char buffer[64]; natsBuffer buf; if (errCode != NULL) *errCode = 0; if (js == NULL) return nats_setDefaultError(NATS_INVALID_ARG); if (nats_IsStringEmpty(stream)) return nats_setError(NATS_INVALID_ARG, "%s", jsErrStreamNameRequired); s = js_setOpts(&nc, &freePfx, js, opts, &o); if (s == NATS_OK) { if (nats_asprintf(&subj, jsApiMsgDeleteT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream) < 0) s = nats_setDefaultError(NATS_NO_MEMORY); if (freePfx) NATS_FREE((char*) o.Prefix); } IFOK(s, natsBuf_InitWithBackend(&buf, buffer, 0, sizeof(buffer))); IFOK(s, natsBuf_AppendByte(&buf, '{')); IFOK(s, nats_marshalULong(&buf, false, "seq", seq)); if ((s == NATS_OK) && noErase) s = natsBuf_Append(&buf, ",\"no_erase\":true", -1); IFOK(s, natsBuf_AppendByte(&buf, '}')); // Send the request IFOK_JSR(s, natsConnection_Request(&resp, js->nc, subj, natsBuf_Data(&buf), natsBuf_Len(&buf), o.Wait)); IFOK(s, _unmarshalSuccessResp(&success, resp, errCode)); if ((s == NATS_OK) && !success) s = nats_setError(NATS_ERR, "failed to delete message %" PRIu64, seq); natsBuf_Cleanup(&buf); natsMsg_Destroy(resp); NATS_FREE(subj); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_DeleteMsg(jsCtx *js, const char *stream, uint64_t seq, jsOptions *opts, jsErrCode *errCode) { natsStatus s; s = _deleteMsg(js, true, stream, seq, opts, errCode); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_EraseMsg(jsCtx *js, const char *stream, uint64_t seq, jsOptions *opts, jsErrCode *errCode) { natsStatus s; s = _deleteMsg(js, false, stream, seq, opts, errCode); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalStreamInfoListResp(natsStrHash *m, apiPaged *page, natsMsg *resp, jsErrCode *errCode) { nats_JSON *json = NULL; jsApiResponse ar; natsStatus s; s = js_unmarshalResponse(&ar, &json, resp); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); if (js_apiResponseIsErr(&ar)) { if (errCode != NULL) *errCode = (int) ar.Error.ErrCode; // If the error code is JSStreamNotFoundErr then pick NATS_NOT_FOUND. if (ar.Error.ErrCode == JSStreamNotFoundErr) s = NATS_NOT_FOUND; else s = nats_setError(NATS_ERR, "%s", ar.Error.Description); } else { nats_JSON **streams = NULL; int streamsLen = 0; IFOK(s, nats_JSONGetLong(json, "total", &page->total)); IFOK(s, nats_JSONGetLong(json, "offset", &page->offset)); IFOK(s, nats_JSONGetLong(json, "limit", &page->limit)); IFOK(s, nats_JSONGetArrayObject(json, "streams", &streams, &streamsLen)); if ((s == NATS_OK) && (streams != NULL)) { int i; for (i=0; (s == NATS_OK) && (iConfig == NULL) || nats_IsStringEmpty(si->Config->Name))) s = nats_setError(NATS_ERR, "%s", "stream name missing from configuration"); IFOK(s, natsStrHash_SetEx(m, (char*) si->Config->Name, true, true, si, (void**) &osi)); if (osi != NULL) jsStreamInfo_Destroy(osi); } // Free the array of JSON objects that was allocated by nats_JSONGetArrayObject. NATS_FREE(streams); } } js_freeApiRespContent(&ar); nats_JSONDestroy(json); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_Streams(jsStreamInfoList **new_list, jsCtx *js, jsOptions *opts, jsErrCode *errCode) { natsStatus s = NATS_OK; natsBuffer *buf = NULL; char *subj = NULL; natsMsg *resp = NULL; natsConnection *nc = NULL; bool freePfx = false; bool done = false; int64_t offset = 0; int64_t start = 0; int64_t timeout = 0; natsStrHash *streams= NULL; jsStreamInfoList *list = NULL; jsOptions o; apiPaged page; if (errCode != NULL) *errCode = 0; if ((new_list == NULL) || (js == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); s = js_setOpts(&nc, &freePfx, js, opts, &o); if (s == NATS_OK) { if (nats_asprintf(&subj, jsApiStreamListT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix) < 0) s = nats_setDefaultError(NATS_NO_MEMORY); if (freePfx) NATS_FREE((char*) o.Prefix); } IFOK(s, natsBuf_Create(&buf, 64)); IFOK(s, natsStrHash_Create(&streams, 16)); if (s == NATS_OK) { memset(&page, 0, sizeof(apiPaged)); start = nats_Now(); } do { IFOK(s, natsBuf_AppendByte(buf, '{')); IFOK(s, nats_marshalLong(buf, false, "offset", offset)); if (!nats_IsStringEmpty(o.Stream.Info.SubjectsFilter)) { IFOK(s, natsBuf_Append(buf, ",\"subject\":\"", -1)); IFOK(s, natsBuf_Append(buf, o.Stream.Info.SubjectsFilter, -1)); IFOK(s, natsBuf_AppendByte(buf, '\"')); } IFOK(s, natsBuf_AppendByte(buf, '}')); timeout = o.Wait - (nats_Now() - start); if (timeout <= 0) s = NATS_TIMEOUT; // Send the request IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, natsBuf_Data(buf), natsBuf_Len(buf), timeout)); IFOK(s, _unmarshalStreamInfoListResp(streams, &page, resp, errCode)); if (s == NATS_OK) { offset += page.limit; done = offset >= page.total; if (!done) { // Reset the request buffer, we may be able to reuse. natsBuf_Reset(buf); } } natsMsg_Destroy(resp); resp = NULL; } while ((s == NATS_OK) && !done); natsBuf_Destroy(buf); NATS_FREE(subj); if (s == NATS_OK) { if (natsStrHash_Count(streams) == 0) { natsStrHash_Destroy(streams); return NATS_NOT_FOUND; } list = (jsStreamInfoList*) NATS_CALLOC(1, sizeof(jsStreamInfoList)); if (list == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); else { list->List = (jsStreamInfo**) NATS_CALLOC(natsStrHash_Count(streams), sizeof(jsStreamInfo*)); if (list->List == NULL) { NATS_FREE(list); list = NULL; s = nats_setDefaultError(NATS_NO_MEMORY); } else { natsStrHashIter iter; void *val = NULL; natsStrHashIter_Init(&iter, streams); while (natsStrHashIter_Next(&iter, NULL, (void**) &val)) { jsStreamInfo *si = (jsStreamInfo*) val; list->List[list->Count++] = si; natsStrHashIter_RemoveCurrent(&iter); } natsStrHashIter_Done(&iter); *new_list = list; } } } if (s != NATS_OK) { natsStrHashIter iter; void *val = NULL; natsStrHashIter_Init(&iter, streams); while (natsStrHashIter_Next(&iter, NULL, (void**) &val)) { jsStreamInfo *si = (jsStreamInfo*) val; natsStrHashIter_RemoveCurrent(&iter); jsStreamInfo_Destroy(si); } natsStrHashIter_Done(&iter); } natsStrHash_Destroy(streams); return NATS_UPDATE_ERR_STACK(s); } void jsStreamInfoList_Destroy(jsStreamInfoList *list) { int i; if (list == NULL) return; for (i=0; iCount; i++) jsStreamInfo_Destroy(list->List[i]); NATS_FREE(list->List); NATS_FREE(list); } static natsStatus _unmarshalNamesListResp(const char *fieldName, natsStrHash *m, apiPaged *page, natsMsg *resp, jsErrCode *errCode) { nats_JSON *json = NULL; jsApiResponse ar; natsStatus s; s = js_unmarshalResponse(&ar, &json, resp); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); if (js_apiResponseIsErr(&ar)) { if (errCode != NULL) *errCode = (int) ar.Error.ErrCode; // If the error code is JSStreamNotFoundErr then pick NATS_NOT_FOUND. if (ar.Error.ErrCode == JSStreamNotFoundErr) s = NATS_NOT_FOUND; else s = nats_setError(NATS_ERR, "%s", ar.Error.Description); } else { char **names = NULL; int namesLen = 0; IFOK(s, nats_JSONGetLong(json, "total", &page->total)); IFOK(s, nats_JSONGetLong(json, "offset", &page->offset)); IFOK(s, nats_JSONGetLong(json, "limit", &page->limit)); IFOK(s, nats_JSONGetArrayStr(json, fieldName, &names, &namesLen)); if ((s == NATS_OK) && (names != NULL)) { int i; for (i=0; (s == NATS_OK) && (i= page.total; if (!done) { // Reset the request buffer, we may be able to reuse. natsBuf_Reset(buf); } } natsMsg_Destroy(resp); resp = NULL; } while ((s == NATS_OK) && !done); natsBuf_Destroy(buf); NATS_FREE(subj); if (s == NATS_OK) { if (natsStrHash_Count(names) == 0) { natsStrHash_Destroy(names); return NATS_NOT_FOUND; } list = (jsStreamNamesList*) NATS_CALLOC(1, sizeof(jsStreamNamesList)); if (list == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); else { list->List = (char**) NATS_CALLOC(natsStrHash_Count(names), sizeof(char*)); if (list->List == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); else { natsStrHashIter iter; char *sname = NULL; natsStrHashIter_Init(&iter, names); while ((s == NATS_OK) && natsStrHashIter_Next(&iter, &sname, NULL)) { char *copyName = NULL; DUP_STRING(s, copyName, sname); if (s == NATS_OK) { list->List[list->Count++] = copyName; natsStrHashIter_RemoveCurrent(&iter); } } natsStrHashIter_Done(&iter); } if (s == NATS_OK) *new_list = list; else jsStreamNamesList_Destroy(list); } } natsStrHash_Destroy(names); return NATS_UPDATE_ERR_STACK(s); } void jsStreamNamesList_Destroy(jsStreamNamesList *list) { int i; if (list == NULL) return; for (i=0; iCount; i++) NATS_FREE(list->List[i]); NATS_FREE(list->List); NATS_FREE(list); } // // Account related functions // static natsStatus _unmarshalAccLimits(nats_JSON *json, jsAccountLimits *limits) { natsStatus s; nats_JSON *obj = NULL; s = nats_JSONGetObject(json, "limits", &obj); if (obj == NULL) return NATS_UPDATE_ERR_STACK(s); IFOK(s, nats_JSONGetLong(obj, "max_memory", &(limits->MaxMemory))); IFOK(s, nats_JSONGetLong(obj, "max_storage", &(limits->MaxStore))); IFOK(s, nats_JSONGetLong(obj, "max_streams", &(limits->MaxStreams))); IFOK(s, nats_JSONGetLong(obj, "max_consumers", &(limits->MaxConsumers))); IFOK(s, nats_JSONGetLong(obj, "max_ack_pending", &(limits->MaxAckPending))); IFOK(s, nats_JSONGetLong(obj, "memory_max_stream_bytes", &(limits->MemoryMaxStreamBytes))); IFOK(s, nats_JSONGetLong(obj, "storage_max_stream_bytes", &(limits->StoreMaxStreamBytes))); IFOK(s, nats_JSONGetBool(obj, "max_bytes_required", &(limits->MaxBytesRequired))); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _fillTier(void *userInfo, const char *subject, nats_JSONField *f) { jsAccountInfo *ai = (jsAccountInfo*) userInfo; natsStatus s = NATS_OK; jsTier *t = NULL; nats_JSON *json = f->value.vobj; t = (jsTier*) NATS_CALLOC(1, sizeof(jsTier)); if (t == NULL) return nats_setDefaultError(NATS_NO_MEMORY); ai->Tiers[ai->TiersLen++] = t; DUP_STRING(s, t->Name, subject); IFOK(s, nats_JSONGetULong(json, "memory", &(t->Memory))); IFOK(s, nats_JSONGetULong(json, "storage", &(t->Store))); IFOK(s, nats_JSONGetLong(json, "streams", &(t->Streams))); IFOK(s, nats_JSONGetLong(json, "consumers", &(t->Consumers))); IFOK(s, _unmarshalAccLimits(json, &(t->Limits))); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalAccTiers(nats_JSON *json, jsAccountInfo *ai) { nats_JSON *obj = NULL; natsStatus s = NATS_OK; int n; s = nats_JSONGetObject(json, "tier", &obj); if (obj == NULL) return NATS_UPDATE_ERR_STACK(s); n = natsStrHash_Count(obj->fields); if (n == 0) return NATS_OK; ai->Tiers = (jsTier**) NATS_CALLOC(n, sizeof(jsTier*)); if (ai->Tiers == NULL) return nats_setDefaultError(NATS_NO_MEMORY); s = nats_JSONRange(obj, TYPE_OBJECT, 0, _fillTier, (void*) ai); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_unmarshalAccountInfo(nats_JSON *json, jsAccountInfo **new_ai) { natsStatus s; nats_JSON *obj = NULL; jsAccountInfo *ai = NULL; ai = (jsAccountInfo*) NATS_CALLOC(1, sizeof(jsAccountInfo)); if (ai == NULL) return nats_setDefaultError(NATS_NO_MEMORY); s = nats_JSONGetULong(json, "memory", &(ai->Memory)); IFOK(s, nats_JSONGetULong(json, "storage", &(ai->Store))); IFOK(s, nats_JSONGetLong(json, "streams", &(ai->Streams))); IFOK(s, nats_JSONGetLong(json, "consumers", &(ai->Consumers))); IFOK(s, nats_JSONGetStr(json, "domain", &(ai->Domain))); IFOK(s, nats_JSONGetObject(json, "api", &obj)); if ((s == NATS_OK) && (obj != NULL)) { IFOK(s, nats_JSONGetULong(obj, "total", &(ai->API.Total))); IFOK(s, nats_JSONGetULong(obj, "errors", &(ai->API.Errors))); obj = NULL; } IFOK(s, _unmarshalAccLimits(json, &(ai->Limits))); IFOK(s, _unmarshalAccTiers(json, ai)); if (s == NATS_OK) *new_ai = ai; else jsAccountInfo_Destroy(ai); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalAccountInfoResp(jsAccountInfo **new_ai, natsMsg *resp, jsErrCode *errCode) { nats_JSON *json = NULL; jsApiResponse ar; natsStatus s; s = js_unmarshalResponse(&ar, &json, resp); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); if (js_apiResponseIsErr(&ar)) { if (errCode != NULL) *errCode = (int) ar.Error.ErrCode; s = nats_setError(NATS_ERR, "%s", ar.Error.Description); } else s = js_unmarshalAccountInfo(json, new_ai); js_freeApiRespContent(&ar); nats_JSONDestroy(json); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_GetAccountInfo(jsAccountInfo **new_ai, jsCtx *js, jsOptions *opts, jsErrCode *errCode) { natsMsg *resp = NULL; char *subj = NULL; natsConnection *nc = NULL; natsStatus s = NATS_OK; bool freePfx = false; jsOptions o; if (errCode != NULL) *errCode = 0; if (new_ai == NULL || js == NULL) return nats_setDefaultError(NATS_INVALID_ARG); s = js_setOpts(&nc, &freePfx, js, opts, &o); if (s == NATS_OK) { if (nats_asprintf(&subj, jsApiAccountInfo, js_lenWithoutTrailingDot(o.Prefix), o.Prefix) < 0) s = nats_setDefaultError(NATS_NO_MEMORY); if (freePfx) NATS_FREE((char*) o.Prefix); } // Send the request IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, NULL, 0, o.Wait)); // If we get a response, unmarshal the response IFOK(s, _unmarshalAccountInfoResp(new_ai, resp, errCode)); // Common cleanup that is done regardless of success or failure. NATS_FREE(subj); natsMsg_Destroy(resp); return NATS_UPDATE_ERR_STACK(s); } void jsAccountInfo_Destroy(jsAccountInfo *ai) { if (ai == NULL) return; if (ai->Tiers != NULL) { int i; for (i=0; iTiersLen; i++) { jsTier *t = ai->Tiers[i]; NATS_FREE((char*) t->Name); NATS_FREE(t); } NATS_FREE(ai->Tiers); } NATS_FREE(ai->Domain); NATS_FREE(ai); } natsStatus jsPlacement_Init(jsPlacement *placement) { if (placement == NULL) return nats_setDefaultError(NATS_INVALID_ARG); memset(placement, 0, sizeof(jsPlacement)); return NATS_OK; } natsStatus jsStreamSource_Init(jsStreamSource *source) { if (source == NULL) return nats_setDefaultError(NATS_INVALID_ARG); memset(source, 0, sizeof(jsStreamSource)); return NATS_OK; } natsStatus jsExternalStream_Init(jsExternalStream *external) { if (external == NULL) return nats_setDefaultError(NATS_INVALID_ARG); memset(external, 0, sizeof(jsExternalStream)); return NATS_OK; } natsStatus jsRePublish_Init(jsRePublish *rp) { if (rp == NULL) return nats_setDefaultError(NATS_INVALID_ARG); memset(rp, 0, sizeof(jsRePublish)); return NATS_OK; } // // Consumer related functions // static natsStatus _marshalDeliverPolicy(natsBuffer *buf, jsDeliverPolicy p) { natsStatus s; const char *dp = NULL; s = natsBuf_Append(buf, "\"deliver_policy\":\"", -1); switch (p) { case js_DeliverAll: dp = jsDeliverAllStr; break; case js_DeliverLast: dp = jsDeliverLastStr; break; case js_DeliverNew: dp = jsDeliverNewStr; break; case js_DeliverByStartSequence: dp = jsDeliverBySeqStr; break; case js_DeliverByStartTime: dp = jsDeliverByTimeStr; break; case js_DeliverLastPerSubject: dp = jsDeliverLastPerSubjectStr;break; default: return nats_setError(NATS_INVALID_ARG, "invalid deliver policy %d", (int) p); } IFOK(s, natsBuf_Append(buf, dp, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _marshalAckPolicy(natsBuffer *buf, jsAckPolicy p) { natsStatus s; const char *ap; s = natsBuf_Append(buf, ",\"ack_policy\":\"", -1); switch (p) { case js_AckNone: ap = jsAckNoneStr; break; case js_AckAll: ap = jsAckAllStr; break; case js_AckExplicit: ap = jsAckExplictStr; break; default: return nats_setError(NATS_INVALID_ARG, "invalid ack policy %d", (int)p); } IFOK(s, natsBuf_Append(buf, ap, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _marshalReplayPolicy(natsBuffer *buf, jsReplayPolicy p) { natsStatus s; const char *rp = NULL; s = natsBuf_Append(buf, ",\"replay_policy\":\"", -1); switch (p) { case js_ReplayOriginal: rp = jsReplayOriginalStr; break; case js_ReplayInstant: rp = jsReplayInstantStr; break; default: return nats_setError(NATS_INVALID_ARG, "invalid replay policy %d", (int)p); } IFOK(s, natsBuf_Append(buf, rp, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _marshalConsumerCreateReq(natsBuffer **new_buf, const char *stream, jsConsumerConfig *cfg) { natsStatus s = NATS_OK; natsBuffer *buf = NULL; // If not set, set some defaults if ((int) cfg->DeliverPolicy < 0) cfg->DeliverPolicy = js_DeliverAll; if ((int) cfg->AckPolicy < 0) cfg->AckPolicy = js_AckExplicit; if ((int) cfg->ReplayPolicy < 0) cfg->ReplayPolicy = js_ReplayInstant; s = natsBuf_Create(&buf, 256); IFOK(s, natsBuf_Append(buf, "{\"stream_name\":\"", -1)); IFOK(s, natsBuf_Append(buf, stream, -1)); IFOK(s, natsBuf_Append(buf, "\",\"config\":{", -1)); // Marshal something that is always present first, so that the optionals // will always start with a "," and we know that there will be a field before that. IFOK(s, _marshalDeliverPolicy(buf, cfg->DeliverPolicy)); if ((s == NATS_OK) && !nats_IsStringEmpty(cfg->Name)) { s = natsBuf_Append(buf, ",\"name\":\"", -1); IFOK(s, natsBuf_Append(buf, cfg->Name, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); } if ((s == NATS_OK) && (!nats_IsStringEmpty(cfg->Description))) { s = natsBuf_Append(buf, ",\"description\":\"", -1); IFOK(s, natsBuf_Append(buf, cfg->Description, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); } if ((s == NATS_OK) && (!nats_IsStringEmpty(cfg->Durable))) { s = natsBuf_Append(buf, ",\"durable_name\":\"", -1); IFOK(s, natsBuf_Append(buf, cfg->Durable, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); } if ((s == NATS_OK) && (!nats_IsStringEmpty(cfg->DeliverSubject))) { s = natsBuf_Append(buf, ",\"deliver_subject\":\"", -1); IFOK(s, natsBuf_Append(buf, cfg->DeliverSubject, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); } if ((s == NATS_OK) && (!nats_IsStringEmpty(cfg->DeliverGroup))) { s = natsBuf_Append(buf, ",\"deliver_group\":\"", -1); IFOK(s, natsBuf_Append(buf, cfg->DeliverGroup, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); } if ((s == NATS_OK) && (cfg->OptStartSeq > 0)) s = nats_marshalLong(buf, true, "opt_start_seq", cfg->OptStartSeq); if ((s == NATS_OK) && (cfg->OptStartTime > 0)) s = _marshalTimeUTC(buf, "opt_start_time", cfg->OptStartTime); IFOK(s, _marshalAckPolicy(buf, cfg->AckPolicy)); if ((s == NATS_OK) && (cfg->AckWait > 0)) s = nats_marshalLong(buf, true, "ack_wait", cfg->AckWait); if ((s == NATS_OK) && (cfg->MaxDeliver > 0)) s = nats_marshalLong(buf, true, "max_deliver", cfg->MaxDeliver); if ((s == NATS_OK) && !nats_IsStringEmpty(cfg->FilterSubject)) { s = natsBuf_Append(buf, ",\"filter_subject\":\"", -1); IFOK(s, natsBuf_Append(buf, cfg->FilterSubject, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); } IFOK(s, _marshalReplayPolicy(buf, cfg->ReplayPolicy)) if ((s == NATS_OK) && (cfg->RateLimit > 0)) s = nats_marshalULong(buf, true, "rate_limit_bps", cfg->RateLimit); if ((s == NATS_OK) && !nats_IsStringEmpty(cfg->SampleFrequency)) { s = natsBuf_Append(buf, ",\"sample_freq\":\"", -1); IFOK(s, natsBuf_Append(buf, cfg->SampleFrequency, -1)); IFOK(s, natsBuf_AppendByte(buf, '"')); } if ((s == NATS_OK) && (cfg->MaxWaiting > 0)) s = nats_marshalLong(buf, true, "max_waiting", cfg->MaxWaiting); if ((s == NATS_OK) && (cfg->MaxAckPending > 0)) s = nats_marshalLong(buf, true, "max_ack_pending", cfg->MaxAckPending); if ((s == NATS_OK) && cfg->FlowControl) s = natsBuf_Append(buf, ",\"flow_control\":true", -1); if ((s == NATS_OK) && (cfg->Heartbeat > 0)) s = nats_marshalLong(buf, true, "idle_heartbeat", cfg->Heartbeat); if ((s == NATS_OK) && cfg->HeadersOnly) s = natsBuf_Append(buf, ",\"headers_only\":true", -1); if ((s == NATS_OK) && (cfg->MaxRequestBatch > 0)) s = nats_marshalLong(buf, true, "max_batch", cfg->MaxRequestBatch); if ((s == NATS_OK) && (cfg->MaxRequestExpires > 0)) s = nats_marshalLong(buf, true, "max_expires", cfg->MaxRequestExpires); if ((s == NATS_OK) && (cfg->MaxRequestMaxBytes > 0)) s = nats_marshalLong(buf, true, "max_bytes", cfg->MaxRequestMaxBytes); if ((s == NATS_OK) && (cfg->InactiveThreshold > 0)) s = nats_marshalLong(buf, true, "inactive_threshold", cfg->InactiveThreshold); if ((s == NATS_OK) && (cfg->BackOff != NULL) && (cfg->BackOffLen > 0)) { char tmp[32]; int i; s = natsBuf_Append(buf, ",\"backoff\":[", -1); for (i=0; (s == NATS_OK) && (iBackOffLen); i++) { snprintf(tmp, sizeof(tmp), "%" PRId64, cfg->BackOff[i]); if (i > 0) s = natsBuf_AppendByte(buf, ','); IFOK(s, natsBuf_Append(buf, tmp, -1)); } IFOK(s, natsBuf_AppendByte(buf, ']')); } if ((s == NATS_OK) && (cfg->Replicas > 0)) s = nats_marshalLong(buf, true, "num_replicas", cfg->Replicas); if ((s == NATS_OK) && cfg->MemoryStorage) s = natsBuf_Append(buf, ",\"mem_storage\":true", -1); IFOK(s, natsBuf_Append(buf, "}}", -1)); if (s == NATS_OK) *new_buf = buf; else natsBuf_Destroy(buf); return NATS_UPDATE_ERR_STACK(s); } void js_destroyConsumerConfig(jsConsumerConfig *cc) { if (cc == NULL) return; NATS_FREE((char*) cc->Name); NATS_FREE((char*) cc->Durable); NATS_FREE((char*) cc->Description); NATS_FREE((char*) cc->DeliverSubject); NATS_FREE((char*) cc->DeliverGroup); NATS_FREE((char*) cc->FilterSubject); NATS_FREE((char*) cc->SampleFrequency); NATS_FREE(cc->BackOff); NATS_FREE(cc); } static natsStatus _unmarshalDeliverPolicy(nats_JSON *json, const char *fieldName, jsDeliverPolicy *dp) { natsStatus s = NATS_OK; char *str = NULL; s = nats_JSONGetStr(json, fieldName, &str); if (str == NULL) return NATS_UPDATE_ERR_STACK(s); if (strcmp(str, jsDeliverAllStr) == 0) *dp = js_DeliverAll; else if (strcmp(str, jsDeliverLastStr) == 0) *dp = js_DeliverLast; else if (strcmp(str, jsDeliverNewStr) == 0) *dp = js_DeliverNew; else if (strcmp(str, jsDeliverBySeqStr) == 0) *dp = js_DeliverByStartSequence; else if (strcmp(str, jsDeliverByTimeStr) == 0) *dp = js_DeliverByStartTime; else if (strcmp(str, jsDeliverLastPerSubjectStr) == 0) *dp = js_DeliverLastPerSubject; else s = nats_setError(NATS_ERR, "unable to unmarshal delivery policy '%s'", str); NATS_FREE(str); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalAckPolicy(nats_JSON *json, const char *fieldName, jsAckPolicy *ap) { natsStatus s = NATS_OK; char *str = NULL; s = nats_JSONGetStr(json, fieldName, &str); if (str == NULL) return NATS_UPDATE_ERR_STACK(s); if (strcmp(str, jsAckNoneStr) == 0) *ap = js_AckNone; else if (strcmp(str, jsAckAllStr) == 0) *ap = js_AckAll; else if (strcmp(str, jsAckExplictStr) == 0) *ap = js_AckExplicit; else s = nats_setError(NATS_ERR, "unable to unmarshal ack policy '%s'", str); NATS_FREE(str); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalReplayPolicy(nats_JSON *json, const char *fieldName, jsReplayPolicy *rp) { natsStatus s = NATS_OK; char *str = NULL; s = nats_JSONGetStr(json, fieldName, &str); if (str == NULL) return NATS_UPDATE_ERR_STACK(s); if (strcmp(str, jsReplayOriginalStr) == 0) *rp = js_ReplayOriginal; else if (strcmp(str, jsReplayInstantStr) == 0) *rp = js_ReplayInstant; else s = nats_setError(NATS_ERR, "unable to unmarshal replay policy '%s'", str); NATS_FREE(str); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalConsumerConfig(nats_JSON *json, const char *fieldName, jsConsumerConfig **new_cc) { natsStatus s = NATS_OK; jsConsumerConfig *cc = NULL; nats_JSON *cjson = NULL; cc = (jsConsumerConfig*) NATS_CALLOC(1, sizeof(jsConsumerConfig)); if (cc == NULL) return nats_setDefaultError(NATS_NO_MEMORY); s = nats_JSONGetObject(json, fieldName, &cjson); if ((s == NATS_OK) && (cjson != NULL)) { s = nats_JSONGetStr(cjson, "durable_name", (char**) &(cc->Durable)); IFOK(s, nats_JSONGetStr(cjson, "name", (char**) &(cc->Name))); IFOK(s, nats_JSONGetStr(cjson, "description", (char**) &(cc->Description))); IFOK(s, nats_JSONGetStr(cjson, "deliver_subject", (char**) &(cc->DeliverSubject))); IFOK(s, nats_JSONGetStr(cjson, "deliver_group", (char**) &(cc->DeliverGroup))); IFOK(s, _unmarshalDeliverPolicy(cjson, "deliver_policy", &(cc->DeliverPolicy))); IFOK(s, nats_JSONGetULong(cjson, "opt_start_seq", &(cc->OptStartSeq))); IFOK(s, nats_JSONGetTime(cjson, "opt_start_time", &(cc->OptStartTime))); IFOK(s, _unmarshalAckPolicy(cjson, "ack_policy", &(cc->AckPolicy))); IFOK(s, nats_JSONGetLong(cjson, "ack_wait", &(cc->AckWait))); IFOK(s, nats_JSONGetLong(cjson, "max_deliver", &(cc->MaxDeliver))); IFOK(s, nats_JSONGetStr(cjson, "filter_subject", (char**) &(cc->FilterSubject))); IFOK(s, _unmarshalReplayPolicy(cjson, "replay_policy", &(cc->ReplayPolicy))); IFOK(s, nats_JSONGetULong(cjson, "rate_limit_bps", &(cc->RateLimit))); IFOK(s, nats_JSONGetStr(cjson, "sample_freq", (char**) &(cc->SampleFrequency))); IFOK(s, nats_JSONGetLong(cjson, "max_waiting", &(cc->MaxWaiting))); IFOK(s, nats_JSONGetLong(cjson, "max_ack_pending", &(cc->MaxAckPending))); IFOK(s, nats_JSONGetBool(cjson, "flow_control", &(cc->FlowControl))); IFOK(s, nats_JSONGetLong(cjson, "idle_heartbeat", &(cc->Heartbeat))); IFOK(s, nats_JSONGetBool(cjson, "headers_only", &(cc->HeadersOnly))); IFOK(s, nats_JSONGetLong(cjson, "max_batch", &(cc->MaxRequestBatch))); IFOK(s, nats_JSONGetLong(cjson, "max_expires", &(cc->MaxRequestExpires))); IFOK(s, nats_JSONGetLong(cjson, "max_bytes", &(cc->MaxRequestMaxBytes))); IFOK(s, nats_JSONGetLong(cjson, "inactive_threshold", &(cc->InactiveThreshold))); IFOK(s, nats_JSONGetArrayLong(cjson, "backoff", &(cc->BackOff), &(cc->BackOffLen))); IFOK(s, nats_JSONGetLong(cjson, "num_replicas", &(cc->Replicas))); IFOK(s, nats_JSONGetBool(cjson, "mem_storage", &(cc->MemoryStorage))); } if (s == NATS_OK) *new_cc = cc; else js_destroyConsumerConfig(cc); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalSeqInfo(nats_JSON *json, const char *fieldName, jsSequenceInfo *si) { natsStatus s = NATS_OK; nats_JSON *sij = NULL; s = nats_JSONGetObject(json, fieldName, &sij); if ((s == NATS_OK) && (sij != NULL)) { IFOK(s, nats_JSONGetULong(sij, "consumer_seq", &(si->Consumer))); IFOK(s, nats_JSONGetULong(sij, "stream_seq", &(si->Stream))); IFOK(s, nats_JSONGetTime(sij, "last_active", &(si->Last))); } return NATS_UPDATE_ERR_STACK(s); } natsStatus js_unmarshalConsumerInfo(nats_JSON *json, jsConsumerInfo **new_ci) { natsStatus s = NATS_OK; jsConsumerInfo *ci = NULL; ci = (jsConsumerInfo*) NATS_CALLOC(1, sizeof(jsConsumerInfo)); if (ci == NULL) return nats_setDefaultError(NATS_NO_MEMORY); s = nats_JSONGetStr(json, "stream_name", &(ci->Stream)); IFOK(s, nats_JSONGetStr(json, "name", &(ci->Name))); IFOK(s, nats_JSONGetTime(json, "created", &(ci->Created))); IFOK(s, _unmarshalConsumerConfig(json, "config", &(ci->Config))); IFOK(s, _unmarshalSeqInfo(json, "delivered", &(ci->Delivered))); IFOK(s, _unmarshalSeqInfo(json, "ack_floor", &(ci->AckFloor))); IFOK(s, nats_JSONGetLong(json, "num_ack_pending", &(ci->NumAckPending))); IFOK(s, nats_JSONGetLong(json, "num_redelivered", &(ci->NumRedelivered))); IFOK(s, nats_JSONGetLong(json, "num_waiting", &(ci->NumWaiting))); IFOK(s, nats_JSONGetULong(json, "num_pending", &(ci->NumPending))); IFOK(s, _unmarshalClusterInfo(json, "cluster", &(ci->Cluster))); IFOK(s, nats_JSONGetBool(json, "push_bound", &(ci->PushBound))); if (s == NATS_OK) *new_ci = ci; else jsConsumerInfo_Destroy(ci); return NATS_UPDATE_ERR_STACK(s); } static natsStatus _unmarshalConsumerCreateOrGetResp(jsConsumerInfo **new_ci, natsMsg *resp, jsErrCode *errCode) { nats_JSON *json = NULL; jsApiResponse ar; natsStatus s; s = js_unmarshalResponse(&ar, &json, resp); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); if (js_apiResponseIsErr(&ar)) { if (errCode != NULL) *errCode = (int) ar.Error.ErrCode; if (ar.Error.ErrCode == JSConsumerNotFoundErr) s = NATS_NOT_FOUND; else s = nats_setError(NATS_ERR, "%s", ar.Error.Description); } else if (new_ci != NULL) { // At this point we need to unmarshal the consumer info itself. s = js_unmarshalConsumerInfo(json, new_ci); } js_freeApiRespContent(&ar); nats_JSONDestroy(json); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_AddConsumer(jsConsumerInfo **new_ci, jsCtx *js, const char *stream, jsConsumerConfig *cfg, jsOptions *opts, jsErrCode *errCode) { natsStatus s = NATS_OK; natsBuffer *buf = NULL; natsConnection *nc = NULL; char *subj = NULL; bool freePfx = false; natsMsg *resp = NULL; jsOptions o; if (errCode != NULL) *errCode = 0; if (js == NULL) return nats_setDefaultError(NATS_INVALID_ARG); if (cfg == NULL) return nats_setError(NATS_INVALID_ARG, "%s", jsErrConsumerConfigRequired); s = _checkStreamName(stream); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); if (!nats_IsStringEmpty(cfg->Name)) { if ((s = js_checkConsName(cfg->Name, false)) != NATS_OK) return NATS_UPDATE_ERR_STACK(s); } if (!nats_IsStringEmpty(cfg->Durable)) { if ((s = js_checkConsName(cfg->Durable, true)) != NATS_OK) return NATS_UPDATE_ERR_STACK(s); } s = js_setOpts(&nc, &freePfx, js, opts, &o); if (s == NATS_OK) { int res; // If there is a Name in the config, this takes precedence. if (!nats_IsStringEmpty(cfg->Name)) { // No subject filter, use . // otherwise, the filter subject goes at the end. if (nats_IsStringEmpty(cfg->FilterSubject)) res = nats_asprintf(&subj, jsApiConsumerCreateExT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream, cfg->Name); else res = nats_asprintf(&subj, jsApiConsumerCreateExWithFilterT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream, cfg->Name, cfg->FilterSubject); } else if (nats_IsStringEmpty(cfg->Durable)) res = nats_asprintf(&subj, jsApiConsumerCreateT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream); else res = nats_asprintf(&subj, jsApiDurableCreateT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream, cfg->Durable); if (res < 0) s = nats_setDefaultError(NATS_NO_MEMORY); if (freePfx) NATS_FREE((char*) o.Prefix); } IFOK(s, _marshalConsumerCreateReq(&buf, stream, cfg)); // Send the request IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, natsBuf_Data(buf), natsBuf_Len(buf), o.Wait)); // If we got a response, check for error or return the consumer info result. IFOK(s, _unmarshalConsumerCreateOrGetResp(new_ci, resp, errCode)); NATS_FREE(subj); natsMsg_Destroy(resp); natsBuf_Destroy(buf); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_UpdateConsumer(jsConsumerInfo **ci, jsCtx *js, const char *stream, jsConsumerConfig *cfg, jsOptions *opts, jsErrCode *errCode) { natsStatus s; if ((cfg != NULL) && nats_IsStringEmpty(cfg->Durable)) return nats_setError(NATS_INVALID_ARG, "%s", jsErrDurRequired); s = js_AddConsumer(ci, js, stream, cfg, opts, errCode); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_GetConsumerInfo(jsConsumerInfo **new_ci, jsCtx *js, const char *stream, const char *consumer, jsOptions *opts, jsErrCode *errCode) { natsStatus s = NATS_OK; char *subj = NULL; bool freePfx = false; natsConnection *nc = NULL; natsMsg *resp = NULL; jsOptions o; if (errCode != NULL) *errCode = 0; if ((js == NULL) || (new_ci == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); s = _checkStreamName(stream); IFOK(s, js_checkConsName(consumer, false)) if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); s = js_setOpts(&nc, &freePfx, js, opts, &o); if (s == NATS_OK) { if (nats_asprintf(&subj, jsApiConsumerInfoT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream, consumer) < 0 ) { s = nats_setDefaultError(NATS_NO_MEMORY); } if (freePfx) NATS_FREE((char*) o.Prefix); } // Send the request IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, NULL, 0, o.Wait)); // If we got a response, check for error or return the consumer info result. IFOK(s, _unmarshalConsumerCreateOrGetResp(new_ci, resp, errCode)); NATS_FREE(subj); natsMsg_Destroy(resp); if (s == NATS_NOT_FOUND) { nats_clearLastError(); return s; } return NATS_UPDATE_ERR_STACK(s); } natsStatus js_DeleteConsumer(jsCtx *js, const char *stream, const char *consumer, jsOptions *opts, jsErrCode *errCode) { natsStatus s = NATS_OK; char *subj = NULL; bool freePfx = false; natsConnection *nc = NULL; natsMsg *resp = NULL; bool success = false; jsOptions o; if (errCode != NULL) *errCode = 0; if (js == NULL) return nats_setDefaultError(NATS_INVALID_ARG); s = _checkStreamName(stream); IFOK(s, js_checkConsName(consumer, false)) if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); s = js_setOpts(&nc, &freePfx, js, opts, &o); if (s == NATS_OK) { if (nats_asprintf(&subj, jsApiConsumerDeleteT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream, consumer) < 0 ) { s = nats_setDefaultError(NATS_NO_MEMORY); } if (freePfx) NATS_FREE((char*) o.Prefix); } // Send the request IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, NULL, 0, o.Wait)); // If we got a response, check for error and success result. IFOK(s, _unmarshalSuccessResp(&success, resp, errCode)); if ((s == NATS_OK) && !success) s = nats_setError(s, "failed to delete consumer '%s'", consumer); NATS_FREE(subj); natsMsg_Destroy(resp); return NATS_UPDATE_ERR_STACK(s); } natsStatus jsConsumerConfig_Init(jsConsumerConfig *cc) { if (cc == NULL) return nats_setDefaultError(NATS_INVALID_ARG); memset(cc, 0, sizeof(jsConsumerConfig)); cc->AckPolicy = -1; cc->DeliverPolicy = -1; cc->ReplayPolicy = -1; return NATS_OK; } void jsConsumerInfo_Destroy(jsConsumerInfo *ci) { if (ci == NULL) return; NATS_FREE(ci->Stream); NATS_FREE(ci->Name); js_destroyConsumerConfig(ci->Config); _destroyClusterInfo(ci->Cluster); NATS_FREE(ci); } static natsStatus _unmarshalConsumerInfoListResp(natsStrHash *m, apiPaged *page, natsMsg *resp, jsErrCode *errCode) { nats_JSON *json = NULL; jsApiResponse ar; natsStatus s; s = js_unmarshalResponse(&ar, &json, resp); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); if (js_apiResponseIsErr(&ar)) { if (errCode != NULL) *errCode = (int) ar.Error.ErrCode; // If the error code is JSStreamNotFoundErr then pick NATS_NOT_FOUND. if (ar.Error.ErrCode == JSStreamNotFoundErr) s = NATS_NOT_FOUND; else s = nats_setError(NATS_ERR, "%s", ar.Error.Description); } else { nats_JSON **consumers = NULL; int consumersLen = 0; IFOK(s, nats_JSONGetLong(json, "total", &page->total)); IFOK(s, nats_JSONGetLong(json, "offset", &page->offset)); IFOK(s, nats_JSONGetLong(json, "limit", &page->limit)); IFOK(s, nats_JSONGetArrayObject(json, "consumers", &consumers, &consumersLen)); if ((s == NATS_OK) && (consumers != NULL)) { int i; for (i=0; (s == NATS_OK) && (iConfig == NULL) || nats_IsStringEmpty(ci->Name))) s = nats_setError(NATS_ERR, "%s", "consumer name missing"); IFOK(s, natsStrHash_SetEx(m, (char*) ci->Name, true, true, ci, (void**) &oci)); if (oci != NULL) jsConsumerInfo_Destroy(oci); } // Free the array of JSON objects that was allocated by nats_JSONGetArrayObject. NATS_FREE(consumers); } } js_freeApiRespContent(&ar); nats_JSONDestroy(json); return NATS_UPDATE_ERR_STACK(s); } natsStatus js_Consumers(jsConsumerInfoList **new_list, jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode) { natsStatus s = NATS_OK; natsBuffer *buf = NULL; char *subj = NULL; natsMsg *resp = NULL; natsConnection *nc = NULL; bool freePfx = false; bool done = false; int64_t offset = 0; int64_t start = 0; int64_t timeout = 0; natsStrHash *cons = NULL; jsConsumerInfoList *list = NULL; jsOptions o; apiPaged page; if (errCode != NULL) *errCode = 0; if ((new_list == NULL) || (js == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); s = _checkStreamName(stream); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); s = js_setOpts(&nc, &freePfx, js, opts, &o); if (s == NATS_OK) { if (nats_asprintf(&subj, jsApiConsumerListT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream) < 0) s = nats_setDefaultError(NATS_NO_MEMORY); if (freePfx) NATS_FREE((char*) o.Prefix); } IFOK(s, natsBuf_Create(&buf, 16)); IFOK(s, natsStrHash_Create(&cons, 16)); if (s == NATS_OK) { memset(&page, 0, sizeof(apiPaged)); start = nats_Now(); } do { IFOK(s, natsBuf_AppendByte(buf, '{')); IFOK(s, nats_marshalLong(buf, false, "offset", offset)); IFOK(s, natsBuf_AppendByte(buf, '}')); timeout = o.Wait - (nats_Now() - start); if (timeout <= 0) s = NATS_TIMEOUT; // Send the request IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, natsBuf_Data(buf), natsBuf_Len(buf), timeout)); IFOK(s, _unmarshalConsumerInfoListResp(cons, &page, resp, errCode)); if (s == NATS_OK) { offset += page.limit; done = offset >= page.total; if (!done) { // Reset the request buffer, we may be able to reuse. natsBuf_Reset(buf); } } natsMsg_Destroy(resp); resp = NULL; } while ((s == NATS_OK) && !done); natsBuf_Destroy(buf); NATS_FREE(subj); if (s == NATS_OK) { if (natsStrHash_Count(cons) == 0) { natsStrHash_Destroy(cons); return NATS_NOT_FOUND; } list = (jsConsumerInfoList*) NATS_CALLOC(1, sizeof(jsConsumerInfoList)); if (list == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); else { list->List = (jsConsumerInfo**) NATS_CALLOC(natsStrHash_Count(cons), sizeof(jsConsumerInfo*)); if (list->List == NULL) { NATS_FREE(list); list = NULL; s = nats_setDefaultError(NATS_NO_MEMORY); } else { natsStrHashIter iter; void *val = NULL; natsStrHashIter_Init(&iter, cons); while (natsStrHashIter_Next(&iter, NULL, (void**) &val)) { jsConsumerInfo *ci = (jsConsumerInfo*) val; list->List[list->Count++] = ci; natsStrHashIter_RemoveCurrent(&iter); } natsStrHashIter_Done(&iter); *new_list = list; } } } if (s != NATS_OK) { natsStrHashIter iter; void *val = NULL; natsStrHashIter_Init(&iter, cons); while (natsStrHashIter_Next(&iter, NULL, (void**) &val)) { jsStreamInfo *si = (jsStreamInfo*) val; natsStrHashIter_RemoveCurrent(&iter); jsStreamInfo_Destroy(si); } natsStrHashIter_Done(&iter); } natsStrHash_Destroy(cons); return NATS_UPDATE_ERR_STACK(s); } void jsConsumerInfoList_Destroy(jsConsumerInfoList *list) { int i; if (list == NULL) return; for (i=0; iCount; i++) jsConsumerInfo_Destroy(list->List[i]); NATS_FREE(list->List); NATS_FREE(list); } natsStatus js_ConsumerNames(jsConsumerNamesList **new_list, jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode) { natsStatus s = NATS_OK; natsBuffer *buf = NULL; char *subj = NULL; natsMsg *resp = NULL; natsConnection *nc = NULL; bool freePfx = false; bool done = false; int64_t offset = 0; int64_t start = 0; int64_t timeout = 0; natsStrHash *names = NULL; jsConsumerNamesList *list = NULL; jsOptions o; apiPaged page; if (errCode != NULL) *errCode = 0; if ((new_list == NULL) || (js == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); s = _checkStreamName(stream); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); s = js_setOpts(&nc, &freePfx, js, opts, &o); if (s == NATS_OK) { if (nats_asprintf(&subj, jsApiConsumerNamesT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream) < 0) s = nats_setDefaultError(NATS_NO_MEMORY); if (freePfx) NATS_FREE((char*) o.Prefix); } IFOK(s, natsBuf_Create(&buf, 16)); IFOK(s, natsStrHash_Create(&names, 16)); if (s == NATS_OK) { memset(&page, 0, sizeof(apiPaged)); start = nats_Now(); } do { IFOK(s, natsBuf_AppendByte(buf, '{')); IFOK(s, nats_marshalLong(buf, false, "offset", offset)); IFOK(s, natsBuf_AppendByte(buf, '}')); timeout = o.Wait - (nats_Now() - start); if (timeout <= 0) s = NATS_TIMEOUT; // Send the request IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, natsBuf_Data(buf), natsBuf_Len(buf), timeout)); IFOK(s, _unmarshalNamesListResp("consumers", names, &page, resp, errCode)); if (s == NATS_OK) { offset += page.limit; done = offset >= page.total; if (!done) { // Reset the request buffer, we may be able to reuse. natsBuf_Reset(buf); } } natsMsg_Destroy(resp); resp = NULL; } while ((s == NATS_OK) && !done); natsBuf_Destroy(buf); NATS_FREE(subj); if (s == NATS_OK) { if (natsStrHash_Count(names) == 0) { natsStrHash_Destroy(names); return NATS_NOT_FOUND; } list = (jsConsumerNamesList*) NATS_CALLOC(1, sizeof(jsConsumerNamesList)); if (list == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); else { list->List = (char**) NATS_CALLOC(natsStrHash_Count(names), sizeof(char*)); if (list->List == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); else { natsStrHashIter iter; char *sname = NULL; natsStrHashIter_Init(&iter, names); while ((s == NATS_OK) && natsStrHashIter_Next(&iter, &sname, NULL)) { char *copyName = NULL; DUP_STRING(s, copyName, sname); if (s == NATS_OK) { list->List[list->Count++] = copyName; natsStrHashIter_RemoveCurrent(&iter); } } natsStrHashIter_Done(&iter); } if (s == NATS_OK) *new_list = list; else jsConsumerNamesList_Destroy(list); } } natsStrHash_Destroy(names); return NATS_UPDATE_ERR_STACK(s); } void jsConsumerNamesList_Destroy(jsConsumerNamesList *list) { int i; if (list == NULL) return; for (i=0; iCount; i++) NATS_FREE(list->List[i]); NATS_FREE(list->List); NATS_FREE(list); } natsStatus js_cloneConsumerConfig(jsConsumerConfig *org, jsConsumerConfig **clone) { natsStatus s = NATS_OK; jsConsumerConfig *c = NULL; *clone = NULL; if (org == NULL) return NATS_OK; c = (jsConsumerConfig*) NATS_CALLOC(1, sizeof(jsConsumerConfig)); if (c == NULL) return nats_setDefaultError(NATS_NO_MEMORY); memcpy(c, org, sizeof(jsConsumerConfig)); // Need to first set all pointers to NULL in case we fail to dup and then // do the cleanup. c->Name = NULL; c->Durable = NULL; c->Description = NULL; c->BackOff = NULL; c->FilterSubject = NULL; c->SampleFrequency = NULL; c->DeliverSubject = NULL; c->DeliverGroup = NULL; // Now dup all strings, etc... IF_OK_DUP_STRING(s, c->Name, org->Name); IF_OK_DUP_STRING(s, c->Durable, org->Durable); IF_OK_DUP_STRING(s, c->Description, org->Description); IF_OK_DUP_STRING(s, c->FilterSubject, org->FilterSubject); IF_OK_DUP_STRING(s, c->SampleFrequency, org->SampleFrequency); IF_OK_DUP_STRING(s, c->DeliverSubject, org->DeliverSubject); IF_OK_DUP_STRING(s, c->DeliverGroup, org->DeliverGroup); if ((s == NATS_OK) && (org->BackOff != NULL) && (org->BackOffLen > 0)) { c->BackOff = (int64_t*) NATS_CALLOC(org->BackOffLen, sizeof(int64_t)); if (c->BackOff == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); else memcpy(c->BackOff, org->BackOff, org->BackOffLen*sizeof(int64_t)); } if (s == NATS_OK) *clone = c; else js_destroyConsumerConfig(c); return NATS_UPDATE_ERR_STACK(s); }