diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d015eb3a..4aa5ddce4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ include(CTest) include(FindPackageHandleStandardArgs) # Uncomment to have the build process verbose -# set(CMAKE_VERBOSE_MAKEFILE TRUE) +set(CMAKE_VERBOSE_MAKEFILE TRUE) # Set output directories for libraries and executables. # This is important for Windows builds to have the DLLs in the same directory as the executables. @@ -29,19 +29,19 @@ option(NATS_UPDATE_VERSION "Update the version file" OFF) option(NATS_UPDATE_DOC "Update the doc template file" OFF) option(NATS_COVERAGE "Code coverage" OFF) option(NATS_BUILD_NO_SPIN "Enable if spin code does not compile on your arch" OFF) -option(NATS_BUILD_WITH_TLS "Build with TLS support" ON) -option(NATS_BUILD_TLS_FORCE_HOST_VERIFY "Forces hostname verification" ON) +option(NATS_BUILD_WITH_TLS "Build with TLS support" OFF) +option(NATS_BUILD_TLS_FORCE_HOST_VERIFY "Forces hostname verification" OFF) option(NATS_BUILD_TLS_USE_OPENSSL_1_1_API "Build for OpenSSL 1.1+" ON) option(NATS_BUILD_USE_SODIUM "Build using libsodium library" OFF) option(NATS_BUILD_EXAMPLES "Build examples" ON) option(NATS_BUILD_LIBUV_EXAMPLE "Build libuv examples" OFF) -option(NATS_BUILD_LIBEVENT_EXAMPLE "Build libevent examples" OFF) -option(NATS_BUILD_STATIC_EXAMPLES "Statically link examples" OFF) -option(NATS_BUILD_STREAMING "Build NATS Streaming" ON) +option(NATS_BUILD_LIBEVENT_EXAMPLE "Build libevent examples" ON) +option(NATS_BUILD_STATIC_EXAMPLES "Statically link examples" ON) +option(NATS_BUILD_STREAMING "Build NATS Streaming" OFF) option(NATS_BUILD_NO_PREFIX_CONNSTS "No prefix for connection status enum" OFF) option(NATS_BUILD_LIB_STATIC "Build static library" ON) -option(NATS_BUILD_LIB_SHARED "Build shared library" ON) -option(NATS_COMPILER_HARDENING "Compiler hardening flags" OFF) +option(NATS_BUILD_LIB_SHARED "Build shared library" OFF) +option(NATS_COMPILER_HARDENING "Compiler hardening flags" ON) if(UNIX AND APPLE) option(CMAKE_MACOSX_RPATH "Build with macOS RPath" ON) endif() @@ -64,7 +64,7 @@ if(NATS_BUILD_WITH_TLS) endif(NATS_BUILD_WITH_TLS) set(LIBUV_DIR "" CACHE PATH "Libuv install directory") -set(LIBEVENT_DIR "" CACHE PATH "Libevent install directory") +set(LIBEVENT_DIR "/usr/local" CACHE PATH "Libevent install directory") set(NATS_DOC_PROJECT_NAME "NATS C Client with JetStream support") if(NATS_BUILD_STREAMING) @@ -165,7 +165,7 @@ if(UNIX) # Some compiler hardening flags. if(NATS_COMPILER_HARDENING) - set(NATS_COMMON_C_FLAGS "${NATS_COMMON_C_FLAGS} -fPIE -fstack-protector-all -D_FORTIFY_SOURCE=2 -O1") + set(NATS_COMMON_C_FLAGS "${NATS_COMMON_C_FLAGS} -fPIE -fstack-protector-all -D_FORTIFY_SOURCE=2 -O0") # Works only with GCC. if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(NATS_COMMON_C_FLAGS "${NATS_COMMON_C_FLAGS} -Wl,-z,relro,-z,now -pie") @@ -279,10 +279,6 @@ endif(UNIX) add_subdirectory(src) add_subdirectory(examples) -add_subdirectory(examples/getstarted) -if(NATS_BUILD_STREAMING) - add_subdirectory(examples/stan) -endif() add_subdirectory(test) -add_subdirectory(test/dylib) + #---------------------------- diff --git a/README.md b/README.md deleted file mode 100644 index 83badc0c3..000000000 --- a/README.md +++ /dev/null @@ -1,1945 +0,0 @@ -# NATS & NATS Streaming - C Client -A C client for the [NATS messaging system](https://nats.io). - -Go [here](http://nats-io.github.io/nats.c) for the online documentation, -and check the [frequently asked questions](https://github.com/nats-io/nats.c#faq). - -This NATS Client implementation is heavily based on the [NATS GO Client](https://github.com/nats-io/nats.go). There is support for Mac OS/X, Linux and Windows (although we don't have specific platform support matrix). - -[![License Apache 2](https://img.shields.io/badge/License-Apache2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) -[![Build Status](https://travis-ci.com/nats-io/nats.c.svg?branch=main)](https://travis-ci.com/github/nats-io/nats.c) -[![Coverage Status](https://coveralls.io/repos/github/nats-io/nats.c/badge.svg?branch=main)](https://coveralls.io/github/nats-io/nats.c?branch=main) -[![Release](https://img.shields.io/badge/release-v3.8.2-blue.svg?style=flat)](https://github.com/nats-io/nats.c/releases/tag/v3.8.2) -[![Documentation](https://img.shields.io/badge/doc-Doxygen-brightgreen.svg?style=flat)](http://nats-io.github.io/nats.c) - -# Table of Contents - -- [Installing](#installing) -- [Building](#building) - * [TLS Support](#tls-support) - * [Link statically](#link-statically) - * [Building with Streaming](#building-with-streaming) - * [Building with Libsodium](#building-with-libsodium) - * [Testing](#testing) -- [Documentation](#documentation) -- [NATS Client](#nats-client) - * [Important Changes](#important-changes) - * [JetStream](#jetstream) - * [JetStream Basic Usage](#jetstream-basic-usage) - * [JetStream Basic Management](#jetstream-basic-usage) - * [KeyValue](#keyvalue) - * [KeyValue Management](#keyvalue-management) - * [KeyValue APIs](#keyvalue-apis) - * [Getting Started](#getting-started) - * [Basic Usage](#basic-usage) - * [Headers](#headers) - * [Wildcard Subscriptions](#wildcard-subscriptions) - * [Queue Groups](#queue-groups) - * [TLS](#tls) - * [New Authentication (Nkeys and User Credentials)](#new-authentication-nkeys-and-user-credentials) - * [Advanced Usage](#advanced-usage) - * [Clustered Usage](#clustered-usage) - * [Using an Event Loop Library](#using-an-event-loop-library) - * [FAQ](#faq) -- [NATS Streaming Client](#nats-streaming-client) - * [Streaming Basic Usage](#streaming-basic-usage) - * [Streaming Subscriptions](#streaming-subscriptions) - * [Streaming Durable Subscriptions](#streaming-durable-subscriptions) - * [Streaming Queue Groups](#streaming-queue-groups) - * [Creating a Queue Group](#creating-a-queue-group) - * [Start Position](#start-position) - * [Leaving the Group](#leaving-the-group) - * [Closing a Queue Group](#closing-a-queue-group) - * [Streaming Durable Queue Groups](#streaming-durable-queue-groups) - * [Creating a Durable Queue Group](#creating-a-durable-queue-group) - * [Start Position of the Durable Queue Group](#start-position-of-the-durable-queue-group) - * [Leaving the Durable Queue Group](#leaving-the-durable-queue-group) - * [Closing the Durable Queue Group](#closing-the-durable-queue-group) - * [Streaming Wildcard Subscriptions](#streaming-wildcard-subscriptions) - * [Streaming Advanced Usage](#streaming-advanced-usage) - * [Connection Status](#connection-status) - * [Asynchronous Publishing](#asynchronous-publishing) - * [Message Acknowledgments and Redelivery](#message-acknowledgments-and-redelivery) - * [Rate limiting/matching](#rate-limitingmatching) - * [Publisher Rate Limiting](#publisher-rate-limiting) - * [Subscriber Rate Limiting](#subscriber-rate-limiting) -- [License](#license) - -## Installing - -There are several package managers with NATS C client library available. If you know one that is not in this list, please submit a PR to add it! - -- [Homebrew](https://github.com/Homebrew/homebrew-core) The "cnats" formula is [here](https://github.com/Homebrew/homebrew-core/blob/master/Formula/c/cnats.rb) -- [vcpkg](https://vcpkg.io) The "cnats" port is [here](https://github.com/microsoft/vcpkg/tree/master/ports/cnats) - -## Building - -First, download the source code: -``` -git clone git@github.com:nats-io/nats.c.git . -``` - -To build the library, use [CMake](https://cmake.org/download/). Note that by default the NATS Streaming API will be built and included in the NATS library. -See below if you do not want to build the Streaming related APIs. - -Make sure that CMake is added to your path. If building on Windows, open a command shell from the Visual Studio Tools menu, and select the appropriate command shell (x64 or x86 for 64 or 32 bit builds respectively). You will also probably need to run this with administrator privileges. - -Create a `build` directory (any name would work) from the root source tree, and `cd` into it. Then issue this command for the first time: - -``` -cmake .. -``` - -In some architectures, you may experience a compilation error for `mutex.c.o` because there is no support -for the assembler instruction that we use to yield when spinning trying to acquire a lock. - -You may get this sort of build error: -``` -/tmp/cc1Yp7sD.s: Assembler messages: -/tmp/cc1Yp7sD.s:302: Error: selected processor does not support ARM mode `yield' -src/CMakeFiles/nats_static.dir/build.make:542: recipe for target 'src/CMakeFiles/nats_static.dir/unix/mutex.c.o' failed -``` -If that's the case, you can solve this by enabling the `NATS_BUILD_NO_SPIN` flag (or use `-DNATS_NO_SPIN` if you compile without CMake): -``` -cmake .. -DNATS_BUILD_NO_SPIN=ON -``` - -If you had previously built the library, you may need to do a `make clean`, or simply delete and re-create the build directory before executing the cmake command. - -To build on Windows, you would need to select the build generator. For instance, to select `nmake`, you would run: - -``` -cmake .. -G "NMake Makefiles" -``` - -Running `cmake -h` would give you the list of possible options and all the generator names. - -Alternatively, you can run the GUI version. From that same *build* command shell, start the GUI: - -``` -c:\program files (x86)\CMake\bin\cmake-gui.exe -``` - -If you started with an empty build directory, you would need to select the source and build directory, then click `Configure`. Here, you will be able to select from the drop-down box the name of the build generator. When done, click `Generate`. Then you can go back to your command shell, or Visual Studio and build. - -To modify some of the build options, you need to edit the cache and rebuild. - -``` -make edit_cache -``` - -Note that if you build on Windows and have selected "NMake Makefiles", replace all following references to `make` with `nmake`. - -Editing the cache allows you to select the build type (Debug, Release, etc), the architecture (64 or 32bit), and so on. - -The default target will build everything, that is, the static and shared NATS libraries and also the examples and the test program. Each are located in their respective directories under your build directory: `src`, `examples` and `test`. - -``` -make install -``` -Will copy both the static and shared libraries in the folder `install/lib` and the public headers in `install/include`. - -## TLS Support - -By default, the library is built with TLS support. You can disable this from the cmake gui `make edit_cache` and switch the `NATS_BUILD_WITH_TLS` option to `OFF`, or pass the option directly to the `cmake` command: - -``` -cmake .. -DNATS_BUILD_WITH_TLS=OFF -``` - -Starting `2.0.0`, when building with TLS/SSL support, the server certificate's expected hostname is always verified. It means that the hostname provided in the URL(s) or through the option `natsOptions_SetExpectedHostname()` will be used to check the hostname present in the certificate. Prior to `2.0.0`, the hostname would be verified *only* if the option `natsOptions_SetExpectedHostname()` was invoked. - -Although we recommend leaving the new default behavior, you can restore the previous behavior by building the library with this option off: - -``` -cmake .. -DNATS_BUILD_TLS_FORCE_HOST_VERIFY=OFF -``` - -The NATS C client is built using APIs from the [OpenSSL](https://github.com/openssl/openssl) library. By default we use `3.0+` APIs. Since OpenSSL `1.0.2` is no longer supported, starting with NATS C Client `v3.6.0` version, the CMake variable `NATS_BUILD_TLS_USE_OPENSSL_1_1_API` is now set to `ON` by default (if you are setting up a new environment) and will use OpenSSL APIs from `1.1+`/`3.0+` APIs. You will still be able to compile with the OpenSSL `1.0.2` library by setting this CMake option to `OFF`: - -``` -cmake .. -DNATS_BUILD_TLS_USE_OPENSSL_1_1_API=OFF -``` - -The variable `NATS_BUILD_TLS_USE_OPENSSL_1_1_API` is deprecated, meaning that in the future this option will simply be removed and only OpenSSL `3.0+` APIs will be used. The code in the library using older OpenSSL APIs will be removed too. - -Note that the variable `NATS_BUILD_WITH_TLS_CLIENT_METHOD` that was deprecated in `v2.0.0` has now been removed. - -Since the NATS C client dynamically links to the OpenSSL library, you need to make sure that you are then running your application against an OpenSSL 1.1+/3.0+ library. - -### Link statically - -If you want to link to the static OpenSSL library, you need to delete the `CMakeCache.txt` and regenerate it with the additional option: -``` -rm CMakeCache.txt -cmake .. -DNATS_BUILD_OPENSSL_STATIC_LIBS=ON -``` -Then call `make` (or equivalent depending on your platform) and this should ensure that the library (and examples and/or test suite executable) are linked against the OpenSSL library, if it was found by CMake. - -## Building with Streaming - -When building the library with Streaming support, the NATS library uses the [libprotobuf-c](https://github.com/protobuf-c/protobuf-c) library. -When cmake runs for the first time (or after removing `CMakeCache.txt` and calling `cmake ..` again), it is looking for the libprotobuf-c library. If it does not find it, a message is printed and the build process fails. -CMake searches for the library in directories where libraries are usually found. However, if you want to specify a specific directory where the library is located, you need to do this: -``` -cmake .. -DNATS_PROTOBUF_DIR= -``` -The static library will be used by default. If you want to change that, or if the library has not the expected name, you need to do this: -``` -# Use the library named mylibproto.so located at /my/location -cmake .. -DNATS_PROTOBUF_LIBRARY=/my/location/mylibproto.so -``` -The two could be combined if the include header is located in a different directory -``` -# Use the library named mylibproto.so located at /my/location and the directory protobuf-c/ containing protobuf-c.h located at /my/other/location -cmake .. -DNATS_PROTOBUF_LIBRARY=/my/location/mylibproto.so -DNATS_PROTOBUF_DIR=/my/other/location -``` - -If you don't want to build the NATS Streaming APIs to be included in the NATS library: -``` -cmake .. -DNATS_BUILD_STREAMING=OFF -``` - -## Building with Libsodium - -When using the new NATS 2.0 security features, the library needs to sign some "nonce" sent by the server during a connect or reconnect. -We use [Ed25519](https://ed25519.cr.yp.to/) public-key signature. The library comes with some code to perform the signature. -In most case, it will be fine, but if performance is an issue (especially if you plan to use the `natsConnection_Sign()` function a lot), you will have the option to build with the [Libsodium](https://github.com/jedisct1/libsodium) library. - -Follow instructions on how to install the libsodium library [here](https://download.libsodium.org/doc/). - -On macOS, you could use `brew`: -``` -brew install libsodium -``` -On Linux, you could use `apt-get` -``` -apt-get install libsodium-dev -``` -Once installed, you can rebuild the NATS C client by first enabling the use of the libsodium library: -``` -cmake .. -DNATS_BUILD_USE_SODIUM=ON -``` -If you have the libsodium library installed in a non standard location that CMake cannot find, you can specify the location of this directory: -``` -cmake .. -DNATS_BUILD_USE_SODIUM=ON -DNATS_SODIUM_DIR=/my/path/to/libsodium -``` - -## Testing - -On platforms where `valgrind` is available, you can run the tests with memory checks. -Here is an example: - -``` -make test ARGS="-T memcheck" -``` - -Or, you can invoke directly the `ctest` program: - -``` -ctest -T memcheck -V -I 1,4 -``` -The above command would run the tests with `valgrind` (`-T memcheck`), with verbose output (`-V`), and run the tests from 1 to 4 (`-I 1,4`). - -If you add a test to `test/test.c`, you need to add it into the `allTests` array. Each entry contains a name, and the test function. You can add it anywhere into this array. -Build you changes: - -``` -make -[ 44%] Built target nats -[ 88%] Built target nats_static -[ 90%] Built target nats-publisher -[ 92%] Built target nats-queuegroup -[ 94%] Built target nats-replier -[ 96%] Built target nats-requestor -[ 98%] Built target nats-subscriber -Scanning dependencies of target testsuite -[100%] Building C object test/CMakeFiles/testsuite.dir/test.c.o -Linking C executable testsuite -[100%] Built target testsuite -``` - -Now regenerate the list by invoking the test suite without any argument: - -``` -./test/testsuite -Number of tests: 77 -``` - -This list the number of tests added to the file `list.txt`. Move this file to the source's test directory. - -``` -mv list.txt ../test/ -``` - -Then, refresh the build: - -``` -cmake .. --- Configuring done --- Generating done --- Build files have been written to: /home/ivan/nats.c/build -``` - -You can use the following environment variables to influence the testsuite behavior. - -When running with memory check, timing changes and overall performance is slower. The following variable allows the testsuite to adjust some of values used during the test: - -``` -export NATS_TEST_VALGRIND=yes -``` - -On Windows, it would be `set` instead of `export`. - -When running the tests in verbose mode, the following environment variable allows you to see the server output from within the test itself. Without this option, the server output is silenced: - -``` -export NATS_TEST_KEEP_SERVER_OUTPUT=yes -``` - -If you want to change the default server executable name (`nats-server.exe`) or specify a specific location, use this environment variable: - -``` -set NATS_TEST_SERVER_EXE=c:\test\nats-server.exe -``` - - -# Documentation - -The public API has been documented using [Doxygen](http://www.stack.nl/~dimitri/doxygen/). - -To generate the documentation, go to the `doc` directory and type the following command: - -``` -doxygen DoxyFile.NATS.Client -``` - -If you toggle the build of the Streaming APIs, and the documentation is no longer matching -what is being built, you can update the documentation by switching the `NATS_UPDATE_DOC` build flag and rebuild the documentation. - -From the build directory: -``` -cmake .. -DNATS_UPDATE_DOC=ON -make -cd /doc -doxygen DoxyFile.NATS.Client -``` - -The generated documentation will be located in the `html` directory. To see the documentation, point your browser to the file `index.html` in that directory. - -Go [here](http://nats-io.github.io/nats.c) for the online documentation. - -The source code is also quite documented. - -# NATS Client - -## Important Changes - -This section lists important changes such as deprecation notices, etc... - -### Version `2.0.0` - -This version introduces the security concepts used by NATS Server `2.0.0` and therefore aligns with the server version. There have been new APIs introduced, but the most important change is the new default behavior with TLS connections: - -* When establishing a secure connection, the server certificate's hostname is now always verified, regardless if the user has invoked `natsOptions_SetExpectedHostname()`. This may break applications that were for instance using an IP to connect to a server that had only the hostname in the certificate. This can be solved by changing your application to use the hostname in the URL or make use of `natsOptions_SetExpectedHostname()`. If this is not possible, you can restore the old behavior by building the library with the new behavior disabled. See #tls-support for more information. - -* This repository used to include precompiled libraries of [libprotobuf-c](https://github.com/protobuf-c/protobuf-c) for macOS, Linux and Windows along with the header files (in the `/pbuf` directory). -We have now removed this directory and require that the user installs the libprotobuf-c library separately. See the [building instructions](#building) to specify the library location if CMake cannot find it directly. - -### Version `1.8.0` - -* The `natsConnStatus` enum values have been prefixed with `NATS_CONN_STATUS_`. If your application is -not using referencing any original value, such as `CONNECTED` or `CLOSED`, etc.. then there is nothing -that you need to do. If you do, you have two options: - * Replace all references from the orignal values to the new value (adding the prefix) - * Compile the library with the option to use the original values (no prefix). Edit the CMake cache -and turn on the option `NATS_BUILD_NO_PREFIX_CONNSTS`. This can be done this way from the build directory: -`cmake .. -DNATS_BUILD_NO_PREFIX_CONNSTS=ON` - -## Getting Started - -The `examples/getstarted` directory has a set of simple examples that are fully functional, yet very simple. -The goal is to demonstrate how easy to use the API is. - -A more complex set of examples are in `examples/` directory and can also be used to benchmark the client library. - -## Basic Usage - -Note that for simplicity, error checking is not performed here. -```c -natsConnection *nc = NULL; -natsSubscription *sub = NULL; -natsMsg *msg = NULL; - -// Connects to the default NATS Server running locally -natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - -// Connects to a server with username and password -natsConnection_ConnectTo(&nc, "nats://ivan:secret@localhost:4222"); - -// Connects to a server with token authentication -natsConnection_ConnectTo(&nc, "nats://myTopSecretAuthenticationToken@localhost:4222"); - -// Simple publisher, sending the given string to subject "foo" -natsConnection_PublishString(nc, "foo", "hello world"); - -// Publish binary data. Content is not interpreted as a string. -char data[] = {1, 2, 0, 4, 5}; -natsConnection_Publish(nc, "foo", (const void*) data, 5); - -// Simple asynchronous subscriber on subject foo, invoking message -// handler 'onMsg' when messages are received, and not providing a closure. -natsConnection_Subscribe(&sub, nc, "foo", onMsg, NULL); - -// Simple synchronous subscriber -natsConnection_SubscribeSync(&sub, nc, "foo"); - -// Using a synchronous subscriber, gets the first message available, waiting -// up to 1000 milliseconds (1 second) -natsSubscription_NextMsg(&msg, sub, 1000); - -// Destroy any message received (asynchronously or synchronously) or created -// by your application. Note that if 'msg' is NULL, the call has no effect. -natsMsg_Destroy(msg); - -// Unsubscribing -natsSubscription_Unsubscribe(sub); - -// Destroying the subscription (this will release the object, which may -// result in freeing the memory). After this call, the object must no -// longer be used. -natsSubscription_Destroy(sub); - -// Publish requests to the given reply subject: -natsConnection_PublishRequestString(nc, "foo", "bar", "help!"); - -// Sends a request (internally creates an inbox) and Auto-Unsubscribe the -// internal subscriber, which means that the subscriber is unsubscribed -// when receiving the first response from potentially many repliers. -// This call will wait for the reply for up to 1000 milliseconds (1 second). -natsConnection_RequestString(&reply, nc, "foo", "help", 1000); - -// Closing a connection (but not releasing the connection object) -natsConnection_Close(nc); - -// When done with the object, free the memory. Note that this call -// closes the connection first, in other words, you could have simply -// this call instead of natsConnection_Close() followed by the destroy -// call. -natsConnection_Destroy(nc); - -// Message handler -void -onMsg(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - // Prints the message, using the message getters: - printf("Received msg: %s - %.*s\n", - natsMsg_GetSubject(msg), - natsMsg_GetDataLength(msg), - natsMsg_GetData(msg)); - - // Don't forget to destroy the message! - natsMsg_Destroy(msg); -} -``` - -## JetStream - -Support for JetStream starts with the version `v3.0.0` of the library and NATS Server `v2.2.0+`, although getting JetStream -specific error codes requires the server at version `v2.3.0+`. Some of the configuration options are only available starting `v2.3.3`, -so we recommend that you use the latest NATS Server release to have a better experience. - -Look at examples named `js-xxx.c` in the `examples` directory for examples on how to use the API. -The new objects and APIs are full documented in the online [documentation](http://nats-io.github.io/nats.c/group__js_group.html). - -### JetStream Basic Usage - -```c -// Connect to NATS -natsConnection_Connect(&conn, opts); - -// Initialize and set some JetStream options -jsOptions jsOpts; -jsOptions_Init(&jsOpts); -jsOpts.PublishAsync.MaxPending = 256; - -// Create JetStream Context -natsConnection_JetStream(&js, conn, &jsOpts); - -// Simple Stream Publisher -js_Publish(&pa, js, "ORDERS.scratch", (const void*) "hello", 5, NULL, &jerr); - -// Simple Async Stream Publisher -for (i=0; i < 500; i++) -{ - js_PublishAsync(js, "ORDERS.scratch", (const void*) "hello", 5, NULL); -} - -// Wait for up to 5 seconds to receive all publish acknowledgments. -jsPubOptions_Init(&jsPubOpts); -jsPubOpts.MaxWait = 5000; -js_PublishAsyncComplete(js, &jsPubOpts); - -// One can get the list of all pending publish async messages, -// to either resend them or simply destroy them. -natsMsgList pending; -s = js_PublishAsyncGetPendingList(&pending, js); -if (s == NATS_OK) -{ - int i; - - for (i=0; i` wildcard matches any length of the fail of a subject, and can only be the last token. - -```c -natsConnection_Subscribe(&sub, nc, "foo.>", onMsg, NULL); -``` -This subscriber would receive any message sent to: - -* foo.bar -* foo.bar.baz -* foo.foo.bar.bax.22 -* etc... - -However, it would not receive messages sent on: - -* foo -* bar.foo.baz -* etc... - -Publishing on this subject would cause the two above subscriber to receive the message: -```c -natsConnection_PublishString(nc, "foo.bar.baz", "got it?"); -``` - -## Queue Groups - -All subscriptions with the same queue name will form a queue group. Each message will be delivered to only one subscriber per queue group, using queue sematics. You can have as many queue groups as you wish. Normal subscribers will continue to work as expected. - -```c -natsConnection_QueueSubscribe(&sub, nc, "foo", "job_workers", onMsg, NULL); -``` - -## TLS - -(Note that the library needs to be built with TLS support - which is by default - for these APIs to work. See the Build chapter on how to build with or without TLS for more details). - -An SSL/TLS connection is configured through the use of `natsOptions`. Depending on the level of security you desire, it can be as simple as setting the secure boolean to true on the `natsOptions_SetSecure()` call. - -Even with full security (client verifying server certificate, and server requiring client certificates), the setup involves only a few calls. - -```c -// Here is the minimum to create a TLS/SSL connection: - -// Create an options object. -natsOptions_Create(&opts); - -// Set the secure flag. -natsOptions_SetSecure(opts, true); - -// You may not need this, but suppose that the server certificate -// is self-signed and you would normally provide the root CA, but -// don't want to. You can disable the server certificate verification -// like this: -natsOptions_SkipServerVerification(opts, true); - -// Connect now... -natsConnection_Connect(&nc, opts); - -// That's it! On success you will have a secure connection with the server! - -(...) - -// This example shows what it takes to have a full SSL configuration, -// including server expected's hostname, root CA, client certificates -// and specific ciphers to use. - -// Create an options object. -natsOptions_Create(&opts); - -// Set the secure flag. -natsOptions_SetSecure(opts, true); - -// For a server with a trusted chain built into the client host, -// simply designate the server name that is expected. Without this -// call, the server certificate is still verified, but not the -// hostname. -natsOptions_SetExpectedHostname(opts, "localhost"); - -// Instead, if you are using a self-signed cert and need to load in the CA. -natsOptions_LoadCATrustedCertificates(opts, caCertFileName); - -// If the server requires client certificates, provide them along with the -// private key, all in one call. -natsOptions_LoadCertificatesChain(opts, certChainFileName, privateKeyFileName); - -// You can also specify preferred ciphers if you want. -natsOptions_SetCiphers(opts, "-ALL:HIGH"); - -// Then simply pass the options object to the connect call: -natsConnection_Connect(&nc, opts); - -// That's it! On success you will have a secure connection with the server! -``` - -## New Authentication (Nkeys and User Credentials) - -This requires server with version >= 2.0.0 - -NATS servers have a new security and authentication mechanism to authenticate with user credentials and Nkeys. The simplest form is to use the helper option `natsOptions_SetUserCredentialsFromFiles()`. - -```c -// Retrieve both user JWT and NKey seed from single file `user.creds`. -s = natsOptions_SetUserCredentialsFromFiles(opts, "user.creds", NULL); -if (s == NATS_OK) - s = natsConnection_Connect(&nc, opts); -``` - -With this option, the library will load the user JWT and NKey seed from a single file. Note that the library wipes the buffers used to read the files. - -If you prefer to store the JWT and seed in two distinct files, use this form instead: - -```c -// Retrieve the user JWT from the file `user.jwt` and the seed from the file `user.nk`. -s = natsOptions_SetUserCredentialsFromFiles(opts, "user.jwt", "user.nk"); -if (s == NATS_OK) - s = natsConnection_Connect(&nc, opts); -``` - -You can also set the callback handlers and manage challenge signing directly. - -```c -/* - * myUserJWTCb is a callback that is supposed to return the user JWT. - * An optional closure can be specified. - * mySignatureCB is a callback that is presented with a nonce and is - * responsible for returning the signature for this nonce. - * An optional closure can be specified. - */ -s = natsOptions_SetUserCredentialsCallbacks(opts, myUserJWTCb, NULL, mySignatureCb, NULL); -if (s == NATS_OK) - s = natsConnection_Connect(&nc, opts); -``` - -For NKey authentication, it is possible to specify the public NKey and the file containing the corresponding NKey seed. On connect, the library will load this file to look for the NKey seed and use it to sign the nonce sent by the server. The library takes care of clearing the memory where the seed is copied as soon -as the nonce is signed. - -```c -s = natsOptions_SetNKeyFromSeed(opts, "UDXU4RCSJNZOIQHZNWXHXORDPRTGNJAHAHFRGZNEEJCPQTT2M7NLCNF4", "seed.nk"); -if (s == NATS_OK) - s = natsConnection_Connect(&nc, opts); -``` -The "seed.nk" file contains the NKey seed (private key). Here is an example: -``` -$ more seed.nk -SUACSSL3UAHUDXKFSNVUZRF5UHPMWZ6BFDTJ7M6USDXIEDNPPQYYYCU3VY -``` - -Finally, it is possible to specify the public NKey and the signature callback. The public key will be sent to the server and the provided callback is responsible for signing the server's nonce. When the server receives the signed nonce, it can check that it was signed poperly using the provided public key. - -```c -/* - * myPublicKey is the user's public key, which will be sent to the server. - * mySignatureCB is a callback that is presented with a nonce and is - * responsible for returning the signature for this nonce. - * An optional closure can be specified. - */ -s = natsOptions_SetNKey(opts, myPublicKey, mySignatureCb, NULL); -if (s == NATS_OK) - s = natsConnection_Connect(&nc, opts); -``` -The signature callback can use any crypto library to sign the nonce, but also the provided `nats_Sign()` function. -```c -natsStatus -mySignatureCb( - char **customErrTxt, - unsigned char **signature, - int *signatureLength, - const char *nonce, - void *closure) -{ - // This approach requires to provide the seed (private key). - // Hardcoding it in the application (like in this example) may not be really safe. - return nats_Sign( - "SUACSSL3UAHUDXKFSNVUZRF5UHPMWZ6BFDTJ7M6USDXIEDNPPQYYYCU3VY", - nonce, signature, signatureLength); -} -``` - -You can sign any content and get the signature in return. The connection must have been created with the `natsOptions_SetUserCredentialsFromFiles()` option for that to work. -```c - s = natsOptions_Create(&opts); - if (s == NATS_OK) - s = natsOptions_SetUserCredentialsFromFiles(opts, "user.creds", NULL); - if (s == NATS_OK) - s = natsConnection_Connect(&nc, opts); - - // Sign some arbitrary content - const unsigned char *content = (const unsigned char*) "hello"; - int contentLen = 5; - unsigned char sig[64]; - - s = natsConnection_Sign(nc, content, contentLen, sig); - if (s == NATS_OK) - { - // Do something with signature... - } -``` - -## Advanced Usage - -Flushing a connection ensures that any data buffered is flushed (sent to) the NATS Server. - -```c -// Flush connection to server, returns when all messages have been processed. -natsConnection_Flush(nc); -printf("All clear!\n"); - -// Same as above but with a timeout value, expressed in milliseconds. -s = natsConnection_FlushTimeout(nc, 1000); -if (s == NATS_OK) - printf("All clear!\n"); -else if (s == NATS_TIMEOUT) - printf("Flushed timed out!\n"); -else - printf("Error during flush: %d - %s\n", s, natsStatus_GetText(s)); -``` - -Auto-unsubscribe allows a subscription to be automatically removed when the subscriber has received a given number of messages. This is used internally by the `natsConnection_Request()` call. - -```c -// Auto-unsubscribe after 100 messages received -natsConnection_Subscribe(&sub, nc, "foo", onMsg, NULL); -natsSubscription_AutoUnsubscribe(sub, 100); -``` - -Subscriptions can be drained. This ensures that the interest is removed from the server but that all messages that were internally queued are processed. - -```c -// This call does not block. -natsSubscription_Drain(sub); - -// If you want to wait for the drain to complete, call this -// and specify a timeout. Zero or negative to wait for ever. -natsSubscription_WaitForDrainCompletion(sub, 0); -``` - -Connections can be drained. This process will first put all registered subscriptions in drain mode and prevent any new subscription from being created. When all subscriptions are drained, the publish calls are drained (by the mean of a connection flush) and new publish calls will fail at this point. Then the connection is closed. - -```c -// Use default timeout of 30 seconds. -// But this call does not block. Use natsOptions_SetClosedCB() to be notified -// that the connection is closed. -natsConnection_Drain(nc); - -// To specify a timeout for the operation to complete, after which the connection -// is forcefully closed. Here is an exampel of a timeout of 10 seconds (10,000 ms). -natsConnection_DrainTimeout(nc, 10000); -``` - -You can have multiple connections in your application, mixing subscribers and publishers. -```c -// Create a connection 'nc1' to host1 -natsConnection_ConnectTo(&nc1, "nats://host1:4222"); - -// Create a connection 'nc2' to host2 -natsConnection_ConnectTo(&nc2, "nats://host2:4222"); - -// Create a subscription on 'foo' from connection 'nc1' -natsConnection_Subscribe(&sub, nc1, "foo", onMsg, NULL); - -// Uses connection 'nc2' to publish a message on subject 'foo'. The subscriber -// created previously will receive it through connection 'nc1'. -natsConnection_PublishString(nc2, "foo", "hello"); -``` - -The use of `natsOptions` allows you to specify options used by the `natsConnection_Connect()` call. Note that the `natsOptions` object that is passed to this call is cloned, whhich means that any modification done to the options object will not have any effect on the connected connection. - -```c -natsOptions *opts = NULL; - -// Create an options object -natsOptions_Create(&opts); - -// Set some properties, starting with the URL to connect to: -natsOptions_SetURL(opts, "nats://host1:4222"); - -// Set a callback for asynchronous errors. This is useful when having an asynchronous -// subscriber, which would otherwise have no other way of reporting an error. -natsOptions_SetErrorHandler(opts, asyncCb, NULL); - -// Connect using those options: -natsConnection_Connect(&nc, opts); - -// Destroy the options object to free memory. The object was cloned by the connection, -// so the options can be safely destroyed. -natsOptions_Destroy(opts); -``` - -As we have seen, all callbacks have a `void *closure` parameter. This is useful when the callback needs to perform some work and need a reference to some object. When setting up the callback, you can specify a pointer to that object. -```c -// Our object definition -typedef struct __Errors -{ - int count; - -} Errors; - -(...) - -int -main(int argc, char **argv) -{ - // Declare an 'Errors' object on the stack. - Errors asyncErrors; - - // Initialize this object - memset(&asyncErrors, 0, sizeof(asyncErrors); - - // Create a natsOptions object. - (...) - - // Set the error callback, and pass the address of our Errors object. - natsOptions_SetErrorHandler(opts, asyncCb, (void*) &asyncErrors); - - // Create the connection and subscriber. - (...) - - // Say that we are done subscribing, we could check the number of errors: - if (asyncErrors.count > 1000) - { - printf("That's a lot of errors!\n"); - } - - (...) -} -``` - -The callback would use the closure this way: -```c -static void -asyncCb(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure) -{ - Errors *errors = (Errors*) closure; - - printf("Async error: %d - %s\n", err, natsStatus_GetText(err)); - - errors->count++; -} -``` -This is the same for all other callbacks used in the C NATS library. - -The library can automaticall reconnect to a NATS Server if the connection breaks. -However, the initial connect itself would fail if no server is available at the -time of the connect. An option has been added to make the connect behaves as -the reconnect, using the reconnect attempts and wait: -```c - s = natsOptions_SetMaxReconnect(opts, 5); - if (s == NATS_OK) - s = natsOptions_SetReconnectWait(opts, 1000); - - // Instruct the library to block the connect call for that - // long until it can get a connection or fails. - if (s == NATS_OK) - s = natsOptions_SetRetryOnFailedConnect(opts, true, NULL, NULL); - - // If the server is not running, this will block for about 5 seconds. - s = natsConnection_Connect(&conn, opts); -``` - -You can make the connect asynchronous (if it can't connect immediately) by -passing a connection handler: -```c - s = natsOptions_SetMaxReconnect(opts, 5); - if (s == NATS_OK) - s = natsOptions_SetReconnectWait(opts, 1000); - if (s == NATS_OK) - s = natsOptions_SetRetryOnFailedConnect(opts, true, connectedCB, NULL); - - // Start the connect. If no server is running, it should return NATS_NOT_YET_CONNECTED. - s = natsConnection_Connect(&conn, opts); - printf("natsConnection_Connect call returned: %s\n", natsStatus_GetText(s)); - - // Connection can be used to create subscription and publish messages (as - // long as the reconnect buffer is not full). -``` -Check the example `examples/connect.c` for more use cases. - -You can also specify a write deadline which means that when the library is trying to -send bytes to the NATS Server, if the connection if unhealthy but as not been reported -as closed, calls will fail with a `NATS_TIMEOUT` error. The socket will be closed and -the library will attempt to reconnect (unless disabled). Note that this could also -happen in the event the server is not consuming fast enough. -```c - // Sets a write deadline of 2 seconds (value is in milliseconds). - s = natsOptions_SetWriteDeadline(opts, 2000); -``` - -## Clustered Usage - -```c -static char *servers[] = { "nats://localhost:1222", - "nats://localhost:1223", - "nats://localhost:1224"}; - -// Setup options to include all servers in the cluster. -// We first created an options object, and pass the list of servers, specifying -// the number of servers on that list. -natsOptions_SetServers(opts, servers, 3); - -// We could also set the amount to sleep between each reconnect attempt (expressed in -// milliseconds), and the number of reconnect attempts. -natsOptions_SetMaxReconnect(opts, 5); -natsOptions_SetReconnectWait(opts, 2000); - -// We could also disable the randomization of the server pool -natsOptions_SetNoRandomize(opts, true); - -// Setup a callback to be notified on disconnects... -natsOptions_SetDisconnectedCB(opts, disconnectedCb, NULL); - -// And on reconncet -natsOptions_SetReconnectedCB(opts, reconnectedCb, NULL); - -// This callback could be used to see who we are connected to on reconnect -static void -reconnectedCb(natsConnection *nc, void *closure) -{ - // Define a buffer to receive the url - char buffer[64]; - - buffer[0] = '\0'; - - natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer)); - printf("Got reconnected to: %s\n", buffer); -} -``` - -## Using an Event Loop Library - -For each connection, the `NATS` library creates a thread reading data from the socket. Publishing data results in the data being appended to a buffer, which is 'flushed' from a timer callback or in place when the buffer reaches a certain size. Flushing means that we write to the socket (and the socket is in blocking-mode). - -If you have multiple connections running in your process, the number of threads will increase (because each connection uses a thread for receiving data from the socket). If this becomes an issue, or if you are already using an event notification library, you can instruct the `NATS` library to use that event library instead of using a thread to do the reads, and directly writing to the socket when data is published. - -This works by setting the event loop and various callbacks through the `natsOptions_SetEventLoop()` API. Depending of the event loop you are using, you then have extra API calls to make. The API is in the `adapters` directory and is documented. - -We provide adapters for two event notification libraries: [libevent](https://github.com/libevent/libevent), and [libuv](https://github.com/libuv/libuv). - -```c -// Create an event loop object -uv_loop_t *uvLoop = uv_default_loop(); - -// Set it into an options object -natsOptions_SetEventLoop(opts, - (void*) uvLoop, - natsLibuv_Attach, - natsLibuv_Read, - natsLibuv_Write, - natsLibuv_Detach); - -// Connect (as usual) -natsConnection_Connect(&conn, opts); - -// Subscribe (as usual) -natsConnection_Subscribe(&sub, conn, subj, onMsg, NULL); - -// Run the event loop -uv_run(uvLoop, UV_RUN_DEFAULT); -``` - -The callback `onMsg` that you have registered will be triggered as usual when data becomes available. - -Where it becomes tricky is when publishing data. Indeed, publishing is merely putting data in a buffer, and it is the event library that will notify a callback that write to the socket should be performed. For that, the event loop needs to be 'running'. - -So if you publish from the thread where the event loop is running, you need to 'run' the loop after each (or a number) of publish calls in order for data to actually be sent out. Alternatively, you can publish from a different thread than the thread running the event loop. - -The above is important to keep in mind regarding calls that are doing request-reply. They should not be made from the thread running the event loop. Here is an example of such calls: - -``` -natsConnection_Request() -natsConnection_Flush() -natsConnection_FlushTimeout() -... -``` - -Indeed, since these calls publish data and wait for a 'response', if you execute then in the event loop thread (or while the loop is not 'running'), then data will not be sent out. Calls will fail to get a response and timeout. - -For `natsConnection_Request()`, use the `natsConnection_PublishRequest()` instead, and have a subscriber for the response registered. - -For others, asynchronous version of these calls should be made available. - -See examples in the `examples` directory for complete usage. - -## FAQ - -Here are some of the frequently asked questions: - -Where do I start? - -There are several resources that will help you get started using the NATS C Client library. -
-The `examples/getstarted` directory contains very basic programs that use -the library for simple functions such as sending a message or setting up a subscription. -
-The `examples` directory itself contains more elaborated examples that include error -handling and more advanced APIs. You will also find examples to that show the use -of the NATS C Client library and external event loops. - -What about support for platform XYZ? - -We support platforms that are available to us for development and testing. This is currently -limited to Linux, macOS and Windows. Even then, there may be OS versions that you may have -problem building with and we will gladly accept PRs to fix the build process as long as it -does not break the ones we support! - -How do I build? - -We use cmake since it allows cross-platforms builds. This works for us. You are free to -create your own makefile or Windows solution. If you want to use cmake, follow these -[instructions](https://github.com/nats-io/nats.c#build). - -I have found a bug in your library, what do I do? - -Please report an issue [here](https://github.com/nats-io/nats.c/issues/new). Give us as much -as possible information on how you can reproduce this. If you have a fix for it, you can -also open a PR. - -Is the library thread-safe? - -All calls use internal locking where needed. As a user, you would need to do your own locking -if you were to share the same callback with different subscribers (since the callback would -be invoked from different threads for each subscriber).
-Note that this is true for any kind of callback that exist in the NATS C library, such as -connection or error handlers, etc.. if you specify the same callback you take the risk that -the code in that callback may be executed from different internal threads. - -What is the threading model of the library? - -The library uses some threads to handle internal timers or dispatch asynchronous errors -for instance. Here is a list of threads created as a result of the user creating NATS -objects: - -- Each connection has a thread to read data from the socket. If you use an external -event loop, this thread is not created. - -- Each connection has a thread responsible for flushing its outgoing buffer. If you create -the connection with the `natsOptions_SetSendAsap()` option, this thread is not created since -any outgoing data is flushed right away. - -- Each asynchronous subscription has a thread used to dispatch messages to the user callback. -If you use `nats_SetMessageDeliveryPoolSize()`, a global thread pool (of the -size given as a parameter to that function) is used instead of a per-async-subscription thread. - -How do I send binary data? - -NATS is a text protocol with message payload being a byte array. The server never interprets -the content of a message. - -The natsConnection_Publish() API accepts a pointer to memory and the user provides how many -bytes from that location need to be sent. The natsConnection_PublishString() is added for -convenience if you want to send strings, but it is really equivalent to calling natsConnection_Publish() -with `strlen` for the number of bytes. - -Is the data sent in place or from a different thread? - -For throughput reasons (and to mimic the Go client this library is based on), the client library -uses a buffer for all socket writes. This buffer is flushed in the thread where the publish occurs -if the buffer is full. The buffer size can be configured with natsOptions_SetIOBufSize(). You can query how -much data is in that buffer using the natsConnection_Buffered() function. - -When a publish call does not fill the buffer, the call returns without any data actually sent -to the server. A dedicated thread (the flusher thread) will flush this buffer automatically. This -helps with throughput since the number of system calls are reduced and the number of bytes -sent at once is higher, however, it can add latency for request/reply situations where one -wants to send one message at a time. To that effect, natsConnection_Request() call does flush -the buffer in place and does not rely on the flusher thread. - -The option natsOptions_SetSendAsap() can be used to force all publish calls from the connection -created with this option to flush the socket buffer at every call and not add delay by relying -on the flusher thread. - -The publish call did not return an error, is the message guaranteed to be sent to a subscriber? - -No! It is not even guaranteed that the server got that message. As described above, the message -could simply be in connection's buffer, even if each publish call is flushing the socket buffer, -after that, the call returns. There is no feedback from the server that it has actually processed -that message. The server could have crashed after reading from the socket. - -Regardless if the server has gotten the message or not, there is a total decoupling between -publishing and subscribing. If the publisher needs to know that its message has bee received -and processed by the subscribing application, request/reply pattern should be used. Check -natsConnection_Request() and natsConnection_PublishRequest() for more details. - -Do I need to call natsConnection_Flush() everywhere? - -This function is not merely flushing the socket buffer, instead it sends a `PING` protocol -message to the server and gets a `PONG` back in a synchronous way. - -As previously described, if you want to flush the socket buffer to reduce latency in all -publish calls, you should create the connection with the "send asap" option. - -The natsConnection_Flush() API is often used to ensure that the server has processed one of the -protocol messages. - -For instance, creating a subscription is asynchronous. When the call returns, you may -get an error if the connection was previously closed, but you would not get an error if your -connection user has no permission to create this subscription for instance. - -Instead, the server sends an error message that is asynchronously received by the client library. -Calling natsConnection_Flush() on the same connection that created the subscription ensures -that the server has processed the subscription and if there was an error has sent that error back -before the `PONG`. It is then possible to check the natsConnection_GetLastError() -to figure out if the subscription was successfully registered or not. - -How is data and protocols received from the server? - -When you create a connection, a library thread is created to read protocols (including messages) -from the socket. You do not have to do anything in that regard. When data is read from the socket -it will be turned into protocols or messages and distributed to appropriate callbacks. - -Lot of things are asynchronous, how do I know if there is an error? - -You should set error callbacks to be notified when asynchronous errors occur. These can be -set through the use of natsOptions. Check natsOptions_SetErrorHandler() for instance. If you -register an error callback for a connection, should an error occurs, your registered error handler -will be invoked. - -Are messages from an asynchronous subscription dispatched in parallel? - -When you create an asynchronous subscription, the library will create a thread that is responsible -to dispatch messages for that subscription. Messages for a given subscription are dispatched -sequentially. That is, your callback is invoked with one message at a time, and only after the -callback returns that the library invokes it again with the next pending message. - -So there is a thread per connection draining data from the socket, and then messages are passed -to the matching subscription thread that is then responsible to dispatch them. - -If you plan to have many subscriptions and to reduce the number of threads used by the library, -you should look at using a library level thread pool that will take care of messages dispatching. -See nats_SetMessageDeliveryPoolSize(). A subscription will make use of the library pool if the -connection it was created from had the natsOptions_UseGlobalMessageDelivery() option set to true. - -Even when using the global pool, messages from a given subscription are always dispatched -sequentially and from the same thread. - -What is the SLOW CONSUMER error that I see in the server? - -The server when sending data to a client (or route) connection sets a write deadline. That is, -if the socket write blocks for that amount of time, it will simply close the connection. - -This error occurs when the client (or other server) is not reading data fast enough from the -socket. As we have seen earlier, a client connection creates a thread whose job is to read -data from the socket, parse it and move protocol or messages to appropriate handlers. - -So this is not really symptomatic of a message handler processing messages too slowly, -instead it is probably the result of resources issues (not enough CPU cycles to read from the -socket, or not reading fast enough) or internal locking contention that prevents the thread -reading from the socket to read data fast enough because it is blocked trying to acquire some -lock. - -What is the SLOW CONSUMER error in the client then? - -This error, in contrast with the error reported by the server, has to do with the disptaching -of messages to the user callback. As explained, messages are moved from the connection's -thread reading from the socket to the subscription's thread responsible for dispatching. - -The subscription's internal queue as a default limited size. When the connection's thread -cannot add a message to that queue because it is full, it will drop the message and if -an error handler has been set, a message will be posted there. - -For instance, having a message handler that takes too much time processing a message is -likely to cause a slow consumer client error if the incoming message rate is high and/or -the subscription pending limits are not big enough. - -The natsSubscription_SetPendingLimits() API can be used to set the subscription's internal -queue limits. Values of `-1` for count and/or size means that the corresponding metric will -not be checked. Setting both to `-1` mean that the client library will queued all incoming -messages, regardless at which speed they are dispatched, which could cause your application -to use lots of memory. - -What happens if the server is restarted or there is a disconnect? - -By default, the library will try to reconnect. Reconnection options can be set to either -disable reconnect logic entirely, or set the number of attempts and delay between attempts. -See natsOptions_SetAllowReconnect(), natsOptions_SetMaxReconnect(), etc... for more information. - -If you have not set natsOptions_SetNoRadomize() to true, then the list of given URLs are randomized -when the connection is created. When a disconnect occurs, the next URL is tried. If that fails, -the library moves to the next. When all have been tried, the loop restart from the first until -they have all been tried the max reconnect attempts defined through options. The library -will pause for as long as defined in the options when trying to reconnect to a server it was -previously connected to. - -If you connect to a server that has the connect URL advertise enabled (default for servers 0.9.2+), -when servers are added to the cluster of NATS Servers, the server will send URLs of this -new server to its clients. This augments the "pool" or URLs that the connection may have been -created with and allows the library to reconnect to servers that have been added to the cluster. - -While the library is disconnected from the server, all publish or new subscription calls are -buffered in memory. This buffer as a default size but can be configured. When this buffer is -full, further publish or new subscription calls will report failures.
-When the client library has reconnected to the server, the pending buffer will -be flushed to the server. - -If your application needs to know if a disconnect occurs, or when the library has reconnected, -you should again set some callbacks to be notified of such events. Use natsOptions_SetDisconnectedCB(), -natsOptions_SetReconnectedCB() and natsOptions_SetClosedCB(). Note that the later is a final -event for a connection. When this callback is invoked, the connection is no longer valid, that -is, you will no longer receive or be able to send data with this connection. - -Do I need to free NATS objects? - -All objects that you create and that have a `_Destroy()` API should indeed be destroyed -if you want to not leak memory.One that is important and often missed is `natsMsg_Destroy()` that -needs to be called in the message handler once you are done processing the message. The -message has been created by the library and given to you in the message handler, but you are -responsible for calling `natsMsg_Destroy()`. - -What is nats_ReleaseThreadMemory() doing? - -The NATS C library may store objects using local thread storage. Threads that are created and -handled by the library are automatically cleaned-up, however, if the user creates a thread -and invokes some NATS APIs, there is a possibility that the library stored something in that -thread local storage. When the thread exits, the user should call this function so that -the library can destroy objects that it may have stored. - -# NATS Streaming Client - -## Streaming Basic Usage - -Note that error checking is being ignored for clarity. Check the `examples/stan` directory -for complete usage. - -``` -// Connect without options -stanConnection_Connect(&sc, cluster, clientID, NULL); - -// Simple Synchronous Publisher. -// This does not return until an ack has been received from NATS Streaming -stanConnection_Publish(sc, "foo", (const void*) "hello", 5); - -// Simple Async Subscriber -stanConnection_PublishAsync(sc, "foo", (const void*) "hello", 5, _pubAckHandler, NULL); - -// Simple Subscription without options (default to non durable, receive new only) -stanConnection_Subscribe(&sub, sc, "foo", onMsg, NULL, NULL); - -// Unsubscribe (note that for non durable subscriptions, Unsubscribe() and Close() are the same -stanSubscription_Unsubscribe(sub); - -// Close connection -stanConnection_Close(sc); -``` - -### Streaming Subscriptions - -NATS Streaming subscriptions are similar to NATS subscriptions, but clients may start their subscription at an earlier point in the message stream, allowing them to receive messages that were published before this client registered interest. - -The options are described with examples below: - -``` -// Create a Subscription Options: -stanSubOptions *subOpts = NULL; - -stanSubOptions_Create(&subOpts); - -// Subscribe starting with most recently published value -stanSubOptions_StartWithLastReceived(subOpts); - -// OR: Receive all stored messages -stanSubOptions_DeliverAllAvailable(subOpts); - -// OR: Receive messages starting at a specific sequence number -stanSubOptions_StartAtSequence(subOpts, 22); - -// OR: Start at messages that were stored 30 seconds ago. Value is expressed in milliseconds. -stanSubOptions_StartAtTimeDelta(subOpts, 30000); - -// Create the subscription with options -stanConnection_Subscribe(&sub, sc, "foo", onMsg, NULL, subOpts); -``` - -### Streaming Durable Subscriptions - -Replay of messages offers great flexibility for clients wishing to begin processing at some earlier point in the data stream. -However, some clients just need to pick up where they left off from an earlier session, without having to manually track their position in the stream of messages. -Durable subscriptions allow clients to assign a durable name to a subscription when it is created. -Doing this causes the NATS Streaming server to track the last acknowledged message for that clientID + durable name, so that only messages since the last acknowledged message will be delivered to the client. - -``` -stanConnection_Connect(&sc, "test-cluster", "client-123", NULL); - -// Create subscription options -stanSubOptions_Create(&subOpts); - -// Set a durable name -stanSubOptions_SetDurableName(subOpts, "my-durable"); - -// Subscribe -stanConnection_Subscribe(&sub, sc, "foo", onMsg, NULL, subOpts); -... -// client receives message sequence 1-40 -... -// client disconnects for an hour -... -// client reconnects with same clientID "client-123" -stanConnection_Connect(&sc, "test-cluster", "client-123", NULL); - -// client re-subscribes to "foo" with same durable name "my-durable" -stanSubOptions_Create(&subOpts); -stanSubOptions_SetDurableName(subOpts, "my-durable"); -stanConnection_Subscribe(&sub, sc, "foo", onMsg, NULL, subOpts); -... -// client receives messages 41-current -``` - -### Streaming Queue Groups - -All subscriptions with the same queue name (regardless of the connection -they originate from) will form a queue group. -Each message will be delivered to only one subscriber per queue group, -using queuing semantics. You can have as many queue groups as you wish. - -Normal subscribers will continue to work as expected. - -#### Creating a Queue Group - -A queue group is automatically created when the first queue subscriber is -created. If the group already exists, the member is added to the group. - -``` -stanConnection_Connect(&sc, "test-cluster", "clientid", NULL); - -// Create a queue subscriber on "foo" for group "bar" -stanConnection_QueueSubscribe(&qsub1, "foo", "bar", onMsg, NULL, NULL); - -// Add a second member -stanConnection_QueueSubscribe(&qsub2, "foo", "bar", onMsg, NULL, NULL); - -// Notice that you can have a regular subscriber on that subject -stanConnection_Subscribe(&sub, sc, "foo", onMsg, NULL, NULL); - -// A message on "foo" will be received by sub and qsub1 or qsub2. -``` - -#### Start Position - -Note that once a queue group is formed, a member's start position is ignored -when added to the group. It will start receive messages from the last -position in the group. - -Suppose the channel `foo` exists and there are `500` messages stored, the group -`bar` is already created, there are two members and the last -message sequence sent is `100`. A new member is added. Note its start position: - -``` -stanSubOptions_Create(&subOpts); -stanSubOptions_StartAtSequence(subOpts, 200); - -stanConnection_QueueSubscribe(&qsub, "foo", "bar", onMsg, NULL, subOpts); -``` - -This will not produce an error, but the start position will be ignored. Assuming -this member would be the one receiving the next message, it would receive message -sequence `101`. - -#### Leaving the Group - -There are two ways of leaving the group: closing the subscriber's connection or -calling `Unsubscribe`: - -``` -// Have qsub leave the queue group -stanSubscription_Unsubscribe(qsub); -``` - -If the leaving member had un-acknowledged messages, those messages are reassigned -to the remaining members. - -#### Closing a Queue Group - -There is no special API for that. Once all members have left (either calling `Unsubscribe`, -or their connections are closed), the group is removed from the server. - -The next call to `QueueSubscribe` with the same group name will create a brand new group, -that is, the start position will take effect and delivery will start from there. - -### Streaming Durable Queue Groups - -As described above, for non durable queue subscribers, when the last member leaves the group, -that group is removed. A durable queue group allows you to have all members leave but still -maintain state. When a member re-joins, it starts at the last position in that group. - -#### Creating a Durable Queue Group - -A durable queue group is created in a similar manner as that of a standard queue group, -except the `DurableName` option must be used to specify durability. - -``` -stanSubOptions_Create(&subOpts); -stanSubOptions_SetDurableName(subOpts, "dur"); - -stanConnection_QueueSubscribe(&qsub, "foo", "bar", onMsg, NULL, subOpts); -``` -A group called `dur:bar` (the concatenation of durable name and group name) is created in -the server. This means two things: - -- The character `:` is not allowed for a queue subscriber's durable name. -- Durable and non-durable queue groups with the same name can coexist. - -``` -// Non durable queue subscriber on group "bar" -stanConnection_QueueSubscribe(&qsub, "foo", "bar", onMsg, NULL, NULL); - -// Durable queue subscriber on group "bar" -stanSubOptions_Create(&subOpts); -stanSubOptions_SetDurableName(subOpts, "mydurablegroup"); -stanConnection_QueueSubscribe(&qsub, "foo", "bar", onMsg, NULL, subOpts); - -// The same message produced on "foo" would be received by both queue subscribers. -``` - -#### Start Position of the Durable Queue Group - -The rules for non-durable queue subscribers apply to durable subscribers. - -#### Leaving the Durable Queue Group - -As for non-durable queue subscribers, if a member's connection is closed, or if -`Unsubscribe` its called, the member leaves the group. Any unacknowledged message -is transferred to remaining members. See *Closing the Group* for important difference -with non-durable queue subscribers. - -#### Closing the Durable Queue Group - -The *last* member calling `Unsubscribe` will close (that is destroy) the -group. So if you want to maintain durability of the group, you should not be -calling `Unsubscribe`. - -So unlike for non-durable queue subscribers, it is possible to maintain a queue group -with no member in the server. When a new member re-joins the durable queue group, -it will resume from where the group left of, actually first receiving all unacknowledged -messages that may have been left when the last member previously left. - - -### Streaming Wildcard Subscriptions - -NATS Streaming subscriptions **do not** support wildcards. - - -## Streaming Advanced Usage - -### Connection Status - -The fact that the NATS Streaming server and clients are not directly connected poses a challenge when it comes to know if a client is still valid. -When a client disconnects, the streaming server is not notified, hence the importance of calling `stanConnection_Close()`. The server sends heartbeats -to the client's private inbox and if it misses a certain number of responses, it will consider the client's connection lost and remove it -from its state. - -Why do we need PINGs? Picture the case where a client connects to a NATS Server which has a route to a NATS Streaming server (either connecting -to a standalone NATS Server or the server it embeds). If the connection between the streaming server and the client's NATS Server is broken, -the client's NATS connection would still be ok, yet, no communication with the streaming server is possible. - -Starting with NATS Streaming Server `0.10.2`, the client library will send PINGs at regular intervals (default is 5 seconds) -and will close the streaming connection after a certain number of PINGs have been sent without any response (default is 3). When that -happens, a callback - if one is registered - will be invoked to notify the user that the connection is permanently lost, and the reason -for the failure. - -Here is how you would specify your own PING values and the callback: - -``` -stanConnOptions_Create(&connOpts); - -// Send PINGs every 10 seconds, and fail after 5 PINGs without any response. -stanConnOptions_SetPings(connOpts, 10, 5); - -// Add a callback to be notified if the STAN connection is lost for good. -stanConnOptions_SetConnectionLostHandler(connOpts, connectionLostCB, NULL); - -// Here is an example of the `connectionLostCB`: -connectionLostCB(stanConnection *sc, const char *errTxt, void *closure) -{ - printf("Connection lost: %s\n", errTxt); -} -``` - -Note that the only way to be notified in your application is to set the callback. If the callback is not set, PINGs are still sent and the connection -will be closed if needed, but the application won't know if it has only subscriptions. A default callback is used to simply -print to standard error the clusterID, the clientID and the error that caused the connection to be lost: -``` -Connection permanently lost: clusterID=test-cluster clientID=client error=connection lost due to PING failure -``` - -When the connection is lost, your application would have to re-create it and all subscriptions if any. - -The library creates its own NATS connection and sets set the reconnect attempts to "infinite". It should therefore be possible -for the library to always reconnect, but this does not mean that the streaming connection will not be closed, even if you set -a very high threshold for the PINGs max out value. Keep in mind that while the client is disconnected, the server is sending -heartbeats to the clients too, and when not getting any response, it will remove that client from its state. When the -communication is restored, the PINGs sent to the server will allow to detect this condition and report to the client that -the connection is now closed. - -Also, while a client is "disconnected" from the server, another application with connectivity to the streaming server may -connect and uses the same client ID. The server, when detecting the duplicate client ID, will try to contact the first client -to know if it should reject the connect request of the second client. Since the communication between the server and the -first client is broken, the server will not get a response and therefore will replace the first client with the second one. - -Prior to NATS Streaming Server `0.10.2`, if the communication between the first client and server were to be restored, -and the application would send messages, the server would accept those because the published messages client ID would be -valid, although the client is not. With a server `0.10.2+`, additional information is sent with each message to allow -the server to reject messages from a client that has been replaced by another client. - -### Asynchronous Publishing - -The basic publish API (`stanConnection_Publish()`) is synchronous; it does not return control to the caller until the -NATS Streaming server has acknowledged receipt of the message. To accomplish this, a unique identifier (GUID) is generated for -the message on creation, and the client library waits for a publish acknowledgment from the server with a matching GUID before -it returns control to the caller, possibly with an error indicating that the operation was not successful due to some server -problem or authorization error. - -Advanced users may wish to process these publish acknowledgments manually to achieve higher publish throughput by not -waiting on individual acknowledgments during the publish operation. An asynchronous publish API is provided for this purpose: - -``` -static void -_pubAckHandler(const char *guid, const char *error, void *closure) -{ - // Note: this callback can be invoked by different threads - if (error != NULL) - printf("pub ack for guid:%s error:%s\n", guid, error); - else - printf("Received ack for msg id %s\n", guid); -} - -(...) - -s = stanConnection_PublishAsync(sc, "foo", (const void*) "hello", 5, _pubAckHandler, NULL); -if (s != NULL) -{ - printf("Error on publish: %d - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); -} -``` -If you want to correlate the published message with the guid in the acknowledgment handler, you should pass -a unique closure as the last argument of the `stanConnection_PublishAsync()` call. Check the `examples/stan/pub-async.c` -file for an example on how to do so. - -### Message Acknowledgments and Redelivery - -NATS Streaming offers At-Least-Once delivery semantics, meaning that once a message has been delivered to an eligible subscriber, -if an acknowledgment is not received within the configured timeout interval, NATS Streaming will attempt redelivery of the message. -This timeout interval is specified by the subscription option `stanSubOptions_SetAckWait()`, which defaults to 30 seconds. - -By default, messages are automatically acknowledged by the NATS Streaming client library after the subscriber's message handler -is invoked. However, there may be cases in which the subscribing client wishes to accelerate or defer acknowledgment of the message. -To do this, the client must set manual acknowledgment mode on the subscription, and invoke `stanSubscription_AckMsg()`. ex: - -``` -// Subscribe with manual ack mode, and set AckWait to 60 seconds -stanSubOptions_Create(&subOpts); -stanSubOptions_SetManualAckMode(subOpts, true); -stanSubOptions_SetAckWait(subOpts, 60000); -stanConnection_Subscribe(&sub, sc, "foo", onMsg, NULL, subOpts); - -// In the callback -void -onMsg(stanConnection *sc, stanSubscription *sub, const char *channel, stanMsg *msg, void *closure) -{ - // ack message before performing I/O intensive operation - stanSubscription_AckMsg(sub, msg); - - printf("Received a message on %s: %.*s\n", - channel, - stanMsg_GetDataLength(msg), - stanMsg_GetData(msg)); -} -``` - -## Rate limiting/matching - -A classic problem of publish-subscribe messaging is matching the rate of message producers with the rate of message consumers. -Message producers can often outpace the speed of the subscribers that are consuming their messages. -This mismatch is commonly called a "fast producer/slow consumer" problem, and may result in dramatic resource utilization spikes -in the underlying messaging system as it tries to buffer messages until the slow consumer(s) can catch up. - -### Publisher rate limiting - -NATS Streaming provides a connection option called `stanConnOptions_SetMaxPubAcksInflight()` that effectively limits the number -of unacknowledged messages that a publisher may have in-flight at any given time. When this maximum is reached, further publish -calls will block until the number of unacknowledged messages falls below the specified limit. ex: - -``` -stanConnOptions_Create(&connOpts); -stanConnOptions_SetMaxPubAcksInflight(connOpts, 25, 1.0); -stanConnection_Connect(&sc, cluster, clientID, connOpts); - -(...) -static void -_pubAckHandler(const char *guid, const char *error, void *closure) -{ - // process the ack - ... -} - -(...) - -for (i = 1; i < 1000; i++) -{ - // If the server is unable to keep up with the publisher, the number of outstanding acks will eventually - // reach the max and this call will block - stanConnection_PublishAsync(sc, "foo", (const void*) "hello", 5, _pubAckHandler, NULL); -} -``` - -Note the last parameter of `stanConnOptions_SetMaxPubAcksInflight()`. This is a float indicating the percentage -of outstanding ACKs to fall below before being allowed to send more messages. For instance, if the maximum is -1000 and percentage is 50% (0.5), the it means that if the publish calls were to be blocked because the -library has sent 1000 messages and not received an ACK yet from the server, the publish calls would be unblocked -only when the library has received 500 ACKs from the server. This prevents the connection from being blocked and -unblocked for each message when the limit has been reached. - -### Subscriber rate limiting - -Rate limiting may also be accomplished on the subscriber side, on a per-subscription basis, using a subscription -option called `stanSubOptions_SetMaxInflight()`. This option specifies the maximum number of outstanding acknowledgments -(messages that have been delivered but not acknowledged) that NATS Streaming will allow for a given subscription. -When this limit is reached, NATS Streaming will suspend delivery of messages to this subscription until the number -of unacknowledged messages falls below the specified limit. ex: - -``` -// Subscribe with manual ack mode and a max in-flight limit of 25 -stanSubOptions_Create(&subOpts); -stanSubOptions_SetManualAckMode(subOpts, true); -stanSubOptions_SetMaxInflight(subOpts, 25); -stanConnection_Subscribe(&sub, "foo", onMsg, NULL, subOpts); - -// In the callback -void -onMsg(stanConnection *sc, stanSubscription *sub, const char *channel, stanMsg *msg, void *closure) -{ - printf("Received a message\n"); - ... - // Does not ack, or takes a very long time to ack - ... - // Message delivery will suspend when the number of unacknowledged messages reaches 25 -} -``` -However, the server will redeliver messages for which it did not receive an acknowledgment for more than the -value passed in `stanSubOptions_SetAckWait()` (or 30 seconds by default). - -# License - -Unless otherwise noted, the NATS source files are distributed -under the Apache Version 2.0 license found in the LICENSE file. diff --git a/examples/asynctimeout.c b/examples/asynctimeout.c deleted file mode 100644 index 8fc762963..000000000 --- a/examples/asynctimeout.c +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2015-2018 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 "examples.h" - -static const char *usage = ""\ -"-gd use global message delivery thread pool\n" \ -"-queue use a queue subscriber with this name\n" \ -"-timeout timeout in milliseconds (default is 10sec)\n" \ -"-count number of expected messages\n"; - -static volatile bool done = false; - -static void -onMsg(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - // This callback will be invoked with a NULL message when the - // subscription times out. - if (print && (msg != NULL)) - printf("Received msg: %s - %.*s\n", - natsMsg_GetSubject(msg), - natsMsg_GetDataLength(msg), - natsMsg_GetData(msg)); - - // We should be using a mutex to protect those variables since - // they are used from the subscription's delivery and the main - // threads. For demo purposes, this is fine. - if ((msg == NULL) || (++count == total)) - { - printf("%s, destroying subscription\n", - (msg == NULL ? "Subscription timed-out" : "All messages received")); - - natsSubscription_Destroy(sub); - done = true; - } - - // It is safe to call natsMsg_Destroy() with a NULL message. - natsMsg_Destroy(msg); -} - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsStatus s; - - opts = parseArgs(argc, argv, usage); - - printf("Listening asynchronously on '%s' with a timeout of %d ms.\n", - subj, (int) timeout); - - s = natsConnection_Connect(&conn, opts); - if (s == NATS_OK) - { - if (name != NULL) - s = natsConnection_QueueSubscribeTimeout(&sub, conn, subj, name, - timeout, onMsg, NULL); - else - s = natsConnection_SubscribeTimeout(&sub, conn, subj, - timeout, onMsg, NULL); - } - // Check every half a second for end of test. - while ((s == NATS_OK) && !done) - { - nats_Sleep(500); - } - - if (s != NATS_OK) - { - printf("Error: %u - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - } - - // Destroy all our objects to avoid report of memory leak - // Do not destroy subscription since it is done in the callback - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - // To silence reports of memory still in used with valgrind - nats_Close(); - - return 0; -} diff --git a/examples/connect.c b/examples/connect.c deleted file mode 100644 index dd89c8b43..000000000 --- a/examples/connect.c +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright 2018 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 "examples.h" - -static void -onMsg(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - if (print) - printf("Received msg: %s - %.*s\n", - natsMsg_GetSubject(msg), - natsMsg_GetDataLength(msg), - natsMsg_GetData(msg)); - - // We should be using a mutex to protect those variables since - // they are used from the subscription's delivery and the main - // threads. For demo purposes, this is fine. - count++; - - natsMsg_Destroy(msg); -} - -static void -connectedCB(natsConnection *nc, void *closure) -{ - char url[256]; - - natsConnection_GetConnectedUrl(nc, url, sizeof(url)); - printf("Connected to %s\n", url); -} - -static void -closedCB(natsConnection *nc, void *closure) -{ - bool *closed = (bool*)closure; - const char *err = NULL; - - natsConnection_GetLastError(nc, &err); - printf("Connect failed: %s\n", err); - *closed = true; -} - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - bool closed = false; - natsStatus s; - - opts = parseArgs(argc, argv, ""); - - // Set a max (re)connect attempts of 50 with a delay of 100 msec. - // Total time will then be around 5 seconds. - - s = natsOptions_SetMaxReconnect(opts, 50); - if (s == NATS_OK) - s = natsOptions_SetReconnectWait(opts, 100); - - // Instruct the library to block the connect call for that - // long until it can get a connection or fails. - if (s == NATS_OK) - s = natsOptions_SetRetryOnFailedConnect(opts, true, NULL, NULL); - - if (s != NATS_OK) - { - printf("Error: %u - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - exit(1); - } - - printf("Ensure no server is running, attempt to connect...\n"); - - // If the server is not running, this will block for about 5 seconds. - start = nats_Now(); - s = natsConnection_Connect(&conn, opts); - elapsed = nats_Now() - start; - - printf("natsConnection_Connect call took %" PRId64 " ms and returned: %s\n", elapsed, natsStatus_GetText(s)); - - // Close/destroy the connection in case you had a server running... - natsConnection_Destroy(conn); - conn = NULL; - - // Now reduce the count, set a connected callback to allow - // connect to be done asynchronously and a closed callback - // to show that if connect fails, the callback is invoked. - s = natsOptions_SetMaxReconnect(opts, 10); - if (s == NATS_OK) - s = natsOptions_SetRetryOnFailedConnect(opts, true, connectedCB, NULL); - if (s == NATS_OK) - s = natsOptions_SetClosedCB(opts, closedCB, (void*)&closed); - - if (s != NATS_OK) - { - printf("Error: %u - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - exit(1); - } - - printf("\n\n"); - printf("Ensure no server is running, attempt to connect with async connect...\n"); - - // Start the connect. If no server is running, it should return - // NATS_NOT_YET_CONNECTED. - s = natsConnection_Connect(&conn, opts); - printf("natsConnection_Connect call returned: %s\n", natsStatus_GetText(s)); - - // Wait for the closed callback to be invoked - while (!closed) - nats_Sleep(100); - - // Destroy the connection object - natsConnection_Destroy(conn); - conn = NULL; - - - // Now change the options to increase the attempts again. - s = natsOptions_SetMaxReconnect(opts, 10); - if (s == NATS_OK) - s = natsOptions_SetReconnectWait(opts, 1000); - // Remove the closed CB for this test - if (s == NATS_OK) - s = natsOptions_SetClosedCB(opts, NULL, NULL); - - if (s != NATS_OK) - { - printf("Error: %u - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - exit(1); - } - - printf("\n\n"); - printf("Ensure no server is running, attempt to connect with async connect...\n"); - - s = natsConnection_Connect(&conn, opts); - printf("Connect returned: %s\n", natsStatus_GetText(s)); - - // Create a subscription and send a message... - s = natsConnection_Subscribe(&sub, conn, subj, onMsg, NULL); - if (s == NATS_OK) - s = natsConnection_Publish(conn, "foo", (const void*)"hello", 5); - - printf("\nStart a server now...\n\n"); - - // Wait for the connect to succeed and message to be received. - while ((s == NATS_OK) && (count != 1)) - nats_Sleep(100); - - printf("Received %d message\n", (int) count); - - // Destroy all our objects to avoid report of memory leak - natsSubscription_Destroy(sub); - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - // To silence reports of memory still in used with valgrind - nats_Close(); - - return 0; -} diff --git a/examples/examples.h b/examples/examples.h index 643e0e48f..5efd7c9b2 100644 --- a/examples/examples.h +++ b/examples/examples.h @@ -14,7 +14,7 @@ #ifndef EXAMPLES_H_ #define EXAMPLES_H_ -#include +#include #include #include @@ -64,8 +64,7 @@ bool pull = false; bool flowctrl = false; static natsStatus -printStats(int mode, natsConnection *conn, natsSubscription *sub, - natsStatistics *stats) +printStats(int mode, natsConnection *conn) //, natsSubscription *sub, natsStatistics *stats) { natsStatus s = NATS_OK; uint64_t inMsgs = 0; @@ -77,24 +76,24 @@ printStats(int mode, natsConnection *conn, natsSubscription *sub, int64_t delivered = 0; int64_t sdropped = 0; - s = natsConnection_GetStats(conn, stats); - if (s == NATS_OK) - s = natsStatistics_GetCounts(stats, &inMsgs, &inBytes, - &outMsgs, &outBytes, &reconnected); - if ((s == NATS_OK) && (sub != NULL)) - { - s = natsSubscription_GetStats(sub, &pending, NULL, NULL, NULL, - &delivered, &sdropped); - - // Since we use AutoUnsubscribe(), when the max has been reached, - // the subscription is automatically closed, so this call would - // return "Invalid Subscription". Ignore this error. - if (s == NATS_INVALID_SUBSCRIPTION) - { - s = NATS_OK; - pending = 0; - } - } + // s = natsConnection_GetStats(conn, stats); + // if (s == NATS_OK) + // s = natsStatistics_GetCounts(stats, &inMsgs, &inBytes, + // &outMsgs, &outBytes, &reconnected); + // if ((s == NATS_OK) && (sub != NULL)) + // { + // s = natsSubscription_GetStats(sub, &pending, NULL, NULL, NULL, + // &delivered, &sdropped); + + // // Since we use AutoUnsubscribe(), when the max has been reached, + // // the subscription is automatically closed, so this call would + // // return "Invalid Subscription". Ignore this error. + // if (s == NATS_INVALID_SUBSCRIPTION) + // { + // s = NATS_OK; + // pending = 0; + // } + // } if (s == NATS_OK) { @@ -120,20 +119,6 @@ printStats(int mode, natsConnection *conn, natsSubscription *sub, return s; } -static void -printPerf(const char *perfTxt) -{ - if ((start > 0) && (elapsed == 0)) - elapsed = nats_Now() - start; - - if (elapsed <= 0) - printf("\nNot enough messages or too fast to report performance!\n"); - else - printf("\n%s %" PRId64 " messages in "\ - "%" PRId64 " milliseconds (%d msgs/sec)\n", - perfTxt, count, elapsed, (int)((count * 1000) / elapsed)); -} - static void printUsageAndExit(const char *progName, const char *usage) { @@ -154,9 +139,6 @@ printUsageAndExit(const char *progName, const char *usage) "%s\n", progName, usage); - natsOptions_Destroy(opts); - nats_Close(); - exit(1); } @@ -200,7 +182,7 @@ parseUrls(const char *urls, natsOptions *gopts) } while (ptr != NULL); if (s == NATS_OK) - s = natsOptions_SetServers(gopts, (const char**) serverUrls, num); + s = nats_SetServers(gopts, (const char**) serverUrls, num); free(urlsCopy); @@ -214,7 +196,8 @@ parseArgs(int argc, char **argv, const char *usage) bool urlsSet = false; int i; - if (natsOptions_Create(&opts) != NATS_OK) + opts = nats_GetDefaultOptions(); + if (opts == NULL) s = NATS_NO_MEMORY; for (i=1; (i - -static void -onMsg(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - printf("Received msg: %s - %.*s\n", - natsMsg_GetSubject(msg), - natsMsg_GetDataLength(msg), - natsMsg_GetData(msg)); - - // Need to destroy the message! - natsMsg_Destroy(msg); - - // Notify the main thread that we are done. - *(bool *)(closure) = true; -} - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsSubscription *sub = NULL; - natsStatus s; - volatile bool done = false; - - printf("Listening on subject 'foo'\n"); - - // Creates a connection to the default NATS URL - s = natsConnection_ConnectTo(&conn, NATS_DEFAULT_URL); - if (s == NATS_OK) - { - // Creates an asynchronous subscription on subject "foo". - // When a message is sent on subject "foo", the callback - // onMsg() will be invoked by the client library. - // You can pass a closure as the last argument. - s = natsConnection_Subscribe(&sub, conn, "foo", onMsg, (void*) &done); - } - if (s == NATS_OK) - { - for (;!done;) { - nats_Sleep(100); - } - } - - // Anything that is created need to be destroyed - natsSubscription_Destroy(sub); - natsConnection_Destroy(conn); - - // If there was an error, print a stack trace and exit - if (s != NATS_OK) - { - nats_PrintLastErrorStack(stderr); - exit(2); - } - - return 0; -} diff --git a/examples/getstarted/headers.c b/examples/getstarted/headers.c deleted file mode 100644 index 8562a3824..000000000 --- a/examples/getstarted/headers.c +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2020-2021 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 - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsMsg *rmsg = NULL; - natsStatus s; - - // Creates a connection to the default NATS URL - s = natsConnection_ConnectTo(&conn, NATS_DEFAULT_URL); - - // Create a message - if (s == NATS_OK) - s = natsMsg_Create(&msg, "foo", NULL, "body", 4); - - // Create a header by setting a key/value - if (s == NATS_OK) - s = natsMsgHeader_Set(msg, "My-Key1", "value1"); - - // Let's set a new key - if (s == NATS_OK) - s = natsMsgHeader_Set(msg, "My-Key2", "value2"); - - // Here we add a value to the first key - if (s == NATS_OK) - s = natsMsgHeader_Add(msg, "My-Key1", "value3"); - - // Adding yet another key - if (s == NATS_OK) - s = natsMsgHeader_Set(msg, "My-Key3", "value4"); - - // Remove a key - if (s == NATS_OK) - s = natsMsgHeader_Delete(msg, "My-Key3"); - - // Let's print all the keys that are currently set - if (s == NATS_OK) - { - const char* *keys = NULL; - int nkeys = 0; - int i; - - s = natsMsgHeader_Keys(msg, &keys, &nkeys); - for (i=0; (s == NATS_OK) && (i - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsStatus s; - - printf("Publishes a message on subject 'foo'\n"); - - // Creates a connection to the default NATS URL - s = natsConnection_ConnectTo(&conn, NATS_DEFAULT_URL); - if (s == NATS_OK) - { - const char data[] = {104, 101, 108, 108, 111, 33}; - - // Publishes the sequence of bytes on subject "foo". - s = natsConnection_Publish(conn, "foo", data, sizeof(data)); - } - - // Anything that is created need to be destroyed - natsConnection_Destroy(conn); - - // If there was an error, print a stack trace and exit - if (s != NATS_OK) - { - nats_PrintLastErrorStack(stderr); - exit(2); - } - - return 0; -} - - - - diff --git a/examples/getstarted/pubmsg.c b/examples/getstarted/pubmsg.c deleted file mode 100644 index 66b369d8c..000000000 --- a/examples/getstarted/pubmsg.c +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2017-2018 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 - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsMsg *msg = NULL; - natsStatus s; - - printf("Publishes a message on subject 'foo'\n"); - - // Creates a connection to the default NATS URL - s = natsConnection_ConnectTo(&conn, NATS_DEFAULT_URL); - if (s == NATS_OK) - { - const char data[] = {104, 101, 108, 108, 111, 33}; - - // Creates a message for subject "foo", no reply, and - // with the given payload. - s = natsMsg_Create(&msg, "foo", NULL, data, sizeof(data)); - } - if (s == NATS_OK) - { - // Publishes the message on subject "foo". - s = natsConnection_PublishMsg(conn, msg); - } - - // Anything that is created need to be destroyed - natsMsg_Destroy(msg); - natsConnection_Destroy(conn); - - // If there was an error, print a stack trace and exit - if (s != NATS_OK) - { - nats_PrintLastErrorStack(stderr); - exit(2); - } - - return 0; -} - - - - diff --git a/examples/getstarted/pubstr.c b/examples/getstarted/pubstr.c deleted file mode 100644 index 6cf0f8272..000000000 --- a/examples/getstarted/pubstr.c +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2017-2018 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 - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsStatus s; - - printf("Publishes a message on subject 'foo'\n"); - - // Creates a connection to the default NATS URL - s = natsConnection_ConnectTo(&conn, NATS_DEFAULT_URL); - if (s == NATS_OK) - { - // This is a convenient function to send a message on "foo" - // as a string. - s = natsConnection_PublishString(conn, "foo", "hello!"); - } - - // Anything that is created need to be destroyed - natsConnection_Destroy(conn); - - // If there was an error, print a stack trace and exit - if (s != NATS_OK) - { - nats_PrintLastErrorStack(stderr); - exit(2); - } - - return 0; -} - - - - diff --git a/examples/getstarted/replier.c b/examples/getstarted/replier.c deleted file mode 100644 index fef1769e8..000000000 --- a/examples/getstarted/replier.c +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2017-2018 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 - -static void -onMsg(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - printf("Received msg: %s - %.*s\n", - natsMsg_GetSubject(msg), - natsMsg_GetDataLength(msg), - natsMsg_GetData(msg)); - - // Sends a reply - if (natsMsg_GetReply(msg) != NULL) - { - natsConnection_PublishString(nc, natsMsg_GetReply(msg), - "here's some help"); - } - - // Need to destroy the message! - natsMsg_Destroy(msg); - - // Notify the main thread that we are done. - *(bool *)(closure) = true; -} - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsSubscription *sub = NULL; - natsStatus s; - volatile bool done = false; - - printf("Listening for requests on subject 'help'\n"); - - // Creates a connection to the default NATS URL - s = natsConnection_ConnectTo(&conn, NATS_DEFAULT_URL); - if (s == NATS_OK) - { - // Creates an asynchronous subscription on subject "help", - // waiting for a request. If a message arrives on this - // subject, the callback onMsg() will be invoked and it - // will send a reply. - s = natsConnection_Subscribe(&sub, conn, "help", onMsg, (void*) &done); - } - if (s == NATS_OK) - { - for (;!done;) { - nats_Sleep(100); - } - } - - // Anything that is created need to be destroyed - natsSubscription_Destroy(sub); - natsConnection_Destroy(conn); - - // If there was an error, print a stack trace and exit - if (s != NATS_OK) - { - nats_PrintLastErrorStack(stderr); - exit(2); - } - - return 0; -} diff --git a/examples/getstarted/requestor.c b/examples/getstarted/requestor.c deleted file mode 100644 index 82cceaf6b..000000000 --- a/examples/getstarted/requestor.c +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2017-2018 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 - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsMsg *reply= NULL; - natsStatus s; - - printf("Publishes a message on subject 'help'\n"); - - // Creates a connection to the default NATS URL - s = natsConnection_ConnectTo(&conn, NATS_DEFAULT_URL); - if (s == NATS_OK) - { - // Sends a request on "help" and expects a reply. - // Will wait only for a given number of milliseconds, - // say for 5 seconds in this example. - s = natsConnection_RequestString(&reply, conn, "help", - "really need some", 5000); - } - if (s == NATS_OK) - { - // If we are here, we should have received the reply - printf("Received reply: %.*s\n", - natsMsg_GetDataLength(reply), - natsMsg_GetData(reply)); - - // Need to destroy the message! - natsMsg_Destroy(reply); - } - - // Anything that is created need to be destroyed - natsConnection_Destroy(conn); - - // If there was an error, print a stack trace and exit - if (s != NATS_OK) - { - nats_PrintLastErrorStack(stderr); - exit(2); - } - - return 0; -} - - - - diff --git a/examples/getstarted/syncsub.c b/examples/getstarted/syncsub.c deleted file mode 100644 index 0365c1b8d..000000000 --- a/examples/getstarted/syncsub.c +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2017-2018 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 - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsStatus s; - - printf("Listening on subject 'foo'\n"); - - // Creates a connection to the default NATS URL - s = natsConnection_ConnectTo(&conn, NATS_DEFAULT_URL); - if (s == NATS_OK) - { - // Creates an synchronous subscription on subject "foo". - s = natsConnection_SubscribeSync(&sub, conn, "foo"); - } - if (s == NATS_OK) - { - // With synchronous subscriptions, one need to poll - // using this function. A timeout is used to instruct - // how long we are willing to wait. The wait is in milliseconds. - // So here, we are going to wait for 5 seconds. - s = natsSubscription_NextMsg(&msg, sub, 5000); - } - if (s == NATS_OK) - { - // If we are here, we should have received a message. - printf("Received msg: %s - %.*s\n", - natsMsg_GetSubject(msg), - natsMsg_GetDataLength(msg), - natsMsg_GetData(msg)); - - // Need to destroy the message! - natsMsg_Destroy(msg); - } - - // Anything that is created need to be destroyed - natsSubscription_Destroy(sub); - natsConnection_Destroy(conn); - - // If there was an error, print a stack trace and exit - if (s != NATS_OK) - { - nats_PrintLastErrorStack(stderr); - exit(2); - } - - return 0; -} diff --git a/examples/js-pub.c b/examples/js-pub.c deleted file mode 100644 index 142fcddb7..000000000 --- a/examples/js-pub.c +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2021 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 "examples.h" - -static const char *usage = ""\ -"-stream stream name (default is 'foo')\n" \ -"-txt text to send (default is 'hello')\n" \ -"-count number of messages to send\n" \ -"-sync publish synchronously (default is async)\n"; - -static void -_jsPubErr(jsCtx *js, jsPubAckErr *pae, void *closure) -{ - int *errors = (int*) closure; - - printf("Error: %u - Code: %u - Text: %s\n", pae->Err, pae->ErrCode, pae->ErrText); - printf("Original message: %.*s\n", natsMsg_GetDataLength(pae->Msg), natsMsg_GetData(pae->Msg)); - - *errors = (*errors + 1); - - // If we wanted to resend the original message, we would do something like that: - // - // js_PublishMsgAsync(js, &(pae->Msg), NULL); - // - // Note that we use `&(pae->Msg)` so that the library set it to NULL if it takes - // ownership, and the library will not destroy the message when this callback returns. - - // No need to destroy anything, everything is handled by the library. -} - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsStatistics *stats = NULL; - natsOptions *opts = NULL; - jsCtx *js = NULL; - jsOptions jsOpts; - jsErrCode jerr = 0; - natsStatus s; - int dataLen=0; - volatile int errors = 0; - bool delStream = false; - - opts = parseArgs(argc, argv, usage); - dataLen = (int) strlen(payload); - - s = natsConnection_Connect(&conn, opts); - - if (s == NATS_OK) - s = jsOptions_Init(&jsOpts); - - if (s == NATS_OK) - { - if (async) - { - jsOpts.PublishAsync.ErrHandler = _jsPubErr; - jsOpts.PublishAsync.ErrHandlerClosure = (void*) &errors; - } - s = natsConnection_JetStream(&js, conn, &jsOpts); - } - - if (s == NATS_OK) - { - jsStreamInfo *si = NULL; - - // First check if the stream already exists. - s = js_GetStreamInfo(&si, js, stream, NULL, &jerr); - if (s == NATS_NOT_FOUND) - { - jsStreamConfig cfg; - - // Since we are the one creating this stream, we can delete at the end. - delStream = true; - - // Initialize the configuration structure. - jsStreamConfig_Init(&cfg); - cfg.Name = stream; - // Set the subject - cfg.Subjects = (const char*[1]){subj}; - // Set the subject count - cfg.SubjectsLen = 1; - // Make it a memory stream. - cfg.Storage = js_MemoryStorage; - // Add the stream, - s = js_AddStream(&si, js, &cfg, NULL, &jerr); - } - if (s == NATS_OK) - { - printf("Stream %s has %" PRIu64 " messages (%" PRIu64 " bytes)\n", - si->Config->Name, si->State.Msgs, si->State.Bytes); - - // Need to destroy the returned stream object. - jsStreamInfo_Destroy(si); - } - } - - if (s == NATS_OK) - s = natsStatistics_Create(&stats); - - if (s == NATS_OK) - { - printf("\nSending %" PRId64 " messages to subject '%s'\n", total, stream); - start = nats_Now(); - } - - for (count = 0; (s == NATS_OK) && (count < total); count++) - { - if (async) - s = js_PublishAsync(js, subj, (const void*) payload, dataLen, NULL); - else - { - jsPubAck *pa = NULL; - - s = js_Publish(&pa, js, subj, (const void*) payload, dataLen, NULL, &jerr); - if (s == NATS_OK) - { - if (pa->Duplicate) - printf("Got a duplicate message! Sequence=%" PRIu64 "\n", pa->Sequence); - - jsPubAck_Destroy(pa); - } - } - } - - if ((s == NATS_OK) && async) - { - jsPubOptions jsPubOpts; - - jsPubOptions_Init(&jsPubOpts); - // Let's set it to 30 seconds, if getting "Timeout" errors, - // this may need to be increased based on the number of messages - // being sent. - jsPubOpts.MaxWait = 30000; - s = js_PublishAsyncComplete(js, &jsPubOpts); - if (s == NATS_TIMEOUT) - { - // Let's get the list of pending messages. We could resend, - // etc, but for now, just destroy them. - natsMsgList list; - - js_PublishAsyncGetPendingList(&list, js); - natsMsgList_Destroy(&list); - } - } - - if (s == NATS_OK) - { - jsStreamInfo *si = NULL; - - elapsed = nats_Now() - start; - printStats(STATS_OUT, conn, NULL, stats); - printPerf("Sent"); - - if (errors != 0) - printf("There were %d asynchronous errors\n", errors); - - // Let's report some stats after the run - s = js_GetStreamInfo(&si, js, stream, NULL, &jerr); - if (s == NATS_OK) - { - printf("\nStream %s has %" PRIu64 " messages (%" PRIu64 " bytes)\n", - si->Config->Name, si->State.Msgs, si->State.Bytes); - - jsStreamInfo_Destroy(si); - } - } - if (delStream && (js != NULL)) - { - printf("\nDeleting stream %s: ", stream); - s = js_DeleteStream(js, stream, NULL, &jerr); - if (s == NATS_OK) - printf("OK!"); - printf("\n"); - } - if (s != NATS_OK) - { - printf("Error: %u - %s - jerr=%u\n", s, natsStatus_GetText(s), jerr); - nats_PrintLastErrorStack(stderr); - } - - // Destroy all our objects to avoid report of memory leak - jsCtx_Destroy(js); - natsStatistics_Destroy(stats); - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - // To silence reports of memory still in used with valgrind - nats_Close(); - - return 0; -} diff --git a/examples/js-sub.c b/examples/js-sub.c deleted file mode 100644 index 30fe1dbe4..000000000 --- a/examples/js-sub.c +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2021 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 "examples.h" - -static const char *usage = ""\ -"-gd use global message delivery thread pool\n" \ -"-sync receive synchronously (default is asynchronous)\n" \ -"-pull use pull subscription\n" \ -"-fc enable flow control\n" \ -"-count number of expected messages\n"; - -static void -onMsg(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - if (print) - printf("Received msg: %s - %.*s\n", - natsMsg_GetSubject(msg), - natsMsg_GetDataLength(msg), - natsMsg_GetData(msg)); - - if (start == 0) - start = nats_Now(); - - // We should be using a mutex to protect those variables since - // they are used from the subscription's delivery and the main - // threads. For demo purposes, this is fine. - if (++count == total) - elapsed = nats_Now() - start; - - // Since this is auto-ack callback, we don't need to ack here. - natsMsg_Destroy(msg); -} - -static void -asyncCb(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure) -{ - printf("Async error: %u - %s\n", err, natsStatus_GetText(err)); - - natsSubscription_GetDropped(sub, (int64_t*) &dropped); -} - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsStatistics *stats = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - jsCtx *js = NULL; - jsErrCode jerr = 0; - jsOptions jsOpts; - jsSubOptions so; - natsStatus s; - bool delStream = false; - - opts = parseArgs(argc, argv, usage); - - printf("Created %s subscription on '%s'.\n", - (pull ? "pull" : (async ? "asynchronous" : "synchronous")), subj); - - s = natsOptions_SetErrorHandler(opts, asyncCb, NULL); - - if (s == NATS_OK) - s = natsConnection_Connect(&conn, opts); - - if (s == NATS_OK) - s = jsOptions_Init(&jsOpts); - - if (s == NATS_OK) - s = jsSubOptions_Init(&so); - if (s == NATS_OK) - { - so.Stream = stream; - so.Consumer = durable; - if (flowctrl) - { - so.Config.FlowControl = true; - so.Config.Heartbeat = (int64_t)1E9; - } - } - - if (s == NATS_OK) - s = natsConnection_JetStream(&js, conn, &jsOpts); - - if (s == NATS_OK) - { - jsStreamInfo *si = NULL; - - // First check if the stream already exists. - s = js_GetStreamInfo(&si, js, stream, NULL, &jerr); - if (s == NATS_NOT_FOUND) - { - jsStreamConfig cfg; - - // Since we are the one creating this stream, we can delete at the end. - delStream = true; - - // Initialize the configuration structure. - jsStreamConfig_Init(&cfg); - cfg.Name = stream; - // Set the subject - cfg.Subjects = (const char*[1]){subj}; - // Set the subject count - cfg.SubjectsLen = 1; - // Make it a memory stream. - cfg.Storage = js_MemoryStorage; - // Add the stream, - s = js_AddStream(&si, js, &cfg, NULL, &jerr); - } - if (s == NATS_OK) - { - printf("Stream %s has %" PRIu64 " messages (%" PRIu64 " bytes)\n", - si->Config->Name, si->State.Msgs, si->State.Bytes); - - // Need to destroy the returned stream object. - jsStreamInfo_Destroy(si); - } - } - - if (s == NATS_OK) - { - if (pull) - s = js_PullSubscribe(&sub, js, subj, durable, &jsOpts, &so, &jerr); - else if (async) - s = js_Subscribe(&sub, js, subj, onMsg, NULL, &jsOpts, &so, &jerr); - else - s = js_SubscribeSync(&sub, js, subj, &jsOpts, &so, &jerr); - } - if (s == NATS_OK) - s = natsSubscription_SetPendingLimits(sub, -1, -1); - - if (s == NATS_OK) - s = natsStatistics_Create(&stats); - - if ((s == NATS_OK) && pull) - { - natsMsgList list; - int i; - - for (count = 0; (s == NATS_OK) && (count < total); ) - { - s = natsSubscription_Fetch(&list, sub, 1024, 5000, &jerr); - if (s != NATS_OK) - break; - - if (start == 0) - start = nats_Now(); - - count += (int64_t) list.Count; - for (i=0; (s == NATS_OK) && (iConfig->Name, si->State.Msgs, si->State.Bytes); - - jsStreamInfo_Destroy(si); - } - if (delStream) - { - printf("\nDeleting stream %s: ", stream); - s = js_DeleteStream(js, stream, NULL, &jerr); - if (s == NATS_OK) - printf("OK!"); - printf("\n"); - } - } - else - { - printf("Error: %u - %s - jerr=%u\n", s, natsStatus_GetText(s), jerr); - nats_PrintLastErrorStack(stderr); - } - - // Destroy all our objects to avoid report of memory leak - jsCtx_Destroy(js); - natsStatistics_Destroy(stats); - natsSubscription_Destroy(sub); - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - // To silence reports of memory still in used with valgrind - nats_Close(); - - return 0; -} diff --git a/examples/libevent-pub.c b/examples/libevent-pub.c index d9449ee63..028696953 100644 --- a/examples/libevent-pub.c +++ b/examples/libevent-pub.c @@ -11,137 +11,152 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "adapters/libevent.h" +#include + +#include "nats/adapters/libevent.h" +#include "nats/adapters/malloc_heap.h" #include "examples.h" -#ifndef WIN32 -#include -#define THREAD_T pthread_t -#define THREAD_FN void * -#define THREAD_RETURN() return (NULL) -#define THREAD_START(threadvar, fn, arg) \ - pthread_create(&(threadvar), NULL, fn, arg) -#define THREAD_JOIN(th) pthread_join(th, NULL) -#else -#include -#define THREAD_T HANDLE -#define THREAD_FN unsigned __stdcall -#define THREAD_RETURN() return (0) -#define THREAD_START(threadvar, fn, arg) do { \ - uintptr_t threadhandle = _beginthreadex(NULL,0,fn,(arg),0,NULL); \ - (threadvar) = (HANDLE) threadhandle; \ - } while (0) -#define THREAD_JOIN(th) WaitForSingleObject(th, INFINITE) -#endif - -static const char *usage = ""\ -"-txt text to send (default is 'hello')\n" \ -"-count number of messages to send\n"; - -typedef struct -{ - natsConnection *conn; - natsStatus status; +static const char *usage = "" + "-txt text to send (default is 'hello')\n" + "-count number of messages to send\n"; -} threadInfo; +static void reportAndFreeData(natsConnection *nc, natsMessage *m, void *closure) +{ + printf("Message published, freeing the data: %.*s\n", (int)nats_GetMessageDataLen(m), nats_GetMessageData(m)); + free(closure); +} -static THREAD_FN -pubThread(void *arg) +static void reportAndDisconnect(natsConnection *nc, natsMessage *m, void *closure) { - threadInfo *info = (threadInfo*) arg; - natsStatus s = NATS_OK; + printf("Last message published, now disconnecting: %.*s\n", (int)nats_GetMessageDataLen(m), nats_GetMessageData(m)); + nats_DestroyConnection(nc); +} - for (count = 0; (s == NATS_OK) && (count < total); count++) - s = natsConnection_PublishString(info->conn, subj, payload); +static void connected(natsConnection *nc, void *closure) +{ + natsStatus s; + natsString payload; + // Send a simple message with static data. Publish-and-forget. + payload = nats_ToString("Hello, NATS!"); + natsMessage *msg1 = NULL; + s = nats_CreateMessage(&msg1, nc, subj); if (s == NATS_OK) - s = natsConnection_Flush(info->conn); - - natsConnection_Close(info->conn); - - info->status = s; - - if (s != NATS_OK) - nats_PrintLastErrorStack(stderr); + s = nats_SetMessagePayload(msg1, payload.data, payload.len); + if (s == NATS_OK) + s = nats_AsyncPublishNoCopy(nc, msg1); + if (s == NATS_OK) + printf("Message enqueued, text: %.*s\n", (int)payload.len, payload.data); + nats_ReleaseMessage(msg1); + + payload = nats_ToString(strdup("Hello, NATS! " + "I was allocated on the heap, " + "my bytes were copied into another buffer, " + "and I was freed immediately by the app.")); + natsMessage *msg2 = NULL; + if (s == NATS_OK) + s = nats_CreateMessage(&msg2, nc, subj); + if (s == NATS_OK) + s = nats_SetMessagePayload(msg2, payload.data, payload.len); + if (s == NATS_OK) + s = nats_AsyncPublish(nc, msg2); + if (s == NATS_OK) + printf("Message enqueued as a copy, freeing the data now: %.*s\n", + (int)nats_GetMessageDataLen(msg2), nats_GetMessageData(msg2)); + free(payload.data); + nats_ReleaseMessage(msg2); + + // Send a message using nats_AsyncPublishNoCopy, will NOT make a copy of the + // message so we need to free our memory once the message has been published. + payload = nats_ToString(strdup("Hello, NATS! " + "I was allocated on the heap, " + "my bytes were NOT copied, " + "and I was freed by a cusom 'message published' callback")); + natsMessage *msg3 = NULL; + if (s == NATS_OK) + s = nats_CreateMessage(&msg3, nc, subj); + if (s == NATS_OK) + s = nats_SetMessagePayload(msg3, payload.data, payload.len); + if (s == NATS_OK) + s = nats_SetOnMessagePublished(msg3, reportAndFreeData, payload.data); + if (s == NATS_OK) + s = nats_AsyncPublishNoCopy(nc, msg3); + if (s == NATS_OK) + printf("Message enqueued, text: %.*s\n", (int)payload.len, payload.data); + nats_ReleaseMessage(msg3); + + // Same, but there is a shortcut if we don't need a full "done" callback, + // just need to free the buffer. + payload = nats_ToString(strdup("Hello, NATS! " + "I was allocated on the heap, " + "my bytes were NOT copied, " + "and I was 'free'-ed by the cleanup, no custom callback needed.")); + natsMessage *msg4 = NULL; + if (s == NATS_OK) + s = nats_CreateMessage(&msg4, nc, subj); + if (s == NATS_OK) + s = nats_SetMessagePayload(msg4, payload.data, payload.len); + if (s == NATS_OK) + s = nats_SetOnMessageCleanup(msg4, free, payload.data); + if (s == NATS_OK) + s = nats_AsyncPublishNoCopy(nc, msg4); + if (s == NATS_OK) + printf("Message enqueued, text: %.*s\n", (int)payload.len, payload.data); + nats_ReleaseMessage(msg4); - THREAD_RETURN(); + // Send the last message and quit once done. This demonstrates how to set a + // "written" callback on a message. + natsMessage *msgFinal = NULL; + if (s == NATS_OK) + s = nats_CreateMessage(&msgFinal, nc, subj); + if (s == NATS_OK) + s = nats_SetOnMessagePublished(msgFinal, reportAndDisconnect, NULL); + if (s == NATS_OK) + s = nats_AsyncPublish(nc, msgFinal); + if (s == NATS_OK) + printf("FINAL message enqueued, text: %.*s\n", (int)payload.len, payload.data); } -int main(int argc, char **argv) +void closedConnection(natsConnection *nc, void *closure) { - natsConnection *conn = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsStatus s = NATS_OK; - struct event_base *evLoop= NULL; - THREAD_T pub; - threadInfo info; - - nats_Open(-1); - - opts = parseArgs(argc, argv, usage); - - printf("Sending %" PRId64 " messages to subject '%s'\n", total, subj); - - // One time initialization of things that we need. - natsLibevent_Init(); + printf("Connection closed, last error: '%s'\n", nats_GetConnectionError(nc)); +} - // Create a loop. - evLoop = event_base_new(); - if (evLoop == NULL) - s = NATS_ERR; +// The event loop and the connection callbacks are passed to the NATS API as +// pointers, they never change once initialized. Allocate them as global data. +static natsEventLoop ev; - // Indicate which loop and callbacks to use once connected. - if (s == NATS_OK) - s = natsOptions_SetEventLoop(opts, (void*) evLoop, - natsLibevent_Attach, - natsLibevent_Read, - natsLibevent_Write, - natsLibevent_Detach); +int main(int argc, char **argv) +{ + natsStatus s = NATS_OK; + natsConnection *conn = NULL; - if (s == NATS_OK) - s = natsConnection_Connect(&conn, opts); + natsOptions *opts = parseArgs(argc, argv, usage); + nats_SetOnConnected(opts, connected, NULL); + nats_SetOnConnectionClosed(opts, closedConnection, NULL); + printf("Hello, mini-nats! Will try to send a message to '%s'\n", subj); + // Create an event loop and initia + s = natsLibevent_Init(&ev); if (s == NATS_OK) - start = nats_Now(); - + s = nats_AsyncConnectWithOptions(&conn, &ev, opts); if (s == NATS_OK) { - info.conn = conn; - info.status = NATS_OK; - - THREAD_START(pub, pubThread, (void*) &info); + event_base_dispatch(ev.loop); } - if (s == NATS_OK) - { - event_base_dispatch(evLoop); - - THREAD_JOIN(pub); - s = info.status; - } + // Cleanup. + nats_DestroyConnection(conn); + nats_Shutdown(); + event_base_free(ev.loop); + libevent_global_shutdown(); - if (s == NATS_OK) - { - printPerf("Sent"); - } - else + if (s != NATS_OK) { printf("Error: %d - %s\n", s, natsStatus_GetText(s)); nats_PrintLastErrorStack(stderr); + return 1; } - - // Destroy all our objects to avoid report of memory leak - natsSubscription_Destroy(sub); - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - if (evLoop != NULL) - event_base_free(evLoop); - - // To silence reports of memory still in used with valgrind - nats_Close(); - libevent_global_shutdown(); - return 0; } diff --git a/examples/libevent-sub.c b/examples/libevent-sub.c deleted file mode 100644 index 63ef072d3..000000000 --- a/examples/libevent-sub.c +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2016-2018 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 "adapters/libevent.h" -#include "examples.h" - -static const char *usage = ""\ -"-gd use global message delivery thread pool\n" \ -"-count number of expected messages\n"; - -static void -onMsg(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - if (print) - printf("Received msg: %s - %.*s\n", - natsMsg_GetSubject(msg), - natsMsg_GetDataLength(msg), - natsMsg_GetData(msg)); - - natsMsg_Destroy(msg); - - if (start == 0) - start = nats_Now(); - - // We should be using a mutex to protect those variables since - // they are used from the subscription's delivery and the main - // threads. For demo purposes, this is fine. - if (++count == total) - { - elapsed = nats_Now() - start; - - natsConnection_Close(nc); - } -} - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsStatus s = NATS_OK; - struct event_base *evLoop= NULL; - - nats_Open(-1); - - opts = parseArgs(argc, argv, usage); - - printf("Listening on '%s'.\n", subj); - - // One time initialization of things that we need. - natsLibevent_Init(); - - // Create a loop. - evLoop = event_base_new(); - if (evLoop == NULL) - s = NATS_ERR; - - // Indicate which loop and callbacks to use once connected. - if (s == NATS_OK) - s = natsOptions_SetEventLoop(opts, (void*) evLoop, - natsLibevent_Attach, - natsLibevent_Read, - natsLibevent_Write, - natsLibevent_Detach); - - if (s == NATS_OK) - s = natsConnection_Connect(&conn, opts); - - if (s == NATS_OK) - s = natsConnection_Subscribe(&sub, conn, subj, onMsg, NULL); - - // For maximum performance, set no limit on the number of pending messages. - if (s == NATS_OK) - s = natsSubscription_SetPendingLimits(sub, -1, -1); - - // Run the event loop. - // This call will return when the connection is closed (either after - // receiving all messages, or disconnected and unable to reconnect). - if (s == NATS_OK) - event_base_dispatch(evLoop); - - if (s == NATS_OK) - { - printPerf("Received"); - } - else - { - printf("Error: %d - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - } - - // Destroy all our objects to avoid report of memory leak - natsSubscription_Destroy(sub); - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - if (evLoop != NULL) - event_base_free(evLoop); - - // To silence reports of memory still in used with valgrind - nats_Close(); - libevent_global_shutdown(); - - return 0; -} diff --git a/examples/libuv-pub.c b/examples/libuv-pub.c deleted file mode 100644 index a8a356ae9..000000000 --- a/examples/libuv-pub.c +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2016-2018 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 "adapters/libuv.h" -#include "examples.h" - -static const char *usage = ""\ -"-txt text to send (default is 'hello')\n" \ -"-count number of messages to send\n"; - -typedef struct -{ - natsConnection *conn; - natsStatus status; - -} threadInfo; - -static void -pubThread(void *arg) -{ - threadInfo *info = (threadInfo*) arg; - natsStatus s = NATS_OK; - - for (count = 0; (s == NATS_OK) && (count < total); count++) - s = natsConnection_PublishString(info->conn, subj, payload); - - if (s == NATS_OK) - s = natsConnection_Flush(info->conn); - - natsConnection_Close(info->conn); - - info->status = s; - - // Since this is a user-thread, call this function to release - // possible thread-local memory allocated by the library. - nats_ReleaseThreadMemory(); -} - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsStatus s = NATS_OK; - uv_loop_t *uvLoop= NULL; - uv_thread_t pub; - threadInfo info; - - opts = parseArgs(argc, argv, usage); - - printf("Sending %" PRId64 " messages to subject '%s'\n", total, subj); - - // One time initialization of things that we need. - natsLibuv_Init(); - - // Create a loop. - uvLoop = uv_default_loop(); - if (uvLoop != NULL) - { - // Libuv is not thread-safe. Almost all calls to libuv need to - // occur from the thread where the loop is running. NATS library - // may have to call into the event loop from different threads. - // This call allows natsLibuv APIs to know if they are executing - // from the event loop thread or not. - natsLibuv_SetThreadLocalLoop(uvLoop); - } - else - { - s = NATS_ERR; - } - - // Indicate which loop and callbacks to use once connected. - if (s == NATS_OK) - s = natsOptions_SetEventLoop(opts, (void*) uvLoop, - natsLibuv_Attach, - natsLibuv_Read, - natsLibuv_Write, - natsLibuv_Detach); - - if (s == NATS_OK) - s = natsConnection_Connect(&conn, opts); - - if (s == NATS_OK) - start = nats_Now(); - - if (s == NATS_OK) - { - info.conn = conn; - info.status = NATS_OK; - - if (uv_thread_create(&pub, pubThread, (void*) &info) != 0) - s = NATS_ERR; - } - - if (s == NATS_OK) - { - uv_run(uvLoop, UV_RUN_DEFAULT); - - uv_thread_join(&pub); - s = info.status; - } - - if (s == NATS_OK) - { - printPerf("Sent"); - } - else - { - printf("Error: %d - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - } - - // Destroy all our objects to avoid report of memory leak - natsSubscription_Destroy(sub); - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - if (uvLoop != NULL) - uv_loop_close(uvLoop); - - // To silence reports of memory still in used with valgrind - nats_Close(); - - return 0; -} diff --git a/examples/libuv-sub.c b/examples/libuv-sub.c deleted file mode 100644 index f0ce356ba..000000000 --- a/examples/libuv-sub.c +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2016-2018 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 "adapters/libuv.h" -#include "examples.h" - -static const char *usage = ""\ -"-gd use global message delivery thread pool\n" \ -"-count number of expected messages\n"; - -static void -onMsg(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - if (print) - printf("Received msg: %s - %.*s\n", - natsMsg_GetSubject(msg), - natsMsg_GetDataLength(msg), - natsMsg_GetData(msg)); - - natsMsg_Destroy(msg); - - if (start == 0) - start = nats_Now(); - - // We should be using a mutex to protect those variables since - // they are used from the subscription's delivery and the main - // threads. For demo purposes, this is fine. - if (++count == total) - { - elapsed = nats_Now() - start; - - natsConnection_Close(nc); - } -} - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsStatus s = NATS_OK; - uv_loop_t *uvLoop= NULL; - - opts = parseArgs(argc, argv, usage); - - printf("Listening on '%s'.\n", subj); - - // One time initialization of things that we need. - natsLibuv_Init(); - - // Create a loop. - uvLoop = uv_default_loop(); - if (uvLoop != NULL) - { - // Libuv is not thread-safe. Almost all calls to libuv need to - // occur from the thread where the loop is running. NATS library - // may have to call into the event loop from different threads. - // This call allows natsLibuv APIs to know if they are executing - // from the event loop thread or not. - natsLibuv_SetThreadLocalLoop(uvLoop); - } - else - { - s = NATS_ERR; - } - - // Indicate which loop and callbacks to use once connected. - if (s == NATS_OK) - s = natsOptions_SetEventLoop(opts, (void*) uvLoop, - natsLibuv_Attach, - natsLibuv_Read, - natsLibuv_Write, - natsLibuv_Detach); - - if (s == NATS_OK) - s = natsConnection_Connect(&conn, opts); - - if (s == NATS_OK) - s = natsConnection_Subscribe(&sub, conn, subj, onMsg, NULL); - - // For maximum performance, set no limit on the number of pending messages. - if (s == NATS_OK) - s = natsSubscription_SetPendingLimits(sub, -1, -1); - - // Run the event loop. - // This call will return when the connection is closed (either after - // receiving all messages, or disconnected and unable to reconnect). - if (s == NATS_OK) - { - uv_run(uvLoop, UV_RUN_DEFAULT); - } - - if (s == NATS_OK) - { - printPerf("Received"); - } - else - { - printf("Error: %d - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - } - - // Destroy all our objects to avoid report of memory leak - natsSubscription_Destroy(sub); - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - if (uvLoop != NULL) - uv_loop_close(uvLoop); - - // To silence reports of memory still in used with valgrind - nats_Close(); - - return 0; -} diff --git a/examples/micro-arithmetics.c b/examples/micro-arithmetics.c deleted file mode 100644 index c3e8f5ee4..000000000 --- a/examples/micro-arithmetics.c +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2023 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 "examples.h" -#include "micro_args.h" - -// Sequence NATS microservice example. -// -// This example illustrates multiple NATS microservices communicating with each -// other. Please see the main microservice, micro-sequence.c for a more detailed -// explanation. -// -// This specific microservice implements add, multiply, and divide operations. - -// arithmeticsOp is a type for a C function that implements am operation: add, -// multiply, divide. -typedef void (*arithmeticsOP)(long double *result, long double a1, long double a2); - -// handle_arithmetics_op is a helper function that wraps an implementation of an -// operation into a request handler. -static microError * -handle_arithmetics_op(microRequest *req, arithmeticsOP op) -{ - microError *err = NULL; - microArgs *args = NULL; - long double a1 = 0, a2 = 0, result = 0; - char buf[1024]; - int len = 0; - - err = micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req)); - if ((err == NULL) && (microArgs_Count(args) != 2)) - { - err = micro_Errorf("invalid number of arguments, expected 2 got %d", microArgs_Count(args)); - } - if (err == NULL) - err = microArgs_GetFloat(&a1, args, 0); - if (err == NULL) - err = microArgs_GetFloat(&a2, args, 1); - if (err == NULL) - op(&result, a1, a2); - if (err == NULL) - len = snprintf(buf, sizeof(buf), "%Lf", result); - if (err == NULL) - err = microRequest_Respond(req, buf, len); - - microArgs_Destroy(args); - return microError_Wrapf(err, "failed to handle arithmetics operation"); -} - -static void add(long double *result, long double a1, long double a2) -{ - *result = a1 + a2; -} - -static void divide(long double *result, long double a1, long double a2) -{ - *result = a1 / a2; -} - -static void multiply(long double *result, long double a1, long double a2) -{ - *result = a1 * a2; -} - -// request handlers for each operation. -static microError *handle_add(microRequest *req) { return handle_arithmetics_op(req, add); } -static microError *handle_divide(microRequest *req) { return handle_arithmetics_op(req, divide); } -static microError *handle_multiply(microRequest *req) { return handle_arithmetics_op(req, multiply); } - -// main is the main entry point for the microservice. -int main(int argc, char **argv) -{ - natsStatus s = NATS_OK; - microError *err = NULL; - natsConnection *conn = NULL; - natsOptions *opts = NULL; - microService *m = NULL; - microGroup *g = NULL; - char errorbuf[1024]; - - microServiceConfig cfg = { - .Description = "Arithmetic operations - NATS microservice example in C", - .Name = "c-arithmetics", - .Version = "1.0.0", - }; - microEndpointConfig add_cfg = { - .Name = "add", - .Handler = handle_add, - }; - microEndpointConfig divide_cfg = { - .Name = "divide", - .Handler = handle_divide, - }; - microEndpointConfig multiply_cfg = { - .Name = "multiply", - .Handler = handle_multiply, - }; - - // Connect to NATS server - opts = parseArgs(argc, argv, ""); - s = natsConnection_Connect(&conn, opts); - if (s != NATS_OK) - { - printf("Error: %u - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - natsOptions_Destroy(opts); - return 1; - } - - // Create the Microservice that listens on nc. - err = micro_AddService(&m, conn, &cfg); - - // Add the endpoints for the functions. - if (err == NULL) - microService_AddGroup(&g, m, "op"); - if (err == NULL) - err = microGroup_AddEndpoint(g, &add_cfg); - if (err == NULL) - err = microGroup_AddEndpoint(g, &multiply_cfg); - if (err == NULL) - err = microGroup_AddEndpoint(g, ÷_cfg); - - // Run the service, until stopped. - if (err == NULL) - err = microService_Run(m); - - // Cleanup. - microService_Destroy(m); - natsOptions_Destroy(opts); - natsConnection_Destroy(conn); - if (err != NULL) - { - printf("Error: %s\n", microError_String(err, errorbuf, sizeof(errorbuf))); - microError_Destroy(err); - return 1; - } - return 0; -} diff --git a/examples/micro-func.c b/examples/micro-func.c deleted file mode 100644 index 17402bd09..000000000 --- a/examples/micro-func.c +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright 2023 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 "examples.h" -#include "micro_args.h" - -// Sequence NATS microservice example. -// -// This example illustrates multiple NATS microservices communicating with each -// other. Please see the main microservice, micro-sequence.c for a more detailed -// explanation. -// -// This specific microservice implements factorial, fibonacci, and power2 -// functions. Instead of performing arithmetic operations locally, we call the -// arithmetics microservice to perform the operations. - -// functionHandler is a type for a C function that implements a "function", i.e. -// power2, factorial, etc. -typedef microError *(*functionHandler)(long double *result, natsConnection *conn, int n); - -// callArithmetics is a helper function that calls the arithmetics microservice. -static microError * -call_arithmetics(long double *result, natsConnection *nc, const char *subject, long double a1, long double a2) -{ - microError *err = NULL; - microClient *client = NULL; - natsMsg *response = NULL; - microArgs *args = NULL; - char buf[1024]; - int len; - - err = micro_NewClient(&client, nc, NULL); - if (err == NULL) - len = snprintf(buf, sizeof(buf), "%Lf %Lf", a1, a2); - if (err == NULL) - err = microClient_DoRequest(&response, client, subject, buf, len); - if (err == NULL) - err = micro_ParseArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response)); - if (err == NULL) - err = microArgs_GetFloat(result, args, 0); - - microClient_Destroy(client); - natsMsg_Destroy(response); - return err; -} - -// factorial implements the factorial(N) function. Calls the arithmetics service -// for all multiplications. -static microError * -factorial(long double *result, natsConnection *nc, int n) -{ - microError *err = NULL; - int i; - - if (n < 1) - return micro_Errorf("n=%d. must be greater than 0", n); - - *result = 1; - for (i = 1; i <= n; i++) - { - err = call_arithmetics(result, nc, "op.multiply", *result, i); - if (err != NULL) - return err; - } - return NULL; -} - -// fibonacci implements the fibonacci(N) function. Calls the arithmetics service -// for all additions. -static microError * -fibonacci(long double *result, natsConnection *nc, int n) -{ - microError *err = NULL; - int i; - long double n1, n2; - - if (n < 0) - return micro_Errorf("n=%d. must be non-negative", n); - - if (n < 2) - { - *result = n; - return NULL; - } - - for (i = 1, n1 = 0, n2 = 1; i <= n; i++) - { - err = call_arithmetics(result, nc, "op.add", n1, n2); - if (err != NULL) - return err; - n1 = n2; - n2 = *result; - } - return NULL; -} - -// power2 implements the 2**N function. Calls the arithmetics service -// for all multiplications. -static microError *power2(long double *result, natsConnection *nc, int n) -{ - microError *err = NULL; - int i; - - if (n < 1) - return micro_Errorf("n=%d. must be greater than 0", n); - - *result = 1; - for (i = 1; i <= n; i++) - { - err = call_arithmetics(result, nc, "op.multiply", *result, 2); - if (err != NULL) - return err; - } - return NULL; -} - -// handle_function_op is a helper function that wraps an implementation function -// like factorial, fibonacci, etc. into a request handler. -static microError * -handle_function_op(microRequest *req, functionHandler op) -{ - microError *err = NULL; - microArgs *args = NULL; - int n = 0; - long double result; - char buf[1024]; - int len = 0; - - err = micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req)); - if ((err == NULL) && (microArgs_Count(args) != 1)) - { - err = micro_Errorf("Invalid number of arguments, expected 1 got %d", microArgs_Count(args)); - } - if (err == NULL) - err = microArgs_GetInt(&n, args, 0); - if (err == NULL) - err = op(&result, microRequest_GetConnection(req), n); - if (err == NULL) - len = snprintf(buf, sizeof(buf), "%Lf", result); - if (err == NULL) - err = microRequest_Respond(req, buf, len); - - microArgs_Destroy(args); - return err; -} - -// handle_... are the request handlers for each function. -static microError *handle_factorial(microRequest *req) { return handle_function_op(req, factorial); } -static microError *handle_fibonacci(microRequest *req) { return handle_function_op(req, fibonacci); } -static microError *handle_power2(microRequest *req) { return handle_function_op(req, power2); } - -// main is the main entry point for the microservice. -int main(int argc, char **argv) -{ - natsStatus s = NATS_OK; - microError *err = NULL; - natsOptions *opts = NULL; - natsConnection *conn = NULL; - microService *m = NULL; - microGroup *g = NULL; - char errorbuf[1024]; - - microServiceConfig cfg = { - .Description = "Functions - NATS microservice example in C", - .Name = "c-functions", - .Version = "1.0.0", - }; - microEndpointConfig factorial_cfg = { - .Name = "factorial", - .Handler = handle_factorial, - }; - microEndpointConfig fibonacci_cfg = { - .Name = "fibonacci", - .Handler = handle_fibonacci, - }; - microEndpointConfig power2_cfg = { - .Name = "power2", - .Handler = handle_power2, - }; - - // Connect to NATS server - opts = parseArgs(argc, argv, ""); - s = natsConnection_Connect(&conn, opts); - if (s != NATS_OK) - { - printf("Error: %u - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - natsOptions_Destroy(opts); - return 1; - } - - // Create the Microservice that listens on nc. - err = micro_AddService(&m, conn, &cfg); - - // Add the endpoints for the functions. - if (err == NULL) - err = microService_AddGroup(&g, m, "f"); - if (err == NULL) - err = microGroup_AddEndpoint(g, &factorial_cfg); - if (err == NULL) - err = microGroup_AddEndpoint(g, &fibonacci_cfg); - if (err == NULL) - err = microGroup_AddEndpoint(g, &power2_cfg); - - // Run the service, until stopped. - if (err == NULL) - err = microService_Run(m); - - // Cleanup. - microService_Destroy(m); - natsOptions_Destroy(opts); - natsConnection_Destroy(conn); - if (err != NULL) - { - printf("Error: %s\n", microError_String(err, errorbuf, sizeof(errorbuf))); - microError_Destroy(err); - return 1; - } - return 0; -} diff --git a/examples/micro-hello.c b/examples/micro-hello.c deleted file mode 100644 index 3a45e9faa..000000000 --- a/examples/micro-hello.c +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2023 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 "examples.h" -#include "micro_args.h" - -// Hello World! NATS microservice example. -// -// Requires NATS server and CLI, and the nats.c examples fully built. See -// https://github.com/nats-io/nats.c#building -// -// RUN: -// ```sh -// $NATS_SERVER & # NATS_SERVER points to the NATS server binary -// nats_pid=$! -// sleep 2 # wait for server to start -// ./examples/nats-micro-hello & -// hello_pid=$! -// sleep 2 # wait for microservice to start -// nats request 'hello' '' -// kill $hello_pid $nats_pid -// ``` -// -// OUTPUT: -// ``` -// 06:34:57 Sending request on "hello" -// 06:34:57 Received with rtt 1.08ms -// Hello, World! -// ``` - -#define HELLO "Hello, World!" - -static microError * -handle(microRequest *req) -{ - return microRequest_Respond(req, HELLO, sizeof(HELLO)); -} - -int main(int argc, char **argv) -{ - natsStatus s = NATS_OK; - microError *err = NULL; - natsConnection *conn = NULL; - natsOptions *opts = NULL; - microService *m = NULL; - char errorbuf[1024]; - - microEndpointConfig hello_cfg = { - .Name = "hello", - .Handler = handle, - }; - microServiceConfig cfg = { - .Description = "Hello World! - NATS microservice example in C", - .Name = "c-hello", - .Version = "1.0.0", - .Endpoint = &hello_cfg, - }; - - // Connect and start the services - opts = parseArgs(argc, argv, ""); - s = natsConnection_Connect(&conn, opts); - if (s != NATS_OK) - { - printf("Error: %u - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - natsOptions_Destroy(opts); - return 1; - } - - err = micro_AddService(&m, conn, &cfg); - if (err == NULL) - err = microService_Run(m); - - microService_Destroy(m); - natsOptions_Destroy(opts); - natsConnection_Destroy(conn); - if (err != NULL) - { - printf("Error: %s\n", microError_String(err, errorbuf, sizeof(errorbuf))); - microError_Destroy(err); - return 1; - } - return 0; -} diff --git a/examples/micro-sequence.c b/examples/micro-sequence.c deleted file mode 100644 index 2e530b2e2..000000000 --- a/examples/micro-sequence.c +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2023 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 "examples.h" -#include "micro_args.h" - -// Sequence NATS microservice example. -// -// This example illustrates multiple NATS microservices communicating with each -// other. -// -// The main service (c-sequence) calculates the sum of 1/f(1) + 1/f(2)... up to -// N (included). It exposes one (default) endpoint, "sequence". The inputs are -// f (the function name) and N. name, can be "factorial", "fibonacci", or -// "power2"). -// -// c-sequence parses the request, then calculates the sequence by calling the -// c-functions microservice to calculate f(1), f(2), etc. The c-functions -// service in turn uses c-arithmetics microservice for all arithmetic -// operations. -// -// Requires NATS server and CLI, and the nats.c examples fully built. See -// https://github.com/nats-io/nats.c#building -// -// RUN: -// ```sh -// $NATS_SERVER & # NATS_SERVER points to the NATS server binary -// nats_pid=$! -// sleep 2 # wait for server to start -// ./examples/nats-micro-sequence & -// sequence_pid=$! -// ./examples/nats-micro-func & -// func_pid=$! -// ./examples/nats-micro-arithmetics & -// arithmetics_pid=$! -// sleep 2 # wait for microservice to start -// nats request -r 'sequence' '"factorial" 10' -// nats request -r 'sequence' '"power2" 10' -// nats request -r 'sequence' '"fibonacci" 10' -// kill $sequence_pid $func_pid $arithmetics_pid $nats_pid -// ``` -// -// OUTPUT: -// ``` -// 2.718282 -// 1.999023 -// 3.341705 -// ``` - -static microError * -call_function(long double *result, natsConnection *nc, const char *subject, int n) -{ - microError *err = NULL; - microClient *client = NULL; - natsMsg *response = NULL; - microArgs *args = NULL; - char buf[256]; - char sbuf[256]; - int len; - - len = snprintf(buf, sizeof(buf), "%d", n); - snprintf(sbuf, sizeof(sbuf), "f.%s", subject); - err = micro_NewClient(&client, nc, NULL); - if (err == NULL) - err = microClient_DoRequest(&response, client, sbuf, buf, len); - if (err == NULL) - err = micro_ParseArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response)); - if (err == NULL) - err = microArgs_GetFloat(result, args, 0); - - microClient_Destroy(client); - natsMsg_Destroy(response); - return err; -} - -// calculates the sum of X/f(1) + X/f(2)... up to N (included). The inputs are X -// (float), f name (string), and N (int). E.g.: '10.0 "power2" 10' will -// calculate 10/2 + 10/4 + 10/8 + 10/16 + 10/32 + 10/64 + 10/128 + 10/256 + -// 10/512 + 10/1024 = 20.998046875 -static microError *handle_sequence(microRequest *req) -{ - microError *err = NULL; - natsConnection *nc = microRequest_GetConnection(req); - microArgs *args = NULL; - int n = 0; - int i; - const char *function = NULL; - long double initialValue = 1.0; - long double value = 1.0; - long double denominator = 0; - char result[64]; - int result_len = 0; - - err = micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req)); - if ((err == NULL) && (microArgs_Count(args) != 2)) - { - err = micro_Errorf("Invalid number of arguments, expected 2 got %d", microArgs_Count(args)); - } - - if (err == NULL) - err = microArgs_GetString(&function, args, 0); - if ((err == NULL) && - (strcmp(function, "factorial") != 0) && - (strcmp(function, "power2") != 0) && - (strcmp(function, "fibonacci") != 0)) - { - err = micro_Errorf("Invalid function name '%s', must be 'factorial', 'power2', or 'fibonacci'", function); - } - if (err == NULL) - err = microArgs_GetInt(&n, args, 1); - if ((err == NULL) && (n < 1)) - { - err = micro_Errorf("Invalid number of iterations %d, must be at least 1", n); - } - - for (i = 1; (err == NULL) && (i <= n); i++) - { - if (err == NULL) - err = call_function(&denominator, nc, function, i); - if ((err == NULL) && (denominator == 0)) - { - err = micro_Errorf("division by zero at step %d", i); - } - if (err == NULL) - value = value + initialValue / denominator; - } - - if (err == NULL) - result_len = snprintf(result, sizeof(result), "%Lf", value); - if (err == NULL) - err = microRequest_Respond(req, result, result_len); - - microArgs_Destroy(args); - return err; -} - -int main(int argc, char **argv) -{ - natsStatus s = NATS_OK; - microError *err = NULL; - natsConnection *conn = NULL; - natsOptions *opts = NULL; - microService *m = NULL; - char errorbuf[1024]; - - microEndpointConfig sequence_cfg = { - .Subject = "sequence", - .Name = "sequence-service", - .Handler = handle_sequence, - }; - microServiceConfig cfg = { - .Description = "Sequence adder - NATS microservice example in C", - .Name = "c-sequence", - .Version = "1.0.0", - .Endpoint = &sequence_cfg, - }; - - opts = parseArgs(argc, argv, ""); - s = natsConnection_Connect(&conn, opts); - if (s != NATS_OK) - { - printf("Error: %u - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - natsOptions_Destroy(opts); - return 1; - } - - err = micro_AddService(&m, conn, &cfg); - if (err == NULL) - err = microService_Run(m); - - microService_Destroy(m); - natsOptions_Destroy(opts); - natsConnection_Destroy(conn); - if (err != NULL) - { - printf("Error: %s\n", microError_String(err, errorbuf, sizeof(errorbuf))); - microError_Destroy(err); - return 1; - } - return 0; -} diff --git a/examples/micro-stats.c b/examples/micro-stats.c deleted file mode 100644 index c50e07648..000000000 --- a/examples/micro-stats.c +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2023 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 -#include - -#include "examples.h" -#include "micro_args.h" - -typedef struct service_state_s -{ - // in a real application this should be protected by a mutex. In this - // example, the main control flow provides synchronization. - int odd_count; -} service_state_t; - -static microError * -handle_default(microRequest *req) -{ - char buf[64]; - const char *response = "odd"; - int n; - service_state_t *state = microRequest_GetServiceState(req); - - snprintf(buf, sizeof(buf), "%.*s", microRequest_GetDataLength(req), microRequest_GetData(req)); - n = atoi(buf); - if (n % 2 != 0) - { - // this should be protected by a mutex in a real application. - state->odd_count++; - - response = "even"; - } - return microRequest_Respond(req, response, strlen(response)); -} - -static microError * -handle_stats(microRequest *req) -{ - microError *err = NULL; - microServiceStats *stats = NULL; - char buf[2048]; - service_state_t *service_state = microRequest_GetServiceState(req); - int total, custom, len; - - err = microService_GetStats(&stats, microRequest_GetService(req)); - if (err != NULL) - return err; - - total = (int) stats->Endpoints[0].NumRequests; - custom = service_state->odd_count; - len = snprintf(buf, sizeof(buf), - "{\"total\":%d,\"odd\":%d}", total, custom); - return microRequest_Respond(req, buf, len); -} - -static microError * -run_example(natsConnection *conn, microRequestHandler stats_handler, char *buf, int buf_cap) -{ - microError *err = NULL; - microService *m = NULL; - microClient *c = NULL; - service_state_t service_state = { - .odd_count = 0, - }; - microEndpointConfig default_cfg = { - .Name = "default", - .Handler = handle_default, - }; - microServiceConfig cfg = { - .Name = "c-stats", - .Description = "NATS microservice in C with a custom stats handler", - .Version = "1.0.0", - .Endpoint = &default_cfg, - .StatsHandler = stats_handler, - .State = &service_state, - }; - int i; - int len; - natsMsg *resp = NULL; - natsMsg *stats_resp = NULL; - - err = micro_AddService(&m, conn, &cfg); - if (err == NULL) - err = micro_NewClient(&c, conn, NULL); - for (i = 0; (err == NULL) && (i < 10); i++) - { - len = snprintf(buf, buf_cap, "%d", i); - if (err == NULL) - err = microClient_DoRequest(&resp, c, "default", buf, len); - if (err == NULL) - natsMsg_Destroy(resp); - } - - if (err == NULL) - err = microClient_DoRequest(&stats_resp, c, "$SRV.STATS.c-stats", "", 0); - if (err == NULL) - { - len = natsMsg_GetDataLength(stats_resp); - if (len > buf_cap - 1) - { - len = buf_cap - 1; - } - memcpy(buf, natsMsg_GetData(stats_resp), len); - buf[len] = '\0'; - - natsMsg_Destroy(stats_resp); - } - - microService_Destroy(m); - microClient_Destroy(c); - return err; -} - -int main(int argc, char **argv) -{ - microError *err = NULL; - natsOptions *opts = parseArgs(argc, argv, ""); - natsConnection *conn = NULL; - char buf[2048]; - - err = micro_ErrorFromStatus( - natsConnection_Connect(&conn, opts)); - if (err == NULL) - err = run_example(conn, NULL, buf, sizeof(buf)); - if (err == NULL) - printf("Default stats response:\n----\n%s\n----\n\n", buf); - - if (err == NULL) - err = run_example(conn, handle_stats, buf, sizeof(buf)); - if (err == NULL) - printf("Custom stats response:\n----\n%s\n----\n\n", buf); - - if (err != NULL) - { - fprintf(stderr, "Error: %s\n", microError_String(err, buf, sizeof(buf))); - } - - natsOptions_Destroy(opts); - natsConnection_Destroy(conn); - microError_Destroy(err); - return err == NULL ? 0 : 1; -} diff --git a/examples/micro_args.h b/examples/micro_args.h deleted file mode 100644 index 088466ba8..000000000 --- a/examples/micro_args.h +++ /dev/null @@ -1,366 +0,0 @@ -// Copyright 2023 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. - -#ifndef MICRO_ARGS_H_ -#define MICRO_ARGS_H_ - -/** - * Request unmarshaled as "arguments", a space-separated list of numbers and strings. - * TODO document the interface. - */ -typedef struct args_s microArgs; - -struct args_s -{ - void **args; - int count; -}; - - -static inline microError *new_args(microArgs **ptr, int n) -{ - *ptr = calloc(1, sizeof(microArgs)); - if (*ptr == NULL) - return micro_ErrorOutOfMemory; - - (*ptr)->args = calloc(n, sizeof(void *)); - if ((*ptr)->args == NULL) - { - free(*ptr); - return micro_ErrorOutOfMemory; - } - - (*ptr)->count = n; - return NULL; -} - -typedef enum parserState -{ - NewArg = 0, - NumberArg, -} parserState; - -// decodes the rest of a string into a pre-allocated buffer of sufficient -// length, or just calculates the needed buffer size. The opening quote must -// have been already processed by the caller (parse). -static microError * -decode_rest_of_string(char *dup, int *decoded_len, int *i, const char *data, int data_len) -{ - char c; - int len = 0; - bool terminated = false; - bool escape = false; - - for (; !terminated && *i < data_len; (*i)++) - { - c = data[*i]; - switch (c) - { - case '"': - if (escape) - { - // include the escaped quote. - if (dup != NULL) - { - dup[len] = c; - } - len++; - escape = false; - } - else - { - // end of quoted string. - terminated = true; - } - break; - - case '\\': - if (!escape) - { - escape = true; - } - else - { - // include the escaped backslash. - if (dup != NULL) - { - dup[len] = c; - } - len++; - escape = false; - } - break; - - default: - if (dup != NULL) - { - dup[len] = c; - } - len++; - escape = false; - break; - } - } - if (!terminated) - { - return micro_Errorf("a quoted string is not properly terminated"); - } - - *decoded_len = len; - return NULL; -} - -static microError * -decode_and_dupe_rest_of_string(char **dup, int *i, const char *data, int data_len) -{ - microError *err = NULL; - int start = *i; - int decoded_len = 0; - - err = decode_rest_of_string(NULL, &decoded_len, i, data, data_len); - if (err != NULL) - { - return err; - } - if (dup == NULL) - { - // nothing else to do - the string has been scanned and validated. - return NULL; - } - - *i = start; - - *dup = calloc(decoded_len + 1, sizeof(char)); - if (*dup == NULL) - { - return micro_ErrorOutOfMemory; - } - - // no need to check for error the 2nd time, we already know the string is - // valid. - decode_rest_of_string(*dup, &decoded_len, i, data, data_len); - (*dup)[decoded_len] = 0; - return NULL; -} - -static microError * -parse(void **args, int *args_len, const char *data, int data_len) -{ - int i = 0; - microError *err = NULL; - char c; - int n = 0; - parserState state = NewArg; - char numbuf[64]; - int num_len = 0; - bool is_float = false; - -#define EOS 0 - for (; i < data_len + 1;) - { - c = (i < data_len) ? data[i] : EOS; - - switch (state) - { - case NewArg: - switch (c) - { - case EOS: - case ' ': - i++; - break; - - case '"': - i++; // consume the opening quote. - err = decode_and_dupe_rest_of_string((char **)(&args[n]), &i, data, data_len); - if (err != NULL) - { - return err; - } - n++; - break; - - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - case '+': - case '.': - state = NumberArg; - num_len = 0; - numbuf[num_len++] = c; - is_float = (c == '.'); - i++; - break; - - default: - return micro_Errorf("unexpected '%c', an argument must be a number or a quoted string", c); - } - break; - - case NumberArg: - switch (c) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - case '+': - case '.': - case 'e': - case 'E': - case ',': - numbuf[num_len] = c; - num_len++; - is_float = is_float || (c == '.') || (c == 'e') || (c == 'E'); - i++; - break; - - case EOS: - case ' ': - if (args != NULL) - { - numbuf[num_len] = 0; - if (is_float) - { - args[n] = calloc(1, sizeof(long double)); - if (args[n] == NULL) - { - return micro_ErrorOutOfMemory; - } - *(long double *)args[n] = strtold(numbuf, NULL); - } - else - { - args[n] = calloc(1, sizeof(int)); - if (args[n] == NULL) - { - return micro_ErrorOutOfMemory; - } - *(int *)args[n] = atoi(numbuf); - } - } - n++; - i++; - state = NewArg; - break; - - default: - return micro_Errorf("unexpected '%c', a number must be followed by a space", c); - } - break; - - default: - return micro_Errorf("unreachable: wrong state for a ' ', expected NewArg or NumberArg, got %d", state); - } - } - - *args_len = n; - return NULL; -} - -static inline void microArgs_Destroy(microArgs *args) -{ - int i; - - if (args == NULL) - return; - - for (i = 0; i < args->count; i++) - { - free(args->args[i]); - } - free(args->args); - free(args); -} - -static microError * -micro_ParseArgs(microArgs **ptr, const char *data, int data_len) -{ - microError *err = NULL; - microArgs *args = NULL; - int n = 0; - - if ((ptr == NULL) || (data == NULL) || (data_len < 0)) - return microError_Wrapf(micro_ErrorInvalidArg, "failed to parse args"); - - if (err == NULL) - err = parse(NULL, &n, data, data_len); - if (err == NULL) - err = new_args(&args, n); - if (err == NULL) - err = parse(args->args, &n, data, data_len); - - if (err != NULL) - { - microArgs_Destroy(args); - return microError_Wrapf(err, "failed to parse args"); - } - *ptr = args; - return NULL; -} - -static inline int microArgs_Count(microArgs *args) -{ - if (args == NULL) - return 0; - - return args->count; -} - -static inline microError * -microArgs_GetInt(int *val, microArgs *args, int index) -{ - if ((args == NULL) || (index < 0) || (index >= args->count) || (val == NULL)) - return micro_ErrorInvalidArg; - - *val = *((int *)args->args[index]); - return NULL; -} - -static inline microError * -microArgs_GetFloat(long double *val, microArgs *args, int index) -{ - if ((args == NULL) || (index < 0) || (index >= args->count) || (val == NULL)) - return micro_ErrorInvalidArg; - - *val = *((long double *)args->args[index]); - return NULL; -} - -static inline microError * -microArgs_GetString(const char **val, microArgs *args, int index) -{ - if ((args == NULL) || (index < 0) || (index >= args->count) || (val == NULL)) - return micro_ErrorInvalidArg; - - *val = (const char *)args->args[index]; - return NULL; -} - - -#endif /* MICRO_H_ */ diff --git a/examples/publisher.c b/examples/publisher.c deleted file mode 100644 index 2aa521fa0..000000000 --- a/examples/publisher.c +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2015-2018 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 "examples.h" - -static const char *usage = ""\ -"-txt text to send (default is 'hello')\n" \ -"-count number of messages to send\n"; - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsStatistics *stats = NULL; - natsOptions *opts = NULL; - natsStatus s; - int dataLen=0; - - opts = parseArgs(argc, argv, usage); - - printf("Sending %" PRId64 " messages to subject '%s'\n", total, subj); - - s = natsConnection_Connect(&conn, opts); - - if (s == NATS_OK) - s = natsStatistics_Create(&stats); - - if (s == NATS_OK) - start = nats_Now(); - - dataLen = (int) strlen(payload); - for (count = 0; (s == NATS_OK) && (count < total); count++) - s = natsConnection_Publish(conn, subj, (const void*) payload, dataLen); - - if (s == NATS_OK) - s = natsConnection_FlushTimeout(conn, 1000); - - if (s == NATS_OK) - { - printStats(STATS_OUT, conn, NULL, stats); - printPerf("Sent"); - } - else - { - printf("Error: %u - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - } - - // Destroy all our objects to avoid report of memory leak - natsStatistics_Destroy(stats); - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - // To silence reports of memory still in used with valgrind - nats_Close(); - - return 0; -} diff --git a/examples/queuegroup.c b/examples/queuegroup.c deleted file mode 100644 index 42e65430a..000000000 --- a/examples/queuegroup.c +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2015-2018 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 "examples.h" - -static const char *usage = "" \ -"-gd use global message delivery thread pool\n" \ -"-name queue name (default is 'worker')\n" \ -"-count number of expected messages\n"; - -static void -onMsg(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - // If 'print' is on, the server is likely to break the connection - // since the client library will become a slow consumer. - if (print) - printf("Received msg: %s - %.*s\n", - natsMsg_GetSubject(msg), - natsMsg_GetDataLength(msg), - natsMsg_GetData(msg)); - - if (start == 0) - start = nats_Now(); - - // We should be using a mutex to protect those variables since - // they are used from the subscription's delivery and the main - // threads. For demo purposes, this is fine. - if (++count == total) - elapsed = nats_Now() - start; - - natsMsg_Destroy(msg); -} - -static void -asyncCb(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure) -{ - printf("Async error: %u - %s\n", err, natsStatus_GetText(err)); -} - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsStatistics *stats = NULL; - natsMsg *msg = NULL; - natsStatus s; - - opts = parseArgs(argc, argv, usage); - - printf("Listening %ssynchronously on '%s' with name '%s'.\n", - (async ? "a" : ""), subj, name); - - s = natsOptions_SetErrorHandler(opts, asyncCb, NULL); - - if (s == NATS_OK) - s = natsConnection_Connect(&conn, opts); - - if (s == NATS_OK) - { - if (async) - s = natsConnection_QueueSubscribe(&sub, conn, subj, name, onMsg, NULL); - else - s = natsConnection_QueueSubscribeSync(&sub, conn, subj, name); - } - - // For maximum performance, set no limit on the number of pending messages. - if (s == NATS_OK) - s = natsSubscription_SetPendingLimits(sub, -1, -1); - - if (s == NATS_OK) - s = natsSubscription_AutoUnsubscribe(sub, (int) total); - - if (s == NATS_OK) - s = natsStatistics_Create(&stats); - - if ((s == NATS_OK) && async) - { - while (s == NATS_OK) - { - s = printStats(STATS_IN|STATS_COUNT,conn, sub, stats); - - if (count == total) - break; - - if (s == NATS_OK) - nats_Sleep(1000); - } - } - else if (s == NATS_OK) - { - int64_t last = 0; - - for (count = 0; (s == NATS_OK) && (count < total); count++) - { - s = natsSubscription_NextMsg(&msg, sub, 10000); - if (s != NATS_OK) - break; - - if (start == 0) - start = nats_Now(); - - if (nats_Now() - last >= 1000) - { - s = printStats(STATS_IN|STATS_COUNT,conn, sub, stats); - last = nats_Now(); - } - - natsMsg_Destroy(msg); - } - } - - if (s == NATS_OK) - { - printStats(STATS_IN|STATS_COUNT,conn, sub, stats); - printPerf("Received"); - } - else - { - printf("Error: %u - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - } - - // Destroy all our objects to avoid report of memory leak - natsStatistics_Destroy(stats); - natsSubscription_Destroy(sub); - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - // To silence reports of memory still in used with valgrind - nats_Close(); - - return 0; -} diff --git a/examples/replier.c b/examples/replier.c deleted file mode 100644 index 5fe76663d..000000000 --- a/examples/replier.c +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2015-2018 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 "examples.h" - -static const char *usage = "" \ -"-gd use global message delivery thread pool\n" \ -"-sync receive synchronously (default is asynchronous)\n" \ -"-count number of expected requests\n"; - -static void -onMsg(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - if (print) - printf("Received msg: %s - %.*s\n", - natsMsg_GetSubject(msg), - natsMsg_GetDataLength(msg), - natsMsg_GetData(msg)); - - if (start == 0) - start = nats_Now(); - - natsConnection_PublishString(nc, natsMsg_GetReply(msg), - "here's some help"); - - // We should be using a mutex to protect those variables since - // they are used from the subscription's delivery and the main - // threads. For demo purposes, this is fine. - if (++count == total) - elapsed = nats_Now() - start; - - natsMsg_Destroy(msg); -} - -static void -asyncCb(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure) -{ - if (print) - printf("Async error: %u - %s\n", err, natsStatus_GetText(err)); - - natsSubscription_GetDropped(sub, (int64_t*) &dropped); -} - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsStatistics *stats = NULL; - natsMsg *msg = NULL; - natsStatus s; - - opts = parseArgs(argc, argv, usage); - - printf("Listening %ssynchronously for requests on '%s'\n", - (async ? "a" : ""), subj); - - s = natsOptions_SetErrorHandler(opts, asyncCb, NULL); - // Since the replier is sending one message at a time, reduce - // latency by making Publish calls send data right away - // instead of buffering them. - if (s == NATS_OK) - s = natsOptions_SetSendAsap(opts, true); - - if (s == NATS_OK) - s = natsConnection_Connect(&conn, opts); - - if (s == NATS_OK) - { - if (async) - s = natsConnection_Subscribe(&sub, conn, subj, onMsg, NULL); - else - s = natsConnection_SubscribeSync(&sub, conn, subj); - } - - // For maximum performance, set no limit on the number of pending messages. - if (s == NATS_OK) - s = natsSubscription_SetPendingLimits(sub, -1, -1); - - if (s == NATS_OK) - s = natsSubscription_AutoUnsubscribe(sub, (int) total); - - if (s == NATS_OK) - s = natsStatistics_Create(&stats); - - if ((s == NATS_OK) && async) - { - while (s == NATS_OK) - { - s = printStats(STATS_IN|STATS_COUNT,conn, sub, stats); - - if (count + dropped == total) - break; - - if (s == NATS_OK) - nats_Sleep(1000); - } - } - else if (s == NATS_OK) - { - int64_t last = 0; - - for (count = 0; (s == NATS_OK) && (count < total); count++) - { - s = natsSubscription_NextMsg(&msg, sub, 10000); - if (s == NATS_OK) - s = natsConnection_PublishString(conn, - natsMsg_GetReply(msg), - "here's some help"); - if (s == NATS_OK) - { - if (start == 0) - start = nats_Now(); - - if (nats_Now() - last >= 1000) - { - s = printStats(STATS_IN|STATS_COUNT,conn, sub, stats); - last = nats_Now(); - } - } - - natsMsg_Destroy(msg); - } - - if (s == NATS_OK) - s = natsConnection_FlushTimeout(conn, 1000); - } - - if (s == NATS_OK) - { - printStats(STATS_IN|STATS_COUNT,conn, sub, stats); - printPerf("Received"); - } - else - { - printf("Error: %u - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - } - - // Destroy all our objects to avoid report of memory leak - natsStatistics_Destroy(stats); - natsSubscription_Destroy(sub); - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - // To silence reports of memory still in used with valgrind - nats_Close(); - - return 0; -} diff --git a/examples/requestor.c b/examples/requestor.c deleted file mode 100644 index 3278efe4e..000000000 --- a/examples/requestor.c +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2015-2018 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 "examples.h" - -static const char *usage = "" \ -"-txt text to send (default is 'hello')\n" \ -"-count number of requests to send\n"; - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsStatistics *stats = NULL; - natsOptions *opts = NULL; - natsMsg *reply = NULL; - int64_t last = 0; - natsStatus s; - - opts = parseArgs(argc, argv, usage); - - printf("Sending %" PRId64 " requests to subject '%s'\n", total, subj); - - s = natsConnection_Connect(&conn, opts); - - if (s == NATS_OK) - s = natsStatistics_Create(&stats); - - if (s == NATS_OK) - start = nats_Now(); - - for (count = 0; (s == NATS_OK) && (count < total); count++) - { - s = natsConnection_RequestString(&reply, conn, subj, payload, 1000); - if (s != NATS_OK) - break; - - if (print) - { - printf("Received reply: %s - %.*s\n", - natsMsg_GetSubject(reply), - natsMsg_GetDataLength(reply), - natsMsg_GetData(reply)); - } - - if (nats_Now() - last >= 1000) - { - s = printStats(STATS_OUT,conn, NULL, stats); - last = nats_Now(); - } - - natsMsg_Destroy(reply); - } - - if (s == NATS_OK) - s = natsConnection_FlushTimeout(conn, 1000); - - if (s == NATS_OK) - { - printStats(STATS_OUT, conn, NULL, stats); - printPerf("Sent"); - } - else - { - printf("Error: %u - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - } - - // Destroy all our objects to avoid report of memory leak - natsStatistics_Destroy(stats); - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - // To silence reports of memory still in used with valgrind - nats_Close(); - - return 0; -} diff --git a/examples/stan/CMakeLists.txt b/examples/stan/CMakeLists.txt deleted file mode 100644 index 550ce1521..000000000 --- a/examples/stan/CMakeLists.txt +++ /dev/null @@ -1,29 +0,0 @@ -if(NOT NATS_BUILD_EXAMPLES) - return() -endif() - -# We need this directory to build the examples -include_directories(${PROJECT_SOURCE_DIR}/src) -include_directories(${PROJECT_SOURCE_DIR}/examples/stan) - -# Get all the .c files in the examples directory -file(GLOB EXAMPLES_SOURCES RELATIVE ${PROJECT_SOURCE_DIR}/examples/stan *.c) - -# For each file... -foreach(examples_src ${EXAMPLES_SOURCES}) - - # Remove the suffix so that it becomes the executable name - string(REPLACE ".c" "" examplename ${examples_src}) - set(exampleexe "stan-${examplename}") - - # Build the executable - add_executable(${exampleexe} ${PROJECT_SOURCE_DIR}/examples/stan/${examples_src}) - - # Link - if(NATS_BUILD_STATIC_EXAMPLES) - target_link_libraries(${exampleexe} nats_static ${NATS_EXTRA_LIB}) - else() - target_link_libraries(${exampleexe} nats ${NATS_EXTRA_LIB}) - endif() - -endforeach() diff --git a/examples/stan/pub-async.c b/examples/stan/pub-async.c deleted file mode 100644 index 695c44471..000000000 --- a/examples/stan/pub-async.c +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2018 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 "../examples.h" - -static const char *usage = ""\ -"-txt text to send (default is 'hello')\n"; - -typedef struct __myPubMsgInfo -{ - const char *payload; - int size; - char ID[30]; - -} myPubMsgInfo; - -static volatile bool done = false; - -static void -_pubAckHandler(const char *guid, const char *error, void *closure) -{ - myPubMsgInfo *pubMsg = (myPubMsgInfo*) closure; - - printf("Ack handler for message ID=%s Data=%.*s GUID=%s - ", - pubMsg->ID, pubMsg->size, pubMsg->payload, guid); - - if (error != NULL) - printf("Error= %s\n", error); - else - printf("Success!\n"); - - // This is a good place to free the pubMsg info since - // we no longer need it. - free(pubMsg); - - // Notify the main thread that we are done. This is - // not the proper way and you should use some locking. - done = true; -} - -int main(int argc, char **argv) -{ - natsStatus s; - stanConnOptions *connOpts = NULL; - stanConnection *sc = NULL; - myPubMsgInfo *pubMsg = NULL; - - // This example demonstrates the use of the pubAckHandler closure - // to correlate published messages and their acks. - - opts = parseArgs(argc, argv, usage); - - printf("Sending 1 message to channel '%s'\n", subj); - - // Now create STAN Connection Options and set the NATS Options. - s = stanConnOptions_Create(&connOpts); - if (s == NATS_OK) - s = stanConnOptions_SetNATSOptions(connOpts, opts); - - // Create the Connection using the STAN Connection Options - if (s == NATS_OK) - s = stanConnection_Connect(&sc, cluster, clientID, connOpts); - - // Once the connection is created, we can destroy the options - natsOptions_Destroy(opts); - stanConnOptions_Destroy(connOpts); - - // Create an object that represents our message - if (s == NATS_OK) - { - pubMsg = (myPubMsgInfo*) calloc(1, sizeof(myPubMsgInfo)); - if (pubMsg == NULL) - s = NATS_NO_MEMORY; - - if (s == NATS_OK) - { - // Say we want to bind the data that we are going to send - // to some unique ID that we know about this message. - pubMsg->payload = payload; - pubMsg->size = (int)strlen(payload); - snprintf(pubMsg->ID, sizeof(pubMsg->ID), "%s:%d", "xyz", 234); - } - } - // We send the message and pass our message info as the closure - // for the pubAckHandler. - if (s == NATS_OK) - { - s = stanConnection_PublishAsync(sc, subj, pubMsg->payload, pubMsg->size, - _pubAckHandler, (void*) pubMsg); - - // Note that if this call fails, then we need to free the pubMsg - // object here since it won't be passed to the ack handler. - if (s != NATS_OK) - free(pubMsg); - } - - if (s == NATS_OK) - { - while (!done) - nats_Sleep(15); - } - - if (s != NATS_OK) - { - printf("Error: %d - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - } - - // Destroy the connection - stanConnection_Destroy(sc); - - // To silence reports of memory still in-use with valgrind. - nats_Sleep(50); - nats_Close(); - - return 0; -} diff --git a/examples/stan/pub.c b/examples/stan/pub.c deleted file mode 100644 index 6e042abe5..000000000 --- a/examples/stan/pub.c +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2018 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 "../examples.h" - -static const char *usage = ""\ -"-txt text to send (default is 'hello')\n" \ -"-count number of messages to send\n" \ -"-sync publish synchronously (default is async)\n"; - -static volatile int ackCount = 0; -static volatile int errCount = 0; - -static void -_pubAckHandler(const char *guid, const char *error, void *closure) -{ - // This callback can be invoked by different threads for the - // same connection, so access should be protected. For this - // example, we don't. - ackCount++; - if (error != NULL) - { - printf("pub ack for guid:%s error=%s\n", guid, error); - errCount++; - } -} - -static void -connectionLostCB(stanConnection *sc, const char *errTxt, void *closure) -{ - bool *connLost = (bool*) closure; - - printf("Connection lost: %s\n", errTxt); - *connLost = true; -} - -int main(int argc, char **argv) -{ - natsStatus s; - stanConnOptions *connOpts = NULL; - stanConnection *sc = NULL; - int len; - bool connLost = false; - - opts = parseArgs(argc, argv, usage); - len = (int) strlen(payload); - - printf("Sending %" PRId64 " messages to channel '%s'\n", total, subj); - - // Now create STAN Connection Options and set the NATS Options. - s = stanConnOptions_Create(&connOpts); - if (s == NATS_OK) - s = stanConnOptions_SetNATSOptions(connOpts, opts); - - // Set smaller ping intervals - if (s == NATS_OK) - s = stanConnOptions_SetPings(connOpts, 1, 5); - - // Add a callback to be notified if the STAN connection is lost for good. - if (s == NATS_OK) - s = stanConnOptions_SetConnectionLostHandler(connOpts, connectionLostCB, (void*)&connLost); - - /* - // To reduce MaxPubAcksInflight to 1000 and factor of 1.0 - if (s == NATS_OK) - s = stanConnOptions_SetMaxPubAcksInflight(connOpts, 1000, 1.0); - */ - - // Create the Connection using the STAN Connection Options - if (s == NATS_OK) - s = stanConnection_Connect(&sc, cluster, clientID, connOpts); - - // Once the connection is created, we can destroy the options - natsOptions_Destroy(opts); - stanConnOptions_Destroy(connOpts); - - if (s == NATS_OK) - start = nats_Now(); - - for (count = 0; (s == NATS_OK) && (count < total); count++) - { - if (async) - s = stanConnection_PublishAsync(sc, subj, (const void*) payload, len, _pubAckHandler, NULL); - else - s = stanConnection_Publish(sc, subj, (const void*) payload, len); - } - - if (!connLost && (s == NATS_OK)) - { - if (async) - { - while (ackCount != total) - nats_Sleep(15); - } - - printPerf("Sent"); - printf("Publish ack received: %d - with error: %d\n", ackCount, errCount); - } - - // If the connection was created, try to close it - if (!connLost && (sc != NULL)) - { - natsStatus closeSts = stanConnection_Close(sc); - - if ((s == NATS_OK) && (closeSts != NATS_OK)) - s = closeSts; - } - - if (s != NATS_OK) - { - // If we finished before the end, let's wait a tiny - // bit to see if the failure is due to connection lost. - if (ackCount != total) - nats_Sleep(100); - - // If connection was lost, the real reason is reported - // the the connectionLostCB callback. - if (!connLost) - { - printf("Error: %d - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - } - } - - // Destroy the connection - stanConnection_Destroy(sc); - - // To silence reports of memory still in-use with valgrind. - nats_Sleep(50); - nats_Close(); - - return 0; -} diff --git a/examples/stan/sub.c b/examples/stan/sub.c deleted file mode 100644 index fa89635ff..000000000 --- a/examples/stan/sub.c +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2018 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 "../examples.h" - -static const char *usage = ""\ -"-c cluster name (default \"test-cluster\")\n" \ -"-id client ID (default \"client\"\n" \ -"-count number of messages to receive\n" \ -"-last deliver starting with last published message (default)\n" \ -"-all deliver all available messages\n" \ -"-seq deliver starting at given sequence number\n" \ -"-durable durable subscription name\n" \ -"-qgroup queue group name\n" \ -"-unsubscribe unsubscribe the durable on exit\n"; - -static volatile bool done = false; - -static void -onMsg(stanConnection *sc, stanSubscription *sub, const char *channel, stanMsg *msg, void *closure) -{ - if (print) - printf("Received on [%s]: sequence:%" PRIu64 " data:%.*s timestamp:%" PRId64 " redelivered: %s\n", - channel, - stanMsg_GetSequence(msg), - stanMsg_GetDataLength(msg), - stanMsg_GetData(msg), - stanMsg_GetTimestamp(msg), - stanMsg_IsRedelivered(msg) ? "yes" : "no"); - - if (start == 0) - start = nats_Now(); - - // We should be using a mutex to protect those variables since - // they are used from the subscription's delivery and the main - // threads. For demo purposes, this is fine. - if (count == total-1) - { - natsStatus s; - - elapsed = nats_Now() - start; - - if (unsubscribe) - s = stanSubscription_Unsubscribe(sub); - else - s = stanSubscription_Close(sub); - - if (s != NATS_OK) - { - printf("Error: %d - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - } - } - - /* - // If manual acknowledgment was selected, we would acknowledge - // the message this way: - stanSubscription_AckMsg(sub, msg); - */ - - stanMsg_Destroy(msg); - - // Increment only here so that when the main thread detects - // that the total has been sent, it does not start cleaning - // objects before we have closed the subscription and destroyed - // the last message. This is to reduce risk of valgrind reporting - // memory still in-use. - count++; -} - -#if WIN32 -static BOOL -sigHandler(DWORD fdwCtrlType) -{ - if (fdwCtrlType==CTRL_C_EVENT) - { - done = true; - return TRUE; - } - return FALSE; -} -#else -static void -sigHandler(int ignored) { - done = true; -} -#endif - -static void -connectionLostCB(stanConnection *sc, const char *errTxt, void *closure) -{ - bool *connLost = (bool*) closure; - - printf("Connection lost: %s\n", errTxt); - *connLost = true; -} - -int main(int argc, char **argv) -{ - natsStatus s; - stanConnOptions *connOpts = NULL; - stanSubOptions *subOpts = NULL; - stanConnection *sc = NULL; - stanSubscription *sub = NULL; - bool connLost = false; - - opts = parseArgs(argc, argv, usage); - - printf("Receiving %" PRId64 " messages from channel '%s'\n", total, subj); - - // Now create STAN Connection Options and set the NATS Options. - s = stanConnOptions_Create(&connOpts); - if (s == NATS_OK) - s = stanConnOptions_SetNATSOptions(connOpts, opts); - - // Add a callback to be notified if the STAN connection is lost for good. - if (s == NATS_OK) - s = stanConnOptions_SetConnectionLostHandler(connOpts, connectionLostCB, (void*)&connLost); - - // Create the Connection using the STAN Connection Options - if (s == NATS_OK) - s = stanConnection_Connect(&sc, cluster, clientID, connOpts); - - // Once connection is created, we can destroy opts and connOpts - natsOptions_Destroy(opts); - stanConnOptions_Destroy(connOpts); - - if (s == NATS_OK) - s = stanSubOptions_Create(&subOpts); - - // If durable - if ((s == NATS_OK) && (durable != NULL)) - s = stanSubOptions_SetDurableName(subOpts, durable); - - // Set position - if (s == NATS_OK) - { - if (deliverAll) - s = stanSubOptions_DeliverAllAvailable(subOpts); - else if (deliverLast) - s = stanSubOptions_StartWithLastReceived(subOpts); - else if (deliverSeq > 0) - s = stanSubOptions_StartAtSequence(subOpts, deliverSeq); - } - - /* - // To manually acknowledge the messages, you would need to set this option - if (s == NATS_OK) - s = stanSubOptions_SetManualAckMode(subOpts, true); - - // To change the number of MaxInflight messages, set this option. - // For instance, to receive a single message between each ACK, set - // the value to 1. - if (s == NATS_OK) - s = stanSubOptions_SetMaxInflight(subOpts, 1); - - // To change the duration after which the server resends unacknowledged - // messages, use this option. For instance, cause re-delivery after 5 seconds: - if (s == NATS_OK) - s = stanSubOptions_SetAckWait(subOpts, 5000); - */ - - // Create subscription - if (s == NATS_OK) - { - if (qgroup != NULL) - s = stanConnection_QueueSubscribe(&sub, sc, subj, qgroup, onMsg, NULL, subOpts); - else - s = stanConnection_Subscribe(&sub, sc, subj, onMsg, NULL, subOpts); - } - - // Once subscription is created, we can destroy the subOpts object - stanSubOptions_Destroy(subOpts); - - if (s == NATS_OK) - { -#if WIN32 - SetConsoleCtrlHandler((PHANDLER_ROUTINE) sigHandler, TRUE); -#else - signal(SIGINT, sigHandler); -#endif - - while (!done && !connLost && (count < total)) - nats_Sleep(15); - - if (!connLost) - printPerf("Received"); - } - - // If test was interrupted before receiving all expected messages, - // close or unsubscribe. Otherwise, this is done in the message - // callback. - if (!connLost && ((sub != NULL) && (count < total))) - { - if (unsubscribe) - s = stanSubscription_Unsubscribe(sub); - else - s = stanSubscription_Close(sub); - } - - // If the connection was created, try to close it - if (!connLost && (sc != NULL)) - { - natsStatus closeSts = stanConnection_Close(sc); - - if ((s == NATS_OK) && (closeSts != NATS_OK)) - s = closeSts; - } - - if (s != NATS_OK) - { - printf("Error: %d - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - } - - // Destroy our objects - stanSubscription_Destroy(sub); - stanConnection_Destroy(sc); - - // To silence reports of memory still in-use with valgrind. - nats_Sleep(50); - nats_Close(); - - return 0; -} diff --git a/examples/subscriber.c b/examples/subscriber.c deleted file mode 100644 index 7ba087398..000000000 --- a/examples/subscriber.c +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2015-2018 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 "examples.h" - -static const char *usage = ""\ -"-gd use global message delivery thread pool\n" \ -"-sync receive synchronously (default is asynchronous)\n" \ -"-count number of expected messages\n"; - -static void -onMsg(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - if (print) - printf("Received msg: %s - %.*s\n", - natsMsg_GetSubject(msg), - natsMsg_GetDataLength(msg), - natsMsg_GetData(msg)); - - if (start == 0) - start = nats_Now(); - - // We should be using a mutex to protect those variables since - // they are used from the subscription's delivery and the main - // threads. For demo purposes, this is fine. - if (++count == total) - elapsed = nats_Now() - start; - - natsMsg_Destroy(msg); -} - -static void -asyncCb(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure) -{ - if (print) - printf("Async error: %u - %s\n", err, natsStatus_GetText(err)); - - natsSubscription_GetDropped(sub, (int64_t*) &dropped); -} - -int main(int argc, char **argv) -{ - natsConnection *conn = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsStatistics *stats = NULL; - natsMsg *msg = NULL; - natsStatus s; - - opts = parseArgs(argc, argv, usage); - - printf("Listening %ssynchronously on '%s'.\n", - (async ? "a" : ""), subj); - - s = natsOptions_SetErrorHandler(opts, asyncCb, NULL); - - if (s == NATS_OK) - s = natsConnection_Connect(&conn, opts); - - if (s == NATS_OK) - { - if (async) - s = natsConnection_Subscribe(&sub, conn, subj, onMsg, NULL); - else - s = natsConnection_SubscribeSync(&sub, conn, subj); - } - - // For maximum performance, set no limit on the number of pending messages. - if (s == NATS_OK) - s = natsSubscription_SetPendingLimits(sub, -1, -1); - - if (s == NATS_OK) - s = natsSubscription_AutoUnsubscribe(sub, (int) total); - - if (s == NATS_OK) - s = natsStatistics_Create(&stats); - - if ((s == NATS_OK) && async) - { - while (s == NATS_OK) - { - s = printStats(STATS_IN|STATS_COUNT, conn, sub, stats); - - if (count + dropped == total) - break; - - if (s == NATS_OK) - nats_Sleep(1000); - } - } - else if (s == NATS_OK) - { - int64_t last = 0; - - for (count = 0; (s == NATS_OK) && (count < total); count++) - { - s = natsSubscription_NextMsg(&msg, sub, 10000); - if (s != NATS_OK) - break; - - if (start == 0) - start = nats_Now(); - - if (nats_Now() - last >= 1000) - { - s = printStats(STATS_IN|STATS_COUNT, conn, sub, stats); - last = nats_Now(); - } - - natsMsg_Destroy(msg); - } - } - - if (s == NATS_OK) - { - printStats(STATS_IN|STATS_COUNT,conn, sub, stats); - printPerf("Received"); - } - else - { - printf("Error: %u - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - } - - // Destroy all our objects to avoid report of memory leak - natsStatistics_Destroy(stats); - natsSubscription_Destroy(sub); - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - // To silence reports of memory still in used with valgrind - nats_Close(); - - return 0; -} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a1351d82f..909ad5965 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,7 +54,6 @@ endif(NATS_BUILD_LIB_STATIC) # Install the libraries and header files # -------------------------------------- - if(NATS_BUILD_LIB_SHARED) set_property(TARGET nats PROPERTY DEBUG_POSTFIX d) target_include_directories(nats PUBLIC @@ -80,5 +79,5 @@ if(NATS_BUILD_LIB_STATIC) endif(NATS_BUILD_LIB_STATIC) install(FILES deprnats.h DESTINATION ${NATS_INCLUDE_DIR} RENAME nats.h) -install(FILES nats.h status.h version.h DESTINATION ${NATS_INCLUDE_DIR}/nats) +install(FILES nats/nats.h DESTINATION ${NATS_INCLUDE_DIR}/nats) install(FILES adapters/libevent.h adapters/libuv.h DESTINATION ${NATS_INCLUDE_DIR}/nats/adapters) diff --git a/src/asynccb.c b/src/asynccb.c deleted file mode 100644 index 57d20ceff..000000000 --- a/src/asynccb.c +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2015-2018 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 "conn.h" -#include "sub.h" -#if defined(NATS_HAS_STREAMING) -#include "stan/conn.h" -#endif - -static void -_freeAsyncCbInfo(natsAsyncCbInfo *info) -{ - NATS_FREE(info->errTxt); - NATS_FREE(info); -} - -static void -_createAndPostCb(natsAsyncCbType type, natsConnection *nc, natsSubscription *sub, natsStatus err, - char *errTxt, void* scPtr) -{ - natsStatus s = NATS_OK; - natsAsyncCbInfo *cb; -#if defined(NATS_HAS_STREAMING) - stanConnection *sc = (stanConnection*) scPtr; -#endif - - cb = NATS_CALLOC(1, sizeof(natsAsyncCbInfo)); - if (cb == NULL) - { - // We will ignore for now since that makes the caller handle the - // possibility of having to run the callbacks in-line... - return; - } - - cb->type = type; - cb->nc = nc; - cb->sub = sub; - if (sub != NULL) - natsSub_retain(sub); - cb->err = err; - cb->errTxt = errTxt; -#if defined(NATS_HAS_STREAMING) - cb->sc = sc; - stanConn_retain(sc); -#endif - natsConn_retain(nc); - - s = nats_postAsyncCbInfo(cb); - if (s != NATS_OK) - { - _freeAsyncCbInfo(cb); - natsConn_release(nc); - } -} - -void -natsAsyncCb_PostConnHandler(natsConnection *nc, natsAsyncCbType type) -{ - _createAndPostCb(type, nc, NULL, NATS_OK, NULL, NULL); -} - -void -natsAsyncCb_PostErrHandler(natsConnection *nc, natsSubscription *sub, natsStatus err, char *errTxt) -{ - _createAndPostCb(ASYNC_ERROR, nc, sub, err, errTxt, NULL); -} - -#if defined(NATS_HAS_STREAMING) -void -natsAsyncCb_PostStanConnLostHandler(stanConnection *sc) -{ - _createAndPostCb(ASYNC_STAN_CONN_LOST, NULL, NULL, NATS_CONNECTION_CLOSED, NULL, (void*) sc); -} -#endif - -void -natsAsyncCb_Destroy(natsAsyncCbInfo *info) -{ - natsConnection *nc = NULL; - natsSubscription *sub = NULL; -#if defined(NATS_HAS_STREAMING) - stanConnection *sc = NULL; -#endif - - if (info == NULL) - return; - - nc = info->nc; - sub = info->sub; -#if defined(NATS_HAS_STREAMING) - sc = info->sc; -#endif - - _freeAsyncCbInfo(info); - natsSub_release(sub); - natsConn_release(nc); -#if defined(NATS_HAS_STREAMING) - stanConn_release(sc); -#endif -} diff --git a/src/asynccb.h b/src/asynccb.h deleted file mode 100644 index 5731e4f57..000000000 --- a/src/asynccb.h +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2015-2020 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. - -#ifndef ASYNCCB_H_ -#define ASYNCCB_H_ - -#include "status.h" - -typedef enum -{ - ASYNC_CLOSED = 0, - ASYNC_DISCONNECTED, - ASYNC_RECONNECTED, - ASYNC_ERROR, - ASYNC_DISCOVERED_SERVERS, - ASYNC_CONNECTED, - ASYNC_LAME_DUCK_MODE, - -#if defined(NATS_HAS_STREAMING) - ASYNC_STAN_CONN_LOST -#endif - -} natsAsyncCbType; - -struct __natsConnection; -struct __natsSubscription; -struct __natsAsyncCbInfo; - -typedef struct __natsAsyncCbInfo -{ - natsAsyncCbType type; - struct __natsConnection *nc; - struct __natsSubscription *sub; - natsStatus err; - char *errTxt; - -#if defined(NATS_HAS_STREAMING) - struct __stanConnection *sc; -#endif - - struct __natsAsyncCbInfo *next; - -} natsAsyncCbInfo; - -void -natsAsyncCb_PostConnHandler(struct __natsConnection *nc, natsAsyncCbType type); - -void -natsAsyncCb_PostErrHandler(struct __natsConnection *nc, - struct __natsSubscription *sub, natsStatus err, char *errTxt); - -#if defined(NATS_HAS_STREAMING) -void -natsAsyncCb_PostStanConnLostHandler(struct __stanConnection *sc); -#endif - -void -natsAsyncCb_Destroy(natsAsyncCbInfo *info); - -#endif /* ASYNCCB_H_ */ diff --git a/src/buf.c b/src/buf.c deleted file mode 100644 index 8ce3b2502..000000000 --- a/src/buf.c +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright 2015-2018 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 -#include - -#include "err.h" -#include "mem.h" -#include "buf.h" - -static natsStatus -_init(natsBuffer *newBuf, char *data, int len, int capacity) -{ - natsBuffer *buf = newBuf; - - // Since we explicitly set all fields, no need for memset - - buf->doFree = false; - - if (data != NULL) - { - buf->data = data; - buf->ownData = false; - } - else - { - buf->data = (char*) NATS_MALLOC(capacity); - if (buf->data == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - buf->ownData = true; - } - - buf->pos = buf->data + len; - buf->len = len; - buf->capacity = capacity; - - return NATS_OK; -} - -natsStatus -natsBuf_InitWithBackend(natsBuffer *newBuf, char *data, int len, int capacity) -{ - if (data == NULL) - return NATS_INVALID_ARG; - - return _init(newBuf, data, len, capacity); -} - -natsStatus -natsBuf_Init(natsBuffer *buf, int capacity) -{ - return _init(buf, NULL, 0, capacity); -} - -static natsStatus -_newBuf(natsBuffer **newBuf, char *data, int len, int capacity) -{ - natsBuffer *buf; - - buf = (natsBuffer*) NATS_MALLOC(sizeof(natsBuffer)); - if (buf == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - if (_init(buf, data, len, capacity) != NATS_OK) - { - NATS_FREE(buf); - return NATS_UPDATE_ERR_STACK(NATS_NO_MEMORY); - } - - buf->doFree = true; - - *newBuf = buf; - - return NATS_OK; -} - -natsStatus -natsBuf_CreateWithBackend(natsBuffer **newBuf, char *data, int len, int capacity) -{ - natsStatus s; - - if (data == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - s = _newBuf(newBuf, data, len, capacity); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsBuf_Create(natsBuffer **newBuf, int capacity) -{ - natsStatus s = _newBuf(newBuf, NULL, 0, capacity); - return NATS_UPDATE_ERR_STACK(s); -} - -void -natsBuf_Reset(natsBuffer *buf) -{ - buf->len = 0; - buf->pos = buf->data; -} - -void -natsBuf_MoveTo(natsBuffer *buf, int newPosition) -{ - assert(newPosition <= buf->capacity); - - buf->len = newPosition; - buf->pos = buf->data + newPosition; -} - -natsStatus -natsBuf_Expand(natsBuffer *buf, int newSize) -{ - int offset = (int) (buf->pos - buf->data); - char *newData = NULL; - - if (newSize <= buf->capacity) - return nats_setDefaultError(NATS_INVALID_ARG); - - if (buf->ownData) - { - newData = NATS_REALLOC(buf->data, newSize); - if (newData == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - } - else - { - newData = NATS_MALLOC(newSize); - if (newData == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - memcpy(newData, buf->data, buf->len); - buf->ownData = true; - } - - if (buf->data != newData) - { - buf->data = newData; - buf->pos = (char*) (buf->data + offset); - } - - buf->capacity = newSize; - - return NATS_OK; -} - -natsStatus -natsBuf_Append(natsBuffer *buf, const char* data, int dataLen) -{ - natsStatus s = NATS_OK; - int64_t n; - - if (dataLen == -1) - dataLen = (int) strlen(data); - - n = (int64_t) buf->len + dataLen; - - if ((n < 0) || (n >= 0x7FFFFFFF)) - return nats_setDefaultError(NATS_NO_MEMORY); - - if (n > (int64_t) buf->capacity) - { - // Increase by 10% - int64_t extra = (int64_t) (n * 0.1); - int64_t newSize; - - // Make sure that we have at least some bytes left after adding. - newSize = (n + (extra < 64 ? 64 : extra)); - - // Overrun. - if (newSize >= 0x7FFFFFFF) - return nats_setDefaultError(NATS_NO_MEMORY); - - s = natsBuf_Expand(buf, (int) newSize); - } - - if (s == NATS_OK) - { - memcpy(buf->pos, data, dataLen); - buf->pos += dataLen; - buf->len += dataLen; - } - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsBuf_AppendByte(natsBuffer *buf, char b) -{ - natsStatus s = NATS_OK; - int c = buf->capacity; - - if (buf->len == c) - { - // Increase by 10% - int extra = (int) (c * 0.1); - int newSize; - - // Make sure that we have at least some bytes left after adding. - newSize = (c + (extra < 64 ? 64 : extra)); - - // Overrun. - if (newSize < 0) - return nats_setDefaultError(NATS_NO_MEMORY); - - s = natsBuf_Expand(buf, newSize); - } - - if (s == NATS_OK) - { - *(buf->pos++) = b; - buf->len++; - } - - return NATS_UPDATE_ERR_STACK(s); -} - -void -natsBuf_Consume(natsBuffer *buf, int n) -{ - int remaining; - - assert(n <= buf->len); - - remaining = buf->len - n; - if (remaining > 0) - memmove(buf->data, buf->data + n, remaining); - - buf->len = remaining; - buf->pos = buf->data + remaining; -} - -void -natsBuf_Destroy(natsBuffer *buf) -{ - if (buf == NULL) - return; - - if (buf->ownData) - NATS_FREE(buf->data); - - if (buf->doFree) - NATS_FREE(buf); - else - memset(buf, 0, sizeof(natsBuffer)); -} diff --git a/src/buf.h b/src/buf.h deleted file mode 100644 index 761571f19..000000000 --- a/src/buf.h +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2015-2018 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. - -#ifndef BUF_H_ -#define BUF_H_ - -#include - -#include "status.h" - -typedef struct __natsBuffer -{ - char* data; - char* pos; - int len; - int capacity; - bool ownData; - bool doFree; - -} natsBuffer; - -#define natsBuf_Data(b) ((b)->data) -#define natsBuf_Capacity(b) ((b)->capacity) -#define natsBuf_Len(b) ((b)->len) -#define natsBuf_Available(b) ((b)->capacity - (b)->len) - - -// Initializes a natsBuffer using 'data' as the back-end byte array. -// The length and capacity are set based on the given parameters. -// Since the 'data' is not owned, and as long as the buffer does not need -// to be expanded, the byte buffer will not be freed when this natsBuffer -// is destroyed. Check natsBuf_Expand() for more details. -// -// One would use this call to initialize a natsBuffer without the added cost -// of allocating memory for the natsBuffer structure itself, for instance -// initializing an natsBuffer on the stack. -natsStatus -natsBuf_InitWithBackend(natsBuffer *buf, char *data, int len, int capacity); - -// Initializes a natsBuffer and creates a byte buffer of 'capacity' bytes. -// The natsBuffer owns the buffer and will therefore free the memory used -// when destroyed. -// -// One would use this call to initialize a natsBuffer without the added cost -// of allocating memory for the natsBuffer structure itself, for instance -// initializing an natsBuffer on the stack. -natsStatus -natsBuf_Init(natsBuffer *buf, int capacity); - -// Creates a new natsBuffer using 'data' as the back-end byte array. -// The length and capacity are set based on the given parameters. -// Since the 'data' is not owned, and as long as the buffer does not need -// to be expanded, the byte buffer will not be freed when this natsBuffer -// is destroyed. Check natsBuf_Expand() for more details. -natsStatus -natsBuf_CreateWithBackend(natsBuffer **newBuf, char *data, int len, int capacity); - -// Creates a new natsBuffer and creates a byte buffer of 'capacity' bytes. -// The natsBuffer owns the buffer and will therefore free the memory used -// when destroyed. -natsStatus -natsBuf_Create(natsBuffer **newBuf, int capacity); - -// Resets the length to zero, and the position to the beginning of the buffer. -void -natsBuf_Reset(natsBuffer *buf); - -// Sets the size of the buffer to 'newPosition' and new data will be appended -// starting at this position. -void -natsBuf_MoveTo(natsBuffer *buf, int newPosition); - -// Expands 'buf' underlying buffer to the given new size 'newSize'. -// -// If 'buf' did not own the underlying buffer, a new buffer is -// created and data copied over. The original data is now detached. -// The underlying buffer is now owned by 'buf' and will be freed when -// the natsBuffer is destroyed. -// -// When 'buf' owns the underlying buffer and it is expanded, a memory -// reallocation of the buffer occurs to satisfy the new size requirement. -// -// Note that one should not save the returned value of natsBuf_Data() and -// use it after any call to natsBuf_Expand/Append/AppendByte() since -// the memory address for the underlying byte buffer may have changed due -// to the buffer expansion. -natsStatus -natsBuf_Expand(natsBuffer *buf, int newSize); - -// Appends 'dataLen' bytes from the 'data' byte array to the buffer, -// potentially expanding the buffer. -// See natsBuf_Expand for details about natsBuffer not owning the data. -natsStatus -natsBuf_Append(natsBuffer *buf, const char* data, int dataLen); - -// Appends a byte to the buffer, potentially expanding the buffer. -// See natsBuf_Expand for details about natsBuffer not owning the data. -natsStatus -natsBuf_AppendByte(natsBuffer *buf, char b); - -// Consume data from a buffer, overwriting the 'n' first bytes by the remaining -// of data in this buffer. -void -natsBuf_Consume(natsBuffer *buf, int n); - -// Reads better when dealing with a buffer that was initialized as opposed to -// created, but calling natsBuf_Destroy() will do the right thing regardless -// of how the buffer was created. -#define natsBuf_Cleanup(b) natsBuf_Destroy((b)) - -// Frees the data if owned (otherwise leaves it untouched) and the structure -// if the buffer was created with one of the natsBuf_CreateX() function, -// otherwise simply 'memset' the structure. -void -natsBuf_Destroy(natsBuffer *buf); - -#endif /* BUF_H_ */ diff --git a/src/comsock.c b/src/comsock.c index 1873be621..85319503e 100644 --- a/src/comsock.c +++ b/src/comsock.c @@ -13,16 +13,8 @@ #include "natsp.h" -#include -#include -#include -#include -#include - -#include "status.h" +#include "hash.h" #include "comsock.h" -#include "mem.h" -#include "util.h" natsStatus natsSock_Init(natsSockCtx *ctx) @@ -79,28 +71,20 @@ natsSock_SetCommonTcpOptions(natsSock fd) } void -natsSock_ShuffleIPs(natsSockCtx *ctx, struct addrinfo **tmp, int tmpSize, struct addrinfo **ipListHead, int count) +natsSock_ShuffleIPs(natsSockCtx *ctx, natsPool *pool, struct addrinfo **ipListHead, int count) { struct addrinfo **ips = NULL; struct addrinfo *p = NULL; - bool doFree = false; int i, j; if (ctx->noRandomize || (ipListHead == NULL) || (*ipListHead == NULL) || count <= 1) return; - if (count > tmpSize) - { - ips = (struct addrinfo**) NATS_CALLOC(count, sizeof(struct addrinfo*)); - // Let's not fail the caller, simply don't shuffle. - if (ips == NULL) - return; - doFree = true; - } - else - { - ips = tmp; - } + ips = (struct addrinfo**) nats_palloc(pool, count * sizeof(struct addrinfo*)); + // Let's not fail the caller, simply don't shuffle. + if (ips == NULL) + return; + p = *ipListHead; // Put them in a array for (i=0; iorderIP == 46) || (ctx->orderIP == 64)) max = 2; - start = nats_Now(); - + start = nats_now(); for (i=0; ifd, false); - - if (s == NATS_OK) + IFOK(s, natsSock_SetBlocking(ctx->fd, false)); + if (STILL_OK(s)) { res = connect(ctx->fd, p->ai_addr, (natsSockLen) p->ai_addrlen); if ((res == NATS_SOCK_ERROR) @@ -273,7 +249,7 @@ natsSock_ConnectTcp(natsSockCtx *ctx, const char *phost, int port) natsDeadline_Init(&(ctx->writeDeadline), timeoutPerIP); s = natsSock_WaitReady(WAIT_FOR_CONNECT, ctx); - if ((s == NATS_OK) && !natsSock_IsConnected(ctx->fd)) + if ((STILL_OK(s)) && !natsSock_IsConnected(ctx->fd)) s = NATS_TIMEOUT; } else if (res == NATS_SOCK_ERROR) @@ -282,18 +258,18 @@ natsSock_ConnectTcp(natsSockCtx *ctx, const char *phost, int port) } } - if (s == NATS_OK) + if (STILL_OK(s)) { s = natsSock_SetCommonTcpOptions(ctx->fd); // We have connected OK and completed setting options, so we are done. - if (s == NATS_OK) + if (STILL_OK(s)) break; } _closeFd(ctx->fd); ctx->fd = NATS_SOCK_INVALID; } - if (s == NATS_OK) + if (STILL_OK(s)) { // Clear the error stack in case we got errors in the loop until // being able to successfully connect. @@ -307,7 +283,7 @@ natsSock_ConnectTcp(natsSockCtx *ctx, const char *phost, int port) // If there was a deadline, reset the deadline with whatever is left. if (totalTimeout > 0) { - int64_t used = nats_Now() - start; + int64_t used = nats_now() - start; int64_t left = totalTimeout - used; natsDeadline_Init(&(ctx->writeDeadline), (left > 0 ? left : 0)); @@ -317,101 +293,20 @@ natsSock_ConnectTcp(natsSockCtx *ctx, const char *phost, int port) } natsStatus -natsSock_ReadLine(natsSockCtx *ctx, char *buffer, size_t maxBufferSize) -{ - natsStatus s = NATS_OK; - int readBytes = 0; - size_t totalBytes = 0; - char *p = buffer; - char *eol; - - // By contract, the caller needs to set buffer[0] to '\0' before the first - // call. - if (*p != '\0') - { - // We assume that this is not the first call with the given buffer. - // Move possible data after the first line to the beginning of the - // buffer. - char *nextLine; - size_t nextStart; - size_t len = 0; - - // The start of the next line will be the length of the line at the - // start of the buffer + 2, which is the number of characters - // representing CRLF. - nextStart = strlen(buffer) + 2; - nextLine = (char*) (buffer + nextStart); - - // There is some data... - if (*nextLine != '\0') - { - // The next line (even if partial) is guaranteed to be NULL - // terminated. - len = strlen(nextLine); - - // Move to the beginning of the buffer (and include the NULL char) - memmove(buffer, nextLine, len + 1); - - // Now, if the string contains a CRLF, we don't even need to read - // from the socket. Update the buffer and return. - if ((eol = strstr(buffer, _CRLF_)) != NULL) - { - // Replace the '\r' with '\0' to NULL terminate the string. - *eol = '\0'; - - // We are done! - return NATS_OK; - } - - // This is a partial, we need to read more data until we get to - // the end of the line (\r\n). - p = (char*) (p + len); - totalBytes += len; - } - else - { - *p = '\0'; - } - } - - while (1) - { - s = natsSock_Read(ctx, p, (maxBufferSize - totalBytes), &readBytes); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - if (totalBytes + readBytes == maxBufferSize) - return nats_setDefaultError(NATS_LINE_TOO_LONG); - - // We need to append a NULL character after what we have received. - *(p + readBytes) = '\0'; - - if ((eol = strstr(p, _CRLF_)) != NULL) - { - *eol = '\0'; - return NATS_OK; - } - - p += readBytes; - totalBytes += readBytes; - } -} - -natsStatus -natsSock_Read(natsSockCtx *ctx, char *buffer, size_t maxBufferSize, int *n) +natsSock_Read(natsSockCtx *ctx, uint8_t *buffer, size_t maxBufferSize, size_t *n) { - natsStatus s = NATS_OK; - int readBytes = 0; - bool needRead = true; + natsStatus s = NATS_OK; + int readBytes = 0; + bool needRead = true; while (needRead) { #if defined(NATS_HAS_TLS) if (ctx->ssl != NULL) - readBytes = SSL_read(ctx->ssl, buffer, (int) maxBufferSize); + readBytes = SSL_read(ctx->ssl, buffer, (int)maxBufferSize); else #endif - readBytes = recv(ctx->fd, buffer, (natsRecvLen) maxBufferSize, 0); + readBytes = recv(ctx->fd, buffer, (natsRecvLen)maxBufferSize, 0); if (readBytes == 0) { @@ -447,12 +342,13 @@ natsSock_Read(natsSockCtx *ctx, char *buffer, size_t maxBufferSize, int *n) #if defined(NATS_HAS_TLS) if (ctx->ssl != NULL) return nats_setError(NATS_IO_ERROR, "SSL_read error: %s", - NATS_SSL_ERR_REASON_STRING); + NATS_SSL_ERR_REASON_STRING); else #endif return nats_setError(NATS_IO_ERROR, "recv error: %d", - NATS_SOCK_GET_ERROR); + NATS_SOCK_GET_ERROR); } + else if (ctx->useEventLoop) { // When using an external event loop, we are done. We will be @@ -468,13 +364,10 @@ natsSock_Read(natsSockCtx *ctx, char *buffer, size_t maxBufferSize, int *n) s = natsSock_WaitReady(WAIT_FOR_READ, ctx); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); - continue; } - if (n != NULL) *n = readBytes; - needRead = false; } @@ -482,11 +375,11 @@ natsSock_Read(natsSockCtx *ctx, char *buffer, size_t maxBufferSize, int *n) } natsStatus -natsSock_Write(natsSockCtx *ctx, const char *data, int len, int *n) +natsSock_Write(natsSockCtx *ctx, natsString *buf, size_t *n) { - natsStatus s = NATS_OK; - int bytes = 0; - bool needWrite = true; + natsStatus s = NATS_OK; + int bytes = 0; + bool needWrite = true; while (needWrite) { @@ -496,7 +389,7 @@ natsSock_Write(natsSockCtx *ctx, const char *data, int len, int *n) else #endif #ifdef MSG_NOSIGNAL - bytes = send(ctx->fd, data, len, MSG_NOSIGNAL); + bytes = send(ctx->fd, buf->data, buf->len, MSG_NOSIGNAL); #else bytes = send(ctx->fd, data, len, 0); #endif @@ -556,54 +449,16 @@ natsSock_Write(natsSockCtx *ctx, const char *data, int len, int *n) s = natsSock_WaitReady(WAIT_FOR_WRITE, ctx); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); - continue; } - if (n != NULL) *n = bytes; - needWrite = false; } return NATS_OK; } -natsStatus -natsSock_WriteFully(natsSockCtx *ctx, const char *data, int len) -{ - natsStatus s = NATS_OK; - int n = 0; - - if (len == 0) - return NATS_OK; - - do - { - s = natsSock_Write(ctx, data, len, &n); - if (s == NATS_OK) - { - data += n; - len -= n; - - // We have sent the whole buffer. Return. - if (len == 0) - return NATS_OK; - } - } - while (s == NATS_OK); - - // If we are using a write deadline, shutdown the socket to trigger a - // possible reconnect - if (s == NATS_TIMEOUT) - { - natsSock_Shutdown(ctx->fd); - ctx->fdActive = false; - } - - return NATS_UPDATE_ERR_STACK(s); -} - void natsSock_ClearDeadline(natsSockCtx *ctx) { @@ -619,7 +474,7 @@ natsSock_InitDeadline(natsSockCtx *ctx, int64_t timeout) } natsStatus -natsSock_GetLocalIPAndPort(natsSockCtx *ctx, char **ip, int *port) +natsSock_GetLocalIPAndPort(natsSockCtx *ctx, natsPool *pool, const char **ip, int *port) { struct sockaddr_storage addr; natsSockLen addrLen = (natsSockLen) sizeof(addr); @@ -657,7 +512,7 @@ natsSock_GetLocalIPAndPort(natsSockCtx *ctx, char **ip, int *port) if (inet_ntop(fam, laddr, localIP, sizeof(localIP)) == NULL) return nats_setError(NATS_SYS_ERROR, "inet_ntop error: %d", NATS_SOCK_GET_ERROR); - if ((*ip = NATS_STRDUP(localIP)) == NULL) + if ((*ip = nats_pstrdupC(pool, localIP)) == NULL) return nats_setDefaultError(NATS_NO_MEMORY); return NATS_OK; diff --git a/src/comsock.h b/src/comsock.h index 73c01fadf..add490c29 100644 --- a/src/comsock.h +++ b/src/comsock.h @@ -15,6 +15,31 @@ #define SOCK_H_ #include "natsp.h" +#include "natstime.h" + +#define WAIT_FOR_READ (0) +#define WAIT_FOR_WRITE (1) +#define WAIT_FOR_CONNECT (2) + +struct __natsSockCtx +{ + natsSock fd; + bool fdActive; + + natsDeadline readDeadline; + natsDeadline writeDeadline; + + // During Connect we don't use an external event loop (such as libuv), then + // set to true. + bool useEventLoop; + + int orderIP; // possible values: 0,4,6,46,64 + + // By default, the list of IPs returned by the hostname resolution will + // be shuffled. This option, if `true`, will disable the shuffling. + bool noRandomize; + +}; natsStatus natsSock_Init(natsSockCtx *ctx); @@ -24,10 +49,10 @@ natsStatus natsSock_WaitReady(int waitMode, natsSockCtx *ctx); void -natsSock_ShuffleIPs(natsSockCtx *ctx, struct addrinfo **tmp, int tmpSize, struct addrinfo **ipListHead, int count); +natsSock_ShuffleIPs(natsSockCtx *ctx, natsPool *pool, struct addrinfo **ipListHead, int count); natsStatus -natsSock_ConnectTcp(natsSockCtx *ctx, const char *host, int port); +natsSock_ConnectTcp(natsSockCtx *ctx, natsPool *pool, const char *host, int port); natsStatus natsSock_SetBlocking(natsSock fd, bool blocking); @@ -35,19 +60,6 @@ natsSock_SetBlocking(natsSock fd, bool blocking); bool natsSock_IsConnected(natsSock fd); -// Reads a line from the socket and returns it without the line-ending characters. -// This call blocks until the line is complete, or the socket is closed or an -// error occurs. -// Handles blocking and non-blocking sockets. For the later, an optional 'deadline' -// indicates how long it can wait for the full read to complete. -// -// NOTE: 'buffer[0]' must be set to '\0' prior to the very first call. If the -// peer is sending multiple lines, it is possible that this function reads the -// next line(s) (or partials) in a single call. In this case, the caller needs -// to repeat the call with the same buffer to "read" the next line. -natsStatus -natsSock_ReadLine(natsSockCtx *ctx, char *buffer, size_t maxBufferSize); - // Reads up to 'maxBufferSize' bytes from the socket and put them in 'buffer'. // If the socket is blocking, wait until some data is available or the socket // is closed or an error occurs. @@ -56,7 +68,7 @@ natsSock_ReadLine(natsSockCtx *ctx, char *buffer, size_t maxBufferSize); // If an external event loop is used, it is possible that this function // returns NATS_OK with 'n' == 0. natsStatus -natsSock_Read(natsSockCtx *ctx, char *buffer, size_t maxBufferSize, int *n); +natsSock_Read(natsSockCtx *ctx, uint8_t *buffer, size_t maxBufferSize, size_t *n); // Writes up to 'len' bytes to the socket. If the socket is blocking, // wait for some data to be sent. If the socket is non-blocking, wait up @@ -64,13 +76,7 @@ natsSock_Read(natsSockCtx *ctx, char *buffer, size_t maxBufferSize, int *n); // If an external event loop is used, it is possible that this function // returns NATS_OK with 'n' == 0. natsStatus -natsSock_Write(natsSockCtx *ctx, const char *data, int len, int *n); - -// Writes 'len' bytes to the socket. Does not return until all bytes -// have been written, unless the socket is closed or an error occurs -// (including write timeout). -natsStatus -natsSock_WriteFully(natsSockCtx *ctx, const char *data, int len); +natsSock_Write(natsSockCtx *ctx, natsString *buf, size_t *n); natsStatus natsSock_Flush(natsSock fd); @@ -84,13 +90,10 @@ natsSock_SetCommonTcpOptions(natsSock fd); void natsSock_Shutdown(natsSock fd); -void -natsSock_ClearDeadline(natsSockCtx *ctx); - void natsSock_InitDeadline(natsSockCtx *ctx, int64_t timeout); natsStatus -natsSock_GetLocalIPAndPort(natsSockCtx *ctx, char **ip, int *port); +natsSock_GetLocalIPAndPort(natsSockCtx *ctx, natsPool *pool, const char **ip, int *port); #endif /* SOCK_H_ */ diff --git a/src/conn.c b/src/conn.c index 1b405c852..3f65df679 100644 --- a/src/conn.c +++ b/src/conn.c @@ -13,4483 +13,267 @@ #include "natsp.h" -#include -#include -#include -#include -#include - +#include "servers.h" #include "conn.h" -#include "mem.h" -#include "buf.h" -#include "parser.h" -#include "srvpool.h" -#include "url.h" #include "opts.h" -#include "util.h" -#include "timer.h" -#include "sub.h" -#include "msg.h" -#include "asynccb.h" -#include "comsock.h" -#include "nkeys.h" -#include "crypto.h" -#include "js.h" -#define DEFAULT_SCRATCH_SIZE (512) -#define MAX_INFO_MESSAGE_SIZE (32768) -#define DEFAULT_FLUSH_TIMEOUT (10000) +#define STALE_CONNECTION "Stale Connection" +#define PERMISSIONS_ERR "Permissions Violation" +#define AUTHORIZATION_ERR "Authorization Violation" +#define AUTHENTICATION_EXPIRED_ERR "User Authentication Expired" -#define NATS_EVENT_ACTION_ADD (true) -#define NATS_EVENT_ACTION_REMOVE (false) +#define NATS_DEFAULT_INBOX_PRE "_INBOX." +#define NATS_DEFAULT_INBOX_PRE_LEN (7) -#ifdef DEV_MODE -// For type safety +#define NATS_MAX_REQ_ID_LEN (19) // to display 2^63-1 number + +#define ERR_CODE_AUTH_EXPIRED (1) +#define ERR_CODE_AUTH_VIOLATION (2) + +#define SET_WRITE_DEADLINE(nc) \ + if ((nc)->opts->writeDeadline > 0) \ + natsDeadline_Init(&(nc)->sockCtx.writeDeadline, (nc)->opts->writeDeadline) -static void _retain(natsConnection *nc) { nc->refs++; } -static void _release(natsConnection *nc) { nc->refs--; } +#ifdef DEV_MODE -void natsConn_Lock(natsConnection *nc) { natsMutex_Lock(nc->mu); } -void natsConn_Unlock(natsConnection *nc) { natsMutex_Unlock(nc->mu); } +#define _retain(c) natsConn_retain(c) +#define _release(c) natsConn_release(c) #else // We know what we are doing :-) -#define _retain(c) ((c)->refs++) +#define _retain(c) ((c)->refs++) #define _release(c) ((c)->refs--) #endif // DEV_MODE +static natsStatus _connect(natsConnection *nc); +static natsStatus _connectTCP(natsConnection *nc); +static natsStatus _createConnectionObject(natsConnection **newnc, natsEventLoop *ev, natsOptions *opts); +static natsStatus _evStopPolling(natsConnection *nc); +static void _freeConn(natsConnection *nc); -// CLIENT_PROTO_ZERO is the original client protocol from 2009. -// http://nats.io/documentation/internals/nats-protocol/ -#define CLIENT_PROTO_ZERO (0) - -// CLIENT_PROTO_INFO signals a client can receive more then the original INFO block. -// This can be used to update clients on other cluster members, etc. -#define CLIENT_PROTO_INFO (1) - -/* - * Forward declarations: - */ -static natsStatus -_spinUpSocketWatchers(natsConnection *nc); - -static natsStatus -_processConnInit(natsConnection *nc); - -static void -_close(natsConnection *nc, natsConnStatus status, bool fromPublicClose, bool doCBs); - -static bool -_processOpError(natsConnection *nc, natsStatus s, bool initialConnect); - -static natsStatus -_flushTimeout(natsConnection *nc, int64_t timeout); - -static bool -_processAuthError(natsConnection *nc, int errCode, char *error); - -static int -_checkAuthError(char *error); - -/* - * ---------------------------------------- - */ - -struct threadsToJoin -{ - natsThread *readLoop; - natsThread *flusher; - natsThread *reconnect; - bool joinReconnect; - -} threadsToJoin; - -static void -_initThreadsToJoin(struct threadsToJoin *ttj, natsConnection *nc, bool joinReconnect) +natsStatus +nats_AsyncConnectWithOptions(natsConnection **newConn, natsEventLoop *ev, natsOptions *options) { - memset(ttj, 0, sizeof(threadsToJoin)); - - ttj->joinReconnect = joinReconnect; + natsStatus s = NATS_OK; + natsConnection *nc = NULL; - if (nc->readLoopThread != NULL) + if (options == NULL) { - ttj->readLoop = nc->readLoopThread; - nc->readLoopThread = NULL; + // s = natsConnection_ConnectTo(newConn, NATS_DEFAULT_URL); + return NATS_UPDATE_ERR_STACK(NATS_INVALID_ARG); } - if (joinReconnect && (nc->reconnectThread != NULL)) - { - ttj->reconnect = nc->reconnectThread; - nc->reconnectThread = NULL; - } + s = _createConnectionObject(&nc, ev, options); + IFOK(s, + natsServers_Create(&(nc->servers), nc->lifetimePool, nc->opts)); + IFOK(s, ALWAYS_OK( + nc->cur = natsServers_Get(nc->servers, 0))); + IFOK(s, _connectTCP(nc)); + IFOK(s, CHECK_NO_MEMORY( + nc->evState = nats_palloc(nc->lifetimePool, ev->ctxSize))); + IFOK(s, nc->ev.attach(nc->evState, ev, nc, nc->sockCtx.fd)); - if (nc->flusherThread != NULL) + if (s != NATS_OK) { - nc->flusherStop = true; - natsCondition_Signal(nc->flusherCond); - - ttj->flusher = nc->flusherThread; - nc->flusherThread = NULL; + natsConn_release(nc); + nats_setError(s, + "Error attaching to the event loop: %d - %s", + s, natsStatus_GetText(s)); + return NATS_UPDATE_ERR_STACK(s); } + nc->evAttached = true; + *newConn = nc; + return NATS_OK; } -static void -_joinThreads(struct threadsToJoin *ttj) +// Creates and initializes a natsConnection memory object, does not take any +// action yet. +static natsStatus +_createConnectionObject(natsConnection **newConn, natsEventLoop *ev, natsOptions *opts) { - if (ttj->readLoop != NULL) - { - natsThread_Join(ttj->readLoop); - natsThread_Destroy(ttj->readLoop); - } + natsStatus s = NATS_OK; + natsPool *pool = NULL; + natsConnection *nc = NULL; - if (ttj->joinReconnect && (ttj->reconnect != NULL)) - { - natsThread_Join(ttj->reconnect); - natsThread_Destroy(ttj->reconnect); - } + s = nats_createPool(&pool, &opts->mem, "conn-lifetime"); + IFOK(s, CHECK_NO_MEMORY(nc = nats_palloc(pool, sizeof(natsConnection)))); + IFOK(s, natsConn_createParser(&(nc->ps), pool)); + IFOK(s, natsSock_Init(&nc->sockCtx)); + IFOK(s, natsWriteChain_init(&nc->writeChain, &opts->mem)); // TODO <>/<> defer to connect time - if (ttj->flusher != NULL) + if (s != NATS_OK) { - natsThread_Join(ttj->flusher); - natsThread_Destroy(ttj->flusher); + nats_releasePool(pool); + return NATS_UPDATE_ERR_STACK(s); } -} - -static void -_clearServerInfo(natsServerInfo *si) -{ - int i; - - NATS_FREE(si->id); - NATS_FREE(si->host); - NATS_FREE(si->version); - - for (i=0; iconnectURLsCount; i++) - NATS_FREE(si->connectURLs[i]); - NATS_FREE(si->connectURLs); - - NATS_FREE(si->nonce); - NATS_FREE(si->clientIP); - - memset(si, 0, sizeof(natsServerInfo)); -} - -static void -_freeConn(natsConnection *nc) -{ - if (nc == NULL) - return; - - natsTimer_Destroy(nc->ptmr); - natsBuf_Destroy(nc->pending); - natsBuf_Destroy(nc->scratch); - natsBuf_Destroy(nc->bw); - natsSrvPool_Destroy(nc->srvPool); - _clearServerInfo(&(nc->info)); - natsCondition_Destroy(nc->flusherCond); - natsCondition_Destroy(nc->pongs.cond); - natsParser_Destroy(nc->ps); - natsThread_Destroy(nc->readLoopThread); - natsThread_Destroy(nc->flusherThread); - natsHash_Destroy(nc->subs); - natsOptions_Destroy(nc->opts); - if (nc->sockCtx.ssl != NULL) - SSL_free(nc->sockCtx.ssl); - NATS_FREE(nc->el.buffer); - natsConn_destroyRespPool(nc); - natsInbox_Destroy(nc->respSub); - natsStrHash_Destroy(nc->respMap); - natsCondition_Destroy(nc->reconnectCond); - natsMutex_Destroy(nc->subsMu); - natsMutex_Destroy(nc->mu); - - NATS_FREE(nc); - - natsLib_Release(); -} - -void -natsConn_retain(natsConnection *nc) -{ - if (nc == NULL) - return; - - natsConn_Lock(nc); - - nc->refs++; - - natsConn_Unlock(nc); -} - -void -natsConn_release(natsConnection *nc) -{ - int refs = 0; - - if (nc == NULL) - return; - - natsConn_Lock(nc); - - refs = --(nc->refs); - - natsConn_Unlock(nc); - - if (refs == 0) - _freeConn(nc); -} - -void -natsConn_lockAndRetain(natsConnection *nc) -{ - natsConn_Lock(nc); - nc->refs++; -} - -void -natsConn_unlockAndRelease(natsConnection *nc) -{ - int refs = 0; - refs = --(nc->refs); + nats_retainGlobalLib(); + nats_cloneOptions(&nc->opts, pool, opts); + nc->ev = *ev; + nc->state = NATS_CONN_STATUS_DISCONNECTED; + nc->refs = 1; + nc->lifetimePool = pool; + CONNTRACEf("_createConnectionObject: created natsConnection: %p", (void *)nc); + *newConn = nc; - natsConn_Unlock(nc); - - if (refs == 0) - _freeConn(nc); + return NATS_OK; } -natsStatus -natsConn_bufferFlush(natsConnection *nc) +// _connectTCP will connect to the server and do the right thing when an +// existing connection is in place. +static natsStatus +_connectTCP(natsConnection *nc) { - natsStatus s = NATS_OK; - int bufLen = natsBuf_Len(nc->bw); - - if (bufLen == 0) - return NATS_OK; + natsStatus s = NATS_OK; - if (nc->usePending) - { - s = natsBuf_Append(nc->pending, natsBuf_Data(nc->bw), bufLen); - } - else if (nc->sockCtx.useEventLoop) - { - if (!(nc->el.writeAdded)) - { - nc->el.writeAdded = true; - s = nc->opts->evCbs.write(nc->el.data, NATS_EVENT_ACTION_ADD); - if (s != NATS_OK) - nats_setError(s, "Error processing write request: %d - %s", - s, natsStatus_GetText(s)); - } + // TODO <>/<> check that we are not already connected? + nc->state = NATS_CONN_STATUS_CONNECTING; + // Initialize the connect-time memory pool. + s = nats_createPool(&nc->connectPool, &nc->opts->mem, "conn-connect"); + if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); - } - else - { - s = natsSock_WriteFully(&(nc->sockCtx), natsBuf_Data(nc->bw), bufLen); - } - natsBuf_Reset(nc->bw); + nc->sockCtx.orderIP = nc->opts->net.orderIP; + nc->sockCtx.noRandomize = nc->opts->net.noRandomize; + s = natsSock_ConnectTcp(&(nc->sockCtx), nc->connectPool, nc->cur->url->host, nc->cur->url->port); + if (STILL_OK(s)) + nc->sockCtx.fdActive = true; + if (STILL_OK(s)) + CONNDEBUGf("TCP connected to %s", nc->cur->url->fullUrl); + return NATS_UPDATE_ERR_STACK(s); } -natsStatus -natsConn_bufferWrite(natsConnection *nc, const char *buffer, int len) +// Low level close call that will do correct cleanup and set +// desired status. Also controls whether user defined callbacks +// will be triggered. The lock should not be held entering this +// function. This function will handle the locking manually. +static void +_close(natsConnection *nc, natsConnStatus state, bool fromPublicClose, bool doCBs) { - natsStatus s = NATS_OK; - int offset = 0; - int avail = 0; - - if (len <= 0) - return NATS_OK; - - if (nc->usePending) - return natsBuf_Append(nc->pending, buffer, len); - - if (nc->sockCtx.useEventLoop) - { - s = natsBuf_Append(nc->bw, buffer, len); - if ((s == NATS_OK) - && (natsBuf_Len(nc->bw) >= nc->opts->ioBufSize) - && !(nc->el.writeAdded)) - { - nc->el.writeAdded = true; - s = nc->opts->evCbs.write(nc->el.data, NATS_EVENT_ACTION_ADD); - if (s != NATS_OK) - nats_setError(s, "Error processing write request: %d - %s", - s, natsStatus_GetText(s)); - } - - return NATS_UPDATE_ERR_STACK(s); - } + natsConn_retain(nc); - if (nc->dontSendInPlace) + if (natsConn_isClosed(nc)) { - s = natsBuf_Append(nc->bw, buffer, len); + nc->state = state; - return NATS_UPDATE_ERR_STACK(s); + natsConn_release(nc); + return; } - // If we have more data that can fit.. - while ((s == NATS_OK) && (len > natsBuf_Available(nc->bw))) - { - // If there is nothing in the buffer... - if (natsBuf_Len(nc->bw) == 0) - { - // Do a single socket write to avoid a copy - s = natsSock_WriteFully(&(nc->sockCtx), buffer + offset, len); - - // We are done - return NATS_UPDATE_ERR_STACK(s); - } + nc->state = NATS_CONN_STATUS_CLOSED; - // We already have data in the buffer, check how many more bytes - // can we fit - avail = natsBuf_Available(nc->bw); + nc->ev.stop(nc->evState); - // Append that much bytes - s = natsBuf_Append(nc->bw, buffer + offset, avail); - - // Flush the buffer - if (s == NATS_OK) - s = natsConn_bufferFlush(nc); + natsSock_Close(nc->sockCtx.fd); + nc->sockCtx.fd = NATS_SOCK_INVALID; - // If success, then decrement what's left to send and update the - // offset. - if (s == NATS_OK) - { - len -= avail; - offset += avail; - } - } + // We need to cleanup some things if the connection was SSL. + // _clearSSL(nc); + nc->sockCtx.fdActive = false; - // If there is data left, the buffer can now hold this data. - if ((s == NATS_OK) && (len > 0)) - s = natsBuf_Append(nc->bw, buffer + offset, len); + if (nc->opts->net.closed != NULL) + nc->opts->net.closed(nc, nc->opts->net.closedClosure); + if (nc->opts->net.disconnected != NULL) + nc->opts->net.disconnected(nc, nc->opts->net.disconnectedClosure); - return NATS_UPDATE_ERR_STACK(s); + nc->state = state; + nc->ev.detach(nc->evState); + natsConn_release(nc); } -natsStatus -natsConn_bufferWriteString(natsConnection *nc, const char *string) +// natsConn_processOpError handles errors from reading or parsing the protocol. +// The lock should not be held entering this function. +bool natsConn_processOpError(natsConnection *nc, natsStatus s) { - natsStatus s = natsConn_bufferWrite(nc, string, (int) strlen(string)); + CONNERROR("ERROR!"); + _close(nc, NATS_CONN_STATUS_CLOSED, false, true); - return NATS_UPDATE_ERR_STACK(s); + return false; } -// _createConn will connect to the server and do the right thing when an -// existing connection is in place. -static natsStatus -_createConn(natsConnection *nc) -{ - natsStatus s = NATS_OK; - - // Sets a deadline for the connect process (not just the low level - // tcp connect. The deadline will be removed when we have received - // the PONG to our initial PING. See _processConnInit(). - natsSock_InitDeadline(&nc->sockCtx, nc->opts->timeout); - - // Set the IP resolution order - nc->sockCtx.orderIP = nc->opts->orderIP; - - // Set ctx.noRandomize based on public NoRandomize option. - nc->sockCtx.noRandomize = nc->opts->noRandomize; - - s = natsSock_ConnectTcp(&(nc->sockCtx), nc->cur->url->host, nc->cur->url->port); - if (s == NATS_OK) - nc->sockCtx.fdActive = true; - - // Need to create or reset the buffer even on failure in case we allow - // retry on failed connect - if ((s == NATS_OK) || nc->opts->retryOnFailedConnect) - { - natsStatus ls = NATS_OK; - - if (nc->bw == NULL) - ls = natsBuf_Create(&(nc->bw), nc->opts->ioBufSize); - else - natsBuf_Reset(nc->bw); - - if (s == NATS_OK) - s = ls; - } - - if (s != NATS_OK) - { - // reset the deadline - natsSock_ClearDeadline(&nc->sockCtx); - } +// static void +// _processPingTimer(natsTimer *timer, void *arg) +// { +// natsConnection *nc = (natsConnection *)arg; - return NATS_UPDATE_ERR_STACK(s); -} +// if (nc->status != NATS_CONN_STATUS_CONNECTED) +// { +// return; +// } -static void -_clearControlContent(natsControl *control) -{ - NATS_FREE(control->op); - NATS_FREE(control->args); -} +// // If we have more PINGs out than PONGs in, consider +// // the connection stale. +// if (++(nc->pout) > nc->opts->maxPingsOut) +// { +// natsConn_processOpError(nc, NATS_STALE_CONNECTION, false); +// return; +// } -static void -_initControlContent(natsControl *control) -{ - control->op = NULL; - control->args = NULL; -} +// _sendPing(nc, NULL); +// } -static bool -_isConnecting(natsConnection *nc) -{ - return nc->status == NATS_CONN_STATUS_CONNECTING; -} +// static void +// _pingStopppedCb(natsTimer *timer, void *closure) +// { +// natsConnection *nc = (natsConnection *)closure; -static bool -_isConnected(natsConnection *nc) -{ - return ((nc->status == NATS_CONN_STATUS_CONNECTED) || natsConn_isDraining(nc)); -} +// natsConn_release(nc); +// } -bool -natsConn_isClosed(natsConnection *nc) +void nats_CloseConnection(natsConnection *nc) { - return nc->status == NATS_CONN_STATUS_CLOSED; -} + if (nc == NULL) + return; -bool -natsConn_isReconnecting(natsConnection *nc) -{ - return (nc->pending != NULL); -} + nats_doNotUpdateErrStack(true); -bool -natsConn_isDraining(natsConnection *nc) -{ - return ((nc->status == NATS_CONN_STATUS_DRAINING_SUBS) || (nc->status == NATS_CONN_STATUS_DRAINING_PUBS)); -} + _close(nc, NATS_CONN_STATUS_CLOSED, true, true); -bool -natsConn_isDrainingPubs(natsConnection *nc) -{ - return nc->status == NATS_CONN_STATUS_DRAINING_PUBS; + nats_doNotUpdateErrStack(false); } -static natsStatus -_readOp(natsConnection *nc, natsControl *control) +void nats_DestroyConnection(natsConnection *nc) { - natsStatus s = NATS_OK; - char buffer[MAX_INFO_MESSAGE_SIZE]; - - buffer[0] = '\0'; - - s = natsSock_ReadLine(&(nc->sockCtx), buffer, sizeof(buffer)); - if (s == NATS_OK) - s = nats_ParseControl(control, buffer); - - return NATS_UPDATE_ERR_STACK(s); + nats_CloseConnection(nc); + natsConn_release(nc); } -static void -_unpackSrvVersion(natsConnection *nc) +void natsConn_freeConn(natsConnection *nc) { - nc->srvVersion.ma = 0; - nc->srvVersion.mi = 0; - nc->srvVersion.up = 0; - - if (nats_IsStringEmpty(nc->info.version)) + if (nc == NULL) return; - sscanf(nc->info.version, "%d.%d.%d", &(nc->srvVersion.ma), &(nc->srvVersion.mi), &(nc->srvVersion.up)); + nats_releasePool(nc->opPool); + nats_releasePool(nc->connectPool); + nats_releasePool(nc->lifetimePool); // will free nc itself + nats_releaseGlobalLib(); } -bool -natsConn_srvVersionAtLeast(natsConnection *nc, int major, int minor, int update) +bool natsConn_srvVersionAtLeast(natsConnection *nc, int major, int minor, int update) { bool ok; - natsConn_Lock(nc); - ok = (((nc->srvVersion.ma > major) - || ((nc->srvVersion.ma == major) && (nc->srvVersion.mi > minor)) - || ((nc->srvVersion.ma == major) && (nc->srvVersion.mi == minor) && (nc->srvVersion.up >= update))) ? true : false); - natsConn_Unlock(nc); + ok = (((nc->srvVersion.ma > major) || ((nc->srvVersion.ma == major) && (nc->srvVersion.mi > minor)) || ((nc->srvVersion.ma == major) && (nc->srvVersion.mi == minor) && (nc->srvVersion.up >= update))) ? true : false); return ok; } -// _processInfo is used to parse the info messages sent -// from the server. -// This function may update the server pool. -static natsStatus -_processInfo(natsConnection *nc, char *info, int len) -{ - natsStatus s = NATS_OK; - nats_JSON *json = NULL; - bool postDiscoveredServersCb = false; - bool postLameDuckCb = false; - - if (info == NULL) - return NATS_OK; - - natsOptions_lock(nc->opts); - postDiscoveredServersCb = (nc->opts->discoveredServersCb != NULL); - postLameDuckCb = (nc->opts->lameDuckCb != NULL); - natsOptions_unlock(nc->opts); - - _clearServerInfo(&(nc->info)); - - s = nats_JSONParse(&json, info, len); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - IFOK(s, nats_JSONGetStr(json, "server_id", &(nc->info.id))); - IFOK(s, nats_JSONGetStr(json, "version", &(nc->info.version))); - IFOK(s, nats_JSONGetStr(json, "host", &(nc->info.host))); - IFOK(s, nats_JSONGetInt(json, "port", &(nc->info.port))); - IFOK(s, nats_JSONGetBool(json, "auth_required", &(nc->info.authRequired))); - IFOK(s, nats_JSONGetBool(json, "tls_required", &(nc->info.tlsRequired))); - IFOK(s, nats_JSONGetBool(json, "tls_available", &(nc->info.tlsAvailable))); - IFOK(s, nats_JSONGetLong(json, "max_payload", &(nc->info.maxPayload))); - IFOK(s, nats_JSONGetArrayStr(json, "connect_urls", - &(nc->info.connectURLs), - &(nc->info.connectURLsCount))); - IFOK(s, nats_JSONGetInt(json, "proto", &(nc->info.proto))); - IFOK(s, nats_JSONGetULong(json, "client_id", &(nc->info.CID))); - IFOK(s, nats_JSONGetStr(json, "nonce", &(nc->info.nonce))); - IFOK(s, nats_JSONGetStr(json, "client_ip", &(nc->info.clientIP))); - IFOK(s, nats_JSONGetBool(json, "ldm", &(nc->info.lameDuckMode))); - IFOK(s, nats_JSONGetBool(json, "headers", &(nc->info.headers))); - - if (s == NATS_OK) - _unpackSrvVersion(nc); - - // The array could be empty/not present on initial connect, - // if advertise is disabled on that server, or servers that - // did not include themselves in the async INFO protocol. - // If empty, do not remove the implicit servers from the pool. - if ((s == NATS_OK) && !nc->opts->ignoreDiscoveredServers && (nc->info.connectURLsCount > 0)) - { - bool added = false; - const char *tlsName = NULL; - - if ((nc->cur != NULL) && (nc->cur->url != NULL) && !nats_HostIsIP(nc->cur->url->host)) - tlsName = (const char*) nc->cur->url->host; - - s = natsSrvPool_addNewURLs(nc->srvPool, - nc->cur->url, - nc->info.connectURLs, - nc->info.connectURLsCount, - tlsName, - &added); - if ((s == NATS_OK) && added && !nc->initc && postDiscoveredServersCb) - natsAsyncCb_PostConnHandler(nc, ASYNC_DISCOVERED_SERVERS); - } - // Process the LDM callback after the above. It will cover cases where - // we have connect URLs and invoke discovered server callback, and case - // where we don't. - if ((s == NATS_OK) && nc->info.lameDuckMode && postLameDuckCb) - natsAsyncCb_PostConnHandler(nc, ASYNC_LAME_DUCK_MODE); - - if (s != NATS_OK) - s = nats_setError(NATS_PROTOCOL_ERROR, - "Invalid protocol: %s", nats_GetLastError(NULL)); - - nats_JSONDestroy(json); - - return NATS_UPDATE_ERR_STACK(s); -} - -// natsConn_processAsyncINFO does the same than processInfo, but is called -// from the parser. Calls processInfo under connection's lock -// protection. -void -natsConn_processAsyncINFO(natsConnection *nc, char *buf, int len) -{ - natsConn_Lock(nc); - // Ignore errors, we will simply not update the server pool... - (void) _processInfo(nc, buf, len); - natsConn_Unlock(nc); -} - -#if defined(NATS_HAS_TLS) -static int -_collectSSLErr(int preverifyOk, X509_STORE_CTX* ctx) -{ - SSL *ssl = NULL; - X509 *cert = X509_STORE_CTX_get_current_cert(ctx); - int depth = X509_STORE_CTX_get_error_depth(ctx); - int err = X509_STORE_CTX_get_error(ctx); - natsConnection *nc = NULL; - - // Retrieve the SSL object, then our connection... - ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); - nc = (natsConnection*) SSL_get_ex_data(ssl, 0); - - // Should we skip serve certificate verification? - if (nc->opts->sslCtx->skipVerify) - return 1; - - if (!preverifyOk) - { - char certName[256]= {0}; - - X509_NAME_oneline(X509_get_subject_name(cert), certName, sizeof(certName)); - - if (err == X509_V_ERR_HOSTNAME_MISMATCH) - { - snprintf_truncate(nc->errStr, sizeof(nc->errStr), "%d:%s:expected=%s:cert=%s", - err, X509_verify_cert_error_string(err), nc->tlsName, - certName); - } - else - { - char issuer[256] = {0}; - - X509_NAME_oneline(X509_get_issuer_name(cert), issuer, sizeof(issuer)); - - snprintf_truncate(nc->errStr, sizeof(nc->errStr), "%d:%s:depth=%d:cert=%s:issuer=%s", - err, X509_verify_cert_error_string(err), depth, - certName, issuer); - } - } - - return preverifyOk; -} -#endif - -// makeTLSConn will wrap an existing Conn using TLS -static natsStatus -_makeTLSConn(natsConnection *nc) +const char * +nats_GetConnectionError(natsConnection *nc) { -#if defined(NATS_HAS_TLS) - natsStatus s = NATS_OK; - SSL *ssl = NULL; - - // Reset nc->errStr before initiating the handshake... - nc->errStr[0] = '\0'; - - natsMutex_Lock(nc->opts->sslCtx->lock); - - s = natsSock_SetBlocking(nc->sockCtx.fd, true); - if (s == NATS_OK) - { - ssl = SSL_new(nc->opts->sslCtx->ctx); - if (ssl == NULL) - { - s = nats_setError(NATS_SSL_ERROR, - "Error creating SSL object: %s", - NATS_SSL_ERR_REASON_STRING); - } - else - { - nats_sslRegisterThreadForCleanup(); - - SSL_set_ex_data(ssl, 0, (void*) nc); - } - } - if (s == NATS_OK) - { - SSL_set_connect_state(ssl); + natsStatus s; - if (SSL_set_fd(ssl, (int) nc->sockCtx.fd) != 1) - { - s = nats_setError(NATS_SSL_ERROR, - "Error connecting the SSL object to a file descriptor : %s", - NATS_SSL_ERR_REASON_STRING); - } - } - if (s == NATS_OK) - { - if (nc->opts->sslCtx->skipVerify) - { - SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL); - } - else - { - nc->tlsName = NULL; + if (nc == NULL) + return "invalid"; - // If we don't force hostname verification, perform it only - // if expectedHostname is set (to be backward compatible with - // releases prior to 2.0.0) - if (nc->opts->sslCtx->expectedHostname != NULL) - nc->tlsName = nc->opts->sslCtx->expectedHostname; -#if defined(NATS_FORCE_HOST_VERIFICATION) - else if (nc->cur->tlsName != NULL) - nc->tlsName = nc->cur->tlsName; - else - nc->tlsName = nc->cur->url->host; -#endif - if (nc->tlsName != NULL) - { -#if defined(NATS_USE_OPENSSL_1_1) - SSL_set_hostflags(ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); - if (!SSL_set1_host(ssl, nc->tlsName)) -#else - X509_VERIFY_PARAM *param = SSL_get0_param(ssl); - X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); - if (!X509_VERIFY_PARAM_set1_host(param, nc->tlsName, 0)) -#endif - s = nats_setError(NATS_SSL_ERROR, "unable to set expected hostname '%s'", nc->tlsName); - } - if (s == NATS_OK) - SSL_set_verify(ssl, SSL_VERIFY_PEER, _collectSSLErr); - } - } - if ((s == NATS_OK) && (SSL_do_handshake(ssl) != 1)) - { - s = nats_setError(NATS_SSL_ERROR, - "SSL handshake error: %s", - (nc->errStr[0] != '\0' ? nc->errStr : NATS_SSL_ERR_REASON_STRING)); - } - // Make sure that if nc-errStr was set in _collectSSLErr but - // the overall handshake is ok, then we clear the error + s = nc->err; if (s == NATS_OK) - { nc->errStr[0] = '\0'; - s = natsSock_SetBlocking(nc->sockCtx.fd, false); - } - - natsMutex_Unlock(nc->opts->sslCtx->lock); - - if (s != NATS_OK) - { - if (ssl != NULL) - SSL_free(ssl); - } - else - { - nc->sockCtx.ssl = ssl; - } - - return NATS_UPDATE_ERR_STACK(s); -#else - return nats_setError(NATS_ILLEGAL_STATE, "%s", NO_SSL_ERR); -#endif -} + else if (nc->errStr[0] == '\0') + snprintf(nc->errStr, sizeof(nc->errStr), "%s", natsStatus_GetText(s)); -// This will check to see if the connection should be -// secure. This can be dictated from either end and should -// only be called after the INIT protocol has been received. -static natsStatus -_checkForSecure(natsConnection *nc) -{ - natsStatus s = NATS_OK; - - // Check for mismatch in setups - if (nc->opts->secure && !nc->info.tlsRequired && !nc->info.tlsAvailable) - s = nats_setDefaultError(NATS_SECURE_CONNECTION_WANTED); - else if (nc->info.tlsRequired && !nc->opts->secure) - { - // Switch to Secure since server needs TLS. - s = natsOptions_SetSecure(nc->opts, true); - } - - if ((s == NATS_OK) && nc->opts->secure) - s = _makeTLSConn(nc); - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_processExpectedInfo(natsConnection *nc) -{ - natsControl control; - natsStatus s; - - _initControlContent(&control); - - s = _readOp(nc, &control); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - if ((s == NATS_OK) - && ((control.op == NULL) - || (strcmp(control.op, _INFO_OP_) != 0))) - { - s = nats_setError(NATS_PROTOCOL_ERROR, - "Unexpected protocol: got '%s' instead of '%s'", - (control.op == NULL ? "" : control.op), - _INFO_OP_); - } - if (s == NATS_OK) - s = _processInfo(nc, control.args, -1); - if (s == NATS_OK) - s = _checkForSecure(nc); - - _clearControlContent(&control); - - return NATS_UPDATE_ERR_STACK(s); -} - -static char* -_escape(char *origin) -{ - char escChar[] = {'\a', '\b', '\f', '\n', '\r', '\t', '\v', '\\'}; - char escRepl[] = {'a', 'b', 'f', 'n', 'r', 't', 'v', '\\'}; - int l = (int) strlen(origin); - int ec = 0; - char *dest = NULL; - char *ptr = NULL; - int i; - int j; - - for (i=0; iopts; - const char *token= NULL; - const char *user = NULL; - const char *pwd = NULL; - const char *name = NULL; - char *sig = NULL; - char *ujwt = NULL; - char *nkey = NULL; - int res; - unsigned char *sigRaw = NULL; - int sigRawLen = 0; - - // Check if NoEcho is set and we have a server that supports it. - if (opts->noEcho && (nc->info.proto < 1)) - return NATS_NO_SERVER_SUPPORT; - - if (nc->cur->url->username != NULL) - user = nc->cur->url->username; - if (nc->cur->url->password != NULL) - pwd = nc->cur->url->password; - if ((user != NULL) && (pwd == NULL)) - { - token = user; - user = NULL; - } - if ((user == NULL) && (token == NULL)) - { - // Take from options (possibly all NULL) - user = opts->user; - pwd = opts->password; - token = opts->token; - nkey = opts->nkey; - - // Options take precedence for an implicit URL. If above is still - // empty, we will check if we have saved a user from an explicit - // URL in the server pool. - if (nats_IsStringEmpty(user) - && nats_IsStringEmpty(token) - && (nc->srvPool->user != NULL)) - { - user = nc->srvPool->user; - pwd = nc->srvPool->pwd; - // Again, if there is no password, assume username is token. - if (pwd == NULL) - { - token = user; - user = NULL; - } - } - } - - if (opts->userJWTHandler != NULL) - { - char *errTxt = NULL; - bool userCb = opts->userJWTHandler != natsConn_userCreds; - - // If callback is not the internal one, we need to release connection lock. - if (userCb) - natsConn_Unlock(nc); - - s = opts->userJWTHandler(&ujwt, &errTxt, (void*) opts->userJWTClosure); - - if (userCb) - { - natsConn_Lock(nc); - if (natsConn_isClosed(nc) && (s == NATS_OK)) - s = NATS_CONNECTION_CLOSED; - } - if ((s != NATS_OK) && (errTxt != NULL)) - { - s = nats_setError(s, "%s", errTxt); - NATS_FREE(errTxt); - } - if ((s == NATS_OK) && !nats_IsStringEmpty(nkey)) - s = nats_setError(NATS_ILLEGAL_STATE, "%s", "user JWT callback and NKey cannot be both specified"); - - if ((s == NATS_OK) && (ujwt != NULL)) - { - char *tmp = _escape(ujwt); - if (tmp == NULL) - { - s = nats_setDefaultError(NATS_NO_MEMORY); - } - else if (tmp != ujwt) - { - NATS_FREE(ujwt); - ujwt = tmp; - } - } - } - - if ((s == NATS_OK) && (!nats_IsStringEmpty(ujwt) || !nats_IsStringEmpty(nkey))) - { - char *errTxt = NULL; - bool userCb = opts->sigHandler != natsConn_signatureHandler; - - if (userCb) - natsConn_Unlock(nc); - - s = opts->sigHandler(&errTxt, &sigRaw, &sigRawLen, nc->info.nonce, opts->sigClosure); - - if (userCb) - { - natsConn_Lock(nc); - if (natsConn_isClosed(nc) && (s == NATS_OK)) - s = NATS_CONNECTION_CLOSED; - } - if ((s != NATS_OK) && (errTxt != NULL)) - { - s = nats_setError(s, "%s", errTxt); - NATS_FREE(errTxt); - } - if (s == NATS_OK) - s = nats_Base64RawURL_EncodeString((const unsigned char*) sigRaw, sigRawLen, &sig); - } - - if ((s == NATS_OK) && (opts->tokenCb != NULL)) - { - if (token != NULL) - s = nats_setError(NATS_ILLEGAL_STATE, "%s", "Token and token handler options cannot be both set"); - - if (s == NATS_OK) - token = opts->tokenCb(opts->tokenCbClosure); - } - - if ((s == NATS_OK) && (opts->name != NULL)) - name = opts->name; - - if (s == NATS_OK) - { - // If our server does not support headers then we can't do them or no responders. - const char *hdrs = nats_GetBoolStr(nc->info.headers); - const char *noResponders = nats_GetBoolStr(nc->info.headers && !nc->opts->disableNoResponders); - - res = nats_asprintf(proto, - "CONNECT {\"verbose\":%s,\"pedantic\":%s,%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\"tls_required\":%s," \ - "\"name\":\"%s\",\"lang\":\"%s\",\"version\":\"%s\",\"protocol\":%d,\"echo\":%s," \ - "\"headers\":%s,\"no_responders\":%s}%s", - nats_GetBoolStr(opts->verbose), - nats_GetBoolStr(opts->pedantic), - (nkey != NULL ? "\"nkey\":\"" : ""), - (nkey != NULL ? nkey : ""), - (nkey != NULL ? "\"," : ""), - (ujwt != NULL ? "\"jwt\":\"" : ""), - (ujwt != NULL ? ujwt : ""), - (ujwt != NULL ? "\"," : ""), - (sig != NULL ? "\"sig\":\"" : ""), - (sig != NULL ? sig : ""), - (sig != NULL ? "\"," : ""), - (user != NULL ? "\"user\":\"" : ""), - (user != NULL ? user : ""), - (user != NULL ? "\"," : ""), - (pwd != NULL ? "\"pass\":\"" : ""), - (pwd != NULL ? pwd : ""), - (pwd != NULL ? "\"," : ""), - (token != NULL ? "\"auth_token\":\"" :""), - (token != NULL ? token : ""), - (token != NULL ? "\"," : ""), - nats_GetBoolStr(opts->secure), - (name != NULL ? name : ""), - CString, NATS_VERSION_STRING, - CLIENT_PROTO_INFO, - nats_GetBoolStr(!opts->noEcho), - hdrs, - noResponders, - _CRLF_); - if (res < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - - NATS_FREE(ujwt); - NATS_FREE(sigRaw); - NATS_FREE(sig); - - return s; -} - -natsStatus -natsConn_sendUnsubProto(natsConnection *nc, int64_t subId, int max) -{ - natsStatus s = NATS_OK; - char *proto = NULL; - int res = 0; - - if (max > 0) - res = nats_asprintf(&proto, _UNSUB_PROTO_, subId, max); - else - res = nats_asprintf(&proto, _UNSUB_NO_MAX_PROTO_, subId); - - if (res < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - { - s = natsConn_bufferWriteString(nc, proto); - NATS_FREE(proto); - } - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsConn_sendSubProto(natsConnection *nc, const char *subject, const char *queue, int64_t sid) -{ - natsStatus s = NATS_OK; - char *proto = NULL; - int res = 0; - - res = nats_asprintf(&proto, _SUB_PROTO_, subject, (queue == NULL ? "" : queue), sid); - if (res < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - { - s = natsConn_bufferWriteString(nc, proto); - NATS_FREE(proto); - proto = NULL; - } - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_resendSubscriptions(natsConnection *nc) -{ - natsStatus s = NATS_OK; - natsSubscription *sub = NULL; - natsHashIter iter; - int adjustedMax; - natsSubscription **subs = NULL; - int i = 0; - int count = 0; - - // Since we are going to send protocols to the server, we don't want to - // be holding the subsMu lock (which is used in processMsg). So copy - // the subscriptions in a temporary array. - natsMutex_Lock(nc->subsMu); - if (natsHash_Count(nc->subs) > 0) - { - subs = NATS_CALLOC(natsHash_Count(nc->subs), sizeof(natsSubscription*)); - if (subs == NULL) - s = NATS_NO_MEMORY; - - if (s == NATS_OK) - { - void *p = NULL; - - natsHashIter_Init(&iter, nc->subs); - while (natsHashIter_Next(&iter, NULL, &p)) - { - subs[count++] = (natsSubscription*) p; - } - natsHashIter_Done(&iter); - } - } - natsMutex_Unlock(nc->subsMu); - - SET_WRITE_DEADLINE(nc); - - for (i=0; (s == NATS_OK) && (ijsi != NULL) && (sub->jsi->ordered)) - { - jsSub_resetOrderedConsumer(sub, sub->jsi->sseq+1); - natsSub_Unlock(sub); - continue; - } - if (natsSub_drainStarted(sub)) - { - natsSub_Unlock(sub); - continue; - } - if (sub->max > 0) - { - if (sub->delivered < sub->max) - adjustedMax = (int)(sub->max - sub->delivered); - - // The adjusted max could be 0 here if the number of delivered - // messages have reached the max, if so, unsubscribe. - if (adjustedMax == 0) - { - natsSub_Unlock(sub); - s = natsConn_sendUnsubProto(nc, sub->sid, 0); - continue; - } - } - - s = natsConn_sendSubProto(nc, sub->subject, sub->queue, sub->sid); - if ((s == NATS_OK) && (adjustedMax > 0)) - s = natsConn_sendUnsubProto(nc, sub->sid, adjustedMax); - - // Hold the lock up to that point so we are sure not to resend - // any SUB/UNSUB for a subscription that is in draining mode. - natsSub_Unlock(sub); - } - - NATS_FREE(subs); - - return s; -} - -static natsStatus -_flushReconnectPendingItems(natsConnection *nc) -{ - natsStatus s = NATS_OK; - - if (nc->pending == NULL) - return NATS_OK; - - if (natsBuf_Len(nc->pending) > 0) - { - // Flush pending buffer - s = natsConn_bufferWrite(nc, natsBuf_Data(nc->pending), - natsBuf_Len(nc->pending)); - - // Regardless of outcome, we must clear the pending buffer - // here to avoid duplicates (if the flush were to fail - // with some messages/partial messages being sent). - natsBuf_Reset(nc->pending); - } - - return s; -} - -static void -_removePongFromList(natsConnection *nc, natsPong *pong) -{ - if (pong->prev != NULL) - pong->prev->next = pong->next; - - if (pong->next != NULL) - pong->next->prev = pong->prev; - - if (nc->pongs.head == pong) - nc->pongs.head = pong->next; - - if (nc->pongs.tail == pong) - nc->pongs.tail = pong->prev; - - pong->prev = pong->next = NULL; -} - -// When the connection is closed, or is disconnected and we are about -// to reconnect, we need to unblock all pending natsConnection_Flush[Timeout]() -// calls: there is no chance that a PING sent to a server is going to be -// echoed by the new server. -static void -_clearPendingFlushRequests(natsConnection *nc) -{ - natsPong *pong = NULL; - - while ((pong = nc->pongs.head) != NULL) - { - // Pop from the queue - _removePongFromList(nc, pong); - - // natsConnection_Flush[Timeout]() is waiting on a condition - // variable and exit when this value is != 0. "Flush" will - // return an error to the caller if the connection status - // is not CONNECTED at that time. - pong->id = -1; - - // There may be more than one user-thread making - // natsConnection_Flush() calls. - natsCondition_Broadcast(nc->pongs.cond); - } - - nc->pongs.incoming = 0; - nc->pongs.outgoingPings = 0; -} - -// Dispose of the respInfo object. -// The boolean `needsLock` indicates if connection lock is required or not. -void -natsConn_disposeRespInfo(natsConnection *nc, respInfo *resp, bool needsLock) -{ - if (resp == NULL) - return; - - // Destroy the message if present in the respInfo object. If it has - // been returned to the RequestX() calls, resp->msg will be NULL here. - if (resp->msg != NULL) - { - natsMsg_Destroy(resp->msg); - resp->msg = NULL; - } - if (!resp->pooled) - { - natsCondition_Destroy(resp->cond); - natsMutex_Destroy(resp->mu); - NATS_FREE(resp); - } - else - { - if (needsLock) - natsConn_Lock(nc); - - resp->closed = false; - resp->closedSts = NATS_OK; - resp->removed = false; - nc->respPool[nc->respPoolIdx++] = resp; - - if (needsLock) - natsConn_Unlock(nc); - } -} - -// Destroy the pool of respInfo objects. -void -natsConn_destroyRespPool(natsConnection *nc) -{ - int i; - respInfo *info; - - for (i=0; irespPoolSize; i++) - { - info = nc->respPool[i]; - info->pooled = false; - natsConn_disposeRespInfo(nc, info, false); - } - NATS_FREE(nc->respPool); -} - -// Creates a new respInfo object, binds it to the request's specific -// subject (that is set in respInbox). The respInfo object is returned. -// Connection's lock is held on entry. -natsStatus -natsConn_addRespInfo(respInfo **newResp, natsConnection *nc, char *respInbox, int respInboxSize) -{ - respInfo *resp = NULL; - natsStatus s = NATS_OK; - - if (nc->respPoolIdx > 0) - { - resp = nc->respPool[--nc->respPoolIdx]; - } - else - { - resp = (respInfo *) NATS_CALLOC(1, sizeof(respInfo)); - if (resp == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - if (s == NATS_OK) - s = natsMutex_Create(&(resp->mu)); - if (s == NATS_OK) - s = natsCondition_Create(&(resp->cond)); - if ((s == NATS_OK) && (nc->respPoolSize < RESP_INFO_POOL_MAX_SIZE)) - { - resp->pooled = true; - nc->respPoolSize++; - } - } - - if (s == NATS_OK) - { - nc->respId[nc->respIdPos] = '0' + nc->respIdVal; - nc->respId[nc->respIdPos + 1] = '\0'; - - // Build the response inbox - memcpy(respInbox, nc->respSub, nc->reqIdOffset); - respInbox[nc->reqIdOffset-1] = '.'; - memcpy(respInbox+nc->reqIdOffset, nc->respId, nc->respIdPos + 2); // copy the '\0' of respId - - nc->respIdVal++; - if (nc->respIdVal == 10) - { - nc->respIdVal = 0; - if (nc->respIdPos > 0) - { - bool shift = true; - int i, j; - - for (i=nc->respIdPos-1; i>=0; i--) - { - if (nc->respId[i] != '9') - { - nc->respId[i]++; - - for (j=i+1; j<=nc->respIdPos-1; j++) - nc->respId[j] = '0'; - - shift = false; - break; - } - } - if (shift) - { - nc->respId[0] = '1'; - - for (i=1; i<=nc->respIdPos; i++) - nc->respId[i] = '0'; - - nc->respIdPos++; - } - } - else - { - nc->respId[0] = '1'; - nc->respIdPos++; - } - if (nc->respIdPos == NATS_MAX_REQ_ID_LEN) - nc->respIdPos = 0; - } - - s = natsStrHash_Set(nc->respMap, respInbox+nc->reqIdOffset, true, - (void*) resp, NULL); - } - - if (s == NATS_OK) - *newResp = resp; - else - natsConn_disposeRespInfo(nc, resp, false); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsConn_initInbox(natsConnection *nc, char *buf, int bufSize, char **newInbox, bool *allocated) -{ - int needed = nc->inboxPfxLen+NUID_BUFFER_LEN+1; - char *inbox = buf; - bool created = false; - natsStatus s; - - if (needed > bufSize) - { - inbox = NATS_MALLOC(needed); - if (inbox == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - created = true; - } - memcpy(inbox, nc->inboxPfx, nc->inboxPfxLen); - // This will add the terminal '\0'; - s = natsNUID_Next(inbox+nc->inboxPfxLen, NUID_BUFFER_LEN+1); - if (s == NATS_OK) - { - *newInbox = inbox; - if (allocated != NULL) - *allocated = created; - } - else if (created) - NATS_FREE(inbox); - - return s; -} - -natsStatus -natsConn_newInbox(natsConnection *nc, natsInbox **newInbox) -{ - natsStatus s; - int inboxLen = nc->inboxPfxLen+NUID_BUFFER_LEN+1; - char *inbox = NATS_MALLOC(inboxLen); - - if (inbox == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - s = natsConn_initInbox(nc, inbox, inboxLen, (char**) newInbox, NULL); - if (s != NATS_OK) - NATS_FREE(inbox); - return s; -} - -// Initialize some of the connection's fields used for request/reply mapping. -// Connection's lock is held on entry. -natsStatus -natsConn_initResp(natsConnection *nc, natsMsgHandler cb) -{ - natsStatus s = NATS_OK; - - nc->respPool = NATS_CALLOC(RESP_INFO_POOL_MAX_SIZE, sizeof(respInfo*)); - if (nc->respPool == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - if (s == NATS_OK) - s = natsStrHash_Create(&nc->respMap, 4); - if (s == NATS_OK) - s = natsConn_newInbox(nc, (natsInbox**) &nc->respSub); - if (s == NATS_OK) - { - char *inbox = NULL; - - if (nats_asprintf(&inbox, "%s.*", nc->respSub) < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - s = natsConn_subscribeNoPoolNoLock(&(nc->respMux), nc, inbox, cb, (void*) nc); - - NATS_FREE(inbox); - } - if (s != NATS_OK) - { - natsInbox_Destroy(nc->respSub); - nc->respSub = NULL; - natsStrHash_Destroy(nc->respMap); - nc->respMap = NULL; - NATS_FREE(nc->respPool); - nc->respPool = NULL; - } - - return NATS_UPDATE_ERR_STACK(s); -} - -// This will clear any pending Request calls. -// Lock is assumed to be held by the caller. -static void -_clearPendingRequestCalls(natsConnection *nc, natsStatus reason) -{ - natsStrHashIter iter; - void *p = NULL; - - if (nc->respMap == NULL) - return; - - natsStrHashIter_Init(&iter, nc->respMap); - while (natsStrHashIter_Next(&iter, NULL, &p)) - { - respInfo *val = (respInfo*) p; - natsMutex_Lock(val->mu); - val->closed = true; - val->closedSts = reason; - val->removed = true; - natsCondition_Signal(val->cond); - natsMutex_Unlock(val->mu); - natsStrHashIter_RemoveCurrent(&iter); - } - natsStrHashIter_Done(&iter); -} - -static void -_clearSSL(natsConnection *nc) -{ - if (nc->sockCtx.ssl == NULL) - return; - - SSL_free(nc->sockCtx.ssl); - nc->sockCtx.ssl = NULL; -} - -// Try to reconnect using the option parameters. -// This function assumes we are allowed to reconnect. -static void -_doReconnect(void *arg) -{ - natsStatus s = NATS_OK; - natsConnection *nc = (natsConnection*) arg; - natsSrvPool *pool = NULL; - int64_t sleepTime = 0; - struct threadsToJoin ttj; - natsThread *rt = NULL; - int wlf = 0; - bool doSleep = false; - int64_t jitter = 0; - int i = 0; - natsCustomReconnectDelayHandler crd = NULL; - void *crdClosure = NULL; - bool postDisconnectedCb = false; - bool postReconnectedCb = false; - bool postConnectedCb = false; - - natsOptions_lock(nc->opts); - postDisconnectedCb = (nc->opts->disconnectedCb != NULL); - postReconnectedCb = (nc->opts->reconnectedCb != NULL); - postConnectedCb = (nc->opts->connectedCb != NULL); - natsOptions_unlock(nc->opts); - - natsConn_Lock(nc); - - _initThreadsToJoin(&ttj, nc, false); - - natsConn_Unlock(nc); - - _joinThreads(&ttj); - - natsConn_Lock(nc); - - // Clear any error. - nc->err = NATS_OK; - nc->errStr[0] = '\0'; - - pool = nc->srvPool; - - // Perform appropriate callback if needed for a disconnect. - // (do not do this if we are here on initial connect failure) - if (!nc->initc && postDisconnectedCb) - natsAsyncCb_PostConnHandler(nc, ASYNC_DISCONNECTED); - - crd = nc->opts->customReconnectDelayCB; - if (crd == NULL) - { - jitter = nc->opts->reconnectJitter; - // TODO: since we sleep only after the whole list has been tried, we can't - // rely on individual *natsSrv to know if it is a TLS or non-TLS url. - // We have to pick which type of jitter to use, for now, we use these hints: - if (nc->opts->secure || (nc->opts->sslCtx != NULL)) - jitter = nc->opts->reconnectJitterTLS; - } - else - crdClosure = nc->opts->customReconnectDelayCBClosure; - - // Note that the pool's size may decrement after the call to - // natsSrvPool_GetNextServer. - for (i=0; (s == NATS_OK) && (natsSrvPool_GetSize(pool) > 0); ) - { - nc->cur = natsSrvPool_GetNextServer(pool, nc->opts, nc->cur); - if (nc->cur == NULL) - { - nc->err = NATS_NO_SERVER; - break; - } - - doSleep = (i+1 >= natsSrvPool_GetSize(pool)); - - if (doSleep) - { - i = 0; - if (crd != NULL) - { - wlf++; - natsConn_Unlock(nc); - sleepTime = crd(nc, wlf, crdClosure); - natsConn_Lock(nc); - } - else - { - sleepTime = nc->opts->reconnectWait; - if (jitter > 0) - sleepTime += rand() % jitter; - } - if (natsConn_isClosed(nc)) - break; - natsCondition_TimedWait(nc->reconnectCond, nc->mu, sleepTime); - } - else - { - i++; - natsConn_Unlock(nc); - natsThread_Yield(); - natsConn_Lock(nc); - } - - // Check if we have been closed first. - if (natsConn_isClosed(nc)) - break; - - // Mark that we tried a reconnect - nc->cur->reconnects += 1; - - // Try to create a new connection - s = _createConn(nc); - if (s != NATS_OK) - { - // Reset error here. We will return NATS_NO_SERVERS at the end of - // this loop if appropriate. - nc->err = NATS_OK; - - // Reset status - s = NATS_OK; - - // Not yet connected, retry... - // Continue to hold the lock - continue; - } - - // We are reconnected - nc->stats.reconnects += 1; - - // Process Connect logic - s = _processConnInit(nc); - // Check if connection has been closed (it could happen due to - // user callback that may be invoked as part of the connect) - // or if the reconnect process should be aborted. - if (natsConn_isClosed(nc) || nc->ar) - { - if (s == NATS_OK) - s = nats_setError(NATS_CONNECTION_CLOSED, "%s", "connection has been closed/destroyed while reconnecting"); - break; - } - - // We have a valid FD. We are now going to send data directly - // to the newly connected server, so we need to disable the - // use of 'pending' for the moment. - nc->usePending = false; - - // Send existing subscription state - if (s == NATS_OK) - s = _resendSubscriptions(nc); - - // Now send off and clear pending buffer - if (s == NATS_OK) - s = _flushReconnectPendingItems(nc); - - if (s != NATS_OK) - { - // In case we were at the last iteration, this is the error - // we will report. - nc->err = s; - - // Reset status - s = NATS_OK; - - // Close the socket since we were connected, but a problem occurred. - // (not doing this would cause an FD leak) - natsSock_Close(nc->sockCtx.fd); - nc->sockCtx.fd = NATS_SOCK_INVALID; - - // We need to re-activate the use of pending since we - // may go back to sleep and release the lock - nc->usePending = true; - natsBuf_Reset(nc->bw); - - // We need to cleanup some things if the connection was SSL. - _clearSSL(nc); - - nc->status = NATS_CONN_STATUS_RECONNECTING; - continue; - } - - // This is where we are truly connected. - nc->status = NATS_CONN_STATUS_CONNECTED; - - // No more failure allowed past this point. - - // Clear connection's last error - nc->err = NATS_OK; - nc->errStr[0] = '\0'; - - // Clear the possible current lastErr - nc->cur->lastAuthErrCode = 0; - - // Clear out server stats for the server we connected to.. - nc->cur->didConnect = true; - nc->cur->reconnects = 0; - - // At this point we know that we don't need the pending buffer - // anymore. Destroy now. - natsBuf_Destroy(nc->pending); - nc->pending = NULL; - nc->usePending = false; - - // Normally only set in _connect() but we need in case we allow - // reconnect logic on initial connect failure. - if (nc->initc) - { - // This was the initial connect. Set this to false. - nc->initc = false; - // Invoke the callback. - if (postConnectedCb) - natsAsyncCb_PostConnHandler(nc, ASYNC_CONNECTED); - } - else - { - // Call reconnectedCB if appropriate. Since we are in a separate - // thread, we could invoke the callback directly, however, we - // still post it so all callbacks from a connection are serialized. - if (postReconnectedCb) - natsAsyncCb_PostConnHandler(nc, ASYNC_RECONNECTED); - } - - nc->inReconnect--; - if (nc->reconnectThread != NULL) - { - rt = nc->reconnectThread; - nc->reconnectThread = NULL; - } - - // Release lock here, we will return below. - natsConn_Unlock(nc); - - // Make sure we flush everything - (void) natsConnection_Flush(nc); - - // Release to compensate for the retain in processOpError. - natsConn_release(nc); - - if (rt != NULL) - { - natsThread_Detach(rt); - natsThread_Destroy(rt); - } - - return; - } - - // Call into close.. We have no servers left.. - if (nc->err == NATS_OK) - nc->err = NATS_NO_SERVER; - - nc->inReconnect--; - nc->rle = true; - natsConn_Unlock(nc); - - _close(nc, NATS_CONN_STATUS_CLOSED, false, true); - - // Release to compensate for the retain in processOpError. - natsConn_release(nc); -} - -// If the connection has the option `sendAsap`, flushes the buffer -// directly, otherwise, notifies the flusher thread that there is -// pending data to send to the server. -natsStatus -natsConn_flushOrKickFlusher(natsConnection *nc) -{ - natsStatus s = NATS_OK; - - if (nc->opts->sendAsap) - { - s = natsConn_bufferFlush(nc); - } - else if (!(nc->flusherSignaled) && (nc->bw != NULL)) - { - nc->flusherSignaled = true; - natsCondition_Signal(nc->flusherCond); - } - return s; -} - -// reads a protocol one byte at a time. -static natsStatus -_readProto(natsConnection *nc, natsBuffer **proto) -{ - natsStatus s = NATS_OK; - char protoEnd = '\n'; - natsBuffer *buf = NULL; - char oneChar[1] = { '\0' }; - - s = natsBuf_Create(&buf, 10); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - for (;;) - { - s = natsSock_Read(&(nc->sockCtx), oneChar, 1, NULL); - if (s != NATS_OK) - { - natsBuf_Destroy(buf); - return NATS_UPDATE_ERR_STACK(s); - } - s = natsBuf_AppendByte(buf, oneChar[0]); - if (s != NATS_OK) - { - natsBuf_Destroy(buf); - return NATS_UPDATE_ERR_STACK(s); - } - if (oneChar[0] == protoEnd) - break; - } - s = natsBuf_AppendByte(buf, '\0'); - if (s != NATS_OK) - { - natsBuf_Destroy(buf); - return NATS_UPDATE_ERR_STACK(s); - } - *proto = buf; - return NATS_OK; -} - -static natsStatus -_sendConnect(natsConnection *nc) -{ - natsStatus s = NATS_OK; - char *cProto = NULL; - natsBuffer *proto = NULL; - bool rup = (nc->pending != NULL); - - // Create the CONNECT protocol - s = _connectProto(nc, &cProto); - - // Because we now possibly release the connection lock in _connectProto() - // (if there is user callbacks for jwt/signing keys), we can't have the - // set/reset of usePending be in doReconnect(). There are then windows in - // which a user Publish() could sneak in a try to send to socket. So limit - // the disable/re-enable of that boolean to around this buffer write/flush - // calls. - if (rup) - nc->usePending = false; - - // Add it to the buffer - if (s == NATS_OK) - s = natsConn_bufferWriteString(nc, cProto); - - // Add the PING protocol to the buffer - if (s == NATS_OK) - s = natsConn_bufferWrite(nc, _PING_OP_, _PING_OP_LEN_); - if (s == NATS_OK) - s = natsConn_bufferWrite(nc, _CRLF_, _CRLF_LEN_); - - // Flush the buffer - if (s == NATS_OK) - s = natsConn_bufferFlush(nc); - - // Reset here.. - if (rup) - nc->usePending = true; - - // Now read the response from the server. - if (s == NATS_OK) - s = _readProto(nc, &proto); - - // If Verbose is set, we expect +OK first. - if ((s == NATS_OK) && nc->opts->verbose) - { - // Check protocol is as expected - if (strncmp(natsBuf_Data(proto), _OK_OP_, _OK_OP_LEN_) != 0) - { - s = nats_setError(NATS_PROTOCOL_ERROR, - "Expected '%s', got '%s'", - _OK_OP_, natsBuf_Data(proto)); - } - natsBuf_Destroy(proto); - proto = NULL; - - // Read the rest now... - if (s == NATS_OK) - s = _readProto(nc, &proto); - } - - // We except the PONG protocol - if ((s == NATS_OK) && (strncmp(natsBuf_Data(proto), _PONG_OP_, _PONG_OP_LEN_) != 0)) - { - // But it could be something else, like -ERR - - if (strncmp(natsBuf_Data(proto), _ERR_OP_, _ERR_OP_LEN_) == 0) - { - char buffer[256]; - int authErrCode = 0; - - buffer[0] = '\0'; - snprintf_truncate(buffer, sizeof(buffer), "%s", natsBuf_Data(proto)); - - // Remove -ERR, trim spaces and quotes. - nats_NormalizeErr(buffer); - - // Look for auth errors. - if ((authErrCode = _checkAuthError(buffer)) != 0) - { - // This sets nc->err to NATS_CONNECTION_AUTH_FAILED - // copy content of buffer into nc->errStr. - _processAuthError(nc, authErrCode, buffer); - s = nc->err; - } - else - s = NATS_ERR; - - // Update stack - s = nats_setError(s, "%s", buffer); - } - else - { - s = nats_setError(NATS_PROTOCOL_ERROR, - "Expected '%s', got '%s'", - _PONG_OP_, natsBuf_Data(proto)); - } - } - // Destroy proto (ok if proto is NULL). - natsBuf_Destroy(proto); - - if (s == NATS_OK) - nc->status = NATS_CONN_STATUS_CONNECTED; - - NATS_FREE(cProto); - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_processConnInit(natsConnection *nc) -{ - natsStatus s = NATS_OK; - - nc->status = NATS_CONN_STATUS_CONNECTING; - - // Process the INFO protocol that we should be receiving - s = _processExpectedInfo(nc); - - // Send the CONNECT and PING protocol, and wait for the PONG. - if (s == NATS_OK) - s = _sendConnect(nc); - - // Clear our deadline, regardless of error - natsSock_ClearDeadline(&nc->sockCtx); - - // If there is no write deadline option, switch to blocking socket here... - if ((s == NATS_OK) && (nc->opts->writeDeadline <= 0)) - s = natsSock_SetBlocking(nc->sockCtx.fd, true); - - // Start the readLoop and flusher threads - if (s == NATS_OK) - s = _spinUpSocketWatchers(nc); - - if ((s == NATS_OK) && (nc->opts->evLoop != NULL)) - { - s = natsSock_SetBlocking(nc->sockCtx.fd, false); - - // If we are reconnecting, buffer will have already been allocated - if ((s == NATS_OK) && (nc->el.buffer == NULL)) - { - nc->el.buffer = (char*) malloc(nc->opts->ioBufSize); - if (nc->el.buffer == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - if (s == NATS_OK) - { - // Set this first in case the event loop triggers the first READ - // event just after this call returns. - nc->sockCtx.useEventLoop = true; - - s = nc->opts->evCbs.attach(&(nc->el.data), - nc->opts->evLoop, - nc, - (int) nc->sockCtx.fd); - if (s == NATS_OK) - { - nc->el.attached = true; - } - else - { - nc->sockCtx.useEventLoop = false; - - nats_setError(s, - "Error attaching to the event loop: %d - %s", - s, natsStatus_GetText(s)); - } - } - } - - return NATS_UPDATE_ERR_STACK(s); -} - -// Main connect function. Will connect to the server -static natsStatus -_connect(natsConnection *nc) -{ - natsStatus s = NATS_OK; - natsStatus retSts= NATS_OK; - natsSrvPool *pool = NULL; - int i = 0; - int l = 0; - int max = 0; - int64_t wtime = 0; - bool retry = false; - bool retryOnFailedConnect = false; - bool hasConnectedCb = false; - - natsOptions_lock(nc->opts); - hasConnectedCb = (nc->opts->connectedCb != NULL); - retryOnFailedConnect = nc->opts->retryOnFailedConnect; - natsOptions_unlock(nc->opts); - - natsConn_Lock(nc); - nc->initc = true; - - pool = nc->srvPool; - - if ((retryOnFailedConnect) && !hasConnectedCb) - { - retry = true; - max = nc->opts->maxReconnect; - wtime = nc->opts->reconnectWait; - } - - for (;;) - { - // The pool may change inside the loop iteration due to INFO protocol. - for (i = 0; i < natsSrvPool_GetSize(pool); i++) - { - nc->cur = natsSrvPool_GetSrv(pool,i); - - s = _createConn(nc); - if (s == NATS_OK) - { - s = _processConnInit(nc); - - if (s == NATS_OK) - { - nc->cur->lastAuthErrCode = 0; - natsSrvPool_SetSrvDidConnect(pool, i, true); - natsSrvPool_SetSrvReconnects(pool, i, 0); - retSts = NATS_OK; - retry = false; - break; - } - else - { - retSts = s; - - natsConn_Unlock(nc); - - _close(nc, NATS_CONN_STATUS_DISCONNECTED, false, false); - - natsConn_Lock(nc); - - nc->cur = NULL; - } - } - else - { - if (natsConn_isClosed(nc)) - { - s = NATS_CONNECTION_CLOSED; - break; - } - - if (s == NATS_IO_ERROR) - retSts = NATS_OK; - } - } - - if (!retry) - break; - - l++; - if ((max > 0) && (l > max)) - break; - - if (wtime > 0) - nats_Sleep(wtime); - } - - // If not connected and retry asynchronously on failed connect - if ((nc->status != NATS_CONN_STATUS_CONNECTED) - && retryOnFailedConnect - && hasConnectedCb) - { - natsConn_Unlock(nc); - - if (_processOpError(nc, retSts, true)) - { - nats_clearLastError(); - return NATS_NOT_YET_CONNECTED; - } - - natsConn_Lock(nc); - } - - if ((retSts == NATS_OK) && (nc->status != NATS_CONN_STATUS_CONNECTED)) - s = nats_setDefaultError(NATS_NO_SERVER); - - nc->initc = false; - natsConn_Unlock(nc); - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_evStopPolling(natsConnection *nc) -{ - natsStatus s; - - nc->sockCtx.useEventLoop = false; - nc->el.writeAdded = false; - s = nc->opts->evCbs.read(nc->el.data, NATS_EVENT_ACTION_REMOVE); - if (s == NATS_OK) - s = nc->opts->evCbs.write(nc->el.data, NATS_EVENT_ACTION_REMOVE); - - return s; -} - -// _processOpError handles errors from reading or parsing the protocol. -// The lock should not be held entering this function. -static bool -_processOpError(natsConnection *nc, natsStatus s, bool initialConnect) -{ - natsConn_Lock(nc); - - if (!initialConnect) - { - if (_isConnecting(nc) || natsConn_isClosed(nc) || (nc->inReconnect > 0)) - { - natsConn_Unlock(nc); - - return false; - } - } - - // Do reconnect only if allowed and we were actually connected - // or if we are retrying on initial failed connect. - if (initialConnect || (nc->opts->allowReconnect && (nc->status == NATS_CONN_STATUS_CONNECTED))) - { - natsStatus ls = NATS_OK; - - // Set our new status - nc->status = NATS_CONN_STATUS_RECONNECTING; - - if (nc->ptmr != NULL) - natsTimer_Stop(nc->ptmr); - - if (nc->sockCtx.fdActive) - { - SET_WRITE_DEADLINE(nc); - natsConn_bufferFlush(nc); - - natsSock_Shutdown(nc->sockCtx.fd); - nc->sockCtx.fdActive = false; - } - - // If we use an external event loop, we need to stop polling - // on the socket since we are going to reconnect. - if (nc->el.attached) - { - ls = _evStopPolling(nc); - natsSock_Close(nc->sockCtx.fd); - nc->sockCtx.fd = NATS_SOCK_INVALID; - - // We need to cleanup some things if the connection was SSL. - _clearSSL(nc); - } - - // Fail pending flush requests. - if (ls == NATS_OK) - _clearPendingFlushRequests(nc); - // If option set, also fail pending requests. - if ((ls == NATS_OK) && nc->opts->failRequestsOnDisconnect) - _clearPendingRequestCalls(nc, NATS_CONNECTION_DISCONNECTED); - - // Create the pending buffer to hold all write requests while we try - // to reconnect. - if (ls == NATS_OK) - ls = natsBuf_Create(&(nc->pending), nc->opts->reconnectBufSize); - if (ls == NATS_OK) - { - nc->usePending = true; - - // Start the reconnect thread - ls = natsThread_Create(&(nc->reconnectThread), - _doReconnect, (void*) nc); - } - if (ls == NATS_OK) - { - // We created the reconnect thread successfully, so retain - // the connection. - _retain(nc); - nc->inReconnect++; - natsConn_Unlock(nc); - - return true; - } - } - - // reconnect not allowed or we failed to setup the reconnect code. - - nc->status = NATS_CONN_STATUS_DISCONNECTED; - nc->err = s; - - natsConn_Unlock(nc); - - _close(nc, NATS_CONN_STATUS_CLOSED, false, true); - - return false; -} - -static void -_readLoop(void *arg) -{ - natsStatus s = NATS_OK; - char *buffer; - int n; - int bufSize; - - natsConnection *nc = (natsConnection*) arg; - - natsConn_Lock(nc); - - bufSize = nc->opts->ioBufSize; - buffer = NATS_MALLOC(bufSize); - if (buffer == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - - if (nc->sockCtx.ssl != NULL) - nats_sslRegisterThreadForCleanup(); - - natsDeadline_Clear(&(nc->sockCtx.readDeadline)); - - if (nc->ps == NULL) - s = natsParser_Create(&(nc->ps)); - - while ((s == NATS_OK) - && !natsConn_isClosed(nc) - && !natsConn_isReconnecting(nc)) - { - natsConn_Unlock(nc); - - n = 0; - - s = natsSock_Read(&(nc->sockCtx), buffer, bufSize, &n); - if ((s == NATS_IO_ERROR) && (NATS_SOCK_GET_ERROR == NATS_SOCK_WOULD_BLOCK)) - s = NATS_OK; - if ((s == NATS_OK) && (n > 0)) - s = natsParser_Parse(nc, buffer, n); - - if (s != NATS_OK) - _processOpError(nc, s, false); - - natsConn_Lock(nc); - } - - NATS_FREE(buffer); - - natsSock_Close(nc->sockCtx.fd); - nc->sockCtx.fd = NATS_SOCK_INVALID; - nc->sockCtx.fdActive = false; - - // We need to cleanup some things if the connection was SSL. - _clearSSL(nc); - - natsParser_Destroy(nc->ps); - nc->ps = NULL; - - // This unlocks and releases the connection to compensate for the retain - // when this thread was created. - natsConn_unlockAndRelease(nc); -} - -static void -_flusher(void *arg) -{ - natsConnection *nc = (natsConnection*) arg; - natsStatus s; - - while (true) - { - natsConn_Lock(nc); - - while (!(nc->flusherSignaled) && !(nc->flusherStop)) - natsCondition_Wait(nc->flusherCond, nc->mu); - - if (nc->flusherStop) - { - natsConn_Unlock(nc); - break; - } - - //TODO: If we process the request right away, performance - // will suffer when sending quickly very small messages. - // The buffer is going to be always flushed, which - // defeats the purpose of a write buffer. - // We need to revisit this. - - // Give a chance to accumulate more requests... - natsCondition_TimedWait(nc->flusherCond, nc->mu, 1); - - nc->flusherSignaled = false; - - if (!_isConnected(nc) || natsConn_isClosed(nc) || natsConn_isReconnecting(nc)) - { - natsConn_Unlock(nc); - break; - } - - if (nc->sockCtx.fdActive && (natsBuf_Len(nc->bw) > 0)) - { - SET_WRITE_DEADLINE(nc); - s = natsConn_bufferFlush(nc); - if ((s != NATS_OK) && (nc->err == NATS_OK)) - nc->err = s; - } - - natsConn_Unlock(nc); - } - - // Release the connection to compensate for the retain when this thread - // was created. - natsConn_release(nc); -} - -static void -_sendPing(natsConnection *nc, natsPong *pong) -{ - natsStatus s = NATS_OK; - - SET_WRITE_DEADLINE(nc); - s = natsConn_bufferWrite(nc, _PING_PROTO_, _PING_PROTO_LEN_); - if (s == NATS_OK) - { - // Flush the buffer in place. - s = natsConn_bufferFlush(nc); - } - if (s == NATS_OK) - { - // Now that we know the PING was sent properly, update - // the number of PING sent. - nc->pongs.outgoingPings++; - - if (pong != NULL) - { - pong->id = nc->pongs.outgoingPings; - - // Add this pong to the list. - pong->next = NULL; - pong->prev = nc->pongs.tail; - - if (nc->pongs.tail != NULL) - nc->pongs.tail->next = pong; - - nc->pongs.tail = pong; - - if (nc->pongs.head == NULL) - nc->pongs.head = pong; - } - } -} - -static void -_processPingTimer(natsTimer *timer, void *arg) -{ - natsConnection *nc = (natsConnection*) arg; - - natsConn_Lock(nc); - - if (nc->status != NATS_CONN_STATUS_CONNECTED) - { - natsConn_Unlock(nc); - return; - } - - // If we have more PINGs out than PONGs in, consider - // the connection stale. - if (++(nc->pout) > nc->opts->maxPingsOut) - { - natsConn_Unlock(nc); - _processOpError(nc, NATS_STALE_CONNECTION, false); - return; - } - - _sendPing(nc, NULL); - - natsConn_Unlock(nc); -} - -static void -_pingStopppedCb(natsTimer *timer, void *closure) -{ - natsConnection *nc = (natsConnection*) closure; - - natsConn_release(nc); -} - -static natsStatus -_spinUpSocketWatchers(natsConnection *nc) -{ - natsStatus s = NATS_OK; - - nc->pout = 0; - nc->flusherStop = false; - - if (nc->opts->evLoop == NULL) - { - // Let's not rely on the created threads acquiring lock that would make it - // safe to retain only on success. - _retain(nc); - - s = natsThread_Create(&(nc->readLoopThread), _readLoop, (void*) nc); - if (s != NATS_OK) - _release(nc); - } - - // Don't start flusher thread if connection was created with SendAsap option. - if ((s == NATS_OK) && !(nc->opts->sendAsap)) - { - _retain(nc); - - s = natsThread_Create(&(nc->flusherThread), _flusher, (void*) nc); - if (s != NATS_OK) - _release(nc); - } - - if ((s == NATS_OK) && (nc->opts->pingInterval > 0)) - { - _retain(nc); - - if (nc->ptmr == NULL) - { - s = natsTimer_Create(&(nc->ptmr), - _processPingTimer, - _pingStopppedCb, - nc->opts->pingInterval, - (void*) nc); - if (s != NATS_OK) - _release(nc); - } - else - { - natsTimer_Reset(nc->ptmr, nc->opts->pingInterval); - } - } - - return s; -} - -// Remove all subscriptions. This will kick out the delivery threads, -// and unblock NextMsg() calls. -static void -_removeAllSubscriptions(natsConnection *nc) -{ - natsHashIter iter; - void *p = NULL; - - natsMutex_Lock(nc->subsMu); - natsHashIter_Init(&iter, nc->subs); - while (natsHashIter_Next(&iter, NULL, &p)) - { - natsSubscription *sub = (natsSubscription*) p; - - (void) natsHashIter_RemoveCurrent(&iter); - - natsSub_close(sub, true); - - natsSub_release(sub); - } - natsHashIter_Done(&iter); - natsMutex_Unlock(nc->subsMu); -} - -// Low level close call that will do correct cleanup and set -// desired status. Also controls whether user defined callbacks -// will be triggered. The lock should not be held entering this -// function. This function will handle the locking manually. -static void -_close(natsConnection *nc, natsConnStatus status, bool fromPublicClose, bool doCBs) -{ - struct threadsToJoin ttj; - bool sockWasActive = false; - bool detach = false; - bool postClosedCb = false; - bool postDisconnectedCb = false; - natsSubscription *sub = NULL; - - natsOptions_lock(nc->opts); - postClosedCb = (nc->opts->closedCb != NULL); - postDisconnectedCb = (nc->opts->disconnectedCb != NULL); - natsOptions_unlock(nc->opts); - - natsConn_lockAndRetain(nc); - - // If invoked from the public Close() call, attempt to flush - // to ensure that server has received all pending data. - // Note that _flushTimeout will release the lock and wait - // for PONG so this is why we do this early in that function. - if (fromPublicClose - && (nc->status == NATS_CONN_STATUS_CONNECTED) - && nc->sockCtx.fdActive - && (natsBuf_Len(nc->bw) > 0)) - { - _flushTimeout(nc, 500); - } - - if (natsConn_isClosed(nc)) - { - nc->status = status; - - natsConn_unlockAndRelease(nc); - return; - } - - nc->status = NATS_CONN_STATUS_CLOSED; - - _initThreadsToJoin(&ttj, nc, true); - - // Kick out all calls to natsConnection_Flush[Timeout](). - _clearPendingFlushRequests(nc); - - // Kick out any queued and blocking requests. - _clearPendingRequestCalls(nc, NATS_CONNECTION_CLOSED); - - if (nc->ptmr != NULL) - natsTimer_Stop(nc->ptmr); - - // Unblock reconnect thread block'ed in sleep of reconnectWait interval - natsCondition_Broadcast(nc->reconnectCond); - - // Remove all subscriptions. This will kick out the delivery threads, - // and unblock NextMsg() calls. - _removeAllSubscriptions(nc); - - // Go ahead and make sure we have flushed the outbound buffer. - if (nc->sockCtx.fdActive) - { - // If there is no readLoop (or using external event loop), then it is - // our responsibility to close the socket. Otherwise, _readLoop is the - // one doing it. - if (ttj.readLoop == NULL) - { - // If event loop attached, stop polling... - if (nc->el.attached) - _evStopPolling(nc); - - natsSock_Close(nc->sockCtx.fd); - nc->sockCtx.fd = NATS_SOCK_INVALID; - - // We need to cleanup some things if the connection was SSL. - _clearSSL(nc); - } - else - { - // Shutdown the socket to stop any read/write operations. - // The socket will be closed by the _readLoop thread. - natsSock_Shutdown(nc->sockCtx.fd); - } - nc->sockCtx.fdActive = false; - sockWasActive = true; - } - - // Perform appropriate callback if needed for a disconnect. - // Do not invoke if we were disconnected and failed to reconnect (since - // it has already been invoked in doReconnect). - if (doCBs && !nc->rle && postDisconnectedCb && sockWasActive) - natsAsyncCb_PostConnHandler(nc, ASYNC_DISCONNECTED); - - sub = nc->respMux; - nc->respMux = NULL; - - natsConn_Unlock(nc); - - if (sub != NULL) - natsSub_release(sub); - - _joinThreads(&ttj); - - natsConn_Lock(nc); - - // Perform appropriate callback if needed for a connection closed. - if (doCBs && postClosedCb) - natsAsyncCb_PostConnHandler(nc, ASYNC_CLOSED); - - nc->status = status; - - if (nc->el.attached) - { - nc->el.attached = false; - detach = true; - _retain(nc); - } - - natsConn_unlockAndRelease(nc); - - if (detach) - { - nc->opts->evCbs.detach(nc->el.data); - natsConn_release(nc); - } -} - -static natsStatus -_createMsg(natsMsg **newMsg, natsConnection *nc, char *buf, int bufLen, int hdrLen) -{ - natsStatus s = NATS_OK; - int subjLen = 0; - char *reply = NULL; - int replyLen = 0; - - subjLen = natsBuf_Len(nc->ps->ma.subject); - - if (nc->ps->ma.reply != NULL) - { - reply = natsBuf_Data(nc->ps->ma.reply); - replyLen = natsBuf_Len(nc->ps->ma.reply); - } - - s = natsMsg_createWithPadding(newMsg, - (const char*) natsBuf_Data(nc->ps->ma.subject), subjLen, - (const char*) reply, replyLen, - (const char*) buf, bufLen, nc->opts->payloadPaddingSize, hdrLen); - return s; -} - -natsStatus -natsConn_processMsg(natsConnection *nc, char *buf, int bufLen) -{ - natsStatus s = NATS_OK; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsMsgDlvWorker *ldw = NULL; - bool sc = false; - bool sm = false; - nats_MsgList *list = NULL; - natsCondition *cond = NULL; - // For JetStream cases - jsSub *jsi = NULL; - bool ctrlMsg = false; - const char *fcReply= NULL; - int jct = 0; - natsMsgFilter mf = NULL; - void *mfc = NULL; - - // Do this outside of locks, even if we end-up having to destroy - // it because we have reached the maxPendingMsgs count or other - // conditions. This reduces lock contention. - s = _createMsg(&msg, nc, buf, bufLen, nc->ps->ma.hdr); - if (s != NATS_OK) - return s; - // bufLen is the total length of headers + data. Since headers become - // more and more prevalent, it makes sense to count them both toward - // the subscription's pending limit. So use bufLen for accounting. - - natsMutex_Lock(nc->subsMu); - - nc->stats.inMsgs += 1; - nc->stats.inBytes += (uint64_t) bufLen; - - if ((mf = nc->filter) != NULL) - { - mfc = nc->filterClosure; - natsMutex_Unlock(nc->subsMu); - - (*mf)(nc, &msg, mfc); - if (msg == NULL) - return NATS_OK; - - natsMutex_Lock(nc->subsMu); - } - - sub = natsHash_Get(nc->subs, nc->ps->ma.sid); - if (sub == NULL) - { - natsMutex_Unlock(nc->subsMu); - natsMsg_Destroy(msg); - return NATS_OK; - } - // We need to retain the subscription since as soon as we release the - // nc->subsMu lock, the subscription could be destroyed and we would - // reference freed memory. - natsSubAndLdw_LockAndRetain(sub); - - natsMutex_Unlock(nc->subsMu); - - if (sub->closed || sub->drainSkip) - { - natsSubAndLdw_UnlockAndRelease(sub); - natsMsg_Destroy(msg); - return NATS_OK; - } - - // Pick condition variable and list based on if the sub is - // part of a global delivery thread pool or not. - // Note about `list`: this is used only to link messages, but - // sub->msgList needs to be used to update/check number of pending - // messages, since in case of delivery thread pool, `list` will have - // messages from many different subscriptions. - if ((ldw = sub->libDlvWorker) != NULL) - { - cond = ldw->cond; - list = &(ldw->msgList); - } - else - { - cond = sub->cond; - list = &(sub->msgList); - } - - jsi = sub->jsi; - // For JS subscriptions (but not pull ones), handle hearbeat and flow control here. - if (jsi && !jsi->pull) - { - ctrlMsg = natsMsg_isJSCtrl(msg, &jct); - if (ctrlMsg && jct == jsCtrlHeartbeat) - { - // Check if the hearbeat has a "Consumer Stalled" header, if - // so, the value is the FC reply to send a nil message to. - // We will send it at the end of this function. - natsMsgHeader_Get(msg, jsConsumerStalledHdr, &fcReply); - } - else if (!ctrlMsg && jsi->ordered) - { - bool replaced = false; - - s = jsSub_checkOrderedMsg(sub, msg, &replaced); - if ((s != NATS_OK) || replaced) - { - natsSubAndLdw_UnlockAndRelease(sub); - natsMsg_Destroy(msg); - return s; - } - } - } - - if (!ctrlMsg) - { - sub->msgList.msgs++; - sub->msgList.bytes += bufLen; - - if (((sub->msgsLimit > 0) && (sub->msgList.msgs > sub->msgsLimit)) - || ((sub->bytesLimit > 0) && (sub->msgList.bytes > sub->bytesLimit))) - { - natsMsg_Destroy(msg); - - sub->dropped++; - - sc = !sub->slowConsumer; - sub->slowConsumer = true; - - // Undo stats from above. - sub->msgList.msgs--; - sub->msgList.bytes -= bufLen; - } - else - { - bool signal= false; - - if ((jsi != NULL) && jsi->ackNone) - natsMsg_setAcked(msg); - - if (sub->msgList.msgs > sub->msgsMax) - sub->msgsMax = sub->msgList.msgs; - - if (sub->msgList.bytes > sub->bytesMax) - sub->bytesMax = sub->msgList.bytes; - - sub->slowConsumer = false; - - msg->sub = sub; - - if (list->head == NULL) - { - list->head = msg; - signal = true; - } - else - list->tail->next = msg; - - list->tail = msg; - - if (signal) - natsCondition_Signal(cond); - - // Store the ACK metadata from the message to - // compare later on with the received heartbeat. - if (jsi != NULL) - s = jsSub_trackSequences(jsi, msg->reply); - } - } - else if ((jct == jsCtrlHeartbeat) && (msg->reply == NULL)) - { - // Handle control heartbeat messages. - s = jsSub_processSequenceMismatch(sub, msg, &sm); - } - else if ((jct == jsCtrlFlowControl) && (msg->reply != NULL)) - { - // We will schedule the send of the FC reply once we have delivered the - // DATA message that was received before this flow control message, which - // has sequence `jsi.fciseq`. However, it is possible that this message - // has already been delivered, in that case, we need to send the FC reply now. - if (sub->delivered >= jsi->fciseq) - fcReply = msg->reply; - else - { - // Schedule a reply after the previous message is delivered. - s = jsSub_scheduleFlowControlResponse(jsi, msg->reply); - } - } - - // If we are going to post to the error handler, do not release yet. - if (sc || sm) - natsSubAndLdw_Unlock(sub); - else - natsSubAndLdw_UnlockAndRelease(sub); - - if ((s == NATS_OK) && fcReply) - s = natsConnection_Publish(nc, fcReply, NULL, 0); - - if (ctrlMsg) - natsMsg_Destroy(msg); - - if (sc || sm) - { - natsConn_Lock(nc); - - nc->err = (sc ? NATS_SLOW_CONSUMER : NATS_MISMATCH); - natsAsyncCb_PostErrHandler(nc, sub, nc->err, NULL); - - // Now release the subscription (it has been retained in - // natsAsyncCb_PostErrHandler function). - natsSub_release(sub); - - natsConn_Unlock(nc); - } - - return s; -} - -void -natsConn_processOK(natsConnection *nc) -{ - // Do nothing for now. -} - -// _processPermissionViolation is called when the server signals a subject -// permissions violation on either publish or subscribe. -static void -_processPermissionViolation(natsConnection *nc, char *error) -{ - natsConn_Lock(nc); - nc->err = NATS_NOT_PERMITTED; - snprintf(nc->errStr, sizeof(nc->errStr), "%s", error); - natsAsyncCb_PostErrHandler(nc, NULL, nc->err, NATS_STRDUP(error)); - natsConn_Unlock(nc); -} - -// _processAuthError does common processing of auth errors. -// We want to do retries unless we get the same error again. -// This allows us for instance to swap credentials and have -// the app reconnect, but if nothing is changing we should bail. -static bool -_processAuthError(natsConnection *nc, int errCode, char *error) -{ - nc->err = NATS_CONNECTION_AUTH_FAILED; - snprintf(nc->errStr, sizeof(nc->errStr), "%s", error); - - if (!nc->initc) - natsAsyncCb_PostErrHandler(nc, NULL, nc->err, NATS_STRDUP(error)); - - if (nc->cur->lastAuthErrCode == errCode) - nc->ar = true; - else - nc->cur->lastAuthErrCode = errCode; - - return nc->ar; -} - -// Checks if the error is an authentication error and if so returns -// the error code for the string, 0 otherwise. -static int -_checkAuthError(char *error) -{ - if (nats_strcasestr(error, AUTHORIZATION_ERR) != NULL) - return ERR_CODE_AUTH_VIOLATION; - else if (nats_strcasestr(error, AUTHENTICATION_EXPIRED_ERR) != NULL) - return ERR_CODE_AUTH_EXPIRED; - return 0; -} - -void -natsConn_processErr(natsConnection *nc, char *buf, int bufLen) -{ - char error[256]; - bool close = false; - int authErrCode = 0; - - // Copy the error in this local buffer. - snprintf(error, sizeof(error), "%.*s", bufLen, buf); - - // Trim spaces and remove quotes. - nats_NormalizeErr(error); - - if (strcasecmp(error, STALE_CONNECTION) == 0) - { - _processOpError(nc, NATS_STALE_CONNECTION, false); - } - else if (nats_strcasestr(error, PERMISSIONS_ERR) != NULL) - { - _processPermissionViolation(nc, error); - } - else if ((authErrCode = _checkAuthError(error)) != 0) - { - natsConn_Lock(nc); - close = _processAuthError(nc, authErrCode, error); - natsConn_Unlock(nc); - } - else - { - close = true; - natsConn_Lock(nc); - nc->err = NATS_ERR; - snprintf(nc->errStr, sizeof(nc->errStr), "%s", error); - natsConn_Unlock(nc); - } - if (close) - _close(nc, NATS_CONN_STATUS_CLOSED, false, true); -} - -void -natsConn_processPing(natsConnection *nc) -{ - natsConn_Lock(nc); - - SET_WRITE_DEADLINE(nc); - if (natsConn_bufferWrite(nc, _PONG_PROTO_, _PONG_PROTO_LEN_) == NATS_OK) - natsConn_flushOrKickFlusher(nc); - - natsConn_Unlock(nc); -} - -void -natsConn_processPong(natsConnection *nc) -{ - natsPong *pong = NULL; - - natsConn_Lock(nc); - - nc->pongs.incoming++; - - // Check if the first pong's id in the list matches the incoming Id. - if (((pong = nc->pongs.head) != NULL) - && (pong->id == nc->pongs.incoming)) - { - // Remove the pong from the list - _removePongFromList(nc, pong); - - // Release the Flush[Timeout] call - pong->id = 0; - - // There may be more than one thread waiting on this - // condition variable, so we use broadcast instead of - // signal. - natsCondition_Broadcast(nc->pongs.cond); - } - - nc->pout = 0; - - natsConn_Unlock(nc); -} - -natsStatus -natsConn_addSubcription(natsConnection *nc, natsSubscription *sub) -{ - natsStatus s = NATS_OK; - void *oldSub = NULL; - - s = natsHash_Set(nc->subs, sub->sid, (void*) sub, &oldSub); - if (s == NATS_OK) - { - assert(oldSub == NULL); - natsSub_retain(sub); - } - - return NATS_UPDATE_ERR_STACK(s); -} - -void -natsConn_removeSubscription(natsConnection *nc, natsSubscription *removedSub) -{ - natsSubscription *sub = NULL; - - natsMutex_Lock(nc->subsMu); - - sub = natsHash_Remove(nc->subs, removedSub->sid); - - // Note that the sub may have already been removed, so 'sub == NULL' - // is not an error. - if (sub != NULL) - natsSub_close(sub, false); - - natsMutex_Unlock(nc->subsMu); - - // If we really removed the subscription, then release it. - if (sub != NULL) - natsSub_release(sub); -} - -static bool -_isQueueNameValid(const char *name) -{ - int i; - int len; - - if (nats_IsStringEmpty(name)) - return false; - - len = (int) strlen(name); - for (i=0; isubsMu); - sub->sid = ++(nc->ssid); - s = natsConn_addSubcription(nc, sub); - natsMutex_Unlock(nc->subsMu); - } - - if (s == NATS_OK) - { - // We will send these for all subs when we reconnect - // so that we can suppress here. - if (!natsConn_isReconnecting(nc)) - { - SET_WRITE_DEADLINE(nc); - s = natsConn_sendSubProto(nc, subj, queue, sub->sid); - if (s == NATS_OK) - s = natsConn_flushOrKickFlusher(nc); - - // We should not return a failure if we get an issue - // with the buffer write (except if it is no memory). - // For IO errors (if we just got disconnected), the - // reconnect logic will resend the sub protocol. - if (s != NATS_NO_MEMORY) - s = NATS_OK; - } - } - - if (s == NATS_OK) - { - *newSub = sub; - } - else if (sub != NULL) - { - // A delivery thread may have been started, but the subscription not - // added to the connection's subscription map. So this is necessary - // for the delivery thread to unroll. - natsSub_close(sub, false); - - natsConn_removeSubscription(nc, sub); - - natsSub_release(sub); - } - - if (lock) - natsConn_Unlock(nc); - - return NATS_UPDATE_ERR_STACK(s); -} - -// Will queue an UNSUB protocol, making sure it is not flushed in place. -// The connection lock is held on entry. -natsStatus -natsConn_enqueueUnsubProto(natsConnection *nc, int64_t sid) -{ - natsStatus s = NATS_OK; - char *proto = NULL; - int res = 0; - - res = nats_asprintf(&proto, _UNSUB_NO_MAX_PROTO_, sid); - if (res < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - { - nc->dontSendInPlace = true; - natsConn_bufferWrite(nc, (const char*) proto, (int) strlen(proto)); - nc->dontSendInPlace = false; - NATS_FREE(proto); - } - - return NATS_UPDATE_ERR_STACK(s); -} - -// Performs the low level unsubscribe to the server. -natsStatus -natsConn_unsubscribe(natsConnection *nc, natsSubscription *sub, int max, bool drainMode, int64_t timeout) -{ - natsStatus s = NATS_OK; - - natsConn_Lock(nc); - - if (natsConn_isClosed(nc)) - { - natsConn_Unlock(nc); - return nats_setDefaultError(NATS_CONNECTION_CLOSED); - } - - natsMutex_Lock(nc->subsMu); - sub = natsHash_Get(nc->subs, sub->sid); - natsMutex_Unlock(nc->subsMu); - if ((sub == NULL) || !natsSubscription_IsValid(sub)) - { - // Already unsubscribed - natsConn_Unlock(nc); - return nats_setDefaultError(NATS_INVALID_SUBSCRIPTION); - } - - if (max > 0) - { - // If we try to set a max but number of delivered messages - // is already higher than that, then we will do an actual - // remove. - if (!natsSub_setMax(sub, max)) - max = 0; - } - if ((max == 0) && !drainMode) - natsConn_removeSubscription(nc, sub); - - if (!drainMode && !natsConn_isReconnecting(nc)) - { - SET_WRITE_DEADLINE(nc); - // We will send these for all subs when we reconnect - // so that we can suppress here. - s = natsConn_sendUnsubProto(nc, sub->sid, max); - if (s == NATS_OK) - s = natsConn_flushOrKickFlusher(nc); - - // We should not return a failure if we get an issue - // with the buffer write (except if it is no memory). - // For IO errors (if we just got disconnected), the - // reconnect logic will resend the unsub protocol. - if ((s != NATS_OK) && (s != NATS_NO_MEMORY)) - { - nats_clearLastError(); - s = NATS_OK; - } - } - else if (drainMode) - { - if (natsConn_isDraining(nc)) - s = nats_setError(NATS_DRAINING, "%s", "Illegal to drain a subscription while its connection is draining"); - else - s = natsSub_startDrain(sub, timeout); - } - - natsConn_Unlock(nc); - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_setupServerPool(natsConnection *nc) -{ - natsStatus s; - - s = natsSrvPool_Create(&(nc->srvPool), nc->opts); - if (s == NATS_OK) - nc->cur = natsSrvPool_GetSrv(nc->srvPool, 0); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsConn_create(natsConnection **newConn, natsOptions *options) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - - s = nats_Open(-1); - if (s == NATS_OK) - { - nc = NATS_CALLOC(1, sizeof(natsConnection)); - if (nc == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - if (s != NATS_OK) - { - // options have been cloned or created for the connection, - // which was supposed to take ownership, so destroy it now. - natsOptions_Destroy(options); - return NATS_UPDATE_ERR_STACK(s); - } - - natsLib_Retain(); - - nc->refs = 1; - nc->sockCtx.fd = NATS_SOCK_INVALID; - nc->opts = options; - - nc->errStr[0] = '\0'; - - s = natsMutex_Create(&(nc->mu)); - if (s == NATS_OK) - s = natsMutex_Create(&(nc->subsMu)); - if (s == NATS_OK) - s = _setupServerPool(nc); - if (s == NATS_OK) - s = natsHash_Create(&(nc->subs), 8); - if (s == NATS_OK) - s = natsSock_Init(&nc->sockCtx); - if (s == NATS_OK) - { - s = natsBuf_Create(&(nc->scratch), DEFAULT_SCRATCH_SIZE); - if (s == NATS_OK) - s = natsBuf_Append(nc->scratch, _HPUB_P_, _HPUB_P_LEN_); - } - if (s == NATS_OK) - s = natsCondition_Create(&(nc->flusherCond)); - if (s == NATS_OK) - s = natsCondition_Create(&(nc->pongs.cond)); - if (s == NATS_OK) - s = natsCondition_Create(&(nc->reconnectCond)); - - if (s == NATS_OK) - { - if (nc->opts->inboxPfx != NULL) - nc->inboxPfx = (const char*) nc->opts->inboxPfx; - else - nc->inboxPfx = NATS_DEFAULT_INBOX_PRE; - - nc->inboxPfxLen = (int) strlen(nc->inboxPfx); - nc->reqIdOffset = nc->inboxPfxLen+NUID_BUFFER_LEN+1; - } - - if (s == NATS_OK) - *newConn = nc; - else - natsConn_release(nc); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsConnection_Connect(natsConnection **newConn, natsOptions *options) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - - if (options == NULL) - { - s = natsConnection_ConnectTo(newConn, NATS_DEFAULT_URL); - return NATS_UPDATE_ERR_STACK(s); - } - - opts = natsOptions_clone(options); - if (opts == NULL) - s = NATS_NO_MEMORY; - - if (s == NATS_OK) - s = natsConn_create(&nc, opts); - if (s == NATS_OK) - s = _connect(nc); - - if ((s == NATS_OK) || (s == NATS_NOT_YET_CONNECTED)) - *newConn = nc; - else - natsConn_release(nc); - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_processUrlString(natsOptions *opts, const char *urls) -{ - int count = 0; - natsStatus s = NATS_OK; - char **serverUrls = NULL; - char *urlsCopy = NULL; - char *commaPos = NULL; - char *ptr = NULL; - - if (urls != NULL) - { - ptr = (char*) urls; - while ((ptr = strchr(ptr, ',')) != NULL) - { - ptr++; - count++; - } - } - if (count == 0) - return natsOptions_SetURL(opts, urls); - - serverUrls = (char**) NATS_CALLOC(count + 1, sizeof(char*)); - if (serverUrls == NULL) - s = NATS_NO_MEMORY; - if (s == NATS_OK) - { - urlsCopy = NATS_STRDUP(urls); - if (urlsCopy == NULL) - { - NATS_FREE(serverUrls); - return NATS_NO_MEMORY; - } - } - - count = 0; - ptr = urlsCopy; - - do - { - serverUrls[count++] = ptr; - - commaPos = strchr(ptr, ','); - if (commaPos != NULL) - { - ptr = (char*)(commaPos + 1); - *(commaPos) = '\0'; - } - - } while (commaPos != NULL); - - if (s == NATS_OK) - s = natsOptions_SetServers(opts, (const char**) serverUrls, count); - - NATS_FREE(urlsCopy); - NATS_FREE(serverUrls); - - return s; -} - -natsStatus -natsConnection_ConnectTo(natsConnection **newConn, const char *url) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - - s = natsOptions_Create(&opts); - if (s == NATS_OK) - { - s = _processUrlString(opts, url); - // We still own the options at this point (until the call to natsConn_create()) - // so if there was an error, we need to destroy the options now. - if (s != NATS_OK) - natsOptions_Destroy(opts); - } - if (s == NATS_OK) - s = natsConn_create(&nc, opts); - if (s == NATS_OK) - s = _connect(nc); - - if (s == NATS_OK) - *newConn = nc; - else - natsConn_release(nc); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsConnection_Reconnect(natsConnection *nc) -{ - if (nc == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsConn_Lock(nc); - if (natsConn_isClosed(nc)) - { - natsConn_Unlock(nc); - return nats_setDefaultError(NATS_CONNECTION_CLOSED); - } - - natsSock_Close(nc->sockCtx.fd); - - natsConn_Unlock(nc); - return NATS_OK; -} - -// Test if connection has been closed. -bool -natsConnection_IsClosed(natsConnection *nc) -{ - bool closed; - - if (nc == NULL) - return true; - - natsConn_Lock(nc); - - closed = natsConn_isClosed(nc); - - natsConn_Unlock(nc); - - return closed; -} - -// Test if connection is reconnecting. -bool -natsConnection_IsReconnecting(natsConnection *nc) -{ - bool reconnecting; - - if (nc == NULL) - return false; - - natsConn_Lock(nc); - - reconnecting = natsConn_isReconnecting(nc); - - natsConn_Unlock(nc); - - return reconnecting; -} - -bool -natsConnection_IsDraining(natsConnection *nc) -{ - bool draining; - - if (nc == NULL) - return false; - - natsConn_Lock(nc); - - draining = natsConn_isDraining(nc); - - natsConn_Unlock(nc); - - return draining; -} - -// Returns the current state of the connection. -natsConnStatus -natsConnection_Status(natsConnection *nc) -{ - natsConnStatus cs; - - if (nc == NULL) - return NATS_CONN_STATUS_CLOSED; - - natsConn_Lock(nc); - - cs = nc->status; - - natsConn_Unlock(nc); - - return cs; -} - -static void -_destroyPong(natsConnection *nc, natsPong *pong) -{ - // If this pong is the cached one, do not free - if (pong == &(nc->pongs.cached)) - memset(pong, 0, sizeof(natsPong)); - else - NATS_FREE(pong); -} - -// Low-level flush. On entry, connection has been verified to no be closed -// and lock is held. -static natsStatus -_flushTimeout(natsConnection *nc, int64_t timeout) -{ - natsStatus s = NATS_OK; - int64_t target = 0; - natsPong *pong = NULL; - - // Use the cached PONG instead of creating one if the list - // is empty - if (nc->pongs.head == NULL) - pong = &(nc->pongs.cached); - else - pong = (natsPong*) NATS_CALLOC(1, sizeof(natsPong)); - - if (pong == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - - if (s == NATS_OK) - { - // Send the ping (and add the pong to the list) - _sendPing(nc, pong); - - target = nats_setTargetTime(timeout); - - // When the corresponding PONG is received, the PONG processing code - // will set pong->id to 0 and do a broadcast. This will allow this - // code to break out of the 'while' loop. - while ((s != NATS_TIMEOUT) - && !natsConn_isClosed(nc) - && (pong->id > 0)) - { - s = natsCondition_AbsoluteTimedWait(nc->pongs.cond, nc->mu, target); - } - - if ((s == NATS_OK) && (nc->status == NATS_CONN_STATUS_CLOSED)) - { - // The connection has been closed while we were waiting - s = nats_setDefaultError(NATS_CONNECTION_CLOSED); - } - else if ((s == NATS_OK) && (pong->id == -1)) - { - // The connection was disconnected and the library is in the - // process of trying to reconnect - s = nats_setDefaultError(NATS_CONNECTION_DISCONNECTED); - } - - if (s != NATS_OK) - { - // If we are here, it is possible that we timed-out, or some other - // error occurred. Make sure the request is no longer in the list. - _removePongFromList(nc, pong); - - // Set the error. If we don't do that, and flush is called in a loop, - // the stack would be growing with Flush/FlushTimeout. - s = nats_setDefaultError(s); - } - - // We are done with the pong - _destroyPong(nc, pong); - } - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsConnection_FlushTimeout(natsConnection *nc, int64_t timeout) -{ - natsStatus s = NATS_OK; - - if (nc == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - if (timeout <= 0) - return nats_setDefaultError(NATS_INVALID_TIMEOUT); - - natsConn_lockAndRetain(nc); - - if (natsConn_isClosed(nc)) - s = nats_setDefaultError(NATS_CONNECTION_CLOSED); - - if (s == NATS_OK) - s = _flushTimeout(nc, timeout); - - natsConn_unlockAndRelease(nc); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsConnection_Flush(natsConnection *nc) -{ - natsStatus s = natsConnection_FlushTimeout(nc, DEFAULT_FLUSH_TIMEOUT); - return NATS_UPDATE_ERR_STACK(s); -} - -static void -_pushDrainErr(natsConnection *nc, natsStatus s, const char *errTxt) -{ - char tmp[256]; - natsConn_Lock(nc); - snprintf(tmp, sizeof(tmp), "Drain error: %s: %u (%s)", errTxt, s, natsStatus_GetText(s)); - natsAsyncCb_PostErrHandler(nc, NULL, s, NATS_STRDUP(tmp)); - natsConn_Unlock(nc); -} - -// Callback invoked for each of the registered subscription. -typedef natsStatus (*subIterFunc)(natsStatus callerSts, natsConnection *nc, natsSubscription *sub); - -// Iterates through all registered subscriptions and execute the callback `f`. -// If the callback returns an error, the iteration continues and the first -// non NATS_OK error is returned. -// -// Connection lock is held on entry. -static natsStatus -_iterateSubsAndInvokeFunc(natsStatus callerSts, natsConnection *nc, subIterFunc f) -{ - natsStatus s = NATS_OK; - natsStatus ls = NATS_OK; - natsSubscription *sub = NULL; - void *p = NULL; - natsHashIter iter; - - natsMutex_Lock(nc->subsMu); - if (natsHash_Count(nc->subs) == 0) - { - natsMutex_Unlock(nc->subsMu); - return NATS_OK; - } - natsHashIter_Init(&iter, nc->subs); - while (natsHashIter_Next(&iter, NULL, &p)) - { - sub = (natsSubscription*) p; - ls = (f)(callerSts, nc, sub); - s = (s == NATS_OK ? ls : s); - } - natsHashIter_Done(&iter); - natsMutex_Unlock(nc->subsMu); - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_enqueUnsubProto(natsStatus callerSts, natsConnection *nc, natsSubscription *sub) -{ - natsStatus s; - - natsSub_Lock(sub); - s = natsConn_enqueueUnsubProto(nc, sub->sid); - natsSub_Unlock(sub); - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_initSubDrain(natsStatus callerSts, natsConnection *nc, natsSubscription *sub) -{ - natsSub_initDrain(sub); - return NATS_OK; -} - -static natsStatus -_startSubDrain(natsStatus callerSts, natsConnection *nc, natsSubscription *sub) -{ - if (callerSts != NATS_OK) - natsSub_setDrainSkip(sub, callerSts); - - natsSub_drain(sub); - return NATS_OK; -} - -static natsStatus -_setSubDrainStatus(natsStatus callerSts, natsConnection *nc, natsSubscription *sub) -{ - if (callerSts != NATS_OK) - natsSub_updateDrainStatus(sub, callerSts); - return NATS_OK; -} - -static void -_flushAndDrain(void *closure) -{ - natsConnection *nc = (natsConnection*) closure; - natsThread *t = NULL; - int64_t timeout = 0; - int64_t deadline = 0; - bool doSubs = false; - bool subsDone = false; - bool timedOut = false; - bool closed = false; - natsStatus s = NATS_OK; - int64_t start; - - natsConn_Lock(nc); - t = nc->drainThread; - timeout = nc->drainTimeout; - closed = natsConn_isClosed(nc); - natsMutex_Lock(nc->subsMu); - doSubs = (natsHash_Count(nc->subs) > 0 ? true : false); - natsMutex_Unlock(nc->subsMu); - natsConn_Unlock(nc); - - if (timeout < 0) - timeout = 0; - else - deadline = nats_setTargetTime(timeout); - - start = nats_Now(); - if (!closed && doSubs) - { - if (timeout == 0) - s = natsConnection_Flush(nc); - else - s = natsConnection_FlushTimeout(nc, timeout); - - if (s != NATS_OK) - _pushDrainErr(nc, s, "unable to flush all subscriptions UNSUB protocols"); - - // Start the draining of all registered subscriptions. - // Update the drain status with possibly failed flush. - natsConn_Lock(nc); - _iterateSubsAndInvokeFunc(s, nc, _startSubDrain); - natsConn_Unlock(nc); - - // Reset status now. - s = NATS_OK; - - // Now wait for the number of subscriptions to go down to 0, or deadline is reached. - while ((timeout == 0) || (deadline - nats_Now() > 0)) - { - natsConn_Lock(nc); - if (!(closed = natsConn_isClosed(nc))) - { - natsMutex_Lock(nc->subsMu); - subsDone = (natsHash_Count(nc->subs) == 0 ? true : false); - natsMutex_Unlock(nc->subsMu); - } - natsConn_Unlock(nc); - if (closed || subsDone) - break; - nats_Sleep(100); - } - // If the connection has been closed, then the subscriptions will have - // be removed from the map. So only try to update the subs' drain status - // if we are here due to a NATS_TIMEOUT, not a NATS_CONNECTION_CLOSED. - if (!closed && !subsDone) - { - _iterateSubsAndInvokeFunc(NATS_TIMEOUT, nc, _setSubDrainStatus); - _pushDrainErr(nc, NATS_TIMEOUT, "timeout waiting for subscriptions to drain"); - timedOut = true; - } - } - - // Now switch to draining PUBS, unless already closed. - natsConn_Lock(nc); - if (!(closed = natsConn_isClosed(nc))) - nc->status = NATS_CONN_STATUS_DRAINING_PUBS; - natsConn_Unlock(nc); - - // Attempt to flush, unless we have already timed out, or connection is closed. - if (!closed && !timedOut) - { - // Reset status - s = NATS_OK; - - // We drain the publish calls - if (timeout == 0) - s = natsConnection_Flush(nc); - else - { - // If there is a timeout, see how long do we have left. - int64_t elapsed = nats_Now() - start; - - if (elapsed < timeout) - s = natsConnection_FlushTimeout(nc, timeout-elapsed); - } - if (s != NATS_OK) - _pushDrainErr(nc, s, "unable to flush publish calls"); - } - - // Finally, close the connection. - if (!closed) - natsConnection_Close(nc); - - natsThread_Detach(t); - natsThread_Destroy(t); - natsConn_release(nc); -} - -static natsStatus -_drain(natsConnection *nc, int64_t timeout) -{ - natsStatus s = NATS_OK; - - if (nc == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsConn_Lock(nc); - if (natsConn_isClosed(nc)) - s = nats_setDefaultError(NATS_CONNECTION_CLOSED); - else if (nc->stanOwned) - s = nats_setError(NATS_ILLEGAL_STATE, "%s", "Illegal to call Drain for connection owned by a streaming connection"); - else if (_isConnecting(nc) || natsConn_isReconnecting(nc)) - s = nats_setError(NATS_ILLEGAL_STATE, "%s", "Illegal to call Drain while the connection is reconnecting"); - else if (!natsConn_isDraining(nc)) - { - // Enqueue UNSUB protocol for all current subscriptions. - s = _iterateSubsAndInvokeFunc(NATS_OK, nc, _enqueUnsubProto); - if (s == NATS_OK) - { - nc->drainTimeout = timeout; - s = natsThread_Create(&nc->drainThread, _flushAndDrain, (void*) nc); - if (s == NATS_OK) - { - // Prevent new subscriptions to be added. - nc->status = NATS_CONN_STATUS_DRAINING_SUBS; - - // Switch drain state to "started" for all subs. This does not fail. - _iterateSubsAndInvokeFunc(NATS_OK, nc, _initSubDrain); - - _retain(nc); - } - } - } - natsConn_Unlock(nc); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsConnection_Drain(natsConnection *nc) -{ - natsStatus s = _drain(nc, DEFAULT_DRAIN_TIMEOUT); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsConnection_DrainTimeout(natsConnection *nc, int64_t timeout) -{ - natsStatus s = _drain(nc, timeout); - return NATS_UPDATE_ERR_STACK(s); -} - -int -natsConnection_Buffered(natsConnection *nc) -{ - int buffered = -1; - - if (nc == NULL) - return buffered; - - natsConn_Lock(nc); - - if ((nc->status != NATS_CONN_STATUS_CLOSED) && (nc->bw != NULL)) - buffered = natsBuf_Len(nc->bw); - - natsConn_Unlock(nc); - - return buffered; -} - -int64_t -natsConnection_GetMaxPayload(natsConnection *nc) -{ - int64_t mp = 0; - - if (nc == NULL) - return 0; - - natsConn_Lock(nc); - - mp = nc->info.maxPayload; - - natsConn_Unlock(nc); - - return mp; -} - -natsStatus -natsConnection_GetStats(natsConnection *nc, natsStatistics *stats) -{ - natsStatus s = NATS_OK; - - if ((nc == NULL) || (stats == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - // Stats are updated either under connection's mu or subsMu mutexes. - // Lock both to safely get them. - natsConn_Lock(nc); - natsMutex_Lock(nc->subsMu); - - memcpy(stats, &(nc->stats), sizeof(natsStatistics)); - - natsMutex_Unlock(nc->subsMu); - natsConn_Unlock(nc); - - return s; -} - -natsStatus -natsConnection_GetConnectedUrl(natsConnection *nc, char *buffer, size_t bufferSize) -{ - natsStatus s = NATS_OK; - - if ((nc == NULL) || (buffer == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsConn_Lock(nc); - - buffer[0] = '\0'; - - if (((nc->status == NATS_CONN_STATUS_CONNECTED) || (nc->status == NATS_CONN_STATUS_CONNECTING)) - && (nc->cur->url->fullUrl != NULL)) - { - if (strlen(nc->cur->url->fullUrl) >= bufferSize) - s = nats_setDefaultError(NATS_INSUFFICIENT_BUFFER); - - if (s == NATS_OK) - snprintf(buffer, bufferSize, "%s", nc->cur->url->fullUrl); - } - - natsConn_Unlock(nc); - - return s; -} - -natsStatus -natsConnection_GetConnectedServerId(natsConnection *nc, char *buffer, size_t bufferSize) -{ - natsStatus s = NATS_OK; - - if ((nc == NULL) || (buffer == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsConn_Lock(nc); - - buffer[0] = '\0'; - - if (((nc->status == NATS_CONN_STATUS_CONNECTED) || (nc->status == NATS_CONN_STATUS_CONNECTING)) - && (nc->info.id != NULL)) - { - if (strlen(nc->info.id) >= bufferSize) - s = nats_setDefaultError(NATS_INSUFFICIENT_BUFFER); - - if (s == NATS_OK) - snprintf(buffer, bufferSize, "%s", nc->info.id); - } - - natsConn_Unlock(nc); - - return s; -} - -natsStatus -natsConnection_GetServers(natsConnection *nc, char ***servers, int *count) -{ - natsStatus s = NATS_OK; - - if ((nc == NULL) || (servers == NULL) || (count == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsConn_Lock(nc); - - s = natsSrvPool_GetServers(nc->srvPool, false, servers, count); - - natsConn_Unlock(nc); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsConnection_GetDiscoveredServers(natsConnection *nc, char ***servers, int *count) -{ - natsStatus s = NATS_OK; - - if ((nc == NULL) || (servers == NULL) || (count == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsConn_Lock(nc); - - s = natsSrvPool_GetServers(nc->srvPool, true, servers, count); - - natsConn_Unlock(nc); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsConnection_GetLastError(natsConnection *nc, const char **lastError) -{ - natsStatus s; - - if (nc == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsConn_Lock(nc); - - s = nc->err; - if (s == NATS_OK) - nc->errStr[0] = '\0'; - else if (nc->errStr[0] == '\0') - snprintf(nc->errStr, sizeof(nc->errStr), "%s", natsStatus_GetText(s)); - - *lastError = nc->errStr; - - natsConn_Unlock(nc); - - return s; -} - -void -natsConn_close(natsConnection *nc) -{ - if (nc == NULL) - return; - - nats_doNotUpdateErrStack(true); - - _close(nc, NATS_CONN_STATUS_CLOSED, true, true); - - nats_doNotUpdateErrStack(false); -} - -void -natsConnection_Close(natsConnection *nc) -{ - bool stanOwned; - - if (nc == NULL) - return; - - natsConn_Lock(nc); - stanOwned = nc->stanOwned; - natsConn_Unlock(nc); - - if (!stanOwned) - natsConn_close(nc); -} - -void -natsConn_destroy(natsConnection *nc, bool fromPublicDestroy) -{ - if (nc == NULL) - return; - - nats_doNotUpdateErrStack(true); - - _close(nc, NATS_CONN_STATUS_CLOSED, fromPublicDestroy, true); - - nats_doNotUpdateErrStack(false); - - natsConn_release(nc); -} - -void -natsConnection_Destroy(natsConnection *nc) -{ - bool stanOwned; - - if (nc == NULL) - return; - - natsConn_Lock(nc); - stanOwned = nc->stanOwned; - natsConn_Unlock(nc); - - if (!stanOwned) - natsConn_destroy(nc, true); -} - -void -natsConnection_ProcessReadEvent(natsConnection *nc) -{ - natsStatus s = NATS_OK; - int n = 0; - char *buffer; - int size; - - natsConn_Lock(nc); - - if (!(nc->el.attached) || (nc->sockCtx.fd == NATS_SOCK_INVALID)) - { - natsConn_Unlock(nc); - return; - } - - if (nc->ps == NULL) - { - s = natsParser_Create(&(nc->ps)); - if (s != NATS_OK) - { - (void) NATS_UPDATE_ERR_STACK(s); - natsConn_Unlock(nc); - return; - } - } - - _retain(nc); - - buffer = nc->el.buffer; - size = nc->opts->ioBufSize; - - natsConn_Unlock(nc); - - // Do not try to read again here on success. If more than one connection - // is attached to the same loop, and there is a constant stream of data - // coming for the first connection, this would starve the second connection. - // So return and we will be called back later by the event loop. - s = natsSock_Read(&(nc->sockCtx), buffer, size, &n); - if (s == NATS_OK) - s = natsParser_Parse(nc, buffer, n); - - if (s != NATS_OK) - _processOpError(nc, s, false); - - natsConn_release(nc); -} - -void -natsConnection_ProcessWriteEvent(natsConnection *nc) -{ - natsStatus s = NATS_OK; - int n = 0; - char *buf; - int len; - - natsConn_Lock(nc); - - if (!(nc->el.attached) || (nc->sockCtx.fd == NATS_SOCK_INVALID)) - { - natsConn_Unlock(nc); - return; - } - - buf = natsBuf_Data(nc->bw); - len = natsBuf_Len(nc->bw); - - s = natsSock_Write(&(nc->sockCtx), buf, len, &n); - if (s == NATS_OK) - { - if (n == len) - { - // We sent all the data, reset buffer and remove WRITE event. - natsBuf_Reset(nc->bw); - - s = nc->opts->evCbs.write(nc->el.data, NATS_EVENT_ACTION_REMOVE); - if (s == NATS_OK) - nc->el.writeAdded = false; - else - nats_setError(s, "Error processing write request: %d - %s", - s, natsStatus_GetText(s)); - } - else - { - // We sent some part of the buffer. Move the remaining at the beginning. - natsBuf_Consume(nc->bw, n); - } - } - - natsConn_Unlock(nc); - - if (s != NATS_OK) - _processOpError(nc, s, false); - - (void) NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsConnection_GetClientID(natsConnection *nc, uint64_t *cid) -{ - natsStatus s = NATS_OK; - - if ((nc == NULL) || (cid == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsConn_Lock(nc); - if (natsConn_isClosed(nc)) - { - s = NATS_CONNECTION_CLOSED; - } - else - { - *cid = nc->info.CID; - if (*cid == 0) - s = NATS_NO_SERVER_SUPPORT; - } - natsConn_Unlock(nc); - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_getJwtOrSeed(char **val, const char *fn, bool seed, int item) -{ - natsStatus s = NATS_OK; - natsBuffer *buf = NULL; - - s = nats_ReadFile(&buf, 1024, fn); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - s = nats_GetJWTOrSeed(val, (const char*) natsBuf_Data(buf), item); - if (s == NATS_NOT_FOUND) - { - s = NATS_OK; - if (!seed) - { - *val = NATS_STRDUP(natsBuf_Data(buf)); - if (*val == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - else - { - // Look for "SU..." - char *nt = NULL; - char *pch = nats_strtok(natsBuf_Data(buf), "\n", &nt); - - while (pch != NULL) - { - char *ptr = pch; - - while (((*ptr == ' ') || (*ptr == '\t')) && (*ptr != '\0')) - ptr++; - - if ((*ptr != '\0') && (*ptr == 'S') && (*(ptr+1) == 'U')) - { - *val = NATS_STRDUP(ptr); - if (*val == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - break; - } - - pch = nats_strtok(NULL, "\n", &nt); - } - if ((s == NATS_OK) && (*val == NULL)) - s = nats_setError(NATS_ERR, "no nkey user seed found in '%s'", fn); - } - } - if (buf != NULL) - { - memset(natsBuf_Data(buf), 0, natsBuf_Capacity(buf)); - natsBuf_Destroy(buf); - buf = NULL; - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsConn_userCreds(char **userJWT, char **customErrTxt, void *closure) -{ - natsStatus s = NATS_OK; - userCreds *uc = (userCreds*) closure; - - if (uc->jwtAndSeedContent != NULL) - s = nats_GetJWTOrSeed(userJWT, uc->jwtAndSeedContent, 0); - else - s = _getJwtOrSeed(userJWT, uc->userOrChainedFile, false, 0); - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_sign(userCreds *uc, const unsigned char *input, int inputLen, unsigned char *sig) -{ - natsStatus s = NATS_OK; - char *encodedSeed = NULL; - - if (uc->jwtAndSeedContent != NULL) - s = nats_GetJWTOrSeed(&encodedSeed, uc->jwtAndSeedContent, 1); - else if (uc->seedFile != NULL) - s = _getJwtOrSeed(&encodedSeed, uc->seedFile, true, 0); - else - s = _getJwtOrSeed(&encodedSeed, uc->userOrChainedFile, true, 1); - - if (s == NATS_OK) - s = natsKeys_Sign((const char*) encodedSeed, input, inputLen, sig); - - if (encodedSeed != NULL) - { - natsCrypto_Clear((void*) encodedSeed, (int) strlen(encodedSeed)); - NATS_FREE(encodedSeed); - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsConn_signatureHandler(char **customErrTxt, unsigned char **psig, int *sigLen, const char *nonce, void *closure) -{ - natsStatus s = NATS_OK; - userCreds *uc = (userCreds*) closure; - char *sig = NULL; - - *psig = NULL; - if (sigLen != NULL) - *sigLen = 0; - - sig = NATS_MALLOC(NATS_CRYPTO_SIGN_BYTES); - if (sig == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - s = _sign(uc, (const unsigned char*) nonce, 0, (unsigned char*) sig); - if (s == NATS_OK) - { - *psig = (unsigned char*) sig; - if (sigLen != NULL) - *sigLen = NATS_CRYPTO_SIGN_BYTES; - } - else - { - NATS_FREE(sig); - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsConnection_Sign(natsConnection *nc, const unsigned char *payload, int payloadLen, unsigned char sig[64]) -{ - natsStatus s = NATS_OK; - userCreds *uc = NULL; - - if ((nc == NULL) || (payloadLen < 0) || (sig == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsConn_Lock(nc); - // We can't sign if that is not set... - uc = nc->opts->userCreds; - if (uc == NULL) - s = nats_setError(NATS_ERR, "%s", "unable to sign since no user credentials have been set"); - else - s = _sign(uc, payload, payloadLen, sig); - natsConn_Unlock(nc); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsConnection_GetClientIP(natsConnection *nc, char **ip) -{ - natsStatus s = NATS_OK; - - if ((nc == NULL) || (ip == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - *ip = NULL; - - natsConn_Lock(nc); - if (natsConn_isClosed(nc)) - s = nats_setDefaultError(NATS_CONNECTION_CLOSED); - else if (nc->info.clientIP == NULL) - s = nats_setDefaultError(NATS_NO_SERVER_SUPPORT); - else if ((*ip = NATS_STRDUP(nc->info.clientIP)) == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - natsConn_Unlock(nc); - - return s; -} - -natsStatus -natsConnection_GetRTT(natsConnection *nc, int64_t *rtt) -{ - natsStatus s = NATS_OK; - int64_t start; - - if ((nc == NULL) || (rtt == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - *rtt = 0; - - natsConn_Lock(nc); - if (natsConn_isClosed(nc)) - s = nats_setDefaultError(NATS_CONNECTION_CLOSED); - else if (natsConn_isReconnecting(nc)) - s = nats_setDefaultError(NATS_CONNECTION_DISCONNECTED); - else - { - start = nats_NowInNanoSeconds(); - s = _flushTimeout(nc, DEFAULT_FLUSH_TIMEOUT); - if (s == NATS_OK) - *rtt = nats_NowInNanoSeconds()-start; - } - natsConn_Unlock(nc); - - return s; -} - -natsStatus -natsConnection_HasHeaderSupport(natsConnection *nc) -{ - bool headers = false; - - if (nc == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsConn_Lock(nc); - headers = nc->info.headers; - natsConn_Unlock(nc); - - if (headers) - return NATS_OK; - - return NATS_NO_SERVER_SUPPORT; -} - -natsStatus -natsConnection_GetLocalIPAndPort(natsConnection *nc, char **ip, int *port) -{ - natsStatus s = NATS_OK; - - if ((nc == NULL) || (ip == NULL) || (port == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - *ip = NULL; - *port = 0; - - natsConn_Lock(nc); - if (natsConn_isClosed(nc)) - s = nats_setDefaultError(NATS_CONNECTION_CLOSED); - else if (!nc->sockCtx.fdActive) - s = nats_setDefaultError(NATS_CONNECTION_DISCONNECTED); - else - s = natsSock_GetLocalIPAndPort(&(nc->sockCtx), ip, port); - natsConn_Unlock(nc); - - return NATS_UPDATE_ERR_STACK(s); -} - -void -natsConn_setFilterWithClosure(natsConnection *nc, natsMsgFilter f, void* closure) -{ - natsMutex_Lock(nc->subsMu); - nc->filter = f; - nc->filterClosure = closure; - natsMutex_Unlock(nc->subsMu); -} - -void -natsConn_defaultErrHandler(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure) -{ - uint64_t cid = 0; - const char *lastErr = NULL; - const char *errTxt = NULL; - - natsConn_Lock(nc); - cid = nc->info.CID; - natsConn_Unlock(nc); - - // Get possibly more detailed error message. If empty, we will print the default - // error status text. - natsConnection_GetLastError(nc, &lastErr); - errTxt = (nats_IsStringEmpty(lastErr) ? natsStatus_GetText(err) : lastErr); - // If there is a subscription, check if it is a JetStream one and if so, take - // the "public" subject (the one that was provided to the subscribe call). - if (sub != NULL) - { - char *subject = NULL; - - natsSub_Lock(sub); - if ((sub->jsi != NULL) && (sub->jsi->psubj != NULL)) - subject = sub->jsi->psubj; - else - subject = sub->subject; - fprintf(stderr, "Error %d - %s on connection [%" PRIu64 "] on \"%s\"\n", err, errTxt, cid, subject); - natsSub_Unlock(sub); - } - else - { - fprintf(stderr, "Error %d - %s on connection [%" PRIu64 "]\n", err, errTxt, cid); - } - fflush(stderr); + return nc->errStr; } diff --git a/src/conn.h b/src/conn.h index 41e93f402..7c27288b1 100644 --- a/src/conn.h +++ b/src/conn.h @@ -14,150 +14,159 @@ #ifndef CONN_H_ #define CONN_H_ -#include "natsp.h" - -#define RESP_INFO_POOL_MAX_SIZE (10) - -#ifdef DEV_MODE -// For type safety - -void natsConn_Lock(natsConnection *nc); -void natsConn_Unlock(natsConnection *nc); - +#include "comsock.h" +#include "conn_write.h" + +// CLIENT_PROTO_ZERO is the original client protocol from 2009. +// http://nats.io/documentation/internals/nats-protocol/ +#define CLIENT_PROTO_ZERO (0) + +// CLIENT_PROTO_INFO signals a client can receive more then the original INFO block. +// This can be used to update clients on other cluster members, etc. +#define CLIENT_PROTO_INFO (1) + +#define NATS_EV_ADD (true) +#define NATS_EV_REMOVE (false) + +typedef enum +{ + NATS_CONN_STATUS_DISCONNECTED = 0, ///< The connection has been disconnected + NATS_CONN_STATUS_CONNECTING, ///< The connection is in the process or connecting + NATS_CONN_STATUS_CONNECTED, ///< The connection is connected + NATS_CONN_STATUS_CLOSED, ///< The connection is closed + NATS_CONN_STATUS_RECONNECTING, ///< The connection is in the process or reconnecting + NATS_CONN_STATUS_DRAINING_SUBS, ///< The connection is draining subscriptions + NATS_CONN_STATUS_DRAINING_PUBS, ///< The connection is draining publishers + +} natsConnStatus; + +struct __natsServerInfo +{ + const char *id; + const char *host; + int port; + const char *version; + bool authRequired; + bool tlsRequired; + bool tlsAvailable; + int64_t maxPayload; + const char **connectURLs; + int connectURLsCount; + int proto; + uint64_t CID; + const char *nonce; + const char *clientIP; + bool lameDuckMode; + bool headers; +}; + +struct __natsConnection +{ + natsOptions *opts; + natsEventLoop ev; + void *evState; + bool evAttached; + natsServer *cur; + natsSockCtx sockCtx; + + int refs; + natsPool *lifetimePool; + natsPool *connectPool; + natsPool *opPool; + + natsConnStatus state; + + natsStatus err; + char errStr[256]; + natsParser *ps; + natsWriteQueue writeChain; + int pingsOut; + + natsServers *servers; + natsServerInfo *info; + struct + { + int ma; + int mi; + int up; + } srvVersion; + + natsConnectionStatistics stats; +}; + +static inline void natsConn_retain(natsConnection *nc) +{ + if (nc == NULL) + return; + nc->refs++; +} + +void natsConn_freeConn(natsConnection *nc); +static inline void natsConn_release(natsConnection *nc) +{ + if (nc == NULL) + return; + if (--(nc->refs) == 0) + natsConn_freeConn(nc); +} + +#define natsConn_isConnecting(nc) ((nc)->state == NATS_CONN_STATUS_CONNECTING) +#define natsConn_isDrainingPubs(nc) ((nc)->state == NATS_CONN_STATUS_DRAINING_PUBS) +#define natsConn_isDrainingSubs(nc) ((nc)->state == NATS_CONN_STATUS_DRAINING_SUBS) +#define natsConn_isDraining(nc) (natsConn_isDrainingPubs(nc) || natsConn_isDrainingSubs(nc)) +#define natsConn_isConnected(nc) (((nc)->state == NATS_CONN_STATUS_CONNECTED || natsConn_isDraining(nc))) +#define natsConn_isClosed(nc) ((nc)->state == NATS_CONN_STATUS_CLOSED) +#define natsConn_initialConnectDone(nc) ((nc)->info != NULL) + +natsStatus natsConn_createParser(natsParser **ps, natsPool *pool); +natsStatus natsConn_parseOp(natsConnection *nc, uint8_t *buf, uint8_t *end, size_t *consumed); +bool natsConn_expectingNewOp(natsParser *ps); + +static bool natsConn_isReconnecting(natsConnection *nc) +{ + return false; // <>/<> FIXME +} + +natsStatus natsConn_processMsg(natsConnection *nc, char *buf, int bufLen); +void natsConn_processOK(natsConnection *nc); +void natsConn_processErr(natsConnection *nc, char *buf, int bufLen); +natsStatus natsConn_processPing(natsConnection *nc); +natsStatus natsConn_processPong(natsConnection *nc); +natsStatus natsConn_processInfo(natsConnection *nc, nats_JSON *json); +bool natsConn_srvVersionAtLeast(natsConnection *nc, int major, int minor, int update); +bool natsConn_processOpError(natsConnection *nc, natsStatus s); + +natsStatus +natsConn_sendPing(natsConnection *nc); +natsStatus +natsConn_sendConnect(natsConnection *nc); + +#define natsConn_subscribeNoPool(sub, nc, subj, cb, closure) natsConn_subscribeImpl((sub), (nc), true, (subj), NULL, 0, (cb), (closure), true, NULL) +#define natsConn_subscribeNoPoolNoLock(sub, nc, subj, cb, closure) natsConn_subscribeImpl((sub), (nc), false, (subj), NULL, 0, (cb), (closure), true, NULL) +#define natsConn_subscribeSyncNoPool(sub, nc, subj) natsConn_subscribeNoPool((sub), (nc), (subj), NULL, NULL) +#define natsConn_subscribeWithTimeout(sub, nc, subj, timeout, cb, closure) natsConn_subscribeImpl((sub), (nc), true, (subj), NULL, (timeout), (cb), (closure), false, NULL) +#define natsConn_subscribe(sub, nc, subj, cb, closure) natsConn_subscribeWithTimeout((sub), (nc), (subj), 0, (cb), (closure)) +#define natsConn_subscribeSync(sub, nc, subj) natsConn_subscribe((sub), (nc), (subj), NULL, NULL) +#define natsConn_queueSubscribeWithTimeout(sub, nc, subj, queue, timeout, cb, closure) natsConn_subscribeImpl((sub), (nc), true, (subj), (queue), (timeout), (cb), (closure), false, NULL) +#define natsConn_queueSubscribe(sub, nc, subj, queue, cb, closure) natsConn_queueSubscribeWithTimeout((sub), (nc), (subj), (queue), 0, (cb), (closure)) +#define natsConn_queueSubscribeSync(sub, nc, subj, queue) natsConn_queueSubscribe((sub), (nc), (subj), (queue), NULL, NULL) + +#ifdef DEV_MODE_CONN +#define CONNTRACEf(fmt, ...) DEVTRACEf("CONN", fmt, __VA_ARGS__) +#define CONNDEBUGf(fmt, ...) DEVDEBUGf("CONN", fmt, __VA_ARGS__) +#define CONNERROR(str) DEVERROR("CONN", str) #else -// We know what we are doing :-) - -#define natsConn_Lock(c) (natsMutex_Lock((c)->mu)) -#define natsConn_Unlock(c) (natsMutex_Unlock((c)->mu)) - -#endif // DEV_MODE - -#define SET_WRITE_DEADLINE(nc) if ((nc)->opts->writeDeadline > 0) natsDeadline_Init(&(nc)->sockCtx.writeDeadline, (nc)->opts->writeDeadline) - -natsStatus -natsConn_create(natsConnection **newConn, natsOptions *options); - -void -natsConn_retain(natsConnection *nc); - -void -natsConn_release(natsConnection *nc); - -natsStatus -natsConn_bufferWrite(natsConnection *nc, const char *buffer, int len); - -natsStatus -natsConn_bufferFlush(natsConnection *nc); - -bool -natsConn_isClosed(natsConnection *nc); - -bool -natsConn_isReconnecting(natsConnection *nc); - -natsStatus -natsConn_flushOrKickFlusher(natsConnection *nc); - -natsStatus -natsConn_processMsg(natsConnection *nc, char *buf, int bufLen); - -void -natsConn_processOK(natsConnection *nc); - -void -natsConn_processErr(natsConnection *nc, char *buf, int bufLen); - -void -natsConn_processPing(natsConnection *nc); - -void -natsConn_processPong(natsConnection *nc); - -#define natsConn_subscribeNoPool(sub, nc, subj, cb, closure) natsConn_subscribeImpl((sub), (nc), true, (subj), NULL, 0, (cb), (closure), true, NULL) -#define natsConn_subscribeNoPoolNoLock(sub, nc, subj, cb, closure) natsConn_subscribeImpl((sub), (nc), false, (subj), NULL, 0, (cb), (closure), true, NULL) -#define natsConn_subscribeSyncNoPool(sub, nc, subj) natsConn_subscribeNoPool((sub), (nc), (subj), NULL, NULL) -#define natsConn_subscribeWithTimeout(sub, nc, subj, timeout, cb, closure) natsConn_subscribeImpl((sub), (nc), true, (subj), NULL, (timeout), (cb), (closure), false, NULL) -#define natsConn_subscribe(sub, nc, subj, cb, closure) natsConn_subscribeWithTimeout((sub), (nc), (subj), 0, (cb), (closure)) -#define natsConn_subscribeSync(sub, nc, subj) natsConn_subscribe((sub), (nc), (subj), NULL, NULL) -#define natsConn_queueSubscribeWithTimeout(sub, nc, subj, queue, timeout, cb, closure) natsConn_subscribeImpl((sub), (nc), true, (subj), (queue), (timeout), (cb), (closure), false, NULL) -#define natsConn_queueSubscribe(sub, nc, subj, queue, cb, closure) natsConn_queueSubscribeWithTimeout((sub), (nc), (subj), (queue), 0, (cb), (closure)) -#define natsConn_queueSubscribeSync(sub, nc, subj, queue) natsConn_queueSubscribe((sub), (nc), (subj), (queue), NULL, NULL) - -natsStatus -natsConn_subscribeImpl(natsSubscription **newSub, - natsConnection *nc, bool lock, const char *subj, const char *queue, - int64_t timeout, natsMsgHandler cb, void *cbClosure, - bool preventUseOfLibDlvPool, jsSub *jsi); - -natsStatus -natsConn_unsubscribe(natsConnection *nc, natsSubscription *sub, int max, bool drainMode, int64_t timeout); - -natsStatus -natsConn_enqueueUnsubProto(natsConnection *nc, int64_t sid); - -natsStatus -natsConn_drainSub(natsConnection *nc, natsSubscription *sub, bool checkConnDrainStatus); - -bool -natsConn_isDraining(natsConnection *nc); - -bool -natsConn_isDrainingPubs(natsConnection *nc); - -void -natsConn_removeSubscription(natsConnection *nc, natsSubscription *sub); - -void -natsConn_processAsyncINFO(natsConnection *nc, char *buf, int len); - -natsStatus -natsConn_addRespInfo(respInfo **newResp, natsConnection *nc, char *respInbox, int respInboxSize); - -void -natsConn_disposeRespInfo(natsConnection *nc, respInfo *resp, bool needsLock); - -natsStatus -natsConn_initResp(natsConnection *nc, natsMsgHandler cb); - -void -natsConn_destroyRespPool(natsConnection *nc); - -natsStatus -natsConn_publish(natsConnection *nc, natsMsg *msg, const char *reply, bool directFlush); - -natsStatus -natsConn_userCreds(char **userJWT, char **customErrTxt, void *closure); - -natsStatus -natsConn_signatureHandler(char **customErrTxt, unsigned char **sig, int *sigLen, const char *nonce, void *closure); - -natsStatus -natsConn_sendSubProto(natsConnection *nc, const char *subject, const char *queue, int64_t sid); - -natsStatus -natsConn_sendUnsubProto(natsConnection *nc, int64_t subId, int max); - -#define natsConn_setFilter(c, f) natsConn_setFilterWithClosure((c), (f), NULL) - -void -natsConn_setFilterWithClosure(natsConnection *nc, natsMsgFilter f, void* closure); - -natsStatus -natsConn_initInbox(natsConnection *nc, char *buf, int bufSize, char **newInbox, bool *allocated); - -natsStatus -natsConn_newInbox(natsConnection *nc, natsInbox **newInbox); - -bool -natsConn_srvVersionAtLeast(natsConnection *nc, int major, int minor, int update); - -void -natsConn_defaultErrHandler(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure); - -void -natsConn_close(natsConnection *nc); - -void -natsConn_destroy(natsConnection *nc, bool fromPublicDestroy); +#define CONNTRACEf DEVNOLOGf +#define CONNDEBUGf DEVNOLOGf +#define CONNERROR DEVNOLOG +#endif + +#ifdef DEV_MODE_CONN_TRACE +#define CONNTRACE_out(_buf) DEVTRACEf("-> ", "%zu: '%s'", (_buf)->len, (natsString_debugPrintable(_buf, 0))); +#define CONNTRACE_in(_buf) DEVTRACEf("<- ", "%zu: '%s'", (_buf)->len, (natsString_debugPrintable(_buf, 0))); +#else +#define CONNTRACE_out(_buf) +#define CONNTRACE_in(_buf) +#endif #endif /* CONN_H_ */ diff --git a/src/conn_parse.c b/src/conn_parse.c new file mode 100644 index 000000000..b62a4e20b --- /dev/null +++ b/src/conn_parse.c @@ -0,0 +1,960 @@ +// Copyright 2015-2020 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 "json.h" +#include "conn.h" + +// cloneMsgArg is used when the split buffer scenario has the pubArg in the existing read buffer, but +// we need to hold onto it into the next read. +// static natsStatus +// _cloneMsgArg(natsConnection *nc) +// { +// natsStatus s; +// natsParser *ps = nc->ps; +// int subjLen = natsBuf_len(ps->ma.subject); + +// s = natsBuf_InitWith(&(ps->argBufRec), +// ps->scratch, +// 0, +// sizeof(ps->scratch)); +// if (STILL_OK(s)) +// { +// ps->argBuf = &(ps->argBufRec); + +// s = natsBuf_Append(ps->argBuf, +// natsBuf_data(ps->ma.subject), +// natsBuf_len(ps->ma.subject)); +// if (STILL_OK(s)) +// { +// natsBuf_Destroy(ps->ma.subject); +// ps->ma.subject = NULL; + +// s = natsBuf_InitWith(&(ps->ma.subjectRec), +// ps->scratch, +// subjLen, +// subjLen); +// if (STILL_OK(s)) +// ps->ma.subject = &(ps->ma.subjectRec); +// } +// } +// if ((STILL_OK(s)) && (ps->ma.reply != NULL)) +// { +// s = natsBuf_Append(ps->argBuf, +// natsBuf_data(ps->ma.reply), +// natsBuf_len(ps->ma.reply)); +// if (STILL_OK(s)) +// { +// int replyLen = natsBuf_len(ps->ma.reply); + +// natsBuf_Destroy(ps->ma.reply); +// ps->ma.reply = NULL; + +// s = natsBuf_InitWith(&(ps->ma.replyRec), +// ps->scratch + subjLen, +// replyLen, +// replyLen); +// if (STILL_OK(s)) +// ps->ma.reply = &(ps->ma.replyRec); +// } +// } + +// return s; +// } + +// struct slice +// { +// uint8_t *start; +// int len; +// }; + +// static natsStatus +// _processMsgArgs(natsParser *ps, natsConnection *nc, uint8_t *buf, int bufLen) +// { +// natsStatus s = NATS_OK; +// int start = -1; +// int index = 0; +// int i; +// uint8_t b; +// struct slice slices[5]; +// char errTxt[256]; +// int indexLimit = 3; +// int minArgs = 3; +// int maxArgs = 4; +// bool hasHeaders = (ps->hdr >= 0 ? true : false); + +// // If headers, the content should be: +// // [reply] +// // otherwise: +// // [reply] +// if (hasHeaders) +// { +// indexLimit = 4; +// minArgs = 4; +// maxArgs = 5; +// } + +// for (i = 0; i < bufLen; i++) +// { +// b = buf[i]; + +// if (((b == ' ') || (b == '\t') || (b == '\r') || (b == '\n'))) +// { +// if (start >=0) +// { +// if (index > indexLimit) +// { +// s = NATS_PROTOCOL_ERROR; +// break; +// } +// slices[index].start = buf + start; +// slices[index].len = i - start; +// index++; +// start = -1; +// } +// } +// else if (start < 0) +// { +// start = i; +// } +// } +// if ((STILL_OK(s)) && (start >= 0)) +// { +// if (index > indexLimit) +// { +// s = NATS_PROTOCOL_ERROR; +// } +// else +// { +// slices[index].start = buf + start; +// slices[index].len = i - start; +// index++; +// } +// } +// if ((STILL_OK(s)) && ((index == minArgs) || (index == maxArgs))) +// { +// int maSizeIndex = index-1; // position of size is always last. +// int hdrSizeIndex = index-2; // position of hdr size is always before last. + +// s = natsBuf_InitWith(&(ps->ma.subjectRec), +// slices[0].start, +// slices[0].len, +// slices[0].len); +// if (STILL_OK(s)) +// { +// ps->ma.subject = &(ps->ma.subjectRec); + +// ps->ma.sid = nats_ParseInt64((const char *)slices[1].start, slices[1].len); + +// if (index == minArgs) +// { +// ps->ma.reply = NULL; +// } +// else +// { +// s = natsBuf_InitWith(&(ps->ma.replyRec), +// slices[2].start, +// slices[2].len, +// slices[2].len); +// if (STILL_OK(s)) +// { +// ps->ma.reply = &(ps->ma.replyRec); +// } +// } +// } +// if (STILL_OK(s)) +// { +// if (hasHeaders) +// { +// ps->ma.hdr = (int) nats_ParseInt64((const char*)slices[hdrSizeIndex].start, +// slices[hdrSizeIndex].len); +// } +// ps->ma.size = (int) nats_ParseInt64((const char*)slices[maSizeIndex].start, +// slices[maSizeIndex].len); +// } +// } +// else +// { +// snprintf(errTxt, sizeof(errTxt), "%s", "processMsgArgs Parse Error: wrong number of arguments"); +// s = NATS_PROTOCOL_ERROR; +// } +// if (ps->ma.sid < 0) +// { +// snprintf(errTxt, sizeof(errTxt), "processMsgArgs Bad or Missing Sid: '%.*s'", +// bufLen, buf); +// s = NATS_PROTOCOL_ERROR; +// } +// if (ps->ma.size < 0) +// { +// snprintf(errTxt, sizeof(errTxt), "processMsgArgs Bad or Missing Size: '%.*s'", +// bufLen, buf); +// s = NATS_PROTOCOL_ERROR; +// } +// if (hasHeaders && ((ps->ma.hdr < 0) || (ps->ma.hdr > ps->ma.size))) +// { +// snprintf(errTxt, sizeof(errTxt), "processMsgArgs Bad or Missing Header Size: '%.*s'", +// bufLen, buf); +// s = NATS_PROTOCOL_ERROR; +// } + +// if (s != NATS_OK) +// { +// // natsConn_Lock(nc); <>//<> +// snprintf(nc->errStr, sizeof(nc->errStr), "%s", errTxt); +// nc->err = s; +// // natsConn_Unlock(nc); +// } + +// return s; +// } + +typedef enum +{ + OP_START = 0, + OP_END, + OP_PLUS, + OP_PLUS_O, + OP_PLUS_OK, + OP_MINUS, + OP_MINUS_E, + OP_MINUS_ER, + OP_MINUS_ERR, + OP_MINUS_ERR_SPC, + MINUS_ERR_ARG, + OP_M, + OP_MS, + OP_MSG, + OP_MSG_SPC, + MSG_ARG, + MSG_PAYLOAD, + MSG_END, + OP_H, + OP_P, + OP_PI, + OP_PIN, + OP_PO, + OP_PON, + OP_I, + OP_IN, + OP_INF, + OP_INFO, + INFO_ARG, + CRLF, + CRLF_CR, + +} natsOp; + +struct __natsParser +{ + natsOp state; + natsOp nextState; + bool skipWhitespace; + + natsStatus (*completef)(natsParser *ps, natsConnection *nc); + + natsJSONParser *jsonParser; + nats_JSON *json; +}; + +natsStatus natsConn_createParser(natsParser **ps, natsPool *pool) +{ + return CHECK_NO_MEMORY( + *ps = nats_palloc(pool, sizeof(natsParser))); +} + +bool natsConn_expectingNewOp(natsParser *ps) +{ + return ps == NULL || ps->state == OP_START; +} + +static natsStatus _completeINFO(natsParser *ps, natsConnection *nc) +{ + natsStatus s = natsConn_processInfo(nc, ps->json); + CONNTRACEf("ParseOp: completed INFO: %s", (STILL_OK(s) ? "OK" : "ERROR")); + return s; +} + +static natsStatus _completePONG(natsParser *ps, natsConnection *nc) +{ + natsStatus s = natsConn_processPong(nc); + CONNTRACEf("ParseOp: completed PONG: %s", (STILL_OK(s) ? "OK" : "ERROR")); + return s; +} + +static natsStatus _completePING(natsParser *ps, natsConnection *nc) +{ + natsStatus s = natsConn_processPing(nc); + CONNTRACEf("ParseOp: completed PING: %s", (STILL_OK(s) ? "OK" : "ERROR")); + return s; +} + +// parse is the fast protocol parser engine. +natsStatus +natsConn_parseOp(natsConnection *nc, uint8_t *buf, uint8_t *end, size_t *consumed) +{ + natsStatus s = NATS_OK; + natsParser *ps = nc->ps; + uint8_t *p = buf; + uint8_t b; + + for (; (STILL_OK(s)) && (p < end) && (ps->state != OP_END); p++) + { + b = *p; + + if ((ps->skipWhitespace) && ((b == ' ') || (b == '\t'))) + continue; + + switch (ps->state) + { + case OP_START: + { + ps->skipWhitespace = false; + switch (b) + { + // case 'M': + // case 'm': + // ps->state = OP_M; + // ps->hdr = -1; + // ps->ma.hdr = -1; + // break; + // case 'H': + // case 'h': + // ps->state = OP_H; + // ps->hdr = 0; + // ps->ma.hdr = 0; + // break; + case 'P': + case 'p': + ps->state = OP_P; + break; + // case '+': + // ps->state = OP_PLUS; + // break; + // case '-': + // ps->state = OP_MINUS; + // break; + case 'I': + case 'i': + ps->state = OP_I; + continue; + default: + s = nats_setError(NATS_PROTOCOL_ERROR, "Expected an operation, got: '%c'", b); + } + continue; + } + case CRLF: + { + switch (b) + { + case '\r': + ps->state = CRLF_CR; + continue; + default: + s = nats_setError(NATS_PROTOCOL_ERROR, "Expected a CRLF, got: '%x'", b); + } + continue; + } + case CRLF_CR: + { + switch (b) + { + case '\n': + ps->state = ps->nextState; + ps->nextState = 0; + continue; + default: + s = nats_setError(NATS_PROTOCOL_ERROR, "Expected a CRLF, got: '%x'", b); + } + continue; + } + case OP_I: + { + switch (b) + { + case 'N': + case 'n': + ps->state = OP_IN; + continue; + default: + s = nats_setError(NATS_PROTOCOL_ERROR, "Expected INFO, got: '%c'", b); + } + continue; + } + case OP_IN: + { + switch (b) + { + case 'F': + case 'f': + ps->state = OP_INF; + continue; + default: + s = nats_setError(NATS_PROTOCOL_ERROR, "Expected INFO, got: '%c'", b); + } + continue; + } + case OP_INF: + { + switch (b) + { + case 'O': + case 'o': + ps->state = OP_INFO; + continue; + default: + s = nats_setError(NATS_PROTOCOL_ERROR, "Expected INFO, got: '%c'", b); + } + continue; + } + case OP_INFO: + { + switch (b) + { + case ' ': + case '\t': + s = natsJSONParser_Create(&(ps->jsonParser), nc->opPool); + if (s != NATS_OK) + continue; + ps->json = NULL; + ps->state = INFO_ARG; + ps->skipWhitespace = true; + continue; + default: + s = nats_setError(NATS_PROTOCOL_ERROR, "Expected a space, got: '%c'", b); + } + continue; + } + case INFO_ARG: + { + size_t consumedByJSON = 0; + s = natsJSONParser_Parse(&ps->json, ps->jsonParser, p, end, &consumedByJSON); + p += consumedByJSON; + if (s != NATS_OK) + continue; + + if (ps->json != NULL) + { + ps->state = CRLF; + ps->completef = _completeINFO; + ps->nextState = OP_END; + } + continue; + } + // case OP_H: + // { + // switch (b) + // { + // case 'M': + // case 'm': + // ps->state = OP_M; + // break; + // default: + // goto parseErr; + // } + // break; + // } + // case OP_M: + // { + // switch (b) + // { + // case 'S': + // case 's': + // ps->state = OP_MS; + // break; + // default: + // goto parseErr; + // } + // break; + // } + // case OP_MS: + // { + // switch (b) + // { + // case 'G': + // case 'g': + // ps->state = OP_MSG; + // break; + // default: + // goto parseErr; + // } + // break; + // } + // case OP_MSG: + // { + // switch (b) + // { + // case ' ': + // case '\t': + // ps->state = OP_MSG_SPC; + // break; + // default: + // goto parseErr; + // } + // break; + // } + // case OP_MSG_SPC: + // { + // switch (b) + // { + // case ' ': + // case '\t': + // continue; + // default: + // ps->state = MSG_ARG; + // ps->afterSpace = i; + // break; + // } + // break; + // } + // case MSG_ARG: + // { + // switch (b) + // { + // case '\r': + // ps->drop = 1; + // break; + // case '\n': + // { + // uint8_t *start = NULL; + // size_t len = 0; + + // if (ps->argBuf != NULL) + // { + // start = natsBuf_data(ps->argBuf); + // len = natsBuf_len(ps->argBuf); + // } + // else + // { + // start = buf + ps->afterSpace; + // len = (i - ps->drop) - ps->afterSpace; + // } + + // s = _processMsgArgs(ps, nc, start, len); + // if (STILL_OK(s)) + // { + // ps->drop = 0; + // ps->afterSpace = i+1; + // ps->state = MSG_PAYLOAD; + + // // jump ahead with the index. If this overruns + // // what is left we fall out and process split + // // buffer. + // i = ps->afterSpace + ps->ma.size - 1; + // } + // break; + // } + // default: + // { + // if (ps->argBuf != NULL) + // s = natsBuf_addB(ps->argBuf, b); + // break; + // } + // } + // break; + // } + // case MSG_PAYLOAD: + // { + // bool done = false; + + // if (ps->msgBuf != NULL) + // { + // if (natsBuf_len(ps->msgBuf) >= ps->ma.size) + // { + // s = natsConn_processMsg(nc, + // natsBuf_data(ps->msgBuf), + // natsBuf_len(ps->msgBuf)); + // done = true; + // } + // else + // { + // // copy as much as we can to the buffer and skip ahead. + // int toCopy = ps->ma.size - natsBuf_len(ps->msgBuf); + // int avail = bufLen - i; + + // if (avail < toCopy) + // toCopy = avail; + + // if (toCopy > 0) + // { + // s = natsBuf_Append(ps->msgBuf, buf+i, toCopy); + // if (STILL_OK(s)) + // i += toCopy - 1; + // } + // else + // { + // s = natsBuf_addB(ps->msgBuf, b); + // } + // } + // } + // else if (i-ps->afterSpace >= ps->ma.size) + // { + // uint8_t *start = NULL; + // size_t len = 0; + + // start = buf + ps->afterSpace; + // len = (i - ps->afterSpace); + + // s = natsConn_processMsg(nc, start, len); + + // done = true; + // } + + // if (done) + // { + // natsBuf_Destroy(ps->argBuf); + // ps->argBuf = NULL; + // natsBuf_Destroy(ps->msgBuf); + // ps->msgBuf = NULL; + // ps->state = MSG_END; + // } + + // break; + // } + // case MSG_END: + // { + // switch (b) + // { + // case '\n': + // ps->drop = 0; + // ps->afterSpace = i+1; + // ps->state = OP_START; + // break; + // default: + // continue; + // } + // break; + // } + // case OP_PLUS: + // { + // switch (b) + // { + // case 'O': + // case 'o': + // ps->state = OP_PLUS_O; + // break; + // default: + // goto parseErr; + // } + // break; + // } + // case OP_PLUS_O: + // { + // switch (b) + // { + // case 'K': + // case 'k': + // ps->state = OP_PLUS_OK; + // break; + // default: + // goto parseErr; + // } + // break; + // } + // case OP_PLUS_OK: + // { + // switch (b) + // { + // case '\n': + // natsConn_processOK(nc); + // ps->drop = 0; + // ps->state = OP_START; + // break; + // } + // break; + // } + // case OP_MINUS: + // { + // switch (b) + // { + // case 'E': + // case 'e': + // ps->state = OP_MINUS_E; + // break; + // default: + // goto parseErr; + // } + // break; + // } + // case OP_MINUS_E: + // { + // switch (b) + // { + // case 'R': + // case 'r': + // ps->state = OP_MINUS_ER; + // break; + // default: + // goto parseErr; + // } + // break; + // } + // case OP_MINUS_ER: + // { + // switch (b) + // { + // case 'R': + // case 'r': + // ps->state = OP_MINUS_ERR; + // break; + // default: + // goto parseErr; + // } + // break; + // } + // case OP_MINUS_ERR: + // { + // switch (b) + // { + // case ' ': + // case '\t': + // ps->state = OP_MINUS_ERR_SPC; + // break; + // default: + // goto parseErr; + // } + // break; + // } + // case OP_MINUS_ERR_SPC: + // { + // switch (b) + // { + // case ' ': + // case '\t': + // continue; + // default: + // ps->state = MINUS_ERR_ARG; + // ps->afterSpace = i; + // break; + // } + // break; + // } + // case MINUS_ERR_ARG: + // { + // switch (b) + // { + // case '\r': + // ps->drop = 1; + // break; + // case '\n': + // { + // // uint8_t *start = NULL; + // // size_t len = 0; + + // // if (ps->argBuf != NULL) + // // { + // // start = natsBuf_data(ps->argBuf); + // // len = natsBuf_len(ps->argBuf); + // // } + // // else + // // { + // // start = buf + ps->afterSpace; + // // len = (i - ps->drop) - ps->afterSpace; + // // } + + // // <>//<> + // // natsConn_processErr(nc, start, len); + + // ps->drop = 0; + // ps->afterSpace = i+1; + // ps->state = OP_START; + + // if (ps->argBuf != NULL) + // { + // natsBuf_Destroy(ps->argBuf); + // ps->argBuf = NULL; + // } + + // break; + // } + // default: + // { + // if (ps->argBuf != NULL) + // s = natsBuf_addB(ps->argBuf, b); + + // break; + // } + // } + // break; + // } + case OP_P: + { + switch (b) + { + case 'I': + case 'i': + ps->state = OP_PI; + continue; + ; + case 'O': + case 'o': + ps->state = OP_PO; + continue; + default: + s = nats_setError(NATS_PROTOCOL_ERROR, "Expected a PING or PONG, got: '%c'", b); + } + continue; + } + case OP_PO: + { + switch (b) + { + case 'N': + case 'n': + ps->state = OP_PON; + continue; + default: + s = nats_setError(NATS_PROTOCOL_ERROR, "Expected a PONG, got: '%c'", b); + } + continue; + } + case OP_PON: + { + switch (b) + { + case 'G': + case 'g': + ps->state = CRLF; + ps->completef = _completePONG; + ps->nextState = OP_END; + continue; + default: + s = nats_setError(NATS_PROTOCOL_ERROR, "Expected a PING, got: '%c'", b); + } + continue; + } + case OP_PI: + { + switch (b) + { + case 'N': + case 'n': + ps->state = OP_PIN; + continue; + default: + s = nats_setError(NATS_PROTOCOL_ERROR, "Expected a PING, got: '%c'", b); + } + continue; + } + case OP_PIN: + { + switch (b) + { + case 'G': + case 'g': + ps->state = CRLF; + ps->completef = _completePING; + ps->nextState = OP_END; + continue; + default: + s = nats_setError(NATS_PROTOCOL_ERROR, "Expected a PING, got: '%c'", b); + } + continue; + } + default: + s = nats_setError(NATS_PROTOCOL_ERROR, "(unreachable) invalid state: %d", ps->state); + } + } + + // // Check for split buffer scenarios + // if ((STILL_OK(s)) + // && ((ps->state == MSG_ARG) + // || (ps->state == MINUS_ERR_ARG) + // || (ps->state == INFO_ARG)) + // && (ps->argBuf == NULL)) + // { + // s = natsBuf_InitWith(&(ps->argBufRec), + // ps->scratch, + // 0, + // sizeof(ps->scratch)); + // if (STILL_OK(s)) + // { + // ps->argBuf = &(ps->argBufRec); + // s = natsBuf_Append(ps->argBuf, + // buf + ps->afterSpace, + // (i - ps->drop) - ps->afterSpace); + // } + // } + // // Check for split msg + // if ((STILL_OK(s)) + // && (ps->state == MSG_PAYLOAD) && (ps->msgBuf == NULL)) + // { + // // We need to clone the msgArg if it is still referencing the + // // read buffer and we are not able to process the msg. + // if (ps->argBuf == NULL) + // s = _cloneMsgArg(nc); + + // if (STILL_OK(s)) + // { + // size_t remainingInScratch; + // size_t toCopy; + + // #ifdef _WIN32 + // // Suppresses the warning that ps->argBuf may be NULL. + // // If ps->argBuf is NULL above, then _cloneMsgArg() will set it. If 's' + // // is NATS_OK here, then ps->argBuf can't be NULL. + // #pragma warning(suppress: 6011) + // #endif + + // // If we will overflow the scratch buffer, just create a + // // new buffer to hold the split message. + // remainingInScratch = sizeof(ps->scratch) - natsBuf_len(ps->argBuf); + // toCopy = bufLen - ps->afterSpace; + + // if (ps->ma.size > remainingInScratch) + // { + // s = natsBuf_CreateCalloc(&(ps->msgBuf), ps->ma.size); + // } + // else + // { + // s = natsBuf_InitWith(&(ps->msgBufRec), + // ps->scratch + natsBuf_len(ps->argBuf), + // 0, remainingInScratch); + // if (STILL_OK(s)) + // ps->msgBuf = &(ps->msgBufRec); + // } + // if (STILL_OK(s)) + // s = natsBuf_Append(ps->msgBuf, + // buf + ps->afterSpace, + // toCopy); + // } + // } + + // if (s != NATS_OK) + // { + // // Let's clear all our pointers... + // natsBuf_Destroy(ps->argBuf); + // ps->argBuf = NULL; + // natsBuf_Destroy(ps->msgBuf); + // ps->msgBuf = NULL; + // natsBuf_Destroy(ps->ma.subject); + // ps->ma.subject = NULL; + // natsBuf_Destroy(ps->ma.reply); + // ps->ma.reply = NULL; + // } + + if (consumed != NULL) + *consumed = p - buf; + + if ((STILL_OK(s)) && (ps->state == OP_END) && (ps->completef != NULL)) + { + s = ps->completef(ps, nc); + ps->state = OP_START; + } + + if (s != NATS_OK) + { + snprintf(nc->errStr, sizeof(nc->errStr), "Parse Error [%u]: '%.*s'", ps->state, (int)(end - p), p); + } + return NATS_UPDATE_ERR_STACK(s); +} diff --git a/src/conn_read.c b/src/conn_read.c new file mode 100644 index 000000000..2a9057547 --- /dev/null +++ b/src/conn_read.c @@ -0,0 +1,247 @@ +// Copyright 2015-2024 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 "conn.h" +#include "json.h" +#include "opts.h" +#include "servers.h" + +static uint64_t _poolc = 0; + +static inline natsStatus _startOp(natsReadBuffer **rbuf, natsConnection *nc) +{ + char name[64]; + if (rbuf != NULL) + *rbuf = NULL; + + _poolc++; + snprintf(name, sizeof(name), "conn-op-%" PRIu64, _poolc); + if (nc->opPool == NULL) + return nats_createPool(&nc->opPool, &nc->opts->mem, name); + else + return nats_recyclePool(&(nc->opPool), rbuf); +} + +void nats_ProcessReadEvent(natsConnection *nc) +{ + natsStatus s = NATS_OK; + natsReadBuffer *rbuf = NULL; + natsString haveRead = {.len = 0}; + + if (!(nc->evAttached) || (nc->sockCtx.fd == NATS_SOCK_INVALID)) + { + return; + } + natsConn_retain(nc); + + // Recycle will create a new pool if needed. We need to do it before we read the data. + if (natsConn_expectingNewOp(nc->ps)) + s = _startOp(NULL, nc); + + // We always exhaust any data we had read in the previous call, so just ask + // for a (new or sufficently free) read buffer. + IFOK(s, natsPool_getReadBuffer(&rbuf, nc->opPool)); + if (STILL_OK(s)) + { + // Do not try to read again here on success. If more than one connection + // is attached to the same loop, and there is a constant stream of data + // coming for the first connection, this would starve the second + // connection. So return and we will be called back later by the event + // loop. + haveRead.data = natsReadBuffer_end(rbuf); + s = natsSock_Read(&(nc->sockCtx), + natsReadBuffer_end(rbuf), + natsReadBuffer_available(&nc->opts->mem, rbuf), + &haveRead.len); + + if (STILL_OK(s)) + CONNTRACE_in(&haveRead); + rbuf->buf.len += haveRead.len; // works on error. + } + + while ((STILL_OK(s)) && (rbuf != NULL) && (natsReadBuffer_unreadLen(rbuf) > 0)) + { + size_t consumedByParser = 0; + + // The parser either detects an end of an op, or consumes the entire slice given to it. We want to consume all of the data we have read, so if it's a new op and there's unread data, start a new buffer. + s = natsConn_parseOp(nc, rbuf->readFrom, natsReadBuffer_end(rbuf), &consumedByParser); + rbuf->readFrom += consumedByParser; // a no-op on error. + + // If the parser is ready for a new op, recycle the pool. Preserve and use + // any not yet parsed bytes. + if ((STILL_OK(s)) && natsConn_expectingNewOp(nc->ps)) + s = _startOp(&rbuf, nc); + } + + if (s != NATS_OK) + natsConn_processOpError(nc, s); + + natsConn_release(nc); +} + +// natsCon_processInfo is used to parse the info messages sent from the server. This +// function may update the server pool. +natsStatus +natsConn_processInfo(natsConnection *nc, nats_JSON *json) +{ + natsStatus s = NATS_OK; + bool sendConnect = false; + + // Check that we are in a valid state to process INFO. + switch (nc->state) + { + case NATS_CONN_STATUS_CONNECTED: + // Nothing else to do here. + break; + + case NATS_CONN_STATUS_CONNECTING: + sendConnect = true; + break; + + default: + return NATS_UPDATE_ERR_STACK(nats_setError(NATS_PROTOCOL_ERROR, + "Received INFO in an unexpected connection state: %d", nc->state)); + } + + if (nc->info == NULL) + { + IFOK(s, CHECK_NO_MEMORY( + nc->info = nats_palloc(nc->lifetimePool, sizeof(natsServerInfo)))); + } + + IFOK(s, nats_unmarshalServerInfo(json, nc->lifetimePool, nc->info)); + if (STILL_OK(s)) + { + nc->srvVersion.ma = 0; + nc->srvVersion.mi = 0; + nc->srvVersion.up = 0; + + if ((nc->info != NULL) && !nats_isCStringEmpty(nc->info->version)) + sscanf(nc->info->version, "%d.%d.%d", &(nc->srvVersion.ma), &(nc->srvVersion.mi), &(nc->srvVersion.up)); + } + + // The array could be empty/not present on initial connect, + // if advertise is disabled on that server, or servers that + // did not include themselves in the async INFO protocol. + // If empty, do not remove the implicit servers from the pool. + if ((STILL_OK(s)) && !nc->opts->net.ignoreDiscoveredServers && (nc->info->connectURLsCount > 0)) + { + bool added = false; + const char *tlsName = NULL; + + if ((nc->cur != NULL) && (nc->cur->url != NULL) && !nats_HostIsIP(nc->cur->url->host)) + tlsName = (const char *)nc->cur->url->host; + + s = natsServers_addNewURLs(nc->servers, + nc->cur->url, + nc->info->connectURLs, + nc->info->connectURLsCount, + tlsName, + &added); + // if ((STILL_OK(s)) && added && !nc->initc && postDiscoveredServersCb) <>//<> + // natsAsyncCb_PostConnHandler(nc, ASYNC_DISCOVERED_SERVERS); + } + + if (sendConnect) + { + // Send the CONNECT protocol to the server. + IFOK(s, natsConn_sendConnect(nc)); + IFOK(s, natsConn_sendPing(nc)); + } + + // Process the LDM callback after the above. It will cover cases where + // we have connect URLs and invoke discovered server callback, and case + // where we don't. + // if ((STILL_OK(s)) && nc->info.lameDuckMode && postLameDuckCb) <>//<> + // natsAsyncCb_PostConnHandler(nc, ASYNC_LAME_DUCK_MODE); + + if (s != NATS_OK) + s = nats_setError(s, "Invalid protocol: %s", nats_GetLastError(NULL)); + + return NATS_UPDATE_ERR_STACK(s); +} + +natsStatus natsConn_processPing(natsConnection *nc) +{ + return natsConn_asyncWrite(nc, &nats_PONG_CRLF, NULL, NULL); +} + +natsStatus natsConn_processPong(natsConnection *nc) +{ + // Check that we are in a valid state to process INFO. + switch (nc->state) + { + case NATS_CONN_STATUS_CONNECTED: + // Nothing else to do here. + break; + + case NATS_CONN_STATUS_CONNECTING: + if (nc->opts->net.connected != NULL) + nc->opts->net.connected(nc, nc->opts->net.connectedClosure); + nc->state = NATS_CONN_STATUS_CONNECTED; + break; + + default: + return NATS_UPDATE_ERR_STACK(nats_setError(NATS_PROTOCOL_ERROR, + "Received INFO in an unexpected connection state: %d", nc->state)); + } + + if (nc->pingsOut == 0) + return nats_setError(NATS_PROTOCOL_ERROR, "%s", "Received unexpected PONG"); + + nc->pingsOut--; + if (nc->pingsOut > nc->opts->proto.maxPingsOut) + return nats_setDefaultError(NATS_STALE_CONNECTION); + + return NATS_OK; +} + +// void natsConn_processErr(natsConnection *nc, char *buf, int bufLen) +// { +// // char error[256]; +// // bool close = false; +// // int authErrCode = 0; + +// // // Copy the error in this local buffer. +// // snprintf(error, sizeof(error), "%.*s", bufLen, buf); + +// // // Trim spaces and remove quotes. +// // nats_NormalizeErr(error); + +// // if (strcasecmp(error, STALE_CONNECTION) == 0) +// // { +// // natsConn_processOpError(nc, NATS_STALE_CONNECTION, false); +// // } +// // else if (nats_strcasestr(error, PERMISSIONS_ERR) != NULL) +// // { +// // _processPermissionViolation(nc, error); +// // } +// // else if ((authErrCode = _checkAuthError(error)) != 0) +// // { +// // natsConn_Lock(nc); +// // close = _processAuthError(nc, authErrCode, error); +// // natsConn_Unlock(nc); +// // } +// // else +// // { +// // close = true; +// // natsConn_Lock(nc); +// // nc->err = NATS_ERR; +// // snprintf(nc->errStr, sizeof(nc->errStr), "%s", error); +// // natsConn_Unlock(nc); +// // } +// // if (close) +// _close(nc, NATS_CONN_STATUS_CLOSED, false, true); +// } diff --git a/src/conn_write.c b/src/conn_write.c new file mode 100644 index 000000000..35d3eb10e --- /dev/null +++ b/src/conn_write.c @@ -0,0 +1,235 @@ +// Copyright 2015-2024 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 "conn.h" +#include "opts.h" +#include "servers.h" +#include "json.h" + +natsStatus natsConn_asyncWrite(natsConnection *nc, const natsString *buf, + natsOnWrittenF donef, void *doneUserData) +{ + natsStatus s = NATS_OK; + + if (nc->sockCtx.fd == NATS_SOCK_INVALID) + return NATS_CONNECTION_CLOSED; + + s = natsWriteChain_add(&(nc->writeChain), buf, donef, doneUserData); + + // The ev write method will schedule this event if not already active. + IFOK(s, nc->ev.write(nc->evState, NATS_EV_ADD)); + + return NATS_UPDATE_ERR_STACK(s); +} + +void nats_ProcessWriteEvent(natsConnection *nc) +{ + natsStatus s = NATS_OK; + + if (nc->sockCtx.fd == NATS_SOCK_INVALID) + return; + + while (natsWriteChain_len(&nc->writeChain) > 0) + { + natsWriteBuffer *wbuf = natsWriteChain_get(&nc->writeChain); + natsString written = { + .data = wbuf->buf.data + wbuf->written, + .len = wbuf->buf.len - wbuf->written + }; + + s = natsSock_Write(&(nc->sockCtx), &written, &written.len); + if (s != NATS_OK) + { + natsConn_processOpError(nc, s); + return; + } + CONNTRACE_out(&written); + + wbuf->written += written.len; + if (wbuf->written >= wbuf->buf.len) + { + natsWriteChain_done(nc, &nc->writeChain); + } + } +} + +static natsStatus _grow(natsWriteQueue *w, size_t cap) +{ + if (natsWriteChain_cap(w) >= cap) + return NATS_OK; + if (natsWriteChain_cap(w) == w->opts->writeQueueMaxBuffers) + return nats_setError( + NATS_INSUFFICIENT_BUFFER, + "write chain has already reached the maximum capacity: %zu", w->opts->writeQueueMaxBuffers); + + size_t allocSize = nats_pageAlignedSize(w->opts, cap * sizeof(natsWriteBuffer)); + cap = allocSize / sizeof(natsWriteBuffer); + if (cap > w->opts->writeQueueMaxBuffers) + cap = w->opts->writeQueueMaxBuffers; + + natsHeap *heap = nats_globalHeap(); + natsWriteBuffer *newChain = heap->realloc(heap, w->chain, allocSize); + if (newChain == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + + if (natsWriteChain_isWrapped(w)) + { + size_t toCopy = natsWriteChain_endPos(w) * sizeof(natsWriteBuffer); + if (toCopy > 0) + { + memcpy( + newChain + natsWriteChain_cap(w), // The end of the old buffer + newChain, // from the beginning + natsWriteChain_endPos(w) * sizeof(natsWriteBuffer)); // to the end of the wrapped around part. + } + } + + size_t len = natsWriteChain_len(w); + w->start = natsWriteChain_startPos(w); // use the old capacity to mod the start position. + w->end = w->start + len; + w->capacity = cap; + w->chain = newChain; + return NATS_OK; +} + +natsStatus natsWriteChain_init(natsWriteQueue *queueAllocaledByCaller, natsMemOptions *opts) +{ + natsWriteQueue *w = queueAllocaledByCaller; + if (w == NULL) + return NATS_UPDATE_ERR_STACK(NATS_INVALID_ARG); + + memset(w, 0, sizeof(natsWriteQueue)); + w->start = 0; + w->end = 0; + w->opts = opts; + + return _grow(w, opts->writeQueueBuffers); +} + +// TODO <>/<> donef should process errors???? +natsStatus natsWriteChain_add(natsWriteQueue *w, const natsString *buffer, + natsOnWrittenF donef, void *doneUserData) +{ + // if we are full, attempt to grow the buffers queue. + if (natsWriteChain_isFull(w)) + { + natsStatus s = _grow(w, natsWriteChain_cap(w) * 2); + if (s != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); + } + + size_t pos = natsWriteChain_endPos(w); + w->chain[pos].buf.data = buffer->data; + w->chain[pos].buf.len = buffer->len; + w->chain[pos].written = 0; + w->chain[pos].done = donef; + w->chain[pos].userData = doneUserData; + w->end++; + return NATS_OK; +} + +natsWriteBuffer *natsWriteChain_get(natsWriteQueue *w) +{ + if (natsWriteChain_isEmpty(w)) + return NULL; + return &w->chain[natsWriteChain_startPos(w)]; +} + +natsStatus natsWriteChain_done(natsConnection *nc, natsWriteQueue *w) +{ + natsWriteBuffer *wbuf = natsWriteChain_get(w); + + if (wbuf == NULL) + return nats_setError(NATS_ERR, "%s", "no current write buffer"); + + if (wbuf->done != NULL) + { + wbuf->done(nc, wbuf->buf.data, wbuf->userData); + } + + w->start++; + return NATS_OK; +} + +static natsStatus +_connectProto(natsString **proto, natsConnection *nc) +{ + natsOptions *opts = nc->opts; + const char *token = NULL; + const char *user = NULL; + const char *pwd = NULL; + + // Check if NoEcho is set and we have a server that supports it. + if (opts->proto.noEcho && (nc->info->proto < 1)) + return NATS_NO_SERVER_SUPPORT; + + if (nc->cur->url->username != NULL) + user = nc->cur->url->username; + if (nc->cur->url->password != NULL) + pwd = nc->cur->url->password; + if ((user != NULL) && (pwd == NULL)) + { + token = user; + user = NULL; + } + if ((user == NULL) && (token == NULL)) + { + // Take from options (possibly all NULL) + user = opts->user; + pwd = opts->password; + token = opts->token; + + // Options take precedence for an implicit URL. If above is still + // empty, we will check if we have saved a user from an explicit + // URL in the server pool. + if (nats_isCStringEmpty(user) && nats_isCStringEmpty(token) && (nc->servers->user != NULL)) + { + user = nc->servers->user; + pwd = nc->servers->pwd; + // Again, if there is no password, assume username is token. + if (pwd == NULL) + { + token = user; + user = NULL; + } + } + } + + return nats_marshalConnect(proto, nc, user, pwd, token, + nc->opts->name, nc->info->headers, + (nc->info->headers && !nc->opts->proto.disableNoResponders)); +} + +natsStatus +natsConn_sendPing(natsConnection *nc) +{ + natsStatus s = natsConn_asyncWrite(nc, &nats_PING_CRLF, NULL, NULL); + if (STILL_OK(s)) + nc->pingsOut++; + return NATS_UPDATE_ERR_STACK(s); +} + +natsStatus +natsConn_sendConnect(natsConnection *nc) +{ + natsStatus s = NATS_OK; + natsString *proto; + + // Allocates the memory in the connect pool. + s = _connectProto(&proto, nc); + IFOK(s, natsConn_asyncWrite(nc, proto, NULL, NULL)); + return NATS_UPDATE_ERR_STACK(s); +} + diff --git a/src/conn_write.h b/src/conn_write.h new file mode 100644 index 000000000..4834ca6bd --- /dev/null +++ b/src/conn_write.h @@ -0,0 +1,54 @@ +// Copyright 2015-2018 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. + +#ifndef CONN_WRITE_H_ +#define CONN_WRITE_H_ + +typedef struct __natsWriteBuffer_s natsWriteBuffer; +typedef struct __natsWriteQueue_s natsWriteQueue; + +typedef void (*natsOnWrittenF)(natsConnection *nc, uint8_t *buffer, void *closure); + +struct __natsWriteBuffer_s +{ + natsString buf; // must be first, so natsWrieBuf* is also a natsString* + size_t written; + natsOnWrittenF done; + void *userData; +}; + +struct __natsWriteQueue_s +{ + natsMemOptions *opts; + size_t capacity; + natsWriteBuffer *chain; // an allocated array of 'capacity' + size_t start; + size_t end; +}; + +natsStatus natsConn_asyncWrite(natsConnection *nc, const natsString *buf, natsOnWrittenF donef, void *doneUserData); + +natsStatus natsWriteChain_init(natsWriteQueue *w, natsMemOptions *opts); +natsStatus natsWriteChain_add(natsWriteQueue *w, const natsString *buf, natsOnWrittenF donef, void *doneUserData); +natsWriteBuffer *natsWriteChain_get(natsWriteQueue *w); +natsStatus natsWriteChain_done(natsConnection *nc, natsWriteQueue *w); + +#define natsWriteChain_cap(_ch) ((_ch)->capacity) +#define natsWriteChain_startPos(_ch) ((_ch)->start % (_ch)->capacity) +#define natsWriteChain_endPos(_ch) ((_ch)->end % (_ch)->capacity) +#define natsWriteChain_len(_w) ((_w)->end - (_w)->start) +#define natsWriteChain_isWrapped(_ch) (natsWriteChain_endPos(_ch) < natsWriteChain_startPos(_ch)) +#define natsWriteChain_isEmpty(_ch) ((_ch)->start == (_ch)->end) +#define natsWriteChain_isFull(_ch) ((natsWriteChain_len(_ch) + 1) == natsWriteChain_cap(_ch)) + +#endif /* CONN_WRITE_H_ */ diff --git a/src/crypto.c b/src/crypto.c index fdebf07e9..f47a612a2 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -11,6 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "natsp.h" + #include "crypto.h" #ifdef NATS_USE_LIBSODIUM @@ -498,7 +500,8 @@ natsCrypto_Sign(const unsigned char *seed, char *sm = NULL; unsigned char sk[NATS_CRYPTO_SECRET_BYTES]; - sm = NATS_MALLOC(inputLen + NATS_CRYPTO_SIGN_BYTES); + // TODO <>/<> use a pool for allocations + sm = nats_alloc(inputLen + NATS_CRYPTO_SIGN_BYTES, false); if (sm == NULL) return nats_setDefaultError(NATS_NO_MEMORY); @@ -507,7 +510,7 @@ natsCrypto_Sign(const unsigned char *seed, memcpy(signature, sm, NATS_CRYPTO_SIGN_BYTES); secure_memzero((void*) sm, NATS_CRYPTO_SIGN_BYTES); secure_memzero((void*) sk, sizeof(sk)); - NATS_FREE(sm); + nats_free(sm); return NATS_OK; } diff --git a/src/crypto.h b/src/crypto.h index 921c1544b..7f40f47fc 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -14,8 +14,6 @@ #ifndef CRYPTO_H_ #define CRYPTO_H_ -#include "status.h" - #define NATS_CRYPTO_SECRET_BYTES 64 #define NATS_CRYPTO_SIGN_BYTES 64 diff --git a/src/dev_mode.h b/src/dev_mode.h new file mode 100644 index 000000000..e1a0bafef --- /dev/null +++ b/src/dev_mode.h @@ -0,0 +1,95 @@ +// Copyright 2015-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. + +#ifndef DEV_MODE_H_ +#define DEV_MODE_H_ + +#ifndef _WIN32 +#define __SHORT_FILE__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) +#else +#define __SHORT_FILE__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) +#endif + +#define DEVNOLOG(s) +#define DEVNOLOGf(fmt, ...) +#define DEVNOLOGx(file, line, func, fmt, ...) + +// Comment/uncomment to enable debug logging and tracking. +#define DEV_MODE (1) + +#ifdef DEV_MODE + +// Comment/uncomment to enable debug logging and tracking in specific modules. +// #define DEV_MODE_CONN +// #define DEV_MODE_CONN_TRACE +#define DEV_MODE_MEM_HEAP +// #define DEV_MODE_MEM_POOL +// #define DEV_MODE_MEM_POOL_TRACE +// #define DEV_MODE_JSON + +#define DEV_MODE_DEFAULT_LOG_LEVEL DEV_MODE_TRACE + +//----------------------------------------------------------------------------- +// Dev mode stuff... + +#define DEV_MODE_CTX , __SHORT_FILE__, __LINE__, __NATS_FUNCTION__ +#define DEV_MODE_PASSARGS , file, line, func +#define DEV_MODE_ARGS , const char *file, int line, const char *func + +#define DEV_MODE_ERROR 1 +#define DEV_MODE_WARN 2 +#define DEV_MODE_INFO 3 +#define DEV_MODE_DEBUG 4 +#define DEV_MODE_TRACE 5 + +#define DEV_MODE_LEVEL_STR(level) (level == DEV_MODE_ERROR ? "ERROR" : (level == DEV_MODE_WARN ? "WARN" : (level == DEV_MODE_INFO ? "INFO" : (level == DEV_MODE_DEBUG ? "DEBUG" : "TRACE")))) + +extern int nats_devmode_log_level; + +#define DEVLOGx(level, module, file, line, func, fmt, ...) \ + ((level <= nats_devmode_log_level) ? fprintf(stderr, "%5s: %6s: " fmt " (%s:%s:%d)\n", DEV_MODE_LEVEL_STR(level), (module), __VA_ARGS__, (func), (file), (line)) : 0) +#define DEVLOG(level, module, str) DEVLOGx((level), (module), __SHORT_FILE__, __LINE__, __func__, "%s", str) +#define DEVLOGf(level, module, fmt, ...) DEVLOGx((level), (module), __SHORT_FILE__, __LINE__, __func__, fmt, __VA_ARGS__) + +const char *natsString_debugPrintable(natsString *buf, size_t limit); +const char *natsString_debugPrintableN(const uint8_t *data, size_t len, size_t limit); +const char *natsString_debugPrintableC(const char *buf, size_t limit); +const char *natsPool_debugPrintable(natsString *buf, natsPool *pool, size_t limit); + +#else // DEV_MODE + +#define DEV_MODE_ARGS +#define DEV_MODE_CTX +#define DEVLOGx(level, module, file, line, func, fmt, ...) +#define DEVLOG(level, module, str) +#define DEVLOGf(level, module, fmt, ...) + +#endif // DEV_MODE + +#define DEVERROR(module, str) DEVLOG(DEV_MODE_ERROR, (module), (str)) +#define DEVERRORf(module, fmt, ...) DEVLOGf(DEV_MODE_ERROR, (module), fmt, __VA_ARGS__) +#define DEVERRORx(module, file, line, func, fmt, ...) DEVLOGx(DEV_MODE_ERROR, (module), (file), (line), (func), fmt, __VA_ARGS__) +#define DEVWARN(module, str) DEVLOG(DEV_MODE_WARN, (module), (str)) +#define DEVWARNf(module, fmt, ...) DEVLOGf(DEV_MODE_WARN, (module), fmt, __VA_ARGS__) +#define DEVWARNx(module, file, line, func, fmt, ...) DEVLOGx(DEV_MODE_WARN, (module), (file), (line), (func), fmt, __VA_ARGS__) +#define DEVINFO(module, str) DEVLOG(DEV_MODE_INFO, (module), (str)) +#define DEVINFOf(module, fmt, ...) DEVLOGf(DEV_MODE_INFO, (module), fmt, __VA_ARGS__) +#define DEVINFOx(module, file, line, func, fmt, ...) DEVLOGx(DEV_MODE_INFO, (module), (file), (line), (func), fmt, __VA_ARGS__) +#define DEVDEBUG(module, str) DEVLOG(DEV_MODE_DEBUG, (module +#define DEVDEBUGf(module, fmt, ...) DEVLOGf(DEV_MODE_DEBUG, (module), fmt, __VA_ARGS__) +#define DEVDEBUGx(module, file, line, func, fmt, ...) DEVLOGx(DEV_MODE_DEBUG, (module +#define DEVTRACE(module, str) DEVLOG(DEV_MODE_TRACE, (module), (str)) +#define DEVTRACEf(module, fmt, ...) DEVLOGf(DEV_MODE_TRACE, (module), fmt, __VA_ARGS__) +#define DEVTRACEx(module, file, line, func, fmt, ...) DEVLOGx(DEV_MODE_TRACE, (module), (file), (line), (func), fmt, __VA_ARGS__) + +#endif /* DEV_MODE_H_ */ diff --git a/src/err.h b/src/err.h index 9a7a95350..f525ea117 100644 --- a/src/err.h +++ b/src/err.h @@ -15,10 +15,6 @@ #ifndef ERR_H_ #define ERR_H_ -#include "status.h" -#include "nats.h" -#include "natsp.h" - #define NATS_SSL_ERR_REASON_STRING ERR_reason_error_string(ERR_get_error()) #define nats_setDefaultError(e) nats_setError((e), "%s", natsStatus_GetText(e)) diff --git a/src/gc.h b/src/gc.h deleted file mode 100644 index 22d0728a4..000000000 --- a/src/gc.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2015-2018 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. - - -#ifndef GC_H_ -#define GC_H_ - -// This callback implements the specific code to free the given object. -// This is invoked by the garbage collector. -typedef void (*nats_FreeObjectCb)(void *object); - -// This structure should be included as the first field of any object -// that needs to be garbage collected. -typedef struct __natsGCItem -{ - struct __natsGCItem *next; - nats_FreeObjectCb freeCb; - -} natsGCItem; - -// Gives the object to the garbage collector. -// Returns 'true' if the GC takes ownership, 'false' otherwise (in this case, -// the caller is responsible for freeing the object). -bool -natsGC_collect(natsGCItem *item); - - -#endif /* GC_H_ */ diff --git a/src/hash.c b/src/hash.c index a7b2ccbce..09deccade 100644 --- a/src/hash.c +++ b/src/hash.c @@ -13,381 +13,26 @@ #include "natsp.h" -#include - -#include "status.h" -#include "mem.h" #include "hash.h" -#define _freeEntry(e) { NATS_FREE(e); (e) = NULL; } - -#define _OFF32 (2166136261) -#define _YP32 (709607) +#define _OFF32 (2166136261) +#define _YP32 (709607) #define _BSZ (8) #define _WSZ (4) -static int _DWSZ = _WSZ << 1; // 8 -static int _DDWSZ = _WSZ << 2; // 16 +static int _DWSZ = _WSZ << 1; // 8 +static int _DDWSZ = _WSZ << 2; // 16 static int _MAX_BKT_SIZE = (1 << 30) - 1; -natsStatus -natsHash_Create(natsHash **newHash, int initialSize) -{ - natsHash *hash = NULL; - - if (initialSize <= 0) - return nats_setDefaultError(NATS_INVALID_ARG); - - if ((initialSize & (initialSize - 1)) != 0) - { - // Size of buckets must be power of 2 - initialSize--; - initialSize |= initialSize >> 1; - initialSize |= initialSize >> 2; - initialSize |= initialSize >> 4; - initialSize |= initialSize >> 8; - initialSize |= initialSize >> 16; - initialSize++; - } - - hash = (natsHash*) NATS_CALLOC(1, sizeof(natsHash)); - if (hash == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - hash->mask = (initialSize - 1); - hash->numBkts = initialSize; - hash->canResize = true; - hash->bkts = (natsHashEntry**) NATS_CALLOC(initialSize, sizeof(natsHashEntry*)); - if (hash->bkts == NULL) - { - NATS_FREE(hash); - return nats_setDefaultError(NATS_NO_MEMORY); - } - - *newHash = hash; - - return NATS_OK; -} - -static natsStatus -_resize(natsHash *hash, int newSize) -{ - natsHashEntry **bkts = NULL; - int newMask = newSize - 1; - natsHashEntry *ne; - natsHashEntry *e; - int k; - int newIndex; - - bkts = (natsHashEntry**) NATS_CALLOC(newSize, sizeof(natsHashEntry*)); - if (bkts == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - for (k = 0; k < hash->numBkts; k++) - { - e = hash->bkts[k]; - while (e != NULL) - { - ne = e; - e = e->next; - - newIndex = ne->key & newMask; - ne->next = bkts[newIndex]; - bkts[newIndex] = ne; - } - } - - NATS_FREE(hash->bkts); - hash->bkts = bkts; - hash->mask = newMask; - hash->numBkts = newSize; - - return NATS_OK; -} - -static natsStatus -_grow(natsHash *hash) -{ - // Can't grow beyond max signed int for now - if (hash->numBkts >= _MAX_BKT_SIZE) - return nats_setDefaultError(NATS_NO_MEMORY); - - return _resize(hash, 2 * (hash->numBkts)); -} - -static void -_shrink(natsHash *hash) -{ - if (hash->numBkts <= _BSZ) - return; - - // Ignore memory issue when resizing, since if we fail to allocate - // the original hash is still intact. - (void) _resize(hash, hash->numBkts / 2); -} - -static natsHashEntry* -_createEntry(int64_t key, void *data) -{ - natsHashEntry *e = (natsHashEntry*) NATS_MALLOC(sizeof(natsHashEntry)); - - if (e == NULL) - return NULL; - - e->key = key; - e->data = data; - e->next = NULL; - - return e; -} - -natsStatus -natsHash_Set(natsHash *hash, int64_t key, void *data, void **oldData) -{ - natsStatus s = NATS_OK; - int index = (int) (key & hash->mask); - natsHashEntry *newEntry = NULL; - natsHashEntry *e; - - if (oldData != NULL) - *oldData = NULL; - - e = (natsHashEntry*) hash->bkts[index]; - while (e != NULL) - { - if (e->key == key) - { - // Success, replace data field - if (oldData != NULL) - *oldData = e->data; - e->data = data; - return NATS_OK; - } - - e = e->next; - } - - // We have a new entry here - newEntry = _createEntry(key, data); - if (newEntry == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - newEntry->next = hash->bkts[index]; - hash->bkts[index] = newEntry; - hash->used++; - - // Check for resizing - if (hash->canResize && (hash->used > hash->numBkts)) - s = _grow(hash); - - return NATS_UPDATE_ERR_STACK(s); -} - -void* -natsHash_Get(natsHash *hash, int64_t key) -{ - natsHashEntry *e; - - e = hash->bkts[key & hash->mask]; - while (e != NULL) - { - if (e->key == key) - return e->data; - - e = e->next; - } - - return NULL; -} - -static void -_maybeShrink(natsHash *hash) -{ - if (hash->canResize - && (hash->numBkts > _BSZ) - && (hash->used < hash->numBkts / 4)) - { - _shrink(hash); - } -} - -void* -natsHash_Remove(natsHash *hash, int64_t key) -{ - natsHashEntry *entryRemoved = NULL; - void *dataRemoved = NULL; - natsHashEntry **e; - - e = (natsHashEntry**) &(hash->bkts[key & hash->mask]); - while (*e != NULL) - { - if ((*e)->key == key) - { - // Success - entryRemoved = *e; - dataRemoved = entryRemoved->data; - - *e = entryRemoved->next; - _freeEntry(entryRemoved); - - hash->used--; - - // Check for resizing - _maybeShrink(hash); - - break; - } - - e = (natsHashEntry**) &((*e)->next); - } - - return dataRemoved; -} - -natsStatus -natsHash_RemoveSingle(natsHash *hash, int64_t *key, void **data) -{ - natsHashEntry *e = NULL; - int i; - - if (hash->used != 1) - return nats_setDefaultError(NATS_ERR); - - for (i=0; inumBkts; i++) - { - e = hash->bkts[i]; - if (e != NULL) - { - if (key != NULL) - *key = e->key; - if (data != NULL) - *data = e->data; - _freeEntry(e); - - hash->used--; - hash->bkts[i] = NULL; - - // Check for resizing - _maybeShrink(hash); - - break; - } - } - return NATS_OK; -} - -void -natsHash_Destroy(natsHash *hash) -{ - natsHashEntry *e, *ne; - int i; - - if (hash == NULL) - return; - - for (i = 0; i < hash->numBkts; i++) - { - e = hash->bkts[i]; - while (e != NULL) - { - ne = e->next; - - _freeEntry(e); - - e = ne; - } - } - - NATS_FREE(hash->bkts); - NATS_FREE(hash); -} - -void -natsHashIter_Init(natsHashIter *iter, natsHash *hash) -{ - memset(iter, 0, sizeof(natsHashIter)); - - hash->canResize = false; - iter->hash = hash; - iter->current = hash->bkts[0]; - iter->next = iter->current; -} - -bool -natsHashIter_Next(natsHashIter *iter, int64_t *key, void **value) -{ - if ((iter->started) && (iter->next == NULL)) - return false; - - if (!(iter->started) && (iter->current == NULL)) - { - while ((iter->next == NULL) - && (iter->currBkt < (iter->hash->numBkts - 1))) - { - iter->next = iter->hash->bkts[++(iter->currBkt)]; - } - - if (iter->next == NULL) - { - iter->started = true; - return false; - } - } - - iter->started = true; - - iter->current = iter->next; - if (iter->current != NULL) - { - if (key != NULL) - *key = iter->current->key; - if (value != NULL) - *value = iter->current->data; - - iter->next = iter->current->next; - } - - while ((iter->next == NULL) - && (iter->currBkt < (iter->hash->numBkts - 1))) - { - iter->next = iter->hash->bkts[++(iter->currBkt)]; - } - - return true; -} - -natsStatus -natsHashIter_RemoveCurrent(natsHashIter *iter) -{ - int64_t key; - - if (iter->current == NULL) - return nats_setDefaultError(NATS_NOT_FOUND); - - key = iter->current->key; - iter->current = iter->next; - - (void) natsHash_Remove(iter->hash, key); - - return NATS_OK; -} - -void -natsHashIter_Done(natsHashIter *iter) -{ - iter->hash->canResize = true; -} - - // Jesteress derivative of FNV1A from [http://www.sanmayce.com/Fastest_Hash/] uint32_t natsStrHash_Hash(const char *data, int dataLen) { - int i = 0; - int dlen = dataLen; - uint32_t h32 = (uint32_t)_OFF32; + int i = 0; + int dlen = dataLen; + uint32_t h32 = (uint32_t)_OFF32; uint64_t k1, k2; uint32_t k3; @@ -395,7 +40,7 @@ natsStrHash_Hash(const char *data, int dataLen) { memcpy(&k1, &(data[i]), sizeof(k1)); memcpy(&k2, &(data[i + 4]), sizeof(k2)); - h32 = (uint32_t) ((((uint64_t) h32) ^ ((k1<<5 | k1>>27) ^ k2)) * _YP32); + h32 = (uint32_t)((((uint64_t)h32) ^ ((k1 << 5 | k1 >> 27) ^ k2)) * _YP32); i += _DDWSZ; } @@ -403,13 +48,13 @@ natsStrHash_Hash(const char *data, int dataLen) if ((dlen & _DWSZ) != 0) { memcpy(&k1, &(data[i]), sizeof(k1)); - h32 = (uint32_t) ((((uint64_t) h32) ^ k1) * _YP32); + h32 = (uint32_t)((((uint64_t)h32) ^ k1) * _YP32); i += _DWSZ; } if ((dlen & _WSZ) != 0) { memcpy(&k3, &(data[i]), sizeof(k3)); - h32 = (uint32_t) ((((uint64_t) h32) ^ (uint64_t) k3) * _YP32); + h32 = (uint32_t)((((uint64_t)h32) ^ (uint64_t)k3) * _YP32); i += _WSZ; } if ((dlen & 1) != 0) @@ -420,12 +65,14 @@ natsStrHash_Hash(const char *data, int dataLen) return h32 ^ (h32 >> 16); } +// pool is optional. If provided, all allocations will be done in the (ever +// growing) pool, otherwise NATS_CALLOC/NATS_FREE are used. natsStatus -natsStrHash_Create(natsStrHash **newHash, int initialSize) +natsStrHash_Create(natsStrHash **newHash, natsPool *pool, int initialSize) { natsStrHash *hash = NULL; - if (initialSize <= 0) + if ((initialSize <= 0) || (initialSize > _MAX_BKT_SIZE) || (pool == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); if ((initialSize & (initialSize - 1)) != 0) @@ -440,17 +87,17 @@ natsStrHash_Create(natsStrHash **newHash, int initialSize) initialSize++; } - hash = (natsStrHash*) NATS_CALLOC(1, sizeof(natsStrHash)); + hash = nats_palloc(pool, sizeof(natsStrHash)); if (hash == NULL) return nats_setDefaultError(NATS_NO_MEMORY); - hash->mask = (initialSize - 1); - hash->numBkts = initialSize; + hash->pool = pool; + hash->mask = (initialSize - 1); + hash->numBkts = initialSize; hash->canResize = true; - hash->bkts = (natsStrHashEntry**) NATS_CALLOC(initialSize, sizeof(natsStrHashEntry*)); + hash->bkts = (natsStrHashEntry **)nats_palloc(pool, initialSize * sizeof(natsStrHashEntry *)); if (hash->bkts == NULL) { - NATS_FREE(hash); return nats_setDefaultError(NATS_NO_MEMORY); } @@ -462,14 +109,14 @@ natsStrHash_Create(natsStrHash **newHash, int initialSize) static natsStatus _resizeStr(natsStrHash *hash, int newSize) { - natsStrHashEntry **bkts = NULL; - int newMask = newSize - 1; - natsStrHashEntry *ne; - natsStrHashEntry *e; - int k; - int newIndex; - - bkts = (natsStrHashEntry**) NATS_CALLOC(newSize, sizeof(natsStrHashEntry*)); + natsStrHashEntry **bkts = NULL; + int newMask = newSize - 1; + natsStrHashEntry *ne; + natsStrHashEntry *e; + int k; + int newIndex; + + bkts = (natsStrHashEntry **)nats_palloc(hash->pool, newSize * sizeof(natsStrHashEntry *) ); if (bkts == NULL) return nats_setDefaultError(NATS_NO_MEMORY); @@ -479,7 +126,7 @@ _resizeStr(natsStrHash *hash, int newSize) while (e != NULL) { ne = e; - e = e->next; + e = e->next; newIndex = ne->hk & newMask; ne->next = bkts[newIndex]; @@ -487,7 +134,6 @@ _resizeStr(natsStrHash *hash, int newSize) } } - NATS_FREE(hash->bkts); hash->bkts = bkts; hash->mask = newMask; hash->numBkts = newSize; @@ -505,108 +151,41 @@ _growStr(natsStrHash *hash) return _resizeStr(hash, 2 * (hash->numBkts)); } -static void -_shrinkStr(natsStrHash *hash) -{ - if (hash->numBkts <= _BSZ) - return; - - // Ignore memory issue when resizing, since if we fail to allocate - // the original hash is still intact. - (void) _resizeStr(hash, hash->numBkts / 2); -} - - -static natsStrHashEntry* -_createStrEntry(uint32_t hk, char *key, bool copyKey, bool freeKey, void *data) -{ - natsStrHashEntry *e = (natsStrHashEntry*) NATS_MALLOC(sizeof(natsStrHashEntry)); - - if (e == NULL) - return NULL; - - e->hk = hk; - e->key = (copyKey ? NATS_STRDUP(key) : key); - e->freeKey = freeKey; - e->data = data; - e->next = NULL; - - if (e->key == NULL) - { - NATS_FREE(e); - return NULL; - } - - return e; -} - // Note that it would be invalid to call with copyKey:true and freeKey:false, // since this would lead to a memory leak. natsStatus -natsStrHash_SetEx(natsStrHash *hash, char *key, bool copyKey, bool freeKey, - void *data, void **oldData) +natsStrHash_Set(natsStrHash *hash, char *key, void *data) { - natsStatus s = NATS_OK; - uint32_t hk = 0; - int index = 0; - natsStrHashEntry *newEntry = NULL; - natsStrHashEntry *e; - char *oldKey; + natsStatus s = NATS_OK; + uint32_t hk = 0; + int index = 0; + natsStrHashEntry *newEntry = NULL; + natsStrHashEntry *e; - if (oldData != NULL) - *oldData = NULL; - - hk = natsStrHash_Hash(key, (int) strlen(key)); + hk = natsStrHash_Hash(key, nats_strlen(key)); index = hk & hash->mask; - e = (natsStrHashEntry*) hash->bkts[index]; + e = (natsStrHashEntry *)hash->bkts[index]; while (e != NULL) { - if ((e->hk == hk) - && (strcmp(e->key, key) == 0)) + if ((e->hk != hk) || (strcmp(e->key, key) != 0)) { - // Success, replace data field - if (oldData != NULL) - *oldData = e->data; - e->data = data; - - // Need to care for situations where previous call - // for same key hash was with different pointers and or - // "config" values (copyKey/freeKey). - - // But if nothing has changed (same pointers and config) we - // can bail early. - if ((key == e->key) && (freeKey == e->freeKey)) - return NATS_OK; - - oldKey = e->key; - // First try to dup the key if required. - if (copyKey) - { - char *newKey = NATS_STRDUP(key); - if (newKey == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - e->key = newKey; - } - // If old config say that we had ownership, then free the - // old key now. - if (e->freeKey) - NATS_FREE(oldKey); - - // Keep track of ownership of this key (copied or not). - e->freeKey = freeKey; - return NATS_OK; + e = e->next; + continue; } - - e = e->next; + // Success, replace data field + e->data = data; + return NATS_OK; } // We have a new entry here - newEntry = _createStrEntry(hk, key, copyKey, freeKey, data); + newEntry = (natsStrHashEntry *)nats_palloc(hash->pool, sizeof(natsStrHashEntry)); if (newEntry == NULL) return nats_setDefaultError(NATS_NO_MEMORY); + newEntry->hk = hk; + newEntry->key = key; + newEntry->data = data; newEntry->next = hash->bkts[index]; hash->bkts[index] = newEntry; hash->used++; @@ -618,17 +197,16 @@ natsStrHash_SetEx(natsStrHash *hash, char *key, bool copyKey, bool freeKey, return NATS_UPDATE_ERR_STACK(s); } -void* -natsStrHash_GetEx(natsStrHash *hash, char *key, int keyLen) +void * +natsStrHash_Get(natsStrHash *hash, char *key, int keyLen) { - natsStrHashEntry *e; - uint32_t hk = natsStrHash_Hash(key, keyLen); + natsStrHashEntry *e; + uint32_t hk = natsStrHash_Hash(key, keyLen); e = hash->bkts[hk & hash->mask]; while (e != NULL) { - if ((e->hk == hk) - && (strncmp(e->key, key, keyLen) == 0)) + if ((e->hk == hk) && (strncmp(e->key, key, keyLen) == 0)) { return e->data; } @@ -639,152 +217,24 @@ natsStrHash_GetEx(natsStrHash *hash, char *key, int keyLen) return NULL; } -static void -_freeStrEntry(natsStrHashEntry *e) -{ - if (e->freeKey) - NATS_FREE(e->key); - - NATS_FREE(e); -} - -static void -_maybeShrinkStr(natsStrHash *hash) -{ - if (hash->canResize - && (hash->numBkts > _BSZ) - && (hash->used < hash->numBkts / 4)) - { - _shrinkStr(hash); - } -} - -void* -natsStrHash_Remove(natsStrHash *hash, char *key) -{ - natsStrHashEntry *entryRemoved = NULL; - void *dataRemoved = NULL; - natsStrHashEntry **e; - uint32_t hk; - - hk = natsStrHash_Hash(key, (int) strlen(key)); - - e = (natsStrHashEntry**) &(hash->bkts[hk & hash->mask]); - while (*e != NULL) - { - if (((*e)->hk == hk) - && (strcmp((*e)->key, key) == 0)) - { - // Success - entryRemoved = *e; - dataRemoved = entryRemoved->data; - - *e = entryRemoved->next; - _freeStrEntry(entryRemoved); - - hash->used--; - - // Check for resizing - _maybeShrinkStr(hash); - - break; - } - - e = (natsStrHashEntry**) &((*e)->next); - } - - return dataRemoved; -} - -natsStatus -natsStrHash_RemoveSingle(natsStrHash *hash, char **key, void **data) -{ - natsStrHashEntry *e = NULL; - int i; - - if (hash->used != 1) - return nats_setDefaultError(NATS_ERR); - - for (i=0; inumBkts; i++) - { - e = hash->bkts[i]; - if (e != NULL) - { - if (key != NULL) - { - char *retKey = e->key; - - if (e->freeKey) - { - retKey = NATS_STRDUP(e->key); - if (retKey == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - } - *key = retKey; - } - if (data != NULL) - *data = e->data; - _freeStrEntry(e); - - hash->used--; - hash->bkts[i] = NULL; - - // Check for resizing - _maybeShrinkStr(hash); - - break; - } - } - return NATS_OK; -} - -void -natsStrHash_Destroy(natsStrHash *hash) -{ - natsStrHashEntry *e, *ne; - int i; - - if (hash == NULL) - return; - - for (i = 0; i < hash->numBkts; i++) - { - e = hash->bkts[i]; - while (e != NULL) - { - ne = e->next; - - _freeStrEntry(e); - - e = ne; - } - } - - NATS_FREE(hash->bkts); - NATS_FREE(hash); -} - -void -natsStrHashIter_Init(natsStrHashIter *iter, natsStrHash *hash) +void natsStrHashIter_Init(natsStrHashIter *iter, natsStrHash *hash) { memset(iter, 0, sizeof(natsStrHashIter)); hash->canResize = false; - iter->hash = hash; - iter->current = hash->bkts[0]; - iter->next = iter->current; + iter->hash = hash; + iter->current = hash->bkts[0]; + iter->next = iter->current; } -bool -natsStrHashIter_Next(natsStrHashIter *iter, char **key, void **value) +bool natsStrHashIter_Next(natsStrHashIter *iter, char **key, void **value) { if ((iter->started) && (iter->next == NULL)) return false; if (!(iter->started) && (iter->current == NULL)) { - while ((iter->next == NULL) - && (iter->currBkt < (iter->hash->numBkts - 1))) + while ((iter->next == NULL) && (iter->currBkt < (iter->hash->numBkts - 1))) { iter->next = iter->hash->bkts[++(iter->currBkt)]; } @@ -809,8 +259,7 @@ natsStrHashIter_Next(natsStrHashIter *iter, char **key, void **value) iter->next = iter->current->next; } - while ((iter->next == NULL) - && (iter->currBkt < (iter->hash->numBkts - 1))) + while ((iter->next == NULL) && (iter->currBkt < (iter->hash->numBkts - 1))) { iter->next = iter->hash->bkts[++(iter->currBkt)]; } @@ -818,24 +267,7 @@ natsStrHashIter_Next(natsStrHashIter *iter, char **key, void **value) return true; } -natsStatus -natsStrHashIter_RemoveCurrent(natsStrHashIter *iter) -{ - char *key; - - if (iter->current == NULL) - return nats_setDefaultError(NATS_NOT_FOUND); - - key = iter->current->key; - iter->current = iter->next; - - (void) natsStrHash_Remove(iter->hash, key); - - return NATS_OK; -} - -void -natsStrHashIter_Done(natsStrHashIter *iter) +void natsStrHashIter_Done(natsStrHashIter *iter) { - natsHashIter_Done((natsHashIter*) iter); + iter->hash->canResize = true; } diff --git a/src/hash.h b/src/hash.h index d1b513ff8..43c2304c1 100644 --- a/src/hash.h +++ b/src/hash.h @@ -11,152 +11,60 @@ // See the License for the specific language governing permissions and // limitations under the License. - #ifndef HASH_H_ #define HASH_H_ -struct __natsHashEntry; - -typedef struct __natsHashEntry -{ - int64_t key; - void *data; - struct __natsHashEntry *next; - -} natsHashEntry; - -typedef struct __natsHash -{ - natsHashEntry **bkts; - int numBkts; - int mask; - int used; - bool canResize; - -} natsHash; - -typedef struct __natsHashIter -{ - natsHash *hash; - natsHashEntry *current; - natsHashEntry *next; - int currBkt; - bool started; - -} natsHashIter; - typedef struct __natsStrHashEntry { - uint32_t hk; - char *key; - bool freeKey; - void *data; - struct __natsStrHashEntry *next; - + uint32_t hk; + char *key; + void *data; + struct __natsStrHashEntry *next; } natsStrHashEntry; -typedef struct __natsStrHash +struct __natsStrHash { - natsStrHashEntry **bkts; - int numBkts; - int mask; - int used; - bool canResize; - -} natsStrHash; - -typedef struct __natsStrHashIter + natsPool *pool; + struct __natsStrHashEntry **bkts; + int numBkts; + int mask; + int used; + bool canResize; +}; + +struct __natsStrHashIter { - natsStrHash *hash; - natsStrHashEntry *current; - natsStrHashEntry *next; - int currBkt; - bool started; - -} natsStrHashIter; - -#define natsHash_Count(h) ((h)->used) -#define natsStrHash_Count(h) ((h)->used) - -// -// Hash with in64_t as the key -// -natsStatus -natsHash_Create(natsHash **newHash, int initialSize); - -natsStatus -natsHash_Set(natsHash *hash, int64_t key, void *data, void **oldData); - -void* -natsHash_Get(natsHash *hash, int64_t key); - -void* -natsHash_Remove(natsHash *hash, int64_t key); + struct __natsStrHash *hash; + struct __natsStrHashEntry *current; + struct __natsStrHashEntry *next; + int currBkt; + bool started; +}; -natsStatus -natsHash_RemoveSingle(natsHash *hash, int64_t *key, void **data); - -void -natsHash_Destroy(natsHash *hash); +#define natsStrHash_Count(h) ((h)->used) // -// Iterator for Hash int64_t +// Hash with char* as the key, created in a memory pool. // -void -natsHashIter_Init(natsHashIter *iter, natsHash *hash); - -bool -natsHashIter_Next(natsHashIter *iter, int64_t *key, void **value); - natsStatus -natsHashIter_RemoveCurrent(natsHashIter *iter); - -void -natsHashIter_Done(natsHashIter *iter); - -// -// Hash with char* as the key -// -natsStatus -natsStrHash_Create(natsStrHash **newHash, int initialSize); +natsStrHash_Create(natsStrHash **newHash, natsPool *pool, int initialSize); uint32_t natsStrHash_Hash(const char *data, int dataLen); -#define natsStrHash_Set(h, k, c, d, o) natsStrHash_SetEx((h), (k), (c), ((c) ? true : false), (d), (o)) - -natsStatus -natsStrHash_SetEx(natsStrHash *hash, char *key, bool copyKey, bool freeKey, - void *data, void **oldData); - -#define natsStrHash_Get(h, k) natsStrHash_GetEx((h), (k), (int) strlen(k)) - -void* -natsStrHash_GetEx(natsStrHash *hash, char *key, int keyLen); - -void* -natsStrHash_Remove(natsStrHash *hash, char *key); - natsStatus -natsStrHash_RemoveSingle(natsStrHash *hash, char **key, void **data); +natsStrHash_Set(natsStrHash *hash, char *key, void *data); -void -natsStrHash_Destroy(natsStrHash *hash); +void * +natsStrHash_Get(natsStrHash *hash, char *key, int keyLen); // // Iterator for Hash char* // -void -natsStrHashIter_Init(natsStrHashIter *iter, natsStrHash *hash); - -bool -natsStrHashIter_Next(natsStrHashIter *iter, char **key, void **value); - -natsStatus -natsStrHashIter_RemoveCurrent(natsStrHashIter *iter); +void natsStrHashIter_Init(natsStrHashIter *iter, natsStrHash *hash); -void -natsStrHashIter_Done(natsStrHashIter *iter); +bool natsStrHashIter_Next(natsStrHashIter *iter, char **key, void **value); +void natsStrHashIter_Done(natsStrHashIter *iter); #endif /* HASH_H_ */ diff --git a/src/include/n-unix.h b/src/include/n-unix.h index ac4919a25..06a17db4b 100644 --- a/src/include/n-unix.h +++ b/src/include/n-unix.h @@ -39,11 +39,6 @@ #include #include -typedef pthread_t natsThread; -typedef pthread_key_t natsThreadLocal; -typedef pthread_mutex_t natsMutex; -typedef pthread_cond_t natsCondition; -typedef pthread_once_t natsInitOnceType; typedef socklen_t natsSockLen; typedef size_t natsRecvLen; @@ -59,7 +54,6 @@ typedef size_t natsRecvLen; #define __NATS_FUNCTION__ __func__ -#define nats_asprintf asprintf #define nats_strcasestr strcasestr #define nats_vsnprintf vsnprintf #define nats_strtok strtok_r diff --git a/src/include/n-win.h b/src/include/n-win.h index e84d65170..1b5611b76 100644 --- a/src/include/n-win.h +++ b/src/include/n-win.h @@ -26,24 +26,9 @@ #pragma comment(lib, "Ws2_32.lib") #pragma warning(disable : 4996) -typedef struct __natsThread -{ - HANDLE t; - DWORD id; - -} natsThread; - -typedef DWORD natsThreadLocal; - -typedef CRITICAL_SECTION natsMutex; -typedef CONDITION_VARIABLE natsCondition; -typedef INIT_ONCE natsInitOnceType; typedef int natsSockLen; typedef int natsRecvLen; -#define NATS_ONCE_TYPE INIT_ONCE -#define NATS_ONCE_STATIC_INIT INIT_ONCE_STATIC_INIT - #define NATS_SOCK_INVALID (INVALID_SOCKET) #define NATS_SOCK_CLOSE(s) closesocket((s)) #define NATS_SOCK_SHUTDOWN(s) {shutdown((s), SD_BOTH); closesocket((s));} @@ -65,8 +50,6 @@ typedef int natsRecvLen; #define nats_vscprintf _vscprintf -int -nats_asprintf(char **newStr, const char *fmt, ...); char* nats_strcasestr(const char *haystack, const char *needle); diff --git a/src/js.c b/src/js.c deleted file mode 100644 index e31dd2f0a..000000000 --- a/src/js.c +++ /dev/null @@ -1,3318 +0,0 @@ -// 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 - -#include "js.h" -#include "mem.h" -#include "conn.h" -#include "util.h" -#include "opts.h" -#include "sub.h" - -#ifdef DEV_MODE -// For type safety - -void js_lock(jsCtx *js) { natsMutex_Lock(js->mu); } -void js_unlock(jsCtx *js) { natsMutex_Unlock(js->mu); } - -static void _retain(jsCtx *js) { js->refs++; } -static void _release(jsCtx *js) { js->refs--; } - -#else - -#define _retain(js) ((js)->refs++) -#define _release(js) ((js)->refs--) - -#endif // DEV_MODE - - -const char* jsDefaultAPIPrefix = "$JS.API"; -const int64_t jsDefaultRequestWait = 5000; -const int64_t jsDefaultStallWait = 200; -const char *jsDigits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; -const int jsBase = 62; -const int64_t jsOrderedHBInterval = NATS_SECONDS_TO_NANOS(5); - -#define jsReplyTokenSize (8) -#define jsDefaultMaxMsgs (512 * 1024) - -#define jsLastConsumerSeqHdr "Nats-Last-Consumer" - -// Forward declarations -static void _hbTimerFired(natsTimer *timer, void* closure); -static void _hbTimerStopped(natsTimer *timer, void* closure); - -typedef struct __jsOrderedConsInfo -{ - int64_t osid; - int64_t nsid; - uint64_t sseq; - natsConnection *nc; - natsSubscription *sub; - char *ndlv; - natsThread *thread; - int max; - bool done; - -} jsOrderedConsInfo; - -static void -_destroyOptions(jsOptions *o) -{ - NATS_FREE((char*) o->Prefix); - NATS_FREE((char*) o->Stream.Purge.Subject); -} - -static void -_freeContext(jsCtx *js) -{ - natsConnection *nc = NULL; - - natsStrHash_Destroy(js->pm); - natsSubscription_Destroy(js->rsub); - _destroyOptions(&(js->opts)); - NATS_FREE(js->rpre); - natsCondition_Destroy(js->cond); - natsMutex_Destroy(js->mu); - natsTimer_Destroy(js->pmtmr); - nc = js->nc; - NATS_FREE(js); - - natsConn_release(nc); -} - -void -js_retain(jsCtx *js) -{ - js_lock(js); - js->refs++; - js_unlock(js); -} - -void -js_release(jsCtx *js) -{ - bool doFree; - - if (js == NULL) - return; - - js_lock(js); - doFree = (--(js->refs) == 0); - js_unlock(js); - - if (doFree) - _freeContext(js); -} - -static void -js_unlockAndRelease(jsCtx *js) -{ - bool doFree; - - doFree = (--(js->refs) == 0); - js_unlock(js); - - if (doFree) - _freeContext(js); -} - -static void -_destroyPMInfo(pmInfo *pmi) -{ - if (pmi == NULL) - return; - - NATS_FREE(pmi->subject); - NATS_FREE(pmi); -} - -void -jsCtx_Destroy(jsCtx *js) -{ - pmInfo *pm; - - if (js == NULL) - return; - - js_lock(js); - if (js->closed) - { - js_unlock(js); - return; - } - js->closed = true; - if (js->rsub != NULL) - { - natsSubscription_Destroy(js->rsub); - js->rsub = NULL; - } - if ((js->pm != NULL) && natsStrHash_Count(js->pm) > 0) - { - natsStrHashIter iter; - void *v = NULL; - - natsStrHashIter_Init(&iter, js->pm); - while (natsStrHashIter_Next(&iter, NULL, &v)) - { - natsMsg *msg = (natsMsg*) v; - natsStrHashIter_RemoveCurrent(&iter); - natsMsg_Destroy(msg); - } - } - while ((pm = js->pmHead) != NULL) - { - js->pmHead = pm->next; - _destroyPMInfo(pm); - } - if (js->pmtmr != NULL) - natsTimer_Stop(js->pmtmr); - js_unlockAndRelease(js); -} - -natsStatus -jsOptions_Init(jsOptions *opts) -{ - if (opts == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - memset(opts, 0, sizeof(jsOptions)); - return NATS_OK; -} - -// Parse the JSON represented by the NATS message's payload and returns the JSON object. -// Unmarshal the API response. -natsStatus -js_unmarshalResponse(jsApiResponse *ar, nats_JSON **new_json, natsMsg *resp) -{ - nats_JSON *json = NULL; - nats_JSON *err = NULL; - natsStatus s; - - memset(ar, 0, sizeof(jsApiResponse)); - - s = nats_JSONParse(&json, natsMsg_GetData(resp), natsMsg_GetDataLength(resp)); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - // Check if there is an "error" field. - s = nats_JSONGetObject(json, "error", &err); - if ((s == NATS_OK) && (err != NULL)) - { - s = nats_JSONGetInt(err, "code", &(ar->Error.Code)); - IFOK(s, nats_JSONGetUInt16(err, "err_code", &(ar->Error.ErrCode))); - IFOK(s, nats_JSONGetStr(err, "description", &(ar->Error.Description))); - } - - if (s == NATS_OK) - *new_json = json; - else - nats_JSONDestroy(json); - - return NATS_UPDATE_ERR_STACK(s); -} - -void -js_freeApiRespContent(jsApiResponse *ar) -{ - if (ar == NULL) - return; - - NATS_FREE(ar->Type); - NATS_FREE(ar->Error.Description); -} - -static natsStatus -_copyPurgeOptions(jsCtx *js, struct jsOptionsStreamPurge *o) -{ - natsStatus s = NATS_OK; - struct jsOptionsStreamPurge *po = &(js->opts.Stream.Purge); - - po->Sequence = o->Sequence; - po->Keep = o->Keep; - - if (!nats_IsStringEmpty(o->Subject)) - { - po->Subject = NATS_STRDUP(o->Subject); - if (po->Subject == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_copyStreamInfoOptions(jsCtx *js, struct jsOptionsStreamInfo *o) -{ - js->opts.Stream.Info.DeletedDetails = o->DeletedDetails; - return NATS_OK; -} - -natsStatus -natsConnection_JetStream(jsCtx **new_js, natsConnection *nc, jsOptions *opts) -{ - jsCtx *js = NULL; - natsStatus s; - - if ((new_js == NULL) || (nc == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - if (opts != NULL) - { - if (opts->Wait < 0) - return nats_setError(NATS_INVALID_ARG, "option 'Wait' (%" PRId64 ") cannot be negative", opts->Wait); - if (opts->PublishAsync.StallWait < 0) - return nats_setError(NATS_INVALID_ARG, "option 'PublishAsyncStallWait' (%" PRId64 ") cannot be negative", opts->PublishAsync.StallWait); - } - - js = (jsCtx*) NATS_CALLOC(1, sizeof(jsCtx)); - if (js == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - js->refs = 1; - // Retain the NATS connection and keep track of it so that if we - // detroy the context, in case of failure to fully initialize, - // we properly release the NATS connection. - natsConn_retain(nc); - js->nc = nc; - // This will be immutable and is computed based on the possible custom - // inbox prefix length (or the default "_INBOX.") - js->rpreLen = nc->inboxPfxLen+jsReplyTokenSize+1; - - s = natsMutex_Create(&(js->mu)); - if (s == NATS_OK) - { - // If Domain is set, use domain to create prefix. - if ((opts != NULL) && !nats_IsStringEmpty(opts->Domain)) - { - if (nats_asprintf((char**) &(js->opts.Prefix), "$JS.%.*s.API", - js_lenWithoutTrailingDot(opts->Domain), opts->Domain) < 0) - { - s = nats_setDefaultError(NATS_NO_MEMORY); - } - } - else if ((opts == NULL) || nats_IsStringEmpty(opts->Prefix)) - { - js->opts.Prefix = NATS_STRDUP(jsDefaultAPIPrefix); - if (js->opts.Prefix == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - else if (nats_asprintf((char**) &(js->opts.Prefix), "%.*s", - js_lenWithoutTrailingDot(opts->Prefix), opts->Prefix) < 0) - { - s = nats_setDefaultError(NATS_NO_MEMORY); - } - } - if ((s == NATS_OK) && (opts != NULL)) - { - struct jsOptionsPublishAsync *pa = &(js->opts.PublishAsync); - - pa->MaxPending = opts->PublishAsync.MaxPending; - // This takes precedence to error handler - if (opts->PublishAsync.AckHandler != NULL) - { - pa->AckHandler = opts->PublishAsync.AckHandler; - pa->AckHandlerClosure = opts->PublishAsync.AckHandlerClosure; - } - else - { - pa->ErrHandler = opts->PublishAsync.ErrHandler; - pa->ErrHandlerClosure = opts->PublishAsync.ErrHandlerClosure; - } - pa->StallWait = opts->PublishAsync.StallWait; - js->opts.Wait = opts->Wait; - } - if (js->opts.Wait == 0) - js->opts.Wait = jsDefaultRequestWait; - if (js->opts.PublishAsync.StallWait == 0) - js->opts.PublishAsync.StallWait = jsDefaultStallWait; - if ((s == NATS_OK) && (opts != NULL)) - { - s = _copyPurgeOptions(js, &(opts->Stream.Purge)); - IFOK(s, _copyStreamInfoOptions(js, &(opts->Stream.Info))); - } - - if (s == NATS_OK) - *new_js = js; - else - jsCtx_Destroy(js); - - return NATS_UPDATE_ERR_STACK(s); -} - -int -js_lenWithoutTrailingDot(const char *str) -{ - int l = (int) strlen(str); - - if (str[l-1] == '.') - l--; - return l; -} - -natsStatus -js_setOpts(natsConnection **nc, bool *freePfx, jsCtx *js, jsOptions *opts, jsOptions *resOpts) -{ - natsStatus s = NATS_OK; - - *freePfx = false; - jsOptions_Init(resOpts); - - if ((opts != NULL) && !nats_IsStringEmpty(opts->Domain)) - { - char *pfx = NULL; - if (nats_asprintf(&pfx, "$JS.%.*s.API", - js_lenWithoutTrailingDot(opts->Domain), opts->Domain) < 0) - { - s = nats_setDefaultError(NATS_NO_MEMORY); - } - else - { - resOpts->Prefix = pfx; - *freePfx = true; - } - } - if (s == NATS_OK) - { - struct jsOptionsStreamPurge *po = &(js->opts.Stream.Purge); - - js_lock(js); - // If not set above... - if (resOpts->Prefix == NULL) - resOpts->Prefix = (opts == NULL || nats_IsStringEmpty(opts->Prefix)) ? js->opts.Prefix : opts->Prefix; - - // Take provided one or default to context's. - resOpts->Wait = (opts == NULL || opts->Wait <= 0) ? js->opts.Wait : opts->Wait; - - // Purge options - if (opts != NULL) - { - struct jsOptionsStreamPurge *opo = &(opts->Stream.Purge); - - // If any field is set, use `opts`, otherwise, we will use the - // context's purge options. - if ((opo->Subject != NULL) || (opo->Sequence > 0) || (opo->Keep > 0)) - po = opo; - } - memcpy(&(resOpts->Stream.Purge), po, sizeof(*po)); - - // Stream info options - resOpts->Stream.Info.DeletedDetails = (opts == NULL ? js->opts.Stream.Info.DeletedDetails : opts->Stream.Info.DeletedDetails); - resOpts->Stream.Info.SubjectsFilter = (opts == NULL ? js->opts.Stream.Info.SubjectsFilter : opts->Stream.Info.SubjectsFilter); - - *nc = js->nc; - js_unlock(js); - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -jsPubOptions_Init(jsPubOptions *opts) -{ - if (opts == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - memset(opts, 0, sizeof(jsPubOptions)); - return NATS_OK; -} - -natsStatus -js_Publish(jsPubAck **new_puback, jsCtx *js, const char *subj, const void *data, int dataLen, - jsPubOptions *opts, jsErrCode *errCode) -{ - natsStatus s; - natsMsg msg; - - natsMsg_init(&msg, subj, (const char*) data, dataLen); - s = js_PublishMsg(new_puback, js, &msg, opts, errCode); - natsMsg_freeHeaders(&msg); - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_setHeadersFromOptions(natsMsg *msg, jsPubOptions *opts) -{ - natsStatus s = NATS_OK; - char temp[64] = {'\0'}; - - if (!nats_IsStringEmpty(opts->MsgId)) - s = natsMsgHeader_Set(msg, jsMsgIdHdr, opts->MsgId); - - if ((s == NATS_OK) && !nats_IsStringEmpty(opts->ExpectLastMsgId)) - s = natsMsgHeader_Set(msg, jsExpectedLastMsgIdHdr, opts->ExpectLastMsgId); - - if ((s == NATS_OK) && !nats_IsStringEmpty(opts->ExpectStream)) - s = natsMsgHeader_Set(msg, jsExpectedStreamHdr, opts->ExpectStream); - - if ((s == NATS_OK) && (opts->ExpectLastSeq > 0)) - { - snprintf(temp, sizeof(temp), "%" PRIu64, opts->ExpectLastSeq); - s = natsMsgHeader_Set(msg, jsExpectedLastSeqHdr, temp); - } - if (s == NATS_OK) - { - if (opts->ExpectNoMessage) - { - s = natsMsgHeader_Set(msg, jsExpectedLastSubjSeqHdr, "0"); - } - else if (opts->ExpectLastSubjectSeq > 0) - { - snprintf(temp, sizeof(temp), "%" PRIu64, opts->ExpectLastSubjectSeq); - s = natsMsgHeader_Set(msg, jsExpectedLastSubjSeqHdr, temp); - } - } - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_checkMaxWaitOpt(int64_t *new_ttl, jsPubOptions *opts) -{ - int64_t ttl; - - if ((ttl = opts->MaxWait) < 0) - return nats_setError(NATS_INVALID_ARG, "option 'MaxWait' (%" PRId64 ") cannot be negative", ttl); - - *new_ttl = ttl; - return NATS_OK; -} - -natsStatus -js_PublishMsg(jsPubAck **new_puback,jsCtx *js, natsMsg *msg, - jsPubOptions *opts, jsErrCode *errCode) -{ - natsStatus s = NATS_OK; - int64_t ttl = 0; - nats_JSON *json = NULL; - natsMsg *resp = NULL; - jsApiResponse ar; - - if (errCode != NULL) - *errCode = 0; - - if ((js == NULL) || (msg == NULL) || nats_IsStringEmpty(msg->subject)) - return nats_setDefaultError(NATS_INVALID_ARG); - - if (opts != NULL) - { - s = _checkMaxWaitOpt(&ttl, opts); - IFOK(s, _setHeadersFromOptions(msg, opts)); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - } - - // As it would be for a NATS connection, if the context has been destroyed, - // the memory is invalid and accessing any field of the context could cause - // a SEGFAULT. But assuming the context is still valid, we can access its - // options and the NATS connection without locking since they are immutable - // and the NATS connection has been retained when getting the JS context. - - // If not set through options, default to the context's Wait value. - if (ttl == 0) - ttl = js->opts.Wait; - - IFOK_JSR(s, natsConnection_RequestMsg(&resp, js->nc, msg, ttl)); - if (s == NATS_OK) - s = js_unmarshalResponse(&ar, &json, resp); - if (s == NATS_OK) - { - if (js_apiResponseIsErr(&ar)) - { - if (errCode != NULL) - *errCode = (int) ar.Error.ErrCode; - s = nats_setError(NATS_ERR, "%s", ar.Error.Description); - } - else if (new_puback != NULL) - { - // The user wants the jsPubAck object back, so we need to unmarshal it. - jsPubAck *pa = NULL; - - pa = (jsPubAck*) NATS_CALLOC(1, sizeof(jsPubAck)); - if (pa == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - { - s = nats_JSONGetStr(json, "stream", &(pa->Stream)); - IFOK(s, nats_JSONGetULong(json, "seq", &(pa->Sequence))); - IFOK(s, nats_JSONGetBool(json, "duplicate", &(pa->Duplicate))); - IFOK(s, nats_JSONGetStr(json, "domain", &(pa->Domain))); - - if (s == NATS_OK) - *new_puback = pa; - else - jsPubAck_Destroy(pa); - } - } - js_freeApiRespContent(&ar); - nats_JSONDestroy(json); - } - natsMsg_Destroy(resp); - return NATS_UPDATE_ERR_STACK(s); -} - -void -jsPubAck_Destroy(jsPubAck *pa) -{ - if (pa == NULL) - return; - - NATS_FREE(pa->Stream); - NATS_FREE(pa->Domain); - NATS_FREE(pa); -} - -static void -_freePubAck(jsPubAck *pa) -{ - if (pa == NULL) - return; - - NATS_FREE(pa->Stream); - NATS_FREE(pa->Domain); -} - -static natsStatus -_parsePubAck(natsMsg *msg, jsPubAck *pa, jsPubAckErr *pae, char *errTxt, size_t errTxtSize) -{ - natsStatus s = NATS_OK; - jsErrCode jerr = 0; - - if (natsMsg_isTimeout(msg)) - { - s = NATS_TIMEOUT; - } - else if (natsMsg_IsNoResponders(msg)) - { - s = NATS_NO_RESPONDERS; - } - else - { - nats_JSON *json = NULL; - jsApiResponse ar; - - // Now unmarshal the API response and check if there was an error. - s = js_unmarshalResponse(&ar, &json, msg); - if (s == NATS_OK) - { - if (js_apiResponseIsErr(&ar)) - { - s = NATS_ERR; - jerr = (jsErrCode) ar.Error.ErrCode; - snprintf(errTxt, errTxtSize, "%s", ar.Error.Description); - } - // If it is not an error and caller wants to decode the jsPubAck... - else if (pa != NULL) - { - memset(pa, 0, sizeof(jsPubAck)); - s = nats_JSONGetStr(json, "stream", &(pa->Stream)); - IFOK(s, nats_JSONGetULong(json, "seq", &(pa->Sequence))); - IFOK(s, nats_JSONGetBool(json, "duplicate", &(pa->Duplicate))); - IFOK(s, nats_JSONGetStr(json, "domain", &(pa->Domain))); - } - - js_freeApiRespContent(&ar); - nats_JSONDestroy(json); - } - } - // This will handle the no responder or timeout cases, or if we had errors - // trying to unmarshal the response. - if (s != NATS_OK) - { - // Set the error text only if not already done. - if (errTxt[0] == '\0') - snprintf(errTxt, errTxtSize, "%s", natsStatus_GetText(s)); - - memset(pae, 0, sizeof(jsPubAckErr)); - pae->Err = s; - pae->ErrCode = jerr; - pae->ErrText = errTxt; - } - return s; -} - -static void -_handleAsyncReply(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - const char *subject = natsMsg_GetSubject(msg); - char *id = NULL; - jsCtx *js = (jsCtx*) closure; - natsMsg *pmsg = NULL; - char errTxt[256] = {'\0'}; - jsPubAckErr pae; - jsPubAck pa; - struct jsOptionsPublishAsync *opa = NULL; - - if ((subject == NULL) || (int) strlen(subject) <= js->rpreLen) - { - natsMsg_Destroy(msg); - return; - } - - id = (char*) (subject+js->rpreLen); - - js_lock(js); - - pmsg = natsStrHash_Remove(js->pm, id); - if (pmsg == NULL) - { - js_unlock(js); - natsMsg_Destroy(msg); - return; - } - - opa = &(js->opts.PublishAsync); - if (opa->AckHandler) - { - jsPubAckErr *ppae = NULL; - jsPubAck *ppa = NULL; - - // If _parsePubAck returns an error, we will set the pointer ppae to - // our stack variable 'pae', otherwise, set the pointer ppa to the - // stack variable 'pa', which is the jsPubAck (positive ack). - if (_parsePubAck(msg, &pa, &pae, errTxt, sizeof(errTxt)) != NATS_OK) - ppae = &pae; - else - ppa = &pa; - - // Invoke the handler with pointer to either jsPubAck or jsPubAckErr. - js_unlock(js); - - (opa->AckHandler)(js, pmsg, ppa, ppae, opa->AckHandlerClosure); - - js_lock(js); - - _freePubAck(ppa); - // Set pmsg to NULL because user was responsible for destroying the message. - pmsg = NULL; - } - else if ((opa->ErrHandler != NULL) && (_parsePubAck(msg, NULL, &pae, errTxt, sizeof(errTxt)) != NATS_OK)) - { - // We will invoke CB only if there is any kind of error. - // Associate the message with the pubAckErr object. - pae.Msg = pmsg; - js_unlock(js); - - (opa->ErrHandler)(js, &pae, opa->ErrHandlerClosure); - - js_lock(js); - - // If the user resent the message, pae->Msg will have been cleared. - // In this case, do not destroy the message. Do not blindly destroy - // an address that could have been set, so destroy only if pmsg - // is same value than pae->Msg. - if (pae.Msg != pmsg) - pmsg = NULL; - } - - // Now that the callback has returned, decrement the number of pending messages. - js->pmcount--; - - // If there are callers waiting for async pub completion, or stalled async - // publish calls and we are now below max pending, broadcast to unblock them. - if (((js->pacw > 0) && (js->pmcount == 0)) - || ((js->stalled > 0) && (js->pmcount <= opa->MaxPending))) - { - natsCondition_Broadcast(js->cond); - } - js_unlock(js); - - natsMsg_Destroy(pmsg); - natsMsg_Destroy(msg); -} - -static void -_subComplete(void *closure) -{ - js_release((jsCtx*) closure); -} - -static natsStatus -_newAsyncReply(char *reply, jsCtx *js) -{ - natsStatus s = NATS_OK; - - // Create the internal objects if it is the first time that we are doing - // an async publish. - if (js->rsub == NULL) - { - s = natsCondition_Create(&(js->cond)); - IFOK(s, natsStrHash_Create(&(js->pm), 64)); - if (s == NATS_OK) - { - js->rpre = NATS_MALLOC(js->rpreLen+1); - if (js->rpre == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - { - char nuid[NUID_BUFFER_LEN+1]; - - s = natsNUID_Next(nuid, sizeof(nuid)); - if (s == NATS_OK) - { - memcpy(js->rpre, js->nc->inboxPfx, js->nc->inboxPfxLen); - memcpy(js->rpre+js->nc->inboxPfxLen, nuid+((int)strlen(nuid)-jsReplyTokenSize), jsReplyTokenSize); - js->rpre[js->rpreLen-1] = '.'; - js->rpre[js->rpreLen] = '\0'; - } - } - } - if (s == NATS_OK) - { - char *subj = NULL; - - if (nats_asprintf(&subj, "%s*", js->rpre) < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - s = natsConn_subscribeNoPool(&(js->rsub), js->nc, subj, _handleAsyncReply, (void*) js); - if (s == NATS_OK) - { - _retain(js); - natsSubscription_SetPendingLimits(js->rsub, -1, -1); - natsSubscription_SetOnCompleteCB(js->rsub, _subComplete, (void*) js); - } - NATS_FREE(subj); - } - if (s != NATS_OK) - { - // Undo the things we created so we retry again next time. - // It is either that or we have to always check individual - // objects to know if we have to create them. - NATS_FREE(js->rpre); - js->rpre = NULL; - natsStrHash_Destroy(js->pm); - js->pm = NULL; - natsCondition_Destroy(js->cond); - js->cond = NULL; - } - } - if (s == NATS_OK) - { - int64_t l; - int i; - - memcpy(reply, js->rpre, js->rpreLen); - l = nats_Rand64(); - for (i=0; i < jsReplyTokenSize; i++) - { - reply[js->rpreLen+i] = jsDigits[l%jsBase]; - l /= jsBase; - } - reply[js->rpreLen+jsReplyTokenSize] = '\0'; - } - - return NATS_UPDATE_ERR_STACK(s); -} - -static void -_timeoutPubAsync(natsTimer *t, void *closure) -{ - jsCtx *js = (jsCtx*) closure; - pmInfo *pm = NULL; - int64_t now = nats_Now(); - int64_t next= 0; - - js_lock(js); - if (js->closed) - { - js_unlock(js); - return; - } - - while (((pm = js->pmHead) != NULL) && (pm->deadline <= now)) - { - natsMsg *m = NULL; - - if (natsMsg_Create(&m, pm->subject, NULL, NULL, 0) != NATS_OK) - break; - - m->sub = js->rsub; - natsMsg_setTimeout(m); - - natsSub_Lock(js->rsub); - if (js->rsub->msgList.tail != NULL) - { - js->rsub->msgList.tail->next = m; - } - else - { - js->rsub->msgList.head = m; - natsCondition_Signal(js->rsub->cond); - } - js->rsub->msgList.tail = m; - js->rsub->msgList.msgs++; - js->rsub->msgList.bytes += natsMsg_dataAndHdrLen(m); - natsSub_Unlock(js->rsub); - - js->pmHead = pm->next; - _destroyPMInfo(pm); - } - - if (js->pmHead == NULL) - { - if (js->pmTail != NULL) - js->pmTail = NULL; - - next = 60*60*1000; - } - else - { - next = js->pmHead->deadline - now; - if (next <= 0) - next = 1; - } - natsTimer_Reset(js->pmtmr, next); - - js_unlock(js); -} - -static void -_timeoutPubAsyncComplete(natsTimer *t, void *closure) -{ - jsCtx *js = (jsCtx*) closure; - js_release(js); -} - -static natsStatus -_trackPublishAsyncTimeout(jsCtx *js, char *subject, int64_t mw) -{ - natsStatus s = NATS_OK; - pmInfo *pm = NULL; - pmInfo *pmi = NATS_CALLOC(1, sizeof(pmInfo)); - - if (pmi == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - pmi->subject = NATS_STRDUP(subject); - if (pmi->subject == NULL) - { - NATS_FREE(pmi); - return nats_setDefaultError(NATS_NO_MEMORY); - } - pmi->deadline = nats_Now() + mw; - - // Check if we can add at the end of the list - if (((pm = js->pmTail) != NULL) && (pmi->deadline >= pm->deadline)) - { - js->pmTail->next = pmi; - js->pmTail = pmi; - } - // Or before the first - else if (((pm = js->pmHead) != NULL) && (pmi->deadline < pm->deadline)) - { - pmi->next = js->pmHead; - js->pmHead = pmi; - natsTimer_Reset(js->pmtmr, mw); - } - // If the list was empty - else if (js->pmHead == NULL) - { - js->pmHead = pmi; - js->pmTail = pmi; - if (js->pmtmr == NULL) - { - s = natsTimer_Create(&js->pmtmr, _timeoutPubAsync, _timeoutPubAsyncComplete, mw, (void*) js); - if (s == NATS_OK) - js_retain(js); - } - else - natsTimer_Reset(js->pmtmr, mw); - } - else - { - // Guaranteed to be somewhere in the list (not first, not last, and at - // least 2 elements). - pm = js->pmHead; - while ((pm != NULL) && (pm->next != NULL) && (pmi->deadline > pm->next->deadline)) - pm = pm->next; - - pmi->next = pm->next; - pm->next = pmi; - } - if (s != NATS_OK) - _destroyPMInfo(pmi); - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_registerPubMsg(natsConnection **nc, char *reply, jsCtx *js, natsMsg *msg, int64_t mw) -{ - natsStatus s = NATS_OK; - char *id = NULL; - bool release = false; - int64_t maxp = 0; - - js_lock(js); - - maxp = js->opts.PublishAsync.MaxPending; - - js->pmcount++; - s = _newAsyncReply(reply, js); - if (s == NATS_OK) - id = reply+js->rpreLen; - if ((s == NATS_OK) - && (maxp > 0) - && (js->pmcount > maxp)) - { - int64_t target = nats_setTargetTime(js->opts.PublishAsync.StallWait); - - _retain(js); - - js->stalled++; - while ((s != NATS_TIMEOUT) && (js->pmcount > maxp)) - s = natsCondition_AbsoluteTimedWait(js->cond, js->mu, target); - js->stalled--; - - if (s == NATS_TIMEOUT) - s = nats_setError(s, "%s", "stalled with too many outstanding async published messages"); - - release = true; - } - if ((s == NATS_OK) && (mw > 0)) - s = _trackPublishAsyncTimeout(js, reply, mw); - if (s == NATS_OK) - s = natsStrHash_Set(js->pm, id, true, msg, NULL); - if (s == NATS_OK) - *nc = js->nc; - else - js->pmcount--; - if (release) - js_unlockAndRelease(js); - else - js_unlock(js); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -js_PublishAsync(jsCtx *js, const char *subj, const void *data, int dataLen, - jsPubOptions *opts) -{ - natsStatus s; - natsMsg *msg = NULL; - - s = natsMsg_Create(&msg, subj, NULL, (const char*) data, dataLen); - IFOK(s, js_PublishMsgAsync(js, &msg, opts)); - - // The `msg` pointer will have been set to NULL if the library took ownership. - natsMsg_Destroy(msg); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -js_PublishMsgAsync(jsCtx *js, natsMsg **msg, jsPubOptions *opts) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - char replyBuf[32 + jsReplyTokenSize + 1]; - char *reply = replyBuf; - int64_t mw = 0; - - if ((js == NULL) || (msg == NULL) || (*msg == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - if (js->rpreLen > 32) - { - reply = NATS_MALLOC(js->rpreLen + jsReplyTokenSize + 1); - if (reply == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - } - - if (opts != NULL) - { - mw = opts->MaxWait; - s = _setHeadersFromOptions(*msg, opts); - } - - // On success, the context will be retained. - IFOK(s, _registerPubMsg(&nc, reply, js, *msg, mw)); - if (s == NATS_OK) - { - s = natsConn_publish(nc, *msg, (const char*) reply, false); - if (s != NATS_OK) - { - char *id = reply+js->rpreLen; - - // The message may or may not have been sent, we don't know for sure. - // We are going to attempt to remove from the map. If we can, then - // we return the failure and the user owns the message. If we can't - // it means that its ack has already been processed, so we consider - // this call a success. If there was a pub ack failure, it is handled - // with the error callback, but regardless, the library owns the message. - js_lock(js); - // If msg no longer in map, Remove() will return NULL. - if (natsStrHash_Remove(js->pm, id) == NULL) - s = NATS_OK; - else - js->pmcount--; - js_unlock(js); - } - } - - // On success, clear the pointer to the message to indicate that the library - // now owns it. If user calls natsMsg_Destroy(), it will have no effect since - // they would call with natsMsg_Destroy(NULL), which is a no-op. - if (s == NATS_OK) - *msg = NULL; - - if (reply != replyBuf) - NATS_FREE(reply); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -js_PublishAsyncComplete(jsCtx *js, jsPubOptions *opts) -{ - natsStatus s = NATS_OK; - int64_t ttl = 0; - int64_t target = 0; - - if (js == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - if (opts != NULL) - { - s = _checkMaxWaitOpt(&ttl, opts); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - } - - js_lock(js); - if ((js->pm == NULL) || (js->pmcount == 0)) - { - js_unlock(js); - return NATS_OK; - } - if (ttl > 0) - target = nats_setTargetTime(ttl); - - _retain(js); - js->pacw++; - while ((s != NATS_TIMEOUT) && (js->pmcount > 0)) - { - if (target > 0) - s = natsCondition_AbsoluteTimedWait(js->cond, js->mu, target); - else - natsCondition_Wait(js->cond, js->mu); - } - js->pacw--; - - // Make sure that if we return timeout, there is really - // still unack'ed publish messages. - if ((s == NATS_TIMEOUT) && (js->pmcount == 0)) - s = NATS_OK; - - js_unlockAndRelease(js); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -js_PublishAsyncGetPendingList(natsMsgList *pending, jsCtx *js) -{ - natsStatus s = NATS_OK; - int count = 0; - - if ((pending == NULL) || (js == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - js_lock(js); - if ((count = natsStrHash_Count(js->pm)) == 0) - { - js_unlock(js); - return NATS_NOT_FOUND; - } - pending->Msgs = (natsMsg**) NATS_CALLOC(count, sizeof(natsMsg*)); - if (pending->Msgs == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - { - natsStrHashIter iter; - void *val = NULL; - int i = 0; - - natsStrHashIter_Init(&iter, js->pm); - while (natsStrHashIter_Next(&iter, NULL, &val)) - { - pending->Msgs[i++] = (natsMsg*) val; - natsStrHashIter_RemoveCurrent(&iter); - if (js->pmcount > 0) - js->pmcount--; - } - pending->Count = count; - } - js_unlock(js); - - if (s != NATS_OK) - natsMsgList_Destroy(pending); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -jsSubOptions_Init(jsSubOptions *opts) -{ - if (opts == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - memset(opts, 0, sizeof(jsSubOptions)); - opts->Config.AckPolicy = -1; - opts->Config.DeliverPolicy = -1; - opts->Config.ReplayPolicy = -1; - return NATS_OK; -} - -static natsStatus -_lookupStreamBySubject(const char **stream, natsConnection *nc, const char *subject, jsOptions *jo, jsErrCode *errCode) -{ - natsStatus s = NATS_OK; - natsBuffer *buf = NULL; - char *apiSubj= NULL; - natsMsg *resp = NULL; - - *stream = NULL; - - // Request will be: {"subject":""} - s = natsBuf_Create(&buf, 14 + (int) strlen(subject)); - IFOK(s, natsBuf_Append(buf, "{\"subject\":\"", -1)); - IFOK(s, natsBuf_Append(buf, subject, -1)); - IFOK(s, natsBuf_Append(buf, "\"}", -1)); - if (s == NATS_OK) - { - if (nats_asprintf(&apiSubj, jsApiStreams, js_lenWithoutTrailingDot(jo->Prefix), jo->Prefix) < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - // Send the request - IFOK_JSR(s, natsConnection_Request(&resp, nc, apiSubj, natsBuf_Data(buf), natsBuf_Len(buf), jo->Wait)); - // If no error, decode response - if ((s == NATS_OK) && (resp != NULL) && (natsMsg_GetDataLength(resp) > 0)) - { - nats_JSON *json = NULL; - char **streams = NULL; - int count = 0; - int i; - - s = nats_JSONParse(&json, natsMsg_GetData(resp), natsMsg_GetDataLength(resp)); - IFOK(s, nats_JSONGetArrayStr(json, "streams", &streams, &count)); - - if ((s == NATS_OK) && (count > 0)) - *stream = streams[0]; - else - s = nats_setError(NATS_ERR, "%s", jsErrNoStreamMatchesSubject); - - // Do not free the first one since we want to return it. - for (i=1; ijs; - natsTimer_Destroy(jsi->hbTimer); - if (jsi->mhMsg != NULL) - { - natsMsg_clearNoDestroy(jsi->mhMsg); - natsMsg_Destroy(jsi->mhMsg); - } - NATS_FREE(jsi->stream); - NATS_FREE(jsi->consumer); - NATS_FREE(jsi->nxtMsgSubj); - NATS_FREE(jsi->cmeta); - NATS_FREE(jsi->fcReply); - NATS_FREE(jsi->psubj); - js_destroyConsumerConfig(jsi->ocCfg); - NATS_FREE(jsi); - - js_release(js); -} - -static void -_autoAckCB(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - jsSub *jsi = (jsSub*) closure; - - natsMsg_setNoDestroy(msg); - - // Invoke user callback - (jsi->usrCb)(nc, sub, msg, jsi->usrCbClosure); - - natsMsg_Ack(msg, NULL); - natsMsg_clearNoDestroy(msg); - natsMsg_Destroy(msg); -} - -natsStatus -jsSub_deleteConsumer(natsSubscription *sub) -{ - jsCtx *js = NULL; - const char *stream = NULL; - const char *consumer = NULL; - natsStatus s; - - natsSub_Lock(sub); - if ((sub->jsi != NULL) && (sub->jsi->dc)) - { - js = sub->jsi->js; - stream = sub->jsi->stream; - consumer = sub->jsi->consumer; - // For drain, we could be trying to delete from - // the thread that checks for drain, or from the - // user checking from drain completion. So we - // switch off if we are going to delete now. - sub->jsi->dc = false; - } - natsSub_Unlock(sub); - - if ((js == NULL) || (stream == NULL) || (consumer == NULL)) - return NATS_OK; - - s = js_DeleteConsumer(js, stream, consumer, NULL, NULL); - if (s == NATS_NOT_FOUND) - s = nats_setError(s, "failed to delete consumer '%s': not found", consumer); - return NATS_UPDATE_ERR_STACK(s); -} - -// Runs under the subscription lock, but lock will be released, -// the connection lock will be possibly acquired/released, then -// the subscription lock reacquired. -void -jsSub_deleteConsumerAfterDrain(natsSubscription *sub) -{ - natsConnection *nc = NULL; - const char *consumer = NULL; - natsStatus s; - - if ((sub->jsi == NULL) || !sub->jsi->dc) - return; - - nc = sub->conn; - consumer = sub->jsi->consumer; - - // Need to release sub lock since deletion of consumer - // will require the connection lock, etc.. - natsSub_Unlock(sub); - - s = jsSub_deleteConsumer(sub); - if (s != NATS_OK) - { - char tmp[256]; - natsConn_Lock(nc); - snprintf(tmp, sizeof(tmp), "failed to delete consumer '%s': %u (%s)", - consumer, s, natsStatus_GetText(s)); - natsAsyncCb_PostErrHandler(nc, sub, s, NATS_STRDUP(tmp)); - natsConn_Unlock(nc); - } - - // Reacquire the lock before returning. - natsSub_Lock(sub); -} - -static natsStatus -_copyString(char **new_str, const char *str, int l) -{ - *new_str = NATS_MALLOC(l+1); - if (*new_str == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - memcpy(*new_str, str, l); - *(*new_str+l) = '\0'; - return NATS_OK; -} - -natsStatus -js_getMetaData(const char *reply, - char **domain, - char **stream, - char **consumer, - uint64_t *numDelivered, - uint64_t *sseq, - uint64_t *dseq, - int64_t *tm, - uint64_t *numPending, - int asked) -{ - natsStatus s = NATS_OK; - const char *p = reply; - const char *np = NULL; - const char *str = NULL; - int done = 0; - int64_t val = 0; - int nt = 0; - int i, l; - struct token { - const char* start; - int len; - }; - struct token tokens[9]; - - memset(tokens, 0, sizeof(tokens)); - - // v1 version of subject is total of 9 tokens: - // - // $JS.ACK....... - // - // Since we are here with the 2 first token stripped, the number of tokens is 7. - // - // v2 version of subject total tokens is 12: - // - // $JS.ACK.......... - // - // Again, since "$JS.ACK." has already been stripped, this is 10 tokens. - // However, the library does not care about anything after the num pending, - // so it would be 9 tokens. - - // Find tokens but stop when we have at most 9 tokens. - while ((nt < 9) && ((np = strchr(p, '.')) != NULL)) - { - tokens[nt].start = p; - tokens[nt].len = (int) (np - p); - nt++; - p = (const char*) (np+1); - } - if (np == NULL) - { - tokens[nt].start = p; - tokens[nt].len = (int) (strlen(p)); - nt++; - } - - // It is invalid if less than 7 or if it has more than 7, it has to have - // at least 9 to be valid. - if ((nt < 7) || ((nt > 7) && (nt < 9))) - return NATS_ERR; - - // If it is 7 tokens (the v1), then insert 2 empty tokens at the beginning. - if (nt == 7) - { - memmove(&(tokens[2]), &(tokens[0]), nt*sizeof(struct token)); - tokens[0].start = NULL; - tokens[0].len = 0; - tokens[1].start = NULL; - tokens[1].len = 0; - // We work with knowledge that we have now 9 tokens. - nt = 9; - } - - for (i=0; i 3) - { - val = nats_ParseInt64(str, l); - // Since we don't expect any negative value, - // if we get -1, which indicates a parsing error, - // return this fact. - if (val == -1) - return NATS_ERR; - } - switch (i) - { - case 0: - if (domain != NULL) - { - // A domain "_" will be sent by new server to indicate - // that there is no domain, but to make the number of tokens - // constant. - if ((str == NULL) || ((l == 1) && (str[0] == '_'))) - *domain = NULL; - else if ((s = _copyString(domain, str, l)) != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - done++; - } - break; - case 1: - // acc hash, ignore. - break; - case 2: - if (stream != NULL) - { - if ((s = _copyString(stream, str, l)) != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - done++; - } - break; - case 3: - if (consumer != NULL) - { - if ((s = _copyString(consumer, str, l)) != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - done++; - } - break; - case 4: - if (numDelivered != NULL) - { - *numDelivered = (uint64_t) val; - done++; - } - break; - case 5: - if (sseq != NULL) - { - *sseq = (uint64_t) val; - done++; - } - break; - case 6: - if (dseq != NULL) - { - *dseq = (uint64_t) val; - done++; - } - break; - case 7: - if (tm != NULL) - { - *tm = val; - done++; - } - break; - case 8: - if (numPending != NULL) - { - *numPending = (uint64_t) val; - done++; - } - break; - } - if (done == asked) - return NATS_OK; - } - return NATS_OK; -} - -natsStatus -jsSub_trackSequences(jsSub *jsi, const char *reply) -{ - natsStatus s = NATS_OK; - - // Data is equivalent to HB, so consider active. - jsi->active = true; - - if ((reply == NULL) || (strstr(reply, jsAckPrefix) != reply)) - return NATS_OK; - - // Keep track of inbound message "sequence" for flow control purposes. - jsi->fciseq++; - - NATS_FREE(jsi->cmeta); - DUP_STRING(s, jsi->cmeta, reply+jsAckPrefixLen); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -jsSub_processSequenceMismatch(natsSubscription *sub, natsMsg *msg, bool *sm) -{ - jsSub *jsi = sub->jsi; - const char *str = NULL; - int64_t val = 0; - struct mismatch *m = &jsi->mismatch; - natsStatus s; - - *sm = false; - - // This is an HB, so update that we are active. - jsi->active = true; - - if (jsi->cmeta == NULL) - return NATS_OK; - - s = js_getMetaData(jsi->cmeta, NULL, NULL, NULL, NULL, &m->sseq, &m->dseq, NULL, NULL, 2); - if (s != NATS_OK) - { - if (s == NATS_ERR) - return nats_setError(NATS_ERR, "invalid JS ACK: '%s'", jsi->cmeta); - return NATS_UPDATE_ERR_STACK(s); - } - - s = natsMsgHeader_Get(msg, jsLastConsumerSeqHdr, &str); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - if (str != NULL) - { - // Now that we have the field, we parse it. This function returns - // -1 if there is a parsing error. - val = nats_ParseInt64(str, (int) strlen(str)); - if (val == -1) - return nats_setError(NATS_ERR, "invalid last consumer sequence: '%s'", str); - - m->ldseq = (uint64_t) val; - } - if (m->ldseq == m->dseq) - { - // Sync subs use this flag to get the NextMsg() to error out and - // return NATS_MISMATCH to indicate that a mismatch was discovered, - // but immediately switch it off so that remaining NextMsg() work ok. - // Here we have resolved the mismatch, so we clear this flag (we - // could check for sync vs async, but no need to bother). - jsi->sm = false; - // Clear the suppression flag. - jsi->ssmn = false; - } - else - { - if (jsi->ordered) - { - s = jsSub_resetOrderedConsumer(sub, jsi->sseq+1); - } - else if (!jsi->ssmn) - { - // Record the sequence mismatch. - jsi->sm = true; - // Prevent following mismatch report until mismatch is resolved. - jsi->ssmn = true; - // Only for async subscriptions, indicate that the connection should - // push a NATS_MISMATCH to the async callback. - if (sub->msgCb != NULL) - *sm = true; - } - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsSubscription_GetConsumerInfo(jsConsumerInfo **ci, natsSubscription *sub, - jsOptions *opts, jsErrCode *errCode) -{ - char *consumer = NULL; - const char *stream = NULL; - jsCtx *js = NULL; - natsStatus s = NATS_OK; - - if ((ci == NULL) || (sub == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsSub_Lock(sub); - if ((sub->jsi == NULL) || (sub->jsi->consumer == NULL)) - { - natsSub_Unlock(sub); - return nats_setDefaultError(NATS_INVALID_SUBSCRIPTION); - } - js = sub->jsi->js; - stream = (const char*) sub->jsi->stream; - DUP_STRING(s, consumer, sub->jsi->consumer); - if (s == NATS_OK) - sub->refs++; - natsSub_Unlock(sub); - - if (s == NATS_OK) - { - s = js_GetConsumerInfo(ci, js, stream, consumer, opts, errCode); - NATS_FREE(consumer); - natsSub_release(sub); - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsSubscription_GetSequenceMismatch(jsConsumerSequenceMismatch *csm, natsSubscription *sub) -{ - jsSub *jsi; - struct mismatch *m = NULL; - - if ((csm == NULL) || (sub == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsSubAndLdw_Lock(sub); - if (sub->jsi == NULL) - { - natsSubAndLdw_Unlock(sub); - return nats_setError(NATS_INVALID_SUBSCRIPTION, "%s", jsErrNotAJetStreamSubscription); - } - jsi = sub->jsi; - m = &jsi->mismatch; - if (m->dseq == m->ldseq) - { - natsSubAndLdw_Unlock(sub); - return NATS_NOT_FOUND; - } - memset(csm, 0, sizeof(jsConsumerSequenceMismatch)); - csm->Stream = m->sseq; - csm->ConsumerClient = m->dseq; - csm->ConsumerServer = m->ldseq; - natsSubAndLdw_Unlock(sub); - return NATS_OK; -} - -char* -jsSub_checkForFlowControlResponse(natsSubscription *sub) -{ - jsSub *jsi = sub->jsi; - char *fcReply = NULL; - - jsi->active = true; - if (sub->delivered >= jsi->fcDelivered) - { - fcReply = jsi->fcReply; - jsi->fcReply = NULL; - jsi->fcDelivered = 0; - } - - return fcReply; -} - -natsStatus -jsSub_scheduleFlowControlResponse(jsSub *jsi, const char *reply) -{ - NATS_FREE(jsi->fcReply); - jsi->fcReply = NATS_STRDUP(reply); - if (jsi->fcReply == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - jsi->fcDelivered = jsi->fciseq; - - return NATS_OK; -} - -static natsStatus -_checkMsg(natsMsg *msg, bool checkSts, bool *usrMsg, natsMsg *mhMsg, const char* reqSubj) -{ - natsStatus s = NATS_OK; - const char *val = NULL; - const char *desc= NULL; - - // Check for missed heartbeat special message - if (msg == mhMsg) - { - *usrMsg = false; - return NATS_MISSED_HEARTBEAT; - } - - *usrMsg = true; - - if ((msg->dataLen > 0) || (msg->hdrLen <= 0)) - return NATS_OK; - - s = natsMsgHeader_Get(msg, STATUS_HDR, &val); - // If no status header, this is still considered a user message, so OK. - if (s == NATS_NOT_FOUND) - return NATS_OK; - // If serious error, return it. - else if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - // At this point, this is known to be a status message, not a user message. - *usrMsg = false; - - // If we don't care about status, we are done. - if (!checkSts) - return NATS_OK; - - // 100 Idle hearbeat, return OK - if (strncmp(val, CTRL_STATUS, HDR_STATUS_LEN) == 0) - return NATS_OK; - - // Before checking for "errors", if the incoming status message is - // for a previous request (message's subject is not reqSubj), then - // simply return NATS_OK. The caller will destroy the message and - // proceed as if nothing was received. - if (strcmp(natsMsg_GetSubject(msg), reqSubj) != 0) - return NATS_OK; - - // 404 indicating that there are no messages. - if (strncmp(val, NOT_FOUND_STATUS, HDR_STATUS_LEN) == 0) - return NATS_NOT_FOUND; - - // 408 indicating request timeout - if (strncmp(val, REQ_TIMEOUT, HDR_STATUS_LEN) == 0) - return NATS_TIMEOUT; - - // The possible 503 is handled directly in natsSub_nextMsg(), so we - // would never get it here in this function. - - natsMsgHeader_Get(msg, DESCRIPTION_HDR, &desc); - return nats_setError(NATS_ERR, "%s", (desc == NULL ? "error checking pull subscribe message" : desc)); -} - -static natsStatus -_sendPullRequest(natsConnection *nc, const char *subj, const char *rply, - natsBuffer *buf, jsFetchRequest *req) -{ - natsStatus s; - int64_t expires; - - // Make our request expiration a bit shorter than user provided expiration. - expires = (req->Expires >= (int64_t) 20E6 ? req->Expires - (int64_t) 10E6 : req->Expires); - - natsBuf_Reset(buf); - s = natsBuf_AppendByte(buf, '{'); - // Currently, Batch is required, so will always be > 0 - IFOK(s, nats_marshalLong(buf, false, "batch", (int64_t) req->Batch)); - if ((s == NATS_OK) && (req->MaxBytes > 0)) - s = nats_marshalLong(buf, true, "max_bytes", req->MaxBytes); - if ((s == NATS_OK) && (expires > 0)) - s = nats_marshalLong(buf, true, "expires", expires); - if ((s == NATS_OK) && (req->Heartbeat > 0)) - s = nats_marshalLong(buf, true, "idle_heartbeat", req->Heartbeat); - if ((s == NATS_OK) && req->NoWait) - s = natsBuf_Append(buf, ",\"no_wait\":true", -1); - IFOK(s, natsBuf_AppendByte(buf, '}')); - - // Sent the request to get more messages. - IFOK(s, natsConnection_PublishRequest(nc, subj, rply, - natsBuf_Data(buf), natsBuf_Len(buf))); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -_fetch(natsMsgList *list, natsSubscription *sub, jsFetchRequest *req, bool simpleFetch) -{ - natsStatus s = NATS_OK; - natsMsg **msgs = NULL; - int count = 0; - int batch = 0; - natsConnection *nc = NULL; - const char *subj = NULL; - const char *rply = NULL; - int pmc = 0; - char buffer[64]; - natsBuffer buf; - int64_t start = 0; - int64_t deadline = 0; - int64_t timeout = 0; - int size = 0; - bool sendReq = true; - jsSub *jsi = NULL; - natsMsg *mhMsg = NULL; - char *reqSubj = NULL; - bool noWait; - - if (list == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - memset(list, 0, sizeof(natsMsgList)); - - if ((sub == NULL) || (req == NULL) || (req->Batch <= 0) || (req->MaxBytes < 0)) - return nats_setDefaultError(NATS_INVALID_ARG); - - if (!req->NoWait && req->Expires <= 0) - return nats_setDefaultError(NATS_INVALID_TIMEOUT); - - natsSub_Lock(sub); - jsi = sub->jsi; - if ((jsi == NULL) || !jsi->pull) - { - natsSub_Unlock(sub); - return nats_setError(NATS_INVALID_SUBSCRIPTION, "%s", jsErrNotAPullSubscription); - } - if (jsi->inFetch) - { - natsSub_Unlock(sub); - return nats_setError(NATS_ERR, "%s", jsErrConcurrentFetchNotAllowed); - } - msgs = (natsMsg**) NATS_CALLOC(req->Batch, sizeof(natsMsg*)); - if (msgs == NULL) - { - natsSub_Unlock(sub); - return nats_setDefaultError(NATS_NO_MEMORY); - } - natsBuf_InitWithBackend(&buf, buffer, 0, sizeof(buffer)); - nc = sub->conn; - subj = jsi->nxtMsgSubj; - pmc = (sub->msgList.msgs > 0); - jsi->inFetch = true; - jsi->fetchID++; - if (nats_asprintf(&reqSubj, "%.*s%" PRIu64, (int) strlen(sub->subject)-1, sub->subject, jsi->fetchID) < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - rply = (const char*) reqSubj; - if ((s == NATS_OK) && req->Heartbeat) - { - int64_t hbi = req->Heartbeat / 1000000; - sub->refs++; - if (jsi->hbTimer == NULL) - { - s = natsMsg_create(&jsi->mhMsg, NULL, 0, NULL, 0, NULL, 0, -1); - if (s == NATS_OK) - { - natsMsg_setNoDestroy(jsi->mhMsg); - s = natsTimer_Create(&jsi->hbTimer, _hbTimerFired, _hbTimerStopped, hbi*2, (void*) sub); - } - if (s != NATS_OK) - sub->refs--; - } - else - natsTimer_Reset(jsi->hbTimer, hbi); - - mhMsg = jsi->mhMsg; - } - natsSub_Unlock(sub); - - if (req->Expires > 0) - { - start = nats_Now(); - timeout = req->Expires / (int64_t) 1E6; - deadline = start + timeout; - } - - // First, if there are already pending messages in the internal sub, - // then get as much messages as we can (but not more than the batch). - while (pmc && (s == NATS_OK) && (count < req->Batch) && ((req->MaxBytes == 0) || (size < req->MaxBytes))) - { - natsMsg *msg = NULL; - bool usrMsg= false; - - // This call will pull messages from the internal sync subscription - // but will not wait (and return NATS_TIMEOUT without updating - // the error stack) if there are no messages. - s = natsSub_nextMsg(&msg, sub, 0, true); - if (s == NATS_TIMEOUT) - { - s = NATS_OK; - break; - } - if (s == NATS_OK) - { - // Here we care only about user messages. We don't need to pass - // the request subject since it is not even checked in this case. - s = _checkMsg(msg, false, &usrMsg, mhMsg, NULL); - if ((s == NATS_OK) && usrMsg) - { - msgs[count++] = msg; - size += msg->wsz; - } - else - natsMsg_Destroy(msg); - } - } - if (s == NATS_OK) - { - // If we come from natsSubscription_Fetch() (simpleFetch is true), then - // we decide on the NoWait value. - if (simpleFetch) - noWait = (req->Batch - count > 1 ? true : false); - else - noWait = req->NoWait; - } - - batch = req->Batch; - // If we have OK and not all messages, we will send a fetch - // request to the server. - while ((s == NATS_OK) && (count != batch) && ((req->MaxBytes == 0) || (size < req->MaxBytes))) - { - natsMsg *msg = NULL; - bool usrMsg = false; - - if (req->Expires > 0) - { - timeout = deadline - nats_Now(); - if (timeout <= 0) - s = NATS_TIMEOUT; - } - - if ((s == NATS_OK) && sendReq) - { - sendReq = false; - req->Batch = req->Batch - count; - req->Expires = NATS_MILLIS_TO_NANOS(timeout); - req->NoWait = noWait; - s = _sendPullRequest(nc, subj, rply, &buf, req); - } - IFOK(s, natsSub_nextMsg(&msg, sub, timeout, true)); - if (s == NATS_OK) - { - s = _checkMsg(msg, true, &usrMsg, mhMsg, rply); - if ((s == NATS_OK) && usrMsg) - { - msgs[count++] = msg; - size += msg->wsz; - } - else - { - natsMsg_Destroy(msg); - // If we come from "simpleFetch" and we have a 404 for - // the noWait request and have not collected any message, - // then resend the request and ask to wait this time. - if (simpleFetch && noWait && (s == NATS_NOT_FOUND) && (count == 0)) - { - s = NATS_OK; - noWait = false; - sendReq = true; - } - } - } - } - - natsBuf_Cleanup(&buf); - - // If count > 0 it means that we have gathered some user messages, - // so we need to return them to the user with a NATS_OK status. - if (count > 0) - { - // If there was an error, we need to clear the error stack, - // since we return NATS_OK. - if (s != NATS_OK) - { - nats_clearLastError(); - s = NATS_OK; - } - - // Update the list with what we have collected. - list->Msgs = msgs; - list->Count = count; - } - - if (s != NATS_OK) - NATS_FREE(msgs); - - natsSub_Lock(sub); - jsi->inFetch = false; - if (req->Heartbeat && (jsi->hbTimer != NULL)) - natsTimer_Stop(jsi->hbTimer); - natsSub_Unlock(sub); - - NATS_FREE(reqSubj); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -jsFetchRequest_Init(jsFetchRequest *request) -{ - if (request == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - memset(request, 0, sizeof(jsFetchRequest)); - return NATS_OK; -} - -natsStatus -natsSubscription_Fetch(natsMsgList *list, natsSubscription *sub, int batch, int64_t timeout, - jsErrCode *errCode) -{ - natsStatus s; - jsFetchRequest req; - - if (errCode != NULL) - *errCode = 0; - - jsFetchRequest_Init(&req); - req.Batch = batch; - req.Expires = NATS_MILLIS_TO_NANOS(timeout); - s = _fetch(list, sub, &req, true); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsSubscription_FetchRequest(natsMsgList *list, natsSubscription *sub, jsFetchRequest *req) -{ - natsStatus s; - - s = _fetch(list, sub, req, false); - return NATS_UPDATE_ERR_STACK(s); -} - -static void -_hbTimerFired(natsTimer *timer, void* closure) -{ - natsSubscription *sub = (natsSubscription*) closure; - jsSub *jsi = sub->jsi; - bool alert= false; - natsConnection *nc = NULL; - bool oc = false; - natsStatus s = NATS_OK; - - natsSub_Lock(sub); - alert = !jsi->active; - oc = jsi->ordered; - jsi->active = false; - if (alert && jsi->pull) - { - // If there are messages pending then we can't really consider - // that we missed hearbeats. Wait for those to be processed and - // we will check missed HBs again. - if (sub->msgList.msgs == 0) - { - sub->msgList.msgs++; - sub->msgList.head = jsi->mhMsg; - sub->msgList.tail = jsi->mhMsg; - sub->msgList.bytes = natsMsg_dataAndHdrLen(jsi->mhMsg); - natsCondition_Signal(sub->cond); - natsTimer_Stop(timer); - } - natsSub_Unlock(sub); - return; - } - nc = sub->conn; - natsSub_Unlock(sub); - - if (!alert) - return; - - // For ordered consumers, we will need to reset - if (oc) - { - natsSub_Lock(sub); - if (!sub->closed) - { - // If we fail in that call, we will report to async err callback - // (if one is specified). - s = jsSub_resetOrderedConsumer(sub, sub->jsi->sseq+1); - } - natsSub_Unlock(sub); - } - - natsConn_Lock(nc); - // Even if we have called resetOrderedConsumer, we will post something - // to the async error callback, either "missed heartbeats", or the error - // that occurred trying to do the reset. - if (s == NATS_OK) - s = NATS_MISSED_HEARTBEAT; - natsAsyncCb_PostErrHandler(nc, sub, s, NULL); - natsConn_Unlock(nc); -} - -// This is invoked when the subscription is destroyed, since in NATS C -// client, timers will automatically fire again, so this callback is -// invoked when the timer has been stopped (and we are ready to destroy it). -static void -_hbTimerStopped(natsTimer *timer, void* closure) -{ - natsSubscription *sub = (natsSubscription*) closure; - - natsSub_release(sub); -} - -static bool -_stringPropertyDiffer(const char *user, const char *server) -{ - if (nats_IsStringEmpty(user)) - return false; - - if (nats_IsStringEmpty(server)) - return true; - - return (strcmp(user, server) != 0 ? true : false); -} - -#define CFG_CHECK_ERR_START "configuration requests %s to be " -#define CFG_CHECK_ERR_END ", but consumer's value is " - -static natsStatus -_checkConfig(jsConsumerConfig *s, jsConsumerConfig *u) -{ - if (_stringPropertyDiffer(u->Durable, s->Durable)) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "'%s'" CFG_CHECK_ERR_END "'%s'", "durable", u->Durable, s->Durable); - - if (_stringPropertyDiffer(u->Description, s->Description)) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "'%s'" CFG_CHECK_ERR_END "'%s'", "description", u->Description, s->Description); - - if ((int) u->DeliverPolicy >= 0 && u->DeliverPolicy != s->DeliverPolicy) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%d" CFG_CHECK_ERR_END "%d", "deliver policy", u->DeliverPolicy, s->DeliverPolicy); - - if (u->OptStartSeq > 0 && u->OptStartSeq != s->OptStartSeq) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%" PRIu64 CFG_CHECK_ERR_END "%" PRIu64, "optional start sequence", u->OptStartSeq, s->OptStartSeq); - - if (u->OptStartTime > 0 && u->OptStartTime != s->OptStartTime) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%" PRId64 CFG_CHECK_ERR_END "%" PRId64, "optional start time", u->OptStartTime, s->OptStartTime); - - if ((int) u->AckPolicy >= 0 && u->AckPolicy != s->AckPolicy) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%d" CFG_CHECK_ERR_END "%d", "ack policy", u->AckPolicy, s->AckPolicy); - - if (u->AckWait > 0 && u->AckWait != s->AckWait) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%" PRId64 CFG_CHECK_ERR_END "%" PRId64, "ack wait", u->AckWait, s->AckWait); - - if (u->MaxDeliver > 0 && u->MaxDeliver != s->MaxDeliver) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%" PRId64 CFG_CHECK_ERR_END "%" PRId64, "max deliver", u->MaxDeliver, s->MaxDeliver); - - if (u->BackOffLen > 0 && u->BackOffLen != s->BackOffLen) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%d" CFG_CHECK_ERR_END "%d", "backoff list length", u->BackOffLen, s->BackOffLen); - - if ((int) u->ReplayPolicy >= 0 && u->ReplayPolicy != s->ReplayPolicy) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%d" CFG_CHECK_ERR_END "%d", "replay policy", u->ReplayPolicy, s->ReplayPolicy); - - if (u->RateLimit > 0 && u->RateLimit != s->RateLimit) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%" PRIu64 CFG_CHECK_ERR_END "%" PRIu64, "rate limit", u->RateLimit, s->RateLimit); - - if (_stringPropertyDiffer(u->SampleFrequency, s->SampleFrequency)) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "'%s'" CFG_CHECK_ERR_END "'%s'", "sample frequency", u->SampleFrequency, s->SampleFrequency); - - if (u->MaxWaiting > 0 && u->MaxWaiting != s->MaxWaiting) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%" PRId64 CFG_CHECK_ERR_END "%" PRId64, "max waiting", u->MaxWaiting, s->MaxWaiting); - - if (u->MaxAckPending > 0 && u->MaxAckPending != s->MaxAckPending) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%" PRId64 CFG_CHECK_ERR_END "%" PRId64, "max ack pending", u->MaxAckPending, s->MaxAckPending); - - // For flow control, we want to fail if the user explicit wanted it, but - // it is not set in the existing consumer. If it is not asked by the user, - // the library still handles it and so no reason to fail. - if (u->FlowControl && !s->FlowControl) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "'%s'" CFG_CHECK_ERR_END "'%s'", "flow control", "true", "false"); - - if (u->Heartbeat > 0 && u->Heartbeat != s->Heartbeat) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%" PRId64 CFG_CHECK_ERR_END "%" PRId64, "heartbeat", u->Heartbeat, s->Heartbeat); - - if (u->HeadersOnly != s->HeadersOnly) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%d" CFG_CHECK_ERR_END "%d", "headers only", u->HeadersOnly, s->HeadersOnly); - - if (u->MaxRequestBatch > 0 && u->MaxRequestBatch != s->MaxRequestBatch) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%" PRId64 CFG_CHECK_ERR_END "%" PRId64, "max request batch", u->Heartbeat, s->Heartbeat); - - if (u->MaxRequestExpires > 0 && u->MaxRequestExpires != s->MaxRequestExpires) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%" PRId64 CFG_CHECK_ERR_END "%" PRId64, "max request expires", u->MaxRequestExpires, s->MaxRequestExpires); - - if (u->InactiveThreshold > 0 && u->InactiveThreshold != s->InactiveThreshold) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%" PRId64 CFG_CHECK_ERR_END "%" PRId64, "inactive threshold", u->InactiveThreshold, s->InactiveThreshold); - - if (u->Replicas > 0 && u->Replicas != s->Replicas) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%" PRId64 CFG_CHECK_ERR_END "%" PRId64, "replicas", u->Replicas, s->Replicas); - - if (u->MemoryStorage != s->MemoryStorage) - return nats_setError(NATS_ERR, CFG_CHECK_ERR_START "%d" CFG_CHECK_ERR_END "%d", "memory storage", u->MemoryStorage, s->MemoryStorage); - - return NATS_OK; -} - -static natsStatus -_processConsInfo(const char **dlvSubject, jsConsumerInfo *info, jsConsumerConfig *userCfg, - bool isPullMode, const char **subjects, int numSubjects, const char *queue) -{ - bool dlvSubjEmpty = false; - jsConsumerConfig *ccfg = info->Config; - const char *dg = NULL; - natsStatus s = NATS_OK; - const char *stackFilterSubject[] = {ccfg->FilterSubject}; - const char **filterSubjects = stackFilterSubject; - int filterSubjectsLen = 1; - int incoming, existing; - - *dlvSubject = NULL; - // Always represent the consumer's filter subjects as a list, to match - // uniformly against the incoming subject list. Consider lists of 1 empty - // subject empty lists. - if (ccfg->FilterSubjectsLen > 0) - { - filterSubjects = ccfg->FilterSubjects; - filterSubjectsLen = ccfg->FilterSubjectsLen; - } - if ((filterSubjectsLen == 1) && nats_IsStringEmpty(filterSubjects[0])) - { - filterSubjects = NULL; - filterSubjectsLen = 0; - } - if ((numSubjects == 1) && nats_IsStringEmpty(subjects[0])) - { - subjects = NULL; - numSubjects = 0; - } - - // Match the subjects against the consumer's filter subjects. - if (numSubjects > 0 && filterSubjectsLen > 0) - { - // If the consumer has filter subject(s), then the subject(s) must match. - bool matches = true; - - // TODO - This is N**2, but we don't expect a large number of subjects. - for (incoming = 0; incoming < numSubjects; incoming++) - { - bool found = false; - for (existing = 0; existing < filterSubjectsLen; existing++) - { - if (strcmp(subjects[incoming], filterSubjects[existing]) == 0) - { - found = true; - break; - } - } - if (!found) - { - matches = false; - break; - } - } - - if (!matches) - { - if (numSubjects == 1 && filterSubjectsLen == 1) - return nats_setError(NATS_ERR, "subject '%s' does not match consumer filter subject '%s'.", subjects[0], filterSubjects[0]); - else - return nats_setError(NATS_ERR, "%d subjects do not match any consumer filter subjects.", numSubjects); - } - } - // Check that if user wants to create a queue sub, - // the consumer has no HB nor FC. - queue = (nats_IsStringEmpty(queue) ? NULL : queue); - - if (queue != NULL) - { - if (ccfg->Heartbeat > 0) - return nats_setError(NATS_ERR, "%s", jsErrNoHeartbeatForQueueSub); - - if (ccfg->FlowControl) - return nats_setError(NATS_ERR, "%s", jsErrNoFlowControlForQueueSub); - } - - dlvSubjEmpty = nats_IsStringEmpty(ccfg->DeliverSubject); - - // Prevent binding a subscription against incompatible consumer types. - if (isPullMode && !dlvSubjEmpty) - { - return nats_setError(NATS_ERR, "%s", jsErrPullSubscribeToPushConsumer); - } - else if (!isPullMode && dlvSubjEmpty) - { - return nats_setError(NATS_ERR, "%s", jsErrPullSubscribeRequired); - } - - // If pull mode, nothing else to check here. - if (isPullMode) - { - s = _checkConfig(ccfg, userCfg); - return NATS_UPDATE_ERR_STACK(s); - } - - // At this point, we know the user wants push mode, and the JS consumer is - // really push mode. - dg = ccfg->DeliverGroup; - - if (nats_IsStringEmpty(dg)) - { - // Prevent an user from attempting to create a queue subscription on - // a JS consumer that was not created with a deliver group. - if (queue != NULL) - { - return nats_setError(NATS_ERR, "%s", - "cannot create a queue subscription for a consumer without a deliver group"); - } - else if (info->PushBound) - { - // Need to reject a non queue subscription to a non queue consumer - // if the consumer is already bound. - return nats_setError(NATS_ERR, "%s", "consumer is already bound to a subscription"); - } - } - else - { - // If the JS consumer has a deliver group, we need to fail a non queue - // subscription attempt: - if (queue == NULL) - { - return nats_setError(NATS_ERR, - "cannot create a subscription for a consumer with a deliver group %s", - dg); - } - else if (strcmp(queue, dg) != 0) - { - // Here the user's queue group name does not match the one associated - // with the JS consumer. - return nats_setError(NATS_ERR, - "cannot create a queue subscription '%s' for a consumer with a deliver group '%s'", - queue, dg); - } - } - s = _checkConfig(ccfg, userCfg); - if (s == NATS_OK) - *dlvSubject = ccfg->DeliverSubject; - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -js_checkConsName(const char *cons, bool isDurable) -{ - int i; - - if (nats_IsStringEmpty(cons)) - return nats_setError(NATS_INVALID_ARG, "%s", jsErrConsumerNameRequired); - - for (i=0; i<(int)strlen(cons); i++) - { - char c = cons[i]; - if ((c == '.') || (c == ' ') || (c == '*') || (c == '>')) - { - return nats_setError(NATS_INVALID_ARG, "%s '%s' (cannot contain '%c')", - (isDurable ? jsErrInvalidDurableName : jsErrInvalidConsumerName), cons, c); - } - } - return NATS_OK; -} - -static natsStatus -_subscribeMulti(natsSubscription **new_sub, jsCtx *js, const char **subjects, int numSubjects, const char *pullDurable, - natsMsgHandler usrCB, void *usrCBClosure, bool isPullMode, - jsOptions *jsOpts, jsSubOptions *opts, jsErrCode *errCode) -{ - natsStatus s = NATS_OK; - const char *stream = NULL; - const char *consumer = NULL; - const char *durable = NULL; - const char *deliver = NULL; - jsErrCode jerr = 0; - jsConsumerInfo *info = NULL; - bool lookupErr = false; - bool consBound = false; - bool isQueue = false; - natsConnection *nc = NULL; - bool freePfx = false; - bool freeStream = false; - jsSub *jsi = NULL; - int64_t hbi = 0; - bool create = false; - natsSubscription *sub = NULL; - natsMsgHandler cb = NULL; - void *cbClosure = NULL; - natsInbox *inbox = NULL; - int64_t maxap = 0; - jsOptions jo; - jsSubOptions o; - jsConsumerConfig cfgStack; - jsConsumerConfig *cfg = NULL; - jsConsumerConfig *ocCfg = NULL; - - if ((new_sub == NULL) || (js == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - s = js_setOpts(&nc, &freePfx, js, jsOpts, &jo); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - // If `opts` is not specified, point to a stack initialized one so - // we don't have to keep checking if `opts` is NULL or not. - if (opts == NULL) - { - jsSubOptions_Init(&o); - opts = &o; - } - if (opts->Config.InactiveThreshold < 0) - return nats_setError(NATS_INVALID_ARG, - "invalid InactiveThreshold value (%d), needs to be greater or equal to 0", - (int) opts->Config.InactiveThreshold); - - // If user configures optional start sequence or time, the deliver policy - // need to be updated accordingly. Server will return error if user tries to have both set. - if (opts->Config.OptStartSeq > 0) - opts->Config.DeliverPolicy = js_DeliverByStartSequence; - if (opts->Config.OptStartTime > 0) - opts->Config.DeliverPolicy = js_DeliverByStartTime; - - isQueue = !nats_IsStringEmpty(opts->Queue); - stream = opts->Stream; - durable = (pullDurable != NULL ? pullDurable : opts->Config.Durable); - consumer = opts->Consumer; - consBound= (!nats_IsStringEmpty(stream) && !nats_IsStringEmpty(consumer)); - - if (((numSubjects <= 0) || nats_IsStringEmpty(subjects[0])) && !consBound) - return nats_setDefaultError(NATS_INVALID_ARG); - - // Do some quick checks here for ordered consumers. - if (opts->Ordered) - { - // Check for pull mode. - if (isPullMode) - return nats_setError(NATS_INVALID_ARG, "%s", jsErrOrderedConsNoPullMode); - // Make sure we are not durable. - if (!nats_IsStringEmpty(durable)) - return nats_setError(NATS_INVALID_ARG, "%s", jsErrOrderedConsNoDurable); - // Check ack policy. - if ((int) opts->Config.AckPolicy != -1) - return nats_setError(NATS_INVALID_ARG, "%s", jsErrOrderedConsNoAckPolicy); - // Check max deliver. If set, it has to be 1. - if ((opts->Config.MaxDeliver > 0) && (opts->Config.MaxDeliver != 1)) - return nats_setError(NATS_INVALID_ARG, "%s", jsErrOrderedConsNoMaxDeliver); - // No deliver subject, we pick our own. - if (!nats_IsStringEmpty(opts->Config.DeliverSubject)) - return nats_setError(NATS_INVALID_ARG, "%s", jsErrOrderedConsNoDeliverSubject); - // Queue groups not allowed. - if (isQueue) - return nats_setError(NATS_INVALID_ARG, "%s", jsErrOrderedConsNoQueue); - // Check for bound consumers. - if (!nats_IsStringEmpty(consumer)) - return nats_setError(NATS_INVALID_ARG, "%s", jsErrOrderedConsNoBind); - } - else if (isQueue) - { - // Reject a user configuration that would want to define hearbeats with - // a queue subscription. - if (opts->Config.Heartbeat > 0) - return nats_setError(NATS_INVALID_ARG, "%s", jsErrNoHeartbeatForQueueSub); - // Same for flow control - if (opts->Config.FlowControl) - return nats_setError(NATS_INVALID_ARG, "%s", jsErrNoFlowControlForQueueSub); - // If no durable name was provided, use the queue name as the durable. - if (nats_IsStringEmpty(durable)) - durable = opts->Queue; - } - - // If a durable name is specified, check that it is valid - if (!nats_IsStringEmpty(durable)) - { - if ((s = js_checkConsName(durable, true)) != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - } - - // In case a consumer has not been set explicitly, then the durable name - // will be used as the consumer name (after that, `consumer` will still be - // possibly NULL). - if (nats_IsStringEmpty(consumer)) - consumer = durable; - - // Find the stream mapped to the subject if not bound to a stream already, - // that is, if user did not provide a `Stream` name through options). - if (nats_IsStringEmpty(stream) && numSubjects > 0) - { - // Use the first subject to find the stream. - s = _lookupStreamBySubject(&stream, nc, subjects[0], &jo, errCode); - if (s != NATS_OK) - goto END; - - freeStream = true; - } - - // If a consumer name is specified, try to lookup the consumer and - // if it exists, will attach to it. - if (!nats_IsStringEmpty(consumer)) - { - s = js_GetConsumerInfo(&info, js, stream, consumer, &jo, &jerr); - lookupErr = (s == NATS_TIMEOUT) || (jerr == JSNotEnabledErr); - } - -PROCESS_INFO: - if (info != NULL) - { - if (info->Config == NULL) - { - s = nats_setError(NATS_ERR, "%s", "no configuration in consumer info"); - goto END; - } - s = _processConsInfo(&deliver, info, &(opts->Config), isPullMode, subjects, numSubjects, opts->Queue); - if (s != NATS_OK) - goto END; - - // Capture the HB interval (convert in millsecond since Go duration is in nanos) - hbi = info->Config->Heartbeat / 1000000; - maxap = info->Config->MaxAckPending; - } - else if (((s != NATS_OK) && (s != NATS_NOT_FOUND)) || ((s == NATS_NOT_FOUND) && consBound)) - { - // If the consumer is being bound and got an error on pull subscribe then allow the error. - if (!(isPullMode && lookupErr && consBound)) - goto END; - - s = NATS_OK; - } - else - { - s = NATS_OK; - // Make a shallow copy of the provided consumer config - // since we may have to change some fields before calling - // AddConsumer. - cfg = &cfgStack; - memcpy(cfg, &(opts->Config), sizeof(jsConsumerConfig)); - - if (!isPullMode) - { - // Attempt to create consumer if not found nor binding. - natsConn_newInbox(nc, &inbox); - deliver = (const char*) inbox; - cfg->DeliverSubject = deliver; - } - - // Do filtering always, server will clear as needed. - if (numSubjects == 1) - { - cfg->FilterSubject = subjects[0]; - } - else - { - cfg->FilterSubjects = subjects; - cfg->FilterSubjectsLen = numSubjects; - } - - if (opts->Ordered) - { - const char *tmp = NULL; - - cfg->FlowControl = true; - cfg->AckPolicy = js_AckNone; - cfg->MaxDeliver = 1; - cfg->AckWait = NATS_SECONDS_TO_NANOS(24*60*60); // Just set to something known, not utilized. - if (opts->Config.Heartbeat <= 0) - cfg->Heartbeat = jsOrderedHBInterval; - cfg->MemoryStorage = true; - cfg->Replicas = 1; - - // Let's clone without the delivery subject because it will need - // to be set to something new when recreating it anyway. - tmp = cfg->DeliverSubject; - cfg->DeliverSubject = NULL; - s = js_cloneConsumerConfig(cfg, &ocCfg); - cfg->DeliverSubject = tmp; - } - else - { - // Set config durable with "durable" variable, which will - // possibly be NULL. - cfg->Durable = durable; - - // Set DeliverGroup to queue name, possibly NULL - cfg->DeliverGroup = opts->Queue; - } - - // Capture the HB interval (convert in millsecond since Go duration is in nanos) - hbi = cfg->Heartbeat / 1000000; - - create = true; - } - if (s == NATS_OK) - { - jsi = (jsSub*) NATS_CALLOC(1, sizeof(jsSub)); - if (jsi == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - { - if (isPullMode && !nats_IsStringEmpty(consumer)) - { - if (nats_asprintf(&(jsi->nxtMsgSubj), jsApiRequestNextT, jo.Prefix, stream, consumer) < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - IF_OK_DUP_STRING(s, jsi->stream, stream); - IFOK(s, nats_formatStringArray(&jsi->psubj, subjects, numSubjects)); - if (s == NATS_OK) - { - jsi->js = js; - jsi->hbi = hbi; - jsi->pull = isPullMode; - jsi->ordered= opts->Ordered; - jsi->ocCfg = ocCfg; - jsi->dseq = 1; - jsi->ackNone= (opts->Config.AckPolicy == js_AckNone || opts->Ordered); - js_retain(js); - - if ((usrCB != NULL) && !opts->ManualAck && !jsi->ackNone) - { - // Keep track of user provided CB and closure - jsi->usrCb = usrCB; - jsi->usrCbClosure = usrCBClosure; - // Use our own when creating the NATS subscription. - cb = _autoAckCB; - cbClosure = (void*) jsi; - } - else if (usrCB != NULL) - { - cb = usrCB; - cbClosure = usrCBClosure; - } - } - } - } - if (s == NATS_OK) - { - char *pullWCInbox = NULL; - - if (isPullMode) - { - // Create a wildcard inbox. - s = natsConn_newInbox(nc, &inbox); - if (s == NATS_OK) - { - if (nats_asprintf(&pullWCInbox, "%s.*", (const char*) inbox) < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - if (s == NATS_OK) - deliver = (const char*) pullWCInbox; - } - // Create the NATS subscription on given deliver subject. Note that - // cb/cbClosure will be NULL for sync or pull subscriptions. - IFOK(s, natsConn_subscribeImpl(&sub, nc, true, deliver, - opts->Queue, 0, cb, cbClosure, false, jsi)); - if ((s == NATS_OK) && (hbi > 0) && !isPullMode) - { - natsSub_Lock(sub); - sub->refs++; - s = natsTimer_Create(&jsi->hbTimer, _hbTimerFired, _hbTimerStopped, hbi*2, (void*) sub); - if (s != NATS_OK) - sub->refs--; - natsSub_Unlock(sub); - } - NATS_FREE(pullWCInbox); - } - if ((s == NATS_OK) && create) - { - // Multiple subscribers could compete in creating the first consumer - // that will be shared using the same durable name. If this happens, then - // do a lookup of the consumer info subscribe using the latest info. - s = js_AddConsumer(&info, js, stream, cfg, &jo, &jerr); - if (s != NATS_OK) - { - if ((jerr != JSConsumerExistingActiveErr) && (jerr != JSConsumerNameExistErr)) - goto END; - - jsConsumerInfo_Destroy(info); - info = NULL; - - s = js_GetConsumerInfo(&info, js, stream, consumer, &jo, &jerr); - if (s != NATS_OK) - goto END; - - // We will re-create the sub/jsi, so destroy here and go back to point where - // we process the consumer info response. - natsSubscription_Destroy(sub); - sub = NULL; - jsi = NULL; - create = false; - - goto PROCESS_INFO; - } - else - { - maxap = info->Config->MaxAckPending; - natsSub_Lock(sub); - jsi->dc = true; - jsi->pending = info->NumPending + info->Delivered.Consumer; - // There may be a race in the case of an ordered consumer where by this - // time, the consumer has been recreated (jsResetOrderedConsumer). So - // set only if jsi->consumer is NULL! - if (jsi->consumer == NULL) - { - DUP_STRING(s, jsi->consumer, info->Name); - if (s == NATS_OK) - { - NATS_FREE(jsi->nxtMsgSubj); - jsi->nxtMsgSubj = NULL; - if (nats_asprintf(&(jsi->nxtMsgSubj), jsApiRequestNextT, jo.Prefix, stream, jsi->consumer) < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - } - natsSub_Unlock(sub); - } - } - -END: - if (s == NATS_OK) - { - int64_t ml = 0; - int64_t bl = 0; - - natsSub_Lock(sub); - ml = (int64_t) sub->msgsLimit; - bl = (int64_t) sub->bytesLimit; - natsSub_Unlock(sub); - if (maxap > ml) - { - ml = maxap; - if (maxap*1024*1024 > bl) - bl = maxap*1024*1024; - - natsSubscription_SetPendingLimits(sub, (int) ml, (int) bl); - } - *new_sub = sub; - } - else - { - if (sub == NULL) - jsSub_free(jsi); - else - natsSubscription_Destroy(sub); - - if (errCode != NULL) - *errCode = jerr; - } - - // Common cleanup regardless of success or not. - jsConsumerInfo_Destroy(info); - if (freePfx) - NATS_FREE((char*) jo.Prefix); - if (freeStream) - NATS_FREE((char*) stream); - natsInbox_Destroy(inbox); - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_subscribe(natsSubscription **new_sub, jsCtx *js, const char *subject, const char *pullDurable, - natsMsgHandler usrCB, void *usrCBClosure, bool isPullMode, - jsOptions *jsOpts, jsSubOptions *opts, jsErrCode *errCode) -{ - natsStatus s = NATS_OK; - const char *singleSubject[] = {subject}; - int numSubjects = 1; - const char **subjects = singleSubject; - - if (nats_IsStringEmpty(subject)) - { - numSubjects = 0; - subjects = NULL; - } - - s = _subscribeMulti(new_sub, js, subjects, numSubjects, pullDurable, usrCB, usrCBClosure, isPullMode, jsOpts, opts, errCode); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -js_Subscribe(natsSubscription **sub, jsCtx *js, const char *subject, - natsMsgHandler cb, void *cbClosure, - jsOptions *jsOpts, jsSubOptions *opts, jsErrCode *errCode) -{ - natsStatus s; - - if (errCode != NULL) - *errCode = 0; - - if (cb == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - s = _subscribe(sub, js, subject, NULL, cb, cbClosure, false, jsOpts, opts, errCode); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -js_SubscribeMulti(natsSubscription **sub, jsCtx *js, const char **subjects, int numSubjects, - natsMsgHandler cb, void *cbClosure, - jsOptions *jsOpts, jsSubOptions *opts, jsErrCode *errCode) -{ - natsStatus s; - - if (errCode != NULL) - *errCode = 0; - - if (cb == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - s = _subscribeMulti(sub, js, subjects, numSubjects, NULL, cb, cbClosure, false, jsOpts, opts, errCode); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -js_SubscribeSync(natsSubscription **sub, jsCtx *js, const char *subject, - jsOptions *jsOpts, jsSubOptions *opts, jsErrCode *errCode) -{ - natsStatus s; - - if (errCode != NULL) - *errCode = 0; - - s = _subscribe(sub, js, subject, NULL, NULL, NULL, false, jsOpts, opts, errCode); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -js_SubscribeSyncMulti(natsSubscription **sub, jsCtx *js, const char **subjects, int numSubjects, - jsOptions *jsOpts, jsSubOptions *opts, jsErrCode *errCode) -{ - natsStatus s; - - if (errCode != NULL) - *errCode = 0; - - s = _subscribeMulti(sub, js, subjects, numSubjects, NULL, NULL, NULL, false, jsOpts, opts, errCode); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -js_PullSubscribe(natsSubscription **sub, jsCtx *js, const char *subject, const char *durable, - jsOptions *jsOpts, jsSubOptions *opts, jsErrCode *errCode) -{ - natsStatus s; - - if (errCode != NULL) - *errCode = 0; - - s = _subscribe(sub, js, subject, durable, NULL, NULL, true, jsOpts, opts, errCode); - return NATS_UPDATE_ERR_STACK(s); -} - -typedef struct __ackOpts -{ - const char *ackType; - bool inProgress; - bool sync; - int64_t nakDelay; - -} _ackOpts; - -static natsStatus -_ackMsg(natsMsg *msg, jsOptions *opts, _ackOpts *o, jsErrCode *errCode) -{ - natsSubscription *sub = NULL; - natsConnection *nc = NULL; - jsCtx *js = NULL; - jsSub *jsi = NULL; - natsStatus s = NATS_OK; - const char *body= o->ackType; - bool sync = o->sync; - int64_t wait = 0; - char tmp[64]; - - if (msg == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - if (natsMsg_isAcked(msg)) - return NATS_OK; - - if (msg->sub == NULL) - return nats_setError(NATS_ILLEGAL_STATE, "%s", jsErrMsgNotBound); - - if (nats_IsStringEmpty(msg->reply)) - return nats_setError(NATS_ILLEGAL_STATE, "%s", jsErrMsgNotJS); - - // All these are immutable and don't need locking. - sub = msg->sub; - jsi = sub->jsi; - js = jsi->js; - nc = sub->conn; - - // If option with Wait is specified, transform all Acks as sync operation. - if ((opts != NULL) && (opts->Wait > 0)) - { - wait = opts->Wait; - sync = true; - } - if (o->nakDelay > 0) - { - int64_t v = NATS_MILLIS_TO_NANOS(o->nakDelay); - snprintf(tmp, sizeof(tmp), "%s {\"delay\":%" PRId64 "}", o->ackType, v); - body = (const char*) tmp; - } - if (sync) - { - natsMsg *rply = NULL; - - if (wait == 0) - { - // When getting a context, if user did not specify a wait, - // we default to jsDefaultRequestWait, so this won't be 0. - js_lock(js); - wait = js->opts.Wait; - js_unlock(js); - } - IFOK_JSR(s, natsConnection_RequestString(&rply, nc, msg->reply, body, wait)); - natsMsg_Destroy(rply); - } - else - { - s = natsConnection_PublishString(nc, msg->reply, body); - } - // Indicate that we have ack'ed the message - if ((s == NATS_OK) && !o->inProgress) - natsMsg_setAcked(msg); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsMsg_Ack(natsMsg *msg, jsOptions *opts) -{ - natsStatus s; - _ackOpts o = {jsAckAck, false, false, 0}; - - s = _ackMsg(msg, opts, &o, NULL); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsMsg_AckSync(natsMsg *msg, jsOptions *opts, jsErrCode *errCode) -{ - natsStatus s; - _ackOpts o = {jsAckAck, false, true, 0}; - - s = _ackMsg(msg, opts, &o, errCode); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsMsg_Nak(natsMsg *msg, jsOptions *opts) -{ - natsStatus s; - _ackOpts o = {jsAckNak, false, false, 0}; - - s = _ackMsg(msg, opts, &o, NULL); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsMsg_NakWithDelay(natsMsg *msg, int64_t delay, jsOptions *opts) -{ - natsStatus s; - _ackOpts o = {jsAckNak, false, false, delay}; - - s = _ackMsg(msg, opts, &o, NULL); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsMsg_InProgress(natsMsg *msg, jsOptions *opts) -{ - natsStatus s; - _ackOpts o = {jsAckInProgress, true, false, 0}; - - s = _ackMsg(msg, opts, &o, NULL); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsMsg_Term(natsMsg *msg, jsOptions *opts) -{ - natsStatus s; - _ackOpts o = {jsAckTerm, false, false, 0}; - - s = _ackMsg(msg, opts, &o, NULL); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsMsg_GetMetaData(jsMsgMetaData **new_meta, natsMsg *msg) -{ - jsMsgMetaData *meta = NULL; - natsStatus s; - - if ((new_meta == NULL) || (msg == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - if (msg->sub == NULL) - return nats_setError(NATS_ILLEGAL_STATE, "%s", jsErrMsgNotBound); - - if (nats_IsStringEmpty(msg->reply)) - return nats_setError(NATS_ILLEGAL_STATE, "%s", jsErrMsgNotJS); - - if (strstr(msg->reply, jsAckPrefix) != msg->reply) - return nats_setError(NATS_ERR, "invalid meta data '%s'", msg->reply); - - meta = (jsMsgMetaData*) NATS_CALLOC(1, sizeof(jsMsgMetaData)); - if (meta == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - s = js_getMetaData(msg->reply+jsAckPrefixLen, - &(meta->Domain), - &(meta->Stream), - &(meta->Consumer), - &(meta->NumDelivered), - &(meta->Sequence.Stream), - &(meta->Sequence.Consumer), - &(meta->Timestamp), - &(meta->NumPending), - 8); - if (s == NATS_ERR) - s = nats_setError(NATS_ERR, "invalid meta data '%s'", msg->reply); - - if (s == NATS_OK) - *new_meta = meta; - else - jsMsgMetaData_Destroy(meta); - - return NATS_UPDATE_ERR_STACK(s); -} - -void -jsMsgMetaData_Destroy(jsMsgMetaData *meta) -{ - if (meta == NULL) - return; - - NATS_FREE(meta->Stream); - NATS_FREE(meta->Consumer); - NATS_FREE(meta->Domain); - NATS_FREE(meta); -} - -bool -natsMsg_isJSCtrl(natsMsg *msg, int *ctrlType) -{ - char *p = NULL; - - *ctrlType = 0; - - if ((msg->dataLen > 0) || (msg->hdrLen <= 0)) - return false; - - if (strstr(msg->hdr, HDR_LINE_PRE) != msg->hdr) - return false; - - p = msg->hdr + HDR_LINE_PRE_LEN; - if (*p != ' ') - return false; - - while ((*p != '\0') && isspace((unsigned char) *p)) - p++; - - if ((*p == '\r') || (*p == '\n') || (*p == '\0')) - return false; - - if (strstr(p, CTRL_STATUS) != p) - return false; - - p += HDR_STATUS_LEN; - - if (!isspace((unsigned char) *p)) - return false; - - while (isspace((unsigned char) *p)) - p++; - - if (strstr(p, "Idle") == p) - *ctrlType = jsCtrlHeartbeat; - else if (strstr(p, "Flow") == p) - *ctrlType = jsCtrlFlowControl; - - return true; -} - -// Update and replace sid. -// Lock should be held on entry but will be unlocked to prevent lock inversion. -int64_t -applyNewSID(natsSubscription *sub) -{ - int64_t osid = 0; - int64_t nsid = 0; - natsConnection *nc = sub->conn; - - natsSub_Unlock(sub); - - natsMutex_Lock(nc->subsMu); - osid = sub->sid; - natsHash_Remove(nc->subs, osid); - // Place new one. - nc->ssid++; - nsid = nc->ssid; - natsHash_Set(nc->subs, nsid, sub, NULL); - natsMutex_Unlock(nc->subsMu); - - natsSub_Lock(sub); - sub->sid = nsid; - return osid; -} - -static void -_recreateOrderedCons(void *closure) -{ - jsOrderedConsInfo *oci = (jsOrderedConsInfo*) closure; - natsConnection *nc = oci->nc; - natsSubscription *sub = oci->sub; - natsThread *t = NULL; - jsSub *jsi = NULL; - jsConsumerInfo *ci = NULL; - jsConsumerConfig *cc = NULL; - natsStatus s; - - // Note: if anything fail here, the reset/recreate of the ordered consumer - // will happen again based on the missed HB timer. - - // Unsubscribe and subscribe with new inbox and sid. - // Remap a new low level sub into this sub since its client accessible. - // This is done here in this thread to prevent lock inversion. - - natsConn_Lock(nc); - SET_WRITE_DEADLINE(nc); - s = natsConn_sendUnsubProto(nc, oci->osid, 0); - if (!oci->done) - { - IFOK(s, natsConn_sendSubProto(nc, oci->ndlv, NULL, oci->nsid)); - if ((s == NATS_OK) && (oci->max > 0)) - s = natsConn_sendUnsubProto(nc, oci->nsid, oci->max); - } - IFOK(s, natsConn_flushOrKickFlusher(nc)); - natsConn_Unlock(nc); - - if (!oci->done && (s == NATS_OK)) - { - natsSub_Lock(sub); - t = oci->thread; - jsi = sub->jsi; - s = js_cloneConsumerConfig(jsi->ocCfg, &cc); - natsSub_Unlock(sub); - - if (s == NATS_OK) - { - // Create consumer request for starting policy. - cc->DeliverSubject = oci->ndlv; - cc->DeliverPolicy = js_DeliverByStartSequence; - cc->OptStartSeq = oci->sseq; - - s = js_AddConsumer(&ci, jsi->js, jsi->stream, cc, NULL, NULL); - if (s == NATS_OK) - { - natsSub_Lock(sub); - // Set the consumer name only if the consumer's info delivery subject - // matches the subscription's current subject. - if (strcmp(ci->Config->DeliverSubject, sub->subject) == 0) - { - NATS_FREE(jsi->consumer); - jsi->consumer = NULL; - DUP_STRING(s, jsi->consumer, ci->Name); - } - natsSub_Unlock(sub); - - jsConsumerInfo_Destroy(ci); - } - - // Clear cc->DeliverSubject now before destroying it (since - // cc->DeliverSubject points to oci->ndlv (not a copy) - cc->DeliverSubject = NULL; - js_destroyConsumerConfig(cc); - } - } - if (s != NATS_OK) - { - char tmp[256]; - const char *lastErr = nats_GetLastError(NULL); - natsConn_Lock(nc); - snprintf(tmp, sizeof(tmp), - "error recreating ordered consumer, will try again: status=%u error=%s", - s, (nats_IsStringEmpty(lastErr) ? natsStatus_GetText(s) : lastErr)); - natsAsyncCb_PostErrHandler(nc, sub, s, NATS_STRDUP(tmp)); - natsConn_Unlock(nc); - } - - NATS_FREE(oci->ndlv); - NATS_FREE(oci); - natsThread_Detach(t); - natsThread_Destroy(t); - natsSub_release(sub); -} - -// We are here if we have detected a gap with an ordered consumer. -// We will create a new consumer and rewire the low level subscription. -// Lock should be held. -natsStatus -jsSub_resetOrderedConsumer(natsSubscription *sub, uint64_t sseq) -{ - natsStatus s = NATS_OK; - natsConnection *nc = sub->conn; - int64_t osid = 0; - natsInbox *newDeliver = NULL; - jsOrderedConsInfo *oci = NULL; - int max = 0; - bool done = false; - jsSub *jsi = sub->jsi; - - if ((jsi == NULL) || (nc == NULL) || sub->closed) - return NATS_OK; - - // Note: if anything fail here, the reset/recreate of the ordered consumer - // will happen again based on the missed HB timer. - - // If there was an AUTO_UNSUB, we need to adjust the new value and send - // an UNSUB for the new sid with new value. - if (sub->max > 0) - { - // If we are at or anove sub->max, then we are done with this sub - // and will send an UNSUB in the _recreateOrderedCons thread function. - if (sub->jsi->fciseq < sub->max) - max = (int)(sub->max - jsi->fciseq); - else - done = true; - } - - // Grab new inbox. - s = natsConn_newInbox(nc, &newDeliver); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - // Quick unsubscribe. Since we know this is a simple push subscriber we do in place. - osid = applyNewSID(sub); - - NATS_FREE(sub->subject); - sub->subject = (char*) newDeliver; - - // We are still in the low level readloop for the connection so we need - // to spin a thread to try to create the new consumer. - // Create object that will hold some state to pass to the thread. - oci = NATS_CALLOC(1, sizeof(jsOrderedConsInfo)); - if (oci == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - DUP_STRING(s, oci->ndlv, (char*) newDeliver); - - if (s == NATS_OK) - { - // Reset some items in jsi. - jsi->dseq = 1; - NATS_FREE(jsi->fcReply); - jsi->fcReply = NULL; - jsi->fcDelivered = 0; - NATS_FREE(jsi->cmeta); - jsi->cmeta = NULL; - - oci->osid = osid; - oci->nsid = sub->sid; - oci->sseq = sseq; - oci->nc = nc; - oci->sub = sub; - oci->max = max; - oci->done = done; - natsSub_retain(sub); - - s = natsThread_Create(&oci->thread, _recreateOrderedCons, (void*) oci); - if (s != NATS_OK) - { - NATS_FREE(oci); - natsSub_release(sub); - } - } - if ((s != NATS_OK) && (oci != NULL)) - { - NATS_FREE(oci->ndlv); - NATS_FREE(oci); - } - return s; -} - -// Check to make sure messages are arriving in order. -// Returns true if the sub had to be replaced. Will cause upper layers to return. -// The caller has verified that sub.jsi != nil and that this is not a control message. -// Lock should be held. -natsStatus -jsSub_checkOrderedMsg(natsSubscription *sub, natsMsg *msg, bool *reset) -{ - natsStatus s = NATS_OK; - jsSub *jsi = NULL; - uint64_t sseq = 0; - uint64_t dseq = 0; - - *reset = false; - - // Ignore msgs with no reply like HBs and flowcontrol, they are handled elsewhere. - if (natsMsg_GetReply(msg) == NULL) - return NATS_OK; - - // Normal message here. - s = js_getMetaData(natsMsg_GetReply(msg), NULL, NULL, NULL, NULL, &sseq, &dseq, NULL, NULL, 2); - if (s == NATS_OK) - { - jsi = sub->jsi; - if (dseq != jsi->dseq) - { - *reset = true; - s = jsSub_resetOrderedConsumer(sub, jsi->sseq+1); - } - else - { - // Update our tracking here. - jsi->dseq = dseq+1; - jsi->sseq = sseq; - } - } - return NATS_UPDATE_ERR_STACK(s); -} diff --git a/src/js.h b/src/js.h deleted file mode 100644 index bc5ec2abe..000000000 --- a/src/js.h +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright 2021 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 "util.h" - -#ifdef DEV_MODE -// For type safety - -void js_lock(jsCtx *js); -void js_unlock(jsCtx *js); - -#else -// We know what we are doing :-) - -#define js_lock(js) (natsMutex_Lock((js)->mu)) -#define js_unlock(c) (natsMutex_Unlock((js)->mu)) - -#endif // DEV_MODE - - -extern const char* jsDefaultAPIPrefix; -extern const int64_t jsDefaultRequestWait; - -#define jsMsgIdHdr "Nats-Msg-Id" -#define jsExpectedStreamHdr "Nats-Expected-Stream" -#define jsExpectedLastSeqHdr "Nats-Expected-Last-Sequence" -#define jsExpectedLastSubjSeqHdr "Nats-Expected-Last-Subject-Sequence" -#define jsExpectedLastMsgIdHdr "Nats-Expected-Last-Msg-Id" -#define jsConsumerStalledHdr "Nats-Consumer-Stalled" - -#define jsErrStreamNameRequired "stream name is required" -#define jsErrConsumerNameRequired "consumer name is required" -#define jsErrNoStreamMatchesSubject "no stream matches subject" -#define jsErrPullSubscribeToPushConsumer "cannot pull subscribe to push based consumer" -#define jsErrPullSubscribeRequired "must use pull subscribe to bind to pull based consumer" -#define jsErrMsgNotBound "message not bound to a subscription" -#define jsErrMsgNotJS "not a JetStream message" -#define jsErrDurRequired "durable name is required" -#define jsErrNotAPullSubscription "not a JetStream pull subscription" -#define jsErrNotAJetStreamSubscription "not a JetStream subscription" -#define jsErrNotApplicableToPullSub "not applicable to JetStream pull subscriptions" -#define jsErrNoHeartbeatForQueueSub "a queue subscription cannot be created for a consumer with heartbeat" -#define jsErrNoFlowControlForQueueSub "a queue subscription cannot be created for a consumer with flow control" -#define jsErrConsumerSeqMismatch "consumer sequence mismatch" -#define jsErrOrderedConsNoDurable "durable can not be set for an ordered consumer" -#define jsErrOrderedConsNoAckPolicy "ack policy can not be set for an ordered consume" -#define jsErrOrderedConsNoMaxDeliver "max deliver can not be set for an ordered consumer" -#define jsErrOrderedConsNoDeliverSubject "deliver subject can not be set for an ordered consumer" -#define jsErrOrderedConsNoQueue "queue can not be set for an ordered consumer" -#define jsErrOrderedConsNoBind "can not bind existing consumer for an ordered consumer" -#define jsErrOrderedConsNoPullMode "can not use pull mode for an ordered consumer" -#define jsErrStreamConfigRequired "stream configuration required" -#define jsErrInvalidStreamName "invalid stream name" -#define jsErrConsumerConfigRequired "consumer configuration required" -#define jsErrInvalidDurableName "invalid durable name" -#define jsErrInvalidConsumerName "invalid consumer name" -#define jsErrConcurrentFetchNotAllowed "concurrent fetch request not allowed" - -#define jsCtrlHeartbeat (1) -#define jsCtrlFlowControl (2) - -#define jsRetPolicyLimitsStr "limits" -#define jsRetPolicyInterestStr "interest" -#define jsRetPolicyWorkQueueStr "workqueue" - -#define jsDiscardPolicyOldStr "old" -#define jsDiscardPolicyNewStr "new" - -#define jsStorageTypeFileStr "file" -#define jsStorageTypeMemStr "memory" - -#define jsStorageCompressionNoneStr "none" -#define jsStorageCompressionS2Str "s2" - -#define jsDeliverAllStr "all" -#define jsDeliverLastStr "last" -#define jsDeliverNewStr "new" -#define jsDeliverBySeqStr "by_start_sequence" -#define jsDeliverByTimeStr "by_start_time" -#define jsDeliverLastPerSubjectStr "last_per_subject" - -#define jsAckNoneStr "none" -#define jsAckAllStr "all" -#define jsAckExplictStr "explicit" - -#define jsReplayOriginalStr "original" -#define jsReplayInstantStr "instant" - -#define jsAckPrefix "$JS.ACK." -#define jsAckPrefixLen (8) - -// Content of ACK messages sent to server -#define jsAckAck "+ACK" -#define jsAckNak "-NAK" -#define jsAckInProgress "+WPI" -#define jsAckTerm "+TERM" - -// jsExtDomainT is used to create a StreamSource External APIPrefix -#define jsExtDomainT "$JS.%s.API" - -// jsApiAccountInfo is for obtaining general information about JetStream. -#define jsApiAccountInfo "%.*s.INFO" - -// jsApiStreamCreateT is the endpoint to create new streams. -#define jsApiStreamCreateT "%.*s.STREAM.CREATE.%s" - -// jsApiStreamUpdateT is the endpoint to update existing streams. -#define jsApiStreamUpdateT "%.*s.STREAM.UPDATE.%s" - -// jsApiStreamPurgeT is the endpoint to purge streams. -#define jsApiStreamPurgeT "%.*s.STREAM.PURGE.%s" - -// jsApiStreamDeleteT is the endpoint to delete streams. -#define jsApiStreamDeleteT "%.*s.STREAM.DELETE.%s" - -// jsApiStreamInfoT is the endpoint to get information on a stream. -#define jsApiStreamInfoT "%.*s.STREAM.INFO.%s" - -// jsApiConsumerCreateT is used to create consumers. -#define jsApiConsumerCreateT "%.*s.CONSUMER.CREATE.%s" - -// jsApiDurableCreateT is used to create durable consumers. -#define jsApiDurableCreateT "%.*s.CONSUMER.DURABLE.CREATE.%s.%s" - -// jsApiConsumerCreateExT is used to create a named consumer. -#define jsApiConsumerCreateExT "%.*s.CONSUMER.CREATE.%s.%s" - -// jsApiConsumerCreateExWithFilterT is used to create a named consumer with a filter subject. -#define jsApiConsumerCreateExWithFilterT "%.*s.CONSUMER.CREATE.%s.%s.%s" - -// jsApiConsumerInfoT is used to get information about consumers. -#define jsApiConsumerInfoT "%.*s.CONSUMER.INFO.%s.%s" - -// jsApiDeleteConsumerT is used to delete consumers. -#define jsApiConsumerDeleteT "%.*s.CONSUMER.DELETE.%s.%s" - -// jsApiStreams can lookup a stream by subject. -#define jsApiStreams "%.*s.STREAM.NAMES" - -// jsApiRequestNextT is the prefix for the request next message(s) for a consumer in worker/pull mode. -#define jsApiRequestNextT "%s.CONSUMER.MSG.NEXT.%s.%s" - -// jsApiMsgDeleteT is the endpoint to remove a message. -#define jsApiMsgDeleteT "%.*s.STREAM.MSG.DELETE.%s" - -// jsApiMsgGetT is the endpoint to get a message, either by sequence or last per subject. -#define jsApiMsgGetT "%.*s.STREAM.MSG.GET.%s" - -// jsApiMsgGetT is the endpoint to get a message, either by sequence or last per subject. -#define jsApiDirectMsgGetT "%.*s.DIRECT.GET.%s" - -// jsApiDirectMsgGetLastBySubjectT is the endpoint to perform a direct get of a message by subject. -#define jsApiDirectMsgGetLastBySubjectT "%.*s.DIRECT.GET.%s.%s" - -// jsApiStreamListT is the endpoint to get the list of stream infos. -#define jsApiStreamListT "%.*s.STREAM.LIST" - -// jsApiStreamNamesT is the endpoint to get the list of stream names. -#define jsApiStreamNamesT "%.*s.STREAM.NAMES" - -// jsApiConsumerListT is the endpoint to get the list of consumers for a stream. -#define jsApiConsumerListT "%.*s.CONSUMER.LIST.%s" - -// jsApiConsumerNamesT is the endpoint to get the list of consumer names for a stream. -#define jsApiConsumerNamesT "%.*s.CONSUMER.NAMES.%s" - -// jsApiConsumerPauseT is the endpoint to pause a consumer. -#define jsApiConsumerPauseT "%.*s.CONSUMER.PAUSE.%s.%s" - -// Creates a subject based on the option's prefix, the subject format and its values. -#define js_apiSubj(s, o, f, ...) (nats_asprintf((s), (f), (o)->Prefix, __VA_ARGS__) < 0 ? NATS_NO_MEMORY : NATS_OK) - -// Execute the JS API request if status is OK on entry. If the result is NATS_NO_RESPONDERS, -// and `errCode` is not NULL, set it to JSNotEnabledErr. -#define IFOK_JSR(s, c) if (s == NATS_OK) { s = (c); if ((s == NATS_NO_RESPONDERS) && (errCode != NULL)) { *errCode = JSNotEnabledErr; } } - -// Returns true if the API response has a Code or ErrCode that is not 0. -#define js_apiResponseIsErr(ar) (((ar)->Error.Code != 0) || ((ar)->Error.ErrCode != 0)) - -// jsApiError is included in all API responses if there was an error. -typedef struct __jsApiError -{ - int Code; - uint16_t ErrCode; - char *Description; - -} jsApiError; - -// apiResponse is a standard response from the JetStream JSON API -typedef struct __jsApiResponse -{ - char *Type; - jsApiError Error; - -} jsApiResponse; - -// Sets the options in `resOpts` based on the given `opts` and defaults to the context -// own options when some options are not specified. -// Returns also the NATS connection to be used to send the request. -// This function will get/release the context's lock. -natsStatus -js_setOpts(natsConnection **nc, bool *freePfx, jsCtx *js, jsOptions *opts, jsOptions *resOpts); - -int -js_lenWithoutTrailingDot(const char *str); - -natsStatus -js_unmarshalResponse(jsApiResponse *ar, nats_JSON **new_json, natsMsg *resp); - -void -js_freeApiRespContent(jsApiResponse *ar); - -natsStatus -js_unmarshalAccountInfo(nats_JSON *json, jsAccountInfo **new_ai); - -natsStatus -js_marshalStreamConfig(natsBuffer **new_buf, jsStreamConfig *cfg); - -natsStatus -js_unmarshalStreamConfig(nats_JSON *json, const char *fieldName, jsStreamConfig **new_cfg); - -void -js_destroyStreamConfig(jsStreamConfig *cfg); - -natsStatus -js_unmarshalStreamState(nats_JSON *pjson, const char *fieldName, jsStreamState *state); - -natsStatus -js_unmarshalStreamInfo(nats_JSON *json, jsStreamInfo **new_si); - -natsStatus -js_unmarshalConsumerInfo(nats_JSON *json, jsConsumerInfo **new_ci); - -void -js_cleanStreamState(jsStreamState *state); - -natsStatus -js_checkConsName(const char *cons, bool isDurable); - -natsStatus -js_getMetaData(const char *reply, - char **domain, - char **stream, - char **consumer, - uint64_t *numDelivered, - uint64_t *sseq, - uint64_t *dseq, - int64_t *tm, - uint64_t *numPending, - int asked); - -void -js_retain(jsCtx *js); - -void -js_release(jsCtx *js); - -natsStatus -js_directGetMsgToJSMsg(const char *stream, natsMsg *msg); - -natsStatus -js_cloneConsumerConfig(jsConsumerConfig *org, jsConsumerConfig **clone); - -void -js_destroyConsumerConfig(jsConsumerConfig *cc); diff --git a/src/jsm.c b/src/jsm.c deleted file mode 100644 index 3c8321491..000000000 --- a/src/jsm.c +++ /dev/null @@ -1,4032 +0,0 @@ -// 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, bool sep, 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); - - if (sep) - s = natsBuf_AppendByte(buf, ','); - - IFOK(s, natsBuf_AppendByte(buf, '"')); - 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_freeMetadata(&(cfg->Metadata)); - NATS_FREE((char *)cfg->SubjectTransform.Source); - NATS_FREE((char *)cfg->SubjectTransform.Destination); - 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) -{ - int i; - - if (info == NULL) - return; - - NATS_FREE(info->Name); - NATS_FREE((char*)info->FilterSubject); - for (i=0; i < info->SubjectTransformsLen; i++) - { - NATS_FREE((char *)info->SubjectTransforms[i].Source); - NATS_FREE((char *)info->SubjectTransforms[i].Destination); - } - NATS_FREE(info->SubjectTransforms); - _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, true, "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 -_unmarshalStorageCompression(nats_JSON *json, const char *fieldName, jsStorageCompression *compression) -{ - natsStatus s = NATS_OK; - const char *str = NULL; - - s = nats_JSONGetStrPtr(json, "compression", &str); - if (str == NULL) - return NATS_UPDATE_ERR_STACK(s); - - if (strcmp(str, jsStorageCompressionNoneStr) == 0) - *compression = js_StorageCompressionNone; - else if (strcmp(str, jsStorageCompressionS2Str) == 0) - *compression = js_StorageCompressionS2; - else - s = nats_setError(NATS_ERR, "unable to unmarshal storage compression '%s'", str); - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_marshalStorageCompression(jsStorageCompression compression, natsBuffer *buf) -{ - natsStatus s; - const char *st = NULL; - - s = natsBuf_Append(buf, ",\"compression\":\"", -1); - switch (compression) - { - case js_StorageCompressionNone: - st = jsStorageCompressionNoneStr; - break; - case js_StorageCompressionS2: - st = jsStorageCompressionS2Str; - break; - default: - return nats_setError(NATS_INVALID_ARG, "invalid storage type %d", (int)compression); - } - IFOK(s, natsBuf_Append(buf, st, -1)); - IFOK(s, natsBuf_AppendByte(buf, '"')); - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_unmarshalSubjectTransformConfig(nats_JSON *obj, jsSubjectTransformConfig *cfg) -{ - natsStatus s = NATS_OK; - - memset(cfg, 0, sizeof(jsSubjectTransformConfig)); - if (obj == NULL) - { - return NATS_OK; - } - - IFOK(s, nats_JSONGetStr(obj, "src", (char **)&(cfg->Source))); - IFOK(s, nats_JSONGetStr(obj, "dest", (char **)&(cfg->Destination))); - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_marshalSubjectTransformConfig(jsSubjectTransformConfig *cfg, natsBuffer *buf) -{ - natsStatus s; - if (cfg == NULL || (nats_IsStringEmpty(cfg->Source) && nats_IsStringEmpty(cfg->Destination))) - return NATS_OK; - - s = natsBuf_Append(buf, ",\"subject_transform\":{", -1); - IFOK(s, natsBuf_Append(buf, "\"src\":\"", -1)); - if (cfg->Source != NULL) - IFOK(s, natsBuf_Append(buf, cfg->Source, -1)); - IFOK(s, natsBuf_Append(buf, "\",\"dest\":\"", -1)); - if (cfg->Destination != NULL) - IFOK(s, natsBuf_Append(buf, cfg->Destination, -1)); - IFOK(s, natsBuf_Append(buf, "\"}", -1)); - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_marshalStreamConsumerLimits(jsStreamConsumerLimits *limits, natsBuffer *buf) -{ - natsStatus s; - if (limits == NULL || (limits->InactiveThreshold == 0 && limits->MaxAckPending == 0)) - return NATS_OK; - - s = natsBuf_Append(buf, ",\"consumer_limits\":{", -1); - IFOK(s, nats_marshalLong(buf, false, "inactive_threshold", limits->InactiveThreshold)); - IFOK(s, nats_marshalLong(buf, true, "max_ack_pending", limits->MaxAckPending)); - IFOK(s, natsBuf_AppendByte(buf, '}')); - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_unmarshalStreamConsumerLimits(nats_JSON *obj, jsStreamConsumerLimits *limits) -{ - natsStatus s = NATS_OK; - - memset(limits, 0, sizeof(*limits)); - if (obj == NULL) - { - return NATS_OK; - } - - IFOK(s, nats_JSONGetLong(obj, "inactive_threshold", &limits->InactiveThreshold)); - IFOK(s, nats_JSONGetInt(obj, "max_ack_pending", &limits->MaxAckPending)); - 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; - nats_JSON *obj = NULL; - 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))); - - IFOK(s, nats_unmarshalMetadata(jcfg, "metadata", &(cfg->Metadata))); - IFOK(s, _unmarshalStorageCompression(jcfg, "storage", &(cfg->Compression))); - IFOK(s, nats_JSONGetULong(jcfg, "first_seq", &(cfg->FirstSeq))); - IFOK(s, nats_JSONGetObject(jcfg, "subject_transform", &obj)); - IFOK(s, _unmarshalSubjectTransformConfig(obj, &(cfg->SubjectTransform))); - obj = NULL; - IFOK(s, nats_JSONGetObject(jcfg, "consumer_limits", &obj)); - IFOK(s, _unmarshalStreamConsumerLimits(obj, &(cfg->ConsumerLimits))); - - 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, nats_marshalMetadata(buf, true, "metadata", cfg->Metadata)); - IFOK(s, _marshalStorageCompression(cfg->Compression, buf)); - IFOK(s, nats_marshalULong(buf, true, "first_seq", cfg->FirstSeq)); - IFOK(s, _marshalSubjectTransformConfig(&cfg->SubjectTransform, buf)); - IFOK(s, _marshalStreamConsumerLimits(&cfg->ConsumerLimits, buf)); - - 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; - nats_JSON **subjectTransforms = NULL; - int subjectTransformsLen = 0; - - 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))); - IFOK(s, nats_JSONGetStr(json, "filter_subject", (char **)&(ssi->FilterSubject))); - - // Get the sources and unmarshal if present - IFOK(s, nats_JSONGetArrayObject(json, "subject_transforms", &subjectTransforms, &subjectTransformsLen)); - if ((s == NATS_OK) && (subjectTransforms != NULL)) - { - int i; - - ssi->SubjectTransforms = (jsSubjectTransformConfig *)NATS_CALLOC(subjectTransformsLen, sizeof(jsSubjectTransformConfig)); - if (ssi->SubjectTransforms == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - - for (i = 0; (s == NATS_OK) && (i < subjectTransformsLen); i++) - { - s = _unmarshalSubjectTransformConfig(subjectTransforms[i], &(ssi->SubjectTransforms[i])); - if (s == NATS_OK) - ssi->SubjectTransformsLen++; - } - // Free the array of JSON objects that was allocated by nats_JSONGetArrayObject. - NATS_FREE(subjectTransforms); - } - - 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; - cfg->Compression = js_StorageCompressionNone; - 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); - - // Make sure the 2.10 config fields actually worked, in case the server is - // older. - if ((s == NATS_OK) && (new_si != NULL) && (*new_si != NULL) - && (cfg->Compression != (*new_si)->Config->Compression) - && (cfg->FirstSeq != (*new_si)->Config->FirstSeq) - && (cfg->Metadata.Count != (*new_si)->Config->Metadata.Count) - && nats_StringEquals(cfg->SubjectTransform.Source, (*new_si)->Config->SubjectTransform.Source) - && nats_StringEquals(cfg->SubjectTransform.Destination, (*new_si)->Config->SubjectTransform.Destination) - && (cfg->ConsumerLimits.InactiveThreshold != (*new_si)->Config->ConsumerLimits.InactiveThreshold) - && (cfg->ConsumerLimits.MaxAckPending != (*new_si)->Config->ConsumerLimits.MaxAckPending) - ) - { - return nats_setError(NATS_INVALID_ARG, "%s", jsErrStreamConfigRequired); - } - - 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, true, "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, '"')); - } - if ((s == NATS_OK) && (cfg->FilterSubjectsLen > 0)) - { - int i; - - s = natsBuf_Append(buf, ",\"filter_subjects\":[", -1); - for (i = 0; (s == NATS_OK) && (i < cfg->FilterSubjectsLen); i++) - { - if (i > 0) - s = natsBuf_AppendByte(buf, ','); - IFOK(s, natsBuf_AppendByte(buf, '"')); - IFOK(s, natsBuf_Append(buf, cfg->FilterSubjects[i], -1)); - IFOK(s, natsBuf_AppendByte(buf, '"')); - } - - IFOK(s, natsBuf_AppendByte(buf, ']')); - } - IFOK(s, nats_marshalMetadata(buf, true, "metadata", cfg->Metadata)); - if ((s == NATS_OK) && (cfg->PauseUntil > 0)) - s = _marshalTimeUTC(buf, true, "pause_until", cfg->PauseUntil); - 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) -{ - int i; - - 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); - for (i = 0; i < cc->FilterSubjectsLen; i++) - NATS_FREE((char *)cc->FilterSubjects[i]); - nats_freeMetadata(&(cc->Metadata)); - NATS_FREE((char *)cc->FilterSubjects); - 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, nats_JSONGetArrayStr(cjson, "filter_subjects", (char ***)&(cc->FilterSubjects), &(cc->FilterSubjectsLen))); - 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))); - IFOK(s, nats_unmarshalMetadata(cjson, "metadata", &(cc->Metadata))); - } - - 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))); - IFOK(s, nats_JSONGetBool(json, "paused", &(ci->Paused))); - IFOK(s, nats_JSONGetLong(json, "pause_remaining", &(ci->PauseRemaining))); - 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) && (cfg->FilterSubjectsLen == 0)) - res = nats_asprintf(&subj, jsApiConsumerCreateExWithFilterT, - js_lenWithoutTrailingDot(o.Prefix), o.Prefix, - stream, cfg->Name, cfg->FilterSubject); - else - res = nats_asprintf(&subj, jsApiConsumerCreateExT, - js_lenWithoutTrailingDot(o.Prefix), o.Prefix, - stream, cfg->Name); - } - 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)); - - if ((s == NATS_OK) - && (new_ci != NULL) - && (cfg->FilterSubjectsLen > 0) - && ((*new_ci)->Config->FilterSubjectsLen == 0)) - { - s = nats_setError(NATS_INVALID_ARG, "%s", "multiple consumer filter subjects not supported by the server"); - } - - 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 -js_unmarshalConsumerPauseResp(nats_JSON *json, jsConsumerPauseResponse **new_cpr) -{ - natsStatus s = NATS_OK; - jsConsumerPauseResponse *cpr = NULL; - - cpr = (jsConsumerPauseResponse *)NATS_CALLOC(1, sizeof(jsConsumerPauseResponse)); - if (cpr == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - s = nats_JSONGetBool(json, "paused", &(cpr->Paused)); - IFOK(s, nats_JSONGetTime(json, "pause_until", &(cpr->PauseUntil))); - IFOK(s, nats_JSONGetLong(json, "pause_remaining", &(cpr->PauseRemaining))); - - if (s == NATS_OK) - *new_cpr = cpr; - else - jsConsumerPauseResponse_Destroy(cpr); - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_unmarshalConsumerPauseResp(jsConsumerPauseResponse **new_cpr, 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_cpr != NULL) - { - // At this point we need to unmarshal the consumer info itself. - s = js_unmarshalConsumerPauseResp(json, new_cpr); - } - - js_freeApiRespContent(&ar); - nats_JSONDestroy(json); - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_marshalConsumerPauseReq(natsBuffer **new_buf, uint64_t pauseUntil) -{ - natsStatus s = NATS_OK; - natsBuffer *buf = NULL; - - s = natsBuf_Create(&buf, 256); - IFOK(s, natsBuf_AppendByte(buf, '{')); - if ((s == NATS_OK) && (pauseUntil > 0)) { - s = _marshalTimeUTC(buf, false, "pause_until", pauseUntil); - } - IFOK(s, natsBuf_AppendByte(buf, '}')); - - if (s == NATS_OK) - *new_buf = buf; - else - natsBuf_Destroy(buf); - - return NATS_UPDATE_ERR_STACK(s); -} - -void -jsConsumerPauseResponse_Destroy(jsConsumerPauseResponse *cpr) -{ - if (cpr == NULL) - return; - - NATS_FREE(cpr); -} - - -natsStatus -js_PauseConsumer(jsConsumerPauseResponse **new_cpr, jsCtx *js, - const char *stream, const char *consumer, - uint64_t pauseUntil, jsOptions *opts, jsErrCode *errCode) -{ - natsStatus s = NATS_OK; - char *subj = NULL; - bool freePfx = false; - natsConnection *nc = NULL; - natsBuffer *buf = NULL; - natsMsg *resp = NULL; - jsOptions o; - - if (errCode != NULL) - *errCode = 0; - - if ((js == NULL) || (new_cpr == 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, jsApiConsumerPauseT, - js_lenWithoutTrailingDot(o.Prefix), o.Prefix, - stream, consumer) < 0) - { - s = nats_setDefaultError(NATS_NO_MEMORY); - } - if (freePfx) - NATS_FREE((char *)o.Prefix); - } - - IFOK(s, _marshalConsumerPauseReq(&buf, pauseUntil)); - - // 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, _unmarshalConsumerPauseResp(new_cpr, resp, errCode)); - - NATS_FREE(subj); - natsMsg_Destroy(resp); - natsBuf_Destroy(buf); - - if (s == NATS_NOT_FOUND) - { - nats_clearLastError(); - return s; - } - - 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->FilterSubjects = NULL; - c->FilterSubjectsLen = 0; - 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) && (org->FilterSubjects != NULL) && (org->FilterSubjectsLen > 0)) - { - c->FilterSubjects = (const char **)NATS_CALLOC(org->FilterSubjectsLen, sizeof(const char *)); - if (c->FilterSubjects == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - - for (int i = 0; (s == NATS_OK) && (i < org->FilterSubjectsLen); i++) - { - IF_OK_DUP_STRING(s, c->FilterSubjects[i], org->FilterSubjects[i]); - } - c->FilterSubjectsLen = org->FilterSubjectsLen; - } - IFOK(s, nats_cloneMetadata(&(c->Metadata), org->Metadata)); - if (s == NATS_OK) - *clone = c; - else - js_destroyConsumerConfig(c); - - return NATS_UPDATE_ERR_STACK(s); -} diff --git a/src/json.h b/src/json.h new file mode 100644 index 000000000..0a9f3b007 --- /dev/null +++ b/src/json.h @@ -0,0 +1,206 @@ +// Copyright 2024 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. + +#ifndef JSON_H_ +#define JSON_H_ + +#include "natsp.h" + +#include "mem.h" + +#define JSON_MAX_NEXTED 100 + +extern int jsonMaxNested; + +#define TYPE_NOT_SET (0) +#define TYPE_STR (1) +#define TYPE_BOOL (2) +#define TYPE_NUM (3) +#define TYPE_INT (4) +#define TYPE_UINT (5) +#define TYPE_DOUBLE (6) +#define TYPE_ARRAY (7) +#define TYPE_OBJECT (8) +#define TYPE_NULL (9) + +// A long double memory size is larger (or equal to) u/int64_t, so use that +// as the maximum size of a num element in an array. +#define JSON_MAX_NUM_SIZE ((int)sizeof(long double)) + +typedef struct +{ + void **values; + int typ; + int eltSize; + int size; + int cap; + +} nats_JSONArray; + +struct __nats_JSON_s +{ + natsStrHash *fields; + nats_JSONArray *array; + natsPool *pool; +}; + +typedef struct +{ + char *name; + int typ; + union + { + char *vstr; + bool vbool; + uint64_t vuint; + int64_t vint; + long double vdec; + nats_JSONArray *varr; + nats_JSON *vobj; + } value; + int numTyp; + +} nats_JSONField; + +natsStatus +natsJSONParser_Create(natsJSONParser **parser, natsPool *pool); + +// Should be called repeatedly until newJSON is initialized. If there are no +// errors, artial parsing returns NATS_OK, jsonObj set to NULL, and consumes the +// entire buf. +natsStatus +natsJSONParser_Parse(nats_JSON **jsonObj, natsJSONParser *parser, const uint8_t *data, const uint8_t *end, size_t *consumed); + +natsStatus +nats_JSONRefField(nats_JSON *json, const char *fieldName, int fieldType, nats_JSONField **retField); + +natsStatus +nats_JSONDupStr(nats_JSON *json, natsPool *pool, const char *fieldName, const char **value); + +natsStatus +nats_JSONDupStrIfDiff(nats_JSON *json, natsPool *pool, const char *fieldName, const char **value); + +natsStatus +nats_JSONRefStr(nats_JSON *json, const char *fieldName, const char **str); + +natsStatus +nats_JSONDupBytes(nats_JSON *json, natsPool *pool, const char *fieldName, unsigned char **value, int *len); + +natsStatus +nats_JSONGetInt(nats_JSON *json, const char *fieldName, int *value); + +natsStatus +nats_JSONGetInt32(nats_JSON *json, const char *fieldName, int32_t *value); + +natsStatus +nats_JSONGetUInt16(nats_JSON *json, const char *fieldName, uint16_t *value); + +natsStatus +nats_JSONGetBool(nats_JSON *json, const char *fieldName, bool *value); + +natsStatus +nats_JSONGetLong(nats_JSON *json, const char *fieldName, int64_t *value); + +natsStatus +nats_JSONGetULong(nats_JSON *json, const char *fieldName, uint64_t *value); + +natsStatus +nats_JSONGetDouble(nats_JSON *json, const char *fieldName, long double *value); + +natsStatus +nats_JSONRefObject(nats_JSON *json, const char *fieldName, nats_JSON **value); + +natsStatus +nats_JSONGetTime(nats_JSON *json, const char *fieldName, int64_t *timeUTC); + +natsStatus +nats_JSONRefArray(nats_JSON *json, const char *fieldName, int fieldType, nats_JSONField **retField); + +natsStatus +nats_JSONDupStringArrayIfDiff(nats_JSON *json, natsPool *pool, const char *fieldName, const char ***array, int *arraySize); + +natsStatus +nats_JSONArrayAsBools(nats_JSONArray *arr, bool **array, int *arraySize); + +natsStatus +nats_JSONGetArrayBool(nats_JSON *json, const char *fieldName, bool **array, int *arraySize); + +natsStatus +nats_JSONArrayAsDoubles(nats_JSONArray *arr, long double **array, int *arraySize); + +natsStatus +nats_JSONGetArrayDouble(nats_JSON *json, const char *fieldName, long double **array, int *arraySize); + +natsStatus +nats_JSONArrayAsInts(nats_JSONArray *arr, int **array, int *arraySize); + +natsStatus +nats_JSONGetArrayInt(nats_JSON *json, const char *fieldName, int **array, int *arraySize); + +natsStatus +nats_JSONArrayAsLongs(nats_JSONArray *arr, int64_t **array, int *arraySize); + +natsStatus +nats_JSONGetArrayLong(nats_JSON *json, const char *fieldName, int64_t **array, int *arraySize); + +natsStatus +nats_JSONArrayAsULongs(nats_JSONArray *arr, uint64_t **array, int *arraySize); + +natsStatus +nats_JSONGetArrayULong(nats_JSON *json, const char *fieldName, uint64_t **array, int *arraySize); + +natsStatus +nats_JSONArrayAsObjects(nats_JSONArray *arr, nats_JSON ***array, int *arraySize); + +natsStatus +nats_JSONGetArrayObject(nats_JSON *json, const char *fieldName, nats_JSON ***array, int *arraySize); + +natsStatus +nats_JSONArrayAsArrays(nats_JSONArray *arr, nats_JSONArray ***array, int *arraySize); + +natsStatus +nats_JSONGetArrayArray(nats_JSON *json, const char *fieldName, nats_JSONArray ***array, int *arraySize); + +typedef natsStatus (*jsonRangeCB)(void *userInfo, const char *fieldName, nats_JSONField *f); + +natsStatus +nats_JSONRange(nats_JSON *json, int expectedType, int expectedNumType, jsonRangeCB cb, void *userInfo); + +natsStatus +nats_EncodeTimeUTC(char *buf, size_t bufLen, int64_t timeUTC); + +natsStatus +nats_marshalLong(natsBuf *buf, bool comma, const char *fieldName, int64_t lval); + +natsStatus +nats_marshalULong(natsBuf *buf, bool comma, const char *fieldName, uint64_t uval); + +natsStatus +nats_marshalDuration(natsBuf *out_buf, bool comma, const char *field_name, int64_t d); + +natsStatus nats_marshalConnect(natsString **out, natsConnection *nc, const char *user, + const char *pwd, const char *token, const char *name, + bool hdrs, bool noResponders); + +natsStatus +nats_unmarshalServerInfo(nats_JSON *json, natsPool *pool, natsServerInfo *info); + +#ifdef DEV_MODE_JSON +#define JSONDEBUG(str) DEVDEBUG("JSON", str) +#define JSONDEBUGf(fmt, ...) DEVDEBUGf("JSON", fmt, __VA_ARGS__) +#else +#define JSONDEBUG DEVNOLOG +#define JSONDEBUGf DEVNOLOGf +#endif + +#endif /* JSON_H_ */ diff --git a/src/json_get.c b/src/json_get.c new file mode 100644 index 000000000..af0709679 --- /dev/null +++ b/src/json_get.c @@ -0,0 +1,490 @@ +// Copyright 2015-2023 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 "hash.h" +#include "json.h" + +#define JSON_GET_AS(jt, t) \ + natsStatus s = NATS_OK; \ + nats_JSONField *field = NULL; \ + s = nats_JSONRefField(json, fieldName, (jt), &field); \ + if ((STILL_OK(s)) && (field == NULL)) \ + { \ + *value = 0; \ + return NATS_OK; \ + } \ + else if (STILL_OK(s)) \ + { \ + switch (field->numTyp) \ + { \ + case TYPE_INT: \ + *value = (t)field->value.vint; \ + break; \ + case TYPE_UINT: \ + *value = (t)field->value.vuint; \ + break; \ + default: \ + *value = (t)field->value.vdec; \ + } \ + } \ + return NATS_UPDATE_ERR_STACK(s); + +#define JSON_ARRAY_AS(_p, _t) \ + int i; \ + _t *values = (_t *)natsPool_alloc((_p), arr->size * sizeof(_t)); \ + if (values == NULL) \ + return nats_setDefaultError(NATS_NO_MEMORY); \ + for (i = 0; i < arr->size; i++) \ + values[i] = ((_t *)arr->values)[i]; \ + *array = values; \ + *arraySize = arr->size; \ + return NATS_OK; + +#define JSON_ARRAY_AS_NUM(_p, _t) \ + int i; \ + _t *values = (_t *)natsPool_alloc((_p), arr->size * sizeof(_t)); \ + if (values == NULL) \ + return nats_setDefaultError(NATS_NO_MEMORY); \ + for (i = 0; i < arr->size; i++) \ + { \ + void *ptr = NULL; \ + ptr = (void *)((char *)(arr->values) + (i * JSON_MAX_NUM_SIZE)); \ + values[i] = *(_t *)ptr; \ + } \ + *array = values; \ + *arraySize = arr->size; \ + return NATS_OK; + +#define JSON_GET_ARRAY(_p, _t, _f) \ + natsStatus s = NATS_OK; \ + nats_JSONField *field = NULL; \ + s = nats_JSONRefArray(json, fieldName, (_t), &field); \ + if ((STILL_OK(s)) && (field == NULL)) \ + { \ + *array = NULL; \ + *arraySize = 0; \ + return NATS_OK; \ + } \ + else if (STILL_OK(s)) \ + s = (_f)((_p), field->value.varr, array, arraySize); \ + return NATS_UPDATE_ERR_STACK(s); + +natsStatus +nats_JSONRefField(nats_JSON *json, const char *fieldName, int fieldType, nats_JSONField **retField) +{ + nats_JSONField *field = NULL; + + field = (nats_JSONField *)natsStrHash_Get(json->fields, (char *)fieldName, nats_strlen(fieldName)); + if ((field == NULL) || (field->typ == TYPE_NULL)) + { + *retField = NULL; + return NATS_OK; + } + + // Check parsed type matches what is being asked. + switch (fieldType) + { + case TYPE_INT: + case TYPE_UINT: + case TYPE_DOUBLE: + if (field->typ != TYPE_NUM) + return nats_setError(NATS_INVALID_ARG, + "Asked for field '%s' as type %d, but got type %d when parsing", + field->name, fieldType, field->typ); + break; + case TYPE_BOOL: + case TYPE_STR: + case TYPE_OBJECT: + if (field->typ != fieldType) + return nats_setError(NATS_INVALID_ARG, + "Asked for field '%s' as type %d, but got type %d when parsing", + field->name, fieldType, field->typ); + break; + default: + return nats_setError(NATS_INVALID_ARG, + "Asked for field '%s' as type %d, but this type does not exist", + field->name, fieldType); + } + *retField = field; + return NATS_OK; +} + +natsStatus +nats_JSONDupStrIfDiff(nats_JSON *json, natsPool *pool, const char *fieldName, const char **value) +{ + natsStatus s = NATS_OK; + nats_JSONField *field = NULL; + + s = nats_JSONRefField(json, fieldName, TYPE_STR, &field); + if ((field == NULL) || (field->value.vstr == NULL)) + { + *value = NULL; + return NATS_UPDATE_ERR_STACK(s); + } + + if ((*value != NULL) && (strcmp(*value, field->value.vstr) == 0)) + return NATS_OK; + + char *tmp = nats_pstrdupC(pool, field->value.vstr); + if (tmp == NULL) + s = nats_setDefaultError(NATS_NO_MEMORY); + else + *value = tmp; + + return NATS_UPDATE_ERR_STACK(s); +} + +natsStatus +nats_JSONDupStr(nats_JSON *json, natsPool *pool, const char *fieldName, const char **value) +{ + natsStatus s = NATS_OK; + nats_JSONField *field = NULL; + + s = nats_JSONRefField(json, fieldName, TYPE_STR, &field); + if ((field == NULL) || (field->value.vstr == NULL)) + { + *value = NULL; + return NATS_UPDATE_ERR_STACK(s); + } + + char *tmp = nats_pstrdupC(pool, field->value.vstr); + if (tmp == NULL) + s = nats_setDefaultError(NATS_NO_MEMORY); + else + *value = tmp; + + return NATS_UPDATE_ERR_STACK(s); +} + +natsStatus +nats_JSONRefStr(nats_JSON *json, const char *fieldName, const char **str) +{ + natsStatus s; + nats_JSONField *field = NULL; + + s = nats_JSONRefField(json, fieldName, TYPE_STR, &field); + if (STILL_OK(s)) + { + if (field == NULL) + *str = NULL; + else + *str = field->value.vstr; + } + return NATS_UPDATE_ERR_STACK(s); +} + +// natsStatus +// nats_JSONGetBytes(nats_JSON *json, const char *fieldName, unsigned char **value, int *len) +// { +// natsStatus s; +// const char *str = NULL; + +// *value = NULL; +// *len = 0; + +// s = nats_JSONRefStr(json, fieldName, &str); +// if ((STILL_OK(s)) && (str != NULL)) +// s = nats_Base64_Decode(str, value, len); +// return NATS_UPDATE_ERR_STACK(s); +// } + +natsStatus +nats_JSONGetInt(nats_JSON *json, const char *fieldName, int *value) +{ + JSON_GET_AS(TYPE_INT, int); +} + +natsStatus +nats_JSONGetInt32(nats_JSON *json, const char *fieldName, int32_t *value) +{ + JSON_GET_AS(TYPE_INT, int32_t); +} + +natsStatus +nats_JSONGetUInt16(nats_JSON *json, const char *fieldName, uint16_t *value) +{ + JSON_GET_AS(TYPE_UINT, uint16_t); +} + +natsStatus +nats_JSONGetBool(nats_JSON *json, const char *fieldName, bool *value) +{ + natsStatus s = NATS_OK; + nats_JSONField *field = NULL; + + s = nats_JSONRefField(json, fieldName, TYPE_BOOL, &field); + if (STILL_OK(s)) + { + *value = (field == NULL ? false : field->value.vbool); + return NATS_OK; + } + return NATS_UPDATE_ERR_STACK(s); +} + +natsStatus +nats_JSONGetLong(nats_JSON *json, const char *fieldName, int64_t *value) +{ + JSON_GET_AS(TYPE_INT, int64_t); +} + +natsStatus +nats_JSONGetULong(nats_JSON *json, const char *fieldName, uint64_t *value) +{ + JSON_GET_AS(TYPE_UINT, uint64_t); +} + +natsStatus +nats_JSONGetDouble(nats_JSON *json, const char *fieldName, long double *value) +{ + JSON_GET_AS(TYPE_DOUBLE, long double); +} + +natsStatus +nats_JSONRefObject(nats_JSON *json, const char *fieldName, nats_JSON **value) +{ + natsStatus s = NATS_OK; + nats_JSONField *field = NULL; + + s = nats_JSONRefField(json, fieldName, TYPE_OBJECT, &field); + if (STILL_OK(s)) + { + *value = (field == NULL ? NULL : field->value.vobj); + return NATS_OK; + } + return NATS_UPDATE_ERR_STACK(s); +} + +// natsStatus +// nats_JSONGetTime(nats_JSON *json, const char *fieldName, int64_t *timeUTC) +// { +// natsStatus s = NATS_OK; +// char *str = NULL; + +// s = nats_JSONGetStr(json, fieldName, &str); +// if ((STILL_OK(s)) && (str == NULL)) +// { +// *timeUTC = 0; +// return NATS_OK; +// } +// else if (s != NATS_OK) +// return NATS_UPDATE_ERR_STACK(s); + +// s = nats_parseTime(str, timeUTC); +// NATS_FREE(str); +// return NATS_UPDATE_ERR_STACK(s); +// } + +natsStatus +nats_JSONRefArray(nats_JSON *json, const char *fieldName, int fieldType, nats_JSONField **retField) +{ + nats_JSONField *field = NULL; + + field = (nats_JSONField *)natsStrHash_Get(json->fields, (char *)fieldName, nats_strlen(fieldName)); + if ((field == NULL) || (field->typ == TYPE_NULL)) + { + *retField = NULL; + return NATS_OK; + } + + // Check parsed type matches what is being asked. + if (field->typ != TYPE_ARRAY) + return nats_setError(NATS_INVALID_ARG, + "Field '%s' is not an array, it has type: %d", + field->name, field->typ); + // If empty array, return NULL/OK + if (field->value.varr->typ == TYPE_NULL) + { + *retField = NULL; + return NATS_OK; + } + if (fieldType != field->value.varr->typ) + return nats_setError(NATS_INVALID_ARG, + "Asked for field '%s' as an array of type: %d, but it is an array of type: %d", + field->name, fieldType, field->typ); + + *retField = field; + return NATS_OK; +} + +static natsStatus +_jsonArrayAsStringsIfDiff(natsPool *pool, nats_JSONArray *arr, const char ***array, int *arraySize) +{ + int i; + bool diffSize = (*arraySize != arr->size); + bool diff = diffSize; + + if (!diffSize) + { + for (i = 0; i < arr->size; i++) + if (strcmp((char *)(arr->values[i]), (*array)[i]) != 0) + { + diff = true; + break; + } + } + if (!diff) + return NATS_OK; + + const char **values = *array; + if (*arraySize < arr->size) + { + values = (const char **)nats_palloc(pool, arr->size * sizeof(char *)); + if (values == NULL) + return NATS_UPDATE_ERR_STACK(nats_setDefaultError(NATS_NO_MEMORY)); + } + + for (i = 0; i < arr->size; i++) + { + if ((values[i] != NULL) && strcmp((char *)(arr->values[i]), values[i]) != 0) + { + values[i] = nats_pstrdupC(pool, (char *)(arr->values[i])); + if (values[i] == NULL) + return NATS_UPDATE_ERR_STACK(nats_setDefaultError(NATS_NO_MEMORY)); + } + } + + *array = values; + *arraySize = arr->size; + return NATS_OK; +} + +natsStatus +nats_JSONDupStringArrayIfDiff(nats_JSON *json, natsPool *pool, const char *fieldName, const char ***array, int *arraySize) +{ + JSON_GET_ARRAY(pool, TYPE_STR, _jsonArrayAsStringsIfDiff); +} + +// static natsStatus +// _jsonArrayAsStringPtrs(natsPool *pool, nats_JSONArray *arr, const char ***array, int *arraySize) +// { +// *array = (const char **)arr->values; +// *arraySize = arr->size; +// } + +// natsStatus +// nats_JSONGetArrayStrPtr(nats_JSON *json, const char *fieldName, char ***array, int *arraySize) +// { +// JSON_GET_ARRAY(TYPE_STR, _jsonArrayAsStringPtrs); +// } + +// static natsStatus +// _jsonArrayAsBools(natsPool *pool, nats_JSONArray *arr, bool **array, int *arraySize) +// { +// JSON_ARRAY_AS(pool, bool); +// } + +// natsStatus +// nats_JSONGetArrayBool(nats_JSON *json, const char *fieldName, bool **array, int *arraySize) +// { +// JSON_GET_ARRAY(TYPE_BOOL, _jsonArrayAsBools); +// } + +// static natsStatus +// _jsonArrayAsDoubles(natsPool *pool, nats_JSONArray *arr, long double **array, int *arraySize) +// { +// JSON_ARRAY_AS_NUM(pool, long double); +// } + +// natsStatus +// nats_JSONGetArrayDouble(nats_JSON *json, const char *fieldName, long double **array, int *arraySize) +// { +// JSON_GET_ARRAY(TYPE_NUM, _jsonArrayAsDoubles); +// } + +// static natsStatus +// _jsonArrayAsInts(natsPool *pool, nats_JSONArray *arr, int **array, int *arraySize) +// { +// JSON_ARRAY_AS_NUM(pool, int); +// } + +// natsStatus +// nats_JSONGetArrayInt(nats_JSON *json, const char *fieldName, int **array, int *arraySize) +// { +// JSON_GET_ARRAY(TYPE_NUM, _jsonArrayAsInts); +// } + +// static natsStatus +// _jsonArrayAsLongs(natsPool *pool, nats_JSONArray *arr, int64_t **array, int *arraySize) +// { +// JSON_ARRAY_AS_NUM(pool, int64_t); +// } + +// natsStatus +// nats_JSONGetArrayLong(nats_JSON *json, const char *fieldName, int64_t **array, int *arraySize) +// { +// JSON_GET_ARRAY(TYPE_NUM, _jsonArrayAsLongs); +// } + +// static natsStatus +// _jsonArrayAsULongs(natsPool *pool, nats_JSONArray *arr, uint64_t **array, int *arraySize) +// { +// JSON_ARRAY_AS_NUM(pool, uint64_t); +// } + +// natsStatus +// nats_JSONGetArrayULong(nats_JSON *json, const char *fieldName, uint64_t **array, int *arraySize) +// { +// JSON_GET_ARRAY(TYPE_NUM, _jsonArrayAsULongs); +// } + +// static natsStatus +// _jsonArrayAsObjects(natsPool *pool, nats_JSONArray *arr, nats_JSON ***array, int *arraySize) +// { +// JSON_ARRAY_AS(pool, nats_JSON *); +// } + +// natsStatus +// nats_JSONGetArrayObject(nats_JSON *json, const char *fieldName, nats_JSON ***array, int *arraySize) +// { +// JSON_GET_ARRAY(TYPE_OBJECT, _jsonArrayAsObjects); +// } + +// static natsStatus +// _jsonArrayAsArrays(natsPool *pool, nats_JSONArray *arr, nats_JSONArray ***array, int *arraySize) +// { +// JSON_ARRAY_AS(pool, nats_JSONArray *); +// } + +// natsStatus +// nats_JSONGetArrayArray(nats_JSON *json, const char *fieldName, nats_JSONArray ***array, int *arraySize) +// { +// JSON_GET_ARRAY(TYPE_ARRAY, _jsonArrayAsArrays); +// } + +natsStatus +nats_JSONRange(nats_JSON *json, int expectedType, int expectedNumType, jsonRangeCB cb, void *userInfo) +{ + natsStrHashIter iter; + char *fname = NULL; + void *val = NULL; + natsStatus s = NATS_OK; + + natsStrHashIter_Init(&iter, json->fields); + while ((STILL_OK(s)) && natsStrHashIter_Next(&iter, &fname, &val)) + { + nats_JSONField *f = (nats_JSONField *)val; + + if (f->typ != expectedType) + s = nats_setError(NATS_ERR, "field '%s': expected value type of %d, got %d", + f->name, expectedType, f->typ); + else if ((f->typ == TYPE_NUM) && (f->numTyp != expectedNumType)) + s = nats_setError(NATS_ERR, "field '%s': expected numeric type of %d, got %d", + f->name, expectedNumType, f->numTyp); + else + s = cb(userInfo, (const char *)f->name, f); + } + natsStrHashIter_Done(&iter); + return NATS_UPDATE_ERR_STACK(s); +} diff --git a/src/json_marshal.c b/src/json_marshal.c new file mode 100644 index 000000000..4cbbe1347 --- /dev/null +++ b/src/json_marshal.c @@ -0,0 +1,327 @@ +// Copyright 2015-2023 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 "json.h" +#include "conn.h" +#include "opts.h" + +natsStatus +nats_EncodeTimeUTC(char *buf, size_t bufLen, int64_t timeUTC) +{ + int64_t t = timeUTC / (int64_t)1E9; + int64_t ns = timeUTC - ((int64_t)t * (int64_t)1E9); + struct tm tp; + int n; + + // We will encode at most: "YYYY:MM:DDTHH:MM:SS.123456789+12:34" + // so we need at least 35+1 characters. + if (bufLen < 36) + return nats_setError(NATS_INVALID_ARG, + "buffer to encode UTC time is too small (%d), needs 36", + (int)bufLen); + + if (timeUTC == 0) + { + snprintf(buf, bufLen, "%s", "0001-01-01T00:00:00Z"); + return NATS_OK; + } + + memset(&tp, 0, sizeof(struct tm)); +#ifdef _WIN32 + _gmtime64_s(&tp, (const __time64_t *)&t); +#else + gmtime_r((const time_t *)&t, &tp); +#endif + n = (int)strftime(buf, bufLen, "%FT%T", &tp); + if (n == 0) + return nats_setDefaultError(NATS_ERR); + + if (ns > 0) + { + char nsBuf[15]; + int i, nd; + + nd = snprintf(nsBuf, sizeof(nsBuf), ".%" PRId64, ns); + for (; (nd > 0) && (nsBuf[nd - 1] == '0');) + nd--; + + for (i = 0; i < nd; i++) + *(buf + n++) = nsBuf[i]; + } + *(buf + n) = 'Z'; + *(buf + n + 1) = '\0'; + + return NATS_OK; +} + +static natsStatus +_marshalLongVal(natsBuf *buf, bool comma, const char *fieldName, bool l, int64_t lval, uint64_t uval) +{ + natsStatus s = NATS_OK; + char temp[32]; + const char *start = (comma ? ",\"" : "\""); + + if (l) + snprintf(temp, sizeof(temp), "%" PRId64, lval); + else + snprintf(temp, sizeof(temp), "%" PRIi64, uval); + + s = natsBuf_addCString(buf, start); + IFOK(s, natsBuf_addCString(buf, fieldName)); + IFOK(s, natsBuf_addCString(buf, "\":")); + IFOK(s, natsBuf_addCString(buf, temp)); + + return NATS_UPDATE_ERR_STACK(s); +} + +natsStatus +nats_marshalLong(natsBuf *buf, bool comma, const char *fieldName, int64_t lval) +{ + natsStatus s = _marshalLongVal(buf, comma, fieldName, true, lval, 0); + return NATS_UPDATE_ERR_STACK(s); +} + +natsStatus +nats_marshalULong(natsBuf *buf, bool comma, const char *fieldName, uint64_t uval) +{ + natsStatus s = _marshalLongVal(buf, comma, fieldName, false, 0, uval); + return NATS_UPDATE_ERR_STACK(s); +} + +// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the +// tail of buf, omitting trailing zeros. It omits the decimal +// point too when the fraction is 0. It returns the index where the +// output bytes begin and the value v/10**prec. +static void +fmt_frac(uint8_t *buf, int w, uint64_t v, int prec, int *nw, uint64_t *nv) +{ + // Omit trailing zeros up to and including decimal point. + bool print = false; + int i; + int digit; + + for (i = 0; i < prec; i++) + { + digit = v % 10; + print = print || digit != 0; + if (print) + { + w--; + buf[w] = digit + '0'; + } + v /= 10; + } + if (print) + { + w--; + buf[w] = '.'; + } + *nw = w; + *nv = v; +} + +// fmtInt formats v into the tail of buf. +// It returns the index where the output begins. +static int +fmt_int(uint8_t *buf, int w, uint64_t v) +{ + if (v == 0) + { + w--; + buf[w] = '0'; + } + else + { + while (v > 0) + { + w--; + buf[w] = v % 10 + '0'; + v /= 10; + } + } + return w; +} + +natsStatus +nats_marshalDuration(natsBuf *out_buf, bool comma, const char *field_name, int64_t d) +{ + // Largest time is 2540400h10m10.000000000s + uint8_t buf[32]; + int w = 32; + bool neg = d < 0; + uint64_t u = (uint64_t)(neg ? -d : d); + int prec; + natsStatus s = NATS_OK; + const char *start = (comma ? ",\"" : "\""); + + if (u < 1000000000) + { + // Special case: if duration is smaller than a second, + // use smaller units, like 1.2ms + w--; + buf[w] = 's'; + w--; + if (u == 0) + { + s = natsBuf_addCString(out_buf, start); + IFOK(s, natsBuf_addCString(out_buf, field_name)); + IFOK(s, natsBuf_addCString(out_buf, "\":\"0s\"")); + return NATS_UPDATE_ERR_STACK(s); + } + else if (u < 1000) + { + // print nanoseconds + prec = 0; + buf[w] = 'n'; + } + else if (u < 1000000) + { + // print microseconds + prec = 3; + // U+00B5 'µ' micro sign == 0xC2 0xB5 (in reverse?) + buf[w] = '\xB5'; + w--; // Need room for two bytes. + buf[w] = '\xC2'; + } + else + { + // print milliseconds + prec = 6; + buf[w] = 'm'; + } + fmt_frac(buf, w, u, prec, &w, &u); + w = fmt_int(buf, w, u); + } + else + { + w--; + buf[w] = 's'; + + fmt_frac(buf, w, u, 9, &w, &u); + + // u is now integer seconds + w = fmt_int(buf, w, u % 60); + u /= 60; + + // u is now integer minutes + if (u > 0) + { + w--; + buf[w] = 'm'; + w = fmt_int(buf, w, u % 60); + u /= 60; + + // u is now integer hours + // Stop at hours because days can be different lengths. + if (u > 0) + { + w--; + buf[w] = 'h'; + w = fmt_int(buf, w, u); + } + } + } + + if (neg) + { + w--; + buf[w] = '-'; + } + + s = natsBuf_addCString(out_buf, start); + IFOK(s, natsBuf_addCString(out_buf, field_name)); + IFOK(s, natsBuf_addCString(out_buf, "\":\"")); + IFOK(s, natsBuf_addBB(out_buf, buf + w, sizeof(buf) - w)); + IFOK(s, natsBuf_addCString(out_buf, "\"")); + return NATS_UPDATE_ERR_STACK(s); +} + +natsStatus nats_marshalConnect(natsString **out, natsConnection *nc, const char *user, + const char *pwd, const char *token, const char *name, + bool hdrs, bool noResponders) +{ + size_t need = 0; + size_t cap = 0; + char *buf = NULL; + + for (int i = 0; i < 2; i++) + { + if (need != 0) + { + buf = nats_palloc(nc->connectPool, need); + if (buf == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + cap = need; + } + + need = snprintf(buf, cap, + "CONNECT {\"verbose\":%s,\"pedantic\":%s,%s%s%s%s%s%s%s%s%s\"tls_required\":%s," + "\"name\":\"%s\",\"lang\":\"%s\",\"version\":\"%s\",\"protocol\":%d,\"echo\":%s," + "\"headers\":%s,\"no_responders\":%s}%s", + nats_GetBoolStr(nc->opts->proto.verbose), + nats_GetBoolStr(nc->opts->proto.pedantic), + (user != NULL ? "\"user\":\"" : ""), + (user != NULL ? user : ""), + (user != NULL ? "\"," : ""), + (pwd != NULL ? "\"pass\":\"" : ""), + (pwd != NULL ? pwd : ""), + (pwd != NULL ? "\"," : ""), + (token != NULL ? "\"auth_token\":\"" : ""), + (token != NULL ? token : ""), + (token != NULL ? "\"," : ""), + nats_GetBoolStr(nc->opts->secure.secure), + (name != NULL ? name : ""), + CLangString, NATS_VERSION_STRING, + CLIENT_PROTO_INFO, + nats_GetBoolStr(!nc->opts->proto.noEcho), + nats_GetBoolStr(hdrs), + nats_GetBoolStr(noResponders), + _CRLF_); + need++; // For '\0' + } + + *out = nats_palloc(nc->connectPool, sizeof(natsString)); + (*out)->data = (uint8_t *)buf; + (*out)->len = need - 1; + return NATS_OK; +} + +// natsStatus +// nats_marshalMetadata(natsBuf *buf, bool comma, const char *fieldName, natsMetadata md) +// { +// natsStatus s = NATS_OK; +// int i; +// const char *start = (comma ? ",\"" : "\""); + +// if (md.Count <= 0) +// return NATS_OK; + +// IFOK(s, natsBuf_addCString(buf, start)); +// IFOK(s, natsBuf_addCString(buf, fieldName)); +// IFOK(s, natsBuf_Append(buf, (const uint8_t *)"\":{", 3)); +// for (i = 0; (STILL_OK(s)) && (i < md.Count); i++) +// { +// IFOK(s, natsBuf_addB(buf, '"')); +// IFOK(s, natsBuf_addCString(buf, md.List[i * 2])); +// IFOK(s, natsBuf_Append(buf, (const uint8_t *)"\":\"", 3)); +// IFOK(s, natsBuf_addCString(buf, md.List[i * 2 + 1])); +// IFOK(s, natsBuf_addB(buf, '"')); + +// if (i != md.Count - 1) +// IFOK(s, natsBuf_addB(buf, ',')); +// } +// IFOK(s, natsBuf_addB(buf, '}')); +// return NATS_OK; +// } diff --git a/src/json_parse.c b/src/json_parse.c new file mode 100644 index 000000000..f2a906e8e --- /dev/null +++ b/src/json_parse.c @@ -0,0 +1,832 @@ +// Copyright 2015-2023 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 + +#include "hash.h" +#include "json.h" + +int jsonMaxNested = JSON_MAX_NEXTED; + +#define JSON_STATE_START (0) +#define JSON_STATE_END (1) +#define JSON_STATE_FIELDS (2) +#define JSON_STATE_ELEMENTS (3) +#define JSON_STATE_COLON (4) +#define JSON_STATE_STRING (5) +#define JSON_STATE_STRING_ESCAPE (6) +#define JSON_STATE_STRING_UTF16 (7) +#define JSON_STATE_VALUE (8) +#define JSON_STATE_VALUE_STRING (9) +#define JSON_STATE_VALUE_TRUE (10) +#define JSON_STATE_VALUE_FALSE (11) +#define JSON_STATE_VALUE_NULL (12) +#define JSON_STATE_VALUE_ARRAY (13) +#define JSON_STATE_VALUE_OBJECT (14) +#define JSON_STATE_VALUE_NUMERIC (15) + +struct __natsJSONParser_s +{ + int state; + + // The JSON object (or array) being parsed. + nats_JSON *json; + + // 1 character can be pushed back and re-processed. + uint8_t undoCh; + + // Toggles whitespace skipping. + bool skipWhitespace; + + // The current field (or array element) being parsed. + nats_JSONField *field; + + // Nested level for this parser, and a pointer to the next nested (in + // chain). + int nestedLevel; + natsJSONParser *nested; + + // Used for parsing numbers and fixed strings 'true', 'false', 'null'. + uint8_t scratchBuf[64]; + natsString scratch; + + // Used for parsing strings. nextState is set after parsing a string. + natsBuf *strBuf; + int nextState; + + // Toggle allowing a sign, dot, or 'e'/'E' when parsing a number. + bool numErrorOnSign; + bool numErrorOnDot; + bool numErrorOnE; + + // Position in the JSON string. + int line; + int pos; +}; + +static natsStatus _addValueToArray(natsJSONParser *parser); +static natsStatus _createField(nats_JSONField **newField, natsPool *pool, const uint8_t *fieldName, size_t len); +static natsStatus _createParser(natsJSONParser **newParser, natsPool *pool, bool isArray, natsJSONParser *from); +static natsStatus _finishBoolValue(natsJSONParser *parser); +static natsStatus _finishNumericValue(natsJSONParser *parser); +static natsStatus _finishNestedValue(natsJSONParser *parser, nats_JSON *obj); +static natsStatus _finishString(natsJSONParser *parser); +static natsStatus _finishValue(natsJSONParser *parser); +static void _startString(natsJSONParser *parser, int nextState); +static void _startValue(natsJSONParser *parser, int state, int typ, uint8_t firstCh); +static natsStatus _decodeUTF16(const uint8_t *data, char *val); + +static inline natsStatus _addFieldToObject(natsJSONParser *parser) +{ + return natsStrHash_Set(parser->json->fields, parser->field->name, (void *)parser->field); +} + +static inline void _resetScratch(natsJSONParser *parser) +{ + memset(parser->scratchBuf, 0, sizeof(parser->scratchBuf)); + parser->scratch.data = parser->scratchBuf; + parser->scratch.len = 0; +} + +static inline void _resetString(natsJSONParser *parser) +{ + natsBuf_Reset(parser->strBuf); + parser->nextState = 0; +} + +static inline natsStatus _addByteToScratch(natsJSONParser *parser, uint8_t ch) +{ + if (parser->scratch.len >= (sizeof(parser->scratchBuf) - 1)) + return nats_setError(NATS_ERR, "error parsing: insufficient scratch buffer, got '%s'", natsString_debugPrintable(&parser->scratch, 0)); + parser->scratchBuf[parser->scratch.len++] = ch; + return NATS_OK; +} + +static inline natsStatus _createObjectParser(natsJSONParser **newParser, natsPool *pool, natsJSONParser *from) +{ + return _createParser(newParser, pool, false, from); +} + +static inline natsStatus _createArrayParser(natsJSONParser **newParser, natsPool *pool, natsJSONParser *from) +{ + return _createParser(newParser, pool, true, from); +} + +natsStatus +natsJSONParser_Create(natsJSONParser **newParser, natsPool *pool) +{ + return _createParser(newParser, pool, false, NULL); +} + +natsStatus +natsJSONParser_Parse(nats_JSON **newJSON, natsJSONParser *parser, const uint8_t *data, const uint8_t *end, size_t *consumed) +{ + nats_JSON *json = NULL; + natsStatus s = NATS_OK; + size_t c = 0; + size_t cNested = 0; + const uint8_t *remaining = data; + +#define _jsonError(_f, ...) \ + nats_setError(NATS_ERR, "JSON parsing error line %d, pos %d: " _f, parser->line + 1, parser->pos, __VA_ARGS__) + JSONDEBUGf("Parsing JSON: '%.*s'", (int)(end - remaining), remaining); + + while ((STILL_OK(s)) && (parser->state != JSON_STATE_END)) + { + // Some states don't need to consume a character, process them first. + switch (parser->state) + { + case JSON_STATE_VALUE_OBJECT: + case JSON_STATE_VALUE_ARRAY: + json = NULL; + s = natsJSONParser_Parse(&json, parser->nested, remaining, end, &cNested); + if (s != NATS_OK) + continue; + if (json != NULL) + _finishNestedValue(parser, json); + remaining += cNested; + c += cNested; + continue; + } + + // Get the next character to process. + char ch = parser->undoCh; + if (ch == 0) + { + // If we have reached the end of the buffer, and there's no "undo" character, we are done. + if (end - remaining == 0) + { + if (consumed != NULL) + *consumed = c; + return NATS_OK; + } + ch = *remaining; + remaining++; + c++; + parser->pos++; + } + else + { + parser->undoCh = 0; + } + + if (ch == '\n') + { + parser->line++; + parser->pos = 0; + continue; + } + + if (parser->skipWhitespace && + ((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n'))) + continue; + + switch (parser->state) + { + case JSON_STATE_START: + if ((parser->json->array != NULL) && (ch == '[')) + { + parser->state = JSON_STATE_ELEMENTS; + } + else if ((parser->json->fields != NULL) && (ch == '{')) + { + parser->state = JSON_STATE_FIELDS; + } + else + { + s = _jsonError("invalid character '%c', expected '{' or '[' at the start of JSON", ch); + } + continue; // JSON_STATE_START + + case JSON_STATE_FIELDS: + switch (ch) + { + case '}': + parser->state = JSON_STATE_END; + parser->skipWhitespace = false; // Do not skip whitespace after the final '}' + continue; + case ',': + // Ignore all commas between fields, nothing to do. + continue; + case '"': + _startString(parser, JSON_STATE_COLON); + continue; + default: + s = _jsonError("invalid character '%c', expected start of a named field", ch); + continue; + } + continue; // JSON_STATE_FIELDS + + case JSON_STATE_ELEMENTS: + switch (ch) + { + case ']': + parser->state = JSON_STATE_END; + parser->skipWhitespace = false; // Do not skip whitespace after the final '}' + continue; + case ',': + parser->state = JSON_STATE_VALUE; + s = _createField(&parser->field, parser->json->pool, (uint8_t *)"array", 5); + continue; + default: + parser->undoCh = ch; + parser->state = JSON_STATE_VALUE; + s = _createField(&parser->field, parser->json->pool, (uint8_t *)"array", 5); + continue; + } + continue; // JSON_STATE_ELEMENTS + + case JSON_STATE_COLON: + switch (ch) + { + case ':': + s = _createField(&parser->field, parser->json->pool, natsBuf_data(parser->strBuf), natsBuf_len(parser->strBuf)); + parser->state = JSON_STATE_VALUE; + continue; + default: + s = _jsonError("invalid character '%c', expected a ':'", ch); + continue; + } + continue; // JSON_STATE_COLON + + case JSON_STATE_VALUE: + switch (ch) + { + case '"': + _startString(parser, JSON_STATE_VALUE_STRING); + parser->field->typ = TYPE_STR; + continue; + case 'n': + _startValue(parser, JSON_STATE_VALUE_NULL, TYPE_NULL, ch); + continue; + case 't': + _startValue(parser, JSON_STATE_VALUE_TRUE, TYPE_BOOL, ch); + continue; + case 'f': + _startValue(parser, JSON_STATE_VALUE_FALSE, TYPE_BOOL, ch); + continue; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + case '+': + case '.': + _startValue(parser, JSON_STATE_VALUE_NUMERIC, TYPE_NUM, ch); + parser->field->numTyp = ((ch == '-') || (ch == '+')) ? TYPE_INT : TYPE_UINT; + continue; + case '[': + parser->state = JSON_STATE_VALUE_ARRAY; + // Create a new parser for the nested object. It will consume + // starting with the next character. + s = _createArrayParser(&(parser->nested), parser->json->pool, parser); + continue; + case '{': + parser->state = JSON_STATE_VALUE_OBJECT; + // Create a new parser for the nested object. It will consume + // starting with the next character. + s = _createObjectParser(&(parser->nested), parser->json->pool, parser); + continue; + default: + s = _jsonError("invalid character '%c', expected a start of a value", ch); + continue; + } + continue; // JSON_STATE_VALUE + + case JSON_STATE_VALUE_NULL: + switch (ch) + { + case 'u': + case 'l': + s = _addByteToScratch(parser, ch); + if ((STILL_OK(s)) && (parser->scratch.len == sizeof("null") - 1)) + { + if (nats_strcmp(parser->scratchBuf, "null") != 0) + { + s = _jsonError("invalid string '%s', expected 'null", parser->scratch); + continue; + } + JSONDEBUGf("added field: (null) \"%s\"", parser->field->name); + s = _finishValue(parser); + } + continue; + default: + s = _jsonError("invalid character '%c', expected 'null'", ch); + continue; + } + continue; // JSON_STATE_VALUE_NULL + + case JSON_STATE_VALUE_TRUE: + switch (ch) + { + case 'r': + case 'u': + case 'e': + s = _addByteToScratch(parser, ch); + + IFOK(s, (parser->scratch.len == sizeof("true") - 1) ? _finishBoolValue(parser) : NATS_OK); + continue; + default: + s = _jsonError("invalid character '%c', expected 'true'", ch); + continue; + } + continue; // JSON_STATE_VALUE_TRUE + + case JSON_STATE_VALUE_FALSE: + switch (ch) + { + case 'a': + case 'l': + case 's': + case 'e': + s = _addByteToScratch(parser, ch); + IFOK(s, (parser->scratch.len == sizeof("false") - 1) ? _finishBoolValue(parser) : NATS_OK); + continue; + default: + s = _jsonError("invalid character '%c', expected 'false'", ch); + continue; + } + continue; // JSON_STATE_VALUE_FALSE + + case JSON_STATE_VALUE_NUMERIC: + switch (ch) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + case '+': + case '.': + case 'e': + case 'E': + if (ch == '+' || ch == '-') + { + if (parser->numErrorOnSign) + { + s = _jsonError("error parsing a number: unexpected sign after %s", parser->scratch); + continue; + } + + parser->numErrorOnSign = true; // only 1 sign allowed + } + if (ch == '.') + { + if (parser->numErrorOnDot) + { + s = _jsonError("error parsing a number: unexpected '.' after %s", parser->scratch); + continue; + } + + parser->numErrorOnDot = true; // only 1 '.' allowed + parser->field->numTyp = TYPE_DOUBLE; + } + if (ch == 'e' || ch == 'E') + { + if (parser->numErrorOnE) + { + s = _jsonError("error parsing a number: unexpected 'e' after %s", parser->scratch); + continue; + } + + parser->numErrorOnE = true; // only 1 'e' allowed + parser->numErrorOnSign = false; // allow sign in exponent + parser->field->numTyp = TYPE_DOUBLE; + } + s = _addByteToScratch(parser, ch); + continue; + + default: + // Any other character is the end of the numeric value. Return + // the character to the input stream to re-process. + parser->undoCh = ch; + s = _finishNumericValue(parser); + continue; + } + continue; // JSON_STATE_VALUE_NUMERIC + + case JSON_STATE_STRING: + switch (ch) + { + case '"': + // end of string + s = _finishString(parser); + continue; + case '\\': + parser->state = JSON_STATE_STRING_ESCAPE; + continue; + default: + s = natsBuf_addB(parser->strBuf, ch); + continue; + } + continue; // JSON_STATE_STRING + + case JSON_STATE_STRING_ESCAPE: + // Whatever character comes, the next one will not be escaped; + // except UTF16 handled separately below. + parser->state = JSON_STATE_STRING; + + switch (ch) + { + case 'b': + s = natsBuf_addB(parser->strBuf, '\b'); + continue; + case 'f': + s = natsBuf_addB(parser->strBuf, '\f'); + continue; + case 'n': + s = natsBuf_addB(parser->strBuf, '\n'); + continue; + case 'r': + s = natsBuf_addB(parser->strBuf, '\r'); + continue; + case 't': + s = natsBuf_addB(parser->strBuf, '\t'); + continue; + case 'u': + parser->state = JSON_STATE_STRING_UTF16; + memset(parser->scratchBuf, 0, sizeof(parser->scratchBuf)); + parser->scratch.len = 0; + continue; + case '"': + case '\\': + case '/': + s = natsBuf_addB(parser->strBuf, ch); + continue; + default: + s = _jsonError("error parsing string '%s': invalid control character", ch); + continue; + } + continue; // JSON_STATE_STRING_ESCAPE + + case JSON_STATE_STRING_UTF16: + if (parser->scratch.len < sizeof("ABCD")) // hex number + { + _addByteToScratch(parser, ch); + if (parser->scratch.len == sizeof("ABCD")) + { + char val = 0; + s = _decodeUTF16(parser->scratchBuf, &val); + if (s != NATS_OK) + { + s = _jsonError("error parsing string '%s': invalid unicode character", parser->scratch); + continue; + } + s = natsBuf_addB(parser->strBuf, val); + parser->state = JSON_STATE_STRING; + memset(parser->scratchBuf, 0, sizeof(parser->scratchBuf)); + parser->scratch.len = 0; + } + } + continue; // JSON_STATE_STRING_UTF16 + + default: + s = _jsonError("invalid state %d", parser->state); + break; + } + } + + if ((STILL_OK(s)) && (parser->state == JSON_STATE_END)) + { + if (consumed != NULL) + *consumed = c; + *newJSON = parser->json; + } + + return NATS_UPDATE_ERR_STACK(s); +} + +static natsStatus +_createParser(natsJSONParser **newParser, natsPool *pool, bool isArray, natsJSONParser *from) +{ + natsStatus s = NATS_OK; + natsJSONParser *parser = NULL; + nats_JSON *json = NULL; + int nestedLevel = 0; + + if (newParser == NULL) + s = nats_setDefaultError(NATS_INVALID_ARG); + + if (from != NULL) + nestedLevel = from->nestedLevel + 1; + if (nestedLevel >= jsonMaxNested) + return nats_setError(NATS_ERR, "json reached maximum nested objects of %d", jsonMaxNested); + + IFOK(s, CHECK_NO_MEMORY(parser = nats_palloc(pool, sizeof(natsJSONParser)))); + IFOK(s, CHECK_NO_MEMORY(json = nats_palloc(pool, sizeof(nats_JSON)))); + if (isArray) + { + IFOK(s, CHECK_NO_MEMORY(json->array = nats_palloc(pool, sizeof(nats_JSONArray)))); + } + else + { + IFOK(s, natsStrHash_Create(&(json->fields), pool, 4)); + } + if (s != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); + + json->pool = pool; + parser->json = json; + parser->state = (nestedLevel == 0 ? JSON_STATE_START : (isArray ? JSON_STATE_ELEMENTS : JSON_STATE_FIELDS)); + parser->skipWhitespace = true; + parser->nestedLevel = nestedLevel; + + if (nestedLevel) + { + parser->nestedLevel = nestedLevel; + + parser->undoCh = from->undoCh; + parser->line = from->line; + parser->pos = from->pos; + parser->strBuf = from->strBuf; + natsBuf_Reset(parser->strBuf); + } + else + { + IFOK(s, natsPool_getGrowableBuf(&parser->strBuf, pool, 0)); + if (s != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); + } + + *newParser = parser; + return NATS_UPDATE_ERR_STACK(s); +} + +static natsStatus +_createField(nats_JSONField **newField, natsPool *pool, const uint8_t *fieldName, size_t len) +{ + nats_JSONField *field = NULL; + + field = nats_palloc(pool, sizeof(nats_JSONField)); + if (field == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + + field->name = nats_palloc(pool, len + 1); + if (field->name == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + + memcpy(field->name, fieldName, len); + field->typ = TYPE_NOT_SET; + + *newField = field; + + return NATS_OK; +} + +static natsStatus +_decodeUTF16(const uint8_t *data, char *val) +{ + int res = 0; + int j; + + if (nats_strlen((const char *)data) < 4) + return NATS_ERR; + + for (j = 0; j < 4; j++) + { + char c = data[j]; + if ((c >= '0') && (c <= '9')) + c = c - '0'; + else if ((c >= 'a') && (c <= 'f')) + c = c - 'a' + 10; + else if ((c >= 'A') && (c <= 'F')) + c = c - 'A' + 10; + else + return NATS_ERR; + + res = (res << 4) + c; + } + *val = (char)res; + return NATS_OK; +} + +static void _startString(natsJSONParser *parser, int nextState) +{ + _resetString(parser); + parser->state = JSON_STATE_STRING; + parser->nextState = nextState; + parser->skipWhitespace = false; +} + +static void _startValue(natsJSONParser *parser, int state, int typ, uint8_t firstCh) +{ + _resetScratch(parser); + if (firstCh != 0) + _addByteToScratch(parser, firstCh); + parser->state = state; + parser->skipWhitespace = false; // true for all types except arrays + parser->field->typ = typ; + + parser->numErrorOnSign = false; + parser->numErrorOnDot = false; + parser->numErrorOnE = false; +} + +static natsStatus _finishString(natsJSONParser *parser) +{ + natsBuf_addB(parser->strBuf, '\0'); + + // TODO: <>/<> Not clean to do this here, but will suffice for now + switch (parser->nextState) + { + case JSON_STATE_VALUE_STRING: + parser->field->value.vstr = nats_pstrdupC(parser->json->pool, (char *)natsBuf_data(parser->strBuf)); + JSONDEBUGf("added field: (string) \"%s\":\"%s\"", parser->field->name, parser->field->value.vstr); + return _finishValue(parser); + + default: + parser->state = parser->nextState; + parser->skipWhitespace = true; + return NATS_OK; + } +} + +static natsStatus _finishValue(natsJSONParser *parser) +{ + natsStatus s = NATS_OK; + bool isArray = (parser->json->array != NULL); + + s = isArray ? _addValueToArray(parser) : _addFieldToObject(parser); + if (s != NATS_OK) + return s; + + parser->field = NULL; + parser->state = isArray ? JSON_STATE_ELEMENTS : JSON_STATE_FIELDS; + parser->skipWhitespace = true; + return NATS_OK; +} + +static natsStatus _finishBoolValue(natsJSONParser *parser) +{ + parser->field->typ = TYPE_BOOL; + if (nats_strcmp(parser->scratchBuf, "true") == 0) + parser->field->value.vbool = true; + else if (nats_strcmp(parser->scratchBuf, "false") == 0) + parser->field->value.vbool = false; + else + return nats_setError(NATS_ERR, "error parsing boolean '%s'", parser->scratch); + + JSONDEBUGf("added field: (bool) \"%s\":%s", parser->field->name, parser->scratchBuf); + return _finishValue(parser); +} + +static natsStatus _finishNumericValue(natsJSONParser *parser) +{ + parser->field->typ = TYPE_NUM; + // numType has been set while scanning for '+', '-', '.', and 'e'. + switch (parser->field->numTyp) + { + case TYPE_INT: + parser->field->value.vint = strtoll((const char *)parser->scratchBuf, NULL, 10); + JSONDEBUGf("added value: (int) \"%s\":%lld", parser->field->name, parser->field->value.vint); + break; + case TYPE_UINT: + parser->field->value.vuint = strtoull((const char *)parser->scratchBuf, NULL, 10); + JSONDEBUGf("added value: (uint) \"%s\":%lld", parser->field->name, parser->field->value.vuint); + break; + case TYPE_DOUBLE: + parser->field->value.vdec = strtold((const char *)parser->scratchBuf, NULL); + JSONDEBUGf("added value: (double) \"%s\":%Lf", parser->field->name, parser->field->value.vdec); + break; + } + return _finishValue(parser); +} + +static natsStatus _finishNestedValue(natsJSONParser *parser, nats_JSON *obj) +{ + switch (parser->state) + { + case JSON_STATE_VALUE_ARRAY: + if (obj->array == NULL) + return nats_setError(NATS_ERR, "%s", "unexpected error parsing array"); + if (obj->array->typ == TYPE_NOT_SET) + obj->array->typ = TYPE_NULL; + parser->field->typ = TYPE_ARRAY; + parser->field->value.varr = obj->array; + JSONDEBUGf("added array value: %d elements, type %d", obj->array->size, obj->array->typ); + break; + case JSON_STATE_VALUE_OBJECT: + if (obj->fields == NULL) + return nats_setError(NATS_ERR, "%s", "unexpected error parsing object"); + parser->field->typ = TYPE_OBJECT; + parser->field->value.vobj = obj; + JSONDEBUGf("added object value: %d fields", natsStrHash_Count(obj->fields)); + break; + default: + return nats_setError(NATS_ERR, "unexpected error parsing nested object '%s'", parser->field->name); + } + parser->nested = NULL; + return _finishValue(parser); +} + +static natsStatus +_addValueToArray(natsJSONParser *parser) +{ + nats_JSONArray *a = parser->json->array; + nats_JSONField *field = parser->field; + int valueType = field->typ; + + if (a->typ == TYPE_NOT_SET) + a->typ = valueType; + if (a->typ != valueType) + return nats_setError(NATS_ERR, "array content of different types '%s'", field->name); + + switch (a->typ) + { + case TYPE_STR: + a->eltSize = sizeof(char *); + break; + case TYPE_BOOL: + a->eltSize = sizeof(bool); + break; + case TYPE_NUM: + a->eltSize = JSON_MAX_NUM_SIZE; + break; + case TYPE_OBJECT: + a->eltSize = sizeof(nats_JSON *); + break; + case TYPE_ARRAY: + a->eltSize = sizeof(nats_JSONArray *); + break; + default: + return _jsonError("array of type %d not supported", a->typ); + } + + if (a->size + 1 > a->cap) + { + void **newValues = NULL; + size_t newCap = a->cap ? 2 * a->cap : 8; + newValues = nats_palloc(parser->json->pool, newCap * a->eltSize); + if (newValues == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + + memcpy(newValues, a->values, a->size * a->eltSize); + a->values = newValues; + a->cap = newCap; + } + // Set value based on type + switch (a->typ) + { + case TYPE_STR: + ((char **)a->values)[a->size++] = field->value.vstr; + break; + case TYPE_BOOL: + ((bool *)a->values)[a->size++] = field->value.vbool; + break; + case TYPE_NUM: + { + void *numPtr = NULL; + size_t sz = 0; + + switch (field->numTyp) + { + case TYPE_INT: + numPtr = &(field->value.vint); + sz = sizeof(int64_t); + break; + case TYPE_UINT: + numPtr = &(field->value.vuint); + sz = sizeof(uint64_t); + break; + default: + numPtr = &(field->value.vdec); + sz = sizeof(long double); + } + memcpy((void *)(((char *)a->values) + (a->size * a->eltSize)), numPtr, sz); + a->size++; + break; + } + case TYPE_OBJECT: + ((nats_JSON **)a->values)[a->size++] = field->value.vobj; + break; + case TYPE_ARRAY: + ((nats_JSONArray **)a->values)[a->size++] = field->value.varr; + break; + } + + return NATS_OK; +} diff --git a/src/json_unmarshal.c b/src/json_unmarshal.c new file mode 100644 index 000000000..7e924d8ab --- /dev/null +++ b/src/json_unmarshal.c @@ -0,0 +1,120 @@ +// Copyright 2015-2023 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 "json.h" +#include "conn.h" + +natsStatus +nats_unmarshalServerInfo(nats_JSON *json, natsPool *pool, natsServerInfo *info) +{ + natsStatus s = NATS_OK; + IFOK(s, nats_JSONDupStrIfDiff(json, pool, "server_id", &info->id)); + IFOK(s, nats_JSONDupStrIfDiff(json, pool, "version", &(info->version))); + IFOK(s, nats_JSONDupStrIfDiff(json, pool, "host", &(info->host))); + IFOK(s, nats_JSONGetInt(json, "port", &(info->port))); + IFOK(s, nats_JSONGetBool(json, "auth_required", &(info->authRequired))); + IFOK(s, nats_JSONGetBool(json, "tls_required", &(info->tlsRequired))); + IFOK(s, nats_JSONGetBool(json, "tls_available", &(info->tlsAvailable))); + IFOK(s, nats_JSONGetLong(json, "max_payload", &(info->maxPayload))); + IFOK(s, nats_JSONDupStringArrayIfDiff(json, pool, "connect_urls", + &(info->connectURLs), + &(info->connectURLsCount))); + IFOK(s, nats_JSONGetInt(json, "proto", &(info->proto))); + IFOK(s, nats_JSONGetULong(json, "client_id", &(info->CID))); + IFOK(s, nats_JSONDupStrIfDiff(json, pool, "nonce", &(info->nonce))); + IFOK(s, nats_JSONDupStrIfDiff(json, pool, "client_ip", &(info->clientIP))); + IFOK(s, nats_JSONGetBool(json, "ldm", &(info->lameDuckMode))); + IFOK(s, nats_JSONGetBool(json, "headers", &(info->headers))); + + return NATS_UPDATE_ERR_STACK(s); +} +// static natsStatus +// _addMD(void *closure, const char *fieldName, nats_JSONField *f) +// { +// natsMetadata *md = (natsMetadata *)closure; + +// char *name = NATS_STRDUP(fieldName); +// char *value = NATS_STRDUP(f->value.vstr); +// if ((name == NULL) || (value == NULL)) +// { +// NATS_FREE(name); +// NATS_FREE(value); +// return nats_setDefaultError(NATS_NO_MEMORY); +// } + +// md->List[md->Count * 2] = name; +// md->List[md->Count * 2 + 1] = value; +// md->Count++; +// return NATS_OK; +// } + +// natsStatus +// nats_unmarshalMetadata(nats_JSON *json, natsPool *pool, const char *fieldName, natsMetadata *md) +// { +// natsStatus s = NATS_OK; +// nats_JSON *mdJSON = NULL; +// int n; + +// md->List = NULL; +// md->Count = 0; +// if (json == NULL) +// return NATS_OK; + +// s = nats_JSONGetObject(json, fieldName, &mdJSON); +// if ((s != NATS_OK) || (mdJSON == NULL)) +// return NATS_OK; + +// n = natsStrHash_Count(mdJSON->fields); +// md->List = NATS_CALLOC(n * 2, sizeof(char *)); +// if (md->List == NULL) +// s = nats_setDefaultError(NATS_NO_MEMORY); +// IFOK(s, nats_JSONRange(mdJSON, TYPE_STR, 0, _addMD, md)); + +// return s; +// } + +// natsStatus +// nats_cloneMetadata(natsPool *pool, natsMetadata *clone, natsMetadata md) +// { +// natsStatus s = NATS_OK; +// int i = 0; +// int n; +// char **list = NULL; + +// clone->Count = 0; +// clone->List = NULL; +// if (md.Count == 0) +// return NATS_OK; + +// n = md.Count * 2; +// list = natsPool_alloc(pool, n * sizeof(char *)); +// if (list == NULL) +// s = nats_setDefaultError(NATS_NO_MEMORY); + +// for (i = 0; (STILL_OK(s)) && (i < n); i++) +// { +// list[i] = nats_StrdupPool(pool, md.List[i]); +// if (list[i] == NULL) +// s = nats_setDefaultError(NATS_NO_MEMORY); +// } + +// if (STILL_OK(s)) +// { +// clone->List = (const char **)list; +// clone->Count = md.Count; +// } + +// return s; +// } diff --git a/src/kv.c b/src/kv.c deleted file mode 100644 index 81de4ce56..000000000 --- a/src/kv.c +++ /dev/null @@ -1,1520 +0,0 @@ -// 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 - -#include "natsp.h" -#include "kv.h" -#include "mem.h" -#include "util.h" -#include "js.h" -#include "conn.h" -#include "sub.h" - -static const char *kvBucketNamePre = "KV_"; -static const char *kvBucketNameTmpl = "KV_%s"; -static const char *kvSubjectsTmpl = "$KV.%s.>"; -static const char *kvSubjectsPreTmpl = "$KV.%s."; -static const char *kvSubjectsPreDomainTmpl = "%s.$KV.%s."; - -#define KV_WATCH_FOR_EVER (int64_t)(0x7FFFFFFFFFFFFFFF) - -#define DEFINE_BUF_FOR_SUBJECT \ -char buffer[128]; \ -natsBuffer buf; - -#define USE_JS_PREFIX true -#define KEY_NAME_ONLY false - -#define FOR_A_PUT true -#define NOT_FOR_A_PUT false - -#define BUILD_SUBJECT(p, fp) \ -s = natsBuf_InitWithBackend(&buf, buffer, 0, sizeof(buffer)); \ -if ((p) && kv->useJSPrefix) \ -{ \ - IFOK(s, natsBuf_Append(&buf, kv->js->opts.Prefix, -1)); \ - IFOK(s, natsBuf_AppendByte(&buf, '.')); \ -} \ -IFOK(s, natsBuf_Append(&buf, ((fp) ? (kv->usePutPre ? kv->putPre : kv->pre) : kv->pre), -1)); \ -IFOK(s, natsBuf_Append(&buf, key, -1)); \ -IFOK(s, natsBuf_AppendByte(&buf, 0)); - -#define KV_DEFINE_LIST \ -kvEntry *e = NULL; \ -kvEntry *h = NULL; \ -kvEntry *t = NULL; \ -int n = 0; \ -int64_t timeout = KV_WATCH_FOR_EVER;\ -int64_t start; \ -int i; - -#define KV_GATHER_LIST \ -start = nats_Now(); \ -while (s == NATS_OK) \ -{ \ - s = kvWatcher_Next(&e, w, timeout); \ - if (s == NATS_OK) \ - { \ - if (e == NULL) \ - break; \ - if (t != NULL) \ - t->next = e; \ - else \ - h = e; \ - t = e; \ - n++; \ - timeout -= (nats_Now() - start); \ - if (timeout <= 0) \ - s = nats_setDefaultError(NATS_TIMEOUT); \ - } \ -} - -////////////////////////////////////////////////////////////////////////////// -// kvStore management APIs -////////////////////////////////////////////////////////////////////////////// - -natsStatus -kvConfig_Init(kvConfig *cfg) -{ - if (cfg == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - memset(cfg, 0, sizeof(kvConfig)); - return NATS_OK; -} - -static bool -validBucketName(const char *bucket) -{ - int i; - char c; - - if (nats_IsStringEmpty(bucket)) - return false; - - for (i=0; i<(int)strlen(bucket); i++) - { - c = bucket[i]; - if ((isalnum((unsigned char) c) == 0) && (c != '_') && (c != '-')) - return false; - } - return true; -} - -static void -_freeKV(kvStore *kv) -{ - jsCtx *js = NULL; - - if (kv == NULL) - return; - - js = kv->js; - NATS_FREE(kv->bucket); - NATS_FREE(kv->stream); - NATS_FREE(kv->pre); - NATS_FREE(kv->putPre); - natsMutex_Destroy(kv->mu); - NATS_FREE(kv); - js_release(js); -} - -static void -_retainKV(kvStore *kv) -{ - natsMutex_Lock(kv->mu); - kv->refs++; - natsMutex_Unlock(kv->mu); -} - -static void -_releaseKV(kvStore *kv) -{ - bool doFree; - - if (kv == NULL) - return; - - natsMutex_Lock(kv->mu); - doFree = (--(kv->refs) == 0); - natsMutex_Unlock(kv->mu); - - if (doFree) - _freeKV(kv); -} - -void -kvStore_Destroy(kvStore *kv) -{ - _releaseKV(kv); -} - -static natsStatus -_createKV(kvStore **new_kv, jsCtx *js, const char *bucket) -{ - natsStatus s = NATS_OK; - kvStore *kv = NULL; - - if (!validBucketName(bucket)) - return nats_setError(NATS_INVALID_ARG, "%s", kvErrInvalidBucketName); - - kv = (kvStore*) NATS_CALLOC(1, sizeof(kvStore)); - if (kv == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - kv->refs = 1; - s = natsMutex_Create(&(kv->mu)); - IF_OK_DUP_STRING(s, kv->bucket, bucket); - if ((s == NATS_OK) && (nats_asprintf(&(kv->stream), kvBucketNameTmpl, bucket) < 0)) - s = nats_setDefaultError(NATS_NO_MEMORY); - if ((s == NATS_OK) && (nats_asprintf(&(kv->pre), kvSubjectsPreTmpl, bucket) < 0)) - s = nats_setDefaultError(NATS_NO_MEMORY); - - if (s == NATS_OK) - { - kv->useJSPrefix = (strcmp(js->opts.Prefix, jsDefaultAPIPrefix) != 0 ? true : false); - kv->js = js; - js_retain(js); - *new_kv = kv; - } - else - _freeKV(kv); - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_changePutPrefixIfMirrorPresent(kvStore *kv, jsStreamInfo *si) -{ - natsStatus s = NATS_OK; - const char *bucket = NULL; - jsStreamSource *m = si->Config->Mirror; - - if (m == NULL) - return NATS_OK; - - bucket = m->Name; - if (strstr(m->Name, kvBucketNamePre) == m->Name) - bucket = m->Name + strlen(kvBucketNamePre); - - if ((m->External != NULL) && !nats_IsStringEmpty(m->External->APIPrefix)) - { - kv->useJSPrefix = false; - - NATS_FREE(kv->pre); - kv->pre = NULL; - if (nats_asprintf(&(kv->pre), kvSubjectsPreTmpl, bucket) < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - else if (nats_asprintf(&(kv->putPre), kvSubjectsPreDomainTmpl, m->External->APIPrefix, bucket) < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - else if (nats_asprintf(&(kv->putPre), kvSubjectsPreTmpl, bucket) < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - - if (s == NATS_OK) - kv->usePutPre = true; - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -js_CreateKeyValue(kvStore **new_kv, jsCtx *js, kvConfig *cfg) -{ - natsStatus s; - int64_t history = 1; - int64_t replicas= 1; - kvStore *kv = NULL; - char *subject= NULL; - jsStreamInfo *si = NULL; - const char *omn = NULL; - const char **osn = NULL; - jsStreamConfig sc; - - if ((new_kv == NULL) || (js == NULL) || (cfg == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - s = _createKV(&kv, js, cfg->Bucket); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - if (cfg->History > 0) - { - if (cfg->History > kvMaxHistory) - s = nats_setError(NATS_INVALID_ARG, "%s %d", kvErrHistoryTooLarge, kvMaxHistory); - else - history = (int64_t) cfg->History; - } - if (s == NATS_OK) - { - if (cfg->Replicas > 0) - replicas = cfg->Replicas; - - if (nats_asprintf(&subject, kvSubjectsTmpl, kv->bucket) < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - if (s == NATS_OK) - { - int64_t maxBytes = (cfg->MaxBytes == 0 ? -1 : cfg->MaxBytes); - int32_t maxMsgSize = (cfg->MaxValueSize == 0 ? -1 : cfg->MaxValueSize); - jsErrCode jerr = 0; - const char **subjects = (const char*[1]){subject}; - - jsStreamConfig_Init(&sc); - sc.Name = kv->stream; - sc.Description = cfg->Description; - sc.MaxMsgsPerSubject = history; - sc.MaxBytes = maxBytes; - sc.MaxAge = cfg->TTL; - sc.MaxMsgSize = maxMsgSize; - sc.Storage = cfg->StorageType; - sc.Replicas = replicas; - sc.AllowRollup = true; - sc.DenyDelete = true; - sc.AllowDirect = true; - sc.RePublish = cfg->RePublish; - - if (cfg->Mirror != NULL) - { - jsStreamSource *m = cfg->Mirror; - - if (!nats_IsStringEmpty(m->Name) - && (strstr(m->Name, kvBucketNamePre) != m->Name)) - { - char *newName = NULL; - if (nats_asprintf(&newName, kvBucketNameTmpl, m->Name) < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - { - omn = m->Name; - m->Name = newName; - } - } - sc.Mirror = m; - sc.MirrorDirect = true; - } - else if (cfg->SourcesLen > 0) - { - osn = (const char**) NATS_CALLOC(cfg->SourcesLen, sizeof(char*)); - if (osn == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - - if (s == NATS_OK) - { - int i; - - for (i=0; iSourcesLen; i++) - { - jsStreamSource *ss = cfg->Sources[i]; - - if (ss == NULL) - continue; - - // Set this regardless of error in the loop. We need it for - // proper cleanup at the end. - osn[i] = ss->Name; - - if ((s == NATS_OK) && !nats_IsStringEmpty(ss->Name) - && (strstr(ss->Name, kvBucketNamePre) != ss->Name)) - { - char *newName = NULL; - - if (nats_asprintf(&newName, kvBucketNameTmpl, ss->Name) < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - ss->Name = newName; - } - } - if (s == NATS_OK) - { - sc.Sources = cfg->Sources; - sc.SourcesLen = cfg->SourcesLen; - } - } - } - else - { - sc.Subjects = subjects; - sc.SubjectsLen = 1; - } - - // If connecting to a v2.7.2+, create with discard new policy - if (natsConn_srvVersionAtLeast(kv->js->nc, 2, 7, 2)) - sc.Discard = js_DiscardNew; - - s = js_AddStream(&si, js, &sc, NULL, &jerr); - if (s == NATS_OK) - { - // If the stream allow direct get message calls, then we will do so. - kv->useDirect = si->Config->AllowDirect; - - s = _changePutPrefixIfMirrorPresent(kv, si); - } - jsStreamInfo_Destroy(si); - - // Restore original mirror/source names - if (omn != NULL) - { - NATS_FREE((char*) cfg->Mirror->Name); - cfg->Mirror->Name = omn; - } - if (osn != NULL) - { - int i; - - for (i=0; iSourcesLen; i++) - { - jsStreamSource *ss = cfg->Sources[i]; - - if ((ss != NULL) && (ss->Name != osn[i])) - { - NATS_FREE((char*) ss->Name); - ss->Name = osn[i]; - } - } - NATS_FREE((char**) osn); - } - } - if (s == NATS_OK) - *new_kv = kv; - else - _freeKV(kv); - - NATS_FREE(subject); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -js_KeyValue(kvStore **new_kv, jsCtx *js, const char *bucket) -{ - natsStatus s; - kvStore *kv = NULL; - jsStreamInfo *si = NULL; - - if ((new_kv == NULL) || (js == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - s = _createKV(&kv, js, bucket); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - s = js_GetStreamInfo(&si, js, kv->stream, NULL, NULL); - if (s == NATS_OK) - { - // If the stream allow direct get message calls, then we will do so. - kv->useDirect = si->Config->AllowDirect; - - // Do some quick sanity checks that this is a correctly formed stream for KV. - // Max msgs per subject should be > 0. - if (si->Config->MaxMsgsPerSubject < 1) - s = nats_setError(NATS_INVALID_ARG, "%s", kvErrBadBucket); - - IFOK(s, _changePutPrefixIfMirrorPresent(kv, si)); - - jsStreamInfo_Destroy(si); - } - - if (s == NATS_OK) - *new_kv = kv; - else - { - _freeKV(kv); - if (s == NATS_NOT_FOUND) - return s; - } - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -js_DeleteKeyValue(jsCtx *js, const char *bucket) -{ - natsStatus s; - char *stream = NULL; - - if (js == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - if (!validBucketName(bucket)) - return nats_setError(NATS_INVALID_ARG, "%s", kvErrBadBucket); - - if (nats_asprintf(&stream, kvBucketNameTmpl, bucket) < 0) - return nats_setDefaultError(NATS_NO_MEMORY); - - s = js_DeleteStream(js, (const char*) stream, NULL, NULL); - - NATS_FREE(stream); - - return NATS_UPDATE_ERR_STACK(s); -} - -////////////////////////////////////////////////////////////////////////////// -// kvStore APIs -////////////////////////////////////////////////////////////////////////////// - -static bool -validKey(const char *key) -{ - int i; - char c; - int last; - - if (nats_IsStringEmpty(key)) - return false; - - last = (int) strlen(key); - for (i=0; ikv = kv; - e->msg = *msg; - e->key = e->msg->subject+(int)strlen(kv->pre); - e->op = kvOp_Put; - - // Indicate that we took ownership of the message - *msg = NULL; - *new_entry = e; - - return NATS_OK; -} - -static kvOperation -_getKVOp(natsMsg *msg) -{ - const char *val = NULL; - kvOperation op = kvOp_Put; - - if (natsMsgHeader_Get(msg, kvOpHeader, &val) == NATS_OK) - { - if (strcmp(val, kvOpDeleteStr) == 0) - op = kvOp_Delete; - else if (strcmp(val, kvOpPurgeStr) == 0) - op = kvOp_Purge; - } - return op; -} - -static natsStatus -_getEntry(kvEntry **new_entry, bool *deleted, kvStore *kv, const char *key, uint64_t revision) -{ - natsStatus s = NATS_OK; - natsMsg *msg = NULL; - kvEntry *e = NULL; - DEFINE_BUF_FOR_SUBJECT; - jsDirectGetMsgOptions dgo; - - *new_entry = NULL; - *deleted = false; - - if (!validKey(key)) - return nats_setError(NATS_INVALID_ARG, "%s", kvErrInvalidKey); - - BUILD_SUBJECT(KEY_NAME_ONLY, NOT_FOR_A_PUT); - - if (kv->useDirect) - { - jsDirectGetMsgOptions_Init(&dgo); - if (revision == 0) - dgo.LastBySubject = natsBuf_Data(&buf); - else - dgo.Sequence = revision; - - IFOK(s, js_DirectGetMsg(&msg, kv->js, kv->stream, NULL, &dgo)); - } - else if (revision == 0) - { - IFOK(s, js_GetLastMsg(&msg, kv->js, kv->stream, natsBuf_Data(&buf), NULL, NULL)); - } - else - { - IFOK(s, js_GetMsg(&msg, kv->js, kv->stream, revision, NULL, NULL)); - } - // If a sequence was provided, just make sure that the retrieved - // message subject matches the request. - if (revision != 0) - IFOK(s, (strcmp(natsMsg_GetSubject(msg), natsBuf_Data(&buf)) == 0 ? NATS_OK : NATS_NOT_FOUND)); - IFOK(s, _createEntry(&e, kv, &msg)); - if (s == NATS_OK) - e->op = _getKVOp(e->msg); - - natsBuf_Cleanup(&buf); - natsMsg_Destroy(msg); - - if (s == NATS_OK) - { - if ((e->op == kvOp_Delete) || (e->op == kvOp_Purge)) - *deleted = true; - *new_entry = e; - } - else - { - kvEntry_Destroy(e); - - if (s == NATS_NOT_FOUND) - { - nats_clearLastError(); - return s; - } - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -_get(kvEntry **new_entry, kvStore *kv, const char *key, uint64_t revision) -{ - natsStatus s; - bool deleted = false; - - if ((new_entry == NULL) || (kv == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - s = _getEntry(new_entry, &deleted, kv, key, revision); - if (s == NATS_OK) - { - if (deleted) - { - kvEntry_Destroy(*new_entry); - *new_entry = NULL; - return NATS_NOT_FOUND; - } - } - else if (s == NATS_NOT_FOUND) - return s; - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -kvStore_Get(kvEntry **new_entry, kvStore *kv, const char *key) -{ - natsStatus s = _get(new_entry, kv, key, 0); - // We don't want stack trace for this error - if (s == NATS_NOT_FOUND) - return s; - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -kvStore_GetRevision(kvEntry **new_entry, kvStore *kv, const char *key, uint64_t revision) -{ - natsStatus s; - - if (revision <= 0) - return nats_setError(NATS_INVALID_ARG, "%s", kvErrInvalidRevision); - - s = _get(new_entry, kv, key, revision); - // We don't want stack trace for this error - if (s == NATS_NOT_FOUND) - return s; - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_putEntry(uint64_t *rev, kvStore *kv, jsPubOptions *po, const char *key, const void *data, int len) -{ - natsStatus s = NATS_OK; - jsPubAck *pa = NULL; - jsPubAck **ppa = NULL; - DEFINE_BUF_FOR_SUBJECT; - - if (rev != NULL) - { - *rev = 0; - ppa = &pa; - } - - if (kv == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - if (!validKey(key)) - return nats_setError(NATS_INVALID_ARG, "%s", kvErrInvalidKey); - - BUILD_SUBJECT(USE_JS_PREFIX, FOR_A_PUT); - IFOK(s, js_Publish(ppa, kv->js, natsBuf_Data(&buf), data, len, po, NULL)); - - if ((s == NATS_OK) && (rev != NULL)) - *rev = pa->Sequence; - - natsBuf_Cleanup(&buf); - jsPubAck_Destroy(pa); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -kvStore_Put(uint64_t *rev, kvStore *kv, const char *key, const void *data, int len) -{ - natsStatus s; - - s = _putEntry(rev, kv, NULL, key, data, len); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -kvStore_PutString(uint64_t *rev, kvStore *kv, const char *key, const char *data) -{ - natsStatus s; - int l = (data == NULL ? 0 : (int) strlen(data)); - - s = kvStore_Put(rev, kv, key, (const void*) data, l); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -kvStore_Create(uint64_t *rev, kvStore *kv, const char *key, const void *data, int len) -{ - natsStatus s; - natsStatus ls; - kvEntry *e = NULL; - bool deleted = false; - - if (kv == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - s = kvStore_Update(rev, kv, key, data, len, 0); - if (s == NATS_OK) - return s; - - // Since we have tombstones for DEL ops for watchers, this could be from that - // so we need to double check. - ls = _getEntry(&e, &deleted, kv, key, 0); - if (ls == NATS_OK) - { - if (deleted) - s = kvStore_Update(rev, kv, key, data, len, kvEntry_Revision(e)); - - kvEntry_Destroy(e); - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -kvStore_CreateString(uint64_t *rev, kvStore *kv, const char *key, const char *data) -{ - natsStatus s = kvStore_Create(rev, kv, key, (const void*) data, (int) strlen(data)); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -kvStore_Update(uint64_t *rev, kvStore *kv, const char *key, const void *data, int len, uint64_t last) -{ - natsStatus s; - jsPubOptions po; - - jsPubOptions_Init(&po); - if (last == 0) - po.ExpectNoMessage = true; - else - po.ExpectLastSubjectSeq = last; - s = _putEntry(rev, kv, &po, key, data, len); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -kvStore_UpdateString(uint64_t *rev, kvStore *kv, const char *key, const char *data, uint64_t last) -{ - natsStatus s = kvStore_Update(rev, kv, key, (const void*) data, (int) strlen(data), last); - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_delete(kvStore *kv, const char *key, bool purge, kvPurgeOptions *opts) -{ - natsStatus s; - natsMsg *msg = NULL; - jsPubOptions o; - jsPubOptions *po = NULL; - DEFINE_BUF_FOR_SUBJECT; - - if (kv == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - if (!validKey(key)) - return nats_setError(NATS_INVALID_ARG, "%s", kvErrInvalidKey); - - BUILD_SUBJECT(USE_JS_PREFIX, FOR_A_PUT); - IFOK(s, natsMsg_Create(&msg, natsBuf_Data(&buf), NULL, NULL, 0)); - if (s == NATS_OK) - { - if (purge) - { - s = natsMsgHeader_Set(msg, kvOpHeader, kvOpPurgeStr); - IFOK(s, natsMsgHeader_Set(msg, JSMsgRollup, JSMsgRollupSubject)); - } - else - { - s = natsMsgHeader_Set(msg, kvOpHeader, kvOpDeleteStr); - } - } - if (purge && (opts != NULL) && (opts->Timeout > 0)) - { - jsPubOptions_Init(&o); - o.MaxWait = opts->Timeout; - po = &o; - } - IFOK(s, js_PublishMsg(NULL, kv->js, msg, po, NULL)); - - natsBuf_Cleanup(&buf); - natsMsg_Destroy(msg); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -kvStore_Delete(kvStore *kv, const char *key) -{ - natsStatus s = _delete(kv, key, false, NULL); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -kvStore_Purge(kvStore *kv, const char *key, kvPurgeOptions *opts) -{ - natsStatus s = _delete(kv, key, true, opts); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -kvPurgeOptions_Init(kvPurgeOptions *opts) -{ - if (opts == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - memset(opts, 0, sizeof(kvPurgeOptions)); - return NATS_OK; -} - -natsStatus -kvStore_PurgeDeletes(kvStore *kv, kvPurgeOptions *opts) -{ - natsStatus s; - kvWatcher *w = NULL; - kvEntry *e = NULL; - kvEntry *h = NULL; - kvEntry *t = NULL; - natsBuffer buf; - char buffer[128]; - kvWatchOptions wo; - kvWatchOptions *wpo = NULL; - - if ((opts != NULL) && (opts->Timeout > 0)) - { - kvWatchOptions_Init(&wo); - wo.Timeout = opts->Timeout; - wpo = &wo; - } - s = kvStore_WatchAll(&w, kv, wpo); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - while (s == NATS_OK) - { - s = kvWatcher_Next(&e, w, KV_WATCH_FOR_EVER); - if (s == NATS_OK) - { - if (e == NULL) - break; - if ((e->op == kvOp_Delete) || (e->op == kvOp_Purge)) - { - if (t != NULL) - t->next = e; - else - h = e; - t = e; - } - } - } - if ((s == NATS_OK) && (h != NULL)) - { - jsOptions po; - int64_t olderThan = (opts != NULL ? opts->DeleteMarkersOlderThan : 0); - int64_t limit = 0; - - // Negative value is used to instruct to always remove markers, regardless - // of age. If set to 0 (or not set), use our default value. - if (olderThan == 0) - olderThan = NATS_SECONDS_TO_NANOS(30*60); // 30 minutes - else if (olderThan > 0) - limit = nats_NowInNanoSeconds() - olderThan; - - jsOptions_Init(&po); - - natsBuf_InitWithBackend(&buf, buffer, 0, sizeof(buffer)); - - // Go over the list, even when s != NATS_OK so we destroy - // each entry and don't have a memory leak. - for (; h != NULL; ) - { - natsBuf_Reset(&buf); - // Use kv->pre here, always. - IFOK(s, natsBuf_Append(&buf, kv->pre, -1)); - IFOK(s, natsBuf_Append(&buf, h->key, -1)); - IFOK(s, natsBuf_AppendByte(&buf, '\0')); - if (s == NATS_OK) - { - po.Stream.Purge.Subject = (const char*) natsBuf_Data(&buf); - po.Stream.Purge.Keep = 0; - if ((olderThan > 0) && (kvEntry_Created(h) >= limit)) - { - // Keep this marker since it is more recent than the threshold. - po.Stream.Purge.Keep = 1; - } - s = js_PurgeStream(kv->js, kv->stream, &po, NULL); - } - e = h; - h = h->next; - kvEntry_Destroy(e); - } - } - kvWatcher_Destroy(w); - return NATS_UPDATE_ERR_STACK(s); -} - -static void -_freeWatcher(kvWatcher *w) -{ - kvStore *kv = NULL; - - natsSubscription_Destroy(w->sub); - natsMutex_Destroy(w->mu); - kv = w->kv; - NATS_FREE(w); - _releaseKV(kv); -} - -static void -_releaseWatcher(kvWatcher *w) -{ - bool doFree; - - if (w == NULL) - return; - - natsMutex_Lock(w->mu); - doFree = (--(w->refs) == 0); - natsMutex_Unlock(w->mu); - - if (doFree) - _freeWatcher(w); -} - -natsStatus -kvWatchOptions_Init(kvWatchOptions *opts) -{ - if (opts == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - memset(opts, 0, sizeof(kvWatchOptions)); - return NATS_OK; -} - -natsStatus -kvWatcher_Next(kvEntry **new_entry, kvWatcher *w, int64_t timeout) -{ - natsStatus s = NATS_OK; - kvEntry *e = NULL; - int64_t start; - - if ((new_entry == NULL) || (w == NULL) || (timeout <= 0)) - return nats_setDefaultError(NATS_INVALID_ARG); - - *new_entry = NULL; - - natsMutex_Lock(w->mu); - start = nats_Now(); -GET_NEXT: - if (w->stopped) - { - s = nats_setDefaultError(NATS_ILLEGAL_STATE); - } - else if (w->retMarker) - { - // Will return a NULL entry (*new_entry is initialized to NULL). - // Mark that we should no longer check/return the "init done" marker. - w->retMarker = false; - } - else - { - natsMsg *msg = NULL; - uint64_t delta= 0; - bool next = false; - - w->refs++; - natsMutex_Unlock(w->mu); - - s = natsSubscription_NextMsg(&msg, w->sub, timeout); - - natsMutex_Lock(w->mu); - if (w->stopped) - { - natsMutex_Unlock(w->mu); - _releaseWatcher(w); - return NATS_ILLEGAL_STATE; - } - w->refs--; - - // Use kv->pre here, always. - if ((s == NATS_OK) && (strlen(msg->subject) <= strlen(w->kv->pre))) - s = nats_setError(NATS_ERR, "invalid update's subject '%s'", msg->subject); - - if ((s == NATS_OK) && ((nats_IsStringEmpty(msg->reply) || - ((int) strlen(msg->reply) <= jsAckPrefixLen)))) - { - s = nats_setError(NATS_ERR, "unable to get metadata from '%s'", msg->reply); - } - IFOK(s, js_getMetaData(msg->reply+jsAckPrefixLen, - NULL, NULL, NULL, NULL, &(msg->seq), - NULL, &(msg->time), &delta, 3)); - if (s == NATS_OK) - { - kvOperation op = _getKVOp(msg); - - if (!w->ignoreDel || (op != kvOp_Delete && op != kvOp_Purge)) - { - s = _createEntry(&e, w->kv, &msg); - if (s == NATS_OK) - { - e->op = op; - e->delta = delta; - } - } - else - { - timeout -= (nats_Now() - start); - if (timeout > 0) - next = true; - else - s = nats_setDefaultError(NATS_TIMEOUT); - } - // Here, regardless of status, need to update this - if (!w->initDone) - { - w->received++; - // We set this on the first trip through.. - if (w->initPending == 0) - w->initPending = delta; - if ((w->received > w->initPending) || (delta == 0)) - { - w->initDone = true; - w->retMarker = true; - } - } - } - // The `msg` variable may be NULL if an entry was created - // and took ownership. It is ok since then destroy will be a no-op. - natsMsg_Destroy(msg); - - if (next) - goto GET_NEXT; - } - natsMutex_Unlock(w->mu); - - if (s == NATS_OK) - *new_entry = e; - - return NATS_UPDATE_ERR_STACK(s); -} - -void -kvWatcher_Destroy(kvWatcher *w) -{ - kvWatcher_Stop(w); - _releaseWatcher(w); -} - -natsStatus -kvStore_Watch(kvWatcher **new_watcher, kvStore *kv, const char *key, kvWatchOptions *opts) -{ - const char *subjects = { key }; - return kvStore_WatchMulti(new_watcher, kv, &subjects, 1, opts); -} - -natsStatus -kvStore_WatchMulti(kvWatcher **new_watcher, kvStore *kv, const char **keys, int numKeys, kvWatchOptions *opts) -{ - natsStatus s; - kvWatcher *w = NULL; - jsSubOptions so; - char *singleSubject[1]; - char **multipleSubjects = NULL; // allocate if numKeys > 1 - char **subscribeSubjects = singleSubject; - int i; - DEFINE_BUF_FOR_SUBJECT; - - if ((new_watcher == NULL) || (kv == NULL) || numKeys <= 0) - return nats_setDefaultError(NATS_INVALID_ARG); - for (i=0; ikv = kv; - w->refs = 1; - - if (numKeys == 1) - { - // special case for single key to avoid a calloc. - subscribeSubjects[0] = (char *)keys[0]; - - } - else - { - multipleSubjects = (char **)NATS_CALLOC(numKeys, sizeof(const char *)); - if (multipleSubjects == NULL) - { - _freeWatcher(w); - return nats_setDefaultError(NATS_NO_MEMORY); - } - subscribeSubjects = multipleSubjects; - } - for (i = 0; i < numKeys; i++) - { - const char *key = keys[i]; - BUILD_SUBJECT(KEY_NAME_ONLY, NOT_FOR_A_PUT); // into buf, '\0'-terminated. - subscribeSubjects[i] = NATS_STRDUP(natsBuf_Data(&buf)); - if (subscribeSubjects[i] == NULL) - { - s = nats_setDefaultError(NATS_NO_MEMORY); - NATS_FREE_STRINGS(subscribeSubjects, i); - NATS_FREE(multipleSubjects); - _freeWatcher(w); - return nats_setDefaultError(NATS_NO_MEMORY); - } - } - IFOK(s, natsMutex_Create(&(w->mu))); - if (s == NATS_OK) - { - // Use ordered consumer to deliver results - - jsSubOptions_Init(&so); - so.Ordered = true; - if ((opts == NULL) || !opts->IncludeHistory) - so.Config.DeliverPolicy = js_DeliverLastPerSubject; - if (opts != NULL) - { - if (opts->MetaOnly) - so.Config.HeadersOnly = true; - if (opts->IgnoreDeletes) - w->ignoreDel = true; - } - // Need to explicitly bind to the stream here because the subject - // we construct may not help find the stream when using mirrors. - so.Stream = kv->stream; - s = js_SubscribeSyncMulti(&(w->sub), kv->js, (const char **)subscribeSubjects, numKeys, NULL, &so, NULL); - IFOK(s, natsSubscription_SetPendingLimits(w->sub, -1, -1)); - if (s == NATS_OK) - { - natsSubscription *sub = w->sub; - - natsSub_Lock(sub); - if ((sub->jsi != NULL) && (sub->jsi->pending == 0)) - { - w->initDone = true; - w->retMarker = true; - } - natsSub_Unlock(sub); - } - } - - natsBuf_Cleanup(&buf); - NATS_FREE_STRINGS(subscribeSubjects, numKeys); - NATS_FREE(multipleSubjects); - - if (s == NATS_OK) - *new_watcher = w; - else - _freeWatcher(w); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -kvStore_WatchAll(kvWatcher **new_watcher, kvStore *kv, kvWatchOptions *opts) -{ - natsStatus s = kvStore_Watch(new_watcher, kv, ">", opts); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -kvStore_Keys(kvKeysList *list, kvStore *kv, kvWatchOptions *opts) -{ - natsStatus s; - kvWatchOptions o; - kvWatcher *w = NULL; - int count = 0; - KV_DEFINE_LIST; - - if (list == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - list->Keys = NULL; - list->Count = 0; - - kvWatchOptions_Init(&o); - if (opts != NULL) - memcpy(&o, opts, sizeof(kvWatchOptions)); - - o.IgnoreDeletes = true; - o.MetaOnly = true; - if (o.Timeout > 0) - timeout = o.Timeout; - - s = kvStore_WatchAll(&w, kv, &o); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - KV_GATHER_LIST; - - // Don't need the watcher anymore. - kvWatcher_Destroy(w); - // On success, create the array of keys. - if ((s == NATS_OK) && (n > 0)) - { - list->Keys = (char**) NATS_CALLOC(n, sizeof(char*)); - if (list->Keys == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - // Transfer keys to the array (on success), and destroy - // the entries if there was an error. - for (i=0; h != NULL; i++) - { - e = h; - h = h->next; - if (s == NATS_OK) - { - DUP_STRING(s, list->Keys[i], e->key); - if (s == NATS_OK) - count++; - } - kvEntry_Destroy(e); - } - // Set the list's Count to `count`, not `n` since `count` - // will reflect the actual number of keys that have been - // properly strdup'ed. - list->Count = count; - - // If there was a failure (especially when strdup'ing) keys, - // this will do the proper cleanup and re-initialize the list. - if (s != NATS_OK) - kvKeysList_Destroy(list); - - return NATS_UPDATE_ERR_STACK(s); -} - -void -kvKeysList_Destroy(kvKeysList *list) -{ - int i; - - if ((list == NULL) || (list->Keys == NULL)) - return; - - for (i=0; iCount; i++) - NATS_FREE(list->Keys[i]); - NATS_FREE(list->Keys); - list->Keys = NULL; - list->Count = 0; -} - -natsStatus -kvStore_History(kvEntryList *list, kvStore *kv, const char *key, kvWatchOptions *opts) -{ - natsStatus s; - kvWatchOptions o; - kvEntry *e = NULL; - kvEntry *h = NULL; - kvEntry *t = NULL; - int n = 0; - kvWatcher *w = NULL; - int64_t timeout = KV_WATCH_FOR_EVER; - int64_t start; - int i; - - if (list == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - list->Entries = NULL; - list->Count = 0; - - kvWatchOptions_Init(&o); - if (opts != NULL) - memcpy(&o, opts, sizeof(kvWatchOptions)); - - o.IncludeHistory = true; - if (o.Timeout > 0) - timeout = o.Timeout; - - s = kvStore_Watch(&w, kv, key, &o); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - KV_GATHER_LIST; - - // Don't need the watcher anymore. - kvWatcher_Destroy(w); - // On success, create the array of entries. - if ((s == NATS_OK) && (n > 0)) - { - list->Entries = (kvEntry**) calloc(n, sizeof(kvEntry*)); - if (list->Entries == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - list->Count = n; - } - // Transfer entries to the array (on success), or destroy - // the entries if there was an error. - for (i=0; h != NULL; i++) - { - e = h; - h = h->next; - if (s == NATS_OK) - list->Entries[i] = e; - else - kvEntry_Destroy(e); - } - // Go client returns "not found" if the subject exists, but - // there is nothing to return, so basically after a purge deletes, - // a key has no data and no marker, and we return "not found". - if ((s == NATS_OK) && (list->Count == 0)) - return NATS_NOT_FOUND; - - return NATS_UPDATE_ERR_STACK(s); -} - -void -kvEntryList_Destroy(kvEntryList *list) -{ - int i; - - if ((list == NULL) || (list->Entries == NULL)) - return; - - for (i=0; iCount; i++) - kvEntry_Destroy(list->Entries[i]); - NATS_FREE(list->Entries); - list->Entries = NULL; - list->Count = 0; -} - -natsStatus -kvWatcher_Stop(kvWatcher *w) -{ - natsStatus s = NATS_OK; - - if (w == NULL) - return NATS_INVALID_ARG; - - natsMutex_Lock(w->mu); - if (!w->stopped) - { - w->stopped = true; - s = natsSubscription_Unsubscribe(w->sub); - } - natsMutex_Unlock(w->mu); - - return NATS_UPDATE_ERR_STACK(s); -} - -const char* -kvStore_Bucket(kvStore *kv) -{ - return (kv == NULL ? NULL : kv->bucket); -} - -natsStatus -kvStore_Status(kvStatus **new_status, kvStore *kv) -{ - natsStatus s; - kvStatus *sts = NULL; - jsStreamInfo *si = NULL; - - if ((new_status == NULL) || (kv == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - s = js_GetStreamInfo(&si, kv->js, kv->stream, NULL, NULL); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - sts = (kvStatus*) NATS_CALLOC(1, sizeof(kvStatus)); - if (sts == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - - if (s == NATS_OK) - { - _retainKV(kv); - sts->kv = kv; - sts->si = si; - *new_status = sts; - } - else - jsStreamInfo_Destroy(si); - - return NATS_UPDATE_ERR_STACK(s); -} - -////////////////////////////////// -// kvStatus APIs -////////////////////////////////// - -const char* -kvStatus_Bucket(kvStatus *sts) -{ - return (sts == NULL ? NULL : sts->kv->bucket); -} - -uint64_t -kvStatus_Values(kvStatus *sts) -{ - return (sts == NULL ? 0 : sts->si->State.Msgs); -} - -int64_t -kvStatus_History(kvStatus *sts) -{ - return (sts == NULL || sts->si->Config == NULL ? 0 : sts->si->Config->MaxMsgsPerSubject); -} - -int64_t -kvStatus_TTL(kvStatus *sts) -{ - return (sts == NULL || sts->si->Config == NULL ? 0 : sts->si->Config->MaxAge); -} - -int64_t -kvStatus_Replicas(kvStatus *sts) -{ - return (sts == NULL || sts->si->Config == NULL ? 0 : sts->si->Config->Replicas); -} - -uint64_t -kvStatus_Bytes(kvStatus *sts) -{ - return (sts == NULL ? 0 : sts->si->State.Bytes); -} - -void -kvStatus_Destroy(kvStatus *sts) -{ - kvStore *kv = NULL; - - if (sts == NULL) - return; - - kv = sts->kv; - jsStreamInfo_Destroy(sts->si); - NATS_FREE(sts); - _releaseKV(kv); -} - -////////////////////////////////// -// kvEntry APIs -////////////////////////////////// - -const char* -kvEntry_Bucket(kvEntry *e) -{ - return (e == NULL ? NULL : kvStore_Bucket(e->kv)); -} - -const char* -kvEntry_Key(kvEntry *e) -{ - return (e == NULL ? NULL : e->key); -} - -const void* -kvEntry_Value(kvEntry *e) -{ - return (e == NULL ? NULL : (const void*) natsMsg_GetData(e->msg)); -} - -int -kvEntry_ValueLen(kvEntry *e) -{ - return (e == NULL ? -1 : natsMsg_GetDataLength(e->msg)); -} - -const char* -kvEntry_ValueString(kvEntry *e) -{ - return (e == NULL ? NULL : natsMsg_GetData(e->msg)); -} - -uint64_t -kvEntry_Revision(kvEntry *e) -{ - return (e == NULL ? 0 : natsMsg_GetSequence(e->msg)); -} - -int64_t -kvEntry_Created(kvEntry *e) -{ - return (e == NULL ? 0 : natsMsg_GetTime(e->msg)); -} - -uint64_t -kvEntry_Delta(kvEntry *e) -{ - return (e == NULL ? 0 : e->delta); -} - -kvOperation -kvEntry_Operation(kvEntry *e) -{ - return (e == NULL ? 0 : e->op); -} - -void -kvEntry_Destroy(kvEntry *e) -{ - kvStore *kv = NULL; - - if (e == NULL) - return; - - kv = e->kv; - natsMsg_Destroy(e->msg); - NATS_FREE(e); - _releaseKV(kv); -} diff --git a/src/kv.h b/src/kv.h deleted file mode 100644 index 3b01335ea..000000000 --- a/src/kv.h +++ /dev/null @@ -1,25 +0,0 @@ -// 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. - -#define kvMaxHistory (64) - -#define kvOpHeader "KV-Operation" -#define kvOpDeleteStr "DEL" -#define kvOpPurgeStr "PURGE" - -#define kvErrInvalidBucketName "invalid bucket name" -#define kvErrHistoryTooLarge "history limited to a max of " -#define kvErrBadBucket "bucket not valid key-value store" -#define kvErrBucketNotFound "bucket not found" -#define kvErrInvalidKey "invalid key" -#define kvErrInvalidRevision "invalid revision" diff --git a/src/mem.h b/src/mem.h index 513998a23..d19548f2a 100644 --- a/src/mem.h +++ b/src/mem.h @@ -14,27 +14,7 @@ #ifndef MEM_H_ #define MEM_H_ -#include - -#define NATS_MALLOC(s) malloc((s)) -#define NATS_CALLOC(c,s) calloc((c), (s)) -#define NATS_REALLOC(p, s) realloc((p), (s)) - -#ifdef _WIN32 -#define NATS_STRDUP(s) _strdup((s)) -#else -#define NATS_STRDUP(s) strdup((s)) -#endif -#define NATS_FREE(p) free((p)) - -// **Note** does not free the array itself. -static void NATS_FREE_STRINGS(char **strings, int count) -{ - if (strings == NULL) - return; - for (int i = 0; i < count; i++) - NATS_FREE((char *)strings[i]); -} +#include // GNU C Library version 2.25 or later. #if defined(__GLIBC__) && \ @@ -62,4 +42,74 @@ static void NATS_FREE_STRINGS(char **strings, int count) #define HAVE_EXPLICIT_MEMSET 1 #endif +typedef struct __natsBuf_s natsBuf; +typedef struct __natsGrowable_s natsGrowable; +typedef struct __natsLarge_s natsLarge; +typedef struct __natsReadBuffer_s natsReadBuffer; +typedef struct __natsReadChain_s natsReadChain; +typedef struct __natsSmall_s natsSmall; + +#define nats_numPages(_memopts, _c) (((_c) + (_memopts)->heapPageSize - 1) / (_memopts)->heapPageSize) +#define nats_pageAlignedSize(_memopts, _c) (nats_numPages(_memopts, _c) * (_memopts)->heapPageSize) + +#ifdef DEV_MODE_MEM_HEAP +#define HEAPTRACEf(fmt, ...) DEVTRACEx("HEAP", file, line, func, fmt, __VA_ARGS__) + +#define nats_alloc(_c, _z) nats_log_alloc(nats_globalHeap(), _c, _z DEV_MODE_CTX) +#define nats_realloc(_p, _c) nats_log_realloc(nats_globalHeap(), _p, _c DEV_MODE_CTX) +#define nats_dupCString(_s) nats_log_dupCString(nats_globalHeap(), _s DEV_MODE_CTX) +#define nats_free(_p) nats_log_free(nats_globalHeap(), _p DEV_MODE_CTX) +#define nats_destroyHeap(_h) nats_log_destroy(nats_globalHeap() DEV_MODE_CTX) +#else // DEV_MODE_MEM_HEAP +#define HEAPTRACEf(fmt, ...) + +#define nats_alloc(_c, _z) (nats_globalHeap())->alloc(nats_globalHeap(), _c, _z) +#define nats_realloc(_p, _c) (nats_globalHeap())->realloc(nats_globalHeap(), _p, _c) +#define nats_dupCString(_s) (nats_globalHeap())->dupCString(nats_globalHeap(), _s) +#define nats_free(_p) (nats_globalHeap())->free(nats_globalHeap(), _p) +#define nats_destroyHeap() (nats_globalHeap())->destroy(nats_globalHeap()) +#endif // DEV_MODE_MEM_HEAP + +static inline void *nats_log_alloc(natsHeap *h, size_t size, bool zero DEV_MODE_ARGS) +{ + void *mem = h->alloc(h, size, zero); + if (mem != NULL) + HEAPTRACEf("allocated bytes:%zu, ptr:%p", size, mem); + return mem; +} + +static inline void *nats_log_realloc(natsHeap *h, void *ptr, size_t size DEV_MODE_ARGS) +{ + void *mem = h->realloc(h, ptr, size); + if (mem != NULL) + HEAPTRACEf("reallocated bytes:%zu, from ptr:%p to ptr:%p", size, ptr, mem); + return mem; +} + +static inline void *nats_log_dupCString(natsHeap *h, const char *s DEV_MODE_ARGS) +{ + void *mem = h->strdup(h, s); + if (mem != NULL) + HEAPTRACEf("duplicated string:%s, bytes:%zu, ptr:%p", s, strlen(s)+1, mem); + return mem; +} + +static inline void nats_log_free(natsHeap *h, void *ptr DEV_MODE_ARGS) +{ + if (ptr != NULL) + { + HEAPTRACEf("freeing ptr:%p", ptr); + h->free(h, ptr); + } +} + +static inline void nats_log_destroy(natsHeap *h DEV_MODE_ARGS) +{ + HEAPTRACEf("destroying heap:%p", (void*)h); + h->destroy(h); +} + +#include "mem_string.h" +#include "mem_pool.h" + #endif /* MEM_H_ */ diff --git a/src/mem_pool.c b/src/mem_pool.c new file mode 100644 index 000000000..963c32a5d --- /dev/null +++ b/src/mem_pool.c @@ -0,0 +1,608 @@ +// Copyright 2015-2024 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. + +#define MEM_POOL_C_ + +#include "natsp.h" + +static inline size_t _smallMax(natsPool *pool) { return pool->opts->heapPageSize - sizeof(natsSmall); } +static inline size_t _smallCap(natsPool *pool, natsSmall *small) { return pool->opts->heapPageSize - small->len; } + +static inline void *_smallGrab(natsSmall *small, size_t size) +{ + void *mem = (uint8_t *)small + small->len; + small->len += size; + return mem; +} + +static inline natsPool * +_initializePool(void *mempage, natsMemOptions *opts, const char *name) +{ + natsSmall *small = mempage; + _smallGrab(small, sizeof(natsSmall)); // mark itself allocated + natsPool *pool = _smallGrab(small, sizeof(natsPool)); + pool->small = small; + pool->opts = opts; + pool->refs = 1; + pool->name = nats_pstrdupC(pool, name); + return pool; +} + +static uint64_t _userPoolC = 0; + +natsStatus +nats_CreatePool(natsPool **newPool, natsOptions *opts) +{ + char name[64]; + // Public API, so we need to check that we're initialized. + natsStatus s = nats_open(); + if (s != NATS_OK) + return s; + + natsMemOptions *memOpts = &nats_defaultMemOptions; + if (opts != NULL) + memOpts = &opts->mem; + + snprintf(name, sizeof(name), "user-pool-%" PRIu64, ++_userPoolC); + return nats_log_createPool(newPool, memOpts, name DEV_MODE_CTX); +} + +static void _free(natsPool *pool DEV_MODE_ARGS) +{ + if (pool == NULL) + return; + + for (natsLarge *l = pool->large; l != NULL; l = l->prev) + { + POOLTRACEx("-l ", "%s: freeing large, ptr:%p", pool->name, (void *)l->data); + nats_free(l->data); + } + + natsSmall *next = NULL; + int i = 0; +#ifdef DEV_MODE_MEM_POOL + void *mem = pool->small; + char namebuf[128]; + strncpy(namebuf, pool->name, sizeof(namebuf)); +#endif + + for (natsSmall *small = pool->small; small != NULL; small = next, i++) + { + next = small->next; + if (i != 0) + POOLTRACEx("-s- ", "%s#%d: freeing small, ptr:%p", namebuf, i, (void *)small); + nats_free(small); + } + + // The pool itself is allocated in the first natsSmall, so no need to free + // it. + POOLTRACEx("POOL", "%s: destroyed pool, heap: %p", namebuf, (void *)(mem)); +} + +void nats_log_releasePool(natsPool *pool DEV_MODE_ARGS) +{ + if (pool == NULL) + return; + pool->refs--; + if (pool->refs == 0) + _free(pool DEV_MODE_PASSARGS); +} + +void nats_ReleasePool(natsPool *pool) +{ + nats_log_releasePool(pool DEV_MODE_CTX); +} + +void nats_RetainPool(natsPool *pool) +{ + pool->refs++; +} + +void *_allocSmall(natsSmall **newOrFound, natsPool *pool, size_t size DEV_MODE_ARGS) +{ + natsSmall *last = pool->small; + natsSmall *small = pool->small; + int i = 0; + void *mem = NULL; + + for (small = pool->small; small != NULL; small = small->next, i++) + { + if (size > _smallCap(pool, small)) + { + last = small; + continue; + } + + mem = _smallGrab(small, size); + + if (newOrFound != NULL) + *newOrFound = small; + // POOLTRACEx("+s", + // "%s#%d: allocated in small: bytes:%zu, remaining:%zu, ptr:%p", pool->name, i, size, _smallCap(pool, small), mem); + return mem; + } + + small = nats_alloc(pool->opts->heapPageSize, NATS_MEM_ZERO_OUT); // greater than sizeof(natsSmall) + if (small == NULL) + { + POOLERRORf("%s: failed to allocate a new small page: %zu bytes", pool->name, pool->opts->heapPageSize); + return NULL; + } + _smallGrab(small, sizeof(natsSmall)); // mark itself allocated + mem = _smallGrab(small, size); + + // Link it to the end of the chain. + last->next = small; + + if (newOrFound != NULL) + *newOrFound = small; + POOLTRACEx("+s+", "%s#%d: (new) allocated in small bytes:%zu, remaining:%zu, ptr:%p", pool->name, i, size, _smallCap(pool, small), mem); + return mem; +} + +static void * +_allocLarge(natsPool *pool, size_t size, natsLarge **newLarge DEV_MODE_ARGS) +{ + natsLarge *large = NULL; + if (newLarge != NULL) + *newLarge = NULL; + + large = nats_palloc(pool, sizeof(natsLarge)); + if (large == NULL) + return NULL; + + large->data = nats_alloc(pool->opts->heapPageSize, NATS_MEM_ZERO_OUT); + if (large->data == NULL) + { + POOLERRORf("%s: failed to alloc large", pool->name); + return NULL; + } + memset(large->data, 0, size); + large->prev = pool->large; + pool->large = large; + + if (newLarge != NULL) + *newLarge = large; + + POOLTRACEx("+l", "%s: allocated %zu, ptr:%p ", pool->name, size, (void *)large->data); + return large->data; +} + +void *natsPool_nolog_alloc(natsPool *pool, size_t size) +{ + if (size > _smallMax(pool)) + return _allocLarge(pool, size, NULL DEV_MODE_CTX); + else + return _allocSmall(NULL, pool, size DEV_MODE_CTX); +} + +void *natsPool_Alloc(natsPool *pool, size_t size) +{ + return natsPool_nolog_alloc(pool, size); +} + +natsStatus natsPool_getReadBuffer(natsReadBuffer **out, natsPool *pool) +{ + if (pool->readChain == NULL) + { + pool->readChain = nats_palloc(pool, sizeof(natsReadChain)); + if (pool->readChain == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + } + natsReadChain *chain = pool->readChain; + + // If we have a current chain and it has enough space, return it. + if ((chain->tail != NULL) && natsReadBuffer_available(pool->opts, chain->tail) >= pool->opts->readBufferMin) + { + *out = chain->tail; + return NATS_OK; + } + + // Don't have a chain or the current one is full, allocate a new one. + natsReadBuffer *rbuf = nats_palloc(pool, sizeof(natsReadBuffer)); + if (rbuf == NULL) + return NATS_NO_MEMORY; + + rbuf->buf.data = nats_alloc(pool->opts->readBufferSize, NATS_MEM_LEAVE_UNINITIALIZED); + if (rbuf->buf.data == NULL) + return NATS_NO_MEMORY; + POOLDEBUGf("%s: allocated read buffer %zu bytes, heap: %p)", pool->name, pool->opts->readBufferSize, (void *)rbuf->buf.data); + rbuf->readFrom = rbuf->buf.data; + rbuf->buf.len = 0; + + if (chain->tail == NULL) + { + // First buffer + chain->head = rbuf; + chain->tail = rbuf; + } + else + { + // Link it to the end of the chain. + chain->tail->next = rbuf; + chain->tail = rbuf; + } + + *out = rbuf; + return NATS_OK; +} + +static void natsPool_log_recycleReadChain(natsReadBuffer *clone, natsPool *pool DEV_MODE_ARGS) +{ + natsReadChain *rc = pool->readChain; + natsReadBuffer *rbuf; + + // Free the buffers, except the last one. + for (rbuf = rc->head; (rbuf != NULL); rbuf = rbuf->next) + { + if (rbuf == rc->tail) + { + POOLTRACEx("recycle", "%s: NOT freeing last read buffer, heap: %p)", pool->name, (void *)rbuf->buf.data); + break; + } + POOLTRACEx("-r", "%s: freeing read buffer, heap: %p", pool->name, (void *)rbuf->buf.data); + nats_free(rbuf->buf.data); + } + + memset(clone, 0, sizeof(natsReadBuffer)); + if (rbuf == NULL) + return; + + *clone = *rbuf; + clone->next = NULL; + if (natsReadBuffer_unreadLen(clone) == 0) + { + // No need to zero out the memory, just reset the pointers. + clone->readFrom = clone->buf.data; + clone->buf.len = 0; + } + return; +} + +natsStatus nats_log_recyclePool(natsPool **poolPtr, natsReadBuffer **outRbuf DEV_MODE_ARGS) +{ + char namebuf[64] = ""; + natsReadBuffer lastToKeep; + natsReadBuffer *rbuf = NULL; + natsPool *pool; + + if (poolPtr == NULL) + return nats_setDefaultError(NATS_INVALID_ARG); + pool = *poolPtr; + *poolPtr = NULL; // in case we fail + + if (pool == NULL) + return nats_setDefaultError(NATS_INVALID_ARG); + if (pool->refs > 1) + { + POOLDEBUGf("%s: refs:%d, NOT recyclable, release", pool->name, pool->refs); + nats_releasePool(pool); + goto CREATE_NEW_POOL; + } + + // need to keep a copy of the name since we will zero out the previous memory. + if (pool->name != NULL) + strncpy(namebuf, pool->name, sizeof(namebuf)); + + // Recycle the chain, keep a copy of the last ReadBuffer so we can append it + // to the recycled pool's read chain. + natsPool_log_recycleReadChain(&lastToKeep, pool DEV_MODE_CTX); + + // Free all Large's. + for (natsLarge *l = pool->large; l != NULL; l = l->prev) + { + POOLTRACEx("-l ", "%s: freeing large, ptr:%p", namebuf, (void *)l->data); + nats_free(l->data); + } + pool->large = NULL; + + // Free all Smalls, except the very first two, they'll be re-used. 2 because + // parseOp will likely want a little memory and a natsBuf that will take a + // Small to itself. pool->small can never be NULL. + natsSmall *s = pool->small; + natsSmall *next = NULL; + for (int i = 0; s != NULL; s = next, i++) + { + if (i < 2) + { + next = s->next; + } + else + { + POOLTRACEx("-s", "%s: freeing small #%d, ptr: %p", namebuf, i, (void *)s); + next = s->next; + nats_free(s); + } + } + + // Re-initialize the (up to) 2 preserved smalls. + next = NULL; // redunant, but makes it clear. + if (pool->small != NULL) + { + void *mem = pool->small; + natsMemOptions *opts = pool->opts; + next = pool->small->next; + memset(mem, 0, opts->heapPageSize); + pool = _initializePool(mem, opts, namebuf); + pool->small->next = next; + POOLDEBUGf("%s: recycled small #0, remaining:%zu, heap: %p", namebuf, _smallCap(pool, pool->small), (void *)pool->small); + } + if (next != NULL) + { + memset(next, 0, pool->opts->heapPageSize); + next->len = sizeof(natsSmall); + POOLDEBUGf("%s: recycled small #1, remaining:%zu, heap: %p", namebuf, _smallCap(pool, next), (void *)next); + } + + // If there was allocated memory in the last ReadBuffer, use it. If it holds + // unread bytes, use it as is, otherwise reset to an empty buffer. + if (lastToKeep.buf.data != NULL) + { + POOLTRACEx(" ", "%s: keeping the last read buffer, heap: %p)", pool->name, (void *)lastToKeep.buf.data); + pool->readChain = nats_palloc(pool, sizeof(natsReadChain)); + if (pool->readChain != NULL) + rbuf = nats_palloc(pool, sizeof(natsReadBuffer)); + if (pool->readChain == NULL || rbuf == NULL) + return NATS_NO_MEMORY; // the caller will release the pool. + + *rbuf = lastToKeep; + pool->readChain->head = rbuf; + pool->readChain->tail = rbuf; + } + + POOLDEBUGf("%s: recycled, heap: %p", namebuf, (void *)(pool->small)); + *poolPtr = pool; + return NATS_OK; + +CREATE_NEW_POOL: + if (pool != NULL) + nats_releasePool(pool); + if (outRbuf != NULL) + *outRbuf = NULL; + return nats_createPool(poolPtr, pool->opts, pool->name); +} + +static inline size_t _newLargeBufSize(natsPool *pool, size_t current, size_t required) +{ + size_t newCap = (2 * current < required) ? required : 2 * current; + newCap = nats_pageAlignedSize(pool->opts, newCap); + return newCap; +} + +static natsStatus +_expandBuf(natsBuf *buf, size_t capacity) +{ + uint8_t *data = NULL; + size_t prevCap = buf->cap; + natsSmall *prevSmall = buf->small; + bool copy = true; + size_t newCap = 0; + natsPool *pool = buf->pool; + + if ((capacity < buf->buf.len) || (pool == NULL)) + return nats_setDefaultError(NATS_INVALID_ARG); + if (capacity >= 0x7FFFFFFF) + return nats_setDefaultError(NATS_NO_MEMORY); + if (capacity <= buf->cap) + return NATS_OK; + + // Only resize a fixed buffer once. + if ((buf->isFixedSize) && (buf->cap != 0)) + return nats_setDefaultError(NATS_INSUFFICIENT_BUFFER); + + if (capacity <= buf->cap) + return NATS_OK; + + // If the buffer was already allocated in a "large" chunk, use realloc(), + // it's most efficient. + if (buf->large != NULL) + { + newCap = _newLargeBufSize(pool, buf->cap, capacity); + buf->large->data = nats_realloc(buf->large->data, newCap); + if (buf->large->data == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + buf->buf.data = buf->large->data; + buf->cap = newCap; + copy = false; + return NATS_OK; + } + + if (capacity > _smallMax(pool)) + { + // We don't fit in a small, allocate a large. + natsLarge *newLarge = NULL; + newCap = _newLargeBufSize(pool, buf->cap, capacity); + data = _allocLarge(pool, newCap, &newLarge DEV_MODE_CTX); + if (data == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + buf->small = NULL; + buf->large = newLarge; + } + else + { + // If a fixed size buffer, treat it as a normal pool allocation. + if (buf->isFixedSize) + { + data = _allocSmall(NULL, pool, capacity DEV_MODE_CTX); + if (data == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + newCap = capacity; + buf->small = NULL; + } + else + { + // Take up a whole page since we may be expanding anyway. Save the + // 'small' pointer in case we need to return the memory page to the + // pool. + newCap = _smallMax(pool); + data = _allocSmall(&buf->small, pool, newCap DEV_MODE_CTX); + if (data == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + buf->large = NULL; + } + } + + if (copy && !nats_IsStringEmpty(&buf->buf)) + memcpy(data, buf->buf.data, buf->buf.len); + buf->buf.data = data; + buf->cap = newCap; + + // If we were previously in a small, return the space to the pool, and zero + // it out. + if (prevSmall != NULL) + { + prevSmall->len -= prevCap; + memset((uint8_t *)prevSmall + prevSmall->len, 0, prevCap); + } + + return NATS_OK; +} + +natsStatus +natsBuf_Reset(natsBuf *buf) +{ + if (buf == NULL) + return nats_setDefaultError(NATS_INVALID_ARG); + buf->buf.len = 0; + return NATS_OK; +} + +static natsStatus +_createBuf(natsBuf **newBuf, natsPool *pool, size_t capacity, bool fixedSize) +{ + natsBuf *buf = nats_palloc(pool, sizeof(natsBuf)); + if (buf == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + buf->pool = pool; + buf->isFixedSize = fixedSize; + + if (capacity == 0) + { + if (fixedSize) + return nats_setDefaultError(NATS_INVALID_ARG); + else + capacity = 1; + } + + natsStatus s = _expandBuf(buf, capacity); + if (s != NATS_OK) + return s; + + *newBuf = buf; + return NATS_OK; +} + +natsStatus natsPool_getFixedBuf(natsBuf **newBuf, natsPool *pool, size_t capacity) +{ + return _createBuf(newBuf, pool, capacity, true); +} + +natsStatus natsPool_getGrowableBuf(natsBuf **newBuf, natsPool *pool, size_t capacity) +{ + return _createBuf(newBuf, pool, capacity, false); +} + +void natsPool_log_recycleBuf(natsBuf *buf DEV_MODE_ARGS) +{ + if (buf == NULL) + return; + + if (buf->large != NULL) + { + POOLTRACEx("-l ", "recycling large from natsBuf, ptr:%p", (void *)buf->large->data); + nats_free(buf->large->data); + } + else if (buf->small != NULL) + { + buf->small->len -= buf->cap; + memset((uint8_t *)buf->small + buf->small->len, 0, buf->cap); + POOLTRACEx("-s ", "recycling small from natsBuf, ptr:%p", (void *)buf->small); + } + + memset(buf, 0, sizeof(natsBuf)); +} + +natsStatus +natsBuf_addBB(natsBuf *buf, const uint8_t *data, size_t len) +{ + natsStatus s = NATS_OK; + size_t n = buf->buf.len + len; + + if (n > buf->cap) + s = _expandBuf(buf, n); + if (s != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); + + memcpy(buf->buf.data + buf->buf.len, data, len); + buf->buf.len += len; + + return NATS_OK; +} + +natsStatus +natsBuf_addB(natsBuf *buf, uint8_t b) +{ + natsStatus s = NATS_OK; + size_t n; + + if ((n = buf->buf.len + 1) > buf->cap) + s = _expandBuf(buf, n); + if (s != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); + + if (STILL_OK(s)) + { + buf->buf.data[buf->buf.len] = b; + buf->buf.len++; + } + + return NATS_UPDATE_ERR_STACK(s); +} + +void *nats_log_palloc(natsPool *pool, size_t size DEV_MODE_ARGS) +{ + void *mem; + + if (size > _smallMax(pool)) + mem = _allocLarge(pool, size, NULL DEV_MODE_PASSARGS); + else + mem = _allocSmall(NULL, pool, size DEV_MODE_PASSARGS); + +#ifdef DEV_MODE + uint8_t *ptr = (uint8_t *)mem; + for (size_t i = 0; i < size; i++) + if (ptr[i] != 0) + POOLDEBUGf("<>/<> %s: allocated memory not zeroed out, size:%zu, ptr:%p\n", pool->name, size, mem); +#endif + + return mem; +} + +natsStatus nats_log_createPool(natsPool **newPool, natsMemOptions *opts, const char *name DEV_MODE_ARGS) +{ + const size_t required = sizeof(natsPool) + sizeof(natsSmall); + if ((newPool == NULL) || (opts == NULL) || (opts->heapPageSize < required)) + return nats_setDefaultError(NATS_INVALID_ARG); + + void *mempage = NULL; + if (required > opts->heapPageSize) + return nats_setError(NATS_INVALID_ARG, "page size %zu too small, need at least %zu", opts->heapPageSize, required); + mempage = nats_alloc(opts->heapPageSize, NATS_MEM_ZERO_OUT); + if (mempage == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + + *newPool = _initializePool(mempage, opts, name); + + POOLTRACEx("POOL", "%s: created, pageSize:%zu, heap: %p", name, (*newPool)->opts->heapPageSize, (void *)((*newPool)->small)); + return NATS_OK; +} diff --git a/src/mem_pool.h b/src/mem_pool.h new file mode 100644 index 000000000..004b6629e --- /dev/null +++ b/src/mem_pool.h @@ -0,0 +1,224 @@ +// Copyright 2015-2018 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. + +#ifndef MEM_POOL_H_ +#define MEM_POOL_H_ + +#include "opts.h" + +//---------------------------------------------------------------------------- +// natsPool - memory pool. +// +// - Uses a linked lists of natsSmall for small memory allocations. Each +// heap-allocated chunk is 1-page NATS_DEFAULT_MEM_PAGE_SIZE) sized. +// - Maximum small allocation size is NATS_DEFAULT_MEM_PAGE_SIZE - +// sizeof(natsSmall). +// - Uses a linked list of natsLarge for large HEAP memory allocations. The +// list elements are allocated in the (small) pool. +// - natsPool_Destroy() will free all memory allocated in the pool. +// +// +->+---------------+ 0 +------------->+---------------+ +// | | natsSmall | | | natsSmall | +// | | - next |---------+ | - next | +// | |---------------+ |---------------| +// | | natsPool | | | +// +--| - small | | | +// | | | used | +// | - large |--- | memory | +// | | | | +// | (most recent) | | | +// |---------------| |---------------| len +// | used | | free | +// | memory | | memory | +// | | | | +// | | | | +// +->+---------------| | | +// | | natsLarge #1 | | | +// | | - prev | | | +// | | - mem ======|=> HEAP | | +// | |---------------| | | +// | | natsLarge #2 | | | +// +--| - prev | | | +// | - mem ======|=> HEAP | | +// |---------------| | | +// | more | | | +// | used | | | +// | memory | | | +// | ... | | | +// |---------------| len | | +// | free | | | +// | memory | | | +// | | | | +// +---------------+ page size +---------------+ page size + +struct __natsSmall_s +{ + struct __natsSmall_s *next; + size_t len; +}; + +struct __natsLarge_s +{ + struct __natsLarge_s *prev; + uint8_t *data; +}; + +struct __natsReadBuffer_s +{ + natsString buf; // must be first, so natsReadBuf* is also a natsString* + struct __natsReadBuffer_s *next; + uint8_t *readFrom; +}; + +struct __natsBuf_s +{ + natsString buf; // must be first, so natsBuf* is also a natsString* + + size_t cap; + natsPool *pool; + natsSmall *small; + natsLarge *large; + bool isFixedSize; +}; + +// natsReadChain provides read buffer(s) to nats_ProcessReadEvent. +// While reading, we allocate or recycle the opPool every time a new operation +// is detected. The last read buffer from the previous operation may get +// recycled as the first read buffer of the new operation, including any +// leftover data in it. +struct __natsReadChain_s +{ + struct __natsReadBuffer_s *head; + struct __natsReadBuffer_s *tail; +}; + +struct __natsPool_s +{ + int refs; + natsMemOptions *opts; + + // small head is the first chunk allocated since that is where we attempt to + // allocate first. + natsSmall *small; + + // large head is the most recent large allocation, for simplicity. + natsLarge *large; + + natsReadChain *readChain; + + const char *name; // stored as a pointer, not copied. +}; + +natsStatus nats_log_createPool(natsPool **newPool, natsMemOptions *opts, const char *name DEV_MODE_ARGS); + +#ifdef DEV_MODE_MEM_POOL +#define POOLTRACEx(module, fmt, ...) DEVLOGx(DEV_MODE_TRACE, module, file, line, func, fmt, __VA_ARGS__) +#define POOLDEBUGf(fmt, ...) DEVDEBUGf("POOL", fmt, __VA_ARGS__) +#define POOLERRORf(fmt, ...) DEVERRORx("POOL", file, line, func, fmt, __VA_ARGS__) + +#if defined(DEV_MODE) && !defined(MEM_POOL_C_) +// avoid using public functions internally, they don't pass the log context +#define nats_ReleasePool(_p) USE_nats_releasePool_INSTEAD +#define nats_CreatePool(_p, _h) USE_nats_createPool_INSTEAD +#define nats_Palloc(_p, _s) USE_natsPool_alloc_INSTEAD +#endif + +#else // DEV_MODE_MEM_POOL + +#define POOLTRACEx(module, fmt, ...) +#define POOLDEBUGf(fmt, ...) +#define POOLERRORf(fmt, ...) + +#endif // DEV_MODE_MEM_POOL + +#define nats_createPool(_p, _opts, _n) nats_log_createPool((_p), (_opts), (_n)DEV_MODE_CTX) +#define nats_releasePool(_p) nats_log_releasePool((_p)DEV_MODE_CTX) +#define nats_pstrdupnC(_p, _str, _len) nats_log_pdupnC((_p), (_str), (_len)DEV_MODE_CTX) +#define nats_pstrdupn(_p, _str, _len) nats_log_pdupn((_p), (_str), (_len)DEV_MODE_CTX) +#define nats_palloc(_p, _s) nats_log_palloc((_p), _s DEV_MODE_CTX) +#define nats_recyclePool(_pptr, _rbufptr) nats_log_recyclePool((_pptr), (_rbufptr)DEV_MODE_CTX) + +#define nats_pstrdupC(_p, _str) nats_pstrdupnC((_p), (const uint8_t *)(_str), nats_strlen(_str) + 1) +#define nats_pstrdupS(_p, _str) (natsString_isEmpty(_str) ? NULL : nats_pstrdupn((_p), (_str)->data, (_str)->len)) +#define nats_pstrdupU(_p, _s) (nats_pstrdupn((_p), (const uint8_t *)(_s), nats_strlen(_s))) + +void *nats_log_palloc(natsPool *pool, size_t size DEV_MODE_ARGS); +void nats_log_releasePool(natsPool *pool DEV_MODE_ARGS); +natsStatus nats_log_recyclePool(natsPool **pool, natsReadBuffer **rbuf DEV_MODE_ARGS); + +static inline char *nats_log_pdupnC(natsPool *pool, const uint8_t *data, size_t len DEV_MODE_ARGS) +{ + if ((data == NULL) || (len == 0)) + return NULL; + char *dup = nats_log_palloc(pool, len DEV_MODE_PASSARGS); + if (dup == NULL) + return NULL; + memcpy(dup, data, len); + POOLTRACEx("!!!", "%s: allocated string '%s'", pool->name, natsString_debugPrintableN(data, len, 64)); + return dup; +} + +static inline natsString *nats_log_pdupn(natsPool *pool, const uint8_t *data, size_t len DEV_MODE_ARGS) +{ + if ((data == NULL) || (len == 0)) + return NULL; + natsString *dup = nats_log_palloc(pool, sizeof(natsString) DEV_MODE_PASSARGS); + if (dup == NULL) + return NULL; + dup->data = nats_log_palloc(pool, len DEV_MODE_PASSARGS); + if (dup->data == NULL) + return NULL; + memcpy(dup->data, data, len); + dup->len = len; + POOLTRACEx("", "%s: allocated string '%s'", pool->name, natsString_debugPrintableN(data, len, 64)); + return dup; +} + +#define natsReadBuf_capacity() nats_memReadBufferSize +#define natsReadBuf_data(_rbuf) ((_rbuf)->buf.data) +#define natsReadBuf_len(_rbuf) ((_rbuf)->buf.len) +#define natsReadBuffer_available(_memopts, _rbuf) ((_memopts)->heapPageSize - (_rbuf)->buf.len) +#define natsReadBuffer_end(_rbuf) ((_rbuf)->buf.data + (_rbuf)->buf.len) +#define natsReadBuffer_unreadLen(_rbuf) (natsReadBuffer_end(_rbuf) - (_rbuf)->readFrom) + +natsStatus natsPool_getReadBuffer(natsReadBuffer **rbuf, natsPool *pool); + +#define natsBuf_available(b) ((b)->cap - (b)->buf.len) +#define natsBuf_capacity(b) ((b)->cap) +#define natsBuf_data(b) ((b)->buf.data) +#define natsBuf_len(b) ((b)->buf.len) +#define natsBuf_string(b) ((natsString *)(b)) + +natsStatus natsPool_getFixedBuf(natsBuf **newBuf, natsPool *pool, size_t cap); +natsStatus natsPool_getGrowableBuf(natsBuf **newBuf, natsPool *pool, size_t initialCap); + +natsStatus natsBuf_Reset(natsBuf *buf); +natsStatus natsBuf_addBB(natsBuf *buf, const uint8_t *data, size_t len); +natsStatus natsBuf_addB(natsBuf *buf, uint8_t b); + +// Does NOT add the terminating 0! +static inline natsStatus +natsBuf_addCString(natsBuf *buf, const char *str) +{ + if (nats_isCStringEmpty(str)) + return NATS_OK; + return natsBuf_addBB(buf, (const uint8_t *)str, strlen(str)); // don't use nats_strlen, no need. +} + +static inline natsStatus +natsBuf_addString(natsBuf *buf, const natsString *str) +{ + return natsBuf_addBB(buf, str->data, str->len); +} + +#endif /* MEM_POOL_H_ */ diff --git a/src/mem_string.c b/src/mem_string.c new file mode 100644 index 000000000..077cbdb76 --- /dev/null +++ b/src/mem_string.c @@ -0,0 +1,167 @@ +// Copyright 2024 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 + +void nats_strlow(uint8_t *dst, uint8_t *src, size_t n) +{ + while (n) + { + *dst = nats_toLower(*src); + dst++; + src++; + n--; + } +} + +size_t +nats_strnlen(uint8_t *p, size_t n) +{ + size_t i; + + for (i = 0; i < n; i++) + { + + if (p[i] == '\0') + { + return i; + } + } + + return n; +} + +uint8_t * +nats_cpystrn(uint8_t *dst, uint8_t *src, size_t n) +{ + if (n == 0) + { + return dst; + } + + while (--n) + { + *dst = *src; + + if (*dst == '\0') + { + return dst; + } + + dst++; + src++; + } + + *dst = '\0'; + + return dst; +} + +#ifdef DEV_MODE + +static char _printbuf[128]; + +const char *_debugPrintable(const uint8_t *data, size_t len, char *out, size_t outCap, size_t limit) +{ + if (data == NULL) + return ""; + if (outCap < 5) + return ""; + if (limit == 0) + limit = len; + if (len > limit) + len = limit; + const uint8_t *end = data + len; + + const size_t maxbuf = outCap - 1; + size_t i = 0; + for (const uint8_t *p = data; (p < end) && (i < maxbuf); p++) + { + if (isprint(*p)) + { + out[i++] = *p; + } + else if (*p == '\n') + { + out[i++] = '\\'; + out[i++] = 'n'; + } + else if (*p == '\r') + { + out[i++] = '\\'; + out[i++] = 'r'; + } + else + { + out[i++] = '?'; + } + + if (i >= maxbuf - 3) + { + out[i++] = '.'; + out[i++] = '.'; + out[i++] = '.'; + } + } + out[i] = '\0'; + return out; +} + +static size_t _debugPrintableLen(natsString *buf) +{ + if (buf == NULL) + { + return 6; + } + uint8_t *end = buf->data + buf->len; + + size_t l = 0; + for (uint8_t *p = buf->data; p < end; p++, l++) + { + if ((*p == '\n') || (*p == '\r')) + l++; + } + return l + 1; +} + +const char *natsString_debugPrintable(natsString *buf, size_t limit) +{ + if (buf == NULL) + return ""; + return _debugPrintable(buf->data, buf->len, _printbuf, sizeof(_printbuf), limit); +} + +const char *natsString_debugPrintableN(const uint8_t *data, size_t len, size_t limit) +{ + return _debugPrintable(data, len, _printbuf, sizeof(_printbuf), limit); +} + +const char *natsString_debugPrintableC(const char *buf, size_t limit) +{ + if (buf == NULL) + return ""; + return natsString_debugPrintableN((const uint8_t *)buf, strlen(buf), limit); +} + +// const char *natsPool_debugPrintable(natsString *buf, natsPool *pool, size_t limit) +// { +// if (buf == NULL) +// return ""; +// size_t cap = _debugPrintableLen(buf); +// char *s = natsPool_alloc(pool, cap); +// return (s != NULL) ? nats_debugPrintableN(buf->data, buf->len, s, cap, limit) : "n/a - out of memory"; +// } + +#endif // DEV_MODE diff --git a/src/mem_string.h b/src/mem_string.h new file mode 100644 index 000000000..bd9c9f9ea --- /dev/null +++ b/src/mem_string.h @@ -0,0 +1,81 @@ +// Copyright 2015-2018 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. + +#ifndef MEM_STRING_H_ +#define MEM_STRING_H_ + +#include + +#define NATS_STR(_str) \ + { \ + .len = sizeof(_str) - 1, .data = (uint8_t *)(_str)} +#define NATS_STRC(_str) \ + { \ + .len = strlen(_str), .data = (uint8_t *)(_str)} +#define NATS_EMPTY_STR \ + { \ + 0, NULL} + +static inline bool natsString_Equal(natsString *str1, natsString *str2) +{ + if (str1 == str2) + return true; + return (str1 != NULL) && (str2 != NULL) && + (str1->len == str2->len) && + (strncmp((const char *)str1->data, (const char *)str2->data, str1->len) == 0); +} + +static inline bool natsString_equalC(const natsString *str1, const char *lit) +{ + if ((str1 == NULL) && (lit == NULL)) + return true; + return (str1 != NULL) && (lit != NULL) && + (str1->len == strlen((const char *)lit)) && + (strncmp((const char *)str1->data, lit, str1->len) == 0); +} + + +static inline bool nats_isCStringEmpty(const char *p) { return (p == NULL) || (*p == '\0'); } + +#define nats_toLower(c) (uint8_t)((c >= 'A' && c <= 'Z') ? (c | 0x20) : c) +#define nats_toUpper(c) (uint8_t)((c >= 'a' && c <= 'z') ? (c & ~0x20) : c) + +static inline size_t nats_strlen(const char *s) { return nats_isCStringEmpty(s) ? 0 : strlen(s); } +static inline uint8_t *nats_strchr(const uint8_t *s, uint8_t find) { return (uint8_t *)strchr((const char *)s, (int)(find)); } +static inline uint8_t *nats_strrchr(const uint8_t *s, uint8_t find) { return (uint8_t *)strrchr((const char *)s, (int)(find)); } +static inline const uint8_t *nats_strstr(const uint8_t *s, const char *find) { return (const uint8_t *)strstr((const char *)s, find); } +static inline int nats_strcmp(const uint8_t *s1, const char *s2) { return strcmp((const char *)s1, s2); } + +static inline int nats_strarray_find(const char **array, int count, const char *str) +{ + for (int i = 0; i < count; i++) + { + if (strcmp(array[i], str) == 0) + return i; + } + return -1; +} + +static inline size_t nats_strarray_remove(char **array, int count, const char *str) +{ + int i = nats_strarray_find((const char **)array, count, str); + if (i < 0) + return count; + + for (int j = i + 1; j < count; j++) + array[j - 1] = array[j]; + + return count - 1; +} + +#endif /* MEM_STRING_H_ */ diff --git a/src/micro.c b/src/micro.c deleted file mode 100644 index 86a572739..000000000 --- a/src/micro.c +++ /dev/null @@ -1,912 +0,0 @@ -// Copyright 2023 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 - -#include "microp.h" -#include "conn.h" -#include "opts.h" -#include "util.h" - -static inline void _lock_service(microService *m) { natsMutex_Lock(m->service_mu); } -static inline void _unlock_service(microService *m) { natsMutex_Unlock(m->service_mu); } - -static microError *_clone_service_config(microServiceConfig **out, microServiceConfig *cfg); -static microError *_new_service(microService **ptr, natsConnection *nc); -static microError *_wrap_connection_event_callbacks(microService *m); - -static void _free_cloned_service_config(microServiceConfig *cfg); -static void _free_service(microService *m); -static void _release_service(microService *m); -static void _retain_service(microService *m); - -microError * -micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *cfg) -{ - natsStatus s = NATS_OK; - microError *err = NULL; - microService *m = NULL; - - if ((new_m == NULL) || (nc == NULL) || (cfg == NULL) || !micro_is_valid_name(cfg->Name) || nats_IsStringEmpty(cfg->Version)) - return micro_ErrorInvalidArg; - - // Make a microservice object, with a reference to a natsConnection. - err = _new_service(&m, nc); - if (err != NULL) - return err; - - IFOK(s, natsMutex_Create(&m->service_mu)); - IFOK(s, natsNUID_Next(m->id, sizeof(m->id))); - err = micro_ErrorFromStatus(s); - - MICRO_CALL(err, _clone_service_config(&m->cfg, cfg)); - - // Wrap the connection callbacks before we subscribe to anything. - MICRO_CALL(err, _wrap_connection_event_callbacks(m)); - - MICRO_CALL(err, micro_init_monitoring(m)); - MICRO_CALL(err, microService_AddEndpoint(m, cfg->Endpoint)); - - if (err != NULL) - { - microError_Ignore(microService_Destroy(m)); - return microError_Wrapf(err, "failed to add microservice %s", cfg->Name); - } - - *new_m = m; - return NULL; -} - -microError * -micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal) -{ - microError *err = NULL; - microEndpoint *ptr = NULL; - microEndpoint *prev_ptr = NULL; - microEndpoint *ep = NULL; - microEndpoint *prev_ep = NULL; - - if (m == NULL) - return micro_ErrorInvalidArg; - if (cfg == NULL) - return NULL; - - err = micro_new_endpoint(&ep, m, prefix, cfg, is_internal); - if (err != NULL) - return microError_Wrapf(err, "failed to create endpoint %s", cfg->Name); - - _lock_service(m); - - if (m->stopped) - { - _unlock_service(m); - return micro_Errorf("can't add an endpoint %s to service %s: the service is stopped", cfg->Name, m->cfg->Name); - } - - if (m->first_ep != NULL) - { - if (strcmp(m->first_ep->subject, ep->subject) == 0) - { - ep->next = m->first_ep->next; - prev_ep = m->first_ep; - m->first_ep = ep; - } - else - { - prev_ptr = m->first_ep; - for (ptr = m->first_ep->next; ptr != NULL; prev_ptr = ptr, ptr = ptr->next) - { - if (strcmp(ptr->subject, ep->subject) == 0) - { - ep->next = ptr->next; - prev_ptr->next = ep; - prev_ep = ptr; - break; - } - } - if (prev_ep == NULL) - { - prev_ptr->next = ep; - } - } - } - else - { - m->first_ep = ep; - } - - _unlock_service(m); - - if (prev_ep != NULL) - { - // Rid of the previous endpoint with the same name, if any. If this - // fails we can return the error, leave the newly added endpoint in the - // list, not started. A retry with the same name will clean it up. - if (err = micro_stop_endpoint(prev_ep), err != NULL) - return err; - micro_release_endpoint(prev_ep); - } - - // retain `m` before the endpoint uses it for its on_complete callback. - _retain_service(m); - - if (err = micro_start_endpoint(ep), err != NULL) - { - // Best effort, leave the new endpoint in the list, as is. A retry with - // the same name will clean it up. - _release_service(m); - return microError_Wrapf(err, "failed to start endpoint %s", ep->name); - } - - if (new_ep != NULL) - *new_ep = ep; - return NULL; -} - -microError * -microService_AddEndpoint(microService *m, microEndpointConfig *cfg) -{ - return micro_add_endpoint(NULL, m, NULL, cfg, false); -} - -microError * -microGroup_AddEndpoint(microGroup *g, microEndpointConfig *cfg) -{ - if (g == NULL) - return micro_ErrorInvalidArg; - - return micro_add_endpoint(NULL, g->m, g->prefix, cfg, false); -} - -microError * -microService_Stop(microService *m) -{ - microError *err = NULL; - microEndpoint *ep = NULL; - bool finalize = false; - microDoneHandler doneHandler = NULL; - - if (m == NULL) - return micro_ErrorInvalidArg; - - _lock_service(m); - - if (m->stopped) - { - _unlock_service(m); - return NULL; - } - ep = m->first_ep; - - for (; ep != NULL; ep = ep->next) - { - if (err = micro_stop_endpoint(ep), err != NULL) - { - _unlock_service(m); - return microError_Wrapf(err, "failed to stop service '%s', stopping endpoint '%s'", m->cfg->Name, ep->name); - } - } - - finalize = (m->first_ep == NULL); - if (finalize) - { - natsLib_stopServiceCallbacks(m); - m->stopped = true; - doneHandler = m->cfg->DoneHandler; - } - - _unlock_service(m); - - if (finalize) - { - if (doneHandler != NULL) - doneHandler(m); - - // Relase the endpoint's server reference from `micro_add_endpoint`. - _release_service(m); - } - - return NULL; -} - -static bool -_find_endpoint(microEndpoint **prevp, microService *m, microEndpoint *to_find) -{ - microEndpoint *ep = NULL; - microEndpoint *prev_ep = NULL; - - if ((m == NULL) || (to_find == NULL)) - return false; - - for (ep = m->first_ep; ep != NULL; ep = ep->next) - { - if (ep == to_find) - { - *prevp = prev_ep; - return true; - } - prev_ep = ep; - } - - return false; -} - -void micro_release_on_endpoint_complete(void *closure) -{ - microEndpoint *ep = (microEndpoint *)closure; - microEndpoint *prev_ep = NULL; - microService *m = NULL; - natsSubscription *sub = NULL; - microDoneHandler doneHandler = NULL; - bool free_ep = false; - bool finalize = false; - - if (ep == NULL) - return; - - m = ep->m; - if ((m == NULL) || (m->service_mu == NULL)) - return; - - micro_lock_endpoint(ep); - ep->is_draining = false; - sub = ep->sub; - ep->sub = NULL; - ep->refs--; - free_ep = (ep->refs == 0); - micro_unlock_endpoint(ep); - - // Force the subscription to be destroyed now. - natsSubscription_Destroy(sub); - - _lock_service(m); - - // Release the service reference for the completed endpoint. It can not be - // the last reference, so no need to free m. - m->refs--; - - // Unlink the endpoint from the service. - if (_find_endpoint(&prev_ep, m, ep)) - { - if (prev_ep != NULL) - { - prev_ep->next = ep->next; - } - else - { - m->first_ep = ep->next; - } - } - - finalize = (!m->stopped) && (m->first_ep == NULL); - if (finalize) - { - natsLib_stopServiceCallbacks(m); - m->stopped = true; - doneHandler = m->cfg->DoneHandler; - } - - _unlock_service(m); - - if (free_ep) - micro_free_endpoint(ep); - - if (finalize) - { - if (doneHandler != NULL) - doneHandler(m); - - // Relase the endpoint's server reference from `micro_add_endpoint`. - _release_service(m); - } -} - -bool microService_IsStopped(microService *m) -{ - bool stopped; - - if ((m == NULL) || (m->service_mu == NULL)) - return true; - - _lock_service(m); - stopped = m->stopped; - _unlock_service(m); - - return stopped; -} - -microError * -microService_Destroy(microService *m) -{ - microError *err = NULL; - - err = microService_Stop(m); - if (err != NULL) - return err; - - _release_service(m); - return NULL; -} - -microError * -microService_Run(microService *m) -{ - if ((m == NULL) || (m->service_mu == NULL)) - return micro_ErrorInvalidArg; - - while (!microService_IsStopped(m)) - { - nats_Sleep(50); - } - - return NULL; -} - -void * -microService_GetState(microService *m) -{ - if (m == NULL) - return NULL; - - return m->cfg->State; -} - -static microError * -_new_service(microService **ptr, natsConnection *nc) -{ - *ptr = NATS_CALLOC(1, sizeof(microService)); - if (*ptr == NULL) - return micro_ErrorOutOfMemory; - - natsConn_retain(nc); - (*ptr)->refs = 1; - (*ptr)->nc = nc; - (*ptr)->started = nats_Now() * 1000000; - return NULL; -} - -static void -_retain_service(microService *m) -{ - if (m == NULL) - return; - - _lock_service(m); - - ++(m->refs); - - _unlock_service(m); -} - -static void -_release_service(microService *m) -{ - int refs = 0; - - if (m == NULL) - return; - - _lock_service(m); - - refs = --(m->refs); - - _unlock_service(m); - - if (refs == 0) - _free_service(m); -} - -static void -_free_service(microService *m) -{ - microGroup *next = NULL; - - if (m == NULL) - return; - - // destroy all groups. - if (m->groups != NULL) - { - microGroup *g = m->groups; - while (g != NULL) - { - next = g->next; - NATS_FREE(g); - g = next; - } - } - - _free_cloned_service_config(m->cfg); - natsConn_release(m->nc); - natsMutex_Destroy(m->service_mu); - NATS_FREE(m); -} - -static inline microError * -_new_service_config(microServiceConfig **ptr) -{ - *ptr = NATS_CALLOC(1, sizeof(microServiceConfig)); - return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; -} - -static microError * -_clone_service_config(microServiceConfig **out, microServiceConfig *cfg) -{ - microError *err = NULL; - microServiceConfig *new_cfg = NULL; - - if (out == NULL || cfg == NULL) - return micro_ErrorInvalidArg; - - err = _new_service_config(&new_cfg); - if (err == NULL) - { - memcpy(new_cfg, cfg, sizeof(microServiceConfig)); - } - // the strings are declared const for the public, but in a clone these need - // to be duplicated. - MICRO_CALL(err, micro_strdup((char **)&new_cfg->Name, cfg->Name)); - MICRO_CALL(err, micro_strdup((char **)&new_cfg->Version, cfg->Version)); - MICRO_CALL(err, micro_strdup((char **)&new_cfg->Description, cfg->Description)); - MICRO_CALL(err, micro_ErrorFromStatus( - nats_cloneMetadata(&new_cfg->Metadata, cfg->Metadata))); - MICRO_CALL(err, micro_clone_endpoint_config(&new_cfg->Endpoint, cfg->Endpoint)); - if (err != NULL) - { - _free_cloned_service_config(new_cfg); - return err; - } - - *out = new_cfg; - return NULL; -} - -static void -_free_cloned_service_config(microServiceConfig *cfg) -{ - if (cfg == NULL) - return; - - // the strings are declared const for the public, but in a clone these need - // to be freed. - NATS_FREE((char *)cfg->Name); - NATS_FREE((char *)cfg->Version); - NATS_FREE((char *)cfg->Description); - nats_freeMetadata(&cfg->Metadata); - micro_free_cloned_endpoint_config(cfg->Endpoint); - NATS_FREE(cfg); -} - -static microError * -_start_service_callbacks(microService *m) -{ - natsStatus s = NATS_OK; - - if (m == NULL) - return micro_ErrorInvalidArg; - - // Extra reference to the service as long as its callbacks are registered. - _retain_service(m); - - s = natsLib_startServiceCallbacks(m); - if (s != NATS_OK) - { - _release_service(m); - } - - return micro_ErrorFromStatus(s); -} - -static microError * -_services_for_connection(microService ***to_call, int *num_microservices, natsConnection *nc) -{ - natsMutex *mu = natsLib_getServiceCallbackMutex(); - natsHash *h = natsLib_getAllServicesToCallback(); - microService *m = NULL; - microService **p = NULL; - natsHashIter iter; - int n = 0; - int i; - - natsMutex_Lock(mu); - - natsHashIter_Init(&iter, h); - while (natsHashIter_Next(&iter, NULL, (void **)&m)) - if (m->nc == nc) - n++; - natsHashIter_Done(&iter); - if (n > 0) - { - p = NATS_CALLOC(n, sizeof(microService *)); - if (p == NULL) - { - natsMutex_Unlock(mu); - return micro_ErrorOutOfMemory; - } - - natsHashIter_Init(&iter, h); - i = 0; - while (natsHashIter_Next(&iter, NULL, (void **)&m)) - { - if (m->nc == nc) - { - _retain_service(m); // for the callback - p[i++] = m; - } - } - natsHashIter_Done(&iter); - } - - natsMutex_Unlock(mu); - - *to_call = p; - *num_microservices = n; - return NULL; -} - -static void -_on_connection_closed(natsConnection *nc, void *ignored) -{ - microService *m = NULL; - microService **to_call = NULL; - microError *err = NULL; - int n = 0; - int i; - - err = _services_for_connection(&to_call, &n, nc); - if (err != NULL) - { - microError_Ignore(err); - return; - } - - for (i = 0; i < n; i++) - { - m = to_call[i]; - microError_Ignore(microService_Stop(m)); - - _release_service(m); - } - - NATS_FREE(to_call); -} - -static void -_on_service_error(microService *m, const char *subject, natsStatus s) -{ - microEndpoint *ep = NULL; - microError *err = NULL; - - if (m == NULL) - return; - - _lock_service(m); - for (ep = m->first_ep; - (ep != NULL) && !micro_match_endpoint_subject(ep->subject, subject); - ep = ep->next) - ; - micro_retain_endpoint(ep); // for the callback - _unlock_service(m); - - if (ep != NULL) - { - if (m->cfg->ErrHandler != NULL) - (*m->cfg->ErrHandler)(m, ep, s); - - err = microError_Wrapf(micro_ErrorFromStatus(s), "NATS error on endpoint %s", ep->subject); - micro_update_last_error(ep, err); - microError_Destroy(err); - } - micro_release_endpoint(ep); // after the callback - - // TODO: Should we stop the service? The Go client does. - microError_Ignore(microService_Stop(m)); -} - -static void -_on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *not_used) -{ - microService *m = NULL; - microService **to_call = NULL; - microError *err = NULL; - const char *subject = NULL; - int n = 0; - int i; - - if (sub == NULL) - { - return; - } - subject = natsSubscription_GetSubject(sub); - - // `to_call` will have a list of retained service pointers. - err = _services_for_connection(&to_call, &n, nc); - if (err != NULL) - { - microError_Ignore(err); - return; - } - - for (i = 0; i < n; i++) - { - m = to_call[i]; - _on_service_error(m, subject, s); - _release_service(m); // release the extra ref in `to_call`. - } - - NATS_FREE(to_call); -} - -static microError * -_wrap_connection_event_callbacks(microService *m) -{ - microError *err = NULL; - - if ((m == NULL) || (m->nc == NULL) || (m->nc->opts == NULL)) - return micro_ErrorInvalidArg; - - // The new service must be in the list for this to work. - MICRO_CALL(err, _start_service_callbacks(m)); - MICRO_CALL(err, micro_ErrorFromStatus( - natsOptions_setMicroCallbacks(m->nc->opts, _on_connection_closed, _on_error))); - - return microError_Wrapf(err, "failed to wrap connection event callbacks"); -} - -microError * -microService_AddGroup(microGroup **new_group, microService *m, const char *prefix) -{ - if ((m == NULL) || (new_group == NULL) || (prefix == NULL)) - return micro_ErrorInvalidArg; - - *new_group = NATS_CALLOC(1, sizeof(microGroup) + - strlen(prefix) + 1); // "prefix\0" - if (new_group == NULL) - { - return micro_ErrorOutOfMemory; - } - - memcpy((*new_group)->prefix, prefix, strlen(prefix) + 1); - (*new_group)->m = m; - (*new_group)->next = m->groups; - m->groups = *new_group; - - return NULL; -} - -microError * -microGroup_AddGroup(microGroup **new_group, microGroup *parent, const char *prefix) -{ - char *p; - size_t len; - - if ((parent == NULL) || (new_group == NULL) || (prefix == NULL)) - return micro_ErrorInvalidArg; - - *new_group = NATS_CALLOC(1, sizeof(microGroup) + - strlen(parent->prefix) + 1 + // "parent_prefix." - strlen(prefix) + 1); // "prefix\0" - if (new_group == NULL) - { - return micro_ErrorOutOfMemory; - } - - p = (*new_group)->prefix; - len = strlen(parent->prefix); - memcpy(p, parent->prefix, len); - p[len] = '.'; - p += len + 1; - memcpy(p, prefix, strlen(prefix) + 1); - (*new_group)->m = parent->m; - (*new_group)->next = parent->m->groups; - parent->m->groups = *new_group; - - return NULL; -} - -natsConnection * -microService_GetConnection(microService *m) -{ - if (m == NULL) - return NULL; - return m->nc; -} - -microError * -microService_GetInfo(microServiceInfo **new_info, microService *m) -{ - microError *err = NULL; - microServiceInfo *info = NULL; - microEndpoint *ep = NULL; - int len; - - if ((new_info == NULL) || (m == NULL) || (m->service_mu == NULL)) - return micro_ErrorInvalidArg; - - info = NATS_CALLOC(1, sizeof(microServiceInfo)); - if (info == NULL) - return micro_ErrorOutOfMemory; - - MICRO_CALL(err, micro_strdup((char **)&info->Name, m->cfg->Name)); - MICRO_CALL(err, micro_strdup((char **)&info->Version, m->cfg->Version)); - MICRO_CALL(err, micro_strdup((char **)&info->Description, m->cfg->Description)); - MICRO_CALL(err, micro_strdup((char **)&info->Id, m->id)); - MICRO_CALL(err, micro_ErrorFromStatus( - nats_cloneMetadata(&info->Metadata, m->cfg->Metadata))); - - if (err == NULL) - { - info->Type = MICRO_INFO_RESPONSE_TYPE; - - _lock_service(m); - - len = 0; - for (ep = m->first_ep; ep != NULL; ep = ep->next) - { - if ((!ep->is_monitoring_endpoint) && (ep->subject != NULL)) - len++; - } - - // Overallocate subjects, will filter out internal ones. - info->Endpoints = NATS_CALLOC(len, sizeof(microEndpointInfo)); - if (info->Endpoints == NULL) - { - err = micro_ErrorOutOfMemory; - } - - len = 0; - for (ep = m->first_ep; (err == NULL) && (ep != NULL); ep = ep->next) - { - if ((!ep->is_monitoring_endpoint) && (ep->subject != NULL)) - { - MICRO_CALL(err, micro_strdup((char **)&info->Endpoints[len].Name, ep->name)); - MICRO_CALL(err, micro_strdup((char **)&info->Endpoints[len].Subject, ep->subject)); - MICRO_CALL(err, micro_ErrorFromStatus( - nats_cloneMetadata(&info->Endpoints[len].Metadata, ep->config->Metadata))); - if (err == NULL) - { - len++; - info->EndpointsLen = len; - } - } - } - _unlock_service(m); - } - - if (err != NULL) - { - microServiceInfo_Destroy(info); - return err; - } - - *new_info = info; - return NULL; -} - -void microServiceInfo_Destroy(microServiceInfo *info) -{ - int i; - - if (info == NULL) - return; - - // casts to quiet the compiler. - for (i = 0; i < info->EndpointsLen; i++) - { - NATS_FREE((char *)info->Endpoints[i].Name); - NATS_FREE((char *)info->Endpoints[i].Subject); - nats_freeMetadata(&info->Endpoints[i].Metadata); - } - NATS_FREE((char *)info->Endpoints); - NATS_FREE((char *)info->Name); - NATS_FREE((char *)info->Version); - NATS_FREE((char *)info->Description); - NATS_FREE((char *)info->Id); - nats_freeMetadata(&info->Metadata); - NATS_FREE(info); -} - -microError * -microService_GetStats(microServiceStats **new_stats, microService *m) -{ - microError *err = NULL; - microServiceStats *stats = NULL; - microEndpoint *ep = NULL; - int len; - long double avg = 0.0; - - if ((new_stats == NULL) || (m == NULL) || (m->service_mu == NULL)) - return micro_ErrorInvalidArg; - - stats = NATS_CALLOC(1, sizeof(microServiceStats)); - if (stats == NULL) - return micro_ErrorOutOfMemory; - - MICRO_CALL(err, micro_strdup((char **)&stats->Name, m->cfg->Name)); - MICRO_CALL(err, micro_strdup((char **)&stats->Version, m->cfg->Version)); - MICRO_CALL(err, micro_strdup((char **)&stats->Id, m->id)); - - if (err == NULL) - { - stats->Started = m->started; - stats->Type = MICRO_STATS_RESPONSE_TYPE; - - _lock_service(m); - - len = 0; - for (ep = m->first_ep; ep != NULL; ep = ep->next) - { - if ((ep != NULL) && (!ep->is_monitoring_endpoint)) - len++; - } - - // Allocate the actual structs, not pointers. - stats->Endpoints = NATS_CALLOC(len, sizeof(microEndpointStats)); - if (stats->Endpoints == NULL) - { - err = micro_ErrorOutOfMemory; - } - - len = 0; - for (ep = m->first_ep; ((err == NULL) && (ep != NULL)); ep = ep->next) - { - if ((ep != NULL) && (!ep->is_monitoring_endpoint) && (ep->endpoint_mu != NULL)) - { - micro_lock_endpoint(ep); - // copy the entire struct, including the last error buffer. - stats->Endpoints[len] = ep->stats; - - MICRO_CALL(err, micro_strdup((char **)&stats->Endpoints[len].Name, ep->name)); - MICRO_CALL(err, micro_strdup((char **)&stats->Endpoints[len].Subject, ep->subject)); - if (err == NULL) - { - avg = (long double)ep->stats.ProcessingTimeSeconds * 1000000000.0 + (long double)ep->stats.ProcessingTimeNanoseconds; - avg = avg / (long double)ep->stats.NumRequests; - stats->Endpoints[len].AverageProcessingTimeNanoseconds = (int64_t)avg; - len++; - stats->EndpointsLen = len; - } - micro_unlock_endpoint(ep); - } - } - - _unlock_service(m); - } - - if (err != NULL) - { - microServiceStats_Destroy(stats); - return err; - } - *new_stats = stats; - return NULL; -} - -void microServiceStats_Destroy(microServiceStats *stats) -{ - int i; - - if (stats == NULL) - return; - - for (i = 0; i < stats->EndpointsLen; i++) - { - NATS_FREE((char *)stats->Endpoints[i].Name); - NATS_FREE((char *)stats->Endpoints[i].Subject); - } - NATS_FREE(stats->Endpoints); - NATS_FREE((char *)stats->Name); - NATS_FREE((char *)stats->Version); - NATS_FREE((char *)stats->Id); - NATS_FREE(stats); -} diff --git a/src/micro_client.c b/src/micro_client.c deleted file mode 100644 index efb658571..000000000 --- a/src/micro_client.c +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2023 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 "microp.h" -#include "conn.h" - -microError * -micro_NewClient(microClient **new_client, natsConnection *nc, microClientConfig *cfg) -{ - microClient *client = NULL; - - if (new_client == NULL) - return micro_ErrorInvalidArg; - - client = NATS_CALLOC(1, sizeof(microClient)); - if (client == NULL) - return micro_ErrorOutOfMemory; - - natsConn_retain(nc); - client->nc = nc; - *new_client = client; - return NULL; -} - -void microClient_Destroy(microClient *client) -{ - if (client == NULL) - return; - - natsConn_release(client->nc); - NATS_FREE(client); -} - -microError * -microClient_DoRequest(natsMsg **reply, microClient *client, const char *subject, const char *data, int data_len) -{ - natsStatus s = NATS_OK; - microError *err = NULL; - natsMsg *msg = NULL; - - if ((client == NULL) || (reply == NULL)) - return micro_ErrorInvalidArg; - - s = natsConnection_Request(&msg, client->nc, subject, data, data_len, 5000); - if (s != NATS_OK) - { - return microError_Wrapf(micro_ErrorFromStatus(s), "request failed"); - } - - err = micro_is_error_message(s, msg); - if (err == NULL) - { - *reply = msg; - } - return err; -} diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c deleted file mode 100644 index c5149e57a..000000000 --- a/src/micro_endpoint.c +++ /dev/null @@ -1,432 +0,0 @@ -// Copyright 2023 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 - -#include "microp.h" -#include "util.h" - -static microError *_dup_with_prefix(char **dst, const char *prefix, const char *src); - -static void _handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); - -static void _retain_endpoint(microEndpoint *ep, bool lock); -static void _release_endpoint(microEndpoint *ep); - -microError * -micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal) -{ - microError *err = NULL; - microEndpoint *ep = NULL; - const char *subj; - - if (cfg == NULL) - return microError_Wrapf(micro_ErrorInvalidArg, "NULL endpoint config"); - if (!micro_is_valid_name(cfg->Name)) - return microError_Wrapf(micro_ErrorInvalidArg, "invalid endpoint name %s", cfg->Name); - if (cfg->Handler == NULL) - return microError_Wrapf(micro_ErrorInvalidArg, "NULL endpoint request handler for %s", cfg->Name); - - if ((cfg->Subject != NULL) && !micro_is_valid_subject(cfg->Subject)) - return micro_ErrorInvalidArg; - - subj = nats_IsStringEmpty(cfg->Subject) ? cfg->Name : cfg->Subject; - - ep = NATS_CALLOC(1, sizeof(microEndpoint)); - if (ep == NULL) - return micro_ErrorOutOfMemory; - ep->is_monitoring_endpoint = is_internal; - ep->m = m; - - MICRO_CALL(err, micro_ErrorFromStatus(natsMutex_Create(&ep->endpoint_mu))); - MICRO_CALL(err, micro_clone_endpoint_config(&ep->config, cfg)); - MICRO_CALL(err, micro_strdup(&ep->name, cfg->Name)); - MICRO_CALL(err, _dup_with_prefix(&ep->subject, prefix, subj)); - if (err != NULL) - { - micro_free_endpoint(ep); - return err; - } - - *new_ep = ep; - return NULL; -} - -microError * -micro_start_endpoint(microEndpoint *ep) -{ - natsStatus s = NATS_OK; - natsSubscription *sub = NULL; - - if ((ep->subject == NULL) || (ep->config == NULL) || (ep->config->Handler == NULL) || (ep->m == NULL)) - // nothing to do - return NULL; - - // reset the stats. - memset(&ep->stats, 0, sizeof(ep->stats)); - - if (ep->is_monitoring_endpoint) - s = natsConnection_Subscribe(&sub, ep->m->nc, ep->subject, _handle_request, ep); - else - s = natsConnection_QueueSubscribe(&sub, ep->m->nc, ep->subject, MICRO_QUEUE_GROUP, _handle_request, ep); - - if (s == NATS_OK) - { - // extra retain for the subscription since we'll need to hold it until - // on_complete. - micro_lock_endpoint(ep); - ep->refs++; - ep->sub = sub; - ep->is_draining = false; - micro_unlock_endpoint(ep); - - natsSubscription_SetOnCompleteCB(sub, micro_release_on_endpoint_complete, ep); - } - else - { - natsSubscription_Destroy(sub); // likely always a no-op. - } - - return micro_ErrorFromStatus(s); -} - -microError * -micro_stop_endpoint(microEndpoint *ep) -{ - natsStatus s = NATS_OK; - natsSubscription *sub = NULL; - - if ((ep == NULL) || (ep->m == NULL)) - return NULL; - - micro_lock_endpoint(ep); - sub = ep->sub; - - if (ep->is_draining || natsConnection_IsClosed(ep->m->nc) || !natsSubscription_IsValid(sub)) - { - // If stopping, _release_on_endpoint_complete will take care of - // finalizing, nothing else to do. In other cases - // _release_on_endpoint_complete has already been called. - micro_unlock_endpoint(ep); - return NULL; - } - - ep->is_draining = true; - micro_unlock_endpoint(ep); - - // When the drain is complete, will release the final ref on ep. - s = natsSubscription_Drain(sub); - if (s != NATS_OK) - { - return microError_Wrapf(micro_ErrorFromStatus(s), - "failed to stop endpoint %s: failed to drain subscription", ep->name); - } - - return NULL; -} - -void micro_retain_endpoint(microEndpoint *ep) -{ - if (ep == NULL) - return; - - micro_lock_endpoint(ep); - - ep->refs++; - - micro_unlock_endpoint(ep); -} - -void micro_release_endpoint(microEndpoint *ep) -{ - int refs; - - if (ep == NULL) - return; - - micro_lock_endpoint(ep); - - refs = --(ep->refs); - - micro_unlock_endpoint(ep); - - if (refs == 0) - micro_free_endpoint(ep); -} - -void micro_free_endpoint(microEndpoint *ep) -{ - if (ep == NULL) - return; - - NATS_FREE(ep->name); - NATS_FREE(ep->subject); - natsSubscription_Destroy(ep->sub); - natsMutex_Destroy(ep->endpoint_mu); - micro_free_cloned_endpoint_config(ep->config); - NATS_FREE(ep); -} - -static void -_update_last_error(microEndpoint *ep, microError *err) -{ - ep->stats.NumErrors++; - microError_String(err, ep->stats.LastErrorString, sizeof(ep->stats.LastErrorString)); -} - -static void -_handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - microError *err = NULL; - microError *service_err = NULL; - microEndpoint *ep = (microEndpoint *)closure; - microService *m; - microEndpointStats *stats = NULL; - microRequestHandler handler; - microRequest *req = NULL; - int64_t start, elapsed_ns = 0, full_s; - - if ((ep == NULL) || (ep->endpoint_mu == NULL) || (ep->config == NULL) || (ep->config->Handler == NULL)) - { - // This would be a bug, we should not have received a message on this - // subscription. - return; - } - - stats = &ep->stats; - m = ep->m; - handler = ep->config->Handler; - - err = micro_new_request(&req, m, ep, msg); - if (err == NULL) - { - // handle the request. - start = nats_NowInNanoSeconds(); - service_err = handler(req); - if (service_err != NULL) - { - // if the handler returned an error, we attempt to respond with it. - // Note that if the handler chose to do its own RespondError which - // fails, and then the handler returns its error - we'll try to - // RespondError again, double-counting the error. - err = microRequest_RespondError(req, service_err); - } - - elapsed_ns = nats_NowInNanoSeconds() - start; - } - - // Update stats. - micro_lock_endpoint(ep); - stats->NumRequests++; - stats->ProcessingTimeNanoseconds += elapsed_ns; - full_s = stats->ProcessingTimeNanoseconds / 1000000000; - stats->ProcessingTimeSeconds += full_s; - stats->ProcessingTimeNanoseconds -= full_s * 1000000000; - _update_last_error(ep, err); - micro_unlock_endpoint(ep); - - microError_Destroy(err); - micro_free_request(req); - natsMsg_Destroy(msg); -} - -void micro_update_last_error(microEndpoint *ep, microError *err) -{ - if (err == NULL || ep == NULL) - return; - - micro_lock_endpoint(ep); - _update_last_error(ep, err); - micro_unlock_endpoint(ep); -} - -bool micro_is_valid_name(const char *name) -{ - int i; - int len; - - if (name == NULL) - return false; - - len = (int)strlen(name); - if (len == 0) - return false; - - for (i = 0; i < len; i++) - { - if (!isalnum(name[i]) && (name[i] != '_') && (name[i] != '-')) - return false; - } - return true; -} - -bool micro_is_valid_subject(const char *subject) -{ - int i; - int len; - - if (subject == NULL) - return false; - - len = (int)strlen(subject); - if (len == 0) - return false; - - for (i = 0; i < len - 1; i++) - { - if ((subject[i] == ' ') || (subject[i] == '>')) - return false; - } - - if ((subject[i] == ' ')) - return false; - - return true; -} - -static inline microError * -_new_endpoint_config(microEndpointConfig **ptr) -{ - *ptr = NATS_CALLOC(1, sizeof(microEndpointConfig)); - return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; -} - -microError * -micro_clone_endpoint_config(microEndpointConfig **out, microEndpointConfig *cfg) -{ - microError *err = NULL; - microEndpointConfig *new_cfg = NULL; - - if (out == NULL) - return micro_ErrorInvalidArg; - - if (cfg == NULL) - { - *out = NULL; - return NULL; - } - - err = _new_endpoint_config(&new_cfg); - if (err == NULL) - { - memcpy(new_cfg, cfg, sizeof(microEndpointConfig)); - } - - MICRO_CALL(err, micro_strdup((char **)&new_cfg->Name, cfg->Name)); - MICRO_CALL(err, micro_strdup((char **)&new_cfg->Subject, cfg->Subject)); - MICRO_CALL(err, micro_ErrorFromStatus( - nats_cloneMetadata(&new_cfg->Metadata, cfg->Metadata))); - - if (err != NULL) - { - micro_free_cloned_endpoint_config(new_cfg); - return err; - } - - *out = new_cfg; - return NULL; -} - -void micro_free_cloned_endpoint_config(microEndpointConfig *cfg) -{ - if (cfg == NULL) - return; - - // the strings are declared const for the public, but in a clone these need - // to be freed. - NATS_FREE((char *)cfg->Name); - NATS_FREE((char *)cfg->Subject); - nats_freeMetadata(&cfg->Metadata); - - NATS_FREE(cfg); -} - -bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_subject) -{ - const char *e = ep_subject; - const char *a = actual_subject; - const char *etok, *enext; - int etok_len; - bool last_etok = false; - const char *atok, *anext; - int atok_len; - bool last_atok = false; - - if (e == NULL || a == NULL) - return false; - - while (true) - { - enext = strchr(e, '.'); - if (enext == NULL) - { - enext = e + strlen(e); - last_etok = true; - } - etok = e; - etok_len = (int)(enext - e); - e = enext + 1; - - anext = strchr(a, '.'); - if (anext == NULL) - { - anext = a + strlen(a); - last_atok = true; - } - atok = a; - atok_len = (int)(anext - a); - a = anext + 1; - - if (last_etok) - { - if (etok_len == 1 && etok[0] == '>') - return true; - - if (!last_atok) - return false; - } - if (!(etok_len == 1 && etok[0] == '*') && - !(etok_len == atok_len && strncmp(etok, atok, etok_len) == 0)) - { - return false; - } - if (last_atok) - { - return last_etok; - } - } -} - -static microError *_dup_with_prefix(char **dst, const char *prefix, const char *src) -{ - size_t len = strlen(src) + 1; - char *p; - - if (!nats_IsStringEmpty(prefix)) - len += strlen(prefix) + 1; - - *dst = NATS_CALLOC(1, len); - if (*dst == NULL) - return micro_ErrorOutOfMemory; - - p = *dst; - if (!nats_IsStringEmpty(prefix)) - { - len = strlen(prefix); - memcpy(p, prefix, len); - p[len] = '.'; - p += len + 1; - } - memcpy(p, src, strlen(src) + 1); - return NULL; -} diff --git a/src/micro_error.c b/src/micro_error.c deleted file mode 100644 index 4b64a4088..000000000 --- a/src/micro_error.c +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright 2023 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 - -#include "microp.h" - -static microError _errorOutOfMemory = { - .is_internal = true, - .status = NATS_NO_MEMORY, - .message = "out of memory", -}; - -static microError _errorInvalidArg = { - .is_internal = true, - .status = NATS_INVALID_ARG, - .message = "invalid function argument", -}; - -static microError _errorInvalidFormat = { - .is_internal = true, - .status = NATS_INVALID_ARG, - .message = "invalid format string", -}; - -microError *micro_ErrorOutOfMemory = &_errorOutOfMemory; -microError *micro_ErrorInvalidArg = &_errorInvalidArg; - -static microError * -verrorf(natsStatus s, int code, const char *format, va_list args) -{ - microError *err = NULL; - char *ptr; - int message_len = 0; - - va_list args2; - va_copy(args2, args); - - if (format == NULL) - format = ""; - - // Do not use nats_vsnprintf here since we want to calculate the size of - // the resulting formatted string. On Windows, that would fail. Use - // that instead. - message_len = nats_vscprintf(format, args); - if (message_len < 0) - { - va_end(args2); - return &_errorInvalidFormat; - } - - err = NATS_CALLOC(1, sizeof(microError) + message_len + 1); - if (err == NULL) - { - va_end(args2); - return &_errorOutOfMemory; - } - - ptr = (char *)(err) + sizeof(microError); - nats_vsnprintf(ptr, message_len + 1, format, args2); - va_end(args2); - err->message = (const char *)ptr; - - err->code = code; - err->status = s; - - return err; -} - -microError * -micro_Errorf(const char *format, ...) -{ - microError *err = NULL; - va_list args; - - va_start(args, format); - err = verrorf(NATS_OK, 0, format, args); - va_end(args); - return err; -} - -microError * -micro_ErrorfCode(int code, const char *format, ...) -{ - microError *err = NULL; - va_list args; - - va_start(args, format); - err = verrorf(NATS_OK, code, format, args); - va_end(args); - return err; -} - -microError * -micro_ErrorFromStatus(natsStatus s) -{ - microError *err = NULL; - const char *message = natsStatus_GetText(s); - size_t message_len = strlen(message); - char *ptr; - - if (s == NATS_OK) - return NULL; - - err = NATS_CALLOC(1, sizeof(microError) + message_len + 1); - if (err == NULL) - return &_errorOutOfMemory; - - ptr = (char *)(err) + sizeof(microError); - memcpy(ptr, message, message_len + 1); - err->message = ptr; - err->status = s; - return err; -} - -microError * -micro_is_error_message(natsStatus status, natsMsg *msg) -{ - microError *err = NULL; - const char *c = NULL, *d = NULL; - bool is_service_error; - bool is_nats_error = (status != NATS_OK); - int code = 0; - - if (msg != NULL) - { - natsMsgHeader_Get(msg, MICRO_ERROR_CODE_HDR, &c); - natsMsgHeader_Get(msg, MICRO_ERROR_HDR, &d); - } - if (!nats_IsStringEmpty(c)) - { - code = atoi(c); - } - is_service_error = (code != 0) || !nats_IsStringEmpty(d); - - if (is_service_error && !is_nats_error) - { - return micro_ErrorfCode(code, d); - } - else if (!is_service_error && is_nats_error) - { - return micro_ErrorFromStatus(status); - } - else if (is_service_error && is_nats_error) - { - err = microError_Wrapf(micro_ErrorFromStatus(status), d); - err->code = code; - return err; - } - - return NULL; -} - -microError * -microError_Wrapf(microError *err, const char *format, ...) -{ - va_list args; - microError *new_err = NULL; - - if (err == NULL) - return NULL; - - va_start(args, format); - new_err = verrorf(NATS_OK, 0, format, args); - va_end(args); - - new_err->cause = err; - return new_err; -} - -const char * -microError_String(microError *err, char *buf, size_t size) -{ - size_t used = 0; - const char *caused; - - if (buf == NULL) - return ""; - if (err == NULL) - { - snprintf(buf, size, "null"); - return buf; - } - - if (err->status != NATS_OK) - { - used += snprintf(buf + used, size - used, "status %u: ", err->status); - } - if (err->code != 0) - { - used += snprintf(buf + used, size - used, "code %d: ", err->code); - } - used += snprintf(buf + used, size - used, "%s", err->message); - - if (err->cause != NULL) - { - used += snprintf(buf + used, size - used, ": "); - caused = microError_String(err->cause, buf + used, size - used); - used += strlen(caused); - } - return buf; -} - -natsStatus -microError_Status(microError *err) -{ - if (err == NULL) - return NATS_OK; - - if (err->status != NATS_OK) - return err->status; - - return microError_Status(err->cause); -} - -void microError_Destroy(microError *err) -{ - if ((err == NULL) || err->is_internal) - return; - - microError_Destroy(err->cause); - NATS_FREE(err); -} diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c deleted file mode 100644 index 80f1188d6..000000000 --- a/src/micro_monitoring.c +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright 2023 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 - -#include "microp.h" -#include "util.h" - -static microError *marshal_ping(natsBuffer **new_buf, microService *m); -static microError *handle_ping(microRequest *req); -static microError *marshal_info(natsBuffer **new_buf, microServiceInfo *info); -static microError *handle_info(microRequest *req); -static microError *marshal_stats(natsBuffer **new_buf, microServiceStats *stats); -static microError *handle_stats(microRequest *req); - -static microError * -add_internal_handler(microService *m, const char *verb, const char *kind, const char *id, const char *name, microRequestHandler handler); -static microError * -add_verb_handlers(microService *m, const char *verb, microRequestHandler handler); -static microError * -new_dotted_subject(char **new_subject, int count, ...); - -microError * -micro_init_monitoring(microService *m) -{ - microError *err = NULL; - MICRO_CALL(err, add_verb_handlers(m, MICRO_PING_VERB, handle_ping)); - MICRO_CALL(err, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); - MICRO_CALL(err, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); - return err; -} - -static microError * -handle_ping(microRequest *req) -{ - microError *err = NULL; - microService *m = microRequest_GetService(req); - natsBuffer *buf = NULL; - - if ((m == NULL) || (m->cfg == NULL)) - return micro_ErrorInvalidArg; // Should not happen - - MICRO_CALL(err, marshal_ping(&buf, m)); - MICRO_CALL(err, microRequest_Respond(req, natsBuf_Data(buf), natsBuf_Len(buf))); - - natsBuf_Destroy(buf); - return err; -} - -static microError * -handle_info(microRequest *req) -{ - microError *err = NULL; - microService *m = microRequest_GetService(req); - microServiceInfo *info = NULL; - natsBuffer *buf = NULL; - - if ((m == NULL) || (m->cfg == NULL)) - return micro_ErrorInvalidArg; // Should not happen - - MICRO_CALL(err, microService_GetInfo(&info, m)); - MICRO_CALL(err, marshal_info(&buf, info)); - MICRO_CALL(err, microRequest_Respond(req, natsBuf_Data(buf), natsBuf_Len(buf))); - - natsBuf_Destroy(buf); - microServiceInfo_Destroy(info); - return err; -} - -static microError * -handle_stats_internal(microRequest *req) -{ - microError *err = NULL; - microService *m = microRequest_GetService(req); - microServiceStats *stats = NULL; - natsBuffer *buf = NULL; - - if ((m == NULL) || (m == NULL)) - return micro_ErrorInvalidArg; // Should not happen - - MICRO_CALL(err, microService_GetStats(&stats, req->Service)); - MICRO_CALL(err, marshal_stats(&buf, stats)); - MICRO_CALL(err, microRequest_Respond(req, natsBuf_Data(buf), natsBuf_Len(buf))); - - natsBuf_Destroy(buf); - microServiceStats_Destroy(stats); - return err; -} - -static microError * -handle_stats(microRequest *req) -{ - microService *m = microRequest_GetService(req); - - if ((m == NULL) || (m->cfg == NULL)) - return micro_ErrorInvalidArg; // Should not happen - - if (m->cfg->StatsHandler != NULL) - return m->cfg->StatsHandler(req); - else - return handle_stats_internal(req); -} - -static microError * -new_dotted_subject(char **new_subject, int count, ...) -{ - va_list args; - int i; - size_t len, n; - char *result, *p; - - va_start(args, count); - len = 0; - for (i = 0; i < count; i++) - { - if (i > 0) - { - len++; /* for the dot */ - } - len += strlen(va_arg(args, char *)); - } - va_end(args); - - result = NATS_CALLOC(len + 1, 1); - if (result == NULL) - { - return micro_ErrorInvalidArg; - } - - len = 0; - va_start(args, count); - for (i = 0; i < count; i++) - { - if (i > 0) - { - result[len++] = '.'; - } - p = va_arg(args, char *); - n = strlen(p); - memcpy(result + len, p, n); - len += n; - } - va_end(args); - - *new_subject = result; - return NULL; -} - -microError * -micro_new_control_subject(char **newSubject, const char *verb, const char *name, const char *id) -{ - if (nats_IsStringEmpty(name) && !nats_IsStringEmpty(id)) - { - return micro_Errorf("service name is required when id is provided: %s", id); - } - - else if (nats_IsStringEmpty(name) && nats_IsStringEmpty(id)) - return new_dotted_subject(newSubject, 2, MICRO_API_PREFIX, verb); - else if (nats_IsStringEmpty(id)) - return new_dotted_subject(newSubject, 3, MICRO_API_PREFIX, verb, name); - else - return new_dotted_subject(newSubject, 4, MICRO_API_PREFIX, verb, name, id); -} - -static microError * -add_internal_handler(microService *m, const char *verb, const char *kind, - const char *id, const char *name, microRequestHandler handler) -{ - microError *err = NULL; - char *subj = NULL; - - err = micro_new_control_subject(&subj, verb, kind, id); - if (err != NULL) - return err; - - microEndpointConfig cfg = { - .Subject = subj, - .Name = name, - .Handler = handler, - }; - err = micro_add_endpoint(NULL, m, "", &cfg, true); - NATS_FREE(subj); - return err; -} - -// __verbHandlers generates control handlers for a specific verb. Each request -// generates 3 subscriptions, one for the general verb affecting all services -// written with the framework, one that handles all services of a particular -// kind, and finally a specific service instance. -static microError * -add_verb_handlers(microService *m, const char *verb, microRequestHandler handler) -{ - microError *err = NULL; - char name[1024]; - - snprintf(name, sizeof(name), "%s-all", verb); - err = add_internal_handler(m, verb, "", "", name, handler); - if (err == NULL) - { - snprintf(name, sizeof(name), "%s-kind", verb); - err = add_internal_handler(m, verb, m->cfg->Name, "", name, handler); - } - if (err == NULL) - { - err = add_internal_handler(m, verb, m->cfg->Name, m->id, verb, handler); - } - return err; -} - -// name and sep must be a string literal -#define IFOK_attr(_name, _value, _sep) \ - IFOK(s, natsBuf_Append(buf, "\"" _name "\":\"", -1)); \ - IFOK(s, natsBuf_Append(buf, (_value) != NULL ? (_value) : "", -1)); \ - IFOK(s, natsBuf_Append(buf, "\"" _sep, -1)); - -static microError * -marshal_ping(natsBuffer **new_buf, microService *m) -{ - natsBuffer *buf = NULL; - natsStatus s; - - s = natsBuf_Create(&buf, 1024); - if (s == NATS_OK) - { - s = natsBuf_AppendByte(buf, '{'); - IFOK_attr("name", m->cfg->Name, ","); - IFOK_attr("version", m->cfg->Version, ","); - IFOK_attr("id", m->id, ","); - IFOK_attr("type", MICRO_PING_RESPONSE_TYPE, ""); - IFOK(s, natsBuf_AppendByte(buf, '}')); - } - - if (s != NATS_OK) - { - natsBuf_Destroy(buf); - return micro_ErrorFromStatus(s); - } - - *new_buf = buf; - return NULL; -} - -static microError * -marshal_info(natsBuffer **new_buf, microServiceInfo *info) -{ - natsBuffer *buf = NULL; - natsStatus s; - int i; - - s = natsBuf_Create(&buf, 4096); - IFOK(s, natsBuf_AppendByte(buf, '{')); - - IFOK_attr("description", info->Description, ","); - - // "endpoints":{...} - if ((s == NATS_OK) && (info->EndpointsLen > 0)) - { - IFOK(s, natsBuf_Append(buf, "\"endpoints\":[", -1)); - for (i = 0; ((s == NATS_OK) && (i < info->EndpointsLen)); i++) - { - IFOK(s, natsBuf_AppendByte(buf, '{')); - IFOK_attr("name", info->Endpoints[i].Name, ""); - IFOK(s, nats_marshalMetadata(buf, true, "metadata", info->Endpoints[i].Metadata)); - IFOK(s, natsBuf_AppendByte(buf, ',')); - IFOK_attr("subject", info->Endpoints[i].Subject, ""); - IFOK(s, natsBuf_AppendByte(buf, '}')); // end endpoint - if (i != info->EndpointsLen - 1) - IFOK(s, natsBuf_AppendByte(buf, ',')); - } - IFOK(s, natsBuf_Append(buf, "],", 2)); - } - - IFOK_attr("id", info->Id, ""); - IFOK(s, nats_marshalMetadata(buf, true, "metadata", info->Metadata)); - IFOK(s, natsBuf_AppendByte(buf, ',')); - IFOK_attr("name", info->Name, ","); - IFOK_attr("type", info->Type, ","); - IFOK_attr("version", info->Version, ""); - IFOK(s, natsBuf_AppendByte(buf, '}')); - - if (s != NATS_OK) - { - natsBuf_Destroy(buf); - return microError_Wrapf(micro_ErrorFromStatus(s), "failed to marshal service info"); - } - *new_buf = buf; - return NULL; -} - -static microError * -marshal_stats(natsBuffer **new_buf, microServiceStats *stats) -{ - natsBuffer *buf = NULL; - natsStatus s; - int i; - char timebuf[128]; - microEndpointStats *ep; - - s = natsBuf_Create(&buf, 8 * 1024); - IFOK(s, natsBuf_AppendByte(buf, '{')); - IFOK_attr("id", stats->Id, ","); - IFOK_attr("name", stats->Name, ","); - IFOK_attr("type", stats->Type, ","); - IFOK(s, nats_EncodeTimeUTC(timebuf, sizeof(timebuf), stats->Started)); - IFOK_attr("started", timebuf, ","); - - if ((s == NATS_OK) && (stats->EndpointsLen > 0)) - { - IFOK(s, natsBuf_Append(buf, "\"endpoints\":[", -1)); - for (i = 0; i < stats->EndpointsLen; i++) - { - ep = &stats->Endpoints[i]; - IFOK(s, natsBuf_AppendByte(buf, '{')); - IFOK_attr("name", ep->Name, ","); - IFOK_attr("subject", ep->Subject, ","); - IFOK(s, nats_marshalLong(buf, false, "num_requests", ep->NumRequests)); - IFOK(s, nats_marshalLong(buf, true, "num_errors", ep->NumErrors)); - IFOK(s, nats_marshalDuration(buf, true, "average_processing_time", ep->AverageProcessingTimeNanoseconds)); - IFOK(s, natsBuf_AppendByte(buf, ',')); - IFOK_attr("last_error", ep->LastErrorString, ""); - IFOK(s, natsBuf_AppendByte(buf, '}')); - - if (i < (stats->EndpointsLen - 1)) - IFOK(s, natsBuf_AppendByte(buf, ',')); - } - IFOK(s, natsBuf_Append(buf, "],", 2)); - } - - IFOK_attr("version", stats->Version, ""); - IFOK(s, natsBuf_AppendByte(buf, '}')); - - if (s == NATS_OK) - { - *new_buf = buf; - return NULL; - } - else - { - natsBuf_Destroy(buf); - return microError_Wrapf(micro_ErrorFromStatus(s), "failed to marshal service info"); - } -} diff --git a/src/micro_request.c b/src/micro_request.c deleted file mode 100644 index 72159b7e3..000000000 --- a/src/micro_request.c +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright 2023 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 "microp.h" - -microError * -microRequest_Respond(microRequest *req, const char *data, size_t len) -{ - return microRequest_RespondCustom(req, NULL, data, len); -} - -microError * -microRequest_RespondError(microRequest *req, microError *err) -{ - return microRequest_RespondCustom(req, err, NULL, 0); -} - -microError * -microRequest_RespondCustom(microRequest *req, microError *service_error, const char *data, size_t len) -{ - natsMsg *msg = NULL; - natsStatus s = NATS_OK; - char buf[64]; - - if ((req == NULL) || (req->Message == NULL) || (req->Message->sub == NULL) || (req->Message->sub->conn == NULL)) - { - s = NATS_INVALID_ARG; - } - if (s == NATS_OK) - { - s = natsMsg_Create(&msg, natsMsg_GetReply(req->Message), NULL, data, (int)len); - } - if ((s == NATS_OK) && (service_error != NULL)) - { - micro_update_last_error(req->Endpoint, service_error); - if (service_error->status != NATS_OK) - { - s = natsMsgHeader_Set(msg, MICRO_STATUS_HDR, natsStatus_GetText(service_error->status)); - } - if (s == NATS_OK) - { - s = natsMsgHeader_Set(msg, MICRO_ERROR_HDR, service_error->message); - } - if (s == NATS_OK) - { - snprintf(buf, sizeof(buf), "%u", service_error->code); - s = natsMsgHeader_Set(msg, MICRO_ERROR_CODE_HDR, buf); - } - } - if (s == NATS_OK) - { - s = natsConnection_PublishMsg(req->Message->sub->conn, msg); - } - - microError_Destroy(service_error); - natsMsg_Destroy(msg); - return microError_Wrapf( - micro_ErrorFromStatus(s), - "microRequest_RespondErrorWithData failed"); -} - -microError * -microRequest_AddHeader(microRequest *req, const char *key, const char *value) -{ - return micro_ErrorFromStatus( - natsMsgHeader_Add(microRequest_GetMsg(req), key, value)); -} - -microError * -microRequest_DeleteHeader(microRequest *req, const char *key) -{ - return micro_ErrorFromStatus( - natsMsgHeader_Delete(microRequest_GetMsg(req), key)); -} - -natsConnection * -microRequest_GetConnection(microRequest *req) -{ - return ((req != NULL) && (req->Service != NULL)) ? req->Service->nc : NULL; -} - -const char * -microRequest_GetData(microRequest *req) -{ - return natsMsg_GetData(microRequest_GetMsg(req)); -} - -int microRequest_GetDataLength(microRequest *req) -{ - return natsMsg_GetDataLength(microRequest_GetMsg(req)); -} - -microEndpoint * -microRequest_GetEndpoint(microRequest *req) -{ - return (req != NULL) ? req->Endpoint : NULL; -} - -microError * -microRequest_GetHeaderKeys(microRequest *req, const char ***keys, int *count) -{ - return micro_ErrorFromStatus( - natsMsgHeader_Keys(microRequest_GetMsg(req), keys, count)); -} - -microError * -microRequest_GetHeaderValue(microRequest *req, const char *key, const char **value) -{ - return micro_ErrorFromStatus( - natsMsgHeader_Get(microRequest_GetMsg(req), key, value)); -} - -microError * -microRequest_GetHeaderValues(microRequest *req, const char *key, const char ***values, int *count) -{ - return micro_ErrorFromStatus( - natsMsgHeader_Values(microRequest_GetMsg(req), key, values, count)); -} - -natsMsg * -microRequest_GetMsg(microRequest *req) -{ - return (req != NULL) ? req->Message : NULL; -} - -const char * -microRequest_GetReply(microRequest *req) -{ - return natsMsg_GetReply(microRequest_GetMsg(req)); -} - -const char *microRequest_GetSubject(microRequest *req) -{ - return natsMsg_GetSubject(microRequest_GetMsg(req)); -} - -void *microRequest_GetServiceState(microRequest *req) -{ - if ((req == NULL) || (req->Service == NULL) || (req->Service->cfg == NULL)) - { - return NULL; - } - return req->Service->cfg->State; -} - -void *microRequest_GetEndpointState(microRequest *req) -{ - if ((req == NULL) || (req->Endpoint == NULL) || (req->Endpoint->config == NULL)) - { - return NULL; - } - return req->Endpoint->config->State; -} - -microError * -microRequest_SetHeader(microRequest *req, const char *key, const char *value) -{ - return micro_ErrorFromStatus( - natsMsgHeader_Set(microRequest_GetMsg(req), key, value)); -} - -microService * -microRequest_GetService(microRequest *req) -{ - return (req != NULL) ? req->Service : NULL; -} - -void micro_free_request(microRequest *req) -{ - NATS_FREE(req); -} - -microError * -micro_new_request(microRequest **new_request, microService *m, microEndpoint *ep, natsMsg *msg) -{ - microRequest *req = NULL; - - // endpoint is optional, service and message references are required. - if ((new_request == NULL) || (m == NULL) || (msg == NULL)) - return micro_ErrorInvalidArg; - - req = (microRequest *)NATS_CALLOC(1, sizeof(microRequest)); - if (req == NULL) - return micro_ErrorOutOfMemory; - - req->Message = msg; - req->Service = m; - req->Endpoint = ep; - *new_request = req; - return NULL; -} diff --git a/src/microp.h b/src/microp.h deleted file mode 100644 index 620d7a0e7..000000000 --- a/src/microp.h +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2023 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. - -#ifndef MICROP_H_ -#define MICROP_H_ - -#include "natsp.h" -#include "mem.h" - -#define MICRO_CALL(__err, __call) \ - if ((__err) == NULL) \ - { \ - __err = (__call); \ - } - -#define MICRO_DO(__err, __block) \ - if ((__err) == NULL) \ - __block; - -#define MICRO_QUEUE_GROUP "q" - -#define MICRO_DEFAULT_ENDPOINT_NAME "default" - -struct micro_error_s -{ - bool is_internal; - struct micro_error_s *cause; - natsStatus status; - int code; - const char *message; -}; - -struct micro_client_s -{ - natsConnection *nc; -}; - -struct micro_endpoint_s -{ - // The name and subject that the endpoint is listening on (may be different - // from one specified in config). - char *name; - char *subject; - - // A copy of the config provided to add_endpoint. - microEndpointConfig *config; - - // Retained/released by the service that owns the endpoint to avoid race - // conditions. - microService *m; - - // Monitoring endpoints are different in a few ways. For now, express it as - // a single flag but consider unbundling: - // - use_queue_group: Service endpoints use a queue group, monitoring - // endpoints don't. - // - forward_response_errors_to_async_handler: Service endpoints handle - // respond errors themselves, standard monitoring endpoints don't, so - // send the errors to the service async handler, if exists. - // - gather_stats: Monitoring endpoints don't need stats. - // - include_in_info: Monitoring endpoints are not listed in INFO - // responses. - bool is_monitoring_endpoint; - - // Mutex for starting/stopping the endpoint, and for updating the stats. - natsMutex *endpoint_mu; - int refs; - bool is_draining; - - // The subscription for the endpoint. If NULL, the endpoint is stopped. - natsSubscription *sub; - - // Endpoint stats. These are initialized only for running endpoints, and are - // cleared if the endpoint is stopped. - microEndpointStats stats; - - microEndpoint *next; -}; - -struct micro_group_s -{ - struct micro_service_s *m; - struct micro_group_s *next; - char prefix[]; -}; - -struct micro_service_s -{ - // these are set at initialization time time and do not change. - natsConnection *nc; - microServiceConfig *cfg; - char id[NUID_BUFFER_LEN + 1]; - - // groups are just convenient wrappers to make "prefixed" endpoints with - // AddEndpoint. They are added at initializaton time, so no need to lock. - struct micro_group_s *groups; - - // these are are updated concurrently with access as the service runs, so - // need to be protected by mutex. - natsMutex *service_mu; - int refs; - - struct micro_endpoint_s *first_ep; - - int64_t started; // UTC time expressed as number of nanoseconds since epoch. - bool stopped; -}; - -/** - * A microservice request. - * - * microRequest represents a request received by a microservice endpoint. - */ -struct micro_request_s -{ - /** - * @brief The NATS message underlying the request. - */ - natsMsg *Message; - - /** - * @brief A reference to the service that received the request. - */ - microService *Service; - - /** - * @brief A reference to the service that received the request. - */ - microEndpoint *Endpoint; -}; - -microError *micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); -microError *micro_clone_endpoint_config(microEndpointConfig **out, microEndpointConfig *cfg); -microError *micro_init_monitoring(microService *m); -microError *micro_is_error_message(natsStatus s, natsMsg *msg); -microError *micro_new_control_subject(char **newSubject, const char *verb, const char *name, const char *id); -microError *micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); -microError *micro_new_request(microRequest **new_request, microService *m, microEndpoint *ep, natsMsg *msg); -microError *micro_start_endpoint(microEndpoint *ep); -microError *micro_stop_endpoint(microEndpoint *ep); - -void micro_free_cloned_endpoint_config(microEndpointConfig *cfg); -void micro_free_endpoint(microEndpoint *ep); -void micro_free_request(microRequest *req); -void micro_release_endpoint(microEndpoint *ep); -void micro_release_on_endpoint_complete(void *closure); -void micro_retain_endpoint(microEndpoint *ep); -void micro_update_last_error(microEndpoint *ep, microError *err); - -bool micro_is_valid_name(const char *name); -bool micro_is_valid_subject(const char *subject); -bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_subject); - -static inline void micro_lock_endpoint(microEndpoint *ep) { natsMutex_Lock(ep->endpoint_mu); } -static inline void micro_unlock_endpoint(microEndpoint *ep) { natsMutex_Unlock(ep->endpoint_mu); } - -static inline microError * -micro_strdup(char **ptr, const char *str) -{ - // Make a strdup(NULL) be a no-op, so we don't have to check for NULL - // everywhere. - if (str == NULL) - { - *ptr = NULL; - return NULL; - } - *ptr = NATS_STRDUP(str); - if (*ptr == NULL) - return micro_ErrorOutOfMemory; - return NULL; -} - -#endif /* MICROP_H_ */ diff --git a/src/msg.c b/src/msg.c index 44e434e32..53c00d4b7 100644 --- a/src/msg.c +++ b/src/msg.c @@ -16,24 +16,23 @@ // Do this after including natsp.h in order to have some of the // GNU specific flag set first. #include -#include -#include "mem.h" +#include "hash.h" +#include "conn.h" #include "msg.h" -int -natsMsgHeader_encodedLen(natsMsg *msg) +int natsMessageHeader_encodedLen(natsMessage *msg) { natsStrHashIter iter; - char *key = NULL; - void *p = NULL; - int hl = 0; + char *key = NULL; + void *p = NULL; + int hl = 0; // Special case: if needsLift is true, it means that this is a message // that was received but never lifted (so for sure no header was added, // modified or removed. So return the current len of the encoded headers. - if (natsMsg_needsLift(msg)) - return msg->hdrLen; + // if (natsMessage_needsLift(msg)) + // return msg->hdr.len; // Here it could be that a message was created, some headers added but // then all were removed before the send. Returning 0 here means that @@ -43,17 +42,17 @@ natsMsgHeader_encodedLen(natsMsg *msg) if (msg->headers == NULL) return 0; - hl = HDR_LINE_LEN; + hl = nats_NATS10.len + _CRLF_LEN_; natsStrHashIter_Init(&iter, msg->headers); while (natsStrHashIter_Next(&iter, &key, &p)) { - natsHeaderValue *v = (natsHeaderValue*) p; + natsHeaderValue *v = (natsHeaderValue *)p; natsHeaderValue *c; for (c = v; c != NULL; c = c->next) { - hl += (int) strlen(key) + 2; // 2 for ": " - hl += (int) strlen(c->value) + _CRLF_LEN_; + hl += (int)strlen(key) + 2; // 2 for ": " + hl += (int)strlen(c->value) + _CRLF_LEN_; } } natsStrHashIter_Done(&iter); @@ -63,117 +62,94 @@ natsMsgHeader_encodedLen(natsMsg *msg) } natsStatus -natsMsgHeader_encode(natsBuffer *buf, natsMsg *msg) +natsMessageHeader_encode(natsBuf *buf, natsMessage *msg) { natsStrHashIter iter; - natsStatus s = NATS_OK; - char *key = NULL; - void *p = NULL; - - // See explanation in natsMsgHeader_encodedLen() - if (natsMsg_needsLift(msg)) - { - s = natsBuf_Append(buf, (const char*) msg->hdr, msg->hdrLen); - return NATS_UPDATE_ERR_STACK(s); - } - - // Based on decision in natsMsgHeader_encodedLen(), + natsStatus s = NATS_OK; + char *key = NULL; + void *p = NULL; + + // See explanation in natsMessageHeader_encodedLen() + // if (natsMessage_needsLift(msg)) + // { + // s = natsBuf_addString(buf, &msg->hdr); + // return NATS_UPDATE_ERR_STACK(s); + // } + // Based on decision in natsMessageHeader_encodedLen(), // getting here with NULL headers is likely a bug. if (msg->headers == NULL) return nats_setError(NATS_ERR, "%s", "trying to encode headers while there is none"); - s = natsBuf_Append(buf, HDR_LINE, HDR_LINE_LEN); - if (s == NATS_OK) + IFOK(s, natsBuf_addString(buf, &nats_NATS10)); + IFOK(s, natsBuf_addString(buf, &nats_CRLF)); + IFOK(s, ALWAYS_OK(natsStrHashIter_Init(&iter, msg->headers))); + while ((STILL_OK(s)) && natsStrHashIter_Next(&iter, &key, &p)) { - natsStrHashIter_Init(&iter, msg->headers); - while ((s == NATS_OK) && natsStrHashIter_Next(&iter, &key, &p)) - { - natsHeaderValue *v = (natsHeaderValue*) p; - natsHeaderValue *c; + natsHeaderValue *v = (natsHeaderValue *)p; + natsHeaderValue *c; - for (c = v; (s == NATS_OK) && (c != NULL); c = c->next) + for (c = v; (STILL_OK(s)) && (c != NULL); c = c->next) + { + IFOK(s, natsBuf_addCString(buf, key)); + IFOK(s, natsBuf_addB(buf, ':')); + IFOK(s, natsBuf_addB(buf, ' ')); + if (STILL_OK(s)) { - s = natsBuf_Append(buf, (const char*) key, (int) strlen(key)); - if (s == NATS_OK) - s = natsBuf_Append(buf, ": ", 2); - if (s == NATS_OK) + int vl = (int)strlen(c->value); + int pos = natsBuf_len(buf); + s = natsBuf_addBB(buf, (const uint8_t *)c->value, vl); + if (STILL_OK(s)) { - int vl = (int) strlen(c->value); - int pos = natsBuf_Len(buf); - - s = natsBuf_Append(buf, (const char*) c->value, vl); - if (s == NATS_OK) + uint8_t *ch = natsBuf_data(buf) + pos; + int i; + for (i = 0; i < vl; i++) { - char *ch = natsBuf_Data(buf)+pos; - int i; - - for (i=0; ineedFree && (v->value != NULL)) - NATS_FREE(v->value); - - if (all && v->next != NULL) - natsHeaderValue_free(v->next, all); - - NATS_FREE(v); -} - natsStatus natsHeaderValue_create(natsHeaderValue **retV, const char *value, bool makeCopy) { - natsStatus s = NATS_OK; - char *cv = (char*) value; - natsHeaderValue *v = NULL; - - *retV = NULL; - - v = NATS_MALLOC(sizeof(natsHeaderValue)); - if (v == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - if (makeCopy && value != NULL) - { - DUP_STRING(s, cv, value); - if (s != NATS_OK) - { - NATS_FREE(v); - return NATS_UPDATE_ERR_STACK(s); - } - } - v->value = cv; - v->needFree = makeCopy; - v->next = NULL; - *retV = v; + // natsStatus s = NATS_OK; + // char *cv = (char*) value; + // natsHeaderValue *v = NULL; + + // *retV = NULL; + + // v = NATS_MALLOC(sizeof(natsHeaderValue)); + // if (v == NULL) + // return nats_setDefaultError(NATS_NO_MEMORY); + + // if (makeCopy && value != NULL) + // { + // DUP_STRING(s, cv, value); + // if (s != NATS_OK) + // { + // NATS_FREE(v); + // return NATS_UPDATE_ERR_STACK(s); + // } + // } + // v->value = cv; + // v->needFree = makeCopy; + // v->next = NULL; + // *retV = v; return NATS_OK; } static natsStatus -_checkMsgAndKey(natsMsg *msg, const char *key) +_checkMsgAndKey(natsMessage *msg, const char *key) { if (msg == NULL) return nats_setError(NATS_INVALID_ARG, "%s", "message cannot be NULL"); @@ -184,12 +160,12 @@ _checkMsgAndKey(natsMsg *msg, const char *key) return NATS_OK; } -static char* +static char * _moveToLF(char *end, char *ptr) { while (ptr != end) { - if ((*ptr == '\r') && (*(ptr+1) == '\n')) + if ((*ptr == '\r') && (*(ptr + 1) == '\n')) return ++ptr; else ptr++; @@ -198,544 +174,496 @@ _moveToLF(char *end, char *ptr) } static natsStatus -_processKeyValue(int line, natsMsg *msg, char *endPtr, char **pPtr, char **lastKey) +_processKeyValue(int line, natsMessage *msg, char *endPtr, char **pPtr, char **lastKey) { - natsStatus s = NATS_OK; - char *ptr = *pPtr; - char *col = NULL; - char *key = NULL; - char *val = NULL; - natsHeaderValue *v = NULL; - bool ml = false; - char *start; - char *endval; - - start = ptr; - if (*ptr == '\r') - { - if ((++ptr == endPtr) || ((*ptr == '\n') && (++ptr == endPtr))) - { - *pPtr = ptr; - return NATS_OK; - } - return nats_setError(NATS_PROTOCOL_ERROR, "invalid start of a key: %s", start); - } - if (isspace((unsigned char) *ptr)) - { - if (line == 0) - return nats_setError(NATS_PROTOCOL_ERROR, "key cannot start with a space: %s", ptr); - - key = *lastKey; - ml = true; - } - else - { - col = strchr((const char*) ptr, (int) ':'); - if (col == NULL) - return nats_setError(NATS_PROTOCOL_ERROR, "column delimiter not found: %s", ptr); - - // Replace column char with \0 to terminate the key string. - key = ptr; - ptr = col+1; - (*col) = '\0'; - } - - while ((ptr != endPtr) && (isspace((unsigned char) *ptr))) - ptr++; - - if (ptr == endPtr) - return nats_setError(NATS_PROTOCOL_ERROR, "no value found for key %s", key); - - val = ptr; - // Now find the \r\n for this value - ptr = _moveToLF(endPtr, ptr); - if (ptr == endPtr) - return nats_setError(NATS_PROTOCOL_ERROR, "no CRLF found for value of key %s", key); - - // Trim right spaces and set to \0 to terminate the value string. - endval = ptr; - // Backtrack to \r and any space characters. Make sure we don't go - // past the beginning of the value pointer. - endval--; - if (*endval == '\r') - endval--; - while ((endval != val) && (isspace((unsigned char) *endval))) - endval--; - endval++; - *(endval) = '\0'; - - if (ml) - { - char *newValue = NULL; - - natsHeaderValue *cur = natsStrHash_Get(msg->headers, key); - if (cur == NULL) - return nats_setError(NATS_PROTOCOL_ERROR, "unable to process folding lines for key %s", key); - - for (; cur->next != NULL; ) - cur = cur->next; - - if (nats_asprintf(&newValue, "%s %s", cur->value, val) == -1) - return nats_setDefaultError(NATS_NO_MEMORY); - - if (cur->needFree) - NATS_FREE(cur->value); - - cur->value = newValue; - cur->needFree = true; - } - else - { - s = natsHeaderValue_create(&v, (const char*) val, false); - if (s == NATS_OK) - { - natsHeaderValue *cur = natsStrHash_Get(msg->headers, key); - if (cur != NULL) - { - for (; cur->next != NULL; ) - cur = cur->next; - - cur->next = v; - } - else - s = natsStrHash_Set(msg->headers, (char*) key, false, (void*) v, NULL); - } - } - - if (s == NATS_OK) - { - ptr++; - *pPtr = ptr; - *lastKey = key; - } + natsStatus s = NATS_OK; + // char *ptr = *pPtr; + // char *col = NULL; + // char *key = NULL; + // char *val = NULL; + // natsHeaderValue *v = NULL; + // bool ml = false; + // char *start; + // char *endval; + + // start = ptr; + // if (*ptr == '\r') + // { + // if ((++ptr == endPtr) || ((*ptr == '\n') && (++ptr == endPtr))) + // { + // *pPtr = ptr; + // return NATS_OK; + // } + // return nats_setError(NATS_PROTOCOL_ERROR, "invalid start of a key: %s", start); + // } + // if (isspace((unsigned char) *ptr)) + // { + // if (line == 0) + // return nats_setError(NATS_PROTOCOL_ERROR, "key cannot start with a space: %s", ptr); + + // key = *lastKey; + // ml = true; + // } + // else + // { + // col = strchr((const char*) ptr, (int) ':'); + // if (col == NULL) + // return nats_setError(NATS_PROTOCOL_ERROR, "column delimiter not found: %s", ptr); + + // // Replace column char with \0 to terminate the key string. + // key = ptr; + // ptr = col+1; + // (*col) = '\0'; + // } + + // while ((ptr != endPtr) && (isspace((unsigned char) *ptr))) + // ptr++; + + // if (ptr == endPtr) + // return nats_setError(NATS_PROTOCOL_ERROR, "no value found for key %s", key); + + // val = ptr; + // // Now find the \r\n for this value + // ptr = _moveToLF(endPtr, ptr); + // if (ptr == endPtr) + // return nats_setError(NATS_PROTOCOL_ERROR, "no CRLF found for value of key %s", key); + + // // Trim right spaces and set to \0 to terminate the value string. + // endval = ptr; + // // Backtrack to \r and any space characters. Make sure we don't go + // // past the beginning of the value pointer. + // endval--; + // if (*endval == '\r') + // endval--; + // while ((endval != val) && (isspace((unsigned char) *endval))) + // endval--; + // endval++; + // *(endval) = '\0'; + + // if (ml) + // { + // char *newValue = NULL; + + // natsHeaderValue *cur = natsStrHash_Get(msg->headers, key); + // if (cur == NULL) + // return nats_setError(NATS_PROTOCOL_ERROR, "unable to process folding lines for key %s", key); + + // for (; cur->next != NULL; ) + // cur = cur->next; + + // if (nats_asprintf(&newValue, "%s %s", cur->value, val) == -1) + // return nats_setDefaultError(NATS_NO_MEMORY); + + // if (cur->needFree) + // NATS_FREE(cur->value); + + // cur->value = newValue; + // cur->needFree = true; + // } + // else + // { + // s = natsHeaderValue_create(&v, (const char*) val, false); + // if (STILL_OK(s)) + // { + // natsHeaderValue *cur = natsStrHash_Get(msg->headers, key); + // if (cur != NULL) + // { + // for (; cur->next != NULL; ) + // cur = cur->next; + + // cur->next = v; + // } + // else + // s = natsStrHash_Set(msg->headers, (char*) key, false, (void*) v, NULL); + // } + // } + + // if (STILL_OK(s)) + // { + // ptr++; + // *pPtr = ptr; + // *lastKey = key; + // } return NATS_UPDATE_ERR_STACK(s); } -static natsStatus -_liftHeaders(natsMsg *msg, bool setOrAdd) -{ - natsStatus s = NATS_OK; - char *ptr = NULL; - char *sts = NULL; - char *endPtr = NULL; - char *lk = NULL; - int i; - - // If there is no header map and needsLift is false, and this is not - // an action to set or add a header, then simply return. - if (!setOrAdd && (msg->headers == NULL) && !natsMsg_needsLift(msg)) - return NATS_OK; - - // For set or add operations, possibly create the headers map. - if (msg->headers == NULL) - { - s = natsStrHash_Create(&(msg->headers), 4); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - } - - // In all cases, if there is no need to lift, we are done. - if (!natsMsg_needsLift(msg)) - return NATS_OK; - - // If hdrLen is less than what we need for NATS/1.0\r\n, then - // clearly this is a bad header. - if ((msg->hdrLen < HDR_LINE_LEN) || (strstr(msg->hdr, HDR_LINE_PRE) != msg->hdr)) - return nats_setError(NATS_PROTOCOL_ERROR, "header prefix missing: %s", msg->hdr); - - endPtr = (char*) (msg->hdr + msg->hdrLen); - - sts = (char*) (msg->hdr + HDR_LINE_PRE_LEN); - while ((sts != endPtr) && (*sts == ' ')) - sts++; - - ptr = sts; - ptr = _moveToLF(endPtr, ptr); - if (ptr != endPtr) - { - char *stsEnd = ptr; - - ptr++; - while ((stsEnd != sts) && (*stsEnd != '\r')) - stsEnd--; - - // Terminate the status. - *stsEnd = '\0'; - } - - if (ptr == endPtr) - return nats_setError(NATS_PROTOCOL_ERROR, "early termination of headers: %s", msg->hdr); - - for (i=0; ((s == NATS_OK) && (ptr != endPtr)); i++) - s = _processKeyValue(i, msg, endPtr, &ptr, &lk); - - if (s == NATS_OK) - { - // At this point we have had no protocol error lifting the header - // so we clear this flag so that we don't attempt to lift again. - natsMsg_clearNeedsLift(msg); - - // Furthermore, we need the flag to be cleared should we need to - // add the no responders header (otherwise we would recursively - // try to lift headers). - // If adding the field fails, it is likely due to memory issue, - // so it is fine to keep "needsLift" as false. - - // Check if we have an inlined status. - if ((sts != NULL) && (*sts != '\0')) - { - // There could be a description... - if (strlen(sts) > HDR_STATUS_LEN) - { - char *desc = (char*) (sts + HDR_STATUS_LEN); - char descb = 0; - - // Save byte that starts the description - descb = *desc; - // Replace with '\0' to end the status. - *desc = '\0'; - - // Set status value (this will make a copy) - s = natsMsgHeader_Set(msg, STATUS_HDR, (const char*) sts); - if (s == NATS_OK) - { - char *desce = NULL; - - // Restore character of starting description - *desc = descb; - // Trim left spaces - while ((*desc != '\0') && isspace((unsigned char) *desc)) - desc++; - - // If we are not at the end of description - if (*desc != '\0') - { - // Go to end of description and walk back to trim right. - desce = (char*) (desc + (int) strlen(desc) - 1); - while ((desce != desc) && isspace((unsigned char) *desce)) - { - *desce = '\0'; - desce--; - } - } - // If there is a description, set the value (this will make a copy) - if (*desc != '\0') - s = natsMsgHeader_Set(msg, DESCRIPTION_HDR, (const char*) desc); - } - } - else - s = natsMsgHeader_Set(msg, STATUS_HDR, (const char*) sts); - } - } - - return NATS_UPDATE_ERR_STACK(s); -} +// static natsStatus +// _liftHeaders(natsMessage *msg, bool setOrAdd) +// { +// natsStatus s = NATS_OK; +// char *ptr = NULL; +// char *sts = NULL; +// char *endPtr = NULL; +// char *lk = NULL; +// int i; + +// // If there is no header map and needsLift is false, and this is not +// // an action to set or add a header, then simply return. +// if (!setOrAdd && (msg->headers == NULL) && !natsMessage_needsLift(msg)) +// return NATS_OK; + +// // For set or add operations, possibly create the headers map. +// if (msg->headers == NULL) +// { +// s = natsStrHash_Create(&(msg->headers), 4); +// if (s != NATS_OK) +// return NATS_UPDATE_ERR_STACK(s); +// } + +// // In all cases, if there is no need to lift, we are done. +// if (!natsMessage_needsLift(msg)) +// return NATS_OK; + +// // If hdrLen is less than what we need for NATS/1.0\r\n, then +// // clearly this is a bad header. +// if ((msg->hdrLen < HDR_LINE_LEN) || (strstr(msg->hdr, HDR_LINE_PRE) != msg->hdr)) +// return nats_setError(NATS_PROTOCOL_ERROR, "header prefix missing: %s", msg->hdr); + +// endPtr = (char*) (msg->hdr + msg->hdrLen); + +// sts = (char*) (msg->hdr + HDR_LINE_PRE_LEN); +// while ((sts != endPtr) && (*sts == ' ')) +// sts++; + +// ptr = sts; +// ptr = _moveToLF(endPtr, ptr); +// if (ptr != endPtr) +// { +// char *stsEnd = ptr; + +// ptr++; +// while ((stsEnd != sts) && (*stsEnd != '\r')) +// stsEnd--; + +// // Terminate the status. +// *stsEnd = '\0'; +// } + +// if (ptr == endPtr) +// return nats_setError(NATS_PROTOCOL_ERROR, "early termination of headers: %s", msg->hdr); + +// for (i=0; ((STILL_OK(s)) && (ptr != endPtr)); i++) +// s = _processKeyValue(i, msg, endPtr, &ptr, &lk); + +// if (STILL_OK(s)) +// { +// // At this point we have had no protocol error lifting the header +// // so we clear this flag so that we don't attempt to lift again. +// natsMessage_clearNeedsLift(msg); + +// // Furthermore, we need the flag to be cleared should we need to +// // add the no responders header (otherwise we would recursively +// // try to lift headers). +// // If adding the field fails, it is likely due to memory issue, +// // so it is fine to keep "needsLift" as false. + +// // Check if we have an inlined status. +// if ((sts != NULL) && (*sts != '\0')) +// { +// // There could be a description... +// if (strlen(sts) > HDR_STATUS_LEN) +// { +// char *desc = (char*) (sts + HDR_STATUS_LEN); +// char descb = 0; + +// // Save byte that starts the description +// descb = *desc; +// // Replace with '\0' to end the status. +// *desc = '\0'; + +// // Set status value (this will make a copy) +// s = natsMessageHeader_Set(msg, STATUS_HDR, (const char*) sts); +// if (STILL_OK(s)) +// { +// char *desce = NULL; + +// // Restore character of starting description +// *desc = descb; +// // Trim left spaces +// while ((*desc != '\0') && isspace((unsigned char) *desc)) +// desc++; + +// // If we are not at the end of description +// if (*desc != '\0') +// { +// // Go to end of description and walk back to trim right. +// desce = (char*) (desc + (int) strlen(desc) - 1); +// while ((desce != desc) && isspace((unsigned char) *desce)) +// { +// *desce = '\0'; +// desce--; +// } +// } +// // If there is a description, set the value (this will make a copy) +// if (*desc != '\0') +// s = natsMessageHeader_Set(msg, DESCRIPTION_HDR, (const char*) desc); +// } +// } +// else +// s = natsMessageHeader_Set(msg, STATUS_HDR, (const char*) sts); +// } +// } + +// return NATS_UPDATE_ERR_STACK(s); +// } natsStatus -natsMsgHeader_Set(natsMsg *msg, const char *key, const char *value) +natsMessageHeader_Set(natsMessage *msg, const char *key, const char *value) { - natsStatus s = NATS_OK; - - if ((s = _checkMsgAndKey(msg, key)) != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - if ((s = _liftHeaders(msg, true)) != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - if (s == NATS_OK) - { - natsHeaderValue *v = NULL; - - s = natsHeaderValue_create(&v, value, true); - if (s == NATS_OK) - { - void *p = NULL; - - s = natsStrHash_Set(msg->headers, (char*) key, true, (void*) v, &p); - if (s != NATS_OK) - natsHeaderValue_free(v, false); - else if (p != NULL) - { - natsHeaderValue *old = (natsHeaderValue*) p; - natsHeaderValue_free(old, true); - } - } - } + natsStatus s = NATS_OK; + + // if ((s = _checkMsgAndKey(msg, key)) != NATS_OK) + // return NATS_UPDATE_ERR_STACK(s); + + // if ((s = _liftHeaders(msg, true)) != NATS_OK) + // return NATS_UPDATE_ERR_STACK(s); + + // if (STILL_OK(s)) + // { + // natsHeaderValue *v = NULL; + + // s = natsHeaderValue_create(&v, value, true); + // if (STILL_OK(s)) + // { + // void *p = NULL; + + // s = natsStrHash_Set(msg->headers, (char*) key, true, (void*) v, &p); + // if (s != NATS_OK) + // natsHeaderValue_free(v, false); + // else if (p != NULL) + // { + // natsHeaderValue *old = (natsHeaderValue*) p; + // natsHeaderValue_free(old, true); + // } + // } + // } return NATS_UPDATE_ERR_STACK(s); } natsStatus -natsMsgHeader_Add(natsMsg *msg, const char *key, const char *value) +natsMessageHeader_Add(natsMessage *msg, const char *key, const char *value) { - natsStatus s = NATS_OK; - - if ((s = _checkMsgAndKey(msg, key)) != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - if ((s = _liftHeaders(msg, true)) != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - if (s == NATS_OK) - { - natsHeaderValue *v = NULL; - - s = natsHeaderValue_create(&v, value, true); - if (s == NATS_OK) - { - natsHeaderValue *cur = natsStrHash_Get(msg->headers, (char*) key); - if (cur != NULL) - { - for (; cur->next != NULL; ) - cur = cur->next; - - cur->next = v; - } - else - s = natsStrHash_Set(msg->headers, (char*) key, true, (void*) v, NULL); - } - } + natsStatus s = NATS_OK; + + // if ((s = _checkMsgAndKey(msg, key)) != NATS_OK) + // return NATS_UPDATE_ERR_STACK(s); + + // if ((s = _liftHeaders(msg, true)) != NATS_OK) + // return NATS_UPDATE_ERR_STACK(s); + + // if (STILL_OK(s)) + // { + // natsHeaderValue *v = NULL; + + // s = natsHeaderValue_create(&v, value, true); + // if (STILL_OK(s)) + // { + // natsHeaderValue *cur = natsStrHash_Get(msg->headers, (char*) key); + // if (cur != NULL) + // { + // for (; cur->next != NULL; ) + // cur = cur->next; + + // cur->next = v; + // } + // else + // s = natsStrHash_Set(msg->headers, (char*) key, true, (void*) v, NULL); + // } + // } return NATS_UPDATE_ERR_STACK(s); } natsStatus -natsMsgHeader_Get(natsMsg *msg, const char *key, const char **value) +natsMessageHeader_Get(natsMessage *msg, const char *key, const char **value) { - natsStatus s = NATS_OK; - natsHeaderValue *v = NULL; + natsStatus s = NATS_OK; + // natsHeaderValue *v = NULL; - if ((s = _checkMsgAndKey(msg, key)) != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); + // if ((s = _checkMsgAndKey(msg, key)) != NATS_OK) + // return NATS_UPDATE_ERR_STACK(s); - if (value == NULL) - return nats_setError(NATS_INVALID_ARG, "%s", "value cannot be NULL"); + // if (value == NULL) + // return nats_setError(NATS_INVALID_ARG, "%s", "value cannot be NULL"); - *value = NULL; + // *value = NULL; - if ((s = _liftHeaders(msg, false)) != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); + // if ((s = _liftHeaders(msg, false)) != NATS_OK) + // return NATS_UPDATE_ERR_STACK(s); - if ((msg->headers == NULL) || natsStrHash_Count(msg->headers) == 0) - return NATS_NOT_FOUND; // normal error, so don't update error stack + // if ((msg->headers == NULL) || natsStrHash_Count(msg->headers) == 0) + // return NATS_NOT_FOUND; // normal error, so don't update error stack - v = natsStrHash_Get(msg->headers, (char*) key); - if (v == NULL) - return NATS_NOT_FOUND; // normal error, so don't update error stack + // v = natsStrHash_Get(msg->headers, (char*) key); + // if (v == NULL) + // return NATS_NOT_FOUND; // normal error, so don't update error stack - *value = (const char*) v->value; + // *value = (const char*) v->value; return NATS_UPDATE_ERR_STACK(s); } natsStatus -natsMsgHeader_Values(natsMsg *msg, const char *key, const char* **values, int *count) +natsMessageHeader_Values(natsMessage *msg, const char *key, const char ***values, int *count) { - natsStatus s = NATS_OK; - int c = 0; - natsHeaderValue *cur = NULL; - const char* *strs = NULL; - natsHeaderValue *v = NULL; + natsStatus s = NATS_OK; + // int c = 0; + // natsHeaderValue *cur = NULL; + // const char* *strs = NULL; + // natsHeaderValue *v = NULL; - if ((s = _checkMsgAndKey(msg, key)) != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); + // if ((s = _checkMsgAndKey(msg, key)) != NATS_OK) + // return NATS_UPDATE_ERR_STACK(s); - if ((values == NULL) || (count == NULL)) - return nats_setError(NATS_INVALID_ARG, "%s", "value or count cannot be NULL"); + // if ((values == NULL) || (count == NULL)) + // return nats_setError(NATS_INVALID_ARG, "%s", "value or count cannot be NULL"); - *values = NULL; - *count = 0; + // *values = NULL; + // *count = 0; - if ((s = _liftHeaders(msg, false)) != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); + // if ((s = _liftHeaders(msg, false)) != NATS_OK) + // return NATS_UPDATE_ERR_STACK(s); - if ((msg->headers == NULL) || natsStrHash_Count(msg->headers) == 0) - return NATS_NOT_FOUND; // normal error, so don't update error stack + // if ((msg->headers == NULL) || natsStrHash_Count(msg->headers) == 0) + // return NATS_NOT_FOUND; // normal error, so don't update error stack - v = natsStrHash_Get(msg->headers, (char*) key); - if (v == NULL) - return NATS_NOT_FOUND; // normal error, so don't update error stack + // v = natsStrHash_Get(msg->headers, (char*) key); + // if (v == NULL) + // return NATS_NOT_FOUND; // normal error, so don't update error stack - for (cur=v; cur != NULL; cur = cur->next) - c++; + // for (cur=v; cur != NULL; cur = cur->next) + // c++; - strs = NATS_CALLOC(c, sizeof(char*)); - if (strs == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - { - int i = 0; + // strs = NATS_CALLOC(c, sizeof(char*)); + // if (strs == NULL) + // s = nats_setDefaultError(NATS_NO_MEMORY); + // else + // { + // int i = 0; - for (cur=v; cur != NULL; cur = cur->next) - strs[i++] = (const char*) cur->value; + // for (cur=v; cur != NULL; cur = cur->next) + // strs[i++] = (const char*) cur->value; - *values = strs; - *count = c; - } + // *values = strs; + // *count = c; + // } return NATS_UPDATE_ERR_STACK(s); } natsStatus -natsMsgHeader_Keys(natsMsg *msg, const char* **keys, int *count) +natsMessageHeader_Keys(natsMessage *msg, const char ***keys, int *count) { - natsStatus s = NATS_OK; - const char* *strs = NULL; - int c = 0; - - if (msg == NULL) - return nats_setError(NATS_INVALID_ARG, "%s", "message cannot be NULL"); - - if ((keys == NULL) || (count == NULL)) - return nats_setError(NATS_INVALID_ARG, "%s", "keys or count cannot be NULL"); - - *keys = NULL; - *count = 0; - - if ((s = _liftHeaders(msg, false)) != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - if ((msg->headers == NULL) || (c = natsStrHash_Count(msg->headers)) == 0) - return NATS_NOT_FOUND; // normal error, so don't update error stack - - strs = NATS_CALLOC(c, sizeof(char*)); - if (strs == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - { - natsStrHashIter iter; - char *hk = NULL; - int i; - - natsStrHashIter_Init(&iter, msg->headers); - for (i=0; natsStrHashIter_Next(&iter, &hk, NULL); i++) - { - strs[i] = (const char*) hk; - } - natsStrHashIter_Done(&iter); - - *keys = strs; - *count = c; - } + natsStatus s = NATS_OK; + // const char* *strs = NULL; + // int c = 0; + + // if (msg == NULL) + // return nats_setError(NATS_INVALID_ARG, "%s", "message cannot be NULL"); + + // if ((keys == NULL) || (count == NULL)) + // return nats_setError(NATS_INVALID_ARG, "%s", "keys or count cannot be NULL"); + + // *keys = NULL; + // *count = 0; + + // if ((s = _liftHeaders(msg, false)) != NATS_OK) + // return NATS_UPDATE_ERR_STACK(s); + + // if ((msg->headers == NULL) || (c = natsStrHash_Count(msg->headers)) == 0) + // return NATS_NOT_FOUND; // normal error, so don't update error stack + + // strs = NATS_CALLOC(c, sizeof(char*)); + // if (strs == NULL) + // s = nats_setDefaultError(NATS_NO_MEMORY); + // else + // { + // natsStrHashIter iter; + // char *hk = NULL; + // int i; + + // natsStrHashIter_Init(&iter, msg->headers); + // for (i=0; natsStrHashIter_Next(&iter, &hk, NULL); i++) + // { + // strs[i] = (const char*) hk; + // } + // natsStrHashIter_Done(&iter); + + // *keys = strs; + // *count = c; + // } return NATS_UPDATE_ERR_STACK(s); } natsStatus -natsMsgHeader_Delete(natsMsg *msg, const char *key) +natsMessageHeader_Delete(natsMessage *msg, const char *key) { - natsStatus s = NATS_OK; - natsHeaderValue *v = NULL; + natsStatus s = NATS_OK; + // natsHeaderValue *v = NULL; - if ((s = _checkMsgAndKey(msg, key)) != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); + // if ((s = _checkMsgAndKey(msg, key)) != NATS_OK) + // return NATS_UPDATE_ERR_STACK(s); - if ((s = _liftHeaders(msg, false)) != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); + // if ((s = _liftHeaders(msg, false)) != NATS_OK) + // return NATS_UPDATE_ERR_STACK(s); - if ((msg->headers == NULL) || natsStrHash_Count(msg->headers) == 0) - return NATS_NOT_FOUND; // normal error, so don't update error stack + // if ((msg->headers == NULL) || natsStrHash_Count(msg->headers) == 0) + // return NATS_NOT_FOUND; // normal error, so don't update error stack - v = natsStrHash_Remove(msg->headers, (char*) key); - if (v == NULL) - return NATS_NOT_FOUND; // normal error, so don't update error stack + // v = natsStrHash_Remove(msg->headers, (char*) key); + // if (v == NULL) + // return NATS_NOT_FOUND; // normal error, so don't update error stack - natsHeaderValue_free(v, true); + // natsHeaderValue_free(v, true); return s; } -void -natsMsg_freeHeaders(natsMsg *msg) -{ - natsStrHashIter iter; - void *p = NULL; - - if (msg->headers == NULL) - return; - - natsStrHashIter_Init(&iter, msg->headers); - for (;natsStrHashIter_Next(&iter, NULL, &p);) - { - natsHeaderValue *v = (natsHeaderValue *)p; - natsHeaderValue_free(v, true); - } - natsStrHash_Destroy(msg->headers); -} - -void -natsMsg_free(void *object) +const char * +natsMessage_GetSubject(const natsMessage *msg) { - natsMsg *msg; - - if (object == NULL) - return; + // if (msg == NULL) + return NULL; - msg = (natsMsg*) object; - natsMsg_freeHeaders(msg); - - NATS_FREE(msg); + // return (const char*) msg->subject; } -void -natsMsg_Destroy(natsMsg *msg) +const char * +natsMessage_GetReply(const natsMessage *msg) { - if (msg == NULL) - return; - - if (natsMsg_isNoDestroy(msg)) - return; - - if (natsGC_collect((natsGCItem *) msg)) - return; + // if (msg == NULL) + return NULL; - natsMsg_free((void*) msg); + // return (const char*) msg->reply; } -const char* -natsMsg_GetSubject(const natsMsg *msg) +const char * +natsMessage_GetData(const natsMessage *msg) { - if (msg == NULL) - return NULL; - - return (const char*) msg->subject; -} - -const char* -natsMsg_GetReply(const natsMsg *msg) -{ - if (msg == NULL) - return NULL; + // if (msg == NULL) + return NULL; - return (const char*) msg->reply; + // return (const char*) msg->data; } -const char* -natsMsg_GetData(const natsMsg *msg) +int natsMessage_GetDataLength(const natsMessage *msg) { - if (msg == NULL) - return NULL; + // if (msg == NULL) + return 0; - return (const char*) msg->data; + // return msg->data.len; } -int -natsMsg_GetDataLength(const natsMsg *msg) -{ - if (msg == NULL) - return 0; +// uint64_t +// natsMessage_GetSequence(natsMessage *msg) +// { +// if (msg == NULL) +// return 0; - return msg->dataLen; -} - -uint64_t -natsMsg_GetSequence(natsMsg *msg) -{ - if (msg == NULL) - return 0; - - return msg->seq; -} +// return msg->seq; +// } int64_t -natsMsg_GetTime(natsMsg *msg) +natsMessage_GetTime(natsMessage *msg) { if (msg == NULL) return 0; @@ -744,174 +672,96 @@ natsMsg_GetTime(natsMsg *msg) } natsStatus -natsMsg_createWithPadding(natsMsg **newMsg, - const char *subject, int subjLen, - const char *reply, int replyLen, - const char *buf, int bufLen, int bufPaddingSize, int hdrLen) +nats_CreateMessage(natsMessage **newm, natsConnection *nc, const char *subj) { - natsMsg *msg = NULL; - char *ptr = NULL; - int bufSize = 0; - int dataLen = bufLen; - bool hasHdrs = (hdrLen > 0 ? true : false); - // Make payload a null-terminated string and add at least one zero byte to the end - int padLen = (bufPaddingSize > 0 ? bufPaddingSize : 1); - - bufSize = subjLen; - bufSize += 1; - if (replyLen > 0) - { - bufSize += replyLen; - bufSize += 1; - } - bufSize += bufLen; - bufSize += padLen; - if (hasHdrs) - bufSize++; - - msg = NATS_MALLOC(sizeof(natsMsg) + bufSize); - if (msg == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - // To be safe, we could 'memset' the message up to sizeof(natsMsg), - // but since we are explicitly initializing most of the fields, we save - // on that call, but we need to make sure what we initialize all fields!!! - - // That being said, we memset the 'gc' structure to protect us in case - // some fields are later added to this 'external' structure and we forget - // about updating this initialization code. - memset(&(msg->gc), 0, sizeof(natsGCItem)); - - msg->hdr = NULL; - msg->hdrLen = 0; - msg->flags = 0; - msg->headers = NULL; - msg->sub = NULL; - msg->next = NULL; - msg->seq = 0; - msg->time = 0; - - ptr = (char*) (((char*) &(msg->next)) + sizeof(msg->next)); + size_t subjLen = nats_strlen(subj); + if (!nats_isSubjectValid((const uint8_t *)subj, subjLen, false)) + return nats_setDefaultError(NATS_INVALID_ARG); - msg->subject = (const char*) ptr; - memcpy(ptr, subject, subjLen); - ptr += subjLen; - *(ptr++) = '\0'; + natsStatus s = NATS_OK; + natsMessage *m = NULL; + natsPool *pool = NULL; - if (replyLen > 0) - { - msg->reply = (const char*) ptr; - memcpy(ptr, reply, replyLen); - ptr += replyLen; - *(ptr++) = '\0'; - } - else + s = nats_createPool(&pool, &nc->opts->mem, "msg"); + IFOK(s, CHECK_NO_MEMORY(m = nats_palloc(pool, sizeof(natsMessage)))); + IFOK(s, CHECK_NO_MEMORY(m->subject = nats_pstrdupU(pool, subj))); + if (NOT_OK(s)) { - msg->reply = NULL; - } - - if (hasHdrs) - { - msg->hdr = ptr; - if (buf != NULL) - { - memcpy(ptr, buf, hdrLen); - buf += hdrLen; - } - ptr += hdrLen; - *(ptr++) = '\0'; - - msg->hdrLen = hdrLen; - natsMsg_setNeedsLift(msg); - dataLen -= hdrLen; + nats_releasePool(pool); + return NATS_UPDATE_ERR_STACK(s); } - msg->data = (const char*) ptr; - msg->dataLen = dataLen; - if (buf != NULL) - memcpy(ptr, buf, dataLen); - ptr += dataLen; - memset(ptr, 0, padLen); - // This is essentially to match server's view of a message size - // when sending messages to pull consumers and keeping track - // of size in regards to a max_bytes setting. - msg->wsz = subjLen + replyLen + bufLen; - - // Setting the callback will trigger garbage collection when - // natsMsg_Destroy() is invoked. - msg->gc.freeCb = natsMsg_free; - - *newMsg = msg; - + m->pool = pool; + *newm = m; return NATS_OK; } -natsStatus -natsMsg_create(natsMsg **newMsg, - const char *subject, int subjLen, - const char *reply, int replyLen, - const char *buf, int bufLen, int hdrLen) +bool natsMessage_IsNoResponders(natsMessage *m) { - return natsMsg_createWithPadding(newMsg, subject, subjLen, reply, replyLen, - buf, bufLen, 0, hdrLen); + const char *val = NULL; + + // To be a "no responders" message, it has to be of 0 length, + // and have a "Status" header with "503" as a value. + return ((m != NULL) && (natsMessage_GetDataLength(m) == 0) && (natsMessageHeader_Get(m, STATUS_HDR, &val) == NATS_OK) && (val != NULL) && (strncmp(val, NO_RESP_STATUS, HDR_STATUS_LEN) == 0)); } -// Used internally to initialize a message structure, generally defined on the stack, -// that will then be passed as a reference to publish functions. -void -natsMsg_init(natsMsg *msg, const char *subj, const char *data, int dataLen) +natsStatus nats_SetOnMessageCleanup(natsMessage *m, void (*f)(void *), void *closure) { - memset(msg, 0, sizeof(natsMsg)); - msg->subject = subj; - msg->data = data; - msg->dataLen = dataLen; + if (m == NULL) + return nats_setError(NATS_INVALID_ARG, "%s", "message cannot be NULL"); + + m->freef = f; + m->freeClosure = closure; + + return NATS_OK; } -natsStatus -natsMsg_Create(natsMsg **newMsg, const char *subj, const char *reply, - const char *data, int dataLen) +natsStatus nats_SetOnMessagePublished(natsMessage *m, natsOnMessagePublishedF f, void *closure) { - natsStatus s = NATS_OK; + if (m == NULL) + return nats_setError(NATS_INVALID_ARG, "%s", "message cannot be NULL"); - if ((subj == NULL) - || (subj[0] == '\0') - || ((reply != NULL) && (reply[0] == '\0'))) - { + m->donef = f; + m->doneClosure = closure; + + return NATS_OK; +} + +natsStatus nats_SetMessagePayload(natsMessage *m, const void *data, size_t dataLen) +{ + if ((m == NULL) || (dataLen == 0) || (data == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); - } + if ((m->out != NULL) || (m->pool == NULL)) + return nats_setError(NATS_ILLEGAL_STATE, "%s", "message payload already set"); - s = natsMsg_create(newMsg, - subj, (int) strlen(subj), - reply, (reply == NULL ? 0 : (int) strlen(reply)), - data, dataLen, -1); + m->out = nats_palloc(m->pool, sizeof(natsString)); + if (m->out == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + m->out->data = (uint8_t*) data; + m->out->len = dataLen; - return NATS_UPDATE_ERR_STACK(s); + return NATS_OK; } -bool -natsMsg_IsNoResponders(natsMsg *m) +void nats_ReleaseMessage(natsMessage *m) { - const char *val = NULL; + if (m == NULL) + return; - // To be a "no responders" message, it has to be of 0 length, - // and have a "Status" header with "503" as a value. - return ((m != NULL) - && (natsMsg_GetDataLength(m) == 0) - && (natsMsgHeader_Get(m, STATUS_HDR, &val) == NATS_OK) - && (val != NULL) - && (strncmp(val, NO_RESP_STATUS, HDR_STATUS_LEN) == 0)); + nats_releasePool(m->pool); } -void -natsMsgList_Destroy(natsMsgList *list) +const uint8_t *nats_GetMessageData(natsMessage *msg) { - int i; + if ((msg == NULL) || (msg->out == NULL)) + return NULL; - if ((list == NULL) || (list->Msgs == NULL)) - return; + return (const uint8_t *)msg->out->data; +} + +size_t nats_GetMessageDataLen(natsMessage *msg) +{ + if ((msg == NULL) || (msg->out == NULL)) + return 0; - for (i=0; i < list->Count; i++) - natsMsg_Destroy(list->Msgs[i]); - NATS_FREE(list->Msgs); - list->Msgs = NULL; - list->Count = 0; + return msg->out->len; } diff --git a/src/msg.h b/src/msg.h index 2cbc9433c..eceaa5967 100644 --- a/src/msg.h +++ b/src/msg.h @@ -14,109 +14,64 @@ #ifndef MSG_H_ #define MSG_H_ -#include "status.h" -#include "gc.h" - -#define HDR_LINE_PRE "NATS/1.0" -#define HDR_LINE_PRE_LEN (8) -#define HDR_LINE HDR_LINE_PRE _CRLF_ -#define HDR_LINE_LEN (10) -#define STATUS_HDR "Status" -#define DESCRIPTION_HDR "Description" -#define NO_RESP_STATUS "503" -#define NOT_FOUND_STATUS "404" -#define REQ_TIMEOUT "408" -#define CTRL_STATUS "100" -#define HDR_STATUS_LEN (3) - -#define natsMsg_setNeedsLift(m) ((m)->flags |= (1 << 0)) -#define natsMsg_needsLift(m) (((m)->flags & (1 << 0)) != 0) -#define natsMsg_clearNeedsLift(m) ((m)->flags &= ~(1 << 0)) - -#define natsMsg_setAcked(m) ((m)->flags |= (1 << 1)) -#define natsMsg_isAcked(m) (((m)->flags & (1 << 1)) != 0) -#define natsMsg_clearAcked(m) ((m)->flags &= ~(1 << 1)) - -#define natsMsg_setNoDestroy(m) ((m)->flags |= (1 << 2)) -#define natsMsg_isNoDestroy(m) (((m)->flags & (1 << 2)) != 0) -#define natsMsg_clearNoDestroy(m) ((m)->flags &= ~(1 << 2)) - -#define natsMsg_setTimeout(m) ((m)->flags |= (1 << 3)) -#define natsMsg_isTimeout(m) (((m)->flags & (1 << 3)) != 0) -#define natsMsg_clearTimeout(m) ((m)->flags &= ~(1 << 3)) - -#define natsMsg_dataAndHdrLen(m) ((m)->dataLen + (m)->hdrLen) - -struct __natsMsg -{ - natsGCItem gc; - - // The message is allocated as a single memory block that contains - // this structure and enough space for the headers/payload. - // Headers, if any, start after the 'next' pointer, followed by - // the message payload. - const char *subject; - const char *reply; - char *hdr; - natsStrHash *headers; - const char *data; - int dataLen; - int hdrLen; - int wsz; - int flags; - uint64_t seq; - int64_t time; - - // subscription (needed when delivery done by connection, - // or for JetStream). - struct __natsSubscription *sub; - - // Must be last field! - struct __natsMsg *next; - - // Nothing after this: the message headers/payload goes there. +static const natsString nats_NATS10 = NATS_STR("NATS/1.0"); -}; +#define STATUS_HDR "Status" +#define DESCRIPTION_HDR "Description" +#define NO_RESP_STATUS "503" +#define NOT_FOUND_STATUS "404" +#define REQ_TIMEOUT "408" +#define CTRL_STATUS "100" +#define HDR_STATUS_LEN (3) -struct __natsHeaderValue; +#define natsMessage_setNeedsLift(m) ((m)->flags |= (1 << 0)) +#define natsMessage_needsLift(m) (((m)->flags & (1 << 0)) != 0) +#define natsMessage_clearNeedsLift(m) ((m)->flags &= ~(1 << 0)) -typedef struct __natsHeaderValue +#define natsMessage_setAcked(m) ((m)->flags |= (1 << 1)) +#define natsMessage_isAcked(m) (((m)->flags & (1 << 1)) != 0) +#define natsMessage_clearAcked(m) ((m)->flags &= ~(1 << 1)) + +#define natsMessage_setTimeout(m) ((m)->flags |= (1 << 2)) +#define natsMessage_isTimeout(m) (((m)->flags & (1 << 2)) != 0) +#define natsMessage_clearTimeout(m) ((m)->flags &= ~(1 << 2)) + +#define natsMessage_setOutgoing(m) ((m)->flags |= (1 << 3)) +#define natsMessage_setIncoming(m) ((m)->flags &= ~(1 << 3)) +#define natsMessage_isOutgoing(m) (((m)->flags & (1 << 3)) != 0) + +struct __natsMessage { - char *value; - bool needFree; - struct __natsHeaderValue *next; + natsString *subject; -} natsHeaderValue; + natsPool *pool; + natsString *reply; + natsStrHash *headers; -int -natsMsgHeader_encodedLen(natsMsg *msg); + natsString *out; + natsReadBuffer *in; -natsStatus -natsMsgHeader_encode(natsBuffer *buf, natsMsg *msg); + natsOnMessagePublishedF donef; + void *doneClosure; -void -natsMsg_init(natsMsg *msg, - const char *subject, - const char *data, int dataLen); + void (*freef)(void *); + void *freeClosure; -natsStatus -natsMsg_create(natsMsg **newMsg, - const char *subject, int subjLen, - const char *reply, int replyLen, - const char *buf, int bufLen, int hdrLen); + int flags; + int64_t time; +}; -natsStatus -natsMsg_createWithPadding(natsMsg **newMsg, - const char *subject, int subjLen, - const char *reply, int replyLen, - const char *buf, int bufLen, int bufPaddingSize, - int hdrLen); +struct __natsHeaderValue; -void -natsMsg_freeHeaders(natsMsg *msg); +typedef struct __natsHeaderValue +{ + char *value; + bool needFree; + struct __natsHeaderValue *next; + +} natsHeaderValue; -// This needs to follow the nats_FreeObjectCb prototype (see gc.h) -void -natsMsg_free(void *object); +int natsMessageHeader_encodedLen(natsMessage *msg); +natsStatus natsMessageHeader_encode(natsBuf *buf, natsMessage *msg); #endif /* MSG_H_ */ diff --git a/src/nats.c b/src/nats.c index 1c2973d63..b6e32ad71 100644 --- a/src/nats.c +++ b/src/nats.c @@ -12,1234 +12,189 @@ // limitations under the License. #include "natsp.h" -#if defined(NATS_HAS_STREAMING) -#include "stan/stanp.h" -#endif +#include "nats/adapters/malloc_heap.h" -#include -#include -#include -#include -#include #include -#include "mem.h" -#include "timer.h" -#include "util.h" -#include "asynccb.h" -#include "conn.h" -#include "sub.h" -#include "nkeys.h" +#include "nuid.h" +#include "hash.h" #include "crypto.h" -#define WAIT_LIB_INITIALIZED \ - natsMutex_Lock(gLib.lock); \ - while (!(gLib.initialized) && !(gLib.initAborted)) \ - natsCondition_Wait(gLib.cond, gLib.lock); \ - natsMutex_Unlock(gLib.lock) +int nats_devmode_log_level = DEV_MODE_DEFAULT_LOG_LEVEL; + +#define MAX_FRAMES (50) typedef struct natsTLError { - natsStatus sts; - char text[256]; - const char *func[MAX_FRAMES]; - int framesCount; - int skipUpdate; + natsStatus sts; + char text[256]; + const char *func[MAX_FRAMES]; + int framesCount; + int skipUpdate; } natsTLError; -typedef struct __natsLibTimers -{ - natsMutex *lock; - natsCondition *cond; - natsThread *thread; - natsTimer *timers; - int count; - bool changed; - bool shutdown; - -} natsLibTimers; - -typedef struct __natsLibAsyncCbs -{ - natsMutex *lock; - natsCondition *cond; - natsThread *thread; - natsAsyncCbInfo *head; - natsAsyncCbInfo *tail; - bool shutdown; - -} natsLibAsyncCbs; - -typedef struct __natsGCList -{ - natsMutex *lock; - natsCondition *cond; - natsThread *thread; - natsGCItem *head; - bool shutdown; - bool inWait; - -} natsGCList; - -typedef struct __natsLibDlvWorkers +typedef struct __natsLib { - natsMutex *lock; - int idx; - int size; - int maxSize; - natsMsgDlvWorker **workers; + natsPool *pool; + natsHeap *heap; -} natsLibDlvWorkers; + bool sslInitialized; + bool initialized; + bool closed; + int refs; -typedef struct __natsLib -{ - // Leave these fields before 'refs' - natsMutex *lock; - volatile bool wasOpenedOnce; - bool sslInitialized; - natsThreadLocal errTLKey; - natsThreadLocal sslTLKey; - natsThreadLocal natsThreadKey; - bool initialized; - bool closed; - natsCondition *closeCompleteCond; - bool *closeCompleteBool; - bool closeCompleteSignal; - bool finalCleanup; - // Do not move 'refs' without checking _freeLib() - int refs; - - bool initializing; - bool initAborted; - bool libHandlingMsgDeliveryByDefault; - int64_t libDefaultWriteDeadline; - - natsLibTimers timers; - natsLibAsyncCbs asyncCbs; - natsLibDlvWorkers dlvWorkers; - - natsCondition *cond; - - natsGCList gc; - - // For micro services code - natsMutex *service_callback_mu; // uses `microService*` as the key and the value. natsHash *all_services_to_callback; } natsLib; -int64_t gLockSpinCount = 2000; - -static natsInitOnceType gInitOnce = NATS_ONCE_STATIC_INIT; -static natsLib gLib; - -static void -_destroyErrTL(void *localStorage) -{ - natsTLError *err = (natsTLError*) localStorage; - - NATS_FREE(err); -} - -static void -_cleanupThreadSSL(void *localStorage) -{ -#if defined(NATS_HAS_TLS) && !defined(NATS_USE_OPENSSL_1_1) - ERR_remove_thread_state(0); -#endif -} - -static void -_finalCleanup(void) -{ - if (gLib.sslInitialized) - { -#if defined(NATS_HAS_TLS) -#if !defined(NATS_USE_OPENSSL_1_1) - ERR_free_strings(); - EVP_cleanup(); - CRYPTO_cleanup_all_ex_data(); - ERR_remove_thread_state(0); -#endif - sk_SSL_COMP_free(SSL_COMP_get_compression_methods()); -#endif - natsThreadLocal_DestroyKey(gLib.sslTLKey); - } - - natsThreadLocal_DestroyKey(gLib.errTLKey); - natsThreadLocal_DestroyKey(gLib.natsThreadKey); - natsMutex_Destroy(gLib.lock); - gLib.lock = NULL; -} - -void -nats_setNATSThreadKey(void) -{ - natsThreadLocal_Set(gLib.natsThreadKey, (const void*)1); -} - -void -nats_ReleaseThreadMemory(void) -{ - void *tl = NULL; - - if (!(gLib.wasOpenedOnce)) - return; - - tl = natsThreadLocal_Get(gLib.errTLKey); - if (tl != NULL) - { - _destroyErrTL(tl); - natsThreadLocal_SetEx(gLib.errTLKey, NULL, false); - } - - tl = NULL; - - natsMutex_Lock(gLib.lock); - if (gLib.sslInitialized) - { - tl = natsThreadLocal_Get(gLib.sslTLKey); - if (tl != NULL) - { - _cleanupThreadSSL(tl); - natsThreadLocal_SetEx(gLib.sslTLKey, NULL, false); - } - } - natsMutex_Unlock(gLib.lock); -} - -#if defined(_WIN32) && _WIN32 -#ifndef NATS_STATIC -BOOL WINAPI DllMain(HINSTANCE hinstDLL, // DLL module handle - DWORD fdwReason, // reason called - LPVOID lpvReserved) // reserved -{ - switch (fdwReason) - { - // For applications linking dynamically NATS library, - // release thread-local memory for user-created threads. - // For portable applications, the user should manually call - // nats_ReleaseThreadMemory() before the thread returns so - // that no memory is leaked regardless if they link statically - // or dynamically. It is safe to call nats_ReleaseThreadMemory() - // twice for the same threads. - case DLL_THREAD_DETACH: - { - nats_ReleaseThreadMemory(); - break; - } - default: - break; - } - - return TRUE; - UNREFERENCED_PARAMETER(hinstDLL); - UNREFERENCED_PARAMETER(lpvReserved); -} -#endif -#endif - -static void -natsLib_Destructor(void) -{ - int refs = 0; - - if (!(gLib.wasOpenedOnce)) - return; - - // Destroy thread locals for the current thread. - nats_ReleaseThreadMemory(); - - // Do the final cleanup if possible - natsMutex_Lock(gLib.lock); - refs = gLib.refs; - if (refs > 0) - { - // If some thread is still around when the process exits and has a - // reference to the library, then don't do the final cleanup now. - // If the process has not fully exited when the lib's last reference - // is decremented, the final cleanup will be executed from that thread. - gLib.finalCleanup = true; - } - natsMutex_Unlock(gLib.lock); - - if (refs != 0) - return; - - _finalCleanup(); -} - -static void -_freeTimers(void) -{ - natsLibTimers *timers = &(gLib.timers); - - natsThread_Destroy(timers->thread); - natsCondition_Destroy(timers->cond); - natsMutex_Destroy(timers->lock); -} - -static void -_freeAsyncCbs(void) -{ - natsLibAsyncCbs *cbs = &(gLib.asyncCbs); - - natsThread_Destroy(cbs->thread); - natsCondition_Destroy(cbs->cond); - natsMutex_Destroy(cbs->lock); -} - -static void -_freeGC(void) -{ - natsGCList *gc = &(gLib.gc); - - natsThread_Destroy(gc->thread); - natsCondition_Destroy(gc->cond); - natsMutex_Destroy(gc->lock); -} - -static void -_freeDlvWorker(natsMsgDlvWorker *worker) -{ - natsThread_Destroy(worker->thread); - natsCondition_Destroy(worker->cond); - natsMutex_Destroy(worker->lock); - NATS_FREE(worker); -} - -static void -_freeDlvWorkers(void) -{ - int i; - natsLibDlvWorkers *workers = &(gLib.dlvWorkers); - - for (i=0; isize; i++) - _freeDlvWorker(workers->workers[i]); - - NATS_FREE(workers->workers); - natsMutex_Destroy(workers->lock); - workers->idx = 0; - workers->size = 0; - workers->workers = NULL; -} - -static void -_freeLib(void) -{ - const unsigned int offset = (unsigned int) offsetof(natsLib, refs); - bool callFinalCleanup = false; - - _freeTimers(); - _freeAsyncCbs(); - _freeGC(); - _freeDlvWorkers(); - natsNUID_free(); - natsMutex_Destroy(gLib.service_callback_mu); - natsHash_Destroy(gLib.all_services_to_callback); - - natsCondition_Destroy(gLib.cond); - - memset((void*) (offset + (char*)&gLib), 0, sizeof(natsLib) - offset); - - natsMutex_Lock(gLib.lock); - callFinalCleanup = gLib.finalCleanup; - if (gLib.closeCompleteCond != NULL) - { - if (gLib.closeCompleteSignal) - { - *gLib.closeCompleteBool = true; - natsCondition_Signal(gLib.closeCompleteCond); - } - gLib.closeCompleteCond = NULL; - gLib.closeCompleteBool = NULL; - gLib.closeCompleteSignal = false; - } - gLib.closed = false; - gLib.initialized = false; - gLib.finalCleanup= false; - natsMutex_Unlock(gLib.lock); - - if (callFinalCleanup) - _finalCleanup(); -} - -void -natsLib_Retain(void) -{ - natsMutex_Lock(gLib.lock); - - gLib.refs++; - - natsMutex_Unlock(gLib.lock); -} - -void -natsLib_Release(void) -{ - int refs = 0; - - natsMutex_Lock(gLib.lock); - - refs = --(gLib.refs); - - natsMutex_Unlock(gLib.lock); - - if (refs == 0) - _freeLib(); -} - -static void -_doInitOnce(void) -{ - natsStatus s; - - memset(&gLib, 0, sizeof(natsLib)); - - s = natsMutex_Create(&(gLib.lock)); - if (s == NATS_OK) - s = natsThreadLocal_CreateKey(&(gLib.errTLKey), _destroyErrTL); - if (s == NATS_OK) - s = natsThreadLocal_CreateKey(&(gLib.natsThreadKey), NULL); - if (s != NATS_OK) - { - fprintf(stderr, "FATAL ERROR: Unable to initialize library!\n"); - fflush(stderr); - abort(); - } - - natsSys_Init(); - - // Setup a hook for when the process exits. - atexit(natsLib_Destructor); -} - -static void -_insertTimer(natsTimer *t) -{ - natsTimer *cur = gLib.timers.timers; - natsTimer *prev = NULL; - - while ((cur != NULL) && (cur->absoluteTime <= t->absoluteTime)) - { - prev = cur; - cur = cur->next; - } - - if (cur != NULL) - { - t->prev = prev; - t->next = cur; - cur->prev = t; - - if (prev != NULL) - prev->next = t; - } - else if (prev != NULL) - { - prev->next = t; - t->prev = prev; - t->next = NULL; - } - - if (prev == NULL) - gLib.timers.timers = t; -} - -// Locks must be held before entering this function -static void -_removeTimer(natsLibTimers *timers, natsTimer *t) -{ - // Switch flag - t->stopped = true; - - // It the timer was in the callback, it has already been removed from the - // list, so skip that. - if (!(t->inCallback)) - { - if (t->prev != NULL) - t->prev->next = t->next; - if (t->next != NULL) - t->next->prev = t->prev; - - if (t == gLib.timers.timers) - gLib.timers.timers = t->next; - - t->prev = NULL; - t->next = NULL; - } - - // Decrease the global count of timers - timers->count--; -} - -int64_t -nats_setTargetTime(int64_t timeout) -{ - int64_t target = nats_Now() + timeout; - if (target < 0) - target = 0x7FFFFFFFFFFFFFFF; - return target; -} - -void -nats_resetTimer(natsTimer *t, int64_t newInterval) -{ - natsLibTimers *timers = &(gLib.timers); - - natsMutex_Lock(timers->lock); - natsMutex_Lock(t->mu); - - // If timer is active, we need first to remove it. This call does the - // right thing if the timer is in the callback. - if (!(t->stopped)) - _removeTimer(timers, t); - - // Bump the timer's global count (it as decreased in the _removeTimers call - timers->count++; - - // Switch stopped flag - t->stopped = false; - - // Set the new interval (may be same than it was before, but that's ok) - t->interval = newInterval; - - // If the timer is in the callback, the insertion and setting of the - // absolute time will be done by the timer thread when returning from - // the timer's callback. - if (!(t->inCallback)) - { - t->absoluteTime = nats_setTargetTime(t->interval); - _insertTimer(t); - } - - natsMutex_Unlock(t->mu); - - if (!(timers->changed)) - natsCondition_Signal(timers->cond); - - timers->changed = true; - - natsMutex_Unlock(timers->lock); -} - -void -nats_stopTimer(natsTimer *t) -{ - natsLibTimers *timers = &(gLib.timers); - bool doCb = false; - - natsMutex_Lock(timers->lock); - natsMutex_Lock(t->mu); - - // If the timer was already stopped, nothing to do. - if (t->stopped) - { - natsMutex_Unlock(t->mu); - natsMutex_Unlock(timers->lock); - - return; - } - - _removeTimer(timers, t); - - doCb = (!(t->inCallback) && (t->stopCb != NULL)); - - natsMutex_Unlock(t->mu); - - if (!(timers->changed)) - natsCondition_Signal(timers->cond); - - timers->changed = true; - - natsMutex_Unlock(timers->lock); - - if (doCb) - (*(t->stopCb))(t, t->closure); -} - -int -nats_getTimersCount(void) -{ - int count = 0; - - natsMutex_Lock(gLib.timers.lock); - - count = gLib.timers.count; - - natsMutex_Unlock(gLib.timers.lock); - - return count; -} - -int -nats_getTimersCountInList(void) -{ - int count = 0; - natsTimer *t; - - natsMutex_Lock(gLib.timers.lock); - - t = gLib.timers.timers; - while (t != NULL) - { - count++; - t = t->next; - } - - natsMutex_Unlock(gLib.timers.lock); - - return count; -} - - -static void -_timerThread(void *arg) -{ - natsLibTimers *timers = &(gLib.timers); - natsTimer *t = NULL; - natsStatus s = NATS_OK; - bool doStopCb; - int64_t target; - - WAIT_LIB_INITIALIZED; - - natsMutex_Lock(timers->lock); - - while (!(timers->shutdown)) - { - // Take the first timer that needs to fire. - t = timers->timers; - - if (t == NULL) - { - // No timer, fire in an hour... - target = nats_setTargetTime(3600 * 1000); - } - else - { - target = t->absoluteTime; - } - - timers->changed = false; - - s = NATS_OK; - - while (!(timers->shutdown) - && (s != NATS_TIMEOUT) - && !(timers->changed)) - { - s = natsCondition_AbsoluteTimedWait(timers->cond, timers->lock, - target); - } - - if (timers->shutdown) - break; - - if ((t == NULL) || timers->changed) - continue; - - natsMutex_Lock(t->mu); - - // Remove timer from the list: - timers->timers = t->next; - if (t->next != NULL) - t->next->prev = NULL; - - t->prev = NULL; - t->next = NULL; - - t->inCallback = true; - - // Retain the timer, since we are going to release the locks for the - // callback. The user may "destroy" the timer from there, so we need - // to be protected with reference counting. - t->refs++; - - natsMutex_Unlock(t->mu); - natsMutex_Unlock(timers->lock); - - (*(t->cb))(t, t->closure); - - natsMutex_Lock(timers->lock); - natsMutex_Lock(t->mu); - - t->inCallback = false; - - // Timer may have been stopped from within the callback, or during - // the window the locks were released. - doStopCb = (t->stopped && (t->stopCb != NULL)); - - // If not stopped, we need to put it back in our list - if (!doStopCb) - { - // Reset our view of what is the time this timer should fire - // because: - // 1- the callback may have taken longer than it should - // 2- the user may have called Reset() with a new interval - t->absoluteTime = nats_setTargetTime(t->interval); - _insertTimer(t); - } - - natsMutex_Unlock(t->mu); - natsMutex_Unlock(timers->lock); - - if (doStopCb) - (*(t->stopCb))(t, t->closure); - - // Compensate for the retain that we made before invoking the timer's - // callback - natsTimer_Release(t); - - natsMutex_Lock(timers->lock); - } - - // Process the timers that were left in the list (not stopped) when the - // library is shutdown. - while ((t = timers->timers) != NULL) - { - natsMutex_Lock(t->mu); - - // Check if we should invoke the callback. Note that although we are - // releasing the locks below, a timer present in the list here is - // guaranteed not to have been stopped (because it would not be in - // the list otherwise, since there is no chance that it is in the - // timer's callback). So just check if there is a stopCb to invoke. - doStopCb = (t->stopCb != NULL); - - // Remove the timer from the list. - _removeTimer(timers, t); - - natsMutex_Unlock(t->mu); - natsMutex_Unlock(timers->lock); - - // Invoke the callback now. - if (doStopCb) - (*(t->stopCb))(t, t->closure); - - // No release of the timer here. The user is still responsible to call - // natsTimer_Destroy(). - - natsMutex_Lock(timers->lock); - } - - natsMutex_Unlock(timers->lock); - - natsLib_Release(); -} - -static void -_asyncCbsThread(void *arg) -{ - natsLibAsyncCbs *asyncCbs = &(gLib.asyncCbs); - natsAsyncCbInfo *cb = NULL; - natsConnection *nc = NULL; -#if defined(NATS_HAS_STREAMING) - stanConnection *sc = NULL; -#endif - - WAIT_LIB_INITIALIZED; - - natsMutex_Lock(asyncCbs->lock); - - // We want all callbacks to be invoked even on shutdown - while (true) - { - while (((cb = asyncCbs->head) == NULL) && !asyncCbs->shutdown) - natsCondition_Wait(asyncCbs->cond, asyncCbs->lock); - - if ((cb == NULL) && asyncCbs->shutdown) - break; - - asyncCbs->head = cb->next; - - if (asyncCbs->tail == cb) - asyncCbs->tail = NULL; - - cb->next = NULL; - - natsMutex_Unlock(asyncCbs->lock); - - nc = cb->nc; -#if defined(NATS_HAS_STREAMING) - sc = cb->sc; -#endif - - switch (cb->type) - { - case ASYNC_CLOSED: - { - (*(nc->opts->closedCb))(nc, nc->opts->closedCbClosure); - if (nc->opts->microClosedCb != NULL) - (*(nc->opts->microClosedCb))(nc, NULL); - break; - } - - case ASYNC_DISCONNECTED: - (*(nc->opts->disconnectedCb))(nc, nc->opts->disconnectedCbClosure); - break; - case ASYNC_RECONNECTED: - (*(nc->opts->reconnectedCb))(nc, nc->opts->reconnectedCbClosure); - break; - case ASYNC_CONNECTED: - (*(nc->opts->connectedCb))(nc, nc->opts->connectedCbClosure); - break; - case ASYNC_DISCOVERED_SERVERS: - (*(nc->opts->discoveredServersCb))(nc, nc->opts->discoveredServersClosure); - break; - case ASYNC_LAME_DUCK_MODE: - (*(nc->opts->lameDuckCb))(nc, nc->opts->lameDuckClosure); - break; - case ASYNC_ERROR: - { - if (cb->errTxt != NULL) - nats_setErrStatusAndTxt(cb->err, cb->errTxt); - - (*(nc->opts->asyncErrCb))(nc, cb->sub, cb->err, nc->opts->asyncErrCbClosure); - if (nc->opts->microAsyncErrCb != NULL) - (*(nc->opts->microAsyncErrCb))(nc, cb->sub, cb->err, NULL); - break; - } -#if defined(NATS_HAS_STREAMING) - case ASYNC_STAN_CONN_LOST: - (*(sc->opts->connectionLostCB))(sc, sc->connLostErrTxt, sc->opts->connectionLostCBClosure); - break; -#endif - default: - break; - } - - natsAsyncCb_Destroy(cb); - nats_clearLastError(); - - natsMutex_Lock(asyncCbs->lock); - } - - natsMutex_Unlock(asyncCbs->lock); - - natsLib_Release(); -} - -natsStatus -nats_postAsyncCbInfo(natsAsyncCbInfo *info) -{ - natsMutex_Lock(gLib.asyncCbs.lock); - - if (gLib.asyncCbs.shutdown) - { - natsMutex_Unlock(gLib.asyncCbs.lock); - return NATS_NOT_INITIALIZED; - } - - info->next = NULL; - - if (gLib.asyncCbs.head == NULL) - gLib.asyncCbs.head = info; - - if (gLib.asyncCbs.tail != NULL) - gLib.asyncCbs.tail->next = info; - - gLib.asyncCbs.tail = info; - - natsCondition_Signal(gLib.asyncCbs.cond); - - natsMutex_Unlock(gLib.asyncCbs.lock); - - return NATS_OK; -} - -static void -_garbageCollector(void *closure) -{ - natsGCList *gc = &(gLib.gc); - natsGCItem *item; - natsGCItem *list; - - WAIT_LIB_INITIALIZED; - - natsMutex_Lock(gc->lock); - - // Process all elements in the list, even on shutdown - while (true) - { - // Go into wait until we are notified to shutdown - // or there is something to garbage collect - gc->inWait = true; - - while (!(gc->shutdown) && (gc->head == NULL)) - { - natsCondition_Wait(gc->cond, gc->lock); - } - - // Out of the wait. Setting this boolean avoids unnecessary - // signaling when an item is added to the collector. - gc->inWait = false; - - // Do not break out on shutdown here, we want to clear the list, - // even on exit so that valgrind and the like are happy. - - // Under the lock, we will switch to a local list and reset the - // GC's list (so that others can add to the list without contention - // (at least from the GC itself). - do - { - list = gc->head; - gc->head = NULL; - - natsMutex_Unlock(gc->lock); - - // Now that we are outside of the lock, we can empty the list. - while ((item = list) != NULL) - { - // Pops item from the beginning of the list. - list = item->next; - item->next = NULL; - - // Invoke the freeCb associated with this object - (*(item->freeCb))((void*) item); - } - - natsMutex_Lock(gc->lock); - } - while (gc->head != NULL); - - // If we were ask to shutdown and since the list is now empty, exit - if (gc->shutdown) - break; - } - - natsMutex_Unlock(gc->lock); - - natsLib_Release(); -} - -bool -natsGC_collect(natsGCItem *item) -{ - natsGCList *gc; - bool signal; - - // If the object was not setup for garbage collection, return false - // so the caller frees the object. - if (item->freeCb == NULL) - return false; - - gc = &(gLib.gc); - - natsMutex_Lock(gc->lock); - - // We will signal only if the GC is in the condition wait. - signal = gc->inWait; - - // Add to the front of the list. - item->next = gc->head; - - // Update head. - gc->head = item; - - if (signal) - natsCondition_Signal(gc->cond); - - natsMutex_Unlock(gc->lock); - - return true; -} - -static void -_libTearDown(void) -{ - int i; - - for (i=0; ithread != NULL) - natsThread_Join(worker->thread); - } - - if (gLib.timers.thread != NULL) - natsThread_Join(gLib.timers.thread); - - if (gLib.asyncCbs.thread != NULL) - natsThread_Join(gLib.asyncCbs.thread); - - if (gLib.gc.thread != NULL) - natsThread_Join(gLib.gc.thread); - - natsLib_Release(); -} - -natsStatus -nats_Open(int64_t lockSpinCount) -{ - natsStatus s = NATS_OK; - - if (!nats_InitOnce(&gInitOnce, _doInitOnce)) - return NATS_FAILED_TO_INITIALIZE; - - natsMutex_Lock(gLib.lock); - - if (gLib.closed || gLib.initialized || gLib.initializing) - { - if (gLib.closed) - s = NATS_FAILED_TO_INITIALIZE; - else if (gLib.initializing) - s = NATS_ILLEGAL_STATE; - - natsMutex_Unlock(gLib.lock); - return s; - } - - gLib.initializing = true; - gLib.initAborted = false; - -#if !defined(_WIN32) - signal(SIGPIPE, SIG_IGN); -#endif - - srand((unsigned int) nats_NowInNanoSeconds()); - - gLib.refs = 1; - - // If the caller specifies negative value, then we use the default - if (lockSpinCount >= 0) - gLockSpinCount = lockSpinCount; - - nats_Base32_Init(); - - s = natsCondition_Create(&(gLib.cond)); - - if (s == NATS_OK) - s = natsCrypto_Init(); - - if (s == NATS_OK) - s = natsMutex_Create(&(gLib.timers.lock)); - if (s == NATS_OK) - s = natsCondition_Create(&(gLib.timers.cond)); - if (s == NATS_OK) - { - s = natsThread_Create(&(gLib.timers.thread), _timerThread, NULL); - if (s == NATS_OK) - gLib.refs++; - } - - if (s == NATS_OK) - s = natsMutex_Create(&(gLib.asyncCbs.lock)); - if (s == NATS_OK) - s = natsCondition_Create(&(gLib.asyncCbs.cond)); - if (s == NATS_OK) - { - s = natsThread_Create(&(gLib.asyncCbs.thread), _asyncCbsThread, NULL); - if (s == NATS_OK) - gLib.refs++; - } - if (s == NATS_OK) - s = natsMutex_Create(&(gLib.gc.lock)); - if (s == NATS_OK) - s = natsCondition_Create(&(gLib.gc.cond)); - if (s == NATS_OK) - { - s = natsThread_Create(&(gLib.gc.thread), _garbageCollector, NULL); - if (s == NATS_OK) - gLib.refs++; - } - if (s == NATS_OK) - s = natsNUID_init(); - - if (s == NATS_OK) - s = natsMutex_Create(&(gLib.dlvWorkers.lock)); - if (s == NATS_OK) - { - char *defaultWriteDeadlineStr = getenv("NATS_DEFAULT_LIB_WRITE_DEADLINE"); - - if (defaultWriteDeadlineStr != NULL) - gLib.libDefaultWriteDeadline = (int64_t) atol(defaultWriteDeadlineStr); - - gLib.libHandlingMsgDeliveryByDefault = (getenv("NATS_DEFAULT_TO_LIB_MSG_DELIVERY") != NULL ? true : false); - gLib.dlvWorkers.maxSize = 1; - gLib.dlvWorkers.workers = NATS_CALLOC(gLib.dlvWorkers.maxSize, sizeof(natsMsgDlvWorker*)); - if (gLib.dlvWorkers.workers == NULL) - s = NATS_NO_MEMORY; - } - if (s == NATS_OK) - s = natsMutex_Create(&gLib.service_callback_mu); - if (s == NATS_OK) - s = natsHash_Create(&gLib.all_services_to_callback, 8); - - if (s == NATS_OK) - gLib.initialized = true; +static natsLib gLib; - // In case of success or error, broadcast so that lib's threads - // can proceed. - if (gLib.cond != NULL) +#if defined(_WIN32) && _WIN32 +#ifndef NATS_STATIC +BOOL WINAPI DllMain(HINSTANCE hinstDLL, // DLL module handle + DWORD fdwReason, // reason called + LPVOID lpvReserved) // reserved +{ + switch (fdwReason) { - if (s != NATS_OK) - { - gLib.initAborted = true; - gLib.timers.shutdown = true; - gLib.asyncCbs.shutdown = true; - gLib.gc.shutdown = true; - } - natsCondition_Broadcast(gLib.cond); + // For applications linking dynamically NATS library, + // release thread-local memory for user-created threads. + // For portable applications, the user should manually call + // nats_ReleaseThreadMemory() before the thread returns so + // that no memory is leaked regardless if they link statically + // or dynamically. It is safe to call nats_ReleaseThreadMemory() + // twice for the same threads. + case DLL_THREAD_DETACH: + { + nats_ReleaseThreadMemory(); + break; + } + default: + break; } - gLib.initializing = false; - gLib.wasOpenedOnce = true; - - natsMutex_Unlock(gLib.lock); - - if (s != NATS_OK) - _libTearDown(); - - return s; + return TRUE; + UNREFERENCED_PARAMETER(hinstDLL); + UNREFERENCED_PARAMETER(lpvReserved); } +#endif +#endif -natsStatus -natsInbox_Create(natsInbox **newInbox) +static void +_freeLib(void) { - natsStatus s; - char *inbox = NULL; - const int size = NATS_DEFAULT_INBOX_PRE_LEN + NUID_BUFFER_LEN + 1; + const unsigned int offset = (unsigned int)offsetof(natsLib, refs); - s = nats_Open(-1); - if (s != NATS_OK) - return s; + natsNUID_free(); + nats_releasePool(gLib.pool); + gLib.heap->destroy(gLib.heap); - inbox = NATS_MALLOC(size); - if (inbox == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); + memset((void *)(offset + (char *)&gLib), 0, sizeof(natsLib) - offset); +} - memcpy(inbox, NATS_DEFAULT_INBOX_PRE, NATS_DEFAULT_INBOX_PRE_LEN); - s = natsNUID_Next(inbox + NATS_DEFAULT_INBOX_PRE_LEN, NUID_BUFFER_LEN + 1); - if (s == NATS_OK) - { - inbox[size-1] = '\0'; - *newInbox = (natsInbox*) inbox; - } - else - NATS_FREE(inbox); - return NATS_UPDATE_ERR_STACK(s); +void nats_retainGlobalLib(void) +{ + gLib.refs++; } -void -natsInbox_Destroy(natsInbox *inbox) +void nats_Shutdown(void) { - if (inbox == NULL) + if (gLib.closed) return; + gLib.closed = true; - NATS_FREE(inbox); + nats_releaseGlobalLib(); } - -static natsStatus -_close(bool wait, int64_t timeout) +void nats_releaseGlobalLib(void) { - natsStatus s = NATS_OK; - natsCondition *cond = NULL; - bool complete = false; - int i; - - // This is to protect against a call to nats_Close() while there - // was no prior call to nats_Open(), either directly or indirectly. - if (!nats_InitOnce(&gInitOnce, _doInitOnce)) - return NATS_ERR; - - natsMutex_Lock(gLib.lock); - - if (gLib.closed || !gLib.initialized) - { - bool closed = gLib.closed; - - natsMutex_Unlock(gLib.lock); + int refs = 0; - if (closed) - return NATS_ILLEGAL_STATE; - return NATS_NOT_INITIALIZED; - } - if (wait) - { - if (natsThreadLocal_Get(gLib.natsThreadKey) != NULL) - s = NATS_ILLEGAL_STATE; - if (s == NATS_OK) - s = natsCondition_Create(&cond); - if (s != NATS_OK) - { - natsMutex_Unlock(gLib.lock); - return s; - } - gLib.closeCompleteCond = cond; - gLib.closeCompleteBool = &complete; - gLib.closeCompleteSignal = true; - } + refs = --(gLib.refs); - gLib.closed = true; + if (refs == 0) + _freeLib(); +} - natsMutex_Lock(gLib.timers.lock); - gLib.timers.shutdown = true; - natsCondition_Signal(gLib.timers.cond); - natsMutex_Unlock(gLib.timers.lock); +natsPool *nats_globalPool(void) +{ + return gLib.pool; +} - natsMutex_Lock(gLib.asyncCbs.lock); - gLib.asyncCbs.shutdown = true; - natsCondition_Signal(gLib.asyncCbs.cond); - natsMutex_Unlock(gLib.asyncCbs.lock); +natsHeap *nats_globalHeap(void) +{ + return gLib.heap; +} - natsMutex_Lock(gLib.gc.lock); - gLib.gc.shutdown = true; - natsCondition_Signal(gLib.gc.cond); - natsMutex_Unlock(gLib.gc.lock); +natsStatus +nats_open(void) +{ + natsStatus s = NATS_OK; - natsMutex_Lock(gLib.dlvWorkers.lock); - for (i=0; ilock); - worker->shutdown = true; - natsCondition_Signal(worker->cond); - natsMutex_Unlock(worker->lock); + return NATS_OK; } - natsMutex_Unlock(gLib.dlvWorkers.lock); - natsMutex_Unlock(gLib.lock); + memset(&gLib, 0, sizeof(natsLib)); + gLib.refs = 1; - nats_ReleaseThreadMemory(); - _libTearDown(); + nats_sysInit(); + nats_Base32_Init(); - if (wait) - { - natsMutex_Lock(gLib.lock); - while ((s != NATS_TIMEOUT) && !complete) - { - if (timeout <= 0) - natsCondition_Wait(cond, gLib.lock); - else - s = natsCondition_TimedWait(cond, gLib.lock, timeout); - } - if (s != NATS_OK) - gLib.closeCompleteSignal = false; - natsMutex_Unlock(gLib.lock); + // Initialize the heap + s = CHECK_NO_MEMORY(gLib.heap = nats_NewMallocHeap()); - natsCondition_Destroy(cond); - } + IFOK(s, nats_createPool(&(gLib.pool), &nats_defaultMemOptions, "global")); + IFOK(s, natsCrypto_Init()); + IFOK(s, natsNUID_init()); + // IFOK(s, natsHash_Create(&gLib.all_services_to_callback, NULL, 8)); + if (STILL_OK(s)) + gLib.initialized = true; + else + _freeLib(); return s; } -void -nats_Close(void) -{ - _close(false, true); -} - -natsStatus -nats_CloseAndWait(int64_t timeout) -{ - return _close(true, timeout); -} - -const char* +// natsStatus +// natsInbox_Create(natsInbox **newInbox) +// { +// natsStatus s; +// char *inbox = NULL; +// const int size = NATS_DEFAULT_INBOX_PRE_LEN + NUID_BUFFER_LEN + 1; + +// s = nats_open(); +// if (s != NATS_OK) +// return s; + +// inbox = NATS_MALLOC(size); +// if (inbox == NULL) +// return nats_setDefaultError(NATS_NO_MEMORY); + +// memcpy(inbox, NATS_DEFAULT_INBOX_PRE, NATS_DEFAULT_INBOX_PRE_LEN); +// s = natsNUID_Next(inbox + NATS_DEFAULT_INBOX_PRE_LEN, NUID_BUFFER_LEN + 1); +// if (STILL_OK(s)) +// { +// inbox[size - 1] = '\0'; +// *newInbox = (natsInbox *)inbox; +// } +// else +// NATS_FREE(inbox); +// return NATS_UPDATE_ERR_STACK(s); +// } + +// void natsInbox_Destroy(natsInbox *inbox) +// { +// if (inbox == NULL) +// return; + +// NATS_FREE(inbox); +// } + +const char * nats_GetVersion(void) { return LIB_NATS_VERSION_STRING; @@ -1260,12 +215,10 @@ _versionGetString(char *buffer, size_t bufLen, uint32_t verNumber) (verNumber & 0xF)); } -bool -nats_CheckCompatibilityImpl(uint32_t headerReqVerNumber, uint32_t headerVerNumber, - const char *headerVerString) +bool nats_CheckCompatibilityImpl(uint32_t headerReqVerNumber, uint32_t headerVerNumber, + const char *headerVerString) { - if ((headerVerNumber < LIB_NATS_VERSION_REQUIRED_NUMBER) - || (headerReqVerNumber > LIB_NATS_VERSION_NUMBER)) + if ((headerVerNumber < LIB_NATS_VERSION_REQUIRED_NUMBER) || (headerReqVerNumber > LIB_NATS_VERSION_NUMBER)) { char reqVerString[10]; char libReqVerString[10]; @@ -1273,8 +226,8 @@ nats_CheckCompatibilityImpl(uint32_t headerReqVerNumber, uint32_t headerVerNumbe _versionGetString(reqVerString, sizeof(reqVerString), headerReqVerNumber); _versionGetString(libReqVerString, sizeof(libReqVerString), NATS_VERSION_REQUIRED_NUMBER); - printf("Incompatible versions:\n" \ - "Header : %s (requires %s)\n" \ + printf("Incompatible versions:\n" + "Header : %s (requires %s)\n" "Library: %s (requires %s)\n", headerVerString, reqVerString, NATS_VERSION_STRING, libReqVerString); @@ -1284,54 +237,26 @@ nats_CheckCompatibilityImpl(uint32_t headerReqVerNumber, uint32_t headerVerNumbe return true; } -static natsTLError* +static natsTLError globalTLError = { + .sts = NATS_OK, + .framesCount = -1, +}; + +static natsTLError * _getTLError(void) { - natsTLError *errTL = NULL; - bool needFree = false; - - // The library should already be initialized, but let's protect against - // situations where foo() invokes bar(), which invokes baz(), which - // invokes nats_Open(). If that last call fails, when we un-wind down - // to foo(), it may be difficult to know that nats_Open() failed and - // that we should not try to invoke natsLib_setError. So we check again - // here that the library has been initialized properly, and if not, we - // simply don't set the error. - if (nats_Open(-1) != NATS_OK) - return NULL; - - errTL = natsThreadLocal_Get(gLib.errTLKey); - if (errTL == NULL) - { - errTL = (natsTLError*) NATS_CALLOC(1, sizeof(natsTLError)); - if (errTL != NULL) - errTL->framesCount = -1; - needFree = (errTL != NULL); - - } - - if ((errTL != NULL) - && (natsThreadLocal_SetEx(gLib.errTLKey, - (const void*) errTL, false) != NATS_OK)) - { - if (needFree) - NATS_FREE(errTL); - - errTL = NULL; - } - - return errTL; + return &globalTLError; } -static char* -_getErrorShortFileName(const char* fileName) +static char * +_getErrorShortFileName(const char *fileName) { char *file = strstr(fileName, "src"); if (file != NULL) file = (file + 4); else - file = (char*) fileName; + file = (char *)fileName; return file; } @@ -1340,12 +265,9 @@ static void _updateStack(natsTLError *errTL, const char *funcName, natsStatus errSts, bool calledFromSetError) { - int idx; + int idx = errTL->framesCount; - idx = errTL->framesCount; - if ((idx >= 0) - && (idx < MAX_FRAMES) - && (strcmp(errTL->func[idx], funcName) == 0)) + if ((idx >= 0) && (idx < MAX_FRAMES) && (strcmp(errTL->func[idx], funcName) == 0)) { return; } @@ -1363,15 +285,15 @@ _updateStack(natsTLError *errTL, const char *funcName, natsStatus errSts, } #if !defined(_WIN32) -__attribute__ ((format (printf, 5, 6))) +__attribute__((format(printf, 5, 6))) #endif natsStatus nats_setErrorReal(const char *fileName, const char *funcName, int line, natsStatus errSts, const char *errTxtFmt, ...) { - natsTLError *errTL = _getTLError(); - char tmp[256]; - va_list ap; - int n; + natsTLError *errTL = _getTLError(); + char tmp[256]; + va_list ap; + int n; if ((errTL == NULL) || errTL->skipUpdate) return errSts; @@ -1389,12 +311,12 @@ nats_setErrorReal(const char *fileName, const char *funcName, int line, natsStat { n = snprintf(errTL->text, sizeof(errTL->text), "(%s:%d): %s", _getErrorShortFileName(fileName), line, tmp); - if ((n < 0) || (n >= (int) sizeof(errTL->text))) + if ((n < 0) || (n >= (int)sizeof(errTL->text))) { - int pos = ((int) strlen(errTL->text)) - 1; + int pos = ((int)strlen(errTL->text)) - 1; int i; - for (i=0; i<3; i++) + for (i = 0; i < 3; i++) errTL->text[pos--] = '.'; } } @@ -1405,15 +327,15 @@ nats_setErrorReal(const char *fileName, const char *funcName, int line, natsStat } #if !defined(_WIN32) -__attribute__ ((format (printf, 4, 5))) +__attribute__((format(printf, 4, 5))) #endif void nats_updateErrTxt(const char *fileName, const char *funcName, int line, const char *errTxtFmt, ...) { - natsTLError *errTL = _getTLError(); - char tmp[256]; - va_list ap; - int n; + natsTLError *errTL = _getTLError(); + char tmp[256]; + va_list ap; + int n; if ((errTL == NULL) || errTL->skipUpdate) return; @@ -1428,26 +350,25 @@ nats_updateErrTxt(const char *fileName, const char *funcName, int line, const ch { n = snprintf(errTL->text, sizeof(errTL->text), "(%s:%d): %s", _getErrorShortFileName(fileName), line, tmp); - if ((n < 0) || (n >= (int) sizeof(errTL->text))) + if ((n < 0) || (n >= (int)sizeof(errTL->text))) { - int pos = ((int) strlen(errTL->text)) - 1; + int pos = ((int)strlen(errTL->text)) - 1; int i; - for (i=0; i<3; i++) + for (i = 0; i < 3; i++) errTL->text[pos--] = '.'; } } } -void -nats_setErrStatusAndTxt(natsStatus err, const char *errTxt) +void nats_setErrStatusAndTxt(natsStatus err, const char *errTxt) { - natsTLError *errTL = _getTLError(); + natsTLError *errTL = _getTLError(); if ((errTL == NULL) || errTL->skipUpdate) return; - errTL->sts = err; + errTL->sts = err; snprintf(errTL->text, sizeof(errTL->text), "%s", errTxt); errTL->framesCount = -1; } @@ -1465,23 +386,21 @@ nats_updateErrStack(natsStatus err, const char *func) return err; } -void -nats_clearLastError(void) +void nats_clearLastError(void) { - natsTLError *errTL = _getTLError(); + natsTLError *errTL = _getTLError(); if ((errTL == NULL) || errTL->skipUpdate) return; - errTL->sts = NATS_OK; - errTL->text[0] = '\0'; + errTL->sts = NATS_OK; + errTL->text[0] = '\0'; errTL->framesCount = -1; } -void -nats_doNotUpdateErrStack(bool skipStackUpdate) +void nats_doNotUpdateErrStack(bool skipStackUpdate) { - natsTLError *errTL = _getTLError(); + natsTLError *errTL = _getTLError(); if (errTL == NULL) return; @@ -1493,25 +412,23 @@ nats_doNotUpdateErrStack(bool skipStackUpdate) else { errTL->skipUpdate--; - assert(errTL->skipUpdate >= 0); } } -const char* +const char * nats_GetLastError(natsStatus *status) { - natsStatus s; - natsTLError *errTL = NULL; + natsStatus s; + natsTLError *errTL = _getTLError(); if (status != NULL) *status = NATS_OK; // Ensure the library is loaded - s = nats_Open(-1); + s = nats_open(); if (s != NATS_OK) return NULL; - errTL = natsThreadLocal_Get(gLib.errTLKey); if ((errTL == NULL) || (errTL->sts == NATS_OK)) return NULL; @@ -1524,21 +441,20 @@ nats_GetLastError(natsStatus *status) natsStatus nats_GetLastErrorStack(char *buffer, size_t bufLen) { - natsTLError *errTL = NULL; - int offset = 0; - int i, max, n, len; + natsTLError *errTL = _getTLError(); + int offset = 0; + int i, max, n, len; if ((buffer == NULL) || (bufLen == 0)) return NATS_INVALID_ARG; buffer[0] = '\0'; - len = (int) bufLen; + len = (int)bufLen; // Ensure the library is loaded - if (nats_Open(-1) != NATS_OK) + if (nats_open() != NATS_OK) return NATS_FAILED_TO_INITIALIZE; - errTL = natsThreadLocal_Get(gLib.errTLKey); if ((errTL == NULL) || (errTL->sts == NATS_OK) || (errTL->framesCount == -1)) return NATS_OK; @@ -1546,7 +462,7 @@ nats_GetLastErrorStack(char *buffer, size_t bufLen) if (max >= MAX_FRAMES) max = MAX_FRAMES - 1; - for (i=0; (i<=max) && (len > 0); i++) + for (i = 0; (i <= max) && (len > 0); i++) { n = snprintf(buffer + offset, len, "%s%s", errTL->func[i], @@ -1559,7 +475,7 @@ nats_GetLastErrorStack(char *buffer, size_t bufLen) else { offset += n; - len -= n; + len -= n; } } @@ -1580,17 +496,16 @@ nats_GetLastErrorStack(char *buffer, size_t bufLen) return NATS_OK; } -void -nats_PrintLastErrorStack(FILE *file) +void nats_PrintLastErrorStack(FILE *file) { - natsTLError *errTL = NULL; + natsTLError *errTL = NULL; int i, max; // Ensure the library is loaded - if (nats_Open(-1) != NATS_OK) + if (nats_open() != NATS_OK) return; - errTL = natsThreadLocal_Get(gLib.errTLKey); + errTL = _getTLError(); if ((errTL == NULL) || (errTL->sts == NATS_OK) || (errTL->framesCount == -1)) return; @@ -1605,8 +520,8 @@ nats_PrintLastErrorStack(FILE *file) if (max >= MAX_FRAMES) max = MAX_FRAMES - 1; - for (i=0; i<=max; i++) - fprintf(file, " %02d - %s\n", (i+1), errTL->func[i]); + for (i = 0; i <= max; i++) + fprintf(file, " %02d - %s\n", (i + 1), errTL->func[i]); if (max != errTL->framesCount) fprintf(file, " %d more...\n", errTL->framesCount - max); @@ -1614,436 +529,11 @@ nats_PrintLastErrorStack(FILE *file) fflush(file); } -void -nats_sslRegisterThreadForCleanup(void) -{ -#if defined(NATS_HAS_TLS) - // Set anything. The goal is that at thread exit, the thread local key - // will have something non NULL associated, which will trigger the - // destructor that we have registered. - (void) natsThreadLocal_Set(gLib.sslTLKey, (void*) 1); -#endif -} - -natsStatus -nats_sslInit(void) -{ - natsStatus s = NATS_OK; - - // Ensure the library is loaded - s = nats_Open(-1); - if (s != NATS_OK) - return s; - - natsMutex_Lock(gLib.lock); - - if (!(gLib.sslInitialized)) - { - // Regardless of success, mark as initialized so that we - // can do cleanup on exit. - gLib.sslInitialized = true; - -#if defined(NATS_HAS_TLS) -#if !defined(NATS_USE_OPENSSL_1_1) - // Initialize SSL. - SSL_library_init(); - SSL_load_error_strings(); -#endif -#endif - s = natsThreadLocal_CreateKey(&(gLib.sslTLKey), _cleanupThreadSSL); - } - - natsMutex_Unlock(gLib.lock); - - return NATS_UPDATE_ERR_STACK(s); -} - -static void -_deliverMsgs(void *arg) -{ - natsMsgDlvWorker *dlv = (natsMsgDlvWorker*) arg; - natsConnection *nc; - natsSubscription *sub; - natsMsgHandler mcb; - void *mcbClosure; - uint64_t delivered; - uint64_t max; - natsMsg *msg; - bool timerNeedReset = false; - jsSub *jsi; - char *fcReply; - - natsMutex_Lock(dlv->lock); - - while (true) - { - while (((msg = dlv->msgList.head) == NULL) && !dlv->shutdown) - natsCondition_Wait(dlv->cond, dlv->lock); - - // Break out only when list is empty - if ((msg == NULL) && dlv->shutdown) - { - break; - } - - // Remove message from list now... - dlv->msgList.head = msg->next; - if (dlv->msgList.tail == msg) - dlv->msgList.tail = NULL; - msg->next = NULL; - - // Get subscription reference from message - sub = msg->sub; - - // Capture these under lock - nc = sub->conn; - mcb = sub->msgCb; - mcbClosure = sub->msgCbClosure; - max = sub->max; - - // Is this a control message? - if (msg->subject[0] == '\0') - { - bool closed = sub->closed; - bool timedOut = sub->timedOut; - bool draining = sub->libDlvDraining; - - // Switch off this flag. - if (draining) - sub->libDlvDraining = false; - - // We need to release this lock... - natsMutex_Unlock(dlv->lock); - - // Release the message - natsMsg_Destroy(msg); - - if (draining) - { - // Subscription is draining, we are past the last message, - // remove the subscription. This will schedule another - // control message for the close. - natsSub_setDrainCompleteState(sub); - natsConn_removeSubscription(nc, sub); - } - else if (closed) - { - natsOnCompleteCB cb = NULL; - void *closure = NULL; - - // Call this in case the subscription was draining. - natsSub_setDrainCompleteState(sub); - - // Check for completion callback - natsSub_Lock(sub); - cb = sub->onCompleteCB; - closure = sub->onCompleteCBClosure; - natsSub_Unlock(sub); - - if (cb != NULL) - (*cb)(closure); - - // Subscription closed, just release - natsSub_release(sub); - } - else if (timedOut) - { - // Invoke the callback with a NULL message. - (*mcb)(nc, sub, NULL, mcbClosure); - } - - // Grab the lock, we go back to beginning of loop. - natsMutex_Lock(dlv->lock); - - if (!draining && !closed && timedOut) - { - // Reset the timedOut boolean to allow for the - // subscription to timeout again, and reset the - // timer to fire again starting from now. - sub->timedOut = false; - natsTimer_Reset(sub->timeoutTimer, sub->timeout); - } - - // Go back to top of loop. - continue; - } - - // Update before checking closed state. - sub->msgList.msgs--; - sub->msgList.bytes -= natsMsg_dataAndHdrLen(msg); - - // Need to check for closed subscription again here. - // The subscription could have been unsubscribed from a callback - // but there were already pending messages. The control message - // is queued up. Until it is processed, we need to simply - // discard the message and continue. - if (sub->closed) - { - natsMsg_Destroy(msg); - continue; - } - - delivered = ++(sub->delivered); - - jsi = sub->jsi; - fcReply = (jsi == NULL ? NULL : jsSub_checkForFlowControlResponse(sub)); - - // Is this a subscription that can timeout? - if (!sub->draining && (sub->timeout != 0)) - { - // Prevent the timer to post a timeout control message - sub->timeoutSuspended = true; - - // If we are dealing with the last pending message for this sub, - // we will reset the timer after the user callback returns. - if (sub->msgList.msgs == 0) - timerNeedReset = true; - } - - natsMutex_Unlock(dlv->lock); - - if ((max == 0) || (delivered <= max)) - { - (*mcb)(nc, sub, msg, mcbClosure); - } - else - { - // We need to destroy the message since the user can't do it - natsMsg_Destroy(msg); - } - - if (fcReply != NULL) - { - natsConnection_Publish(nc, fcReply, NULL, 0); - NATS_FREE(fcReply); - } - - // Don't do 'else' because we need to remove when we have hit - // the max (after the callback returns). - if ((max > 0) && (delivered >= max)) - { - // Call this blindly, it will be a no-op if the subscription was not draining. - natsSub_setDrainCompleteState(sub); - // If we have hit the max for delivered msgs, remove sub. - natsConn_removeSubscription(nc, sub); - } - - natsMutex_Lock(dlv->lock); - - // Check if timer need to be reset for subscriptions that can timeout. - if (!sub->closed && (sub->timeout != 0) && timerNeedReset) - { - timerNeedReset = false; - - // Do this only on timer reset instead of after each return - // from callback. The reason is that if there are still pending - // messages for this subscription (this is the case otherwise - // timerNeedReset would be false), we should prevent - // the subscription to timeout anyway. - sub->timeoutSuspended = false; - - // Reset the timer to fire in `timeout` from now. - natsTimer_Reset(sub->timeoutTimer, sub->timeout); - } - } - - natsMutex_Unlock(dlv->lock); - - natsLib_Release(); -} - -natsStatus -nats_SetMessageDeliveryPoolSize(int max) -{ - natsStatus s = NATS_OK; - natsLibDlvWorkers *workers; - - // Ensure the library is loaded - s = nats_Open(-1); - if (s != NATS_OK) - return s; - - workers = &gLib.dlvWorkers; - - natsMutex_Lock(workers->lock); - - if (max <= 0) - { - natsMutex_Unlock(workers->lock); - return nats_setError(NATS_ERR, "%s", "Pool size cannot be negative or zero"); - } - - // Do not error on max < workers->maxSize in case we allow shrinking - // the pool in the future. - if (max > workers->maxSize) - { - natsMsgDlvWorker **newArray = NATS_CALLOC(max, sizeof(natsMsgDlvWorker*)); - if (newArray == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - if (s == NATS_OK) - { - int i; - for (i=0; isize; i++) - newArray[i] = workers->workers[i]; - - NATS_FREE(workers->workers); - workers->workers = newArray; - workers->maxSize = max; - } - } - - natsMutex_Unlock(workers->lock); - - return NATS_UPDATE_ERR_STACK(s); -} - -// Post a control message to the worker's queue. -natsStatus -natsLib_msgDeliveryPostControlMsg(natsSubscription *sub) -{ - natsStatus s; - natsMsg *controlMsg = NULL; - natsMsgDlvWorker *worker = (sub->libDlvWorker); - - // Create a "end" message and post it to the delivery worker - s = natsMsg_create(&controlMsg, NULL, 0, NULL, 0, NULL, 0, -1); - if (s == NATS_OK) - { - nats_MsgList *l; - bool signal = false; - - natsMutex_Lock(worker->lock); - - controlMsg->sub = sub; - - l = &(worker->msgList); - if (l->head == NULL) - { - l->head = controlMsg; - signal = true; - } - else - l->tail->next = controlMsg; - l->tail = controlMsg; - - if (signal) - natsCondition_Signal(worker->cond); - - natsMutex_Unlock(worker->lock); - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsLib_msgDeliveryAssignWorker(natsSubscription *sub) -{ - natsStatus s = NATS_OK; - natsLibDlvWorkers *workers = &(gLib.dlvWorkers); - natsMsgDlvWorker *worker = NULL; - - natsMutex_Lock(workers->lock); - - if (workers->maxSize == 0) - { - natsMutex_Unlock(workers->lock); - return nats_setError(NATS_FAILED_TO_INITIALIZE, "%s", "Message delivery thread pool size is 0!"); - } - - worker = workers->workers[workers->idx]; - if (worker == NULL) - { - worker = NATS_CALLOC(1, sizeof(natsMsgDlvWorker)); - if (worker == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - if (s == NATS_OK) - s = natsMutex_Create(&worker->lock); - if (s == NATS_OK) - s = natsCondition_Create(&worker->cond); - if (s == NATS_OK) - { - natsLib_Retain(); - s = natsThread_Create(&worker->thread, _deliverMsgs, (void*) worker); - if (s != NATS_OK) - natsLib_Release(); - } - if (s == NATS_OK) - { - workers->workers[workers->idx] = worker; - workers->size++; - } - else - { - _freeDlvWorker(worker); - } - } - if (s == NATS_OK) - { - sub->libDlvWorker = worker; - if (++(workers->idx) == workers->maxSize) - workers->idx = 0; - } - - natsMutex_Unlock(workers->lock); - - return NATS_UPDATE_ERR_STACK(s); -} - -bool -natsLib_isLibHandlingMsgDeliveryByDefault(void) -{ - return gLib.libHandlingMsgDeliveryByDefault; -} - int64_t -natsLib_defaultWriteDeadline(void) -{ - return gLib.libDefaultWriteDeadline; -} - -void -natsLib_getMsgDeliveryPoolInfo(int *maxSize, int *size, int *idx, natsMsgDlvWorker ***workersArray) -{ - natsLibDlvWorkers *workers = &gLib.dlvWorkers; - - natsMutex_Lock(workers->lock); - *maxSize = workers->maxSize; - *size = workers->size; - *idx = workers->idx; - *workersArray = workers->workers; - natsMutex_Unlock(workers->lock); -} - -natsStatus -natsLib_startServiceCallbacks(microService *m) -{ - natsStatus s; - - natsMutex_Lock(gLib.service_callback_mu); - s = natsHash_Set(gLib.all_services_to_callback, (int64_t)m, (void *)m, NULL); - natsMutex_Unlock(gLib.service_callback_mu); - - return NATS_UPDATE_ERR_STACK(s); -} - -void -natsLib_stopServiceCallbacks(microService *m) -{ - if (m == NULL) - return; - - natsMutex_Lock(gLib.service_callback_mu); - natsHash_Remove(gLib.all_services_to_callback, (int64_t)m); - natsMutex_Unlock(gLib.service_callback_mu); -} - -natsMutex* -natsLib_getServiceCallbackMutex(void) -{ - return gLib.service_callback_mu; -} - -natsHash* -natsLib_getAllServicesToCallback(void) +nats_setTargetTime(int64_t timeout) { - return gLib.all_services_to_callback; + int64_t target = nats_now() + timeout; + if (target < 0) + target = 0x7FFFFFFFFFFFFFFF; + return target; } diff --git a/src/nats.h b/src/nats.h deleted file mode 100644 index dc331e08f..000000000 --- a/src/nats.h +++ /dev/null @@ -1,8614 +0,0 @@ -// Copyright 2015-2023 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. - -#ifndef NATS_H_ -#define NATS_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include -#include -#include - -#include "status.h" -#include "version.h" - -/** \def NATS_EXTERN - * \brief Needed for shared library. - * - * Based on the platform this is compiled on, it will resolve to - * the appropriate instruction so that objects are properly exported - * when building the shared library. - */ -#if defined(_WIN32) - #include - #if defined(nats_EXPORTS) - #define NATS_EXTERN __declspec(dllexport) - #elif defined(nats_IMPORTS) - #define NATS_EXTERN __declspec(dllimport) - #else - #define NATS_EXTERN - #endif - - typedef SOCKET natsSock; -#else - #define NATS_EXTERN - typedef int natsSock; -#endif - -/*! \mainpage %NATS C client. - * - * \section intro_sec Introduction - * - * The %NATS C Client is part of %NATS, an open-source cloud-native - * messaging system, and is supported by [Synadia Communications Inc.](http://www.synadia.com). - * This client, written in C, follows the go client closely, but - * diverges in some places. - * - * \section install_sec Installation - * - * Instructions to build and install the %NATS C Client can be - * found at the [NATS C Client GitHub page](https://github.com/nats-io/nats.c) - * - * \section faq_sec Frequently Asked Questions - * - * Some of the frequently asked questions can be found [here](https://github.com/nats-io/nats.c#faq) - * - * \section other_doc_section Other Documentation - * - * This documentation focuses on the %NATS C Client API; for additional - * information, refer to the following: - * - * - [General Documentation for nats.io](http://nats.io/documentation) - * - [NATS C Client found on GitHub](https://github.com/nats-io/nats.c) - * - [The NATS Server (nats-server) found on GitHub](https://github.com/nats-io/nats-server) - */ - -/** \brief The default `NATS Server` URL. - * - * This is the default URL a `NATS Server`, running with default listen - * port, can be reached at. - */ -#define NATS_DEFAULT_URL "nats://localhost:4222" - -/** \brief Message header for JetStream messages representing the message payload size - * - * When creating a JetStream consumer, if the `HeadersOnly` boolean is specified, - * the subscription will receive messages with headers only (no message payload), - * and a header of this name containing the size of the message payload that was - * omitted. - * - * @see jsConsumerConfig - */ - #define JSMsgSize "Nats-Msg-Size" - -/** \brief Message header for JetStream message for rollup - * - * If message is sent to a stream's subject with this header set, and the stream - * is configured with `AllowRollup` option, then the server will insert this - * message and delete all previous messages in the stream. - * - * If the header is set to #JSMsgRollupSubject, then only messages on the - * specific subject this message is sent to are deleted. - * - * If the header is set to #JSMsgRollupAll, then all messages on all subjects - * are deleted. - */ - #define JSMsgRollup "Nats-Rollup" - -/** \brief Message header value causing rollup per subject - * - * This is a possible value for the #JSMsgRollup header indicating that only - * messages for the subject the rollup message is sent will be removed. - * - * @see JSMsgRollup - */ - #define JSMsgRollupSubject "sub" - - /** \brief Message header value causing rollup for all subjects - * - * This is a possible value for the #JSMsgRollup header indicating that all - * messages for all subjects will be removed. - * - * @see JSMsgRollup - */ - #define JSMsgRollupAll "all" - - // Headers for republished messages and direct get. - #define JSStream "Nats-Stream" - #define JSSequence "Nats-Sequence" - #define JSLastSequence "Nats-Last-Sequence" - #define JSTimeStamp "Nats-Time-Stamp" - #define JSSubject "Nats-Subject" - -// -// Types. -// -/** \defgroup typesGroup Types - * - * NATS Types. - * @{ - */ - -/** \brief A connection to a `NATS Server`. - * - * A #natsConnection represents a bare connection to a `NATS Server`. It will - * send and receive byte array payloads. - */ -typedef struct __natsConnection natsConnection; - -/** \brief Statistics of a #natsConnection - * - * Tracks various statistics received and sent on a connection, - * including counts for messages and bytes. - */ -typedef struct __natsStatistics natsStatistics; - -/** \brief Interest on a given subject. - * - * A #natsSubscription represents interest in a given subject. - */ -typedef struct __natsSubscription natsSubscription; - -/** \brief A structure holding a subject, optional reply and payload. - * - * #natsMsg is a structure used by Subscribers and - * #natsConnection_PublishMsg(). - */ -typedef struct __natsMsg natsMsg; - -/** \brief Way to configure a #natsConnection. - * - * Options can be used to create a customized #natsConnection. - */ -typedef struct __natsOptions natsOptions; - -/** \brief Unique subject often used for point-to-point communication. - * - * This can be used as the reply for a request. Inboxes are meant to be - * unique so that replies can be sent to a specific subscriber. That - * being said, inboxes can be shared across multiple subscribers if - * desired. - */ -typedef char natsInbox; - -/** \brief A list of NATS messages. - * - * Used by some APIs which return a list of #natsMsg objects. - * - * Those APIs will not create the object, but instead initialize - * the object to which a pointer to that object will be passed to it. - * Typically, the user will define the object on the stack and - * pass a pointer to this object to APIs that require a pointer - * to a #natsMsgList object. - * - * Similarly, calling #natsMsgList_Destroy will call #natsMsg_Destroy - * on any message still in the list, free the array containing pointers - * to the messages, but not free the #natsMsgList object itself. - * - * \note If the user wants to keep some of the messages from the - * list, the pointers of those messages in the `Msgs` array should - * be set to `NULL`. The value `Count` MUST not be changed. The - * function #natsMsgList_Destroy will iterate through all - * pointers in the list and only destroy the ones that have not - * been set to `NULL`. - * - * @see natsMsgList_Destroy - */ -typedef struct natsMsgList -{ - natsMsg **Msgs; - int Count; - -} natsMsgList; - -/** \brief A type to represent user-provided metadata, a list of k=v pairs. - * - * Used in JetStream, microservice configuration. - */ - -typedef struct natsMetadata -{ - // User-provided metadata for the stream, encoded as an array of {"key", "value",...} - const char **List; - // Number of key/value pairs in Metadata, 1/2 of the length of the array. - int Count; -} natsMetadata; - -/** - * The JetStream context. Use for JetStream assets management and communication. - * - * \warning A context MUST not be destroyed concurrently with #jsCtx API calls - * (for instance #js_Publish or #js_PublishAsync, etc...). However, it - * is safe to destroy the context while a #jsPubAckErrHandler callback is - * running or while inside #js_PublishAsyncComplete. - */ -typedef struct __jsCtx jsCtx; - -/** - * JetStream publish options. - * - * These are options that you can provide to JetStream publish APIs. - * - * The common usage will be to initialize a structure on the stack by - * calling #jsPubOptions_Init. Note that strings are owned by - * the application and need to be valid for the duration of the API - * call this object is passed to. - * - * \note It is the user responsibility to free the strings if they - * have been allocated. - * - * @see jsPubOptions_Init - */ -typedef struct jsPubOptions -{ - int64_t MaxWait; ///< Amount of time (in milliseconds) to wait for a publish response, default will the context's Wait value. - const char *MsgId; ///< Message ID used for de-duplication. - const char *ExpectStream; ///< Expected stream to respond from the publish call. - const char *ExpectLastMsgId; ///< Expected last message ID in the stream. - uint64_t ExpectLastSeq; ///< Expected last message sequence in the stream. - uint64_t ExpectLastSubjectSeq; ///< Expected last message sequence for the subject in the stream. - bool ExpectNoMessage; ///< Expected no message (that is, sequence == 0) for the subject in the stream. - -} jsPubOptions; - -/** - * Determines how messages in a set are retained. - */ -typedef enum -{ - js_LimitsPolicy = 0, ///< Specifies that messages are retained until any given limit is reached, which could be one of MaxMsgs, MaxBytes, or MaxAge. This is the default. - js_InterestPolicy, ///< Specifies that when all known observables have acknowledged a message it can be removed. - js_WorkQueuePolicy, ///< Specifies that when the first worker or subscriber acknowledges the message it can be removed. - -} jsRetentionPolicy; - -/** - * Determines how to proceed when limits of messages or bytes are reached. - */ -typedef enum -{ - js_DiscardOld = 0, ///< Will remove older messages to return to the limits. This is the default. - js_DiscardNew, ///< Will fail to store new messages. - -} jsDiscardPolicy; - -/** - * Determines how messages are stored for retention. - */ -typedef enum -{ - js_FileStorage = 0, ///< Specifies on disk storage. It's the default. - js_MemoryStorage, ///< Specifies in memory only. - -} jsStorageType; - -/** - * Determines how messages are compressed when stored for retention. - */ -typedef enum -{ - js_StorageCompressionNone = 0, ///< Specifies no compression. It's the default. - js_StorageCompressionS2, ///< Specifies S2. -} jsStorageCompression; - -/** - * Determines how the consumer should select the first message to deliver. - */ -typedef enum -{ - js_DeliverAll = 0, ///< Starts from the very beginning of a stream. This is the default. - js_DeliverLast, ///< Starts with the last sequence received. - js_DeliverNew, ///< Starts with messages sent after the consumer is created. - js_DeliverByStartSequence, ///< Starts from a given sequence. - js_DeliverByStartTime, ///< Starts from a given UTC time (number of nanoseconds since epoch) - js_DeliverLastPerSubject, ///< Starts with the last message for all subjects received. - -} jsDeliverPolicy; - -/** - * Determines how the consumer should acknowledge delivered messages. - */ -typedef enum -{ - js_AckExplicit = 0, ///< Requires ack or nack for all messages. - js_AckNone, ///< Requires no acks for delivered messages. - js_AckAll, ///< When acking a sequence number, this implicitly acks all sequences below this one as well. - - -} jsAckPolicy; - -/** - * Determines how the consumer should replay messages it already has queued in the stream. - */ -typedef enum -{ - js_ReplayInstant = 0, ///< Replays messages as fast as possible. - js_ReplayOriginal, ///< Maintains the same timing as the messages were received. - -} jsReplayPolicy; - - -/** - * Used to guide placement of streams in clustered JetStream. - * - * Initialize the object with #jsPlacement_Init. - * - * \note The strings are applications owned and will not be freed by the library. - * - * See #jsStreamConfig for information on how to configure a stream. - * - * @see jsPlacement_Init - */ -typedef struct jsPlacement -{ - const char *Cluster; - const char **Tags; - int TagsLen; - -} jsPlacement; - -/** - * Allows you to qualify access to a stream source in another account. - * - * Initialize the object with #jsExternalStream_Init. - * - * \note The strings are applications owned and will not be freed by the library. - * - * See #jsStreamConfig for information on how to configure a stream. - */ -typedef struct jsExternalStream -{ - const char *APIPrefix; - const char *DeliverPrefix; - -} jsExternalStream; - -/** - * Dictates how streams can source from other streams. - * - * Initialize the object with #jsStreamSource_Init. - * - * \note The strings are applications owned and will not be freed by the library. - * - * \note The `OptStartTime` needs to be expressed as the number of nanoseconds - * passed since 00:00:00 UTC Thursday, 1 January 1970. - * - * See #jsStreamConfig for information on how to configure a stream. - */ -typedef struct jsStreamSource -{ - const char *Name; - uint64_t OptStartSeq; - int64_t OptStartTime; ///< UTC time expressed as number of nanoseconds since epoch. - const char *FilterSubject; - jsExternalStream *External; - // Domain and External are mutually exclusive. - // If Domain is set, an External value will be created with - // the APIPrefix constructed based on the Domain value. - const char *Domain; - -} jsStreamSource; - -/** - * Allows a source subject to be mapped to a destination subject for republishing. - */ -typedef struct jsRePublish -{ - const char *Source; - const char *Destination; - bool HeadersOnly; - -} jsRePublish; - -/** - * SubjectTransformConfig is for applying a subject transform (to matching - * messages) before doing anything else when a new message is received - */ -typedef struct jsSubjectTransformConfig -{ - const char *Source; - const char *Destination; -} jsSubjectTransformConfig; - -/** - * SubjectTransformConfig is for applying a subject transform (to matching - * messages) before doing anything else when a new message is received - */ -typedef struct jsStreamConsumerLimits -{ - int64_t InactiveThreshold; - int MaxAckPending; -} jsStreamConsumerLimits; - -/** - * Configuration of a JetStream stream. - * - * There are sensible defaults for most. If no subjects are - * given the name will be used as the only subject. - * - * In order to add/update a stream, a configuration needs to be set. - * The typical usage would be to initialize all required objects on the stack - * and configure them, then pass the pointer to the configuration to - * #js_AddStream or #js_UpdateStream. - * - * \note The strings are applications owned and will not be freed by the library. - * - * \note NATS server 2.10 added user-provided Metadata, storage Compression - * type, FirstSeq to specify the starting sequence number, and SubjectTransform. - * - * @see jsStreamConfig_Init - * - * \code{.unparsed} - * jsStreamConfig sc; - * jsPlacement p; - * jsStreamSource m; - * jsExternalStream esm; - * jsStreamSource s1; - * jsStreamSource s2; - * jsExternalStream esmS2; - * const char *subjects[] = {"foo", "bar"}; - * const char *tags[] = {"tag1", "tag2"}; - * jsStreamSource *sources[] = {&s1, &s2}; - * jsRePublish rp; - * - * jsStreamConfig_Init(&sc); - * - * jsPlacement_Init(&p); - * p.Cluster = "MyCluster"; - * p.Tags = tags; - * p.TagsLen = 2; - * - * jsStreamSource_Init(&m); - * m.Name = "AStream"; - * m.OptStartSeq = 100; - * m.FilterSubject = "foo"; - * jsExternalStream_Init(&esm); - * esm.APIPrefix = "mirror.prefix."; - * esm.DeliverPrefix = "deliver.prefix."; - * m.External = &esm; - * - * jsStreamSource_Init(&s1); - * s1.Name = "StreamOne"; - * s1.OptStartSeq = 10; - * s1.FilterSubject = "stream.one"; - * - * jsStreamSource_Init(&s2); - * s2.Name = "StreamTwo"; - * s2.FilterSubject = "stream.two"; - * jsExternalStream_Init(&esmS2); - * esmS2.APIPrefix = "mirror.prefix."; - * esmS2.DeliverPrefix = "deliver.prefix."; - * s2.External = &esmS2; - * - * sc.Name = "MyStream"; - * sc.Subjects = subjects; - * sc.SubjectsLen = 2; - * sc.Retention = js_InterestPolicy; - * sc.Replicas = 3; - * sc.Placement = &p; - * sc.Mirror = &m; - * sc.Sources = sources; - * sc.SourcesLen = 2; - * - * // For RePublish subject: - * jsRePublish_Init(&rp); - * rp.Source = ">"; - * rp.Destination = "RP.>"; - * sc.RePublish = &rp; - * - * s = js_AddStream(&si, js, &sc, NULL, &jerr); - * \endcode - */ -typedef struct jsStreamConfig { - const char *Name; - const char *Description; - const char **Subjects; - int SubjectsLen; - jsRetentionPolicy Retention; - int64_t MaxConsumers; - int64_t MaxMsgs; - int64_t MaxBytes; - int64_t MaxAge; - int64_t MaxMsgsPerSubject; - int32_t MaxMsgSize; - jsDiscardPolicy Discard; - jsStorageType Storage; - int64_t Replicas; - bool NoAck; - const char *Template; - int64_t Duplicates; - jsPlacement *Placement; - jsStreamSource *Mirror; - jsStreamSource **Sources; - int SourcesLen; - bool Sealed; ///< Seal a stream so no messages can get our or in. - bool DenyDelete; ///< Restrict the ability to delete messages. - bool DenyPurge; ///< Restrict the ability to purge messages. - /** - * Allows messages to be placed into the system and purge - * all older messages using a special message header. - */ - bool AllowRollup; - - // Allow republish of the message after being sequenced and stored. - jsRePublish *RePublish; - - // Allow higher performance, direct access to get individual messages. E.g. KeyValue - bool AllowDirect; - // Allow higher performance and unified direct access for mirrors as well. - bool MirrorDirect; - - // Allow KV like semantics to also discard new on a per subject basis - bool DiscardNewPerSubject; - - /** - * @brief Configuration options introduced in 2.10 - * - * - Metadata is a user-provided array of key/value pairs, encoded as a - * string array [n1, v1, n2, v2, ...] representing key/value pairs - * {n1:v1, n2:v2, ...}. - * - * - Compression: js_StorageCompressionNone (default) or - * js_StorageCompressionS2 - * - * - FirstSeq: the starting sequence number for the stream. - * - * - SubjectTransformConfig is for applying a subject transform (to - * matching messages) before doing anything else when a new message is - * received - * - * - ConsumerLimits is for setting the limits on certain options on all - * consumers of the stream. - */ - - natsMetadata Metadata; - jsStorageCompression Compression; - uint64_t FirstSeq; - jsSubjectTransformConfig SubjectTransform; - jsStreamConsumerLimits ConsumerLimits; -} jsStreamConfig; - -/** - * Information about messages that have been lost - */ -typedef struct jsLostStreamData -{ - uint64_t *Msgs; - int MsgsLen; - uint64_t Bytes; - -} jsLostStreamData; - -/** - * This indicate that the given `Subject` in a stream contains `Msgs` messages. - * - * @see jsStreamStateSubjects - */ -typedef struct jsStreamStateSubject -{ - const char *Subject; - uint64_t Msgs; - -} jsStreamStateSubject; - -/** - * List of subjects optionally returned in the stream information request. - * - * This structure indicates the number of elements in the list, that is, - * the list contains `Count` #jsStreamStateSubject elements. - * - * To get this list in #jsStreamState, you have to ask for it through #jsOptions. - * - * \code{.unparsed} - * jsStreamInfo *si = NULL; - * jsOptions o; - * - * jsOptions_Init(&o); - * o.Stream.Info.SubjectsFilter = "foo.>"; - * s = js_GetStreamInfo(&si, js, "MY_STREAM", &o, &jerr); - * - * // handle errors and assume si->State.Subjects is not NULL - * - * for (i=0; iState.Subjects->Count; i++) - * { - * jsStreamStateSubject *subj = &(si->State.Subjects->List[i]); - * printf("Subject=%s Messages count=%d\n", subj->Subject, (int) subj->Msgs); - * } - * \endcode - * - * @see jsStreamStateSubject - * @see js_GetStreamInfo - * @see jsOptions.Stream.Info.SubjectsFilter - */ -typedef struct jsStreamStateSubjects -{ - jsStreamStateSubject *List; - int Count; - -} jsStreamStateSubjects; - -/** - * Information about the given stream - * - * \note `FirstTime` and `LastTime` are message timestamps expressed as the number - * of nanoseconds passed since 00:00:00 UTC Thursday, 1 January 1970. - */ -typedef struct jsStreamState -{ - uint64_t Msgs; - uint64_t Bytes; - uint64_t FirstSeq; - int64_t FirstTime; ///< UTC time expressed as number of nanoseconds since epoch. - uint64_t LastSeq; - int64_t LastTime; ///< UTC time expressed as number of nanoseconds since epoch. - int64_t NumSubjects; - jsStreamStateSubjects *Subjects; - uint64_t NumDeleted; - uint64_t *Deleted; - int DeletedLen; - jsLostStreamData *Lost; - int64_t Consumers; - -} jsStreamState; - -/** - * Information about all the peers in the cluster that - * are supporting the stream or consumer. - */ -typedef struct jsPeerInfo -{ - char *Name; - bool Current; - bool Offline; - int64_t Active; - uint64_t Lag; - -} jsPeerInfo; - -/** - * Information about the underlying set of servers - * that make up the stream or consumer. - */ -typedef struct jsClusterInfo -{ - char *Name; - char *Leader; - jsPeerInfo **Replicas; - int ReplicasLen; - -} jsClusterInfo; - -/** - * Information about an upstream stream source. - */ -typedef struct jsStreamSourceInfo -{ - char *Name; - jsExternalStream *External; - uint64_t Lag; - int64_t Active; - const char * FilterSubject; - jsSubjectTransformConfig *SubjectTransforms; - int SubjectTransformsLen; - -} jsStreamSourceInfo; - -/** - * Information about an alternate stream represented by a mirror. - */ -typedef struct jsStreamAlternate -{ - const char *Name; - const char *Domain; - const char *Cluster; - -} jsStreamAlternate; - -/** - * Configuration and current state for this stream. - * - * \note `Created` is the timestamp when the stream was created, expressed as - * the number of nanoseconds passed since 00:00:00 UTC Thursday, 1 January 1970. - */ -typedef struct jsStreamInfo -{ - jsStreamConfig *Config; - int64_t Created; ///< UTC time expressed as number of nanoseconds since epoch. - jsStreamState State; - jsClusterInfo *Cluster; - jsStreamSourceInfo *Mirror; - jsStreamSourceInfo **Sources; - int SourcesLen; - jsStreamAlternate **Alternates; - int AlternatesLen; - -} jsStreamInfo; - -/** - * List of stream information objects returned by #js_Streams - * - * \note Once done, the list should be destroyed calling #jsStreamInfoList_Destroy - * - * @see jsStreamInfoList_Destroy - */ -typedef struct jsStreamInfoList -{ - jsStreamInfo **List; - int Count; - -} jsStreamInfoList; - -/** - * List of stream names returned by #js_StreamNames - * - * \note Once done, the list should be destroyed calling #jsStreamNamesList_Destroy - * - * @see jsStreamNamesList_Destroy - */ -typedef struct jsStreamNamesList -{ - char **List; - int Count; - -} jsStreamNamesList; - -/** - * Configuration of a JetStream consumer. - * - * In order to add a consumer, a configuration needs to be set. - * The typical usage would be to initialize all required objects on the stack - * and configure them, then pass the pointer to the configuration to - * #js_AddConsumer. - * - * \note `OptStartTime` needs to be expressed as the number of nanoseconds - * passed since 00:00:00 UTC Thursday, 1 January 1970. - * - * \note The strings are applications owned and will not be freed by the library. - * - * \note `SampleFrequency` is a sampling value, represented as a string such as "50" - * for 50%, that causes the server to produce advisories for consumer ack metrics. - * - * \note `Durable` cannot contain the character ".". - * - * \note `HeadersOnly` means that the subscription will not receive any message payload, - * instead, it will receive only messages headers (if present) with the addition of - * the header #JSMsgSize ("Nats-Msg-Size"), whose value is the payload size. - * - * \note NATS server 2.10 added FilterSubjects, an array of multiple filter - * subjects. It is mutually exclusive with the previously available single - * FilterSubject. - * - * \note NATS server 2.10 added consumer Metadata which contains user-provided - * string name/value pairs. - * - * @see jsConsumerConfig_Init - * - * \code{.unparsed} - * jsConsumerInfo *ci = NULL; - * jsConsumerConfig cc; - * - * jsConsumerConfig_Init(&cc); - * cc.Durable = "MY_DURABLE"; - * cc.DeliverSubject = "foo"; - * cc.DeliverPolicy = js_DeliverNew; - * - * s = js_AddConsumer(&ci, js, &cc, NULL, &jerr); - * \endcode - */ -typedef struct jsConsumerConfig -{ - const char *Name; - const char *Durable; - const char *Description; - jsDeliverPolicy DeliverPolicy; - uint64_t OptStartSeq; - int64_t OptStartTime; ///< UTC time expressed as number of nanoseconds since epoch. - jsAckPolicy AckPolicy; - int64_t AckWait; - int64_t MaxDeliver; - int64_t *BackOff; ///< Redelivery durations expressed in nanoseconds - int BackOffLen; - const char *FilterSubject; - jsReplayPolicy ReplayPolicy; - uint64_t RateLimit; - const char *SampleFrequency; - int64_t MaxWaiting; - int64_t MaxAckPending; - bool FlowControl; - int64_t Heartbeat; ///< Heartbeat interval expressed in number of nanoseconds. - bool HeadersOnly; - - // Pull based options. - int64_t MaxRequestBatch; ///< Maximum Pull Consumer request batch size. - int64_t MaxRequestExpires; ///< Maximum Pull Consumer request expiration, expressed in number of nanoseconds. - int64_t MaxRequestMaxBytes; ///< Maximum Pull Consumer request maximum bytes. - - // Push based options. - const char *DeliverSubject; - const char *DeliverGroup; - - // Ephemeral inactivity threshold. - int64_t InactiveThreshold; ///< How long the server keeps an ephemeral after detecting loss of interest, expressed in number of nanoseconds. - - // Generally inherited by parent stream and other markers, now can be configured directly. - int64_t Replicas; - // Force memory storage. - bool MemoryStorage; - - // Configuration options introduced in 2.10 - - const char **FilterSubjects; ///< Multiple filter subjects - int FilterSubjectsLen; - natsMetadata Metadata; ///< User-provided metadata for the consumer, encoded as an array of {"key", "value",...} - - // Configuration options introduced in 2.11 - - int64_t PauseUntil; ///< Suspends the consumer until this deadline, represented as number of nanoseconds since epoch. -} jsConsumerConfig; - -/** - * This represents a consumer sequence mismatch between the server and client - * views. - * - * This can help applications find out if messages have been missed. Without - * this and during a disconnect, it would be possible that a subscription - * is not aware that it missed messages from the server. When acknowledgment - * mode is other than #js_AckNone, messages would ultimately be redelivered, - * but for #js_AckNone, they would not. But even with an acknowledgment mode - * this may help finding sooner that something went wrong and let the application - * decide if it wants to recreate the subscription starting at a given - * sequence. - * - * The gap of missing messages could be calculated as `ConsumerServer-ConsumerClient`. - * - * @see natsSubscription_GetSequenceMismatch - */ -typedef struct jsConsumerSequenceMismatch -{ - uint64_t Stream; ///< This is the stream sequence that the application should resume from. - uint64_t ConsumerClient; ///< This is the consumer sequence that was last received by the library. - uint64_t ConsumerServer; ///< This is the consumer sequence last sent by the server. - -} jsConsumerSequenceMismatch; - -/** - * JetStream subscribe options. - * - * These are options that you can provide to JetStream subscribe APIs. - * - * The common usage will be to initialize a structure on the stack by - * calling #jsSubOptions_Init. Note that strings are owned by - * the application and need to be valid for the duration of the API - * call this object is passed to. - * - * \note It is the user responsibility to free the strings if they - * have been allocated. - * - * @see jsSubOptions_Init - */ -typedef struct jsSubOptions -{ - /** - * If specified, the library will only bind to this stream, - * otherwise, the library communicates with the server to - * get the stream name that has the matching subject given - * to the #js_Subscribe family calls. - */ - const char *Stream; ///< If specified, the consumer will be bound to this stream name. - /** - * If specified, the #js_Subscribe family calls will only - * attempt to create a subscription for this matching consumer. - * - * That is, the consumer should exist prior to the call, - * either created by the application calling #js_AddConsumer - * or it should have been created with some other tools - * such as the NATS cli. - */ - const char *Consumer; ///< If specified, the subscription will be bound to an existing consumer from the `Stream` without attempting to create. - /** - * If specified, the low level NATS subscription will be a - * queue subscription, which means that the load on the - * delivery subject will be balanced across multiple members - * of the same queue group. - * - * This makes sense only if the delivery subject in the - * `Config` field of #jsSubOptions is the same for the - * members of the same group. - * - * When no `Durable` name is specified in the `Config` block, then the - * queue name will be used as the consumer's durable name. In this case, - * the queue name cannot contain the character ".". - */ - const char *Queue; ///< Queue name for queue subscriptions. - /** - * This has meaning only for asynchronous subscriptions, - * and only if the consumer's acknowledgment mode is - * other than #js_AckNone. - * - * For asynchronous subscriptions, the default behavior - * is for the library to acknowledge the message once - * the user callback returns. - * - * This option allows you to take control of when the - * message should be acknowledged. - */ - bool ManualAck; ///< If true, the user will have to acknowledge the messages. - /** - * This allows the user to fully configure the JetStream - * consumer. - */ - jsConsumerConfig Config; ///< Consumer configuration. - /** - * This will create a fifo ephemeral consumer for in order delivery of - * messages. There are no redeliveries and no acks. - * Flow control and heartbeats are required and set by default, but - * the heartbeats value can be overridden in the consumer configuration. - */ - bool Ordered; ///< If true, this will be an ordered consumer. - -} jsSubOptions; - -/** - * Includes the consumer and stream sequence info from a JetStream consumer. - */ -typedef struct jsSequencePair -{ - uint64_t Consumer; - uint64_t Stream; - -} jsSequencePair; - -/** - * Has both the consumer and the stream sequence and last activity. - */ -typedef struct jsSequenceInfo -{ - uint64_t Consumer; - uint64_t Stream; - int64_t Last; ///< UTC time expressed as number of nanoseconds since epoch. - -} jsSequenceInfo; - -/** - * Configuration and current state for this consumer. - * - * \note `Created` is the timestamp when the consumer was created, expressed as the number - * of nanoseconds passed since 00:00:00 UTC Thursday, 1 January 1970. - */ -typedef struct jsConsumerInfo -{ - char *Stream; - char *Name; - int64_t Created; ///< UTC time expressed as number of nanoseconds since epoch. - jsConsumerConfig *Config; - jsSequenceInfo Delivered; - jsSequenceInfo AckFloor; - int64_t NumAckPending; - int64_t NumRedelivered; - int64_t NumWaiting; - uint64_t NumPending; - jsClusterInfo *Cluster; - bool PushBound; - bool Paused; - int64_t PauseRemaining; ///< Remaining time in nanoseconds. -} jsConsumerInfo; - -/** - * List of consumers information objects returned by #js_Consumers - * - * \note Once done, the list should be destroyed calling #jsConsumerInfoList_Destroy - * - * @see jsStreamInfoList_Destroy - */ -typedef struct jsConsumerInfoList -{ - jsConsumerInfo **List; - int Count; - -} jsConsumerInfoList; - -/** - * List of consumer names returned by #js_ConsumerNames - * - * \note Once done, the list should be destroyed calling #jsConsumerNamesList_Destroy - * - * @see jsConsumerNamesList_Destroy - */ -typedef struct jsConsumerNamesList -{ - char **List; - int Count; - -} jsConsumerNamesList; - -/** - * Request to pause the consumer, used to call js_PauseConsumer. - * - * @see js_PauseConsumer - */ -typedef struct jsConsumerPauseResponse -{ - bool Paused; - int64_t PauseUntil; ///< UTC time expressed as number of nanoseconds since epoch. - int64_t PauseRemaining; ///< Remaining time in nanoseconds. -} jsConsumerPauseResponse; - -/** - * Reports on API calls to JetStream for this account. - */ -typedef struct jsAPIStats -{ - uint64_t Total; - uint64_t Errors; - -} jsAPIStats; - -/** - * Includes the JetStream limits of the current account. - */ -typedef struct jsAccountLimits -{ - int64_t MaxMemory; - int64_t MaxStore; - int64_t MaxStreams; - int64_t MaxConsumers; - int64_t MaxAckPending; - int64_t MemoryMaxStreamBytes; - int64_t StoreMaxStreamBytes; - bool MaxBytesRequired; - -} jsAccountLimits; - -typedef struct jsTier -{ - const char *Name; - uint64_t Memory; - uint64_t Store; - int64_t Streams; - int64_t Consumers; - jsAccountLimits Limits; - -} jsTier; - -/** - * Information about the JetStream usage from the current account. - */ -typedef struct jsAccountInfo -{ - uint64_t Memory; - uint64_t Store; - int64_t Streams; - int64_t Consumers; - char *Domain; - jsAPIStats API; - jsAccountLimits Limits; - jsTier **Tiers; - int TiersLen; - -} jsAccountInfo; - -/** - * This represents the JetStream metadata associated with received messages. - * - * @see natsMsg_GetMetaData - * @see jsMsgMetaData_Destroy - * - */ -typedef struct jsMsgMetaData -{ - jsSequencePair Sequence; - uint64_t NumDelivered; - uint64_t NumPending; - int64_t Timestamp; - char *Stream; - char *Consumer; - char *Domain; - -} jsMsgMetaData; - -/** - * Ack received after successfully publishing a message. - */ -typedef struct jsPubAck -{ - char *Stream; - uint64_t Sequence; - char *Domain; - bool Duplicate; - -} jsPubAck; - -/** - * Publish acknowledgment failure that will be passed to the optional - * #jsPubAckErrHandler callback. - */ -typedef struct jsPubAckErr -{ - natsMsg *Msg; - natsStatus Err; - jsErrCode ErrCode; - const char *ErrText; - -} jsPubAckErr; - -#ifndef BUILD_IN_DOXYGEN -// Forward declarations -typedef void (*jsPubAckErrHandler)(jsCtx *js, jsPubAckErr *pae, void *closure); -typedef void (*jsPubAckHandler)(jsCtx *js, natsMsg *msg, jsPubAck *pa, jsPubAckErr *pae, void *closure); -#endif - -/** - * Options for the js_DirectGetMsg() call, which retrieves a message - * from any server (not only the leader) as long as the stream has - * been created with a AllowDirect option. - * - * Note that some options are mutually exclusive but are not checked - * byt the library. The server will reject invalid requests and - * the library will return the error returned from the server. - */ -typedef struct jsDirectGetMsgOptions -{ - uint64_t Sequence; ///< Get the message at this sequence - const char *NextBySubject; ///< Get the next message (based on sequence) for that subject - const char *LastBySubject; ///< Get the last message on that subject - -} jsDirectGetMsgOptions; - -/** - * Options for the natsSubscription_FetchRequest() call, which is - * similar to natsSubscription_Fetch() but gives more control in - * the configuration of the fetch. - */ -typedef struct jsFetchRequest -{ - int64_t Expires; ///< Expiration of the request, expressed in nanoseconds - int Batch; ///< Maximum number of messages to be received (see MaxBytes) - int64_t MaxBytes; ///< Maximum bytes for the request (request complete based on whichever Batch or MaxBytes comes first) - bool NoWait; ///< Will not wait if the request cannot be completed - int64_t Heartbeat; ///< Have server sends heartbeats to help detect communication failures - -} jsFetchRequest; - -/** - * JetStream context options. - * - * Initialize the object with #jsOptions_Init. - */ -typedef struct jsOptions -{ - const char *Prefix; ///< JetStream prefix, default is "$JS.API" - const char *Domain; ///< Domain changes the domain part of JetSteam API prefix. - int64_t Wait; ///< Amount of time (in milliseconds) to wait for various JetStream API requests, default is 5000 ms (5 seconds). - - /** - * Publish Async options - */ - struct jsOptionsPublishAsync - { - int64_t MaxPending; ///< Maximum outstanding asynchronous publishes that can be inflight at one time. - - // If jsPubAckHandler is specified, the callback will be invoked - // for every asynchronous published message, either as a positive - // result, or with the error encountered when publishing that - // message. If this callback is specified, ErrHandler (see below) - // will be ignored. - jsPubAckHandler AckHandler; ///< Callback invoked for each asynchronous published message. - void *AckHandlerClosure; ///< Closure (or user data) passed to #jsPubAckHandler callback. - - // This callback is invoked for messages published asynchronously - // when an error is returned by the server or if the library has - // timed-out waiting for an acknowledgment back from the server - // (if publish uses the jsPubOptions.MaxWait). - jsPubAckErrHandler ErrHandler; ///< Callback invoked when error encountered publishing a given message. - void *ErrHandlerClosure; ///< Closure (or user data) passed to #jsPubAckErrHandler callback. - - int64_t StallWait; ///< Amount of time (in milliseconds) to wait in a PublishAsync call when there is MaxPending inflight messages, default is 200 ms. - - } PublishAsync; - - /** - * Advanced stream options - * - * * `Purge` for advanced purge options. - * * `Info` for advanced information retrieval options. - */ - struct jsOptionsStream - { - /** - * Advanced stream purge options - * - * * `Subject` will filter the purge request to only messages that match the subject, which can have wildcards.
- * * `Sequence` will purge up to but not including this sequence and can be combined with subject filtering.
- * * `Keep` will specify how many messages to keep and can be combined with subject filtering.
- * - * \note `Sequence` and `Keep` are mutually exclusive, so both can not be set at the same time. - */ - struct jsOptionsStreamPurge - { - const char *Subject; ///< This is the subject to match against messages for the purge command. - uint64_t Sequence; ///< Purge up to but not including sequence. - uint64_t Keep; ///< Number of messages to keep. - - } Purge; ///< Optional stream purge options. - - /** - * Advance stream information retrieval options - */ - struct jsOptionsStreamInfo - { - bool DeletedDetails; ///< Get the list of deleted message sequences. - const char *SubjectsFilter; ///< Get the list of subjects in this stream. - - } Info; ///< Optional stream information retrieval options. - - } Stream; ///< Optional stream options. - -} jsOptions; - -/** - * The KeyValue store object. - */ -typedef struct __kvStore kvStore; - -/** - * The KeyValue entry object. - */ -typedef struct __kvEntry kvEntry; - -/** - * The KeyValue status object. - */ -typedef struct __kvStatus kvStatus; - -/** - * The KeyValue watcher object. - */ -typedef struct __kvWatcher kvWatcher; - -/** - * Determines the type of operation of a #kvEntry - */ -typedef enum -{ - kvOp_Unknown = 0, - kvOp_Put, - kvOp_Delete, - kvOp_Purge, - -} kvOperation; - -/** - * KeyValue configuration object. - * - * Initialize the object with #kvConfig_Init. - */ -typedef struct kvConfig -{ - const char *Bucket; - const char *Description; - int32_t MaxValueSize; - uint8_t History; - int64_t TTL; - int64_t MaxBytes; - jsStorageType StorageType; - int Replicas; - jsRePublish *RePublish; - jsStreamSource *Mirror; - jsStreamSource **Sources; - int SourcesLen; - -} kvConfig; - -/** - * KeyValue watcher options object. - * - * Initialize the object with #kvWatchOptions_Init - */ -typedef struct kvWatchOptions -{ - bool IgnoreDeletes; - bool IncludeHistory; - bool MetaOnly; - int64_t Timeout; ///< How long to wait (in milliseconds) for some operations to complete. - -} kvWatchOptions; - -/** - * KeyValue purge options object. - * - * Initialize the object with #kvPurgeOptions_Init - */ -typedef struct kvPurgeOptions -{ - // How long to wait (in milliseconds) for some operations to complete. - int64_t Timeout; - - // When calling kvStore_PurgeDeletes(), all keys that have a delete or - // purge marker as the last entry are gathered and then those keys - // are purged of their content, including the marker. - // Starting with NATS C client v3.3.0, if this option is not specified, - // only the markers older than 30 minutes will be deleted. Use this - // option to set the limit or a negative value to force removal of - // markers regardless of their age. - // The value is expressed as a time in nanoseconds. - int64_t DeleteMarkersOlderThan; - -} kvPurgeOptions; - -/** \brief A list of KeyValue store entries. - * - * Used by some APIs which return a list of #kvEntry objects. - * - * Those APIs will not create the object, but instead initialize - * the object. - * - * Typically, the user will define the object on the stack and - * pass a pointer to this object to APIs that require a pointer - * to a #kvEntryList object. - * - * Similarly, calling #kvEntryList_Destroy will call #kvEntry_Destroy - * on entries in the list, free the array containing pointers - * to the entries, but not free the #kvEntryList object itself. - * - * @see kvEntryList_Destroy - */ -typedef struct kvEntryList -{ - kvEntry **Entries; - int Count; - -} kvEntryList; - -/** \brief A list of KeyValue store keys. - * - * Used by some APIs which return a list of key names. - * - * Those APIs will not create the object, but instead initialize - * the object. - * - * Typically, the user will define the object on the stack and - * pass a pointer to this object to APIs that require a pointer - * to a #kvKeysList object. - * - * Similarly, calling #kvKeysList_Destroy will free key strings - * in the list, free the array containing pointers to the keys, - * but not free the #kvKeysList object itself. - * - * @see kvKeysList_Cleanup - */ -typedef struct kvKeysList -{ - char **Keys; - int Count; - -} kvKeysList; - -#if defined(NATS_HAS_STREAMING) -/** \brief A connection to a `NATS Streaming Server`. - * - * A #stanConnection represents a connection to a `NATS Streaming Server`. - */ -typedef struct __stanConnection stanConnection; - -/** \brief Interest on a given channel. - * - * A #stanSubscription represents interest in a given channel. - */ -typedef struct __stanSubscription stanSubscription; - -/** \brief The Streaming message. - * - * #stanMsg is the object passed to the subscriptions' message callbacks. - */ -typedef struct __stanMsg stanMsg; - -/** \brief Way to configure a #stanConnection. - * - * Options can be used to create a customized #stanConnection. - */ -typedef struct __stanConnOptions stanConnOptions; - -/** \brief Way to configure a #stanSubscription. - * - * Options can be used to create a customized #stanSubscription. - */ -typedef struct __stanSubOptions stanSubOptions; -#endif - -/** @} */ // end of typesGroup - -// -// Callbacks. -// - -/** \defgroup callbacksGroup Callbacks - * - * NATS Callbacks. - * @{ - */ - -/** \brief Callback used to deliver messages to the application. - * - * This is the callback that one provides when creating an asynchronous - * subscription. The library will invoke this callback for each message - * arriving through the subscription's connection. - * - * \warning If this callback is setup for a subject that is used as the reply - * subject to #natsConnection_PublishRequest calls (and its variants), it - * is possible to get an empty message with a header "Status" with value - * "503" that is sent by the server when there were no responders on the - * request's subject. Use #natsMsg_IsNoResponders to know if that is the case. - * - * @see natsConnection_Subscribe() - * @see natsConnection_QueueSubscribe() - * @see natsMsg_IsNoResponders() - */ -typedef void (*natsMsgHandler)( - natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); - -/** \brief Callback used to notify the user of asynchronous connection events. - * - * This callback is used for asynchronous events such as disconnected - * and closed connections. - * - * @see natsOptions_SetClosedCB() - * @see natsOptions_SetDisconnectedCB() - * @see natsOptions_SetReconnectedCB() - * - * \warning Such callback is invoked from a dedicated thread and the state - * of the connection that triggered the event may have changed since - * that event was generated. - */ -typedef void (*natsConnectionHandler)( - natsConnection *nc, void *closure); - -/** \brief Callback used to notify the user of errors encountered while processing - * inbound messages. - * - * This callback is used to process asynchronous errors encountered while processing - * inbound messages, such as #NATS_SLOW_CONSUMER. - */ -typedef void (*natsErrHandler)( - natsConnection *nc, natsSubscription *subscription, natsStatus err, - void *closure); - -/** \brief Attach this connection to the external event loop. - * - * After a connection has (re)connected, this callback is invoked. It should - * perform the necessary work to start polling the given socket for READ events. - * - * @param userData location where the adapter implementation will store the - * object it created and that will later be passed to all other callbacks. If - * `*userData` is not `NULL`, this means that this is a reconnect event. - * @param loop the event loop (as a generic void*) this connection is being - * attached to. - * @param nc the connection being attached to the event loop. - * @param socket the socket to poll for read/write events. - */ -typedef natsStatus (*natsEvLoop_Attach)( - void **userData, - void *loop, - natsConnection *nc, - natsSock socket); - -/** \brief Read event needs to be added or removed. - * - * The `NATS` library will invoke this callback to indicate if the event - * loop should start (`add is `true`) or stop (`add` is `false`) polling - * for read events on the socket. - * - * @param userData the pointer to an user object created in #natsEvLoop_Attach. - * @param add `true` if the event library should start polling, `false` otherwise. - */ -typedef natsStatus (*natsEvLoop_ReadAddRemove)( - void *userData, - bool add); - -/** \brief Write event needs to be added or removed. - * - * The `NATS` library will invoke this callback to indicate if the event - * loop should start (`add is `true`) or stop (`add` is `false`) polling - * for write events on the socket. - * - * @param userData the pointer to an user object created in #natsEvLoop_Attach. - * @param add `true` if the event library should start polling, `false` otherwise. - */ -typedef natsStatus (*natsEvLoop_WriteAddRemove)( - void *userData, - bool add); - -/** \brief Detach from the event loop. - * - * The `NATS` library will invoke this callback to indicate that the connection - * no longer needs to be attached to the event loop. User can cleanup some state. - * - * @param userData the pointer to an user object created in #natsEvLoop_Attach. - */ -typedef natsStatus (*natsEvLoop_Detach)( - void *userData); - -/** \brief Callback used to fetch and return account signed user JWT. - * - * This handler is invoked when connecting and reconnecting. It should - * return the user JWT that will be sent to the server. - * - * The user JWT is returned as a string that is allocated by the user and is - * freed by the library after the handler is invoked. - * - * If the user is unable to return the JWT, a status other than `NATS_OK` should - * be returned (we recommend `NATS_ERR`). A custom error message can be returned - * through `customErrTxt`. The user must allocate the memory for this error - * message and the library will free it after the invocation of the handler. - * - * \warning There may be repeated invocations of this handler for a given connection - * so it is crucial to always return a copy of the user JWT maintained by the - * application, since again, the library will free the memory pointed by `userJWT` - * after each invocation of this handler. - * - * @see natsOptions_SetUserCredentialsCallbacks() - * @see natsOptions_SetUserCredentialsFromFiles() - */ -typedef natsStatus (*natsUserJWTHandler)( - char **userJWT, - char **customErrTxt, - void *closure); - - -/** \brief Callback used to sign a nonce sent by the server. - * - * This handler is invoked when connecting and reconnecting. It should - * sign the given `nonce` and return a raw signature through `signature` and - * specify how many characters the signature has using `signatureLength`. - * - * The memory pointed by `signature` must be allocated by the user and - * will be freed by the library after each invocation of this handler. - * - * If the user is unable to sign, a status other than `NATS_OK` (we recommend - * `NATS_ERR`) should be returned. A custom error message can be returned - * through `customErrTxt`. The user must allocate the memory for this error - * message and the library will free it after the invocation of this handler. - * - * The library will base64 encode this raw signature and send that to the server. - * - * \warning There may be repeated invocations of this handler for a given connection - * so it is crucial to always return a copy of the signature, since again, - * the library will free the memory pointed by `signature` after each invocation - * of this handler. - * - * @see natsOptions_SetUserCredentialsCallbacks() - * @see natsOptions_SetUserCredentialsFromFiles() - * @see natsOptions_SetNKey() - */ -typedef natsStatus (*natsSignatureHandler)( - char **customErrTxt, - unsigned char **signature, - int *signatureLength, - const char *nonce, - void *closure); - -/** \brief Callback used to build a token on connections and reconnections. - * - * This is the function that one provides to build a different token at each reconnect. - * - * @see natsOptions_SetTokenHandler() - * - * \warning Such callback is invoked synchronously from the connection thread. - */ -typedef const char* (*natsTokenHandler)(void *closure); - - -/** \brief Callback used to notify that an object lifecycle is complete. - * - * Currently used for asynchronous #natsSubscription objects. When set, this callback will - * be invoked after the subscription is closed and the message handler has returned. - * - * @see natsSubscription_SetOnCompleteCB() - */ -typedef void (*natsOnCompleteCB)(void *closure); - -/** \brief Callback used to specify how long to wait between reconnects. - * - * This callback is used to get from the user the desired delay the library - * should pause before attempting to reconnect again. Note that this is invoked - * after the library tried the whole list of URLs and failed to reconnect. - * - * \note This callback is invoked from the connection reconnect thread and waits - * for user input. It should not block and instead quickly return the desired - * reconnect delay. - * The state of the connection is disconnected when this callback is invoked. - * Not much can be done with the passed connection, but user can call - * #natsConnection_Close() if desired. This will abort the reconnect attempts - * and close the connection. - * - * @param nc the pointer to the #natsConnection invoking this handler. - * @param attempts the number of times the library tried the whole list of server URLs. - * @param closure an optional pointer to a user defined object that was specified when - * registering the callback. - * @return the number of milliseconds to wait before trying to reconnect. - */ -typedef int64_t (*natsCustomReconnectDelayHandler)(natsConnection *nc, int attempts, void *closure); - -#ifdef BUILD_IN_DOXYGEN -/** \brief Callback used to process asynchronous publish errors from JetStream. - * - * Callback used to process asynchronous publish errors from JetStream #js_PublishAsync - * and #js_PublishMsgAsync calls. The provided #jsPubAckErr object gives the user - * access to the encountered error along with the original message sent to the server - * for possible restransmitting. - * - * \note If the message is resent, the library will not destroy the original - * message and once again take ownership of it. To resend the message, do the - * following so that the library knows not to destroy the message (since the - * call will clear the `Msg` field from the #jsPubAckErr object). - * - * \code{.unparsed} - * void myPAECallback(jsCtx *js, jsPubAckErr *pae, void* closure) - * { - * ... - * // Resend the message - * js_PublishMsgAsync(js, &(pae->Msg), NULL); - * } - * \endcode - * - * \warning The #jsPubAckErr object and its content will be invalid as - * soon as the callback returns. - * - * \warning Unlike a NATS message callback, the user does not have to destroy - * the original NATS message (present in the #jsPubAckErr object), the - * library will do it. - * - * @param js the pointer to the #jsCtx object. - * @param pae the pointer to the #jsPubAckErr object. - * @param closure an optional pointer to a user defined object that was specified when - * registering the callback. - */ -typedef void (*jsPubAckErrHandler)(jsCtx *js, jsPubAckErr *pae, void *closure); - -/** \brief Callback used to process asynchronous publish responses from JetStream. - * - * Callback used to process asynchronous publish responses (positive and negatives) - * from JetStream #js_PublishAsync and #js_PublishMsgAsync calls. The provided - * #jsPubAck or #jsPubAckErr objects give the user access to the successful - * acknowledgment from the server or the encountered error along with the original - * message sent to the server for possible restransmitting. - * - * \warning The user is responsible for destroying the message. If the message - * is resent using the #js_PublishMsgAsync call, the library is taking ownership - * of the message and calling #natsMsg_Destroy will have no effect because - * the pointer will have been set to `NULL`. So it is recommended to always - * call #natsMsg_Destroy at the end of the function. - * - * \code{.unparsed} - * void myAckHandler(jsCtx *js, natsMsg *msg, jsPubAck *pa, jsPubAckErr *pae, void *closure) - * { - * if (pa != NULL) - * { - * // Success case... - * } - * else if (pae != NULL) - * { - * // Error case... - * // If the application wants to resend the message: - * js_PublishMsgAsync(js, &msg, NULL); - * } - * natsMsg_Destroy(msg); - * } - * \endcode - * - * \warning The #jsPubAck and #jsPubAckErr objects and their content will be - * invalid as soon as the callback returns. - * - * @param js the pointer to the #jsCtx object. - * @param msg the pointer to the original published #natsMsg. - * @param pa the pointer to the #jsPubAck object. - * @param pae the pointer to the #jsPubAckErr object. - * @param closure an optional pointer to a user defined object that was specified when - * registering the callback. - */ -typedef void (*jsPubAckHandler)(jsCtx *js, natsMsg *msg, jsPubAck *pa, jsPubAckErr *pae, void *closure); -#endif - -#if defined(NATS_HAS_STREAMING) -/** \brief Callback used to notify of an asynchronous publish result. - * - * This is used for asynchronous publishing to provide status of the acknowledgment. - * The function will be passed the GUID and any error state. No error means the - * message was successfully received by NATS Streaming. - * - * @see stanConnection_PublishAsync() - */ -typedef void (*stanPubAckHandler)(const char *guid, const char *error, void *closure); - -/** \brief Callback used to deliver messages to the application. - * - * This is the callback that one provides when creating an asynchronous - * subscription. The library will invoke this callback for each message - * arriving through the subscription's connection. - * - * @see stanConnection_Subscribe() - * @see stanConnection_QueueSubscribe() - */ -typedef void (*stanMsgHandler)( - stanConnection *sc, stanSubscription *sub, const char *channel, stanMsg *msg, void *closure); - -/** \brief Callback used to notify the user of the permanent loss of the connection. - * - * This callback is used to notify the user that the connection to the Streaming - * server is permanently lost. - * - */ -typedef void (*stanConnectionLostHandler)( - stanConnection *sc, const char* errorTxt, void *closure); -#endif - -/** @} */ // end of callbacksGroup - -// -// Functions. -// -/** \defgroup funcGroup Functions - * - * NATS Functions. - * @{ - */ - -/** \defgroup libraryGroup Library - * - * Library and helper functions. - * @{ - */ - -/** \brief Initializes the library. - * - * This initializes the library. - * - * It is invoked automatically when creating a connection, using a default - * spin count. However, you can call this explicitly before creating the very - * first connection in order for your chosen spin count to take effect. - * - * \warning You must not call #nats_Open and #nats_Close concurrently. - * - * @param lockSpinCount The number of times the library will spin trying to - * lock a mutex object. - */ -NATS_EXTERN natsStatus -nats_Open(int64_t lockSpinCount); - - -/** \brief Returns the Library's version. - * - * Returns the version of the library your application is linked with. - */ -NATS_EXTERN const char* -nats_GetVersion(void); - -/** \brief Returns the Library's version as a number. - * - * The version is returned as an hexadecimal number. For instance, if the - * string version is "1.2.3", the value returned will be: - * - * > 0x010203 - */ -NATS_EXTERN uint32_t -nats_GetVersionNumber(void); - -#ifdef BUILD_IN_DOXYGEN -/** \brief Check that the header is compatible with the library. - * - * The version of the header you used to compile your application may be - * incompatible with the library the application is linked with. - * - * This function will check that the two are compatibles. If they are not, - * a message is printed and the application will exit. - * - * @return `true` if the header and library are compatibles, otherwise the - * application exits. - * - * @see nats_GetVersion - * @see nats_GetVersionNumber - */ -NATS_EXTERN bool nats_CheckCompatibility(void); -#else - -#define nats_CheckCompatibility() nats_CheckCompatibilityImpl(NATS_VERSION_REQUIRED_NUMBER, \ - NATS_VERSION_NUMBER, \ - NATS_VERSION_STRING) - -NATS_EXTERN bool -nats_CheckCompatibilityImpl(uint32_t reqVerNumber, uint32_t verNumber, const char *verString); - -#endif - -/** \brief Gives the current time in milliseconds. - * - * Gives the current time in milliseconds. - */ -NATS_EXTERN int64_t -nats_Now(void); - -/** \brief Gives the current time in nanoseconds. - * - * Gives the current time in nanoseconds. When such granularity is not - * available, the time returned is still expressed in nanoseconds. - */ -NATS_EXTERN int64_t -nats_NowInNanoSeconds(void); - -/** \brief Sleeps for a given number of milliseconds. - * - * Causes the current thread to be suspended for at least the number of - * milliseconds. - * - * @param sleepTime the number of milliseconds. - */ -NATS_EXTERN void -nats_Sleep(int64_t sleepTime); - -/** \brief Returns the calling thread's last known error. - * - * Returns the calling thread's last known error. This can be useful when - * #natsConnection_Connect fails. Since no connection object is returned, - * you would not be able to call #natsConnection_GetLastError. - * - * @param status if not `NULL`, this function will store the last error status - * in there. - * @return the thread local error string. - * - * \warning Do not free the string returned by this function. - */ -NATS_EXTERN const char* -nats_GetLastError(natsStatus *status); - -/** \brief Returns the calling thread's last known error stack. - * - * Copies the calling thread's last known error stack into the provided buffer. - * If the buffer is not big enough, #NATS_INSUFFICIENT_BUFFER is returned. - * - * @param buffer the buffer into the stack is copied. - * @param bufLen the size of the buffer - */ -NATS_EXTERN natsStatus -nats_GetLastErrorStack(char *buffer, size_t bufLen); - -/** \brief Prints the calling thread's last known error stack into the file. - * - * This call prints the calling thread's last known error stack into the file `file`. - * It first prints the error status and the error string, then the stack. - * - * Here is an example for a call: - * - * \code{.unparsed} - * Error: 29 - SSL Error - (conn.c:565): SSL handshake error: sslv3 alert bad certificate - * Stack: (library version: 1.2.3-beta) - * 01 - _makeTLSConn - * 02 - _checkForSecure - * 03 - _processExpectedInfo - * 04 - _processConnInit - * 05 - _connect - * 06 - natsConnection_Connect - * \endcode - * - * @param file the file the stack is printed to. - */ -NATS_EXTERN void -nats_PrintLastErrorStack(FILE *file); - -/** \brief Sets the maximum size of the global message delivery thread pool. - * - * Normally, each asynchronous subscriber that is created has its own - * message delivery thread. The advantage is that it reduces lock - * contentions, therefore improving performance.
- * However, if an application creates many subscribers, this is not scaling - * well since the process would use too many threads. - * - * The library has a thread pool that can perform message delivery. If - * a connection is created with the proper option set - * (#natsOptions_UseGlobalMessageDelivery), then this thread pool - * will be responsible for delivering the messages. The thread pool is - * lazily initialized, that is, no thread is used as long as no subscriber - * (requiring global message delivery) is created. - * - * Each subscriber will be attached to a given worker on the pool so that - * message delivery order is guaranteed. - * - * This call allows you to set the maximum size of the pool. - * - * \note At this time, a pool does not shrink, but the caller will not get - * an error when calling this function with a size smaller than the current - * size. - * - * @see natsOptions_UseGlobalMessageDelivery() - * @see \ref envVariablesGroup - * - * @param max the maximum size of the pool. - */ -NATS_EXTERN natsStatus -nats_SetMessageDeliveryPoolSize(int max); - -/** \brief Release thread-local memory possibly allocated by the library. - * - * This needs to be called on user-created threads where NATS calls are - * performed. This does not need to be called in threads created by - * the library. For instance, do not call this function in the - * message handler that you specify when creating a subscription. - * - * Also, you do not need to call this in an user thread (or the main) - * if you are calling nats_Close() there. - */ -NATS_EXTERN void -nats_ReleaseThreadMemory(void); - -/** \brief Signs a given text using the provided private key. - * - * The key is the encoded string representation of the private key, or seed. - * This is what you get when generating an NKey using NATS tooling. - * - * The input is a string, generally the nonce sent by the server when - * accepting a connection. - * - * This function signs the input and returns the signature through the - * output arguments. This call allocates memory necessary to hold - * the signature. If this is used as part of the signature callback - * passed to #natsOptions_SetNKey(), then the memory will be automatically - * freed by the library after the signature has been inserted in the CONNECT - * protocol. - * If this function is used outside of this context, it is the user responsibility - * to free the allocated memory when no longer needed. - * - * @see natsOptions_SetNKey() - * - * @param encodedSeed the string encoded private key, also known as seed. - * @param input the input to be signed. - * @param signature the memory location of allocated memory containing the signed input. - * @param signatureLength the size of the allocated signature. - */ -NATS_EXTERN natsStatus -nats_Sign(const char *encodedSeed, - const char *input, - unsigned char **signature, - int *signatureLength); - -/** \brief Tear down the library. - * - * Releases memory used by the library. - * - * For this to take effect, all NATS objects that you have created - * must first be destroyed. - * - * This call does not block and it is possible that the library - * is not unloaded right away if there are still internal threads referencing - * it, so calling #nats_Open() right away may fail. If you want to ensure - * that the library is fully unloaded, call #nats_CloseAndWait() instead. - * - * \note There are still a small number of thread local keys and a mutex - * that are not freed until the application exit (in which case a final cleanup - * is executed). - * - * \warning You must not call #nats_Open and #nats_Close concurrently. - * - * @see nats_CloseAndWait() - */ -NATS_EXTERN void -nats_Close(void); - -/** \brief Tear down the library and wait for all resources to be released. - * - * Similar to #nats_Close() except that this call will make sure that all - * references to the library are decremented before returning (up to the - * given timeout). Internal threads (such as subscriptions dispatchers, - * etc..) hold a reference to the library. Only when all references have - * been released that this call will return. It means that you must call - * all the "destroy" calls before calling this function, otherwise it will - * block forever (or up to given timeout). - * - * For instance, this code would "deadlock": - * \code{.unparsed} - * natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - * nats_CloseWait(0); - * natsConnection_Destroy(nc); - * \endcode - * But this would work as expected: - * \code{.unparsed} - * natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - * natsConnection_Destroy(nc); - * nats_CloseWait(0); - * \endcode - * The library and other objects (such as connections, subscriptions, etc) - * use internal threads. After the destroy call, it is possible or even likely - * that some threads are still running, holding references to the library. - * Unlike #nats_Close(), which will simply ensure that the library is ultimately - * releasing memory, the #nats_CloseAndWait() API will ensure that all those internal - * threads have unrolled and that the memory used by the library is released before - * returning. - * - * \note If a timeout is specified, the call may return #NATS_TIMEOUT but the - * library is still being tear down and memory will be released. The error is just - * to notify you that the operation did not complete in the allotted time. Calling - * #nats_Open() in this case (or any implicit opening of the library) may result - * in an error since the library may still be in the process of being closed. - * - * \warning Due to the blocking nature it is illegal to call this from any - * NATS thread (such as message or connection callbacks). If trying to do so, - * a #NATS_ILLEGAL_STATE error will be returned. - * - * @see nats_Close() - * - * @param timeout the maximum time to wait for the library to be closed. If - * negative or 0, waits for as long as needed. - */ -NATS_EXTERN natsStatus -nats_CloseAndWait(int64_t timeout); - -/** @} */ // end of libraryGroup - -/** \defgroup statusGroup Status - * - * Functions related to #natsStatus. - * @{ - */ - -/** \brief Get the text corresponding to a #natsStatus. - * - * Returns the static string corresponding to the given status. - * - * \warning The returned string is a static string, do not attempt to free - * it. - * - * @param s status to get the text representation from. - */ -NATS_EXTERN const char* -natsStatus_GetText(natsStatus s); - -/** @} */ // end of statusGroup - -/** \defgroup statsGroup Statistics - * - * Statistics Functions. - * @{ - */ - -/** \brief Creates a #natsStatistics object. - * - * Creates a statistics object that can be passed to #natsConnection_GetStats(). - * - * \note The object needs to be destroyed when no longer needed. - * - * @see #natsStatistics_Destroy() - * - * @param newStats the location where to store the pointer to the newly created - * #natsStatistics object. - */ -NATS_EXTERN natsStatus -natsStatistics_Create(natsStatistics **newStats); - -/** \brief Extracts the various statistics values. - * - * Gets the counts out of the statistics object. - * - * \note You can pass `NULL` to any of the count your are not interested in - * getting. - * - * @see natsConnection_GetStats() - * - * @param stats the pointer to the #natsStatistics object to get the values from. - * @param inMsgs total number of inbound messages. - * @param inBytes total size (in bytes) of inbound messages. - * @param outMsgs total number of outbound messages. - * @param outBytes total size (in bytes) of outbound messages. - * @param reconnects total number of times the client has reconnected. - */ -NATS_EXTERN natsStatus -natsStatistics_GetCounts(const natsStatistics *stats, - uint64_t *inMsgs, uint64_t *inBytes, - uint64_t *outMsgs, uint64_t *outBytes, - uint64_t *reconnects); - -/** \brief Destroys the #natsStatistics object. - * - * Destroys the statistics object, freeing up memory. - * - * @param stats the pointer to the #natsStatistics object to destroy. - */ -NATS_EXTERN void -natsStatistics_Destroy(natsStatistics *stats); - -/** @} */ // end of statsGroup - -/** \defgroup optsGroup Options - * - * NATS Options. - * @{ - */ - -/** \brief Creates a #natsOptions object. - * - * Creates a #natsOptions object. This object is used when one wants to set - * specific options prior to connecting to the `NATS Server`. - * - * After making the appropriate natsOptions_Set calls, this object is passed - * to the #natsConnection_Connect() call, which will clone this object. After - * natsConnection_Connect() returns, modifications to the options object - * will not affect the connection. - * - * \note The object needs to be destroyed when no longer needed.* - * - * @see natsConnection_Connect() - * @see natsOptions_Destroy() - * - * @param newOpts the location where store the pointer to the newly created - * #natsOptions object. - */ -NATS_EXTERN natsStatus -natsOptions_Create(natsOptions **newOpts); - -/** \brief Sets the URL to connect to. - * - * Sets the URL of the `NATS Server` the client should try to connect to. - * The URL can contain optional user name and password. - * - * Some valid URLS: - * - * - nats://localhost:4222 - * - nats://user\@localhost:4222 - * - nats://user:password\@localhost:4222 - * - * @see natsOptions_SetServers - * @see natsOptions_SetUserInfo - * @see natsOptions_SetToken - * - * @param opts the pointer to the #natsOptions object. - * @param url the string representing the URL the connection should use - * to connect to the server. - * - */ -/* - * The above is for doxygen. The proper syntax for username/password - * is without the '\' character: - * - * nats://localhost:4222 - * nats://user@localhost:4222 - * nats://user:password@localhost:4222 - */ -NATS_EXTERN natsStatus -natsOptions_SetURL(natsOptions *opts, const char *url); - -/** \brief Set the list of servers to try to (re)connect to. - * - * This specifies a list of servers to try to connect (or reconnect) to. - * Note that if you call #natsOptions_SetURL() too, the actual list will contain - * the one from #natsOptions_SetURL() and the ones specified in this call. - * - * @see natsOptions_SetURL - * @see natsOptions_SetUserInfo - * @see natsOptions_SetToken - * - * @param opts the pointer to the #natsOptions object. - * @param servers the array of strings representing the server URLs. - * @param serversCount the size of the array. - */ -NATS_EXTERN natsStatus -natsOptions_SetServers(natsOptions *opts, const char** servers, int serversCount); - -/** \brief Sets the user name/password to use when not specified in the URL. - * - * Credentials are usually provided through the URL in the form: - * nats://foo:bar\@localhost:4222.
- * Until now, you could specify URLs in two ways, with #natsOptions_SetServers - * or #natsConnection_ConnectTo. The client library would connect (or reconnect) - * only to this given list of URLs, so if any of the server in the list required - * authentication, you were responsible for providing the appropriate credentials - * in the URLs.
- *
- * However, with cluster auto-discovery, the client library asynchronously receives - * URLs of servers in the cluster. These URLs do not contain any embedded credentials. - *
- * You need to use this function (or #natsOptions_SetToken) to instruct the client - * library to use those credentials when connecting to a server that requires - * authentication and for which there is no embedded credentials in the URL. - * - * @see natsOptions_SetToken - * @see natsOptions_SetURL - * @see natsOptions_SetServers - * - * @param opts the pointer to the #natsOptions object. - * @param user the user name to send to the server during connect. - * @param password the password to send to the server during connect. - */ -NATS_EXTERN natsStatus -natsOptions_SetUserInfo(natsOptions *opts, const char *user, const char *password); - -/** \brief Sets the token to use when not specified in the URL. - * - * Tokens are usually provided through the URL in the form: - * nats://mytoken\@localhost:4222.
- * Until now, you could specify URLs in two ways, with #natsOptions_SetServers - * or #natsConnection_ConnectTo. The client library would connect (or reconnect) - * only to this given list of URLs, so if any of the server in the list required - * authentication, you were responsible for providing the appropriate token - * in the URLs.
- *
- * However, with cluster auto-discovery, the client library asynchronously receives - * URLs of servers in the cluster. These URLs do not contain any embedded tokens. - *
- * You need to use this function (or #natsOptions_SetUserInfo) to instruct the client - * library to use this token when connecting to a server that requires - * authentication and for which there is no embedded token in the URL. - * - * @see natsOptions_SetUserInfo - * @see natsOptions_SetURL - * @see natsOptions_SetServers - * - * @param opts the pointer to the #natsOptions object. - * @param token the token to send to the server during connect. - */ -NATS_EXTERN natsStatus -natsOptions_SetToken(natsOptions *opts, const char *token); - -/** \brief Sets the tokenCb to use whenever a token is needed. - * - * For use cases where setting a static token through the URL
- * or through #natsOptions_SetToken is not desirable.
- *
- * This function can be used to generate a token whenever the client needs one.
- * Some example of use cases: expiring token, credential rotation, ... - * - * @see natsOptions_SetToken - * - * @param opts the pointer to the #natsOptions object. - * @param tokenCb the tokenCb to use to generate a token to the server during connect. - * @param closure a pointer to an user defined object (can be `NULL`). See - * the #natsMsgHandler prototype. - */ -NATS_EXTERN natsStatus -natsOptions_SetTokenHandler(natsOptions *opts, natsTokenHandler tokenCb, - void *closure); - -/** \brief Indicate if the servers list should be randomized. - * - * If 'noRandomize' is true, then the list of server URLs is used in the order - * provided by #natsOptions_SetURL() + #natsOptions_SetServers(). Otherwise, the - * list is formed in a random order. - * - * @param opts the pointer to the #natsOptions object. - * @param noRandomize if `true`, the list will be used as-is. - */ -NATS_EXTERN natsStatus -natsOptions_SetNoRandomize(natsOptions *opts, bool noRandomize); - -/** \brief Sets the (re)connect process timeout. - * - * This timeout, expressed in milliseconds, is used to interrupt a (re)connect - * attempt to a `NATS Server`. This timeout is used both for the low level TCP - * connect call, and for timing out the response from the server to the client's - * initial `PING` protocol. - * - * @param opts the pointer to the #natsOptions object. - * @param timeout the time, in milliseconds, allowed for an individual connect - * (or reconnect) to complete. - * - */ -NATS_EXTERN natsStatus -natsOptions_SetTimeout(natsOptions *opts, int64_t timeout); - -/** \brief Sets the name. - * - * This name is sent as part of the `CONNECT` protocol. There is no default name. - * - * @param opts the pointer to the #natsOptions object. - * @param name the name to set. - */ -NATS_EXTERN natsStatus -natsOptions_SetName(natsOptions *opts, const char *name); - -/** \brief Sets the secure mode. - * - * Indicates to the server if the client wants a secure (SSL/TLS) connection. - * - * The default is `false`. - * - * @param opts the pointer to the #natsOptions object. - * @param secure `true` for a secure connection, `false` otherwise. - */ -NATS_EXTERN natsStatus -natsOptions_SetSecure(natsOptions *opts, bool secure); - -/** \brief Loads the trusted CA certificates from a file. - * - * Loads the trusted CA certificates from a file. - * - * Note that the certificates are added to a SSL context for this #natsOptions - * object at the time of this call, so possible errors while loading the - * certificates will be reported now instead of when a connection is created. - * You can get extra information by calling #nats_GetLastError. - * - * @param opts the pointer to the #natsOptions object. - * @param fileName the file containing the CA certificates. - * - */ -NATS_EXTERN natsStatus -natsOptions_LoadCATrustedCertificates(natsOptions *opts, const char *fileName); - -/** \brief Sets the trusted CA certificates from memory. - * - * Similar to #natsOptions_LoadCATrustedCertificates expect that instead - * of loading from file, this loads from the given memory location. - * - * If more than one certificate need to be provided, they need to be - * concatenated. For instance: - * - * \code{.unparsed} - * const char *certs = - * "-----BEGIN CERTIFICATE-----\n" - * "MIIGjzCCBHegAwIBAgIJAKT2W9SKY7o4MA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD\n" - * (...) - * "-----END CERTIFICATE-----\n" - * "-----BEGIN CERTIFICATE-----\n" - * "MIIXyz...\n" - * (...) - * "-----END CERTIFICATE-----\n" - * \endcode - * - * @see natsOptions_LoadCATrustedCertificates - * - * @param opts the pointer to the #natsOptions object. - * @param certificates the string containing the concatenated CA certificates. - */ -NATS_EXTERN natsStatus -natsOptions_SetCATrustedCertificates(natsOptions *opts, const char *certificates); - -/** \brief Loads the certificate chain from a file, using the given key. - * - * The certificates must be in PEM format and must be sorted starting with - * the subject's certificate, followed by intermediate CA certificates if - * applicable, and ending at the highest level (root) CA. - * - * The private key file format supported is also PEM. - * - * See #natsOptions_LoadCATrustedCertificates regarding error reports. - * - * @param opts the pointer to the #natsOptions object. - * @param certsFileName the file containing the client certificates. - * @param keyFileName the file containing the client private key. - */ -NATS_EXTERN natsStatus -natsOptions_LoadCertificatesChain(natsOptions *opts, - const char *certsFileName, - const char *keyFileName); - -/** \brief Sets the client certificate and key. - * - * Similar to #natsOptions_LoadCertificatesChain expect that instead - * of loading from file, this loads from the given memory locations. - * - * @see natsOptions_LoadCertificatesChain() - * - * @param opts the pointer to the #natsOptions object. - * @param cert the memory location containing the client certificates. - * @param key the memory location containing the client private key. - */ -NATS_EXTERN natsStatus -natsOptions_SetCertificatesChain(natsOptions *opts, - const char *cert, - const char *key); - -/** \brief Sets the list of available ciphers. - * - * \note This function does not impact TLSv1.3 ciphersuites. - * - * Sets the list of available ciphers. - * Check https://www.openssl.org/docs/man1.1.1/man1/ciphers.html for the - * proper syntax. Here is an example: - * - * > "-ALL:HIGH" - * - * See #natsOptions_LoadCATrustedCertificates regarding error reports. - * - * @param opts the pointer to the #natsOptions object. - * @param ciphers the ciphers suite. - */ -NATS_EXTERN natsStatus -natsOptions_SetCiphers(natsOptions *opts, const char *ciphers); - -/** \brief Sets the list of available ciphers for TLSv1.3. - * - * Sets the list of available ciphers. - * Check https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_ciphersuites.html for the - * proper syntax. Here is an example: - * - * > "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256" - * - * See #natsOptions_LoadCATrustedCertificates regarding error reports. - * - * @param opts the pointer to the #natsOptions object. - * @param ciphers the ciphers suite. - */ -NATS_EXTERN natsStatus -natsOptions_SetCipherSuites(natsOptions *opts, const char *ciphers); - -/** \brief Sets the server certificate's expected hostname. - * - * If set, the library will check that the hostname in the server - * certificate matches the given `hostname`. This will occur when a connection - * is created, not at the time of this call. - * - * @param opts the pointer to the #natsOptions object. - * @param hostname the expected server certificate hostname. - */ -NATS_EXTERN natsStatus -natsOptions_SetExpectedHostname(natsOptions *opts, const char *hostname); - -/** \brief Switch server certificate verification. - * - * By default, the server certificate is verified. You can disable the verification - * by passing true to this function. - * - * \warning This is fine for tests but use with caution since this is not secure. - * - * @param opts the pointer to the #natsOptions object. - * @param skip set it to true to disable - or skip - server certificate verification. - */ -NATS_EXTERN natsStatus -natsOptions_SkipServerVerification(natsOptions *opts, bool skip); - -/** \brief Sets the verbose mode. - * - * Sets the verbose mode. If `true`, sends are echoed by the server with - * an `OK` protocol message. - * - * The default is `false`. - * - * @param opts the pointer to the #natsOptions object. - * @param verbose `true` for a verbose protocol, `false` otherwise. - */ -NATS_EXTERN natsStatus -natsOptions_SetVerbose(natsOptions *opts, bool verbose); - -/** \brief Sets the pedantic mode. - * - * Sets the pedantic mode. If `true` some extra checks will be performed - * by the server. - * - * The default is `false` - * - * @param opts the pointer to the #natsOptions object. - * @param pedantic `true` for a pedantic protocol, `false` otherwise. - */ -NATS_EXTERN natsStatus -natsOptions_SetPedantic(natsOptions *opts, bool pedantic); - -/** \brief Sets the ping interval. - * - * Interval, expressed in milliseconds, in which the client sends `PING` - * protocols to the `NATS Server`. - * - * @param opts the pointer to the #natsOptions object. - * @param interval the interval, in milliseconds, at which the connection - * will send `PING` protocols to the server. - */ -NATS_EXTERN natsStatus -natsOptions_SetPingInterval(natsOptions *opts, int64_t interval); - -/** \brief Sets the limit of outstanding `PING`s without corresponding `PONG`s. - * - * Specifies the maximum number of `PING`s without corresponding `PONG`s (which - * should be received from the server) before closing the connection with - * the #NATS_STALE_CONNECTION status. If reconnection is allowed, the client - * library will try to reconnect. - * - * @param opts the pointer to the #natsOptions object. - * @param maxPingsOut the maximum number of `PING`s without `PONG`s - * (positive number). - */ -NATS_EXTERN natsStatus -natsOptions_SetMaxPingsOut(natsOptions *opts, int maxPingsOut); - -/** \brief Sets the size of the internal read/write buffers. - * - * Sets the size, in bytes, of the internal read/write buffers used for - * reading/writing data from a socket. - * If not specified, or the value is 0, the library will use a default value, - * currently set to 32KB. - * - * @param opts the pointer to the #natsOptions object. - * @param ioBufSize the size, in bytes, of the internal buffer for read/write - * operations. - */ -NATS_EXTERN natsStatus -natsOptions_SetIOBufSize(natsOptions *opts, int ioBufSize); - -/** \brief Indicates if the connection will be allowed to reconnect. - * - * Specifies whether or not the client library should try to reconnect when - * losing the connection to the `NATS Server`. - * - * The default is `true`. - * - * @param opts the pointer to the #natsOptions object. - * @param allow `true` if the connection is allowed to reconnect, `false` - * otherwise. - */ -NATS_EXTERN natsStatus -natsOptions_SetAllowReconnect(natsOptions *opts, bool allow); - -/** \brief Sets the maximum number of reconnect attempts. - * - * Specifies the maximum number of reconnect attempts. - * - * @param opts the pointer to the #natsOptions object. - * @param maxReconnect the maximum number of reconnects (positive number). - */ -NATS_EXTERN natsStatus -natsOptions_SetMaxReconnect(natsOptions *opts, int maxReconnect); - -/** \brief Sets the time between reconnect attempts. - * - * Specifies how long to wait between two reconnect attempts from the same - * server. This means that if you have a list with S1,S2 and are currently - * connected to S1, and get disconnected, the library will immediately - * attempt to connect to S2. If this fails, it will go back to S1, and this - * time will wait for `reconnectWait` milliseconds since the last attempt - * to connect to S1. - * - * @param opts the pointer to the #natsOptions object. - * @param reconnectWait the time, in milliseconds, to wait between attempts - * to reconnect to the same server. - */ -NATS_EXTERN natsStatus -natsOptions_SetReconnectWait(natsOptions *opts, int64_t reconnectWait); - -/** \brief Set the upper bound of a random delay added to reconnect wait. - * - * After a disconnect, the library will try to reconnect to any server URLs - * in its list (the URLs are either provided by the user or discovered through - * gossip protocol). - * - * After the library failed to reconnect to every server in the list, it - * will wait for `reconnectWait` as specified with #natsOptions_SetReconnectWait(). - * This option adds some random jitter to the reconnect wait delay. - * - * This will help minimize the thundering herd phenomenon. For instance, suppose - * a server has 1000 connections, all were created at different times, but - * have the same reconnect wait option. If this server suddenly stops, then all - * connections will detect the failure and initiate a reconnect at the same time. - * The issue is even greater when those connections are TLS because of the added - * cost of the TLS handshake. - * - * @see natsOptions_SetReconnectWait() - * - * @param opts the pointer to the #natsOptions object. - * @param jitter the jitter in milliseconds for non TLS connections. - * @param jitterTLS the jitter in milliseconds for TLS connections. - */ -NATS_EXTERN natsStatus -natsOptions_SetReconnectJitter(natsOptions *opts, int64_t jitter, int64_t jitterTLS); - -/** \brief Sets the handler to invoke when the library needs to wait before the next reconnect attempts. - * - * This callback is invoked after the library tried every URL in the server list - * and failed to reconnect. It passes to the user the current number of attempts. - * This function shall return the amount of time the library will sleep before attempting - * to reconnect again. - * - * It is strongly recommended that this value contains some jitter to prevent all - * connections to attempt reconnecting at the same time. - * - * \note When using this approach, the reconnect wait as specified by #natsOptions_SetReconnectWait() - * is ignored. - * - * @param opts the pointer to the #natsOptions object. - * @param cb the custom reconnect delay handler to invoke. - * @param closure a pointer to an user defined object (can be `NULL`). See - * the #natsCustomReconnectDelayHandler prototype. - */ -NATS_EXTERN natsStatus -natsOptions_SetCustomReconnectDelay(natsOptions *opts, - natsCustomReconnectDelayHandler cb, - void *closure); - -/** \brief Sets the size of the backing buffer used during reconnect. - * - * Sets the size, in bytes, of the backing buffer holding published data - * while the library is reconnecting. Once this buffer has been exhausted, - * publish operations will return the #NATS_INSUFFICIENT_BUFFER error. - * If not specified, or the value is 0, the library will use a default value, - * currently set to 8MB. - * - * @param opts the pointer to the #natsOptions object. - * @param reconnectBufSize the size, in bytes, of the backing buffer for - * write operations during a reconnect. - */ -NATS_EXTERN natsStatus -natsOptions_SetReconnectBufSize(natsOptions *opts, int reconnectBufSize); - -/** \brief Sets the maximum number of pending messages per subscription. - * - * Specifies the maximum number of inbound messages that can be buffered in the - * library, for each subscription, before inbound messages are dropped and - * #NATS_SLOW_CONSUMER status is reported to the #natsErrHandler callback (if - * one has been set). - * - * @see natsOptions_SetErrorHandler() - * - * @param opts the pointer to the #natsOptions object. - * @param maxPending the number of messages allowed to be buffered by the - * library before triggering a slow consumer scenario. - */ -NATS_EXTERN natsStatus -natsOptions_SetMaxPendingMsgs(natsOptions *opts, int maxPending); - -/** \brief Sets the maximum number of pending bytes per subscription. - * - * Specifies the maximum number of inbound bytes that can be buffered in the - * library, for each subscription, before inbound messages are dropped and - * #NATS_SLOW_CONSUMER status is reported to the #natsErrHandler callback (if - * one has been set). - * - * @see natsOptions_SetErrorHandler() - * - * @param opts the pointer to the #natsOptions object. - * @param maxPending the number of bytes allowed to be buffered by the - * library before triggering a slow consumer scenario. - */ -NATS_EXTERN natsStatus -natsOptions_SetMaxPendingBytes(natsOptions* opts, int64_t maxPending); - -/** \brief Sets the error handler for asynchronous events. - * - * Specifies the callback to invoke when an asynchronous error - * occurs. This is used by applications having only asynchronous - * subscriptions that would not know otherwise that a problem with the - * connection occurred. - * - * @see natsErrHandler - * - * @param opts the pointer to the #natsOptions object. - * @param errHandler the error handler callback. - * @param closure a pointer to an user object that will be passed to - * the callback. `closure` can be `NULL`. - */ -NATS_EXTERN natsStatus -natsOptions_SetErrorHandler(natsOptions *opts, natsErrHandler errHandler, - void *closure); - -/** \brief Sets the callback to be invoked when a connection to a server - * is permanently lost. - * - * Specifies the callback to invoke when a connection is terminally closed, - * that is, after all reconnect attempts have failed (when reconnection is - * allowed). - * - * @param opts the pointer to the #natsOptions object. - * @param closedCb the callback to be invoked when the connection is closed. - * @param closure a pointer to an user object that will be passed to - * the callback. `closure` can be `NULL`. - */ -NATS_EXTERN natsStatus -natsOptions_SetClosedCB(natsOptions *opts, natsConnectionHandler closedCb, - void *closure); - -/** \brief Sets the callback to be invoked when the connection to a server is - * lost. - * - * Specifies the callback to invoke when a connection to the `NATS Server` - * is lost. - * - * \warning Invocation of this callback is asynchronous, which means that - * the state of the connection may have changed when this callback is - * invoked. - * - * @param opts the pointer to the #natsOptions object. - * @param disconnectedCb the callback to be invoked when a connection to - * a server is lost - * @param closure a pointer to an user object that will be passed to - * the callback. `closure` can be `NULL`. - */ -NATS_EXTERN natsStatus -natsOptions_SetDisconnectedCB(natsOptions *opts, - natsConnectionHandler disconnectedCb, - void *closure); - -/** \brief Sets the callback to be invoked when the connection has reconnected. - * - * Specifies the callback to invoke when the client library has successfully - * reconnected to a `NATS Server`. - * - * \warning Invocation of this callback is asynchronous, which means that - * the state of the connection may have changed when this callback is - * invoked. - * - * @param opts the pointer to the #natsOptions object. - * @param reconnectedCb the callback to be invoked when the connection to - * a server has been re-established. - * @param closure a pointer to an user object that will be passed to - * the callback. `closure` can be `NULL`. - */ -NATS_EXTERN natsStatus -natsOptions_SetReconnectedCB(natsOptions *opts, - natsConnectionHandler reconnectedCb, - void *closure); - -/** \brief Sets the callback to be invoked when new servers are discovered. - * - * Specifies the callback to invoke when the client library has been notified - * of one or more new `NATS Servers`. - * - * \warning Invocation of this callback is asynchronous, which means that - * the state may have changed when this callback is invoked. - * - * @param opts the pointer to the #natsOptions object. - * @param discoveredServersCb the callback to be invoked when new servers - * have been discovered. - * @param closure a pointer to an user object that will be passed to - * the callback. `closure` can be `NULL`. - */ -NATS_EXTERN natsStatus -natsOptions_SetDiscoveredServersCB(natsOptions *opts, - natsConnectionHandler discoveredServersCb, - void *closure); - -/** \brief Sets if the library should ignore or not discovered servers. - * - * By default, when a server joins a cluster, a client is notified - * of the new URL and added to the list so it can be used in case - * of a reconnect. - * - * The servers can be configured to disable this gossip, however, if - * not done at the servers level, this option allows the discovered - * servers to be ignored. - * - * @param opts the pointer to the #natsOptions object. - * @param ignore if discovered server should be ignored or not. - */ -NATS_EXTERN natsStatus -natsOptions_SetIgnoreDiscoveredServers(natsOptions *opts, bool ignore); - -/** \brief Sets the callback to be invoked when server enters lame duck mode. - * - * Specifies the callback to invoke when the server notifies - * the connection that it entered lame duck mode, that is, going to - * gradually disconnect all its connections before shuting down. This is - * often used in deployments when upgrading NATS Servers. - * - * \warning Invocation of this callback is asynchronous, which means that - * the state may have changed when this callback is invoked. - * - * @param opts the pointer to the #natsOptions object. - * @param lameDuckCb the callback to be invoked when server enters - * lame duck mode. - * @param closure a pointer to an user object that will be passed to - * the callback. `closure` can be `NULL`. - */ -NATS_EXTERN natsStatus -natsOptions_SetLameDuckModeCB(natsOptions *opts, - natsConnectionHandler lameDuckCb, - void *closure); - -/** \brief Sets the external event loop and associated callbacks. - * - * If you want to use an external event loop, the `NATS` library will not - * create a thread to read data from the socket, and will not directly write - * data to the socket. Instead, the library will invoke those callbacks - * for various events. - * - * @param opts the pointer to the #natsOptions object. - * @param loop the `void*` pointer to the external event loop. - * @param attachCb the callback invoked after the connection is connected, - * or reconnected. - * @param readCb the callback invoked when the event library should start or - * stop polling for read events. - * @param writeCb the callback invoked when the event library should start or - * stop polling for write events. - * @param detachCb the callback invoked when a connection is closed. - */ -NATS_EXTERN natsStatus -natsOptions_SetEventLoop(natsOptions *opts, - void *loop, - natsEvLoop_Attach attachCb, - natsEvLoop_ReadAddRemove readCb, - natsEvLoop_WriteAddRemove writeCb, - natsEvLoop_Detach detachCb); - -/** \brief Switch on/off the use of a central message delivery thread pool. - * - * Normally, each asynchronous subscriber that is created has its own - * message delivery thread. The advantage is that it reduces lock - * contentions, therefore improving performance.
- * However, if an application creates many subscribers, this is not scaling - * well since the process would use too many threads. - * - * When a connection is created from a `nats_Options` that has enabled - * global message delivery, asynchronous subscribers from this connection - * will use a shared thread pool responsible for message delivery. - * - * \note The message order per subscription is still guaranteed. - * - * @see nats_SetMessageDeliveryPoolSize() - * @see \ref envVariablesGroup - * - * @param opts the pointer to the #natsOptions object. - * @param global if `true`, uses the global message delivery thread pool, - * otherwise, each asynchronous subscriber will create their own message - * delivery thread. - */ -NATS_EXTERN natsStatus -natsOptions_UseGlobalMessageDelivery(natsOptions *opts, bool global); - -/** \brief Dictates the order in which host name are resolved during connect. - * - * The library would previously favor IPv6 addresses during the connect process. - *
- * You can now change the order, or even exclude a family of addresses, using - * this option. Here is the list of possible values: - *
- * Value | Meaning - * ------|-------- - * 46 | try IPv4 first, if it fails try IPv6 - * 64 | try IPv6 first, if it fails try IPv4 - * 4 | use only IPv4 - * 6 | use only IPv6 - * 0 | any family, no specific order - * - * \note If this option is not set, or you specify `0` for the order, the - * library will use the first IP (based on the DNS configuration) for which - * a successful connection can be made. - * - * @param opts the pointer to the #natsOptions object. - * @param order a string representing the order for the IP resolution. - */ -NATS_EXTERN natsStatus -natsOptions_IPResolutionOrder(natsOptions *opts, int order); - -/** \brief Sets if Publish calls should send data right away. - * - * For throughput performance, the client library tries by default to buffer - * as much data as possible before sending it over TCP. - * - * Setting this option to `true` will make Publish calls send the - * data right away, reducing latency, but also throughput. - * - * A good use-case would be a connection used to solely send replies. - * Imagine, a requestor sending a request, waiting for the reply before - * sending the next request.
- * The replier application will send only one reply at a time (since - * it will not receive the next request until the requestor receives - * the reply).
- * In such case, it makes sense for the reply to be sent right away. - * - * The alternative would be to call #natsConnection_Flush(), - * but this call requires a round-trip with the server, which is less - * efficient than using this option. - * - * Note that the Request() call already automatically sends the request - * as fast as possible, there is no need to set an option for that. - * - * @param opts the pointer to the #natsOptions object. - * @param sendAsap a boolean indicating if published data should be - * sent right away or be buffered. - */ -NATS_EXTERN natsStatus -natsOptions_SetSendAsap(natsOptions *opts, bool sendAsap); - -/** \brief Switches the use of old style requests. - * - * Setting `useOldStyle` to `true` forces the request calls to use the original - * behavior, which is to create a new inbox, a new subscription on that inbox - * and set auto-unsubscribe to 1. - * - * @param opts the pointer to the #natsOptions object. - * @param useOldStyle a boolean indicating if old request style should be used. - */ -NATS_EXTERN natsStatus -natsOptions_UseOldRequestStyle(natsOptions *opts, bool useOldStyle); - -/** \brief Fails pending requests on disconnect event. - * - * If this option is enabled, all pending #natsConnection_Request() family - * calls will fail with the #NATS_CONNECTION_DISCONNECTED status. - * - * \note This does not apply to requests from connections that use the - * old style requests. - * - * @see natsOptions_UseOldRequestStyle - * - * @param opts the pointer to the #natsOptions object. - * @param failRequests a boolean indicating if pending requests should fail - * when a disconnect event occurs. - */ -NATS_EXTERN natsStatus -natsOptions_SetFailRequestsOnDisconnect(natsOptions *opts, bool failRequests); - -/** \brief Sets if connection receives its own messages. - * - * This configures whether the server will echo back messages - * that are sent on this connection if there is also matching subscriptions. - * - * Set this to `true` to prevent the server from sending back messages - * produced by this connection. The default is false, that is, messages - * originating from this connection will be sent by the server if the - * connection has matching subscriptions. - * - * \note This is supported on servers >= version 1.2.0. Calling - * #natsConnection_Connect() with the option set to `true` to server below - * this version will return the `NATS_NO_SERVER_SUPPORT` error. - * - * @param opts the pointer to the #natsOptions object. - * @param noEcho a boolean indicating if sent messages can be delivered back - * to this connection or not. - */ -NATS_EXTERN natsStatus -natsOptions_SetNoEcho(natsOptions *opts, bool noEcho); - -/** \brief Indicates if initial connect failure should be retried or not. - * - * By default, #natsConnection_Connect() attempts to connect to a server - * specified in provided list of servers. If it cannot connect and the list has been - * fully tried, the function returns an error. - * - * This option is used to changed this default behavior. - * - * If `retry` is set to `true` and connection cannot be established right away, the - * library will attempt to connect based on the reconnect attempts - * and delay settings. - * - * \note The connect retry logic uses reconnect settings even if #natsOptions_SetAllowReconnect() - * has been set to false. In other words, a failed connect may be retried even though - * a reconnect will not be allowed should the connection to the server be lost - * after initial connect. - * - * The behavior will then depend on the value of the `connectedCb` parameter: - * - * * If `NULL`, then the call blocks until it can connect - * or exhausts the reconnect attempts. - * - * * If not `NULL`, and no connection can be immediately - * established, the #natsConnection_Connect() calls returns #NATS_NOT_YET_CONNECTED - * to indicate that no connection is currently established, but will - * try asynchronously to connect using the reconnect attempts/delay settings. If - * the connection is later established, the specified callback will be - * invoked. If no connection can be made and the retry attempts are exhausted, - * the callback registered with #natsOptions_SetClosedCB(), if any, will be - * invoked. - * - * \note If #natsConnection_Connect() returns `NATS_OK` (that is, a connection to - * a `NATS Server` was established in that call), then the `connectedCb` callback - * will not be invoked. - * - * If `retry` is set to false, #natsConnection_Connect() behaves as originally - * designed, that is, returns an error and no connection object if failing to connect - * to any server in the list. - * - * \note The `connectedCb` parameter is ignored and set to `NULL` in the options object - * when `retry` is set to `false`. - * - * @see natsOptions_SetMaxReconnect() - * @see natsOptions_SetReconnectWait() - * @see natsOptions_SetClosedCB() - * - * @param opts the pointer to the #natsOptions object. - * @param retry a boolean indicating if a failed connect should be retried. - * @param connectedCb if `retry` is true and this is not `NULL`, then the - * connect may be asynchronous and this callback will be invoked if the connect - * succeeds. - * @param closure a pointer to an user object that will be passed to the callback. `closure` can be `NULL`. - */ -NATS_EXTERN natsStatus -natsOptions_SetRetryOnFailedConnect(natsOptions *opts, bool retry, - natsConnectionHandler connectedCb, void* closure); - -/** \brief Sets the callbacks to fetch user JWT and sign server's nonce. - * - * Any time the library creates a TCP connection to the server, the server - * in response sends an `INFO` protocol. That `INFO` protocol, for NATS Server - * at v2.0.0+, may include a `nonce` for the client to sign. - * - * If this option is set, the library will invoke the two handlers to fetch - * the user JWT and sign the server's nonce. - * - * This is an option that will be used only by users that are able to - * sign using Ed25519 (public-key signature system). Most users will probably - * prefer the user of #natsOptions_SetUserCredentialsFromFiles(). - * - * \note natsOptions_SetUserCredentialsCallbacks() and natsOptions_SetNKey() - * are mutually exclusive. Calling this function will remove the NKey and - * replace the signature handler, that was set with natsOptions_SetNKey(), - * with this one. - * - * @see natsUserJWTHandler - * @see natsSignatureHandler - * @see natsOptions_SetUserCredentialsFromFiles() - * - * @param opts the pointer to the #natsOptions object. - * @param ujwtCB the callback to invoke to fetch the user JWT. - * @param ujwtClosure the closure that will be passed to the `ujwtCB` callback. - * @param sigCB the callback to invoke to sign the server nonce. - * @param sigClosure the closure that will be passed to the `sigCB` callback. - */ -NATS_EXTERN natsStatus -natsOptions_SetUserCredentialsCallbacks(natsOptions *opts, - natsUserJWTHandler ujwtCB, - void *ujwtClosure, - natsSignatureHandler sigCB, - void *sigClosure); - -/** \brief Sets the file(s) to use to fetch user JWT and seed required to sign nonce. - * - * This is a convenient option that specifies the files(s) to use to fetch - * the user JWT and the user seed to be used to sign the server's nonce. - * - * The `userOrChainedFile` contains the user JWT token and possibly the user - * NKey seed. Note the format of this file: - * - * \code{.unparsed} - * -----BEGIN NATS USER JWT----- - * ...an user JWT token... - * ------END NATS USER JWT------ - * - * ************************* IMPORTANT ************************* - * NKEY Seed printed below can be used to sign and prove identity. - * NKEYs are sensitive and should be treated as secrets. - * - * -----BEGIN USER NKEY SEED----- - * SU... - * ------END USER NKEY SEED------ - * \endcode - * - * The `---BEGIN NATS USER JWT---` header is used to detect where the user - * JWT is in this file. - * - * If the file does not contain the user NKey seed, then the `seedFile` file - * name must be specified and must contain the user NKey seed. - * - * \note natsOptions_SetUserCredentialsFromFiles() and natsOptions_SetNKey() - * are mutually exclusive. Calling this function will remove the NKey and - * replace the signature handler, that was set with natsOptions_SetNKey(), - * with an internal one that will handle the signature. - * - * @param opts the pointer to the #natsOptions object. - * @param userOrChainedFile the name of the file containing the user JWT and - * possibly the user NKey seed. - * @param seedFile the name of the file containing the user NKey seed. - */ -NATS_EXTERN natsStatus -natsOptions_SetUserCredentialsFromFiles(natsOptions *opts, - const char *userOrChainedFile, - const char *seedFile); - -/** \brief Sets JWT handler and handler to sign nonce that uses seed. - * - * This function acts similarly to natsOptions_SetUserCredentialsFromFiles() but reads from memory instead - * from a file. Also it assumes that `jwtAndSeedContent` contains both the JWT and NKey seed. - * - * As for the format, see natsOptions_SetUserCredentialsFromFiles() documentation. - * - * @see natsOptions_SetUserCredentialsFromFiles() - * - * @param opts the pointer to the #natsOptions object. - * @param jwtAndSeedContent string containing user JWT and user NKey seed. - */ -NATS_EXTERN natsStatus -natsOptions_SetUserCredentialsFromMemory(natsOptions *opts, - const char *jwtAndSeedContent); - -/** \brief Sets the NKey public key and signature callback. - * - * Any time the library creates a TCP connection to the server, the server - * in response sends an `INFO` protocol. That `INFO` protocol, for NATS Server - * at v2.0.0+, may include a `nonce` for the client to sign. - * - * If this option is set, the library will add the NKey public key `pubKey` - * to the `CONNECT` protocol along with the server's nonce signature resulting - * from the invocation of the signature handler `sigCB`. - * - * \note natsOptions_SetNKey() and natsOptions_SetUserCredentialsCallbacks() - * or natsOptions_SetUserCredentialsFromFiles() are mutually exclusive. - * Calling this function will remove the user JWT callback and replace the - * signature handler, that was set with one of the user credentials options, - * with this one. - * - * @see natsSignatureHandler - * - * @param opts the pointer to the #natsOptions object. - * @param pubKey the user NKey public key. - * @param sigCB the callback to invoke to sign the server nonce. - * @param sigClosure the closure that will be passed to the `sigCB` callback. - */ -NATS_EXTERN natsStatus -natsOptions_SetNKey(natsOptions *opts, - const char *pubKey, - natsSignatureHandler sigCB, - void *sigClosure); - -/** \brief Sets the NKey public key and its seed file. - * - * Any time the library creates a TCP connection to the server, the server - * in response sends an `INFO` protocol. That `INFO` protocol, for NATS Server - * at v2.0.0+, may include a `nonce` for the client to sign. - * - * If this option is set, the library will add the NKey public key `pubKey` - * to the `CONNECT` protocol along with the server's nonce signature signed - * using the private key from the provided seed file. The library takes care - * of clearing the memory holding the private key read from the file as soon - * as it is no longer needed. - * - * \note natsOptions_SetNKeyFromSeed() and natsOptions_SetUserCredentialsCallbacks() - * or natsOptions_SetUserCredentialsFromFiles() are mutually exclusive. - * Calling this function will remove the user JWT callback and replace the - * signature handler, that was set with one of the user credentials options, - * with this one. - * - * @see natsSignatureHandler - * - * @param opts the pointer to the #natsOptions object. - * @param pubKey the user NKey public key. - * @param seedFile the name of the file containing the user NKey seed. - */ -NATS_EXTERN natsStatus -natsOptions_SetNKeyFromSeed(natsOptions *opts, - const char *pubKey, - const char *seedFile); - -/** \brief Sets the write deadline. - * - * If this is set, the socket is set to non-blocking mode and - * write will have a deadline set. If the deadline is reached, - * the write call will return an error which will translate - * to publish calls, or any library call trying to send data - * to the server, to possibly fail. - * - * @param opts the pointer to the #natsOptions object. - * @param deadline the write deadline expressed in milliseconds. - * If set to 0, it means that there is no deadline and socket - * is in blocking mode. - */ -NATS_EXTERN natsStatus -natsOptions_SetWriteDeadline(natsOptions *opts, int64_t deadline); - -/** \brief Enable/Disable the "no responders" feature. - * - * By default, when a connection to a NATS Server v2.2.0+ is made, - * the library signals to the server that it supports the "no responders" - * feature, which means that if a request is made, and there are - * no subscriptions on the request subject (no responders), then - * the server sends back an empty message with the header "Status" - * and value "503". The request APIs capture this message and - * instead return a #NATS_NO_RESPONDERS status, instead of waiting - * for the timeout to occur and return #NATS_TIMEOUT. - * - * In case where users set up their own asynchronous subscription - * on the reply subject and publish the request with #natsConnection_PublishRequest - * and the like, then the message callback may be invoked with this - * "no responders" message, which can be checked with #natsMsg_IsNoResponders. - * - * However, if users don't want to have to deal with that, it is - * possible to instruct the server to disable this feature for - * a given connection. If that is the case, requests will behave - * as with pre-v2.2.0 servers, in that the request will timeout - * when there are no responders. - * - * \note This function is to disable the feature that is normally - * enabled by default. Passing `false` means that it would enable - * it again if you had previously disable the option. However, the - * feature may still be disabled when connecting to a server that - * does not support it. - * - * @see natsMsg_IsNoResponders() - * - * @param opts the pointer to the #natsOptions object. - * @param disabled the boolean to indicate if the feature should be - * disabled or not. - */ -NATS_EXTERN natsStatus -natsOptions_DisableNoResponders(natsOptions *opts, bool disabled); - -/** \brief Sets a custom inbox prefix - * - * The default inbox prefix is "_INBOX", but you can change it - * using this option. This can be useful when setting permissions - * and/or with import/exports across different accounts. - * - * The prefix must be a valid subject and not contain any of the - * wildcards tokens `*` nor `>`. - * - * To clear the custom inbox prefix, call this function with `NULL` - * or the empty string. - * - * @param opts the pointer to the #natsOptions object. - * @param inboxPrefix the desired inbox prefix. - */ -NATS_EXTERN natsStatus -natsOptions_SetCustomInboxPrefix(natsOptions *opts, const char *inboxPrefix); - -/** \brief Sets a custom padding when allocating buffer for incoming messages - * - * By default library allocates natsMsg with payload buffer size - * equal to payload size. Sometimes it can be useful to add some - * padding to the end of the buffer which can be tweaked using - * this option. - * - * To clear the custom message buffer padding, call this function with 0. - * Changing this option has no effect on existing NATS connections. - * - * @param opts the pointer to the #natsOptions object. - * @param paddingSize the desired inbox prefix. - */ -NATS_EXTERN natsStatus -natsOptions_SetMessageBufferPadding(natsOptions *opts, int paddingSize); - -/** \brief Destroys a #natsOptions object. - * - * Destroys the natsOptions object, freeing used memory. See the note in - * the natsOptions_Create() call. - * - * @param opts the pointer to the #natsOptions object to destroy. - */ -NATS_EXTERN void -natsOptions_Destroy(natsOptions *opts); - -/** @} */ // end of optsGroup - -#if defined(NATS_HAS_STREAMING) -/** \defgroup stanConnOptsGroup Streaming Connection Options - * - * NATS Streaming Connection Options. - * @{ - */ - -/** \brief Creates a #stanConnOptions object. - * - * Creates a #stanConnOptions object. This object is used when one wants to set - * specific options prior to connecting to the `NATS Streaning Server`. - * - * After making the appropriate `stanConnOptions_SetXXX()` calls, this object is passed - * to the #stanConnection_Connect() call, which will clone this object. After - * #stanConnection_Connect() returns, modifications to the options object - * will not affect the connection. - * - * The default options set in this call are: - * url: `nats://localhost:4222` - * connection wait: 2 seconds - * publish ack wait: 30 seconds - * discovery prefix: `_STAN.discovery` - * maximum publish acks inflight and percentage: 16384, 50% - * ping interval: 5 seconds - * max ping out without response: 3 - * - * \note The object needs to be destroyed when no longer needed. - * - * @see stanConnection_Connect() - * @see stanConnOptions_Destroy() - * - * @param newOpts the location where store the pointer to the newly created - * #stanConnOptions object. - */ -NATS_EXTERN natsStatus -stanConnOptions_Create(stanConnOptions **newOpts); - -/** \brief Sets the URL to connect to. - * - * Sets the URL of the `NATS Streaming Server` the client should try to connect to. - * The URL can contain optional user name and password. You can provide a comma - * separated list of URLs too. - * - * Some valid URLS: - * - * - nats://localhost:4222 - * - nats://user\@localhost:4222 - * - nats://user:password\@localhost:4222 - * - nats://host1:4222,nats://host2:4222,nats://host3:4222 - * - * \note This option takes precedence over #natsOptions_SetURL when NATS options - * are passed with #stanConnOptions_SetNATSOptions. - * - * @param opts the pointer to the #stanConnOptions object. - * @param url the string representing the URL the connection should use - * to connect to the server. - * - */ -NATS_EXTERN natsStatus -stanConnOptions_SetURL(stanConnOptions *opts, const char *url); - -/** \brief Sets the NATS Options to use to create the connection. - * - * The Streaming client connects to the NATS Streaming Server through - * a regular NATS Connection (#natsConnection). To configure this connection - * create a #natsOptions and configure it as needed, then call this function. - * - * This function clones the passed options, so after this call, any - * changes to the given #natsOptions will not affect the #stanConnOptions. - * - * \note If both #natsOptions_SetURL and #stanConnOptions_SetURL are used - * the URL(s) set in the later take precedence. - * - * @param opts the pointer to the #stanConnOptions object. - * @param nOpts the pointer to the #natsOptions object to use to create - * the connection to the server. - */ -NATS_EXTERN natsStatus -stanConnOptions_SetNATSOptions(stanConnOptions *opts, natsOptions *nOpts); - -/** \brief Sets the timeout for establishing a connection. - * - * Value expressed in milliseconds. - * - * Default is 2000 milliseconds (2 seconds). - * - * @param opts the pointer to the #stanConnOptions object. - * @param wait how long to wait for a response from the streaming server. - */ -NATS_EXTERN natsStatus -stanConnOptions_SetConnectionWait(stanConnOptions *opts, int64_t wait); - -/** \brief Sets the timeout for waiting for an ACK for a published message. - * - * Value expressed in milliseconds. - * - * Default is 30000 milliseconds (30 seconds). - * - * @param opts the pointer to the #stanConnOptions object. - * @param wait how long to wait for a response from the streaming server. - */ -NATS_EXTERN natsStatus -stanConnOptions_SetPubAckWait(stanConnOptions *opts, int64_t wait); - -/** \brief Sets the subject prefix the library sends the connect request to. - * - * Default is `_STAN.discovery` - * - * @param opts the pointer to the #stanConnOptions object. - * @param prefix the subject prefix the library sends the connect request to. - */ -NATS_EXTERN natsStatus -stanConnOptions_SetDiscoveryPrefix(stanConnOptions *opts, const char *prefix); - - -/** \brief Sets the maximum number of published messages without outstanding ACKs from the server. - * - * A connection will block #stanConnection_Publish() or #stanConnection_PublishAsync calls - * if the number of outstanding published messages has reached this number. - * - * When the connection receives ACKs, the number of outstanding messages will decrease. - * If the number falls between `maxPubAcksInflight * percentage`, then the blocked publish - * calls will be released. - * - * @param opts the pointer to the #stanConnOptions object. - * @param maxPubAcksInflight the maximum number of published messages without ACKs from the server. - * @param percentage the percentage (expressed as a float between ]0.0 and 1.0]) of the maxPubAcksInflight - * number below which a blocked publish call is released. - */ -NATS_EXTERN natsStatus -stanConnOptions_SetMaxPubAcksInflight(stanConnOptions *opts, int maxPubAcksInflight, float percentage); - -/** \brief Sets the ping interval and max out values. - * - * Value expressed in number of seconds. - * - * Default is 5 seconds and 3 missed PONGs. - * - * The interval needs to be at least 1 and represents the number of seconds. - * The maxOut needs to be at least 2, since the count of sent PINGs increase - * whenever a PING is sent and reset to 0 when a response is received. - * Setting to 1 would cause the library to close the connection right away. - * - * @param opts the pointer to the #stanConnOptions object. - * @param interval the number of seconds between each PING. - * @param maxOut the maximum number of PINGs without receiving a PONG back. - */ -NATS_EXTERN natsStatus -stanConnOptions_SetPings(stanConnOptions *opts, int interval, int maxOut); - -/** \brief Sets the connection lost handler. - * - * This callback will be invoked should the client permanently lose - * contact with the server (or another client replaces it while being - * disconnected). The callback will not be invoked on normal #stanConnection_Close(). - * - * @param opts the pointer to the #stanConnOptions object. - * @param handler the handler to be invoked when the connection to the streaming server is lost. - * @param closure the closure the library will pass to the callback. - */ -NATS_EXTERN natsStatus -stanConnOptions_SetConnectionLostHandler(stanConnOptions *opts, stanConnectionLostHandler handler, void *closure); - -/** \brief Destroys a #stanConnOptions object. - * - * Destroys the #stanConnOptions object, freeing used memory. See the note in - * the #stanConnOptions_Create() call. - * - * @param opts the pointer to the #stanConnOptions object to destroy. - */ -NATS_EXTERN void -stanConnOptions_Destroy(stanConnOptions *opts); - -/** @} */ // end of stanConnOptsGroup - -/** \defgroup stanSubOptsGroup Streaming Subscription Options - * - * NATS Streaming Subscription Options. - * @{ - */ - -/** \brief Creates a #stanSubOptions object. - * - * Creates a #stanSubOptions object. This object is used when one wants to set - * specific options prior to create a subscription. - * - * After making the appropriate `stanSubOptions_SetXXX()` calls, this object is passed - * to the #stanConnection_Subscribe() or #stanConnection_QueueSubscribe() call, which - * will clone this object. It means that modifications to the options object will not - * affect the created subscription. - * - * The default options set in this call are: - * ackWait: 30000 milliseconds (30 seconds) - * maxIinflight: 1024 - * start position: new only - * - * \note The object needs to be destroyed when no longer needed. - * - * @see stanConnection_Subscribe() - * @see stanConnection_QueueSubscribe() - * @see stanSubOptions_Destroy() - * - * @param newOpts the location where store the pointer to the newly created - * #stanSubOptions object. - */ -NATS_EXTERN natsStatus -stanSubOptions_Create(stanSubOptions **newOpts); - -/** \brief Sets the Durable Name for this subscription. - * - * If a durable name is set, this subscription will be durable. It means that - * if the application stops and re-create the same subscription with the - * same connection client ID and durable name (or simply durable name for - * queue subscriptions), then the server will resume (re)delivery of messages - * from the last known position in the steam for that durable. - * - * It means that the start position, if provided, is used only when the durable - * subscription is first created, then is ignored by the server for the rest - * of the durable subscription lifetime. - * - * \note Durable names should be alphanumeric and not contain the character `:`. - * - * @param opts the pointer to the #stanSubOptions object. - * @param durableName the string representing the name of the durable subscription. - * - */ -NATS_EXTERN natsStatus -stanSubOptions_SetDurableName(stanSubOptions *opts, const char *durableName); - -/** \brief Sets the timeout for waiting for an ACK from the cluster's point of view for delivered messages. - * - * Value expressed in milliseconds. - * - * Default is 30000 milliseconds (30 seconds). - * - * If the server does not receive an acknowledgment from the subscription for - * a delivered message after this amount of time, the server will re-deliver - * the unacknowledged message. - * - * @param opts the pointer to the #stanSubOptions object. - * @param wait how long the server will wait for an acknowledgment from the subscription. - */ -NATS_EXTERN natsStatus -stanSubOptions_SetAckWait(stanSubOptions *opts, int64_t wait); - -/** \brief Sets the the maximum number of messages the cluster will send without an ACK. - * - * Default is 1024. - * - * If a subscription receives messages but does not acknowledge them, the server will - * stop sending new messages when it reaches this number. Unacknowledged messages are - * re-delivered regardless of that setting. - * - * @param opts the pointer to the #stanSubOptions object. - * @param maxInflight the maximum number of messages the subscription will receive without sending back ACKs. - */ -NATS_EXTERN natsStatus -stanSubOptions_SetMaxInflight(stanSubOptions *opts, int maxInflight); - -/** \brief Sets the desired start position based on the given sequence number. - * - * This allows the subscription to start at a specific sequence number in - * the channel's message log. - * - * If the sequence is smaller than the first available message in the - * message log (old messages dropped due to channel limits), - * the subscription will be created with the first available sequence. - * - * Conversely, if the given sequence is higher than the currently - * last sequence number, the subscription will receive only new published messages. - * - * @param opts the pointer to the #stanSubOptions object. - * @param seq the starting sequence. - */ -NATS_EXTERN natsStatus -stanSubOptions_StartAtSequence(stanSubOptions *opts, uint64_t seq); - -/** \brief Sets the desired start position based on the given time. - * - * When the subscription is created, the server will send messages starting - * with the message which timestamp is at least the given time. The time - * is expressed in number of milliseconds since the EPOCH, as given by - * nats_Now() for instance. - * - * If the time is in the past and the most recent message's timestamp is older - * than the given time, or if the time is in the future, the subscription - * will receive only new messages. - * - * @param opts the pointer to the #stanSubOptions object. - * @param time the start time, expressed in milliseconds since the EPOCH. - */ -NATS_EXTERN natsStatus -stanSubOptions_StartAtTime(stanSubOptions *opts, int64_t time); - -/** \brief Sets the desired start position based on the given delta. - * - * When the subscription is created, the server will send messages starting - * with the message which timestamp is at least now minus `delta`. In other words, - * this means start receiving messages that were sent n milliseconds ago. - * - * The delta is expressed in milliseconds. - * - * @param opts the pointer to the #stanSubOptions object. - * @param delta he historical time delta (from now) from which to start receiving messages. - */ -NATS_EXTERN natsStatus -stanSubOptions_StartAtTimeDelta(stanSubOptions *opts, int64_t delta); - -/** \brief The subscription should start with the last message in the channel. - * - * When the subscription is created, the server will start sending messages - * starting with the last message currently in the channel message's log. - * The subscription will then receive any new published message. - * - * @param opts the pointer to the #stanSubOptions object. - */ -NATS_EXTERN natsStatus -stanSubOptions_StartWithLastReceived(stanSubOptions *opts); - -/** \brief The subscription should start with the first message in the channel. - * - * When the subscription is created, the server will start sending messages - * starting with the first message currently in the channel message's log. - * - * @param opts the pointer to the #stanSubOptions object. - */ -NATS_EXTERN natsStatus -stanSubOptions_DeliverAllAvailable(stanSubOptions *opts); - -/** \brief Sets the subscription's acknowledgment mode. - * - * By default, a subscription will automatically send a message acknowledgment - * after the #stanMsgHandler callback returns. - * - * In order to control when the acknowledgment is sent, set the acknowledgment - * mode to `manual` and call #stanSubscription_AckMsg(). - * - * @see #stanSubscription_AckMsg() - * - * @param opts the pointer to the #stanSubOptions object. - * @param manual a boolean indicating if the subscription should auto-acknowledge - * or if the user will. - */ -NATS_EXTERN natsStatus -stanSubOptions_SetManualAckMode(stanSubOptions *opts, bool manual); - -/** \brief Destroys a #stanSubOptions object. - * - * Destroys the #stanSubOptions object, freeing used memory. See the note in - * the #stanSubOptions_Create() call. - * - * @param opts the pointer to the #stanSubOptions object to destroy. - */ -NATS_EXTERN void -stanSubOptions_Destroy(stanSubOptions *opts); - -/** @} */ // end of stanSubOptsGroup -#endif - -/** \defgroup inboxGroup Inboxes - * - * NATS Inboxes. - * @{ - */ - -/** \brief Creates an inbox. - * - * Returns an inbox string which can be used for directed replies from - * subscribers. These are guaranteed to be unique, but can be shared - * and subscribed to by others. - * - * \note The inbox needs to be destroyed when no longer needed. - * - * @see #natsInbox_Destroy() - * - * @param newInbox the location where to store a pointer to the newly - * created #natsInbox. - */ -NATS_EXTERN natsStatus -natsInbox_Create(natsInbox **newInbox); - -/** \brief Destroys the inbox. - * - * Destroys the inbox. - * - * @param inbox the pointer to the #natsInbox object to destroy. - */ -NATS_EXTERN void -natsInbox_Destroy(natsInbox *inbox); - -/** @} */ // end of inboxGroup - -/** \defgroup msgGroup Message - * - * NATS Message. - * @{ - */ - -/** \brief Destroys this list of messages. - * - * This function iterates through the list of all messages and call #natsMsg_Destroy - * for each valid (not set to `NULL`) message. It then frees the array that was - * allocated to hold pointers to those messages. - * - * \note The #natsMsgList object itself is not freed since it is expected that - * users will pass a pointer to a stack object. Should the user create its own - * object, it will be the user responsibility to free this object. - * - * @param list the #natsMsgList list of #natsMsg objects to destroy. - */ -NATS_EXTERN void -natsMsgList_Destroy(natsMsgList *list); - -/** \brief Creates a #natsMsg object. - * - * Creates a #natsMsg object. This is used by the subscription related calls - * and by #natsConnection_PublishMsg(). - * - * \note Messages need to be destroyed with #natsMsg_Destroy() when no - * longer needed. - * - * @see natsMsg_Destroy() - * - * @param newMsg the location where to store the pointer to the newly created - * #natsMsg object. - * @param subj the subject this message will be sent to. Cannot be `NULL`. - * @param reply the optional reply for this message. - * @param data the optional message payload. - * @param dataLen the size of the payload. - */ -NATS_EXTERN natsStatus -natsMsg_Create(natsMsg **newMsg, const char *subj, const char *reply, - const char *data, int dataLen); - -/** \brief Returns the subject set in this message. - * - * Returns the subject set on that message. - * - * \warning The string belongs to the message and must not be freed. - * Copy it if needed. - * @param msg the pointer to the #natsMsg object. - */ -NATS_EXTERN const char* -natsMsg_GetSubject(const natsMsg *msg); - -/** \brief Returns the reply set in this message. - * - * Returns the reply, possibly `NULL`. - * - * \warning The string belongs to the message and must not be freed. - * Copy it if needed. - * - * @param msg the pointer to the #natsMsg object. - */ -NATS_EXTERN const char* -natsMsg_GetReply(const natsMsg *msg); - -/** \brief Returns the message payload. - * - * Returns the message payload, possibly `NULL`. - * - * Note that although the data sent and received from the server is not `NULL` - * terminated, the NATS C Client does add a `NULL` byte to the received payload. - * If you expect the received data to be a "string", then this conveniently - * allows you to call #natsMsg_GetData() without having to copy the returned - * data to a buffer to add the `NULL` byte at the end. - * - * \warning The string belongs to the message and must not be freed. - * Copy it if needed. - * - * @param msg the pointer to the #natsMsg object. - */ -NATS_EXTERN const char* -natsMsg_GetData(const natsMsg *msg); - -/** \brief Returns the message length. - * - * Returns the message's payload length, possibly 0. - * - * @param msg the pointer to the #natsMsg object. - */ -NATS_EXTERN int -natsMsg_GetDataLength(const natsMsg *msg); - -/** \brief Set the header entries associated with `key` to the single element `value`. - * - * It will replace any existing value associated with `key`. - * - * \warning Prior to v3.0.0, the `key` was stored in its canonical form, this is no - * longer the case. Header keys are now case sensitive. - * - * \warning Headers are not thread-safe, that is, you must not set/add/get values or - * delete keys for the same message from different threads. The internal structure - * of `natsMsg` may possible be altered during this call. - * - * @param msg the pointer to the #natsMsg object. - * @param key the key under which the `value` will be stored. It can't ne `NULL` or empty. - * @param value the string to store under the given `key`. The value can be `NULL` or empty string. - */ -NATS_EXTERN natsStatus -natsMsgHeader_Set(natsMsg *msg, const char *key, const char *value); - -/** \brief Add `value` to the header associated with `key`. - * - * It will append to any existing values associated with `key`. - * - * \warning Prior to v3.0.0, the `key` was stored in its canonical form, this is no - * longer the case. Header keys are now case sensitive. - * - * \warning Headers are not thread-safe, that is, you must not set/add/get values or - * delete keys for the same message from different threads. The internal structure - * of `natsMsg` may possible be altered during this call. - * - * @param msg the pointer to the #natsMsg object. - * @param key the key under which the `value` will be stored. It can't ne `NULL` or empty. - * @param value the string to add to the values associated with the given `key`. The value can be `NULL` or empty string. - */ -NATS_EXTERN natsStatus -natsMsgHeader_Add(natsMsg *msg, const char *key, const char *value); - -/** \brief Get the header entry associated with `key`. - * - * If more than one entry for the `key` is available, the first is returned. - * The returned value is owned by the library and MUST not be freed or altered. - * - * \warning Prior to v3.0.0, the `key` was stored in its canonical form, this is no - * longer the case. Header keys are now case sensitive. - * - * \warning Headers are not thread-safe, that is, you must not set/add/get values or - * delete keys for the same message from different threads. The internal structure - * of `natsMsg` may possible be altered during this call. - * - * @param msg the pointer to the #natsMsg object. - * @param key the key for which the value is requested. - * @param value the memory location where the library will store the pointer to the first - * value (if more than one is found) associated with the `key`. - * @return NATS_NOT_FOUND if `key` is not present in the headers. - */ -NATS_EXTERN natsStatus -natsMsgHeader_Get(natsMsg *msg, const char *key, const char **value); - -/** \brief Get all header values associated with `key`. - * - * The returned strings are own by the library and MUST not be freed or altered. - * However, the returned array `values` MUST be freed by the user. - * - * \code{.c} - * const char* *values = NULL; - * int count = 0; - * - * s = natsMsgHeader_Values(msg, "My-Key", &values, &count); - * if (s == NATS_OK) - * { - * // do something with the values - * - * // then free the array of pointers. - * free((void*) values); - * } - * \endcode - * - * \warning Prior to v3.0.0, the `key` was stored in its canonical form, this is no - * longer the case. Header keys are now case sensitive. - * - * \warning Headers are not thread-safe, that is, you must not set/add/get values or - * delete keys for the same message from different threads. The internal structure - * of `natsMsg` may possible be altered during this call. - * - * @param msg the pointer to the #natsMsg object. - * @param key the key for which the values are requested. - * @param values the memory location where the library will store the pointer to the array - * of values. - * @param count the memory location where the library will store the number of values returned. - * @return NATS_NOT_FOUND if `key` is not present in the headers. - */ -NATS_EXTERN natsStatus -natsMsgHeader_Values(natsMsg *msg, const char *key, const char* **values, int *count); - -/** \brief Get all header keys. - * - * The returned strings are own by the library and MUST not be freed or altered. - * However, the returned array `keys` MUST be freed by the user. - * - * \code{.c} - * const char* *keys = NULL; - * int count = 0; - * - * s = natsMsgHeader_Keys(msg, &keys, &count); - * if (s == NATS_OK) - * { - * // do something with the keys - * - * // then free the array of pointers. - * free((void*) keys); - * } - * \endcode - * - * \warning Headers are not thread-safe, that is, you must not set/add/get values or - * delete keys for the same message from different threads. The internal structure - * of `natsMsg` may possible be altered during this call. - * - * @param msg the pointer to the #natsMsg object. - * @param keys the memory location where the library will store the pointer to the array - * of keys. - * @param count the memory location where the library will store the number of keys returned. - * @return NATS_NOT_FOUND if no key is present. - */ -NATS_EXTERN natsStatus -natsMsgHeader_Keys(natsMsg *msg, const char* **keys, int *count); - -/** \brief Delete the value(s) associated with `key`. - * - * \warning Prior to v3.0.0, the `key` was stored in its canonical form, this is no - * longer the case. Header keys are now case sensitive. - * - * \warning Headers are not thread-safe, that is, you must not set/add/get values or - * delete keys for the same message from different threads. The internal structure - * of `natsMsg` may possible be altered during this call. - * - * @param msg the pointer to the #natsMsg object. - * @param key the key to delete from the headers map. - * @return NATS_NOT_FOUND if `key` is not present in the headers. - */ -NATS_EXTERN natsStatus -natsMsgHeader_Delete(natsMsg *msg, const char *key); - -/** \brief Indicates if this message is a "no responders" message from the server. - * - * Starting with the NATS Server v2.2.0+ and the C client v2.2.0+ releases, which - * introduced support for message headers and the "no responders" feature, if a - * request is received by the server and there are no subscriptions on the - * request's subject, the server sends a message with no payload but with a header - * "Status" with value "503". - * - * The call #natsConnection_Request() and its variants intercept this special - * message and instead of returning it to the user, they return #NATS_NO_RESPONDERS. - * - * If a synchronous subscription is created on a subject used as a reply subject - * to a #natsConnection_PublishRequest call (and its variants), #natsSubscription_NextMsg - * also detects this message and returns #NATS_NO_RESPONDERS (but it was not from - * release v2.2.0 to v2.4.1). - * - * For asynchronous subscriptions, the user may want to know that the request - * failed because there are no responders. For that reason, the message is passed - * to the message callback, and this function can be used to detect that this - * is a "no responders" message from the server and act accordingly. - * - * @param msg the pointer to the #natsMsg object. - * @return `true` if this message is a "no responders" message from the server, - * that is, has no payload and the "Status" header with "503" as the value. - */ -NATS_EXTERN bool -natsMsg_IsNoResponders(natsMsg *msg); - -/** \brief Destroys the message object. - * - * Destroys the message, freeing memory. - * - * @param msg the pointer to the #natsMsg object to destroy. - */ -NATS_EXTERN void -natsMsg_Destroy(natsMsg *msg); - -/** @} */ // end of msgGroup - -#if defined(NATS_HAS_STREAMING) -/** \defgroup stanMsgGroup Streaming Message - * - * NATS Streaming Message. - * @{ - */ - -/** \brief Returns the message's sequence number. - * - * Returns the message's sequence number (as assigned by the cluster). - * - * @param msg the pointer to the #stanMsg object. - */ -NATS_EXTERN uint64_t -stanMsg_GetSequence(const stanMsg *msg); - -/** \brief Returns the message's timestamp. - * - * Returns the message's timestamp (as assigned by the cluster). - * - * @param msg the pointer to the #stanMsg object. - */ -NATS_EXTERN int64_t -stanMsg_GetTimestamp(const stanMsg *msg); - -/** \brief Returns the message's redelivered flag. - * - * Returns the message's redelivered flag. This can help detect if this - * message is a possible duplicate (due to redelivery and at-least-once - * semantic). - * - * @param msg the pointer to the #stanMsg object. - */ -NATS_EXTERN bool -stanMsg_IsRedelivered(const stanMsg *msg); - -/** \brief Returns the message payload. - * - * Returns the message payload, possibly `NULL`. - * - * Note that although the data sent and received from the server is not `NULL` - * terminated, the NATS C Client does add a `NULL` byte to the received payload. - * If you expect the received data to be a "string", then this conveniently - * allows you to call #stanMsg_GetData() without having to copy the returned - * data to a buffer to add the `NULL` byte at the end. - * - * \warning The string belongs to the message and must not be freed. - * Copy it if needed. - * - * @param msg the pointer to the #stanMsg object. - */ -NATS_EXTERN const char* -stanMsg_GetData(const stanMsg *msg); - -/** \brief Returns the message length. - * - * Returns the message's payload length, possibly 0. - * - * @param msg the pointer to the #stanMsg object. - */ -NATS_EXTERN int -stanMsg_GetDataLength(const stanMsg *msg); - -/** \brief Destroys the message object. - * - * Destroys the message, freeing memory. - * - * @param msg the pointer to the #stanMsg object to destroy. - */ -NATS_EXTERN void -stanMsg_Destroy(stanMsg *msg); - -/** @} */ // end of stanMsgGroup -#endif - -/** \defgroup connGroup Connection - * - * NATS Connection - * @{ - */ - -/** \defgroup connMgtGroup Management - * - * Functions related to connection management. - * @{ - */ - -/** \brief Connects to a `NATS Server` using the provided options. - * - * Attempts to connect to a `NATS Server` with multiple options. - * - * This call is cloning the #natsOptions object. Once this call returns, - * changes made to the `options` will not have an effect to this - * connection. The `options` can however be changed prior to be - * passed to another #natsConnection_Connect() call if desired. - * - * @see #natsOptions - * @see #natsConnection_Destroy() - * - * @param nc the location where to store the pointer to the newly created - * #natsConnection object. - * @param options the options to use for this connection. If `NULL` - * this call is equivalent to #natsConnection_ConnectTo() with #NATS_DEFAULT_URL. - * - */ -NATS_EXTERN natsStatus -natsConnection_Connect(natsConnection **nc, natsOptions *options); - -/** \brief Causes the client to drop the connection to the current server and - * perform standard reconnection process. - * - * This means that all subscriptions and consumers should be resubscribed and - * their work resumed after successful reconnect where all reconnect options are - * respected. - * - * @param nc the pointer to the #natsConnection object. - */ -natsStatus -natsConnection_Reconnect(natsConnection *nc); - -/** \brief Process a read event when using external event loop. - * - * When using an external event loop, and the callback indicating that - * the socket is ready for reading, this call will read data from the - * socket and process it. - * - * @param nc the pointer to the #natsConnection object. - * - * \warning This API is reserved for external event loop adapters. - */ -NATS_EXTERN void -natsConnection_ProcessReadEvent(natsConnection *nc); - -/** \brief Process a write event when using external event loop. - * - * When using an external event loop, and the callback indicating that - * the socket is ready for writing, this call will write data to the - * socket. - * - * @param nc the pointer to the #natsConnection object. - * - * \warning This API is reserved for external event loop adapters. - */ -NATS_EXTERN void -natsConnection_ProcessWriteEvent(natsConnection *nc); - -/** \brief Connects to a `NATS Server` using any of the URL from the given list. - * - * Attempts to connect to a `NATS Server`. - * - * This call supports multiple comma separated URLs. If more than one is - * specified, it behaves as if you were using a #natsOptions object and - * called #natsOptions_SetServers() with the equivalent array of URLs. - * The list is randomized before the connect sequence starts. - * - * @see #natsConnection_Destroy() - * @see #natsOptions_SetServers() - * - * @param nc the location where to store the pointer to the newly created - * #natsConnection object. - * @param urls the URL to connect to, or the list of URLs to chose from. - * If `NULL` this call is equivalent to #natsConnection_ConnectTo() - * with #NATS_DEFAULT_URL - */ -NATS_EXTERN natsStatus -natsConnection_ConnectTo(natsConnection **nc, const char *urls); - -/** \brief Test if connection has been closed. - * - * Tests if connection has been closed. - * - * @param nc the pointer to the #natsConnection object. - */ -NATS_EXTERN bool -natsConnection_IsClosed(natsConnection *nc); - -/** \brief Test if connection is reconnecting. - * - * Tests if connection is reconnecting. - * - * @param nc the pointer to the #natsConnection object. - */ -NATS_EXTERN bool -natsConnection_IsReconnecting(natsConnection *nc); - -/** \brief Test if connection is draining. - * - * Tests if connection is draining. - * - * @param nc the pointer to the #natsConnection object. - */ -NATS_EXTERN bool -natsConnection_IsDraining(natsConnection *nc); - -/** \brief Returns the current state of the connection. - * - * Returns the current state of the connection. - * - * @see #natsConnStatus - * - * @param nc the pointer to the #natsConnection object. - */ -NATS_EXTERN natsConnStatus -natsConnection_Status(natsConnection *nc); - -/** \brief Returns the number of bytes to be sent to the server. - * - * When calling any of the publish functions, data is not necessarily - * immediately sent to the server. Some buffering occurs, allowing - * for better performance. This function indicates if there is any - * data not yet transmitted to the server. - * - * @param nc the pointer to the #natsConnection object. - * @return the number of bytes to be sent to the server, or -1 if the - * connection is closed. - */ -NATS_EXTERN int -natsConnection_Buffered(natsConnection *nc); - -/** \brief Flushes the connection. - * - * Performs a round trip to the server and return when it receives the - * internal reply. - * - * Note that if this call occurs when the connection to the server is - * lost, the `PING` will not be echoed even if the library can connect - * to a new (or the same) server. Therefore, in such situation, this - * call will fail with the status #NATS_CONNECTION_DISCONNECTED. - * - * If the connection is closed while this call is in progress, then the - * status #NATS_CONN_STATUS_CLOSED would be returned instead. - * - * @param nc the pointer to the #natsConnection object. - */ -NATS_EXTERN natsStatus -natsConnection_Flush(natsConnection *nc); - -/** \brief Flushes the connection with a given timeout. - * - * Performs a round trip to the server and return when it receives the - * internal reply, or if the call times-out (timeout is expressed in - * milliseconds). - * - * See possible failure case described in #natsConnection_Flush(). - * - * @param nc the pointer to the #natsConnection object. - * @param timeout in milliseconds, is the time allowed for the flush - * to complete before #NATS_TIMEOUT error is returned. - */ -NATS_EXTERN natsStatus -natsConnection_FlushTimeout(natsConnection *nc, int64_t timeout); - -/** \brief Returns the maximum message payload. - * - * Returns the maximum message payload accepted by the server. The - * information is gathered from the `NATS Server` when the connection is - * first established. - * - * @param nc the pointer to the #natsConnection object. - * @return the maximum message payload. - */ -NATS_EXTERN int64_t -natsConnection_GetMaxPayload(natsConnection *nc); - -/** \brief Gets the connection statistics. - * - * Copies in the provided statistics structure, a snapshot of the statistics for - * this connection. - * - * @param nc the pointer to the #natsConnection object. - * @param stats the pointer to a #natsStatistics object in which statistics - * will be copied. - */ -NATS_EXTERN natsStatus -natsConnection_GetStats(natsConnection *nc, natsStatistics *stats); - -/** \brief Gets the URL of the currently connected server. - * - * Copies in the given buffer, the connected server's Url. If the buffer is - * too small, an error is returned. - * - * @param nc the pointer to the #natsConnection object. - * @param buffer the buffer in which the URL is copied. - * @param bufferSize the size of the buffer. - */ -NATS_EXTERN natsStatus -natsConnection_GetConnectedUrl(natsConnection *nc, char *buffer, size_t bufferSize); - -/** \brief Gets the server Id. - * - * Copies in the given buffer, the connected server's Id. If the buffer is - * too small, an error is returned. - * - * @param nc the pointer to the #natsConnection object. - * @param buffer the buffer in which the server id is copied. - * @param bufferSize the size of the buffer. - */ -NATS_EXTERN natsStatus -natsConnection_GetConnectedServerId(natsConnection *nc, char *buffer, size_t bufferSize); - -/** \brief Returns the list of server URLs known to this connection. - * - * Returns the list of known servers, including additional servers - * discovered after a connection has been established (with servers - * version 0.9.2 and above). - * - * No credential information is included in any of the server URLs - * returned by this call.
- * If you want to use any of these URLs to connect to a server that - * requires authentication, you will need to use #natsOptions_SetUserInfo - * or #natsOptions_SetToken. - * - * \note The user is responsible for freeing the memory of the returned array. - * - * @param nc the pointer to the #natsConnection object. - * @param servers the location where to store the pointer to the array - * of server URLs. - * @param count the location where to store the number of elements of the - * returned array. - */ -NATS_EXTERN natsStatus -natsConnection_GetServers(natsConnection *nc, char ***servers, int *count); - -/** \brief Returns the list of discovered server URLs. - * - * Unlike #natsConnection_GetServers, this function only returns - * the list of servers that have been discovered after the a connection - * has been established (with servers version 0.9.2 and above). - * - * No credential information is included in any of the server URLs - * returned by this call.
- * If you want to use any of these URLs to connect to a server that - * requires authentication, you will need to use #natsOptions_SetUserInfo - * or #natsOptions_SetToken. - * - * \note The user is responsible for freeing the memory of the returned array. - * - * @param nc the pointer to the #natsConnection object. - * @param servers the location where to store the pointer to the array - * of server URLs. - * @param count the location where to store the number of elements of the - * returned array. - */ -NATS_EXTERN natsStatus -natsConnection_GetDiscoveredServers(natsConnection *nc, char ***servers, int *count); - -/** \brief Gets the last connection error. - * - * Returns the last known error as a 'natsStatus' and the location to the - * null-terminated error string. - * - * \warning The returned string is owned by the connection object and - * must not be freed. - * - * @param nc the pointer to the #natsConnection object. - * @param lastError the location where the pointer to the connection's last - * error string is copied. - */ -NATS_EXTERN natsStatus -natsConnection_GetLastError(natsConnection *nc, const char **lastError); - -/** \brief Gets the current client ID assigned by the server. - * - * Returns the client ID assigned by the server to which the client is - * currently connected to. - * - * \note The value may change if the client reconnects. - * - * This function returns #NATS_NO_SERVER_SUPPORT if the server's version - * is less than 1.2.0. - * - * @param nc the pointer to the #natsConnection object. - * @param cid the location where to store the client ID. - */ -NATS_EXTERN natsStatus -natsConnection_GetClientID(natsConnection *nc, uint64_t *cid); - -/** \brief Drains the connection with default timeout. - * - * Drain will put a connection into a drain state. All subscriptions will - * immediately be put into a drain state. Upon completion, the publishers - * will be drained and can not publish any additional messages. Upon draining - * of the publishers, the connection will be closed. Use the #natsOptions_SetClosedCB() - * option to know when the connection has moved from draining to closed. - * - * This call uses a default drain timeout of 30 seconds. - * - * \warning This function does not block waiting for the draining operation - * to complete. - * - * @see natsOptions_SetClosedCB - * @see natsConnection_DrainTimeout - * - * @param nc the pointer to the #natsConnection object. - */ -NATS_EXTERN natsStatus -natsConnection_Drain(natsConnection *nc); - -/** \brief Drains the connection with given timeout. - * - * Identical to #natsConnection_Drain but the timeout can be specified here. - * - * The value is expressed in milliseconds. Zero or negative value means - * that the operation will not timeout. - * - * \warning This function does not block waiting for the draining operation - * to complete. - * - * @see natsOptions_SetClosedCB - * @see natsConnection_Drain - * - * @param nc the pointer to the #natsConnection object. - * @param timeout the allowed time for a drain operation to complete, expressed - * in milliseconds. - */ -NATS_EXTERN natsStatus -natsConnection_DrainTimeout(natsConnection *nc, int64_t timeout); - -/** \brief Signs any 'message' using the connection's user credentials. - * - * The connection must have been created with the #natsOptions_SetUserCredentialsFromFiles. - * - * This call will sign the given message with the private key extracted through - * the user credentials file(s). - * - * @param nc the pointer to the #natsConnection object. - * @param message the byte array to sign. - * @param messageLen the length of the given byte array. - * @param sig an array of 64 bytes in which the signature will be copied. - */ -NATS_EXTERN natsStatus -natsConnection_Sign(natsConnection *nc, - const unsigned char *message, int messageLen, - unsigned char sig[64]); - -/** \brief Returns the client's IP address as reported by the server. - * - * When a connection is created to the server, the server identifies - * the connection's remote IP address and return it back to the client. - * - * \note The user is responsible to free memory allocated to store - * the client IP address. - * - * \note This is supported on servers >= version 2.1.6. Calling - * #natsConnection_GetClientIP() with server below this version will - * return the #NATS_NO_SERVER_SUPPORT error. - * - * @see natsConnection_GetLocalIPAndPort to get the local IP and port instead. - * - * @param nc the pointer to the #natsConnection object. - * @param ip the memory location where to store the client's IP string. - * The user is responsible from freeing this memory. - */ -NATS_EXTERN natsStatus -natsConnection_GetClientIP(natsConnection *nc, char **ip); - -/** \brief Returns the round trip time between this client and the server. - * - * The value returned is in nanoseconds. - * - * \note If the library is currently trying to reconnect, this call will - * return #NATS_CONNECTION_DISCONNECTED. - * - * @param nc the pointer to the #natsConnection object. - * @param rtt the memory location where to store the rtt. - */ -NATS_EXTERN natsStatus -natsConnection_GetRTT(natsConnection *nc, int64_t *rtt); - -/** \brief Returns if the connection to current server supports headers. - * - * Returns NATS_OK if the server this client is currently connected to - * supports headers, NATS_NO_SERVER_SUPPORT otherwise. - * - * @param nc the pointer to the #natsConnection object. - */ -NATS_EXTERN natsStatus -natsConnection_HasHeaderSupport(natsConnection *nc); - -/** \brief Returns the connection local IP and port. - * - * Unlike #natsConnection_GetClientIP, this function returns the - * connection's local IP and port. - * - * \note The user is responsible for freeing the memory allocated - * for the returned IP string. - * - * @param nc the pointer of the #natsConnection object. - * @param ip the memory location where to store the local IP string. - * The user is responsible from freeing this memory. - * @param port the memory location where to store the local port. - * - * @return #NATS_OK on success. - * @return #NATS_CONNECTION_DISCONNECTED if disconnected. - * @return #NATS_CONNECTION_CLOSED is connection is closed. - * @return #NATS_SYS_ERROR if a system error getting the IP and port occurred. - * @return #NATS_NO_MEMORY if the library was unable to allocate memory for the returned IP string. - */ -NATS_EXTERN natsStatus -natsConnection_GetLocalIPAndPort(natsConnection *nc, char **ip, int *port); - -/** \brief Closes the connection. - * - * Closes the connection to the server. This call will release all blocking - * calls, such as #natsConnection_Flush() and #natsSubscription_NextMsg(). - * The connection object is still usable until the call to - * #natsConnection_Destroy(). - * - * @param nc the pointer to the #natsConnection object. - */ -NATS_EXTERN void -natsConnection_Close(natsConnection *nc); - -/** \brief Destroys the connection object. - * - * Destroys the connection object, freeing up memory. - * If not already done, this call first closes the connection to the server. - * - * @param nc the pointer to the #natsConnection object. - */ -NATS_EXTERN void -natsConnection_Destroy(natsConnection *nc); - -/** @} */ // end of connMgtGroup - -/** \defgroup connPubGroup Publishing - * - * Publishing functions - * @{ - */ - -/** \brief Publishes data on a subject. - * - * Publishes the data argument to the given subject. The data argument is left - * untouched and needs to be correctly interpreted on the receiver. - * - * @param nc the pointer to the #natsConnection object. - * @param subj the subject the data is sent to. - * @param data the data to be sent, can be `NULL`. - * @param dataLen the length of the data to be sent. - */ -NATS_EXTERN natsStatus -natsConnection_Publish(natsConnection *nc, const char *subj, - const void *data, int dataLen); - -/** \brief Publishes a string on a subject. - * - * Convenient function to publish a string. This call is equivalent to: - * - * \code{.c} - * const char* myString = "hello"; - * - * natsConnection_Publish(nc, subj, (const void*) myString, (int) strlen(myString)); - * \endcode - * - * @param nc the pointer to the #natsConnection object. - * @param subj the subject the data is sent to. - * @param str the string to be sent. - */ -NATS_EXTERN natsStatus -natsConnection_PublishString(natsConnection *nc, const char *subj, - const char *str); - -/** \brief Publishes a message on a subject. - * - * Publishes the #natsMsg, which includes the subject, an optional reply and - * optional data. - * - * @see #natsMsg_Create() - * - * @param nc the pointer to the #natsConnection object. - * @param msg the pointer to the #natsMsg object to send. - */ -NATS_EXTERN natsStatus -natsConnection_PublishMsg(natsConnection *nc, natsMsg *msg); - -/** \brief Publishes data on a subject expecting replies on the given reply. - * - * Publishes the data argument to the given subject expecting a response on - * the reply subject. Use #natsConnection_Request() for automatically waiting - * for a response inline. - * - * @param nc the pointer to the #natsConnection object. - * @param subj the subject the request is sent to. - * @param reply the reply on which resonses are expected. - * @param data the data to be sent, can be `NULL`. - * @param dataLen the length of the data to be sent. - */ -NATS_EXTERN natsStatus -natsConnection_PublishRequest(natsConnection *nc, const char *subj, - const char *reply, const void *data, int dataLen); - -/** \brief Publishes a string on a subject expecting replies on the given reply. - * - * Convenient function to publish a request as a string. This call is - * equivalent to: - * - * \code{.c} - * const char* myString = "hello"; - * - * natsPublishRequest(nc, subj, reply, (const void*) myString, (int) strlen(myString)); - * \endcode - * - * @param nc the pointer to the #natsConnection object. - * @param subj the subject the request is sent to. - * @param reply the reply on which resonses are expected. - * @param str the string to send. - */ -NATS_EXTERN natsStatus -natsConnection_PublishRequestString(natsConnection *nc, const char *subj, - const char *reply, const char *str); - -/** \brief Sends a request and waits for a reply. - * - * Sends a request payload and delivers the first response message, - * or an error, including a timeout if no message was received properly. - * - * \warning If connected to a NATS Server v2.2.0+ with no responder running - * when the request is received, this call will return a #NATS_NO_RESPONDERS error. - * - * @param replyMsg the location where to store the pointer to the received - * #natsMsg reply. - * @param nc the pointer to the #natsConnection object. - * @param subj the subject the request is sent to. - * @param data the data of the request, can be `NULL`. - * @param dataLen the length of the data to send. - * @param timeout in milliseconds, before this call returns #NATS_TIMEOUT - * if no response is received in this alloted time. - */ -NATS_EXTERN natsStatus -natsConnection_Request(natsMsg **replyMsg, natsConnection *nc, const char *subj, - const void *data, int dataLen, int64_t timeout); - -/** \brief Sends a request (as a string) and waits for a reply. - * - * Convenient function to send a request as a string. This call is - * equivalent to: - * - * \code{.c} - * const char* myString = "hello"; - * - * natsConnection_Request(replyMsg, nc, subj, (const void*) myString, (int) strlen(myString)); - * \endcode - * - * \warning See warning about no responders in #natsConnection_Request(). - * - * @param replyMsg the location where to store the pointer to the received - * #natsMsg reply. - * @param nc the pointer to the #natsConnection object. - * @param subj the subject the request is sent to. - * @param str the string to send. - * @param timeout in milliseconds, before this call returns #NATS_TIMEOUT - * if no response is received in this alloted time. - */ -NATS_EXTERN natsStatus -natsConnection_RequestString(natsMsg **replyMsg, natsConnection *nc, - const char *subj, const char *str, - int64_t timeout); - -/** \brief Sends a request based on the given `requestMsg` and waits for a reply. - * - * Similar to #natsConnection_Request but uses `requestMsg` to extract subject, - * and payload to send. - * - * \warning See warning about no responders in #natsConnection_Request(). - * - * @param replyMsg the location where to store the pointer to the received - * #natsMsg reply. - * @param nc the pointer to the #natsConnection object. - * @param requestMsg the message used for the request. - * @param timeout in milliseconds, before this call returns #NATS_TIMEOUT - * if no response is received in this alloted time. - */ -NATS_EXTERN natsStatus -natsConnection_RequestMsg(natsMsg **replyMsg, natsConnection *nc, - natsMsg *requestMsg, int64_t timeout); - -/** @} */ // end of connPubGroup - -/** \defgroup connSubGroup Subscribing - * - * Subscribing functions. - * @{ - */ - -/** \brief Creates an asynchronous subscription. - * - * Expresses interest in the given subject. The subject can have wildcards - * (see \ref wildcardsGroup). Messages will be delivered to the associated - * #natsMsgHandler. - * - * @param sub the location where to store the pointer to the newly created - * #natsSubscription object. - * @param nc the pointer to the #natsConnection object. - * @param subject the subject this subscription is created for. - * @param cb the #natsMsgHandler callback. - * @param cbClosure a pointer to an user defined object (can be `NULL`). See - * the #natsMsgHandler prototype. - */ -NATS_EXTERN natsStatus -natsConnection_Subscribe(natsSubscription **sub, natsConnection *nc, - const char *subject, natsMsgHandler cb, - void *cbClosure); - -/** \brief Creates an asynchronous subscription with a timeout. - * - * Expresses interest in the given subject. The subject can have wildcards - * (see \ref wildcardsGroup). Messages will be delivered to the associated - * #natsMsgHandler. - * - * If no message is received by the given timeout (in milliseconds), the - * message handler is invoked with a `NULL` message.
- * You can then destroy the subscription in the callback, or simply - * return, in which case, the message handler will fire again when a - * message is received or the subscription times-out again. - * - * \note Receiving a message reset the timeout. Until all pending messages - * are processed, no timeout will occur. The timeout starts when the - * message handler for the last pending message returns. - * - * \warning If you re-use message handler code between subscriptions with - * and without timeouts, keep in mind that the message passed in the - * message handler may be `NULL`. - * - * @param sub the location where to store the pointer to the newly created - * #natsSubscription object. - * @param nc the pointer to the #natsConnection object. - * @param subject the subject this subscription is created for. - * @param timeout the interval (in milliseconds) after which, if no message - * is received, the message handler is invoked with a `NULL` message. - * @param cb the #natsMsgHandler callback. - * @param cbClosure a pointer to an user defined object (can be `NULL`). See - * the #natsMsgHandler prototype. - */ -NATS_EXTERN natsStatus -natsConnection_SubscribeTimeout(natsSubscription **sub, natsConnection *nc, - const char *subject, int64_t timeout, - natsMsgHandler cb, void *cbClosure); - -/** \brief Creates a synchronous subcription. - * - * Similar to #natsConnection_Subscribe, but creates a synchronous subscription - * that can be polled via #natsSubscription_NextMsg(). - * - * @param sub the location where to store the pointer to the newly created - * #natsSubscription object. - * @param nc the pointer to the #natsConnection object. - * @param subject the subject this subscription is created for. - */ -NATS_EXTERN natsStatus -natsConnection_SubscribeSync(natsSubscription **sub, natsConnection *nc, - const char *subject); - -/** \brief Creates an asynchronous queue subscriber. - * - * Creates an asynchronous queue subscriber on the given subject. - * All subscribers with the same queue name will form the queue group and - * only one member of the group will be selected to receive any given - * message asynchronously. - * - * @param sub the location where to store the pointer to the newly created - * #natsSubscription object. - * @param nc the pointer to the #natsConnection object. - * @param subject the subject this subscription is created for. - * @param queueGroup the name of the group. - * @param cb the #natsMsgHandler callback. - * @param cbClosure a pointer to an user defined object (can be `NULL`). See - * the #natsMsgHandler prototype. - * - */ -NATS_EXTERN natsStatus -natsConnection_QueueSubscribe(natsSubscription **sub, natsConnection *nc, - const char *subject, const char *queueGroup, - natsMsgHandler cb, void *cbClosure); - -/** \brief Creates an asynchronous queue subscriber with a timeout. - * - * Creates an asynchronous queue subscriber on the given subject. - * All subscribers with the same queue name will form the queue group and - * only one member of the group will be selected to receive any given - * message asynchronously. - * - * If no message is received by the given timeout (in milliseconds), the - * message handler is invoked with a `NULL` message.
- * You can then destroy the subscription in the callback, or simply - * return, in which case, the message handler will fire again when a - * message is received or the subscription times-out again. - * - * \note Receiving a message reset the timeout. Until all pending messages - * are processed, no timeout will occur. The timeout starts when the - * message handler for the last pending message returns. - * - * \warning If you re-use message handler code between subscriptions with - * and without timeouts, keep in mind that the message passed in the - * message handler may be `NULL`. - * - * @param sub the location where to store the pointer to the newly created - * #natsSubscription object. - * @param nc the pointer to the #natsConnection object. - * @param subject the subject this subscription is created for. - * @param queueGroup the name of the group. - * @param timeout the interval (in milliseconds) after which, if no message - * is received, the message handler is invoked with a `NULL` message. - * @param cb the #natsMsgHandler callback. - * @param cbClosure a pointer to an user defined object (can be `NULL`). See - * the #natsMsgHandler prototype. - */ -NATS_EXTERN natsStatus -natsConnection_QueueSubscribeTimeout(natsSubscription **sub, natsConnection *nc, - const char *subject, const char *queueGroup, - int64_t timeout, natsMsgHandler cb, void *cbClosure); - -/** \brief Creates a synchronous queue subscriber. - * - * Similar to #natsConnection_QueueSubscribe, but creates a synchronous - * subscription that can be polled via #natsSubscription_NextMsg(). - * - * @param sub the location where to store the pointer to the newly created - * #natsSubscription object. - * @param nc the pointer to the #natsConnection object. - * @param subject the subject this subscription is created for. - * @param queueGroup the name of the group. - */ -NATS_EXTERN natsStatus -natsConnection_QueueSubscribeSync(natsSubscription **sub, natsConnection *nc, - const char *subject, const char *queueGroup); - -/** @} */ // end of connSubGroup - -/** @} */ // end of connGroup - -/** \defgroup subGroup Subscription - * - * NATS Subscriptions. - * @{ - */ - -/** \brief Enables the No Delivery Delay mode. - * - * By default, messages that arrive are not immediately delivered. This - * generally improves performance. However, in case of request-reply, - * this delay has a negative impact. In such case, call this function - * to have the subscriber be notified immediately each time a message - * arrives. - * - * @param sub the pointer to the #natsSubscription object. - * - * \deprecated No longer needed. Will be removed in the future. - */ -NATS_EXTERN natsStatus -natsSubscription_NoDeliveryDelay(natsSubscription *sub); - -/** \brief Returns the next available message. - * - * Return the next message available to a synchronous subscriber or block until - * one is available. - * A timeout (expressed in milliseconds) can be used to return when no message - * has been delivered. If the value is zero, then this call will not wait and - * return the next message that was pending in the client, and #NATS_TIMEOUT - * otherwise. - * - * \note If you create a subscription for a subject used as the reply subject - * of a #natsConnection_PublishRequest call (or any of its variant), and there - * are no responders for the request subject, NATS Servers v2.2.0+ will return - * an empty message with a header "Status" and value "503". The library v2.2.0 - * until v2.4.1 would return this message to the user, which was wrong.
- * The library now returns no message and #NATS_NO_RESPONDERS status. - * - * @param nextMsg the location where to store the pointer to the next available - * message. - * @param sub the pointer to the #natsSubscription object. - * @param timeout time, in milliseconds, after which this call will return - * #NATS_TIMEOUT if no message is available. - */ -NATS_EXTERN natsStatus -natsSubscription_NextMsg(natsMsg **nextMsg, natsSubscription *sub, - int64_t timeout); - -/** \brief Unsubscribes. - * - * Removes interest on the subject. Asynchronous subscription may still have - * a callback in progress, in that case, the subscription will still be valid - * until the callback returns. - * - * @param sub the pointer to the #natsSubscription object. - */ -NATS_EXTERN natsStatus -natsSubscription_Unsubscribe(natsSubscription *sub); - -/** \brief Auto-Unsubscribes. - * - * This call issues an automatic #natsSubscription_Unsubscribe that is - * processed by the server when 'max' messages have been received. - * This can be useful when sending a request to an unknown number - * of subscribers. - * - * @param sub the pointer to the #natsSubscription object. - * @param max the maximum number of message you want this subscription - * to receive. - */ -NATS_EXTERN natsStatus -natsSubscription_AutoUnsubscribe(natsSubscription *sub, int max); - -/** \brief Gets the number of pending messages. - * - * Returns the number of queued messages in the client for this subscription. - * - * \deprecated Use #natsSubscription_GetPending instead. - * - * @param sub the pointer to the #natsSubscription object. - * @param queuedMsgs the location where to store the number of queued messages. - */ -NATS_EXTERN natsStatus -natsSubscription_QueuedMsgs(natsSubscription *sub, uint64_t *queuedMsgs); - -/** \brief Gets the subscription id. - * - * Returns the id of the given subscription. - * - * \note Invalid or closed subscriptions will cause a value of 0 to be returned. - * - * @param sub the pointer to the #natsSubscription object. - */ -NATS_EXTERN int64_t -natsSubscription_GetID(natsSubscription* sub); - -/** \brief Gets the subject name. - * - * Returns the subject of the given subscription. - * - * \note Invalid or closed subscriptions will cause a value of NULL to be returned. - * - * \warning The string belongs to the subscription and must not be freed. Copy it if needed. - * - * @param sub the pointer to the #natsSubscription object. - */ -NATS_EXTERN const char* -natsSubscription_GetSubject(natsSubscription* sub); - -/** \brief Sets the limit for pending messages and bytes. - * - * Specifies the maximum number and size of incoming messages that can be - * buffered in the library for this subscription, before new incoming messages are - * dropped and #NATS_SLOW_CONSUMER status is reported to the #natsErrHandler - * callback (if one has been set). - * - * If no limit is set at the subscription level, the limit set by #natsOptions_SetMaxPendingMsgs - * before creating the connection will be used. - * - * \note If no option is set, there is still a default of `65536` messages and - * `65536 * 1024` bytes. - * - * @see natsOptions_SetMaxPendingMsgs - * @see natsSubscription_GetPendingLimits - * - * @param sub he pointer to the #natsSubscription object. - * @param msgLimit the limit in number of messages that the subscription can hold. - * @param bytesLimit the limit in bytes that the subscription can hold. - */ -NATS_EXTERN natsStatus -natsSubscription_SetPendingLimits(natsSubscription *sub, int msgLimit, int bytesLimit); - -/** \brief Returns the current limit for pending messages and bytes. - * - * Regardless if limits have been explicitly set with #natsSubscription_SetPendingLimits, - * this call will store in the provided memory locations, the limits set for - * this subscription. - * - * \note It is possible for `msgLimit` and/or `bytesLimits` to be `NULL`, in which - * case the corresponding value is obviously not stored, but the function will - * not return an error. - * - * @see natsOptions_SetMaxPendingMsgs - * @see natsSubscription_SetPendingLimits - * - * @param sub the pointer to the #natsSubscription object. - * @param msgLimit if not `NULL`, the memory location where to store the maximum - * number of pending messages for this subscription. - * @param bytesLimit if not `NULL`, the memory location where to store the maximum - * size of pending messages for this subscription. - */ -NATS_EXTERN natsStatus -natsSubscription_GetPendingLimits(natsSubscription *sub, int *msgLimit, int *bytesLimit); - -/** \brief Returns the number of pending messages and bytes. - * - * Returns the total number and size of pending messages on this subscription. - * - * \note It is possible for `msgs` and `bytes` to be NULL, in which case the - * corresponding values are obviously not stored, but the function will not return - * an error. - * - * @param sub the pointer to the #natsSubscription object. - * @param msgs if not `NULL`, the memory location where to store the number of - * pending messages. - * @param bytes if not `NULL`, the memory location where to store the total size of - * pending messages. - */ -NATS_EXTERN natsStatus -natsSubscription_GetPending(natsSubscription *sub, int *msgs, int *bytes); - -/** \brief Returns the number of delivered messages. - * - * Returns the number of delivered messages for this subscription. - * - * @param sub the pointer to the #natsSubscription object. - * @param msgs the memory location where to store the number of - * delivered messages. - */ -NATS_EXTERN natsStatus -natsSubscription_GetDelivered(natsSubscription *sub, int64_t *msgs); - -/** \brief Returns the number of dropped messages. - * - * Returns the number of known dropped messages for this subscription. This happens - * when a consumer is not keeping up and the library starts to drop messages - * when the maximum number (and/or size) of pending messages has been reached. - * - * \note If the server declares the connection a slow consumer, this number may - * not be valid. - * - * @see natsOptions_SetMaxPendingMsgs - * @see natsSubscription_SetPendingLimits - * - * @param sub the pointer to the #natsSubscription object. - * @param msgs the memory location where to store the number of dropped messages. - */ -NATS_EXTERN natsStatus -natsSubscription_GetDropped(natsSubscription *sub, int64_t *msgs); - -/** \brief Returns the maximum number of pending messages and bytes. - * - * Returns the maximum of pending messages and bytes seen so far. - * - * \note `msgs` and/or `bytes` can be NULL. - * - * @param sub the pointer to the #natsSubscription object. - * @param msgs if not `NULL`, the memory location where to store the maximum - * number of pending messages seen so far. - * @param bytes if not `NULL`, the memory location where to store the maximum - * number of bytes pending seen so far. - */ -NATS_EXTERN natsStatus -natsSubscription_GetMaxPending(natsSubscription *sub, int *msgs, int *bytes); - -/** \brief Clears the statistics regarding the maximum pending values. - * - * Clears the statistics regarding the maximum pending values. - * - * @param sub the pointer to the #natsSubscription object. - */ -NATS_EXTERN natsStatus -natsSubscription_ClearMaxPending(natsSubscription *sub); - -/** \brief Get various statistics from this subscription. - * - * This is a convenient function to get several subscription's statistics - * in one call. - * - * \note Any or all of the statistics pointers can be `NULL`. - * - * @see natsSubscription_GetPending - * @see natsSubscription_GetMaxPending - * @see natsSubscription_GetDelivered - * @see natsSubscription_GetDropped - * - * @param sub the pointer to the #natsSubscription object. - * @param pendingMsgs if not `NULL`, memory location where to store the - * number of pending messages. - * @param pendingBytes if not `NULL`, memory location where to store the - * total size of pending messages. - * @param maxPendingMsgs if not `NULL`, memory location where to store the - * maximum number of pending messages seen so far. - * @param maxPendingBytes if not `NULL`, memory location where to store the - * maximum total size of pending messages seen so far. - * @param deliveredMsgs if not `NULL`, memory location where to store the - * number of delivered messages. - * @param droppedMsgs if not `NULL`, memory location where to store the - * number of dropped messages. - */ -NATS_EXTERN natsStatus -natsSubscription_GetStats(natsSubscription *sub, - int *pendingMsgs, - int *pendingBytes, - int *maxPendingMsgs, - int *maxPendingBytes, - int64_t *deliveredMsgs, - int64_t *droppedMsgs); - -/** \brief Checks the validity of the subscription. - * - * Returns a boolean indicating whether the subscription is still active. - * This will return false if the subscription has already been closed, - * or auto unsubscribed. - * - * @param sub the pointer to the #natsSubscription object. - */ -NATS_EXTERN bool -natsSubscription_IsValid(natsSubscription *sub); - -/** \brief Drains the subscription with a default timeout. - * - * Drain will remove interest but continue to invoke callbacks until all messages - * have been processed. - * - * This call uses a default drain timeout of 30 seconds. See #natsSubscription_DrainTimeout - * for details on behavior when timeout elapses. - * - * \warning This function does not block waiting for the operation - * to complete. To synchronously wait, see #natsSubscription_WaitForDrainCompletion - * - * @see natsSubscription_DrainTimeout - * @see natsSubscription_WaitForDrainCompletion - * @see natsSubscription_DrainCompletionStatus - * - * @param sub the pointer to the #natsSubscription object. - */ -NATS_EXTERN natsStatus -natsSubscription_Drain(natsSubscription *sub); - -/** \brief Drains the subscription with the specified timeout. - * - * Drain will remove interest but continue to invoke callbacks until all messages - * have been processed, or the specified timeout has elapsed. In that case, the - * subscription will be forcibly closed and remaining pending messages (if any) - * will not be processed. - * - * The timeout is expressed in milliseconds. Zero or negative value means that - * the call will not timeout, but see below for more details. - * - * When this call returns, the UNSUBSCRIBE protocol for this subscription has - * been enqueued to the outgoing connection buffer, but not sent to the server, - * ensuring that this call does not block. - * - * The library then asynchronously ensures that this protocol is sent and waits - * for a confirmation from the server. After that, it is guaranteed that no - * new message for this subscription will be received and the library can proceed - * with the rest of the draining. - * - * However, should the "flush" of the protocol fail, the library will ensure that - * no new message is added to the subscription (in the event the server did not - * receive the UNSUBSCRIBE protocol and still attempts to deliver messages), and - * will proceed with the draining of the pending messages. Users can check the - * status of the draining after it has completed by calling #natsSubscription_DrainCompletionStatus. - * - * If no timeout is specified (that is, value is zero or negative), a timeout - * will be used for the "flush" of the protocol. Again, even in case of failure, - * the draining will proceed. - * - * If a timeout is specified, the complete process: "flush" of the protocol - * and draining of messages, must happen before the timeout elapses otherwise the - * subscription will be forcibly closed, and not all message callbacks may be invoked. - * - * Regardless of the presence of a timeout or not, should the subscription or connection - * be closed while draining occurs, the draining process will stop. The - * #natsSubscription_WaitForDrainCompletion call will not report an error. To - * know if an error occurred, the user can call #natsSubscription_DrainCompletionStatus - * after ensuring that the drain has completed. - * - * \warning This function does not block waiting for the operation - * to complete. To synchronously wait, see #natsSubscription_WaitForDrainCompletion - * - * @see natsSubscription_Drain - * @see natsSubscription_WaitForDrainCompletion - * @see natsSubscription_DrainCompletionStatus - * - * @param sub the pointer to the #natsSubscription object. - * @param timeout how long to wait for the operation to complete, expressed in - * milliseconds. If the timeout elapses the subscription will be closed. - */ -NATS_EXTERN natsStatus -natsSubscription_DrainTimeout(natsSubscription *sub, int64_t timeout); - -/** \brief Blocks until the drain operation completes. - * - * This function blocks until the subscription is fully drained. - * Returns no error if the subscription is drained or closed, otherwise - * returns the error if the subscription was not in drained mode (#NATS_ILLEGAL_STATE) - * or if this subscription was not drained or closed prior to the specified - * timeout (#NATS_TIMEOUT). - * - * The timeout is expressed in milliseconds. Zero or negative value - * means that the call will not timeout. - * - * Note that if this call times-out, it does not mean that the drain stops. - * The drain will continue until its own timeout elapses. - * - * @see natsSubscription_Drain - * @see natsSubscription_DrainTimeout - * @see natsSubscription_DrainCompletionStatus - * - * @param sub the pointer to the #natsSubscription object. - * @param timeout how long to wait for the operation to complete, expressed in - * milliseconds. - */ -NATS_EXTERN natsStatus -natsSubscription_WaitForDrainCompletion(natsSubscription *sub, int64_t timeout); - -/** \brief Returns the status of the drain after completion. - * - * Once the drain has completed, users can use this function to know if the - * drain completed successfully or not. - * - * Possible return values (the list is not exhaustive): - * - * NATS_OK the library sent the UNSUBSCRIBE protocol and finished processing - * all messages that were pending.
- * NATS_ILLEGAL_STATE this call was made for a subscription that had not - * started draining or the draining is still in progress.
- * NATS_INVALID_SUBSCRIPTION the subscription was closed while draining, - * which means that some messages may not have been processed.
- * NATS_CONNECTION_CLOSED the connection was closed while draining, which - * means that some messages may not have been processed. - * - * \note This call does not wait for the drain completion (see #natsSubscription_WaitForDrainCompletion - * for that). - * - * @see natsSubscription_Drain - * @see natsSubscription_DrainTimeout - * @see natsSubscription_WaitForDrainCompletion - * - * @param sub the pointer to the #natsSubscription object. - */ -NATS_EXTERN natsStatus -natsSubscription_DrainCompletionStatus(natsSubscription *sub); - -/** \brief Sets a completion callback. - * - * In order to make sure that an asynchronous subscription's message handler is no longer invoked once the - * subscription is closed (#natsSubscription_Unsubscribe), the subscription should be closed from the - * message handler itslef. - * - * If the application closes the subscription from a different thread and immediately frees resources - * needed in the message handler, there is a risk of a crash since the subscription's message handler - * may still be invoked one last time or already in the process of executing. - * - * To address this, the user can set a callback that will be invoked after the subscription is closed and the - * message handler has returned. This applies to asynchronous subscriptions using their own dispatcher or using - * the library's delivery thread pool. - * - * \note You don't need to call this function if you are not freeing resources needed in the message handler or if - * you always close the subscription from the message handler itself. - * - * \note If you plan on calling this function, you should do so before calling #natsSubscription_AutoUnsubscribe, since - * there is a risk that the subscription be removed as soon as #natsSubscription_AutoUnsubscribe returns. - * - * Calling this function on a synchronous or closed subscription will return #NATS_INVALID_SUBSCRIPTION. - * - * @see natsOnCompleteCB - * - * @param sub the pointer to the #natsSubscription object - * @param cb the callback to invoke when the last message of a closed subscription has been dispatched - * @param closure the pointer to a user defined object (possibly `NULL`) that will be passed to the callback - */ -NATS_EXTERN natsStatus -natsSubscription_SetOnCompleteCB(natsSubscription *sub, natsOnCompleteCB cb, void *closure); - -/** \brief Destroys the subscription. - * - * Destroys the subscription object, freeing up memory. - * If not already done, this call will removes interest on the subject. - * - * @param sub the pointer to the #natsSubscription object to destroy. - */ -NATS_EXTERN void -natsSubscription_Destroy(natsSubscription *sub); - -/** @} */ // end of subGroup - -#if defined(NATS_HAS_STREAMING) -/** \defgroup stanConnGroup Streaming Connection - * - * NATS Streaming Connection - * @{ - */ - -/** \defgroup stanConnMgtGroup Management - * - * Functions related to connection management. - * @{ - */ - -/** \brief Connects to a `NATS Streaming Server` using the provided options. - * - * Attempts to connect to a `NATS Streaming Server`. - * - * This call is cloning the #stanConnOptions object, if given. Once this call returns, - * changes made to the `options` will not have an effect to this connection. - * The `options` can however be changed prior to be passed to another - * #stanConnection_Connect() call if desired. - * - * \note The Streaming connection does not honor the NATS Connection option - * #natsOptions_SetRetryOnFailedConnect(). If you pass NATS Options with - * this option enabled, no error is returned, but if the connection cannot - * be established "right away", the connect call will return an error. - * - * \warning If connecting to a NATS Server v2.2.0+ and there is no Streaming server - * listening on the connect request subject, this call will return #NATS_NO_RESPONDERS, - * not #NATS_TIMEOUT. - * - * @see #stanConnOptions - * @see #stanConnection_Destroy() - * - * @param sc the location where to store the pointer to the newly created - * #natsConnection object. - * @param clusterID the name of the cluster this connection is for. - * @param clientID the client ID for this connection. Only one connection - * with this ID will be accepted by the server. Use only a-zA-Z0-9_- characters. - * @param options the options to use for this connection (can be `NULL`). - */ -NATS_EXTERN natsStatus -stanConnection_Connect(stanConnection **sc, const char *clusterID, const char *clientID, - stanConnOptions *options); - -/** \brief Returns the underlying NATS Connection. - * - * This can be used if the application needs to do non streaming messaging - * but does not want to create a separate NATS Connection. - * - * Obtain a NATS connection from a NATS streaming connection. The NATS - * connection can be used to perform regular NATS operations, but it is owned - * and managed by the NATS streaming connection. It cannot be closed, - * which will happen when the NATS streaming connection is closed. - * - * \note For each call to this function, the user must call - * #stanConnection_ReleaseNATSConnection() when access to the NATS Connection - * is no longer needed. - * - * \warning The returned connection cannot be closed, drained nor destroyed. - * Calling corresponding functions will have no effect or return #NATS_ILLEGAL_STATE. - * - * @see stanConnection_ReleaseNATSConnection() - * - * @param sc the pointer to the #stanConnection object. - * @param nc the location where to store the pointer of the #natsConnection object. - */ -NATS_EXTERN natsStatus -stanConnection_GetNATSConnection(stanConnection *sc, natsConnection **nc); - -/** \brief Releases the NATS Connection. - * - * This should be paired with the #stanConnection_GetNATSConnection() call. - * That is, after getting a reference to the underlying NATS Connection and - * once that connection is no longer needed, calling this function will - * allow resources to be properly released when the streaming connection is destroyed. - * - * You would normally call #stanConnection_GetNATSConnection() and this function - * only once. - * - * After the last #stanConnection_ReleaseNATSConnection() call is made, you - * must no longer use the NATS Connection because if #stanConnection_Destroy() - * is called, that could make the pointer to the NATS Connection invalid. - * - * \note If the streaming connection is closed/destroyed before the last - * call to #stanConnection_ReleaseNATSConnection, the pointer to the NATS - * connection will still be valid, although all calls will fail since the - * connection is now closed. Calling this function will release the streaming - * object allowing memory to be freed. - * - * @see stanConnection_GetNATSConnection - * - * @param sc the pointer to the #stanConnection object. - */ -NATS_EXTERN void -stanConnection_ReleaseNATSConnection(stanConnection *sc); - -/** \brief Closes the connection. - * - * Closes the connection to the server. This call will release all blocking - * calls. The connection object is still usable until the call to - * #stanConnection_Destroy(). - * - * \warning See warning about connecting to a NATS Server v2.2.0+ in #stanConnection_Connect(). - * - * @param sc the pointer to the #stanConnection object. - */ -NATS_EXTERN natsStatus -stanConnection_Close(stanConnection *sc); - -/** \brief Destroys the connection object. - * - * Destroys the connection object, freeing up memory. - * If not already done, this call first closes the connection to the server. - * - * @param sc the pointer to the #stanConnection object. - */ -NATS_EXTERN natsStatus -stanConnection_Destroy(stanConnection *sc); - -/** @} */ // end of stanConnMgtGroup - -/** \defgroup stanConnPubGroup Publishing - * - * Publishing functions - * @{ - */ - -/** \brief Publishes data on a channel. - * - * Publishes the data argument to the given channel. The data argument is left - * untouched and needs to be correctly interpreted on the receiver. - * - * @param sc the pointer to the #stanConnection object. - * @param channel the channel name the data is sent to. - * @param data the data to be sent, can be `NULL`. - * @param dataLen the length of the data to be sent. - */ -NATS_EXTERN natsStatus -stanConnection_Publish(stanConnection *sc, const char *channel, - const void *data, int dataLen); - -/** \brief Asynchronously publishes data on a channel. - * - * Publishes the data argument to the given channel. The data argument is left - * untouched and needs to be correctly interpreted on the receiver. - * - * This function does not wait for an acknowledgment back from the server. - * Instead, the library will invoke the provided callback when that acknowledgment - * comes. - * - * In order to correlate the acknowledgment with the published message, you - * can use the `ahClosure` since this will be passed to the #stanPubAckHandler - * on every invocation. In other words, you should use a unique closure for - * each published message. - * - * @param sc the pointer to the #natsConnection object. - * @param channel the channel name the data is sent to. - * @param data the data to be sent, can be `NULL`. - * @param dataLen the length of the data to be sent. - * @param ah the publish acknowledgment callback. If `NULL` the user will not - * be notified of the publish result. - * @param ahClosure the closure the library will pass to the `ah` callback if - * one has been set. - */ -NATS_EXTERN natsStatus -stanConnection_PublishAsync(stanConnection *sc, const char *channel, - const void *data, int dataLen, - stanPubAckHandler ah, void *ahClosure); - -/** @} */ // end of stanConnPubGroup - -/** \defgroup stanConnSubGroup Subscribing - * - * Subscribing functions. - * @{ - */ - -/** \brief Creates a subscription. - * - * Expresses interest in the given subject. The subject can NOT have wildcards. - * Messages will be delivered to the associated #stanMsgHandler. - * - * \warning See warning about connecting to a NATS Server v2.2.0+ in #stanConnection_Connect(). - * - * @param sub the location where to store the pointer to the newly created - * #natsSubscription object. - * @param sc the pointer to the #natsConnection object. - * @param channel the channel this subscription is created for. - * @param cb the #stanMsgHandler callback. - * @param cbClosure a pointer to an user defined object (can be `NULL`). See - * the #stanMsgHandler prototype. - * @param options the optional to further configure the subscription. - */ -NATS_EXTERN natsStatus -stanConnection_Subscribe(stanSubscription **sub, stanConnection *sc, - const char *channel, stanMsgHandler cb, - void *cbClosure, stanSubOptions *options); - -/** \brief Creates a queue subscription. - * - * Creates a queue subscriber on the given channel. - * All subscribers with the same queue name will form the queue group and - * only one member of the group will be selected to receive any given - * message asynchronously. - * - * \warning See warning about connecting to a NATS Server v2.2.0+ in #stanConnection_Connect(). - * - * @param sub the location where to store the pointer to the newly created - * #natsSubscription object. - * @param sc the pointer to the #natsConnection object. - * @param channel the channel name this subscription is created for. - * @param queueGroup the name of the group. - * @param cb the #natsMsgHandler callback. - * @param cbClosure a pointer to an user defined object (can be `NULL`). See - * the #natsMsgHandler prototype. - * @param options the optional options to further configure this queue subscription. - */ -NATS_EXTERN natsStatus -stanConnection_QueueSubscribe(stanSubscription **sub, stanConnection *sc, - const char *channel, const char *queueGroup, - stanMsgHandler cb, void *cbClosure, stanSubOptions *options); - -/** @} */ // end of stanConnSubGroup - -/** @} */ // end of stanConnGroup - -/** \defgroup stanSubGroup Streaming Subscription - * - * NATS Streaming Subscriptions. - * @{ - */ - -/** \brief Sets a completion callback. - * - * In order to make sure that an asynchronous subscription's message handler is no longer invoked once the - * subscription is closed (or unsubscribed) (#stanSubscription_Close, #stanSubscription_Unsubscribe), the - * subscription should be closed from the message handler itslef. - * - * If the application closes the subscription from a different thread and immediately frees resources - * needed in the message handler, there is a risk of a crash since the subscription's message handler - * may still be invoked one last time or already in the process of executing. - * - * To address this, the user can set a callback that will be invoked after the subscription is closed and the - * message handler has returned. - * - * \note You don't need to call this function if you are not freeing resources needed in the message handler or if - * you always close the subscription from the message handler itself. - * - * @see natsOnCompleteCB - * - * @param sub the pointer to the #stanSubscription object - * @param cb the callback to invoke when the last message of a closed subscription has been dispatched - * @param closure the pointer to a user defined object (possibly `NULL`) that will be passed to the callback - */ -NATS_EXTERN natsStatus -stanSubscription_SetOnCompleteCB(stanSubscription *sub, natsOnCompleteCB cb, void *closure); - -/** \brief Acknowledge a message. - * - * If the subscription is created with manual acknowledgment mode (see #stanSubOptions_SetManualAckMode) - * then it is the user responsibility to acknowledge received messages when - * appropriate. - * - * @param sub the pointer to the #stanSubscription object. - * @param msg the message to acknowledge. - */ -NATS_EXTERN natsStatus -stanSubscription_AckMsg(stanSubscription *sub, stanMsg *msg); - -/** \brief Permanently remove a subscription. - * - * Removes interest on the channel. The subscription may still have - * a callback in progress, in that case, the subscription will still be valid - * until the callback returns. - * - * For non-durable subscriptions, #stanSubscription_Unsubscribe and #stanSubscription_Close - * have the same effect. - * - * For durable subscriptions, calling this function causes the server - * to remove the durable subscription (instead of simply suspending it). - * It means that once this call is made, calling #stanConnection_Subscribe() with - * the same durable name creates a brand new durable subscription, instead - * of simply resuming delivery. - * - * \warning See warning about connecting to a NATS Server v2.2.0+ in #stanConnection_Connect(). - * - * @param sub the pointer to the #stanSubscription object. - */ -NATS_EXTERN natsStatus -stanSubscription_Unsubscribe(stanSubscription *sub); - -/** \brief Closes the subscription. - * - * Similar to #stanSubscription_Unsubscribe() except that durable interest - * is not removed in the server. The durable subscription can therefore be - * resumed. - * - * \warning See warning about connecting to a NATS Server v2.2.0+ in #stanConnection_Connect(). - * - * @param sub the pointer to the #stanSubscription object. - */ -NATS_EXTERN natsStatus -stanSubscription_Close(stanSubscription *sub); - -/** \brief Destroys the subscription. - * - * Destroys the subscription object, freeing up memory. - * If not already done, this call will removes interest on the subject. - * - * @param sub the pointer to the #stanSubscription object to destroy. - */ -NATS_EXTERN void -stanSubscription_Destroy(stanSubscription *sub); - -/** @} */ // end of stanSubGroup -#endif - -/** \defgroup jsGroup JetStream - * - * JetStream. - * @{ - */ - -/** \brief Initializes a streaming context options structure. - * - * Use this before setting specific stream context options and passing it - * to JetStream APIs. - * - * @param opts the pointer to the #jsOptions to initialize. - */ -NATS_EXTERN natsStatus -jsOptions_Init(jsOptions *opts); - -/** \brief Returns a new JetStream context. - * - * A JetStream context is used for messaging and assets management. - * - * Since the underlying NATS connection is used for communication, the NATS connection - * should stay valid while using the JetStream context. That is, do not close/destroy - * the NATS connection before destroying the JetStream context. - * - * \note When done, the context should be destroyed to release memory. - * - * @param js the location where to store the pointer to the newly created #jsCtx object. - * @param nc the pointer to the #natsConnection object from which to get the JetStream context. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - */ -NATS_EXTERN natsStatus -natsConnection_JetStream(jsCtx **js, natsConnection *nc, jsOptions *opts); - -/** \brief Destroys the JetStream context. - * - * Releases memory used by the context object. - * - * @param js the pointer to the #jsCtx object to destroy. - */ -NATS_EXTERN void -jsCtx_Destroy(jsCtx *js); - -/** \defgroup jsAssetsGroup JetStream Assets Management - * - * JetStream Assets Management - * @{ - */ - -/** \brief Initializes a streaming configuration structure. - * - * Use this before setting specific stream configuration options and passing this - * configuration to some of the stream management APIs. - * - * @param cfg the pointer to the #jsStreamConfig to initialize. - */ -NATS_EXTERN natsStatus -jsStreamConfig_Init(jsStreamConfig *cfg); - -/** \brief Initializes a placement configuration structure. - * - * Use this before setting specific stream placement options. - * - * @param placement the pointer to the #jsPlacement to initialize. - */ -NATS_EXTERN natsStatus -jsPlacement_Init(jsPlacement *placement); - -/** \brief Initializes a stream source configuration structure. - * - * Use this before setting specific stream source options. - * - * @param source the pointer to the #jsStreamSource to initialize. - */ -NATS_EXTERN natsStatus -jsStreamSource_Init(jsStreamSource *source); - -/** \brief Initializes an external stream configuration structure. - * - * Use this before setting specific external stream options. - * - * @param external the pointer to the #jsExternalStream to initialize. - */ -NATS_EXTERN natsStatus -jsExternalStream_Init(jsExternalStream *external); - -/** \brief Initializes a republish structure. - * - * Use this to set the source, destination and/or headers only for a stream re-publish. - * - * @param rp the pointer to the #jsRePublish to initialize. - */ -NATS_EXTERN natsStatus -jsRePublish_Init(jsRePublish *rp); - -/** \brief Creates a stream. - * - * Creates a stream based on the provided configuration (that cannot be `NULL`). - * The name is mandatory and cannot contain . characters. - * - * \note If you do not need a #jsStreamInfo to be returned, you can pass `NULL`, - * otherwise, on success you are responsible for freeing this object. - * - * @see jsStreamConfig_Init - * @see jsStreamInfo_Destroy - * - * @param si the location where to store the pointer to the new #jsStreamInfo object in - * response to the creation request, or `NULL` if the stream information is not needed. - * @param js the pointer to the #jsCtx context. - * @param cfg the pointer to the #jsStreamConfig. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_AddStream(jsStreamInfo **si, jsCtx *js, jsStreamConfig *cfg, jsOptions *opts, jsErrCode *errCode); - -/** \brief Updates a stream. - * - * Updates a stream based on the provided configuration (that cannot be `NULL`). - * The name is mandatory and cannot contain . characters. - * - * \note If you do not need a #jsStreamInfo to be returned, you can pass `NULL`, - * otherwise, on success you are responsible for freeing this object. - * - * @see jsStreamConfig_Init - * @see jsStreamInfo_Destroy - * - * @param si the location where to store the pointer to the new #jsStreamInfo object in - * response to the creation request, or `NULL` if the stream information is not needed. - * @param js the pointer to the #jsCtx context. - * @param cfg the pointer to the #jsStreamConfig. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_UpdateStream(jsStreamInfo **si, jsCtx *js, jsStreamConfig *cfg, jsOptions *opts, jsErrCode *errCode); - -/** \brief Purges a stream. - * - * Purges the stream named stream. - * - * For more advanced purge options, you can specify them through #jsOptions. - * - * \code{.unparsed} - * jsOptions o; - * - * jsOptions_Init(&o); - * o.Stream.Purge.Subject = "foo"; - * o.Stream.Purge.Sequence = 4; - * - * js_PurgeStream(js, "MY_STREAM", &o, &jerr); - * \endcode - * - * @param js the pointer to the #jsCtx context. - * @param stream the name of the stream to purge. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_PurgeStream(jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode); - -/** \brief Deletes a stream. - * - * Deletes the stream named stream. - * - * @param js the pointer to the #jsCtx context. - * @param stream the name of the stream to delete. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_DeleteStream(jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode); - -/** \brief Retrieves a JetStream message from the stream by sequence. - * - * Retrieves a raw stream message stored in JetStream by sequence number. - * - * \note The message needs to be destroyed by calling #natsMsg_Destroy. - * - * @see js_GetLastMsg - * @see natsMsg_Destroy - * - * @param msg the memory location where the library will store the pointer to the #natsMsg. - * @param js the pointer to the #jsCtx context. - * @param stream the name of the stream. - * @param seq the sequence in the stream of the message being retrieved. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_GetMsg(natsMsg **msg, jsCtx *js, const char *stream, uint64_t seq, jsOptions *opts, jsErrCode *errCode); - -/** \brief Retrieves the last JetStream message from the stream for a given subject. - * - * Retrieves the last JetStream message from the stream for a given subject. - * - * \note The message needs to be destroyed by calling #natsMsg_Destroy. - * - * @see js_GetMsg - * @see natsMsg_Destroy - * - * @param msg the memory location where the library will store the pointer to the #natsMsg. - * @param js the pointer to the #jsCtx context. - * @param stream the name of the stream. - * @param subject the subject for which the last message is being retrieved. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_GetLastMsg(natsMsg **msg, jsCtx *js, const char *stream, const char *subject, jsOptions *opts, jsErrCode *errCode); - -/** \brief Initializes a direct get message options structure. - * - * Use this before setting specific direct get message options and passing it - * to #js_DirectGetMsg API. - * - * @param opts the pointer to the #jsDirectGetMsgOptions object. - */ -NATS_EXTERN natsStatus -jsDirectGetMsgOptions_Init(jsDirectGetMsgOptions *opts); - -/** \brief Retrieves directly a JetStream message based on provided options. - * - * If a stream is created with `AllowDirect`, it is possible to retrieve a message - * without going through the leader. - * - * To specify the options, call #jsDirectGetMsgOptions_Init first and the set - * the appropriate options, then invoke this function. - * - * \note Some options are mutually exclusive but the library is not doing the - * check and leave it to the server to do it and return the error returned by - * the server. - * - * \note This API can only be used against servers that support the direct - * get feature, which is `v2.9.0+`. If running against an older server the - * call will likely timeout. - * - * @param msg the location where to store the pointer to the retrieved message. - * @param js the pointer to the #jsCtx context. - * @param stream the name of the stream. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param dgOpts the pointer to the #jsDirectGetMsgOptions object, cannot be `NULL`. - */ -NATS_EXTERN natsStatus -js_DirectGetMsg(natsMsg **msg, jsCtx *js, const char *stream, jsOptions *opts, jsDirectGetMsgOptions *dgOpts); - -/** \brief Deletes a message from the stream. - * - * Deletes the message at sequence seq in the stream named stream. - * - * \note To completely erase the content of the deleted message when stored on disk, - * use #js_EraseMsg instead. - * - * @see js_EraseMsg - * - * @param js the pointer to the #jsCtx context. - * @param stream the name of the stream. - * @param seq the sequence in the stream of the message to delete. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_DeleteMsg(jsCtx *js, const char *stream, uint64_t seq, jsOptions *opts, jsErrCode *errCode); - -/** \brief Erases a message from the stream. - * - * Similar to #js_DeleteMsg except that the content of the deleted message is - * erased from stable storage. - * - * @see js_DeleteMsg - * - * @param js the pointer to the #jsCtx context. - * @param stream the name of the stream. - * @param seq the sequence in the stream of the message to erase. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_EraseMsg(jsCtx *js, const char *stream, uint64_t seq, jsOptions *opts, jsErrCode *errCode); - -/** \brief Retreives information from a stream. - * - * Returns information about the stream named stream. - * - * \note You need to free the returned object. - * - * To get some detailed information about deleted messages, set this option: - * - * \code{.unparsed} - * jsOptions o; - * - * jsOptions_Init(&o); - * o.Stream.Info.DeletedDetails = true; - * js_GetStreamInfo(&si, js, "MY_STREAM", &o, &jerr); - * \endcode - * - * @see jsStreamInfo_Destroy - * - * @param si the location where to store the pointer to the new #jsStreamInfo object in - * response to the creation request. - * @param js the pointer to the #jsCtx context. - * @param stream the name of the stream which information is retrieved. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_GetStreamInfo(jsStreamInfo **si, jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode); - -/** \brief Destroys the stream information object. - * - * Releases memory allocated for this stream information object. - * - * @param si the pointer to the #jsStreamInfo object. - */ -NATS_EXTERN void -jsStreamInfo_Destroy(jsStreamInfo *si); - -/** \brief Retrieves the list of all available streams. - * - * Retrieves the list of all #jsStreamInfo. It is possible to filter - * which streams are to be retrieved based on a subject filter. - * - * \warning The list should be destroyed when no longer used by - * calling #jsStreamInfoList_Destroy. - * - * @param list the location where to store the pointer to the new #jsStreamInfoList object. - * @param js the pointer to the #jsCtx context. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_Streams(jsStreamInfoList **list, jsCtx *js, jsOptions *opts, jsErrCode *errCode); - -/** \brief Destroys the stream information list object. - * - * Releases memory allocated for this stream information list. - * - * \warning All #jsStreamInfo pointers contained in the list will - * be destroyed by this call. - * - * @param list the pointer to the #jsStreamInfoList object. - */ -NATS_EXTERN void -jsStreamInfoList_Destroy(jsStreamInfoList *list); - -/** \brief Retrieves the list of all available stream names. - * - * Retrieves the list of all stream names. It is possible to filter - * which streams are to be retrieved based on a subject filter. - * - * \warning The list should be destroyed when no longer used by - * calling #jsStreamNamesList_Destroy. - * - * @param list the location where to store the pointer to the new #jsStreamNamesList object. - * @param js the pointer to the #jsCtx context. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_StreamNames(jsStreamNamesList **list, jsCtx *js, jsOptions *opts, jsErrCode *errCode); - -/** \brief Destroys the stream names list object. - * - * Releases memory allocated for this list of stream names. - * - * \warning All string pointers contained in the list will - * be destroyed by this call. - * - * @param list the pointer to the #jsStreamNamesList object. - */ -NATS_EXTERN void -jsStreamNamesList_Destroy(jsStreamNamesList *list); - -/** \brief Initializes a consumer configuration structure. - * - * Use this before adding a consumer. - * - * @see #jsConsumerConfig - * - * @param cc the pointer to the #jsConsumerConfig to initialize. - */ -NATS_EXTERN natsStatus -jsConsumerConfig_Init(jsConsumerConfig *cc); - -/** \brief Adds a JetStream consumer. - * - * Adds a consumer based on the provided configuration (that cannot be `NULL`). - * - * \note If you do not need a #jsConsumerInfo to be returned, you can pass `NULL`, - * otherwise, on success you are responsible for freeing this object. - * - * @see jsConsumerConfig_Init - * @see jsConsumerInfo_Destroy - * - * @param ci the location where to store the pointer to the new #jsConsumerInfo object in - * response to the creation request, or `NULL` if the consumer information is not needed. - * @param js the pointer to the #jsCtx context. - * @param stream the name of the stream. - * @param cfg the pointer to the #jsConsumerConfig. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_AddConsumer(jsConsumerInfo **ci, jsCtx *js, - const char *stream, jsConsumerConfig *cfg, - jsOptions *opts, jsErrCode *errCode); - -/** \brief Updates a JetStream consumer. - * - * Updates a consumer based on the provided configuration (that cannot be `NULL`). - * - * \note If you do not need a #jsConsumerInfo to be returned, you can pass `NULL`, - * otherwise, on success you are responsible for freeing this object. - * - * @see jsConsumerConfig_Init - * @see jsConsumerInfo_Destroy - * - * @param ci the location where to store the pointer to the new #jsConsumerInfo object in - * response to the creation request, or `NULL` if the consumer information is not needed. - * @param js the pointer to the #jsCtx context. - * @param stream the name of the stream. - * @param cfg the pointer to the #jsConsumerConfig. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_UpdateConsumer(jsConsumerInfo **ci, jsCtx *js, - const char *stream, jsConsumerConfig *cfg, - jsOptions *opts, jsErrCode *errCode); - -/** \brief Retrieves information about a consumer. - * - * \note The returned object should be destroyed using #jsConsumerInfo_Destroy in order - * to free allocated memory. - * - * @param ci the location where to store the pointer to the new #jsConsumerInfo object. - * @param js the pointer to the #jsCtx context. - * @param stream the name of the stream. - * @param consumer the name of the consumer. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_GetConsumerInfo(jsConsumerInfo **ci, jsCtx *js, - const char *stream, const char *consumer, - jsOptions *opts, jsErrCode *errCode); - -/** \brief Deletes a consumer. - * - * Deletes the consumer named consumer from stream named stream. - * - * @param js the pointer to the #jsCtx context. - * @param stream the name of the stream. - * @param consumer the name of the consumer. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_DeleteConsumer(jsCtx *js, const char *stream, const char *consumer, - jsOptions *opts, jsErrCode *errCode); - -/** \brief Pauses a consumer. - * - * Pauses the consumer named consumer on stream named stream. - * - * @param new_cpr if not NULL, will receive the response of the operation. - * @param js the pointer to the #jsCtx context. - * @param stream the name of the stream. - * @param consumer the name of the consumer. - * @param pauseUntil the time in nanoseconds since the Unix epoch to pause the consumer until. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ - -NATS_EXTERN natsStatus -js_PauseConsumer(jsConsumerPauseResponse **new_cpr, jsCtx *js, - const char *stream, const char *consumer, - uint64_t pauseUntil, jsOptions *opts, jsErrCode *errCode); - -/** \brief Destroys the PauseConsumer response object. - * - * Releases memory allocated for this object. - * - * @param cpr the pointer to the #jsConsumerPauseResponse object. - */ -NATS_EXTERN void -jsConsumerPauseResponse_Destroy(jsConsumerPauseResponse *cpr); - -/** \brief Destroys the consumer information object. - * - * Releases memory allocated for this consumer information object. - * - * @param ci the pointer to the #jsConsumerInfo object. - */ -NATS_EXTERN void -jsConsumerInfo_Destroy(jsConsumerInfo *ci); - -/** \brief Retrieves the list of all available consumers for a stream. - * - * Retrieves the list of all #jsConsumerInfo for a given stream. - * - * \warning The list should be destroyed when no longer used by - * calling #jsConsumerInfoList_Destroy. - * - * @param list the location where to store the pointer to the new #jsConsumerInfoList object. - * @param js the pointer to the #jsCtx context. - * @param stream the stream name whose consumer list is requested. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_Consumers(jsConsumerInfoList **list, jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode); - -/** \brief Destroys the consumer information list object. - * - * Releases memory allocated for this consumer information list. - * - * \warning All #jsConsumerInfo pointers contained in the list will - * be destroyed by this call. - * - * @param list the pointer to the #jsConsumerInfoList object. - */ -NATS_EXTERN void -jsConsumerInfoList_Destroy(jsConsumerInfoList *list); - -/** \brief Retrieves the list of all available consumer names for a stream. - * - * Retrieves the list of all consumer names for a given stream. - * - * \warning The list should be destroyed when no longer used by - * calling #jsConsumerNamesList_Destroy. - * - * @param list the location where to store the pointer to the new #jsConsumerNamesList object. - * @param js the pointer to the #jsCtx context. - * @param stream the stream name whose consumer list is requested. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_ConsumerNames(jsConsumerNamesList **list, jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode); - -/** \brief Destroys the consumer names list object. - * - * Releases memory allocated for this list of consumer names. - * - * \warning All string pointers contained in the list will - * be destroyed by this call. - * - * @param list the pointer to the #jsConsumerNamesList object. - */ -NATS_EXTERN void -jsConsumerNamesList_Destroy(jsConsumerNamesList *list); - -/** \brief Retrieves information about the JetStream usage from an account. - * - * Retrieves information about the JetStream usage from an account. - * - * \note The returned object should be destroyed using #jsAccountInfo_Destroy in order - * to free allocated memory. - * - * @param ai the location where to store the pointer to the new #jsAccountInfo object in - * response to the account information request. - * @param js the pointer to the #jsCtx context. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_GetAccountInfo(jsAccountInfo **ai, jsCtx *js, jsOptions *opts, jsErrCode *errCode); - -/** \brief Destroys the account information object. - * - * Releases memory allocated for this account information object. - * - * @param ai the pointer to the #jsAccountInfo object. - */ -NATS_EXTERN void -jsAccountInfo_Destroy(jsAccountInfo *ai); - -/** @} */ // end of jsAssetsGroup - -/** \defgroup jsPubGroup Publishing - * - * Publishing functions - * @{ - */ - -/** \brief Initializes a publish options structure. - * - * Use this before setting specific publish options and passing this - * configuration to the JetStream publish APIs. - * - * @param opts the pointer to the #jsPubOptions to initialize. - */ -NATS_EXTERN natsStatus -jsPubOptions_Init(jsPubOptions *opts); - -/** \brief Publishes data on a subject to JetStream. - * - * Publishes the data to the given subject to JetStream. - * - * See #js_PublishMsg for details. - * - * @param pubAck the location where to store the pub acknowledgment, or `NULL` if not needed. - * @param js the pointer to the #jsCtx object. - * @param subj the subject the data is sent to. - * @param data the data to be sent, can be `NULL`. - * @param dataLen the length of the data to be sent. - * @param opts the publish options, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_Publish(jsPubAck **pubAck, jsCtx *js, const char *subj, const void *data, int dataLen, - jsPubOptions *opts, jsErrCode *errCode); - -/** \brief Publishes a message to JetStream. - * - * Publishes the given message to JetStream. - * - * \note If you are not interested in inspecting the publish acknowledgment, you can - * pass `NULL`, but keep in mind that the publish acknowledgment is still sent by - * the server. - * \note The returned #jsPubAck object needs to be destroyed with #jsPubAck_Destroy - * when no longer needed. - * - * @see jsPubAck_Destroy - * - * @param pubAck the location where to store the pub acknowledgment, or `NULL` if not needed. - * @param js the pointer to the #jsCtx object. - * @param msg the pointer to the #natsMsg object to send. - * @param opts the publish options, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -js_PublishMsg(jsPubAck **pubAck, jsCtx *js, natsMsg *msg, - jsPubOptions *opts, jsErrCode *errCode); - -/** \brief Destroys the publish acknowledgment object. - * - * Releases memory allocated for this publish acknowledgment object. - * - * @param pubAck the #jsPubAck object to destroy. - */ -NATS_EXTERN void -jsPubAck_Destroy(jsPubAck *pubAck); - -/** \brief Publishes data to JetStream but does not wait for a #jsPubAck. - * - * See #js_PublishMsgAsync for details. - * - * @param js the pointer to the #jsCtx object. - * @param subj the subject the data is sent to. - * @param data the data to be sent, can be `NULL`. - * @param dataLen the length of the data to be sent. - * @param opts the publish options, possibly `NULL`. - */ -NATS_EXTERN natsStatus -js_PublishAsync(jsCtx *js, const char *subj, const void *data, int dataLen, - jsPubOptions *opts); - -/** \brief Publishes a message to JetStream but does not wait for a #jsPubAck. - * - * Publishes a message asynchronously to JetStream. User can call #js_PublishAsyncComplete - * to be notified when all publish acknowledgments for the pending publish calls - * have been received. - * - * \note If this call is successful, the library takes ownership of the message - * and will destroy it after the acknowledgment has been received, or will - * present it to the user through the #jsPubAckErrHandler callback. To prevent the user - * from accessing/destroying the message while in use by the library, this function - * requires a pointer to the pointer of the message so that it can be cleared. - * That way, the user should always call #natsMsg_Destroy, regardless of success or - * failure, since #natsMsg_Destroy will have no effect if the message pointer is `NULL`. - * - * @see js_PublishAsyncComplete - * @see jsPubAckErrHandler - * - * @param js the pointer to the #jsCtx object. - * @param msg the memory location where the pointer to the #natsMsg object is located. - * If the library takes ownership of the message, this location will be cleared so a following - * call to #natsMsg_Destroy would have no effect. - * @param opts the publish options, possibly `NULL`. - */ -NATS_EXTERN natsStatus -js_PublishMsgAsync(jsCtx *js, natsMsg **msg, jsPubOptions *opts); - -/** \brief Wait for all outstanding messages to be acknowledged. - * - * This call will block until the library has received acknowledgment for - * all outstanding published messages. - * - * To limit the wait, user can pass a #jsPubOptions with a `MaxWait` set to the - * maximum number of milliseconds that the call should block. - * - * @param js the pointer to the #jsCtx object. - * @param opts the publish options, possibly `NULL`. - */ -NATS_EXTERN natsStatus -js_PublishAsyncComplete(jsCtx *js, jsPubOptions *opts); - -/** \brief Returns the list of pending messages published asynchronously. - * - * This call returns the list of all asynchronously published messages - * for which no acknowledgment have been received yet. - * - * The user has now back ownership of the messages and can resend send if - * desired or simply destroy them. - * - * \note After this call returns, it is possible that acknowledgments arrive - * from the server but since they have been removed from the pending list, the - * acknowledgments will be discarded (no #jsPubAckErrHandler callback invoked). - * If the server did receive a particular message and the user in the meantime - * has resent that message, it would be a duplicate, so in order for the server - * to detect this duplicate, ensure that the stream's duplicate window setting - * is specified and a unique message ID was set when sending the message. - * - * \warning The user must call #natsMsgList_Destroy to release memory - * allocated by this call and destroy all pending messages still present in the list. - * - * \code{.unparsed} - * natsMsgList pending; - * - * s = js_PublishAsyncGetPendingList(&pending, js); - * if (s == NATS_OK) - * { - * int i; - * - * for (i=0; i - * It means that calling `natsSubscription_Fetch(&list, sub, 10, 5000)` may - * return after less than 5 seconds with only 3 messages. - * - * @param list the location to a #natsMsgList that will be filled by the result of this call. - * @param sub the pointer to the #natsSubscription object. - * @param batch the batch size, that is, the maximum number of messages to return. - * @param timeout the timeout (required) expressed in number of milliseconds. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -natsSubscription_Fetch(natsMsgList *list, natsSubscription *sub, int batch, int64_t timeout, - jsErrCode *errCode); - -/** \brief Initializes a fetch request options structure. - * - * Use this before setting specific fetch options and passing it to #natsSubscription_FetchRequest. - * - * @param request the pointer to the #jsFetchRequest object. - */ -NATS_EXTERN natsStatus -jsFetchRequest_Init(jsFetchRequest *request); - -/** \brief Fetches messages for a pull subscription with a complete request configuration - * - * Similar to #natsSubscription_Fetch but a full #jsFetchRequest configuration is provided - * for maximum control. - * - * Initialize the #jsFetchRequest structure using #jsFetchRequest_Init and then set - * the parameters desired, then invoke this function. - * - * @param list the location to a #natsMsgList that will be filled by the result of this call. - * @param sub the pointer to the #natsSubscription object. - * @param request the pointer to a #jsFetchRequest configuration. - */ -NATS_EXTERN natsStatus -natsSubscription_FetchRequest(natsMsgList *list, natsSubscription *sub, jsFetchRequest *request); - -/** \brief Returns the jsConsumerInfo associated with this subscription. - * - * Returns the #jsConsumerInfo associated with this subscription. - * - * @param ci the location where to store the pointer to the new #jsConsumerInfo object. - * @param sub the pointer to the #natsSubscription object. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -natsSubscription_GetConsumerInfo(jsConsumerInfo **ci, natsSubscription *sub, - jsOptions *opts, jsErrCode *errCode); - -/** \brief Returns the consumer sequence mismatch information. - * - * If `Heartbeat` is configured in #jsConsumerConfig object (or configured in an existing - * JetStream consumer), the server sends heartbeats to the client at the given interval. - * - * Those heartbeats contains information that allow the application to detect a mismatch - * between the server and client's view of the state of the consumer. - * - * If the library detects a sequence mismatch, the behavior is different depending on - * the type of subscription: - * - * * For asynchronous subscriptions: the error #NATS_MISMATCH is published to the error handler - * (see #natsOptions_SetErrorHandler). - * * For synchronous subscriptions: the next call to #natsSubscription_NextMsg() will - * return the error #NATS_MISMATCH (but will succeed afterwards). - * - * In both cases, the user should check what the mismatch is using this function and - * possibly recreate the consumer based on the provided information. - * - * \note For a valid JetStream subscription, this function will return #NATS_NOT_FOUND - * if no consumer sequence mismatch has been detected. - * - * @see jsConsumerSequenceMismatch - * - * @param csm the pointer location where to copy the consumer sequence mismatch information. - * @param sub the pointer to the #natsSubscription object. - */ -NATS_EXTERN natsStatus -natsSubscription_GetSequenceMismatch(jsConsumerSequenceMismatch *csm, natsSubscription *sub); - -/** @} */ // end of jsSubGroup - -/** \defgroup jsMsg Messages - * - * Function specific to JetStream messages - * @{ - */ - -/** \brief Returns metadata from this JetStream message. - * - * This works only for JetStream messages that have been received through - * a subscription callback or calling #natsSubscription_NextMsg. - * - * \note The user must destroy the returned object with #jsMsgMetaData_Destroy. - * - * \note This function will return an error for non JetStream messages. - * - * @param new_meta the location where to store the pointer to the newly created - * #jsMsgMetaData object. - * @param msg the pointer to the #natsMsg object, which should be a JetStream - * message received through a subscription callback or #natsSubscription_NextMsg. - */ -NATS_EXTERN natsStatus -natsMsg_GetMetaData(jsMsgMetaData **new_meta, natsMsg *msg); - -/** \brief Destroys the message metadata object. - * - * Releases memory allocated for this #jsMsgMetaData object. - * - * @param meta the pointer to the #jsMsgMetaData object. - */ -NATS_EXTERN void -jsMsgMetaData_Destroy(jsMsgMetaData *meta); - -/** \brief Acknowledges a message. - * - * This tells the server that the message was successfully processed and - * it can move on to the next message. - * - * @param msg the pointer to the #natsMsg object. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - */ -NATS_EXTERN natsStatus -natsMsg_Ack(natsMsg *msg, jsOptions *opts); - -/** \brief Acknowledges a message and wait for a confirmation. - * - * This is the synchronous version of #natsMsg_Ack. This indicates successful - * message processing, and waits for confirmation from the server that - * the acknowledgment has been processed. - * - * @param msg the pointer to the #natsMsg object. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - * @param errCode the location where to store the JetStream specific error code, or `NULL` - * if not needed. - */ -NATS_EXTERN natsStatus -natsMsg_AckSync(natsMsg *msg, jsOptions *opts, jsErrCode *errCode); - -/** \brief Negatively acknowledges a message. - * - * This tells the server to redeliver the message. You can configure the - * number of redeliveries by passing `MaxDeliver` when you subscribe. - * - * The default is infinite redeliveries. - * - * @param msg the pointer to the #natsMsg object. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - */ -NATS_EXTERN natsStatus -natsMsg_Nak(natsMsg *msg, jsOptions *opts); - -/** \brief Negatively acknowledges a message. - * - * This tells the server to redeliver the message after the given `delay` - * duration expressed in milliseconds. You can configure the number of - * redeliveries by passing `MaxDeliver` when you subscribe. - * - * The default is infinite redeliveries. - * - * @param msg the pointer to the #natsMsg object. - * @param delay the amount of time before the redelivery expressed in milliseconds. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - */ -NATS_EXTERN natsStatus -natsMsg_NakWithDelay(natsMsg *msg, int64_t delay, jsOptions *opts); - -/** \brief Resets redelivery timer on the server. - * - * This tells the server that this message is being worked on. It resets - * the redelivery timer on the server. - * - * @param msg the pointer to the #natsMsg object. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - */ -NATS_EXTERN natsStatus -natsMsg_InProgress(natsMsg *msg, jsOptions *opts); - -/** \brief Abandon this message. - * - * This tells the server to not redeliver this message, regardless of - * the value `MaxDeliver`. - * - * @param msg the pointer to the #natsMsg object. - * @param opts the pointer to the #jsOptions object, possibly `NULL`. - */ -NATS_EXTERN natsStatus -natsMsg_Term(natsMsg *msg, jsOptions *opts); - -/** \brief Returns the sequence number of this JetStream message. - * - * Returns the sequence number of this JetStream message, or `0` if `msg` is `NULL` - * or not a JetStream message. - * - * \note This applies to JetStream messages retrieved with #js_GetMsg or #js_GetLastMsg. - * - * @param msg the pointer to the #natsMsg object. - */ -NATS_EXTERN uint64_t -natsMsg_GetSequence(natsMsg *msg); - -/** \brief Returns the timestamp (in UTC) of this JetStream message. - * - * Returns the timestamp (in UTC) of this JetStream message, or `0` if `msg` is `NULL` - * or not a JetStream message. - * - * \note This applies to JetStream messages retrieved with #js_GetMsg or #js_GetLastMsg. - * - * @param msg the pointer to the #natsMsg object. - */ -NATS_EXTERN int64_t -natsMsg_GetTime(natsMsg *msg); - -/** @} */ // end of jsMsg - -/** @} */ // end of jsGroup - -/** \defgroup kvGroup KeyValue store - * - * A KeyValue store is a materialized view of JetStream. - * - * \warning EXPERIMENTAL FEATURE! We reserve the right to change the API without - * necessarily bumping the major version of the library. - * - * @{ - */ - -/** \defgroup kvGroupMgt KeyValue store management - * - * These functions allow to create, get or delete a KeyValue store. - * - * \warning EXPERIMENTAL FEATURE! We reserve the right to change the API without - * necessarily bumping the major version of the library. - * - * @{ - */ - -/** \brief Initializes a KeyValue configuration structure. - * - * Use this before setting specific #kvConfig options and passing it to #js_CreateKeyValue. - * - * @see js_CreateKeyValue - * - * @param cfg the pointer to the stack variable #kvConfig to initialize. - */ -NATS_EXTERN natsStatus -kvConfig_Init(kvConfig *cfg); - -/** \brief Creates a KeyValue store with a given configuration. - * - * Creates a KeyValue store with a given configuration. - * - * Bucket names are restricted to this set of characters: `A-Z`, `a-z`, `0-9`, `_` and `-`. - * - * \note The return #kvStore object needs to be destroyed using #kvStore_Destroy when - * no longer needed to free allocated memory. This is different from deleting a KeyValue store - * from the server using the #js_DeleteKeyValue API. - * - * @param new_kv the location where to store the newly created #kvStore object. - * @param js the pointer to the #jsCtx object. - * @param cfg the pointer to the #kvConfig configuration information used to create the #kvStore object. - */ -NATS_EXTERN natsStatus -js_CreateKeyValue(kvStore **new_kv, jsCtx *js, kvConfig *cfg); - -/** \brief Looks-up and binds to an existing KeyValue store. - * - * This call is when the user wants to use an existing KeyValue store. - * If the store does not already exists, an error is returned. - * - * Bucket names are restricted to this set of characters: `A-Z`, `a-z`, `0-9`, `_` and `-`. - * - * \note The return #kvStore object needs to be destroyed using #kvStore_Destroy when - * no longer needed to free allocated memory. This is different from deleting a KeyValue store - * from the server using the #js_DeleteKeyValue API. - * - * @param new_kv the location where to store the newly created #kvStore object. - * @param js the pointer to the #jsCtx object. - * @param bucket the name of the bucket of the existing KeyValue store. - */ -NATS_EXTERN natsStatus -js_KeyValue(kvStore **new_kv, jsCtx *js, const char *bucket); - -/** \brief Deletes a KeyValue store. - * - * This will delete the KeyValue store with the `bucket` name. - * - * Bucket names are restricted to this set of characters: `A-Z`, `a-z`, `0-9`, `_` and `-`. - * - * @param js the pointer to the #jsCtx object. - * @param bucket the name of the bucket of the existing KeyValue store. - */ -NATS_EXTERN natsStatus -js_DeleteKeyValue(jsCtx *js, const char *bucket); - -/** \brief Destroys a KeyValue store object. - * - * This will simply free memory resources in the library for this #kvStore, - * but does not delete the KeyValue store in the server. - * - * @param kv the pointer to the #kvStore object. - */ -NATS_EXTERN void -kvStore_Destroy(kvStore *kv); - -/** @} */ // end of kvGroupMgt - -/** \defgroup kvEntry KeyValue store entries - * - * These functions allow to inspect a the value, or entry, of a given key. - * - * \warning EXPERIMENTAL FEATURE! We reserve the right to change the API without - * necessarily bumping the major version of the library. - * - * @{ - */ - -/** \brief Returns the name of the bucket the data was loaded from. - * - * Returns the name of the bucket the data was loaded from, or `NULL` if `e` itself is `NULL`. - * - * @param e the pointer to the #kvEntry object. - */ -NATS_EXTERN const char* -kvEntry_Bucket(kvEntry *e); - -/** \brief Returns the name of the key that was retrieved. - * - * Returns the name of the key that was retrieved, or `NULL` if `e` itself is `NULL`. - * - * @param e the pointer to the #kvEntry object. - */ -NATS_EXTERN const char* -kvEntry_Key(kvEntry *e); - -/** \brief Returns the value for this key. - * - * Returns the value for this key, or `NULL` if `e` itself is `NULL`. - * - * @param e the pointer to the #kvEntry object. - */ -NATS_EXTERN const void* -kvEntry_Value(kvEntry *e); - -/** \brief Returns the value length for this key. - * - * Returns the value length for this key, or `-1` if `e` itself is `NULL`. - * - * @param e the pointer to the #kvEntry object. - */ -NATS_EXTERN int -kvEntry_ValueLen(kvEntry *e); - -/** \brief Returns the value, as a string, for this key. - * - * If the value is an actual string, this call will return a NULL terminating string (`const char*`), - * or `NULL` if `e` itself is `NULL`. - * - * @param e the pointer to the #kvEntry object. - */ -NATS_EXTERN const char* -kvEntry_ValueString(kvEntry *e); - -/** \brief Returns the unique sequence for this value. - * - * Returns the unique sequence for this value, or `0` if `e` itself is `NULL`. - * - * @param e the pointer to the #kvEntry object. - */ -NATS_EXTERN uint64_t -kvEntry_Revision(kvEntry *e); - -/** \brief Returns the time (in UTC) the data was put in the bucket. - * - * Returns the time (in UTC) the data was put in the bucket, or `0` if `e` itself is `NULL`. - * - * @param e the pointer to the #kvEntry object. - */ -NATS_EXTERN int64_t -kvEntry_Created(kvEntry *e); - -/** \brief Returns the distance from the latest value. - * - * Returns the distance from the latest value, or `0` if `e` itself is `NULL`. - * - * If history is enabled this is effectively the index of the historical value, - * 0 for latest, 1 for most recent etc... - * - * @param e the pointer to the #kvEntry object. - */ -NATS_EXTERN uint64_t -kvEntry_Delta(kvEntry *e); - -/** \brief Returns the type of operation of this value. - * - * Returns the type of operation of this value. - * - * @see kvOperation - * - * @param e the pointer to the #kvEntry object. - */ -NATS_EXTERN kvOperation -kvEntry_Operation(kvEntry *e); - -/** \brief Destroys the KeyValue entry object. - * - * Releases memory allocated for this #kvEntry object. - * - * @param e the pointer to the #kvEntry object. - */ -NATS_EXTERN void -kvEntry_Destroy(kvEntry *e); - -/** @} */ // end of kvEntry - -/** \brief Returns the latest entry for the key. - * - * Returns the latest entry for the key. - * - * \note The entry should be destroyed to release memory using #kvEntry_Destroy. - * - * @param new_entry the location where to store the pointer to the entry associated with the `key`. - * @param kv the pointer to the #kvStore object. - * @param key the name of the key. - */ -NATS_EXTERN natsStatus -kvStore_Get(kvEntry **new_entry, kvStore *kv, const char *key); - -/** \brief Returns the entry at the specific revision for the key. - * - * Returns the entry at the specific revision for the key, or #NATS_NOT_FOUND if there is no - * entry for that key and revision. - * - * \note The entry should be destroyed to release memory using #kvEntry_Destroy. - * - * @param new_entry the location where to store the pointer to the entry associated with the `key`. - * @param kv the pointer to the #kvStore object. - * @param key the name of the key. - * @param revision the revision of the entry (must be > 0). - */ -NATS_EXTERN natsStatus -kvStore_GetRevision(kvEntry **new_entry, kvStore *kv, const char *key, uint64_t revision); - -/** \brief Places the new value for the key into the store. - * - * Places the new value for the key into the store. - * - * @param rev the location where to store the revision of this value, or `NULL` if the stream information is not needed. - * @param kv the pointer to the #kvStore object. - * @param key the name of the key. - * @param data the pointer to the data in memory. - * @param len the number of bytes to copy from the data's memory location. - */ -NATS_EXTERN natsStatus -kvStore_Put(uint64_t *rev, kvStore *kv, const char *key, const void *data, int len); - -/** \brief Places the new value (as a string) for the key into the store. - * - * Places the new value, as a string, for the key into the store. - * - * \note This is equivalent of calling #kvStore_Put with `(int) strlen(data)`. - * - * \warning The NULL terminating character is not included in the number of bytes stored in the KeyValue store. - * - * @param rev the location where to store the revision of this value, or `NULL` if the stream information is not needed. - * @param kv the pointer to the #kvStore object. - * @param key the name of the key. - * @param data the pointer to the string to store. - */ -NATS_EXTERN natsStatus -kvStore_PutString(uint64_t *rev, kvStore *kv, const char *key, const char *data); - -/** \brief Places the value for the key into the store if and only if the key does not exist. - * - * Places the value for the key into the store if and only if the key does not exist. - * - * @param rev the location where to store the revision of this value, or `NULL` if the stream information is not needed. - * @param kv the pointer to the #kvStore object. - * @param key the name of the key. - * @param data the pointer to the data in memory. - * @param len the number of bytes to copy from the data's memory location. - */ -NATS_EXTERN natsStatus -kvStore_Create(uint64_t *rev, kvStore *kv, const char *key, const void *data, int len); - -/** \brief Places the value (as a string) for the key into the store if and only if the key does not exist. - * - * Places the value (as a string) for the key into the store if and only if the key does not exist. - * - * \note This is equivalent of calling #kvStore_Create with `(int) strlen(data)`. - * - * \warning The NULL terminating character is not included in the number of bytes stored in the KeyValue store. - * - * @param rev the location where to store the revision of this value, or `NULL` if the stream information is not needed. - * @param kv the pointer to the #kvStore object. - * @param key the name of the key. - * @param data the pointer to the string. - */ -NATS_EXTERN natsStatus -kvStore_CreateString(uint64_t *rev, kvStore *kv, const char *key, const char *data); - -/** \brief Updates the value for the key into the store if and only if the latest revision matches. - * - * Updates the value for the key into the store if and only if the latest revision matches. - * - * @param rev the location where to store the revision of this value, or `NULL` if the stream information is not needed. - * @param kv the pointer to the #kvStore object. - * @param key the name of the key. - * @param data the pointer to the data in memory. - * @param len the number of bytes to copy from the data's memory location. - * @param last the expected latest revision prior to the update. - */ -NATS_EXTERN natsStatus -kvStore_Update(uint64_t *rev, kvStore *kv, const char *key, const void *data, int len, uint64_t last); - -/** \brief Updates the value (as a string) for the key into the store if and only if the latest revision matches. - * - * Updates the value (as a string) for the key into the store if and only if the latest revision matches. - * - * \note This is equivalent of calling #kvStore_Update with `(int) strlen(data)`. - * - * \warning The NULL terminating character is not included in the number of bytes stored in the KeyValue store. - * - * @param rev the location where to store the revision of this value, or `NULL` if the stream information is not needed. - * @param kv the pointer to the #kvStore object. - * @param key the name of the key. - * @param data the pointer to the string. - * @param last the expected latest revision prior to the update. - */ -NATS_EXTERN natsStatus -kvStore_UpdateString(uint64_t *rev, kvStore *kv, const char *key, const char *data, uint64_t last); - -/** \brief Deletes a key by placing a delete marker and leaving all revisions. - * - * Deletes a key by placing a delete marker and leaving all revisions. - * - * @param kv the pointer to the #kvStore object. - * @param key the name of the key. - */ -NATS_EXTERN natsStatus -kvStore_Delete(kvStore *kv, const char *key); - -/** \brief Deletes a key by placing a purge marker and removing all revisions. - * - * Deletes a key by placing a purge marker and removing all revisions. - * - * @param kv the pointer to the #kvStore object. - * @param key the name of the key. - * @param opts the pointer to the #kvPurgeOptions, possibly `NULL`. - */ -NATS_EXTERN natsStatus -kvStore_Purge(kvStore *kv, const char *key, kvPurgeOptions *opts); - -/** \brief Initializes a KeyValue watcher options structure. - * - * Use this before setting specific watcher options and passing it - * to #kvStore_Watch. - * - * @param opts the pointer to the #kvWatchOptions to initialize. - */ -NATS_EXTERN natsStatus -kvWatchOptions_Init(kvWatchOptions *opts); - -/** \brief Initializes a KeyValue purge options structure. - * - * Use this before setting specific purge options and passing it - * to #kvStore_Purge or #kvStore_PurgeDeletes. - * - * @param opts the pointer to the #kvPurgeOptions to initialize. - */ -NATS_EXTERN natsStatus -kvPurgeOptions_Init(kvPurgeOptions *opts); - -/** \brief Purge and removes delete markers. - * - * Removes data and delete markers, but may keep the markers that are considered - * more recent than a certain threshold (default is 30 minutes). - * - * This is a maintenance option if there is a larger buildup of delete markers. - * - * \note Use #kvPurgeOptions.Timeout to specify how long to wait (in milliseconds) - * in gathering all keys that have purge markers. This function will still - * purge some of the keys and return #NATS_TIMEOUT to indicate that it may not - * have deleted them all. - * - * @see kvPurgeOptions_Init - * - * @param kv the pointer to the #kvStore object. - * @param opts the pointer to the #kvPurgeOptions, possibly `NULL`. - */ -NATS_EXTERN natsStatus -kvStore_PurgeDeletes(kvStore *kv, kvPurgeOptions *opts); - -/** \brief Returns a watcher for any updates to keys that match the `keys` argument. - * - * Returns a watcher for any updates to keys that match the `keys` argument, which - * could include wildcard. - * - * A `NULL` entry will be posted when the watcher has received all initial values. - * - * Call #kvWatcher_Next to get the next #kvEntry. - * - * \note The watcher should be destroyed to release memory using #kvWatcher_Destroy. - * - * @param new_watcher the location where to store the pointer to the new #kvWatcher object. - * @param kv the pointer to the #kvStore object. - * @param keys the keys (wildcard possible) to create the watcher for. - * @param opts the watcher options, possibly `NULL`. - */ -NATS_EXTERN natsStatus -kvStore_Watch(kvWatcher **new_watcher, kvStore *kv, const char *keys, kvWatchOptions *opts); - -/** \brief Returns a watcher for any updates to keys that match one of the - * `keys` argument. - * - * Returns a watcher for any updates to keys that match the one of `keys` - * argument, which could include wildcards. - * - * A `NULL` entry will be posted when the watcher has received all initial - * values. - * - * Call #kvWatcher_Next to get the next #kvEntry. - * - * \note The watcher should be destroyed to release memory using - * #kvWatcher_Destroy. - * - * @param new_watcher the location where to store the pointer to the new - * #kvWatcher object. - * @param kv the pointer to the #kvStore object. - * @param keys the keys (wildcard possible) to create the watcher for. - * @param numKeys the number of keys in the `keys` array. - * @param opts the watcher options, possibly `NULL`. - */ -NATS_EXTERN natsStatus -kvStore_WatchMulti(kvWatcher **new_watcher, kvStore *kv, const char **keys, int numKeys, kvWatchOptions *opts); - -/** \brief Returns a watcher for any updates to any keys of the KeyValue store bucket. - * - * Returns a watcher for any updates to any keys of the KeyValue store bucket. - * - * A `NULL` entry will be posted when the watcher has received all initial values. - * - * Call #kvWatcher_Next to get the next #kvEntry. - * - * \note The watcher should be destroyed to release memory using #kvWatcher_Destroy. - * - * @param new_watcher the location where to store the pointer to the new #kvWatcher object. - * @param kv the pointer to the #kvStore object. - * @param opts the watcher options, possibly `NULL`. - */ -NATS_EXTERN natsStatus -kvStore_WatchAll(kvWatcher **new_watcher, kvStore *kv, kvWatchOptions *opts); - -/** \brief Returns all keys in the bucket. - * - * Returns all keys in the bucket. - * - * \note Use #kvWatchOptions.Timeout to specify how long to wait (in milliseconds) - * to gather all keys for this bucket. If the deadline is reached, this function - * will return #NATS_TIMEOUT and no keys. - * - * \warning The user should call #kvKeysList_Destroy to release memory allocated - * for the entries list. - * - * @see kvWatchOptions_Init - * @see kvKeysList_Destroy - * - * @param list list the pointer to a #kvKeysList that will be initialized and filled with resulting key strings. - * @param kv the pointer to the #kvStore object. - * @param opts the history options, possibly `NULL`. - */ -NATS_EXTERN natsStatus -kvStore_Keys(kvKeysList *list, kvStore *kv, kvWatchOptions *opts); - -/** \brief Destroys this list of KeyValue store key strings. - * - * This function iterates through the list of all key strings and free them. - * It then frees the array that was allocated to hold pointers to those keys. - * - * \note The #kvKeysList object itself is not freed since it is expected that - * users will pass a pointer to a stack object. Should the user create its own - * object, it will be the user responsibility to free this object. - * - * @param list the #kvKeysList list of key strings to destroy. - */ -NATS_EXTERN void -kvKeysList_Destroy(kvKeysList *list); - -/** \brief Returns all historical entries for the key. - * - * Returns all historical entries for the key - * - * Use the options to alter the behavior. For instance, if delete markers - * are not desired, option #kvWatchOptions.IgnoreDeletes should be specified. - * - * \note Use #kvWatchOptions.Timeout to specify how long to wait (in milliseconds) - * to gather all entries for this key. If the deadline is reached, this function - * will return #NATS_TIMEOUT and no entries. - * - * \warning The user should call #kvEntryList_Destroy to release memory allocated - * for the entries list. - * - * @see kvWatchOptions_Init - * @see kvEntryList_Destroy - * - * @param list the pointer to a #kvEntryList that will be initialized and filled with resulting entries. - * @param kv the pointer to the #kvStore object. - * @param key the key for which the history is requested. - * @param opts the history options, possibly `NULL`. - */ -NATS_EXTERN natsStatus -kvStore_History(kvEntryList *list, kvStore *kv, const char *key, kvWatchOptions *opts); - -/** \brief Destroys this list of KeyValue store entries. - * - * This function iterates through the list of all entries and call #kvEntry_Destroy. - * It then frees the array that was allocated to hold pointers to those entries. - * - * \note The #kvEntryList object itself is not freed since it is expected that - * users will pass a pointer to a stack object. Should the user create its own - * object, it will be the user responsibility to free this object. - * - * @param list the #kvEntryList list of #kvEntry objects to destroy. - */ -NATS_EXTERN void -kvEntryList_Destroy(kvEntryList *list); - -/** \brief Returns the bucket name of this KeyValue store object. - * - * Returns the bucket name of this KeyValue store object, or `NULL` if - * `kv` itself is NULL. - * - * \warning Do not free the string returned by this function. - * - * @param kv the pointer to the #kvStore object. - */ -NATS_EXTERN const char* -kvStore_Bucket(kvStore *kv); - -// PurgeDeletes - -/** \brief Returns the status and configuration of a bucket. - * - * Returns the status and configuration of a bucket. - * - * \note The status should be destroyed to release memory using #kvStatus_Destroy. - * - * @param new_status the location where to store the status of this KeyValue store. - * @param kv the pointer to the #kvStore object. - */ -NATS_EXTERN natsStatus -kvStore_Status(kvStatus **new_status, kvStore *kv); - -/** \defgroup kvWatcher KeyValue store watcher - * - * These functions allow to receive updates for key(s) on a given bucket. - * - * \warning EXPERIMENTAL FEATURE! We reserve the right to change the API without - * necessarily bumping the major version of the library. - * - * @{ - */ - -/** \brief Returns the next entry for this watcher. - * - * Returns the next entry for this watcher. The entry may be `NULL` - * (with #NATS_OK status) to indicate that the initial state has - * been retrieved. - * - * If a thread is waiting on this call, it can be canceled with a call to - * #kvWatcher_Stop. - * - * \note The entry should be destroyed to release memory using #kvEntry_Destroy. - * - * @param new_entry the location where to store the pointer to the #kvEntry object. - * @param w the pointer to the #kvWatcher object. - * @param timeout how long to wait (in milliseconds) for the next entry. - */ -NATS_EXTERN natsStatus -kvWatcher_Next(kvEntry **new_entry, kvWatcher *w, int64_t timeout); - -/** \brief Stops the watcher. - * - * Stops the watcher. Stopping a stopped watcher returns #NATS_OK. - * - * After this call, new and existing calls to #kvWatcher_Next (that are waiting - * for an update) will return with #NATS_ILLEGAL_STATE. - * - * @param w the pointer to the #kvWatcher object. - */ -NATS_EXTERN natsStatus -kvWatcher_Stop(kvWatcher *w); - -/** \brief Destroys the KeyValue watcher object. - * - * Releases memory allocated for this #kvWatcher object. - * - * @param w the pointer to the #kvWatcher object. - */ -NATS_EXTERN void -kvWatcher_Destroy(kvWatcher *w); - -/** @} */ // end of kvWatcher - -/** \defgroup kvStatus KeyValue store status - * - * These functions allow to inspect the status of a bucket. - * - * \warning EXPERIMENTAL FEATURE! We reserve the right to change the API without - * necessarily bumping the major version of the library. - * - * @{ - */ - -/** \brief Returns the bucket name. - * - * Returns the bucket name, or `NULL` if `sts` itself is `NULL`. - * - * @param sts the pointer to the #kvStatus object. - */ -NATS_EXTERN const char* -kvStatus_Bucket(kvStatus *sts); - -/** \brief Returns how many messages are in the bucket, including historical values. - * - * Returns how many messages are in the bucket, including historical values, or `0` if `sts` itself is `NULL`. - * - * @param sts the pointer to the #kvStatus object. - */ -NATS_EXTERN uint64_t -kvStatus_Values(kvStatus *sts); - -/** \brief Returns the configured history kept per key. - * - * Returns the configured history kept per key, or `0` if `sts` itself is `NULL`. - * - * @param sts the pointer to the #kvStatus object. - */ -NATS_EXTERN int64_t -kvStatus_History(kvStatus *sts); - -/** \brief Returns how long the bucket keeps values for. - * - * Returns how long the bucket keeps values for, or `0` if `sts` itself is `NULL`. - * - * @param sts the pointer to the #kvStatus object. - */ -NATS_EXTERN int64_t -kvStatus_TTL(kvStatus *sts); - -/** \brief Returns the number of replicas to keep for a bucket. - * - * Returns the number of replicas to keep for a bucket, or `0` if `sts` itself is `NULL`. - * - * @param sts the pointer to the #kvStatus object. - */ -NATS_EXTERN int64_t -kvStatus_Replicas(kvStatus *sts); - -/** \brief Returns the size (in bytes) of this bucket. - * - * Returns the size (in bytes) of this bucket, or `0` if `sts` itself is `NULL`. - * - * @param sts the pointer to the #kvStatus object. - */ -NATS_EXTERN uint64_t -kvStatus_Bytes(kvStatus *sts); - -/** \brief Destroys the KeyValue status object. - * - * Releases memory allocated for this #kvStatus object. - * - * @param sts the pointer to the #kvStatus object. - */ -NATS_EXTERN void -kvStatus_Destroy(kvStatus *sts); - -/** @} */ // end of kvStatus - -/** @} */ // end of kvGroup - -/** @} */ // end of funcGroup - -// -// Microservices. -// - -/** \defgroup microGroup EXPERIMENTAL - Microservices - * - * \warning EXPERIMENTAL FEATURE! We reserve the right to change the API without - * necessarily bumping the major version of the library. - * - * ### NATS Microservices. - * - * Microservices can expose one or more request-response endpoints that process - * incoming NATS messages. - * - * Microservices are created by calling micro_AddService, and configured by - * passing a microServiceConfig to it. Many microservices can share a single - * connection to a NATS server. - * - * Once created, a microservice will subscribe to all endpoints' subjects and - * associate them with the configured handlers. It will also subscribe to and - * service monitoring subjects for service-specific pings, metadata, and - * statistics requests. The endpoint subscriptions are created with a queue - * group, so that incoming requests are automatically load-balanced across all - * running instances of a microservice. The monitoring subscriptions are not - * groupped, each service instance receives and responds to all monitoring - * requests. - * - * Once created, the microservice is asyncronous, message handlers and other - * callbacks will be invoked in separate threads. No further action is needed. - * Your program can use microService_Stop, microService_IsStopped to control the - * execution of the service. - * - * @{ - */ - -/** \defgroup microTypes Types - * - * Microservice types. - * - * @{ - */ - -/** - * @brief The Microservice client. - * - * Initialize with #micro_NewClient and destroy with #microClient_Destroy. - * - * @see micro_NewClient, microClient_Destroy - */ -typedef struct micro_client_s microClient; - -/** - * @brief The Microservice configuration object. For forward compatibility only. - */ -typedef struct __for_forward_compatibility_only microClientConfig; - -/** - * @brief `microEndpoint` represents a microservice endpoint. - * - * The only place where this struct is used by the user is in callbacks, to - * identify which endpoint was called, or caused an error. - * - * @see microRequestHandler, microErrorHandler, microServiceConfig, - * microEndpointConfig - */ -typedef struct micro_endpoint_s microEndpoint; - -/** - * @brief The Microservice endpoint configuration object. - * - * @see micro_endpoint_config_s for descriptions of the fields, - * micro_service_config_s, microServiceConfig, microService_AddEndpoint, - * microGroup_AddEndpoint - */ -typedef struct micro_endpoint_config_s microEndpointConfig; - -/** - * @brief static information about an endpoint. - * - * microEndpointInfo is returned by microService_GetInfo function, as part of microServiceInfo. It - * is also accessible by sending a `$SRV.INFO.[.]` request to - * the service. See micro_endpoint_info_s for descriptions of the fields. - * - * @see micro_endpoint_info_s, micro_service_info_s, microService_GetInfo - */ -typedef struct micro_endpoint_info_s microEndpointInfo; - -/** - * @brief The Microservice endpoint-level stats struct. - * - * Returned as part of microEndpointStats. See micro_endpoint_stats_s for - * descriptions of the fields. - * - * @see micro_endpoint_stats_s, microServiceStats, microService_GetStats - */ -typedef struct micro_endpoint_stats_s microEndpointStats; - -/** - * @brief the Microservice error object. - * - * This error type is returned by most microservice functions. You can create - * your own custom errors by using #micro_Errorf and wrap existing errors using - * #microError_Wrapf. Errors are heap-allocated and must be freed with either - * #microError_Destroy or by passing it into #microRequest_Respond. - * - * There are no public fields in this struct, use #microError_Code, - * #microError_Status, and #microError_String to get more information about the - * error. - */ -typedef struct micro_error_s microError; - -/** - * @brief a collection of endpoints and other groups, with a - * common prefix to their subjects and names. - * - * It has no other purpose than - * convenience, for organizing endpoint subject space. - */ -typedef struct micro_group_s microGroup; - -/** - * @brief a request received by a microservice endpoint. - * - * @see micro_request_s for descriptions of the fields. - */ -typedef struct micro_request_s microRequest; - -/** - * @brief the main object for a configured microservice. - * - * It can be created with #micro_AddService, and configured by passing a - * microServiceConfig to it. Once no longer needed, a microservice should be - * destroyed with microService_Destroy. - * - * @see micro_AddService, microServiceConfig, microEndpointConfig, - * microService_Destroy, microService_Stop, microService_IsStopped, - * microService_Run - */ -typedef struct micro_service_s microService; - -/** - * @brief The microservice configuration object. - * - * The service is created with a clone of the config and all of its values, so - * the original can be freed or modified after calling #micro_AddService. See - * micro_service_config_s for descriptions of the fields. - * - * @see micro_service_config_s - */ -typedef struct micro_service_config_s microServiceConfig; - -/** - * @brief Information about a running microservice. - * - * microServiceInfo is the struct returned by microService_GetInfo function. It - * is also accessible by sending a `$SRV.INFO.[.]` request to - * the service. See micro_service_info_s for descriptions of the fields. - * - * @see micro_service_info_s, microService_GetInfo - */ -typedef struct micro_service_info_s microServiceInfo; - -/** - * @brief The Microservice service-level stats struct. - * - * @see micro_service_stats_s for descriptions of the fields, - * microService_GetStats - */ -typedef struct micro_service_stats_s microServiceStats; - - -extern NATS_EXTERN microError *micro_ErrorOutOfMemory; -extern NATS_EXTERN microError *micro_ErrorInvalidArg; - -/** @} */ // end of microTypes - -/** \defgroup microCallbacks Callbacks - * - * Microservice callbacks. - * @{ - */ - -/** - * @brief Callback type for request processing. - * - * This is the callback that one provides when creating a microservice endpoint. - * The library will invoke this callback for each message arriving to the - * specified subject. - * - * @param req The request object, containing the message and other relevant - * references. - * - * @see microEndpointConfig, micro_endpoint_config_s. - */ -typedef microError *(*microRequestHandler)(microRequest *req); - -/** - * @brief Callback type for async error notifications. - * - * If specified in microServiceConfig, this callback is invoked for internal - * errors (e.g. message delivery failures) related to a microservice. If the - * error is associated with an endpoint, the ep parameter points at the - * endpoint. However, this handler may be invoked for errors happening in - * monitoring subjects, in which case ep is NULL. - * - * The error handler is invoked asynchronously, in a separate theread. - * - * The error handler is not invoked for microservice-level errors that are sent - * back to the client as responses. Note that the error counts in - * microEndpointStats include both internal and service-level errors. - * - * @param m The microservice object. - * @param ep The endpoint object, or NULL if the error is not associated with an - * endpoint. - * @param s The NATS status for the error. - * - * @see microServiceConfig, micro_service_config_s. - */ -typedef void (*microErrorHandler)(microService *m, microEndpoint *ep, natsStatus s); - -/** - * @brief Callback type for `Done` (service stopped) notifications. - * - * If specified in microServiceConfig, this callback is invoked right after the - * service stops. In the C client, this callback is invoked directly from the - * microService_Stop function, in whatever thread is executing it. - * - * @param m The microservice object. - * - * @see microServiceConfig, micro_service_config_s. - */ -typedef void (*microDoneHandler)(microService *m); - -/** @} */ // end of microCallbacks - -/** \defgroup microStructs Public structs - * - * Microservice public structs. - * - * @{ - */ - -/** - * The Microservice endpoint configuration object. - */ -struct micro_endpoint_config_s -{ - /** - * @brief The name of the endpoint. - * - * Used in the service stats to list endpoints by name. Must not be empty. - */ - const char *Name; - - /** - * @brief The NATS subject the endpoint will listen on. - * - * Wildcards are allowed. If `Subject` is empty, it attempts to default to - * `Name`, provided it is a valid subject. - * - * For endpoints added to a group, the subject is automatically prefixed - * with the group's prefix. - */ - const char *Subject; - - /** - * @briefMetadata for the endpoint, a JSON-encoded user-provided object, - * e.g. `{"key":"value"}` - */ - natsMetadata Metadata; - - /** - * @brief The request handler for the endpoint. - */ - microRequestHandler Handler; - - /** - * @brief A user-provided pointer to store with the endpoint - * (state/closure). - */ - void *State; -}; - -/** - * microEndpointInfo is the struct for the endpoint's static metadata. - */ -struct micro_endpoint_info_s -{ - /** - * @brief The name of the service. - */ - const char *Name; - - /** - * @brief The semantic version of the service. - */ - const char *Subject; - - /** - * @briefMetadata for the endpoint, a JSON-encoded user-provided object, - * e.g. `{"key":"value"}` - */ - natsMetadata Metadata; -}; - -/** - * The Microservice endpoint stats struct. - */ -struct micro_endpoint_stats_s -{ - const char *Name; - const char *Subject; - - /** - * @brief The number of requests received by the endpoint. - */ - int64_t NumRequests; - - /** - * @brief The number of errors, service-level and internal, associated with - * the endpoint. - */ - int64_t NumErrors; - - /** - * @brief total request processing time (the seconds part). - */ - int64_t ProcessingTimeSeconds; - - /** - * @brief total request processing time (the nanoseconds part). - */ - int64_t ProcessingTimeNanoseconds; - - /** - * @brief average request processing time, in ns. - */ - int64_t AverageProcessingTimeNanoseconds; - - /** - * @brief a copy of the last error message. - */ - char LastErrorString[2048]; -}; - -/** - * @brief The Microservice top-level configuration object. - * - * The service is created with a clone of the config and all of its values, so - * the original can be freed or modified after calling micro_AddService. - */ -struct micro_service_config_s -{ - /** - * @brief The name of the service. - * - * It can be used to compose monitoring messages specific to this service. - */ - const char *Name; - - /** - * @brief The (semantic) version of the service. - */ - const char *Version; - - /** - * @brief The description of the service. - */ - const char *Description; - - /** - * @brief Metadata for the service, a JSON-encoded user-provided object, e.g. `{"key":"value"}` - */ - natsMetadata Metadata; - - /** - * @brief The "main" (aka default) endpoint configuration. - * - * It is the default in that it does not require calling - * microService_AddEndpoint, it is added automatically when creating the - * service. - */ - microEndpointConfig *Endpoint; - - /** - * @brief A custom stats handler. - * - * It will be called to output the service's stats. It replaces the default - * stats handler but can pull the service stats using microService_GetStats - * function, then marshal them itself, as appropriate. - */ - microRequestHandler StatsHandler; - - /** - * @brief An error notification handler. - * - * It will be called asynchonously upon internal errors. It does not get - * called for application-level errors, successfully sent out by the - * microservice. - */ - microErrorHandler ErrHandler; - - /** - * @brief A callback handler for handling the final cleanup `Done` event, - * right before the service is destroyed. - * - * It will be called directly from #microService_Stop method, so it may be - * executed in any of the user threads or in the async callback thread if - * the service stops itself on connection closed or an error event. - */ - microDoneHandler DoneHandler; - - /** - * @brief A user-provided pointer to state data. - * - * A closure that is accessible from the request, stats, and internal event - * handlers. Please note that handlers are invoked on separate threads, - * consider thread-safe mechanisms of accessing the data. - */ - void *State; -}; - -/** - * microServiceInfo is the struct returned by microService_GetInfo function. It - * is also accessible by sending a `$SRV.INFO.[.]` request to - * the service. - */ -struct micro_service_info_s -{ - /** - * @brief Response type. Always `"io.nats.micro.v1.info_response"`. - */ - const char *Type; - - /** - * @brief The name of the service. - */ - const char *Name; - - /** - * @brief The semantic version of the service. - */ - const char *Version; - - /** - * @brief The description of the service. - */ - const char *Description; - - /** - * @brief The ID of the service instance responding to the request. - */ - const char *Id; - - /** - * @brief Metadata for the service, a JSON-encoded user-provided object, e.g. `{"key":"value"}` - */ - natsMetadata Metadata; - - /** - * @brief Endpoints. - */ - microEndpointInfo *Endpoints; - - /** - * @brief The number of endpoints in the `Endpoints` array. - */ - int EndpointsLen; -}; - -/** - * The Microservice stats struct. - */ -struct micro_service_stats_s -{ - /** - * @brief Response type. Always `"io.nats.micro.v1.stats_response"`. - */ - const char *Type; - - /** - * @brief The name of the service. - */ - const char *Name; - - /** - * @brief The semantic version of the service. - */ - const char *Version; - - /** - * @brief The ID of the service instance responding to the request. - */ - const char *Id; - - /** - * @brief The timestamp of when the service was started. - */ - int64_t Started; - - /** - * @brief The stats for each endpoint of the service. - */ - microEndpointStats *Endpoints; - - /** - * @brief The number of endpoints in the `endpoints` array. - */ - int EndpointsLen; -}; - -/** @} */ // end of microStructs - -/** \defgroup microConstants Public constants - * - * Microservice public constants. - * @{ - */ - -/** - * @brief The prefix for all microservice monitoring subjects. - * - * For example, `"$SRV.PING"`. - */ -#define MICRO_API_PREFIX "$SRV" - -/** - * @brief The `type` set in the `$SRV.INFO` responses. - */ -#define MICRO_INFO_RESPONSE_TYPE "io.nats.micro.v1.info_response" - -/** - * @brief For `$SRV.INFO.*` subjects. - */ -#define MICRO_INFO_VERB "INFO" - -/** - * @brief The `type` set in the `$SRV.PING` response. - */ -#define MICRO_PING_RESPONSE_TYPE "io.nats.micro.v1.ping_response" - -/** - * @brief For `$SRV.PING` subjects. - */ -#define MICRO_PING_VERB "PING" - -/** - * @brief The `type` set in the `STATS` response. - */ -#define MICRO_STATS_RESPONSE_TYPE "io.nats.micro.v1.stats_response" - -/** - * @brief The "verb" used in `$SRV.STATS` subjects. - */ -#define MICRO_STATS_VERB "STATS" - -/** - * @brief The response message header used to communicate an erroneous NATS - * status back to the requestor. - */ -#define MICRO_STATUS_HDR "Nats-Status" - -/** - * @brief The response message header used to communicate an error message back - * to the requestor. - */ -#define MICRO_ERROR_HDR "Nats-Service-Error" - -/** - * @brief The response message header used to communicate an integer error code - * back to the requestor. - */ -#define MICRO_ERROR_CODE_HDR "Nats-Service-Error-Code" - -/** @} */ // end of microConstants - -/** \defgroup microFunctions Functions - * - * Microservice functions. - * @{ - */ - -/** \defgroup microServiceFunctions microService - * - * Functions that operate with #microService. - * @{ - */ - -/** @brief Creates and starts a new microservice. - * - * @note The microservice should be destroyed to clean up using - * #microService_Destroy. - * - * @param new_microservice the location where to store the pointer to the new - * #microService object. - * @param nc the #natsConnection the service will use to receive and respond to - * requests. - * @param config a #microServiceConfig object with the configuration of the - * service. See #micro_service_config_s for descriptions of the fields. - * - * @return a #microError if an error occurred. - * - * @see #microService_Destroy, #microService_AddEndpoint, #microServiceConfig, - * #microEndpointConfig - */ -NATS_EXTERN microError * -micro_AddService(microService **new_microservice, natsConnection *nc, microServiceConfig *config); - -/** @brief Adds an endpoint to a microservice and starts listening for messages. - * - * Endpoints are currently destroyed when the service is stopped, there is no - * present way to remove or stop individual endpoints. - * - * @param m the #microService that the endpoint will be added to. - * @param config a #microEndpointConfig object with the configuration of the - * endpoint. See #micro_endpoint_config_s for descriptions of the fields. - * - * @return a #microError if an error occurred. - * - * @see #microService_Destroy, #microEndpointConfig - */ -NATS_EXTERN microError * -microService_AddEndpoint(microService *m, microEndpointConfig *config); - -/** @brief Adds an group (prefix) to a microservice. - * - * Groups are associated with a service, and are destroyed when the service is - * destroyed. - * - * @param new_group the location where to store the pointer to the new - * #microGroup object. - * @param m the #microService that the group will be added to. - * @param prefix a prefix to use on names and subjects of all endpoints in the - * group. - * - * @return a #microError if an error occurred. - * - * @see #microGroup_AddGroup, #microGroup_AddEndpoint - */ -NATS_EXTERN microError * -microService_AddGroup(microGroup **new_group, microService *m, const char *prefix); - -/** @brief Destroys a microservice, stopping it first if needed. - * - * @note This function may fail while stopping the service, do not assume - * unconditional success if clean up is important. - * - * @param m the #microService to stop and destroy. - * - * @return a #microError if an error occurred. - * - * @see #microService_Stop, #microService_Run - */ -NATS_EXTERN microError * -microService_Destroy(microService *m); - -/** @brief Returns the connection associated with the service. If the service - * was successfully started, it is safe to assume it's not NULL, however it may - * already have been disconnected or closed. - * - * @param m the #microService. - * - * @return a #natsConnection associated with the service. - */ -NATS_EXTERN natsConnection * -microService_GetConnection(microService *m); - -/** @brief Returns a #microServiceInfo for a microservice. - * - * @note the #microServiceInfo struct returned by this call should be freed with - * #microServiceInfo_Destroy. - * - * @param new_info receives the pointer to the #microServiceInfo. - * @param m the #microService to query. - * - * @return a #microError if an error occurred. - * - * @see #microServiceInfo, #micro_service_info_s - */ -NATS_EXTERN microError * -microService_GetInfo(microServiceInfo **new_info, microService *m); - -/** @brief Returns the pointer to state data (closure). It is originally - * provided in #microServiceConfig.State. - * - * @param m the #microService. - * - * @return the state pointer. - * - * @see #microServiceConfig - */ -NATS_EXTERN void * -microService_GetState(microService *m); - -/** @brief Returns run-time statistics for a microservice. - * - * @note the #microServiceStats struct returned by this call should be freed - * with #microServiceStats_Destroy. - * - * @param new_stats receives the pointer to the #microServiceStats. - * @param m the #microService to query. - * - * @return a #microError if an error occurred. - * - * @see #microServiceStats, #micro_service_stats_s - */ -NATS_EXTERN microError * -microService_GetStats(microServiceStats **new_stats, microService *m); - -/** @brief Checks if the service is stopped. - * - * @param m the #microService. - * - * @return `true` if stopped, otherwise `false`. - * - * @see #micro_AddService, #microService_Stop, #microService_Run - */ -NATS_EXTERN bool -microService_IsStopped(microService *m); - -/** @brief Waits for a microservice to stop. - * - * #micro_AddService starts the service with async subscriptions. - * #microService_Run waits for the service to stop. - * - * @param m the #microService. - * - * @return a #microError for invalid arguments, otherwise always succeeds. - * - * @see #micro_AddService, #microService_Stop - */ -NATS_EXTERN microError * -microService_Run(microService *m); - -/** @brief Stops a running microservice. - * - * Drains and closes the all subscriptions (endpoints and monitoring), resets - * the stats, and calls the `Done` callback for the service, so it can do its - * own clean up if needed. - * - * It is possible that this call encounters an error while stopping the service, - * in which case it aborts and returns the error. The service then may be in a - * partially stopped state, and the `Done` callback will not have been called. - * - * @param m the #microService. - * - * @return a #microError if an error occurred. - * - * @see #micro_AddService, #microService_Run - */ -NATS_EXTERN microError *microService_Stop(microService *m); - -/** @} */ // end of microServiceFunctions - -/** \defgroup microGroupFunctions microGroup - * - * Functions that operate with #microGroup. - * @{ - */ - -/** @brief Adds a sub-group to #microGroup. - * - * The new subgroup will be prefixed as "parent_prefix.prefix.". Groups are - * associated with a service, and are destroyed when the service is destroyed. - * - * @param new_group the location where to store the pointer to the new - * #microGroup object. - * @param parent the #microGroup that the new group will be added to. - * @param prefix a prefix to use on names and subjects of all endpoints in the - * group. - * - * @return a #microError if an error occurred. - * - * @see #microGroup_AddGroup, #microGroup_AddEndpoint - */ -NATS_EXTERN microError * -microGroup_AddGroup(microGroup **new_group, microGroup *parent, const char *prefix); - -/** @brief Adds an endpoint to a #microGroup and starts listening for messages. - * - * This is a convenience method to add an endpoint with its `Subject` and `Name` - * prefixed with the group's prefix. - * - * @param g the #microGroup that the endpoint will be added to. - * @param config a #microEndpointConfig object with the configuration of the - * endpoint. See #micro_endpoint_config_s for descriptions of the fields. - * - * @return a #microError if an error occurred. - * - * @see #microService_Destroy, #microService_AddEndpoint, #microEndpointConfig - */ -NATS_EXTERN microError * -microGroup_AddEndpoint(microGroup *g, microEndpointConfig *config); - -/** @} */ // end of microGroupFunctions - -/** \defgroup microRequestFunctions microRequest - * - * Functions that operate with #microRequest. - * @{ - */ - -/** @brief Adds a header to the underlying NATS request message. - * - * @param req the request. - * @param key the key under which the `value` will be stored. It can't ne `NULL` - * or empty. - * @param value the string to add to the values associated with the given `key`. - * The value can be `NULL` or empty string. - * - * @return a #microError if an error occurred. - * - * @see #natsMsgHeader_Add, #microRequest_DeleteHeader, #microRequest_GetHeaderKeys, - * #microRequest_GetHeaderValue, #microRequest_GetHeaderValues - */ -NATS_EXTERN microError * -microRequest_AddHeader(microRequest *req, const char *key, const char *value); - -/** @brief Deletes a header from the underlying NATS request message. - * - * @param req the request. - * @param key the key to delete from the headers map. - * - * @return a #microError if an error occurred. - * - * @see #natsMsgHeader_Delete, #microRequest_AddHeader - */ -NATS_EXTERN microError * -microRequest_DeleteHeader(microRequest *req, const char *key); - -/** @brief Returns the connection associated with the request. - * - * @param req the request. - * - * @return a #natsConnection associated with the request. In fact, it's the - * connection associated with the service, so by the time the request handler is - * invoked the connection state may be different from when it was received. - */ -NATS_EXTERN natsConnection * -microRequest_GetConnection(microRequest *req); - -/** @brief Returns the data in the the request, as a byte array. - * - * @note The request owns the data, so it should not be freed other than with - * `microRequest_Destroy`. - * @note The data is not `NULL` terminated. Use `microRequest_GetDataLength` to - * obtain the number of bytes. - * - * @param req the request. - * - * @return a pointer to the request's data. - * - * @see #natsMsg_GetData, #microRequest_GetDataLength - */ -NATS_EXTERN const char * -microRequest_GetData(microRequest *req); - -/** @brief Returns the number of data bytes in the the request. - * - * @param req the request. - * - * @return the number of data bytes in the request. - * - * @see #natsMsg_GetDataLength, #microRequest_GetData - */ -NATS_EXTERN int -microRequest_GetDataLength(microRequest *req); - -/** \brief Returns the pointer to the user-provided endpoint state, if - * the request is associated with an endpoint. - * - * @param req the request. - * - * @return the `state` pointer provided in microEndpointConfig. - * - * @see #microEndpointConfig, #micro_endpoint_config_s - */ -NATS_EXTERN void * -microRequest_GetEndpointState(microRequest *req); - -/** @brief Gets the list of all header keys in the NATS message underlying the - * request. - * - * The returned strings are own by the library and MUST not be freed or altered. - * However, the returned array `keys` MUST be freed by the user. - * - * @param req the request. - * @param keys the memory location where the library will store the pointer to - * the array of keys. - * @param count the memory location where the library will store the number of - * keys returned. - * - * @return a #microError if an error occurred. - * - * @see #natsMsgHeader_Keys, #microRequest_GetHeaderValue, #microRequest_GetHeaderValues - */ -NATS_EXTERN microError * -microRequest_GetHeaderKeys(microRequest *req, const char ***keys, int *count); - -/** @brief Get the header entry associated with `key` from the NATS message underlying the request. - * - * @param req the request. - * @param key the key for which the value is requested. - * @param value the memory location where the library will store the pointer to the first - * value (if more than one is found) associated with the `key`. - * - * @return a #microError if an error occurred. - * - * @see #natsMsgHeader_Get, #microRequest_GetHeaderValue, #microRequest_GetHeaderValues - */ -NATS_EXTERN microError * -microRequest_GetHeaderValue(microRequest *req, const char *key, const char **value); - -/** @brief Get all header values associated with `key` from the NATS message - * underlying the request. - * - * @param req the request. - * @param key the key for which the values are requested. - * @param values the memory location where the library will store the pointer to - * the array value (if more than one is found) associated with the `key`. - * @param count the memory location where the library will store the number of - * values returned. - * - * @return a #microError if an error occurred. - * - * @see #natsMsgHeader_Values, #microRequest_GetHeaderValue, - * #microRequest_GetHeaderKeys - */ -NATS_EXTERN microError * -microRequest_GetHeaderValues(microRequest *req, const char *key, const char ***values, int *count); - -/** @brief Get the NATS message underlying the request. - * - * @param req the request. - * - * @return the pointer to #natsMsg. - */ -NATS_EXTERN natsMsg * -microRequest_GetMsg(microRequest *req); - -/** @brief Returns the reply subject set in this message. - * - * Returns the reply, possibly `NULL`. - * - * @warning The string belongs to the message and must not be freed. - * Copy it if needed. - * - * @param req the request. - */ -NATS_EXTERN const char * -microRequest_GetReply(microRequest *req); - -/** @brief Returns the pointer to the microservice associated with the request. - * - * @param req the request. - * - * @return the microservice pointer. - */ -NATS_EXTERN microService * -microRequest_GetService(microRequest *req); - -/** @brief Returns the pointer to the user-provided service state. - * - * @param req the request. - * - * @return the `state` pointer provided in microServiceConfig. - * - * @see #microServiceConfig, #micro_service_config_s - */ -NATS_EXTERN void * -microRequest_GetServiceState(microRequest *req); - -/** @brief Returns the subject of the request message. - * - * @warning The string belongs to the message and must not be freed. - * Copy it if needed. - * - * @param req the request. - */ -NATS_EXTERN const char * -microRequest_GetSubject(microRequest *req); - -/** - * @brief Respond to a request, on the same NATS connection. - * - * @param req the request. - * @param data the response data. - * @param len the length of the response data. - * - * @return an error, if any. - */ -NATS_EXTERN microError * -microRequest_Respond(microRequest *req, const char *data, size_t len); - -/** - * @brief Respond to a request with a simple error. - * - * If err is NULL, `RespondError` does nothing. - * - * @note microRequest_RespondError is called automatially if the handler returns - * an error. Usually, there is no need for a handler to use this function - * directly. If the request - * - * @param req the request. - * @param err the error to include in the response header. If `NULL`, no error. - * - * @return an error, if any. - */ -NATS_EXTERN microError * -microRequest_RespondError(microRequest *req, microError *err); - -/** - * @brief Respond to a message, with an OK or an error. - * - * If err is NULL, `RespondErrorWithData` is equivalent to `Respond`. If err is - * not NULL, the response will include the error in the response header, and err - * will be freed. - * - * The following example illustrates idiomatic usage in a request handler. Since - * this handler handles its own error responses, the only error it might return - * would be a failure to send the response. - * - * \code{.c} - * err = somefunc(); - * if (err != NULL) { - * return microRequest_RespondCustom(req, err, error_data, data_len); - * } - * ... - * \endcode - * - * Or, if the request handler has its own cleanup logic: - * - * \code{.c} - * if (err = somefunc(), err != NULL) - * goto CLEANUP; - * ... - * - * CLEANUP: - * if (err != NULL) { - * return microRequest_RespondCustom(req, err, error_data, data_len); - * } - * return NULL; - * \endcode - * - * @param req the request. - * @param err the error to include in the response header. If `NULL`, no error. - * @param data the response data. - * @param len the length of the response data. - * - * @note - * - * - * @return an error, if any. - */ -NATS_EXTERN microError * -microRequest_RespondCustom(microRequest *req, microError *err, const char *data, size_t len); - -/** @brief Add `value` to the header associated with `key` in the NATS message - * underlying the request. - * - * @param req the request. - * @param key the key under which the `value` will be stored. It can't ne `NULL` - * or empty. - * @param value the string to store under the given `key`. The value can be - * `NULL` or empty string. - * - * @return a #microError if an error occurred. - * - * @see #natsMsgHeader_Set - */ -NATS_EXTERN microError * -microRequest_SetHeader(microRequest *req, const char *key, const char *value); - -/** @} */ // end of microRequestFunctions - -/** \defgroup microErrorFunctions microError - * - * Functions that create and manipulate #microError. - * @{ - */ - -/** @brief creates a new #microError, with a printf-like formatted message. - * - * @note Errors must be freed with #microError_Destroy, but often they are - * simply returned up the call stack. - * - * @param format printf-like format. - * - * @return a new #microError or micro_ErrorOutOfMemory. - */ -NATS_EXTERN microError * -micro_Errorf(const char *format, ...); - -/** @brief creates a new #microError, with a code and a printf-like formatted - * message. - * - * @note Errors must be freed with #microError_Destroy, but often they are - * simply returned up the call stack. - * - * @param code an `int` code, loosely modeled after the HTTP status code. - * @param format printf-like format. - * - * @return a new #microError or micro_ErrorOutOfMemory. - */ -NATS_EXTERN microError * -micro_ErrorfCode(int code, const char *format, ...); - -/** @brief Wraps a NATS status into a #microError, if not a NATS_OK. - * - * @param s NATS status of receiving the response. - * - * @return a new #microError or `NULL` if no error if found. - */ -NATS_EXTERN microError * -micro_ErrorFromStatus(natsStatus s); - -/** @brief returns the int code of the error. - * - * @param err the error. - * - * @return the int code. - */ -NATS_EXTERN int -microError_Code(microError *err); - -/** @brief destroys a #microError. - * - * @param err the error. - */ -NATS_EXTERN void -microError_Destroy(microError *err); - -#define microError_Ignore(__err) microError_Destroy(__err) - -/** - * @brief Returns the NATS status associated with the error. - * - * @param err - * - * @return the status - */ -NATS_EXTERN natsStatus -microError_Status(microError *err); - -/** - * @brief Returns a printable string with the error message. - * - * This function outputs into a user-provided buffer, and returns a "`%s`-able" - * pointer to it. - * - * @param err the error. - * @param buf the output buffer. - * @param len the capacity of the output buffer. - * @return `buf` - */ -NATS_EXTERN const char * -microError_String(microError *err, char *buf, size_t len); - -/** - * @brief Wraps an exising #microError with a higher printf-like formatted - * message. - * - * @warning The original error may be freed and should not be refered to after - * having been wrapped. - * - * @param err the original error - * @param format the new message to prepend to the original error message. - * @param ... - * - * @return a new, wrapped, error. - */ -NATS_EXTERN microError * -microError_Wrapf(microError *err, const char *format, ...); - -/** @} */ // end of microErrorFunctions - -/** \defgroup microClientFunctions microClient - * - * @{ - */ - -/** - * @brief Creates a new microservice client. - * - * @param new_client received the pointer to the new client. - * @param nc a NATS connection. - * @param cfg for future use, use NULL for now. - * - * @return a #microError if an error occurred. - */ -NATS_EXTERN microError * -micro_NewClient(microClient **new_client, natsConnection *nc, microClientConfig *cfg); - -/** - * @brief Destroys a microservice client. - * - * @param client the client to destroy. - */ -NATS_EXTERN void -microClient_Destroy(microClient *client); - -/** - * @brief Sends a request to a microservice and receives the response. - * - * @param reply receives the pointer to the response NATS message. `reply` must - * be freed with #natsMsg_Destroy. - * @param client the client to use. - * @param subject the subject to send the request on. - * @param data the request data. - * @param data_len the request data length. - * - * @return a #microError if an error occurred. - */ -NATS_EXTERN microError * -microClient_DoRequest(natsMsg **reply, microClient *client, const char *subject, const char *data, int data_len); - -/** @} */ // end of microClientFunctions - -/** \defgroup microCleanupFunctions Miscellaneous - * - * Functions to destroy miscellaneous objects. - * @{ - */ - -/** - * @brief Destroys a #microServiceInfo object. - * - * @param info the object to destroy. - */ -NATS_EXTERN void -microServiceInfo_Destroy(microServiceInfo *info); - -/** - * @brief Destroys a #microServiceStats object. - * - * @param stats the object to destroy. - */ -NATS_EXTERN void -microServiceStats_Destroy(microServiceStats *stats); - -/** @} */ // end of microCleanupFunctions - -/** @} */ // end of microFunctions - -/** @} */ // end of microGroup - -/** \defgroup wildcardsGroup Wildcards - * @{ - * Use of wildcards. There are two type of wildcards: `*` for partial, - * and `>` for full. - * - * A subscription on subject `foo.*` would receive messages sent to: - * - `foo.bar` - * - `foo.baz` - * - * but not on: - * - * - `foo.bar.baz` (too many elements) - * - `bar.foo`. (does not start with `foo`). - * - * A subscription on subject `foo.>` would receive messages sent to: - * - `foo.bar` - * - `foo.baz` - * - `foo.bar.baz` - * - * but not on: - * - `foo` (only one element, needs at least two) - * - `bar.baz` (does not start with `foo`). - ** @} */ - -/** \defgroup envVariablesGroup Environment Variables - * @{ - * You will find here the environment variables that change the default behavior - * of the NATS C Client library. - *

- * Name | Effect - * -----|:-----: - * `NATS_DEFAULT_TO_LIB_MSG_DELIVERY` | On #nats_Open, the library looks for this environment variable. If set (to any value), the library will default to using a global thread pool to perform message delivery. See #natsOptions_UseGlobalMessageDelivery and #nats_SetMessageDeliveryPoolSize. - * - ** @} */ - - -#ifdef __cplusplus -} -#endif - -#endif /* NATS_H_ */ diff --git a/src/adapters/libevent.h b/src/nats/adapters/libevent.h similarity index 52% rename from src/adapters/libevent.h rename to src/nats/adapters/libevent.h index c9863242f..e583cb7e5 100644 --- a/src/adapters/libevent.h +++ b/src/nats/adapters/libevent.h @@ -11,11 +11,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef LIBEVENT_H_ -#define LIBEVENT_H_ +#ifndef NATS_LIBEVENT_H_ +#define NATS_LIBEVENT_H_ #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif /** \cond @@ -27,13 +28,13 @@ extern "C" { typedef struct { - natsConnection *nc; - struct event_base *loop; - struct event *read; - struct event *write; - struct event *keepActive; + natsConnection *nc; + struct event_base *loop; + struct event *read; + struct event *write; + struct event *keepActive; -} natsLibeventEvents; +} natsLibevent; // Forward declarations natsStatus natsLibevent_Read(void *userData, bool add); @@ -43,41 +44,26 @@ natsStatus natsLibevent_Detach(void *userData); * */ - /** \defgroup libeventFunctions Libevent Adapter * * Adapter to plug a `NATS` connection to a `libevent` event loop. * @{ */ -/** \brief Initialize the adapter. - * - * Needs to be called once so that the adapter can initialize some state. - */ -void -natsLibevent_Init(void) -{ -#if _WIN32 - evthread_use_windows_threads(); -#else - evthread_use_pthreads(); -#endif -} - static void natsLibevent_ProcessEvent(evutil_socket_t fd, short event, void *arg) { - natsLibeventEvents *nle = (natsLibeventEvents*) arg; + natsLibevent *l = (natsLibevent *)arg; if (event & EV_READ) - natsConnection_ProcessReadEvent(nle->nc); + nats_ProcessReadEvent(l->nc); if (event & EV_WRITE) - natsConnection_ProcessWriteEvent(nle->nc); + nats_ProcessWriteEvent(l->nc); } static void -keepAliveCb(evutil_socket_t fd, short flags, void * arg) +keepAliveCb(evutil_socket_t fd, short flags, void *arg) { // do nothing... } @@ -95,65 +81,47 @@ keepAliveCb(evutil_socket_t fd, short flags, void * arg) * @param socket the socket to start polling on. */ natsStatus -natsLibevent_Attach(void **userData, void *loop, natsConnection *nc, natsSock socket) +natsLibevent_Attach(void *userData, natsEventLoop *ev, natsConnection *nc, natsSock socket) { - struct event_base *libeventLoop = (struct event_base*) loop; - natsLibeventEvents *nle = (natsLibeventEvents*) (*userData); - natsStatus s = NATS_OK; - - // This is the first attach (when reconnecting, nle will be non-NULL). - if (nle == NULL) - { - nle = (natsLibeventEvents*) calloc(1, sizeof(natsLibeventEvents)); - if (nle == NULL) - return NATS_NO_MEMORY; - - nle->nc = nc; - nle->loop = libeventLoop; - - nle->keepActive = event_new(nle->loop, -1, EV_PERSIST, keepAliveCb, NULL); - if (nle->keepActive == NULL) - s = NATS_NO_MEMORY; + natsLibevent *l = (natsLibevent *)userData; + natsStatus s = NATS_OK; - if (s == NATS_OK) - { - struct timeval timeout; + if (l == NULL) + return NATS_INVALID_ARG; - timeout.tv_sec = 100000; - timeout.tv_usec = 0; + // cleanup any prior state. + natsLibevent_Detach(userData); + l->nc = nc; + l->loop = (struct event_base *)ev->loop; - if (event_add(nle->keepActive, &timeout) != 0) - s = NATS_ERR; - } - } - else + l->keepActive = event_new(l->loop, -1, EV_PERSIST, keepAliveCb, NULL); + if (l->keepActive == NULL) + s = NATS_NO_MEMORY; + if (s == NATS_OK) { - if (nle->read != NULL) - { - event_free(nle->read); - nle->read = NULL; - } - if (nle->write != NULL) - { - event_free(nle->write); - nle->write = NULL; - } + struct timeval timeout; + timeout.tv_sec = 100000; + timeout.tv_usec = 0; + if (event_add(l->keepActive, &timeout) != 0) + s = NATS_ERR; } if (s == NATS_OK) { - nle->read = event_new(nle->loop, socket, EV_READ|EV_PERSIST, - natsLibevent_ProcessEvent, (void*) nle); - natsLibevent_Read((void*) nle, true); - - nle->write = event_new(nle->loop, socket, EV_WRITE|EV_PERSIST, - natsLibevent_ProcessEvent, (void*) nle); + // Create the read event and add it right away. Persist it until + // expicitly removed when detaching. + l->read = event_new(l->loop, socket, EV_READ | EV_PERSIST, + natsLibevent_ProcessEvent, (void *)l); + natsLibevent_Read((void *)l, true); + + // Create the write event. It will be added when needed by + // natsConn_asyncWrite. + l->write = event_new(l->loop, socket, EV_WRITE, + natsLibevent_ProcessEvent, (void *)l); } - if (s == NATS_OK) - *userData = (void*) nle; - else - natsLibevent_Detach((void*) nle); + if (s != NATS_OK) + natsLibevent_Detach(userData); return s; } @@ -169,13 +137,13 @@ natsLibevent_Attach(void **userData, void *loop, natsConnection *nc, natsSock so natsStatus natsLibevent_Read(void *userData, bool add) { - natsLibeventEvents *nle = (natsLibeventEvents*) userData; - int res; + natsLibevent *le = (natsLibevent *)userData; + int res; if (add) - res = event_add(nle->read, NULL); + res = event_add(le->read, NULL); else - res = event_del_noblock(nle->read); + res = event_del_noblock(le->read); return (res == 0 ? NATS_OK : NATS_ERR); } @@ -191,17 +159,44 @@ natsLibevent_Read(void *userData, bool add) natsStatus natsLibevent_Write(void *userData, bool add) { - natsLibeventEvents *nle = (natsLibeventEvents*) userData; - int res; + natsLibevent *le = (natsLibevent *)userData; + int res = 0; if (add) - res = event_add(nle->write, NULL); + { + if (event_pending(le->write, EV_WRITE, NULL) == 0) + res = event_add(le->write, NULL); + } else - res = event_del_noblock(nle->write); + { + res = event_del_noblock(le->write); + } return (res == 0 ? NATS_OK : NATS_ERR); } +// TODO: <>/<> comment +natsStatus +natsLibevent_Stop(void *userData) +{ + natsLibevent *l = (natsLibevent *)userData; + + if (l->read != NULL) + { + event_del_noblock(l->read); + event_free(l->read); + l->read = NULL; + } + if (l->write != NULL) + { + event_del_noblock(l->write); + event_free(l->write); + l->write = NULL; + } + + return NATS_OK; +} + /** \brief The connection is closed, it can be safely detached. * * When a connection is closed (not disconnected, pending a reconnect), this @@ -213,20 +208,38 @@ natsLibevent_Write(void *userData, bool add) natsStatus natsLibevent_Detach(void *userData) { - natsLibeventEvents *nle = (natsLibeventEvents*) userData; + natsLibevent *l = (natsLibevent *)userData; + + natsLibevent_Stop(userData); - if (nle->read != NULL) - event_free(nle->read); - if (nle->write != NULL) - event_free(nle->write); - if (nle->keepActive != NULL) + if (l->keepActive != NULL) { - event_active(nle->keepActive, 0, 0); - event_free(nle->keepActive); + event_active(l->keepActive, 0, 0); + event_free(l->keepActive); } - free(nle); + return NATS_OK; +} +/** \brief Initialize the adapter. + * + * Needs to be called once so that the adapter can initialize some state. + */ +natsStatus natsLibevent_Init(natsEventLoop *ev) +{ + natsEventLoop base = { + .loop = event_base_new(), + .ctxSize = sizeof(natsLibevent), + .attach = natsLibevent_Attach, + .read = natsLibevent_Read, + .write = natsLibevent_Write, + .detach = natsLibevent_Detach, + .stop = natsLibevent_Stop, + }; + if (base.loop == NULL) + return NATS_NO_MEMORY; + + *ev = base; return NATS_OK; } @@ -236,4 +249,4 @@ natsLibevent_Detach(void *userData) } #endif -#endif /* LIBEVENT_H_ */ +#endif /* NATS_LIBEVENT_H_ */ diff --git a/src/adapters/libuv.h b/src/nats/adapters/libuv.h similarity index 96% rename from src/adapters/libuv.h rename to src/nats/adapters/libuv.h index 50499e98b..e40446519 100644 --- a/src/adapters/libuv.h +++ b/src/nats/adapters/libuv.h @@ -147,15 +147,15 @@ natsLibuvPoll(uv_poll_t* handle, int status, int events) // There was an error, try to process as a read event. // If we had an issue with the socket, this will cause // an auto-reconnect. - natsConnection_ProcessReadEvent(nle->nc); + nats_ProcessReadEvent(nle->nc); return; } if (events & UV_READABLE) - natsConnection_ProcessReadEvent(nle->nc); + nats_ProcessReadEvent(nle->nc); if (events & UV_WRITABLE) - natsConnection_ProcessWriteEvent(nle->nc); + nats_ProcessWriteEvent(nle->nc); } static natsStatus @@ -211,7 +211,7 @@ uvAsyncAttach(natsLibuvEvents *nle) if (nle->handle == NULL) s = NATS_NO_MEMORY; - if (s == NATS_OK) + if (STILL_OK(s)) { #if UV_VERSION_MAJOR <= 1 if (uv_poll_init_socket(nle->loop, nle->handle, nle->socket) != 0) @@ -221,7 +221,7 @@ uvAsyncAttach(natsLibuvEvents *nle) s = NATS_ERR; } - if ((s == NATS_OK) + if ((STILL_OK(s)) && (nle->handle->data = (void*) nle) && (uv_poll_start(nle->handle, UV_READABLE, natsLibuvPoll) != 0)) { @@ -364,22 +364,22 @@ natsLibuv_Attach(void **userData, void *loop, natsConnection *nc, natsSock socke if (nle->lock == NULL) s = NATS_NO_MEMORY; - if ((s == NATS_OK) && (uv_mutex_init(nle->lock) != 0)) + if ((STILL_OK(s)) && (uv_mutex_init(nle->lock) != 0)) s = NATS_ERR; - if ((s == NATS_OK) + if ((STILL_OK(s)) && ((nle->scheduler = (uv_async_t*) malloc(sizeof(uv_async_t))) == NULL)) { s = NATS_NO_MEMORY; } - if ((s == NATS_OK) + if ((STILL_OK(s)) && (uv_async_init(uvLoop, nle->scheduler, uvAsyncCb) != 0)) { s = NATS_ERR; } - if (s == NATS_OK) + if (STILL_OK(s)) { nle->nc = nc; nle->loop = uvLoop; @@ -387,7 +387,7 @@ natsLibuv_Attach(void **userData, void *loop, natsConnection *nc, natsSock socke } } - if (s == NATS_OK) + if (STILL_OK(s)) { nle->socket = socket; nle->events = UV_READABLE; @@ -398,7 +398,7 @@ natsLibuv_Attach(void **userData, void *loop, natsConnection *nc, natsSock socke s = uvAsyncAttach(nle); } - if (s == NATS_OK) + if (STILL_OK(s)) *userData = (void*) nle; else natsLibuv_Detach((void*) nle); diff --git a/src/nats/adapters/malloc_heap.h b/src/nats/adapters/malloc_heap.h new file mode 100644 index 000000000..4ffb80840 --- /dev/null +++ b/src/nats/adapters/malloc_heap.h @@ -0,0 +1,63 @@ +// Copyright 2024 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. + +#ifndef NATS_MALLOC_HEAP_H_ +#define NATS_MALLOC_HEAP_H_ + +#include "../nats.h" +#include + +// ---------------------------------------------------------------------------- +// C global heap (malloc) adapter + +// dependencies +static void *nats_C_malloc(natsHeap *h, size_t size, bool zero) +{ + return zero ? calloc(1, size) : malloc(size); +} + +static void *nats_C_realloc(natsHeap *h, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +static void nats_C_free(natsHeap *h, void *ptr) +{ + free(ptr); +} + +static char * nats_C_strdup(natsHeap *h, const char *src) +{ + return strdup(src); +} + +static void nats_C_destroy(natsHeap *h) +{ + free(h); +} + +static natsHeap *nats_NewMallocHeap(void) +{ + natsHeap *h = (natsHeap *)malloc(sizeof(natsHeap)); + if (h != NULL) + { + h->alloc = nats_C_malloc; + h->realloc = nats_C_realloc; + h->free = nats_C_free; + h->strdup = nats_C_strdup; + h->destroy = nats_C_destroy; + } + return h; +} + +#endif /* MEM_HEAP_H_ */ diff --git a/src/deprnats.h b/src/nats/nats.h similarity index 50% rename from src/deprnats.h rename to src/nats/nats.h index 8bdc620bd..f9a1924b8 100644 --- a/src/deprnats.h +++ b/src/nats/nats.h @@ -1,4 +1,4 @@ -// Copyright 2017-2018 The NATS Authors +// Copyright 2015-2023 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 @@ -11,15 +11,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SRC_DEPRNATS_H_ -#define SRC_DEPRNATS_H_ +#ifndef NATS_H_ +#define NATS_H_ -#ifndef _WIN32 -#warning "`#include ` is deprecated. Please use `#include `" -#else -#pragma message ("`#include ` is deprecated. Please use `#include `") -#endif +#include "nats_base.h" +#include "nats_mem.h" +#include "nats_net.h" +#include "nats_opts.h" +#include "nats_msg.h" -#include +NATS_EXTERN void nats_Shutdown(void); +NATS_EXTERN const char *nats_GetVersion(void); +NATS_EXTERN uint32_t nats_GetVersionNumber(void); +NATS_EXTERN const char *nats_GetLastError(natsStatus *status); +NATS_EXTERN natsStatus nats_GetLastErrorStack(char *buffer, size_t bufLen); +NATS_EXTERN void nats_PrintLastErrorStack(FILE *file); -#endif /* SRC_DEPRNATS_H_ */ +#endif /* NATS_H_ */ diff --git a/src/nats/nats_base.h b/src/nats/nats_base.h new file mode 100644 index 000000000..4c8319981 --- /dev/null +++ b/src/nats/nats_base.h @@ -0,0 +1,51 @@ +// Copyright 2015-2023 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. + +#ifndef NATS_BASE_H_ +#define NATS_BASE_H_ + +#include +#include +#include +#include +#include + +#include "nats_version.h" + +/** \def NATS_EXTERN + * \brief Needed for shared library. + * + * Based on the platform this is compiled on, it will resolve to + * the appropriate instruction so that objects are properly exported + * when building the shared library. + */ +#define NATS_EXTERN +#if defined(_WIN32) +#undef NATS_EXTERN +#if defined(nats_EXPORTS) +#define NATS_EXTERN __declspec(dllexport) +#elif defined(nats_IMPORTS) +#define NATS_EXTERN __declspec(dllimport) +#endif +#endif + +#include "nats_status.h" + +typedef struct __natsMessage natsMessage; +typedef struct __natsOptions natsOptions; +typedef struct __natsConnection natsConnection; + +typedef void (*natsOnConnectionEventF)(natsConnection *nc, void *closure); +typedef void (*natsOnMessagePublishedF)(natsConnection *nc, natsMessage *msg, void *closure); + +#endif /* NATS_BASE_H_ */ diff --git a/src/nats/nats_mem.h b/src/nats/nats_mem.h new file mode 100644 index 000000000..5973a7731 --- /dev/null +++ b/src/nats/nats_mem.h @@ -0,0 +1,78 @@ +// Copyright 2015-2023 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. + +#ifndef NATS_MEM_H_ +#define NATS_MEM_H_ + +#include "nats_base.h" + +// Strings + +typedef struct __natsString_s +{ + size_t len; + uint8_t *data; +} natsString; + +static inline natsString nats_ToString(const char *str) +{ + natsString s; + + s.len = (str == NULL) ? 0 : strlen(str); + s.data = (uint8_t *)str; + + return s; +} + +static inline bool nats_IsStringEmpty(const natsString *str) +{ + return (str == NULL) || (str->len == 0); +} + +static inline size_t nats_StringLen(const natsString *str) +{ + return nats_IsStringEmpty(str) ? 0 : str->len; +} + +// Heap (implementation is provided by adapters, like nats/adapters/c_heap.h) + +#define NATS_MEM_ZERO_OUT true +#define NATS_MEM_LEAVE_UNINITIALIZED false + +typedef struct __natsHeap_s natsHeap; + +typedef void *(*natsHeapAllocF)(natsHeap *h, size_t size, bool zero); +typedef void (*natsHeapDestroyF)(natsHeap *h); +typedef char *(*natsHeapStrdupF)(natsHeap *h, const char *s); +typedef void (*natsHeapFreeF)(natsHeap *h, void *ptr); +typedef void *(*natsHeapReallocF)(natsHeap *h, void *ptr, size_t); + +struct __natsHeap_s +{ + natsHeapAllocF alloc; + natsHeapReallocF realloc; + natsHeapFreeF free; + natsHeapStrdupF strdup; + natsHeapDestroyF destroy; +}; + +// Pool + +typedef struct __natsPool_s natsPool; + +NATS_EXTERN natsStatus nats_CreatePool(natsPool **newPool, natsOptions *opts); +NATS_EXTERN void nats_RetainPool(natsPool *pool); +NATS_EXTERN void nats_ReleasePool(natsPool *pool); +NATS_EXTERN void *nats_Palloc(natsPool *pool, size_t size); + +#endif /* NATS_MEM_H_ */ diff --git a/src/nats/nats_msg.h b/src/nats/nats_msg.h new file mode 100644 index 000000000..bf04ed87b --- /dev/null +++ b/src/nats/nats_msg.h @@ -0,0 +1,35 @@ +// Copyright 2015-2023 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. + +#ifndef NATS_MESSAGE_H_ +#define NATS_MESSAGE_H_ + +#include "nats_base.h" +#include "nats_mem.h" +#include "nats_net.h" + +NATS_EXTERN natsStatus nats_AsyncPublish(natsConnection *nc, natsMessage *msg); +NATS_EXTERN natsStatus nats_AsyncPublishNoCopy(natsConnection *nc, natsMessage *msg); +NATS_EXTERN natsStatus nats_CreateMessage(natsMessage **newm, natsConnection *nc, const char *subj); +NATS_EXTERN natsStatus nats_RetainMessage(natsMessage *msg); +NATS_EXTERN natsStatus nats_SetMessageHeader(natsMessage *m, const char *key, const char *value); +NATS_EXTERN natsStatus nats_SetMessagePayload(natsMessage *m, const void *data, size_t dataLen); +NATS_EXTERN natsStatus nats_SetMessageReplyTo(natsMessage *m, const char *reply); +NATS_EXTERN natsStatus nats_SetOnMessageCleanup(natsMessage *m, void (*f)(void *), void *closure); +NATS_EXTERN natsStatus nats_SetOnMessagePublished(natsMessage *m, natsOnMessagePublishedF, void *closure); +NATS_EXTERN const uint8_t *nats_GetMessageData(natsMessage *msg); +NATS_EXTERN size_t nats_GetMessageDataLen(natsMessage *msg); + + NATS_EXTERN void nats_ReleaseMessage(natsMessage *msg); + +#endif /* NATS_MESSAGE_H_ */ diff --git a/src/nats/nats_net.h b/src/nats/nats_net.h new file mode 100644 index 000000000..538facd9c --- /dev/null +++ b/src/nats/nats_net.h @@ -0,0 +1,112 @@ +// Copyright 2015-2023 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. + +#ifndef NATS_NET_H_ +#define NATS_NET_H_ + +#include "nats_base.h" +#include "nats_mem.h" +#include "nats_opts.h" + +#if defined(_WIN32) +#include +typedef SOCKET natsSock; +#else +typedef int natsSock; +#endif + +typedef struct __natsEventLoop_s natsEventLoop; + +typedef struct +{ + uint64_t inMsgs; + uint64_t outMsgs; + uint64_t inBytes; + uint64_t outBytes; + uint64_t reconnects; +} natsConnectionStatistics; + +/** \brief Attach this connection to the external event loop. + * + * After a connection has (re)connected, this callback is invoked. It should + * perform the necessary work to start polling the given socket for READ events. + * + * @param userData location where the adapter implementation will store the + * object it created and that will later be passed to all other callbacks. If + * `*userData` is not `NULL`, this means that this is a reconnect event. + * @param pool the pool to allocate userData from (nc->lifetimePool, but we + * can't see inside nc here). + * @param loop the event loop (as a generic void*) this connection is being + * attached to. + * @param nc the connection being attached to the event loop. + * @param socket the socket to poll for read/write events. + */ +typedef natsStatus (*natsEventLoop_AttachF)( + void *userData, + natsEventLoop *ev, + natsConnection *nc, + natsSock socket); + +/** \brief Read or write event needs to be added or removed. + * + * The `NATS` library will invoke this callback to indicate if the event + * loop should start (`add is `true`) or stop (`add` is `false`) polling + * for read events on the socket. + * + * @param userData the pointer to an user object created in #natsEventLoop_Attach. + * @param add `true` if the event library should start polling, `false` otherwise. + */ +typedef natsStatus (*natsEventLoop_AddRemoveF)( + void *userData, + bool add); + +/** \brief Stop polling or detach from the event loop. + * + * The `NATS` library will invoke this callback to indicate that the connection + * no longer needs to be attached to the event loop. User can cleanup some state. + * + * @param userData the pointer to an user object created in #natsEventLoop_Attach. + */ +typedef natsStatus (*natsEventLoop_StopF)( + void *userData); + +struct __natsEventLoop_s +{ + // Allocating and freeing the context object is the responsibility of the + // user (us here). The size comes from this config. + size_t ctxSize; + + // Each adapter offers a ..._New (e.g. Libevent_New) function that will + // create a new base loop, and store it here for the callbacks to use. + void *loop; + + natsEventLoop_AttachF attach; + natsEventLoop_AddRemoveF read; + natsEventLoop_AddRemoveF write; + natsEventLoop_StopF stop; + natsEventLoop_StopF detach; +}; + +// Creates a connection object and a socket, attaches to the event loop provided by the caller. +// - ev is required +// - cb is optional, but used often +// - opts is optional, reasonably defaulted +NATS_EXTERN natsStatus nats_AsyncConnectWithOptions(natsConnection **newConn, natsEventLoop *ev, natsOptions *options); +NATS_EXTERN natsStatus nats_AsyncConnectTo(natsConnection **newConn, natsEventLoop *ev, const char *url, natsOnConnectionEventF cb, void *closure); +NATS_EXTERN void nats_CloseConnection(natsConnection *nc); +NATS_EXTERN void nats_DestroyConnection(natsConnection *nc); +NATS_EXTERN const char *nats_GetConnectionError(natsConnection *nc); +NATS_EXTERN void nats_ProcessReadEvent(natsConnection *nc); +NATS_EXTERN void nats_ProcessWriteEvent(natsConnection *nc); + +#endif /* NATS_NET_H_ */ diff --git a/src/nats/nats_opts.h b/src/nats/nats_opts.h new file mode 100644 index 000000000..137b3fdac --- /dev/null +++ b/src/nats/nats_opts.h @@ -0,0 +1,43 @@ +// Copyright 2015-2023 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. + +#ifndef NATS_OPTS_H_ +#define NATS_OPTS_H_ + +#include "nats_base.h" + +/** \def NATS_EXTERN + * \brief Needed for shared library. + * + * Based on the platform this is compiled on, it will resolve to + * the appropriate instruction so that objects are properly exported + * when building the shared library. + */ + +#define NATS_DEFAULT_URL "nats://localhost:4222" + +NATS_EXTERN natsOptions *nats_GetDefaultOptions(void); + +// Network and connection options +NATS_EXTERN natsStatus nats_SetIPResolutionOrder(natsOptions *opts, int order); +NATS_EXTERN natsStatus nats_SetNoRandomize(natsOptions *opts, bool noRandomize); +NATS_EXTERN natsStatus nats_SetServers(natsOptions *opts, const char **servers, int serversCount); +NATS_EXTERN natsStatus nats_SetName(natsOptions *opts, const char *name); + +NATS_EXTERN natsStatus nats_SetOnConnected(natsOptions *opts, natsOnConnectionEventF f, void *closure); +NATS_EXTERN natsStatus nats_SetOnConnectionClosed(natsOptions *opts, natsOnConnectionEventF f, void *closure); + +// NATS protocol options +NATS_EXTERN natsStatus nats_SetVerbose(natsOptions *opts, bool verbose); + +#endif /* NATS_OPTS_H_ */ diff --git a/src/status.h b/src/nats/nats_status.h similarity index 93% rename from src/status.h rename to src/nats/nats_status.h index ee8bfe703..c9133fe05 100644 --- a/src/status.h +++ b/src/nats/nats_status.h @@ -11,40 +11,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef STATUS_H_ -#define STATUS_H_ +#ifndef NATS_STATUS_H_ +#define NATS_STATUS_H_ #ifdef __cplusplus extern "C" { #endif -/// The connection state -typedef enum -{ -#if defined(NATS_CONN_STATUS_NO_PREFIX) - // This is deprecated and applications referencing connection - // status should be updated to use the values prefixed with NATS_CONN_STATUS_. - - DISCONNECTED = 0, ///< The connection has been disconnected - CONNECTING, ///< The connection is in the process or connecting - CONNECTED, ///< The connection is connected - CLOSED, ///< The connection is closed - RECONNECTING, ///< The connection is in the process or reconnecting - DRAINING_SUBS, ///< The connection is draining subscriptions - DRAINING_PUBS, ///< The connection is draining publishers -#else - NATS_CONN_STATUS_DISCONNECTED = 0, ///< The connection has been disconnected - NATS_CONN_STATUS_CONNECTING, ///< The connection is in the process or connecting - NATS_CONN_STATUS_CONNECTED, ///< The connection is connected - NATS_CONN_STATUS_CLOSED, ///< The connection is closed - NATS_CONN_STATUS_RECONNECTING, ///< The connection is in the process or reconnecting - NATS_CONN_STATUS_DRAINING_SUBS, ///< The connection is draining subscriptions - NATS_CONN_STATUS_DRAINING_PUBS, ///< The connection is draining publishers -#endif - -} natsConnStatus; - /// Status returned by most of the APIs typedef enum { @@ -272,4 +246,6 @@ typedef enum { } #endif -#endif /* STATUS_H_ */ +NATS_EXTERN const char *natsStatus_GetText(natsStatus s); + +#endif // NATS_STATUS_H_ diff --git a/src/version.h b/src/nats/nats_version.h similarity index 86% rename from src/version.h rename to src/nats/nats_version.h index e06ea3530..49df2186f 100644 --- a/src/version.h +++ b/src/nats/nats_version.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef VERSION_H_ -#define VERSION_H_ +#ifndef NATS_VERSION_H_ +#define NATS_VERSION_H_ #ifdef __cplusplus extern "C" { @@ -25,16 +25,16 @@ extern "C" { #define NATS_VERSION_MINOR 9 #define NATS_VERSION_PATCH 0 -#define NATS_VERSION_STRING "3.9.0-beta" +#define NATS_VERSION_STRING "4.0.0-mininats" #define NATS_VERSION_NUMBER ((NATS_VERSION_MAJOR << 16) | \ (NATS_VERSION_MINOR << 8) | \ NATS_VERSION_PATCH) -#define NATS_VERSION_REQUIRED_NUMBER 0x030900 +#define NATS_VERSION_REQUIRED_NUMBER 0x040000 #ifdef __cplusplus } #endif -#endif /* VERSION_H_ */ +#endif // NATS_VERSION_H_ diff --git a/src/natsp.h b/src/natsp.h index c749b84bb..33dd781ec 100644 --- a/src/natsp.h +++ b/src/natsp.h @@ -15,927 +15,101 @@ #define NATSP_H_ #if defined(_WIN32) -# include "include/n-win.h" +#include "include/n-win.h" #else -# include "include/n-unix.h" +#include "include/n-unix.h" #endif -#if defined(NATS_HAS_TLS) -#include -#include -#include -#include -#else -#define SSL void* -#define SSL_free(c) { (c) = NULL; } -#define SSL_CTX void* -#define SSL_CTX_free(c) { (c) = NULL; } -#define NO_SSL_ERR "The library was built without SSL support!" -#endif - -#include "err.h" -#include "nats.h" -#include "buf.h" -#include "parser.h" -#include "timer.h" -#include "url.h" -#include "srvpool.h" -#include "msg.h" -#include "asynccb.h" -#include "hash.h" -#include "stats.h" -#include "natstime.h" -#include "nuid.h" - -// Comment/uncomment to replace some function calls with direct structure -// access -//#define DEV_MODE (1) - -#define LIB_NATS_VERSION_STRING NATS_VERSION_STRING -#define LIB_NATS_VERSION_NUMBER NATS_VERSION_NUMBER -#define LIB_NATS_VERSION_REQUIRED_NUMBER NATS_VERSION_REQUIRED_NUMBER - -#define CString "C" - -#define _OK_OP_ "+OK" -#define _ERR_OP_ "-ERR" -#define _MSG_OP_ "MSG" -#define _PING_OP_ "PING" -#define _PONG_OP_ "PONG" -#define _INFO_OP_ "INFO" - -#define _CRLF_ "\r\n" -#define _SPC_ " " -#define _HPUB_P_ "HPUB " - -#define _PING_PROTO_ "PING\r\n" -#define _PONG_PROTO_ "PONG\r\n" -#define _SUB_PROTO_ "SUB %s %s %" PRId64 "\r\n" -#define _UNSUB_PROTO_ "UNSUB %" PRId64 " %d\r\n" -#define _UNSUB_NO_MAX_PROTO_ "UNSUB %" PRId64 " \r\n" - -#define STALE_CONNECTION "Stale Connection" -#define PERMISSIONS_ERR "Permissions Violation" -#define AUTHORIZATION_ERR "Authorization Violation" -#define AUTHENTICATION_EXPIRED_ERR "User Authentication Expired" - -#define _CRLF_LEN_ (2) -#define _SPC_LEN_ (1) -#define _HPUB_P_LEN_ (5) -#define _PING_OP_LEN_ (4) -#define _PONG_OP_LEN_ (4) -#define _PING_PROTO_LEN_ (6) -#define _PONG_PROTO_LEN_ (6) -#define _OK_OP_LEN_ (3) -#define _ERR_OP_LEN_ (4) - -#define NATS_DEFAULT_INBOX_PRE "_INBOX." -#define NATS_DEFAULT_INBOX_PRE_LEN (7) - -#define NATS_MAX_REQ_ID_LEN (19) // to display 2^63-1 number - -#define WAIT_FOR_READ (0) -#define WAIT_FOR_WRITE (1) -#define WAIT_FOR_CONNECT (2) - -#define DEFAULT_DRAIN_TIMEOUT 30000 // 30 seconds - -#define MAX_FRAMES (50) - -#define nats_IsStringEmpty(s) ((((s) == NULL) || ((s)[0] == '\0')) ? true : false) -#define nats_HasPrefix(_s, _prefix) (nats_IsStringEmpty(_s) ? nats_IsStringEmpty(_prefix) : (strncmp((_s), (_prefix), strlen(_prefix)) == 0)) - -static inline bool nats_StringEquals(const char *s1, const char *s2) -{ - if (s1 == NULL) - return (s2 == NULL); - if (s2 == NULL) - return false; - - return strcmp(s1, s2); -} - -#define DUP_STRING(s, s1, s2) \ - { \ - (s1) = NATS_STRDUP(s2); \ - if ((s1) == NULL) \ - (s) = nats_setDefaultError(NATS_NO_MEMORY); \ - } - -#define IF_OK_DUP_STRING(s, s1, s2) \ - if (((s) == NATS_OK) && !nats_IsStringEmpty(s2)) \ - DUP_STRING((s), (s1), (s2)) - - -#define ERR_CODE_AUTH_EXPIRED (1) -#define ERR_CODE_AUTH_VIOLATION (2) - -// This is temporary until we remove original connection status enum -// values without NATS_CONN_STATUS_ prefix -#if defined(NATS_CONN_STATUS_NO_PREFIX) -#define NATS_CONN_STATUS_DISCONNECTED DISCONNECTED -#define NATS_CONN_STATUS_CONNECTING CONNECTING -#define NATS_CONN_STATUS_CONNECTED CONNECTED -#define NATS_CONN_STATUS_CLOSED CLOSED -#define NATS_CONN_STATUS_RECONNECTING RECONNECTING -#define NATS_CONN_STATUS_DRAINING_SUBS DRAINING_SUBS -#define NATS_CONN_STATUS_DRAINING_PUBS DRAINING_PUBS -#endif - -#define IFOK(s, c) if (s == NATS_OK) { s = (c); } - -#define NATS_MILLIS_TO_NANOS(d) (((int64_t)d)*(int64_t)1E6) -#define NATS_SECONDS_TO_NANOS(d) (((int64_t)d)*(int64_t)1E9) - -extern int64_t gLockSpinCount; - -typedef void (*natsInitOnceCb)(void); - -typedef struct __natsControl -{ - char *op; - char *args; - -} natsControl; - -typedef struct __natsServerInfo -{ - char *id; - char *host; - int port; - char *version; - bool authRequired; - bool tlsRequired; - bool tlsAvailable; - int64_t maxPayload; - char **connectURLs; - int connectURLsCount; - int proto; - uint64_t CID; - char *nonce; - char *clientIP; - bool lameDuckMode; - bool headers; - -} natsServerInfo; - -typedef struct __natsSSLCtx -{ - natsMutex *lock; - int refs; - SSL_CTX *ctx; - char *expectedHostname; - bool skipVerify; - -} natsSSLCtx; - -#define natsSSLCtx_getExpectedHostname(ctx) ((ctx)->expectedHostname) - -typedef struct -{ - natsEvLoop_Attach attach; - natsEvLoop_ReadAddRemove read; - natsEvLoop_WriteAddRemove write; - natsEvLoop_Detach detach; - -} natsEvLoopCallbacks; - -typedef struct __userCreds -{ - char *userOrChainedFile; - char *seedFile; - char *jwtAndSeedContent; - -} userCreds; - -struct __natsOptions -{ - // This field must be the first (see natsOptions_clone, same if you add - // allocated fields such as strings). - natsMutex *mu; - - char *url; - char **servers; - int serversCount; - bool noRandomize; - int64_t timeout; - char *name; - bool verbose; - bool pedantic; - bool allowReconnect; - bool secure; - int ioBufSize; - int maxReconnect; - int64_t reconnectWait; - int reconnectBufSize; - int64_t writeDeadline; - - char *user; - char *password; - char *token; - - natsTokenHandler tokenCb; - void *tokenCbClosure; - - natsConnectionHandler closedCb; - void *closedCbClosure; - - natsConnectionHandler disconnectedCb; - void *disconnectedCbClosure; - - natsConnectionHandler reconnectedCb; - void *reconnectedCbClosure; - - natsConnectionHandler discoveredServersCb; - void *discoveredServersClosure; - bool ignoreDiscoveredServers; - - natsConnectionHandler connectedCb; - void *connectedCbClosure; - - natsConnectionHandler lameDuckCb; - void *lameDuckClosure; - - natsErrHandler asyncErrCb; - void *asyncErrCbClosure; - - natsConnectionHandler microClosedCb; - natsErrHandler microAsyncErrCb; - - int64_t pingInterval; - int maxPingsOut; - int maxPendingMsgs; - int64_t maxPendingBytes; - - natsSSLCtx *sslCtx; - - void *evLoop; - natsEvLoopCallbacks evCbs; - - bool libMsgDelivery; - - int orderIP; // possible values: 0,4,6,46,64 - - // forces the old method of Requests that utilize - // a new Inbox and a new Subscription for each request - bool useOldRequestStyle; - - // If set to true, the Publish call will flush in place and - // not rely on the flusher. - bool sendAsap; - - // If set to true, pending requests will fail with NATS_CONNECTION_DISCONNECTED - // when the library detects a disconnection. - bool failRequestsOnDisconnect; - - // NoEcho configures whether the server will echo back messages - // that are sent on this connection if we also have matching subscriptions. - // Note this is supported on servers >= version 1.2. Proto 1 or greater. - bool noEcho; - - // If set to true, in case of failed connect, tries again using - // reconnect options values. - bool retryOnFailedConnect; - - // Callback/closure used to get the user JWT. Will be set to - // internal natsConn_userCreds function when userCreds != NULL. - natsUserJWTHandler userJWTHandler; - void *userJWTClosure; - - // Callback/closure used to sign the server nonce. Will be set to - // internal natsConn_signatureHandler function when userCreds != NULL; - natsSignatureHandler sigHandler; - void *sigClosure; - - // Public NKey that will be used to authenticate when connecting - // to the server. - char *nkey; - - // If user has invoked natsOptions_SetUserCredentialsFromFiles or - // natsOptions_SetUserCredentialsFromMemory, this will be set and points to - // userOrChainedFile, seedFile, or possibly directly contains the JWT+seed content. - struct __userCreds *userCreds; - - // Reconnect jitter added to reconnect wait - int64_t reconnectJitter; - int64_t reconnectJitterTLS; - - // Custom handler to specify reconnect wait time. - natsCustomReconnectDelayHandler customReconnectDelayCB; - void *customReconnectDelayCBClosure; - - // Disable the "no responders" feature. - bool disableNoResponders; - - // Custom inbox prefix - char *inboxPfx; - - // Custom message payload padding size - int payloadPaddingSize; -}; - -typedef struct __nats_MsgList -{ - natsMsg *head; - natsMsg *tail; - int msgs; - int bytes; - -} nats_MsgList; - -typedef struct __natsMsgDlvWorker -{ - natsMutex *lock; - natsCondition *cond; - natsThread *thread; - bool shutdown; - nats_MsgList msgList; - -} natsMsgDlvWorker; - -typedef struct __pmInfo -{ - char *subject; - int64_t deadline; - struct __pmInfo *next; - -} pmInfo; - -struct __jsCtx -{ - natsMutex *mu; - natsConnection *nc; - jsOptions opts; - int refs; - natsCondition *cond; - natsStrHash *pm; - natsTimer *pmtmr; - pmInfo *pmHead; - pmInfo *pmTail; - natsSubscription *rsub; - char *rpre; - int rpreLen; - int pacw; - int64_t pmcount; - int stalled; - bool closed; -}; - -typedef struct __jsSub -{ - jsCtx *js; - char *stream; - char *consumer; - char *psubj; - char *nxtMsgSubj; - bool pull; - bool inFetch; - bool ordered; - bool dc; // delete JS consumer in Unsub()/Drain() - bool ackNone; - uint64_t fetchID; - - // This is ConsumerInfo's Pending+Consumer.Delivered that we get from the - // add consumer response. Note that some versions of the server gather the - // consumer info *after* the creation of the consumer, which means that - // some messages may have been already delivered. So the sum of the two - // is a more accurate representation of the number of messages pending or - // in the process of being delivered to the subscription when created. - uint64_t pending; - - int64_t hbi; - bool active; - natsTimer *hbTimer; - natsMsg *mhMsg; - - char *cmeta; - uint64_t sseq; - uint64_t dseq; - // Skip sequence mismatch notification. This is used for - // async subscriptions to notify the asyn err handler only - // once. Should the mismatch be resolved, this will be - // cleared so notification can happen again. - bool ssmn; - // Sequence mismatch. This is for synchronous subscription - // so that they don't have to rely on async error callback. - // Calling NextMsg() when this is true will cause NextMsg() - // to return NATS_SLOW_CONSUMER, so that user can check - // the sequence mismatch report. Should the mismatch be - // resolved, this will be cleared. - bool sm; - // These are the mismatch seq info - struct mismatch - { - uint64_t sseq; - uint64_t dseq; - uint64_t ldseq; - } mismatch; - - // When in auto-ack mode, we have an internal callback - // that will call natsMsg_Ack after the user callback returns. - // We need to keep track of the user callback/closure though. - natsMsgHandler usrCb; - void *usrCbClosure; - - // For flow control, when the subscription reaches this - // delivered count, then send a message to this reply subject. - uint64_t fcDelivered; - uint64_t fciseq; - char *fcReply; - - // When reseting an OrderedConsumer, need the original configuration. - jsConsumerConfig *ocCfg; - -} jsSub; - -struct __kvStore -{ - natsMutex *mu; - int refs; - jsCtx *js; - char *bucket; - char *stream; - char *pre; - char *putPre; - bool usePutPre; - bool useJSPrefix; - bool useDirect; - -}; - -struct __kvEntry -{ - kvStore *kv; - const char *key; - natsMsg *msg; - uint64_t delta; - kvOperation op; - struct __kvEntry *next; - -}; - -struct __kvStatus -{ - kvStore *kv; - jsStreamInfo *si; - -}; - -struct __kvWatcher -{ - natsMutex *mu; - int refs; - kvStore *kv; - natsSubscription *sub; - uint64_t initPending; - uint64_t received; - bool ignoreDel; - bool initDone; - bool retMarker; - bool stopped; - -}; - -struct __natsSubscription -{ - natsMutex *mu; - - int refs; - - // This is non-zero when auto-unsubscribe is used. - uint64_t max; - - // This is updated in the delivery thread (or NextMsg) and indicates - // how many message have been presented to the callback (or returned - // from NextMsg). Like 'msgs', this is also used to determine if we - // have reached the max number of messages. - uint64_t delivered; - - // The list of messages waiting to be delivered to the callback (or - // returned from NextMsg). - nats_MsgList msgList; - - // True if msgList.count is over pendingMax - bool slowConsumer; - - // Condition variable used to wait for message delivery. - natsCondition *cond; - - // The subscriber is closed (or closing). - bool closed; - - // Indicates if this subscription is actively draining. - bool draining; - // This holds if draining has started and/or completed. - uint8_t drainState; - // Thread started to do the flush and wait for drain to complete. - natsThread *drainThread; - // Holds the status of the drain: if there was an error during the drain process. - natsStatus drainStatus; - // This is the timeout for the drain operation. - int64_t drainTimeout; - // This is set if the flush failed and will prevent the connection for pushing further messages. - bool drainSkip; - - // Same than draining but for the global delivery situation. - // This boolean will be switched off when processed, as opposed - // to draining that once set does not get reset. - bool libDlvDraining; - - // If true, the subscription is closed, but because the connection - // was closed, not because of subscription (auto-)unsubscribe. - bool connClosed; - - // Subscriber id. Assigned during the creation, does not change after that. - int64_t sid; - - // Subject that represents this subscription. This can be different - // than the received subject inside a Msg if this is a wildcard. - char *subject; - - // Optional queue group name. If present, all subscriptions with the - // same name will form a distributed queue, and each message will - // only be processed by one member of the group. - char *queue; - - // Reference to the connection that created this subscription. - struct __natsConnection *conn; - - // Delivery thread (for async subscription). - natsThread *deliverMsgsThread; - - // If message delivery is done by the library instead, this is the - // reference to the worker handling this subscription. - natsMsgDlvWorker *libDlvWorker; - - // Message callback and closure (for async subscription). - natsMsgHandler msgCb; - void *msgCbClosure; - - int64_t timeout; - natsTimer *timeoutTimer; - bool timedOut; - bool timeoutSuspended; - - // Pending limits, etc.. - int msgsMax; - int bytesMax; - int msgsLimit; - int bytesLimit; - int64_t dropped; - - // Complete callback - natsOnCompleteCB onCompleteCB; - void *onCompleteCBClosure; - - // For JetStream - jsSub *jsi; - -}; - -typedef struct __natsPong -{ - int64_t id; - - struct __natsPong *prev; - struct __natsPong *next; - -} natsPong; - -typedef struct __natsPongList -{ - natsPong *head; - natsPong *tail; - - int64_t incoming; - int64_t outgoingPings; - - natsPong cached; - - natsCondition *cond; - -} natsPongList; - -typedef struct __natsSockCtx -{ - natsSock fd; - bool fdActive; - - natsDeadline readDeadline; - natsDeadline writeDeadline; - - SSL *ssl; - - // This is true when we are using an external event loop (such as libuv). - bool useEventLoop; - - int orderIP; // possible values: 0,4,6,46,64 - - // By default, the list of IPs returned by the hostname resolution will - // be shuffled. This option, if `true`, will disable the shuffling. - bool noRandomize; - -} natsSockCtx; - -typedef struct __respInfo -{ - natsMutex *mu; - natsCondition *cond; - natsMsg *msg; - bool closed; - natsStatus closedSts; - bool removed; - bool pooled; - -} respInfo; - -// Used internally for testing and allow to alter/suppress an incoming message -typedef void (*natsMsgFilter)(natsConnection *nc, natsMsg **msg, void* closure); - -struct __natsConnection -{ - natsMutex *mu; - natsOptions *opts; - natsSrv *cur; - const char *tlsName; - - int refs; - - natsSockCtx sockCtx; - - natsSrvPool *srvPool; - - natsBuffer *pending; - bool usePending; - - natsBuffer *bw; - natsBuffer *scratch; - - natsServerInfo info; - - int64_t ssid; - natsHash *subs; - natsMutex *subsMu; - - natsConnStatus status; - bool initc; // true if the connection is performing the initial connect - bool ar; // abort reconnect attempts - bool rle; // reconnect loop ended - natsStatus err; - char errStr[256]; - - natsParser *ps; - natsTimer *ptmr; - int pout; - - natsPongList pongs; - - natsThread *readLoopThread; - - natsThread *flusherThread; - natsCondition *flusherCond; - bool flusherSignaled; - bool flusherStop; - - natsThread *reconnectThread; - int inReconnect; - natsCondition *reconnectCond; - - natsStatistics stats; - - natsThread *drainThread; - int64_t drainTimeout; - bool dontSendInPlace; - - // Set to true when owned by a Streaming connection, - // which will prevent user from calling Close and/or Destroy. - bool stanOwned; - - // New Request style - char respId[NATS_MAX_REQ_ID_LEN+1]; - int respIdPos; - int respIdVal; - char *respSub; // The wildcard subject - natsSubscription *respMux; // A single response subscription - natsStrHash *respMap; // Request map for the response msg - respInfo **respPool; - int respPoolSize; - int respPoolIdx; - - // For inboxes. We now support custom prefixes, so we can't rely - // on constants based on hardcoded "_INBOX." prefix. - const char *inboxPfx; - int inboxPfxLen; - int reqIdOffset; - - struct - { - bool attached; - bool writeAdded; - void *buffer; - void *data; - } el; - - // Msg filters for testing. - // Protected by subsMu - natsMsgFilter filter; - void *filterClosure; - - // Server version - struct - { - int ma; - int mi; - int up; - } srvVersion; -}; - -// +#include "nats/nats.h" +#include "dev_mode.h" + +#define SSL void * +#define SSL_free(c) \ + { \ + (c) = NULL; \ + } +#define SSL_CTX void * +#define SSL_CTX_free(c) \ + { \ + (c) = NULL; \ + } +#define NO_SSL_ERR "The library was built without SSL support!" + +#define LIB_NATS_VERSION_STRING NATS_VERSION_STRING +#define LIB_NATS_VERSION_NUMBER NATS_VERSION_NUMBER +#define LIB_NATS_VERSION_REQUIRED_NUMBER NATS_VERSION_REQUIRED_NUMBER + +#define CLangString "C" + +#define STILL_OK(_s) ((_s) == NATS_OK) +#define NOT_OK(_s) ((_s) != NATS_OK) +#define ALWAYS_OK(_e) ((_e), NATS_OK) +#define IFOK(s, c) \ + if (STILL_OK(s)) \ + { \ + s = (c); \ + } +#define IFNULL(_e, _err) ((_e) == NULL ? (_err) : NATS_OK) +#define CHECK_NO_MEMORY(_e) IFNULL((_e), NATS_NO_MEMORY) + +#define NATS_MILLIS_TO_NANOS(d) (((int64_t)d) * (int64_t)1E6) +#define NATS_SECONDS_TO_NANOS(d) (((int64_t)d) * (int64_t)1E9) + +#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)) +#define MAX(X, Y) (((X) > (Y)) ? (X) : (Y)) + +//----------------------------------------------------------------------------- +// Types + +typedef struct __nats_JSON_s nats_JSON; +typedef struct __natsJSONParser_s natsJSONParser; +typedef struct __natsHash natsHash; +typedef struct __natsHashIter natsHashIter; +typedef struct __natsParser natsParser; +typedef struct __natsServer_s natsServer; +typedef struct __natsServerInfo natsServerInfo; +typedef struct __natsServers_s natsServers; +typedef struct __natsSockCtx natsSockCtx; +typedef struct __natsStrHash natsStrHash; +typedef struct __natsStrHashIter natsStrHashIter; + +//----------------------------------------------------------------------------- // Library -// - -void -natsSys_Init(void); - -void -natsLib_Retain(void); - -void -natsLib_Release(void); - -int64_t -nats_setTargetTime(int64_t timeout); - -void -nats_resetTimer(natsTimer *t, int64_t newInterval); - -void -nats_stopTimer(natsTimer *t); - -// Returns the number of timers that have been created and not stopped. -int -nats_getTimersCount(void); -// Returns the number of timers actually in the list. This should be -// equal to nats_getTimersCount() or nats_getTimersCount() - 1 when a -// timer thread is invoking a timer's callback. -int -nats_getTimersCountInList(void); +natsHeap *nats_globalHeap(void); +natsPool *nats_globalPool(void); +int64_t nats_now(void); +int64_t nats_nowInNanoSeconds(void); +natsStatus nats_open(void); +void nats_releaseGlobalLib(void); +void nats_retainGlobalLib(void); +int64_t nats_setTargetTime(int64_t timeout); +void nats_sleep(int64_t sleepTime); +void nats_sysInit(void); -natsStatus -nats_postAsyncCbInfo(natsAsyncCbInfo *info); +//----------------------------------------------------------------------------- +// Other includes -void -nats_sslRegisterThreadForCleanup(void); - -natsStatus -nats_sslInit(void); - -natsStatus -natsLib_msgDeliveryPostControlMsg(natsSubscription *sub); - -natsStatus -natsLib_msgDeliveryAssignWorker(natsSubscription *sub); - -bool -natsLib_isLibHandlingMsgDeliveryByDefault(void); - -int64_t -natsLib_defaultWriteDeadline(void); - -void -natsLib_getMsgDeliveryPoolInfo(int *maxSize, int *size, int *idx, natsMsgDlvWorker ***workersArray); - -void -nats_setNATSThreadKey(void); - -natsStatus -natsLib_startServiceCallbacks(microService *m); - -void -natsLib_stopServiceCallbacks(microService *m); - -natsMutex* -natsLib_getServiceCallbackMutex(void); - -natsHash* -natsLib_getAllServicesToCallback(void); - -// -// Threads -// -typedef void (*natsThreadCb)(void *arg); - -natsStatus -natsThread_Create(natsThread **t, natsThreadCb cb, void *arg); - -bool -natsThread_IsCurrent(natsThread *t); - -void -natsThread_Join(natsThread *t); - -void -natsThread_Detach(natsThread *t); - -void -natsThread_Yield(void); - -void -natsThread_Destroy(natsThread *t); - -natsStatus -natsThreadLocal_CreateKey(natsThreadLocal *tl, void (*destructor)(void*)); - -void* -natsThreadLocal_Get(natsThreadLocal tl); - -#define natsThreadLocal_Set(k, v) natsThreadLocal_SetEx((k), (v), true) - -natsStatus -natsThreadLocal_SetEx(natsThreadLocal tl, const void *value, bool setErr); - -void -natsThreadLocal_DestroyKey(natsThreadLocal tl); - -bool -nats_InitOnce(natsInitOnceType *control, natsInitOnceCb cb); - - -// -// Conditions -// -natsStatus -natsCondition_Create(natsCondition **cond); - -void -natsCondition_Wait(natsCondition *cond, natsMutex *mutex); - -natsStatus -natsCondition_TimedWait(natsCondition *cond, natsMutex *mutex, int64_t timeout); - -natsStatus -natsCondition_AbsoluteTimedWait(natsCondition *cond, natsMutex *mutex, - int64_t absoluteTime); - -void -natsCondition_Signal(natsCondition *cond); - -void -natsCondition_Broadcast(natsCondition *cond); - -void -natsCondition_Destroy(natsCondition *cond); - -// -// Mutexes -// -natsStatus -natsMutex_Create(natsMutex **newMutex); - -void -natsMutex_Lock(natsMutex *m); - -bool -natsMutex_TryLock(natsMutex *m); - -void -natsMutex_Unlock(natsMutex *m); - -void -natsMutex_Destroy(natsMutex *m); - -// -// JetStream -// -void -jsSub_free(jsSub *sub); - -natsStatus -jsSub_deleteConsumer(natsSubscription *sub); - -void -jsSub_deleteConsumerAfterDrain(natsSubscription *sub); - -natsStatus -jsSub_trackSequences(jsSub *jsi, const char *reply); - -natsStatus -jsSub_processSequenceMismatch(natsSubscription *sub, natsMsg *msg, bool *sm); - -char* -jsSub_checkForFlowControlResponse(natsSubscription *sub); - -natsStatus -jsSub_scheduleFlowControlResponse(jsSub *jsi, const char *reply); - -natsStatus -jsSub_checkOrderedMsg(natsSubscription *sub, natsMsg *msg, bool *reset); - -natsStatus -jsSub_resetOrderedConsumer(natsSubscription *sub, uint64_t sseq); - -bool -natsMsg_isJSCtrl(natsMsg *msg, int *ctrlType); +#include "err.h" +#include "util.h" +#include "mem.h" + +#define _CRLF_ "\r\n" +#define _SPC_ " " + +#define _CRLF_LEN_ (sizeof(_CRLF_) - 1) +#define _SPC_LEN_ (sizeof(_SPC_) - 1) + +// These depend on mem.h but are used elsewhere, so define here. +static const natsString nats_CRLF = NATS_STR(_CRLF_); +static const natsString nats_SPACE = NATS_STR(_SPC_); +// static const natsString nats_OK = NATS_STR("+OK"); +static const natsString nats_ERR = NATS_STR("-ERR"); +// static const natsString nats_MSG = NATS_STR("MSG"); +static const natsString nats_PING_CRLF = NATS_STR("PING" _CRLF_); +static const natsString nats_PONG_CRLF = NATS_STR("PONG" _CRLF_); +// static const natsString nats_INFO = NATS_STR("INFO"); +static const natsString nats_PUB = NATS_STR("PUB"); +static const natsString nats_HPUB = NATS_STR("HPUB"); #endif /* NATSP_H_ */ diff --git a/src/natstime.c b/src/natstime.c index 8e87d7568..542b682a8 100644 --- a/src/natstime.c +++ b/src/natstime.c @@ -13,13 +13,15 @@ #include "natsp.h" +#include "natstime.h" + #ifdef _WIN32 #include #endif #include int64_t -nats_Now(void) +nats_now(void) { #ifdef _WIN32 struct _timeb now; @@ -39,7 +41,7 @@ nats_Now(void) } int64_t -nats_NowInNanoSeconds(void) +nats_nowInNanoSeconds(void) { #ifdef _WIN32 struct _timeb now; @@ -79,7 +81,7 @@ natsDeadline_GetTimeout(natsDeadline *deadline) if (!(deadline->active)) return -1; - timeout = (int) (deadline->absoluteTime - nats_Now()); + timeout = (int) (deadline->absoluteTime - nats_now()); if (timeout < 0) timeout = 0; diff --git a/src/nkeys.c b/src/nkeys.c deleted file mode 100644 index 3d636c93b..000000000 --- a/src/nkeys.c +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2019 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 "crypto.h" -#include "nkeys.h" - -// PREFIX_BYTE_SEED is the version byte used for encoded NATS Seeds -#define PREFIX_BYTE_SEED ((char) (18 << 3)) // Base32-encodes to 'S...' - -// PREFIX_BYTE_PRIVATE is the version byte used for encoded NATS Private keys -#define PREFIX_BYTE_PRIVATE ((char) (15 << 3)) // Base32-encodes to 'P...' - -// PREFIX_BYTE_SERVER is the version byte used for encoded NATS Servers -#define PREFIX_BYTE_SERVER ((char) (13 << 3)) // Base32-encodes to 'N...' - -// PREFIX_BYTE_CLUSTER is the version byte used for encoded NATS Clusters -#define PREFIX_BYTE_CLUSTER ((char) (2 << 3)) // Base32-encodes to 'C...' - -// PREFIX_BYTE_ACCOUNT is the version byte used for encoded NATS Accounts -#define PREFIX_BYTE_ACCOUNT ((char) 0) // Base32-encodes to 'A...' - -// PREFIX_BYTE_USER is the version byte used for encoded NATS Users -#define PREFIX_BYTE_USER ((char) (20 << 3)) // Base32-encodes to 'U...' - -static uint16_t -_getUInt16LittleEndian(char *src) -{ - uint16_t b0 = (uint16_t) (src[0] & 0xFF); - uint16_t b1 = (uint16_t) (src[1] & 0xFF); - - return (b0 | b1<<8); -} - -static bool -_isValidPublicPrefixByte(char b) -{ - switch (b) - { - case PREFIX_BYTE_USER: - case PREFIX_BYTE_SERVER: - case PREFIX_BYTE_CLUSTER: - case PREFIX_BYTE_ACCOUNT: - return true; - default: - return false; - } -} - -static natsStatus -_decodeSeed(const char *seed, char *raw, int rawMax) -{ - natsStatus s = NATS_OK; - uint16_t crc = 0; - char b1 = 0; - char b2 = 0; - int rawLen = 0; - - s = nats_Base32_DecodeString(seed, raw, rawMax, &rawLen); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - if (rawLen < 4) - return nats_setError(NATS_ERR, "%s", NKEYS_INVALID_ENCODED_KEY); - - // Read the crc that is stored as the two last bytes - crc = _getUInt16LittleEndian((char*)(raw + rawLen - 2)); - - // ensure checksum is valid - if (!nats_CRC16_Validate((unsigned char*) raw, rawLen - 2, crc)) - return nats_setError(NATS_ERR, "%s", NKEYS_INVALID_CHECKSUM); - - // Need to do the reverse here to get back to internal representation. - b1 = raw[0] & 248; // 248 = 11111000 - b2 = (raw[0]&7)<<5 | ((raw[1] & 248) >> 3); // 7 = 00000111 - - if (b1 != PREFIX_BYTE_SEED) - return nats_setError(NATS_ERR, "%s", NKEYS_INVALID_SEED); - - if (!_isValidPublicPrefixByte(b2)) - return nats_setError(NATS_ERR, "%s", NKEYS_INVALID_PREFIX); - - return NATS_OK; -} - -natsStatus -natsKeys_Sign(const char *encodedSeed, const unsigned char *input, int inputLen, unsigned char *signature) -{ - natsStatus s = NATS_OK; - char *seed = NULL; - int seedLen = 0; - - if ((input != NULL) && (inputLen == 0)) - inputLen = (int) strlen((char*) input); - - seedLen = (int)((strlen(encodedSeed) * 5) / 8); - seed = NATS_CALLOC(1, seedLen); - if (seed == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - if (s == NATS_OK) - s = _decodeSeed(encodedSeed, seed, seedLen); - if (s == NATS_OK) - { - // The actual seed starts after the first 2 characters. - s = natsCrypto_Sign((const unsigned char*) (seed+2), input, inputLen, signature); - } - if (seed != NULL) - { - natsCrypto_Clear((void*) seed, seedLen); - NATS_FREE(seed); - } - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -nats_Sign(const char *encodedSeed, - const char *input, - unsigned char **signature, - int *signatureLength) -{ - natsStatus s; - unsigned char sig[NATS_CRYPTO_SIGN_BYTES]; - - if (nats_IsStringEmpty(encodedSeed)) - return nats_setError(NATS_INVALID_ARG, "%s", "seed cannot be empty"); - - if (nats_IsStringEmpty(input)) - return nats_setError(NATS_INVALID_ARG, "%s", "input cannot be empty"); - - if ((signature == NULL) || (signatureLength == NULL)) - return nats_setError(NATS_INVALID_ARG, "%s", "signature and/or signatureLength cannot be NULL"); - - s = natsKeys_Sign(encodedSeed, (const unsigned char*) input, (int) strlen(input), sig); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - *signature = (unsigned char*) NATS_MALLOC(NATS_CRYPTO_SIGN_BYTES); - if (*signature == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - memcpy(*signature, sig, NATS_CRYPTO_SIGN_BYTES); - *signatureLength = NATS_CRYPTO_SIGN_BYTES; - return NATS_OK; -} diff --git a/src/nkeys.h b/src/nkeys.h deleted file mode 100644 index 153f878b8..000000000 --- a/src/nkeys.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2019 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. - -#ifndef NKEYS_H_ -#define NKEYS_H_ - -#include "natsp.h" - -#define NKEYS_INVALID_ENCODED_KEY "invalid encoded key" -#define NKEYS_INVALID_CHECKSUM "invalid checksum" -#define NKEYS_INVALID_SEED "invalid seed" -#define NKEYS_INVALID_PREFIX "invalid prefix byte" - -natsStatus -natsKeys_Sign(const char *encodedSeed, const unsigned char *input, int inputLen, unsigned char *signature); - -#endif /* NKEYS_H_ */ diff --git a/src/nuid.c b/src/nuid.c index 167d3ddcd..98884705e 100644 --- a/src/nuid.c +++ b/src/nuid.c @@ -13,6 +13,9 @@ #include "natsp.h" +#include "nuid.h" +#include "err.h" + // From https://en.wikipedia.org/wiki/Multiply-with-carry // CMWC working parts @@ -121,7 +124,6 @@ typedef struct natsNUID typedef struct natsLockedNUID { - natsMutex *mu; natsNUID nuid; } natsLockedNUID; @@ -163,9 +165,9 @@ _resetSequential(natsNUID *nuid) natsStatus s; s = _nextLong(&(nuid->seq), false, maxSeq); - if (s == NATS_OK) + if (STILL_OK(s)) s = _nextLong(&(nuid->inc), false, maxInc - minInc); - if (s == NATS_OK) + if (STILL_OK(s)) nuid->inc += minInc; return NATS_UPDATE_ERR_STACK(s); @@ -180,7 +182,7 @@ _randomizePrefix(natsNUID *nuid) int64_t r = 0; s = _nextLong(&r, true, maxPre); - if (s == NATS_OK) + if (STILL_OK(s)) { int64_t l; int i = NUID_PRE_LEN; @@ -197,26 +199,25 @@ _randomizePrefix(natsNUID *nuid) void natsNUID_free(void) { - natsMutex_Destroy(globalNUID.mu); - globalNUID.mu = NULL; } // Seed sequential random with math/random and current time and generate crypto prefix. natsStatus natsNUID_init(void) { - natsStatus s; - unsigned int seed = (unsigned int) nats_NowInNanoSeconds(); + natsStatus s = NATS_OK; + unsigned int seed = (unsigned int) nats_nowInNanoSeconds(); memset(&globalNUID, 0, sizeof(natsLockedNUID)); srand(seed); _initCMWC(seed); - s = natsMutex_Create(&(globalNUID.mu)); - if (s == NATS_OK) + // <>//<> + // s = natsMutex_Create(&(globalNUID.mu)); + if (STILL_OK(s)) s = _resetSequential(&(globalNUID.nuid)); - if (s == NATS_OK) + if (STILL_OK(s)) s = _randomizePrefix(&(globalNUID.nuid)); if (s != NATS_OK) @@ -240,11 +241,11 @@ _nextNUID(natsNUID *nuid, char *buffer, int bufferLen) if (nuid->seq >= maxSeq) { s = _randomizePrefix(nuid); - if (s == NATS_OK) + if (STILL_OK(s)) s = _resetSequential(nuid); } - if (s == NATS_OK) + if (STILL_OK(s)) { int64_t l; int i; @@ -272,11 +273,12 @@ natsNUID_Next(char *buffer, int bufferLen) { natsStatus s; - natsMutex_Lock(globalNUID.mu); + // <>//<> + // natsMutex_Lock(globalNUID.mu); s = _nextNUID(&(globalNUID.nuid), buffer, bufferLen); - natsMutex_Unlock(globalNUID.mu); + // natsMutex_Unlock(globalNUID.mu); return NATS_UPDATE_ERR_STACK(s); } diff --git a/src/nuid.h b/src/nuid.h index a015a02bc..1f4150226 100644 --- a/src/nuid.h +++ b/src/nuid.h @@ -14,8 +14,6 @@ #ifndef NUID_H_ #define NUID_H_ -#include "status.h" - #define NUID_BUFFER_LEN (12 + 10) // Seed sequential random with math/random and current time and generate crypto prefix. diff --git a/src/opts.c b/src/opts.c index 1c7864635..8827c4c4e 100644 --- a/src/opts.c +++ b/src/opts.c @@ -11,1638 +11,361 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "natsp.h" - #include -#include "mem.h" -#include "opts.h" -#include "util.h" +#include "natsp.h" + #include "conn.h" +#include "opts.h" natsStatus -natsOptions_SetURL(natsOptions *opts, const char* url) +nats_SetURL(natsOptions *opts, const char *url) { natsStatus s = NATS_OK; - LOCK_AND_CHECK_OPTIONS(opts, 0); + CHECK_OPTIONS(opts, 0); if (opts->url != NULL) - { - NATS_FREE(opts->url); opts->url = NULL; - } if (url != NULL) - s = nats_Trim(&(opts->url), url); - - UNLOCK_OPTS(opts); + s = nats_Trim(&(opts->url), opts->pool, url); return NATS_UPDATE_ERR_STACK(s); } -static void -_freeServers(natsOptions *opts) -{ - int i; - - if ((opts->servers == NULL) || (opts->serversCount == 0)) - return; - - for (i = 0; i < opts->serversCount; i++) - NATS_FREE(opts->servers[i]); - - NATS_FREE(opts->servers); - - opts->servers = NULL; - opts->serversCount = 0; -} - natsStatus -natsOptions_SetServers(natsOptions *opts, const char** servers, int serversCount) +nats_SetServers(natsOptions *opts, const char **servers, int serversCount) { - natsStatus s = NATS_OK; - int i; - - LOCK_AND_CHECK_OPTIONS(opts, - (((servers != NULL) && (serversCount <= 0)) - || ((servers == NULL) && (serversCount != 0)))); + natsStatus s = NATS_OK; + int i; - _freeServers(opts); + CHECK_OPTIONS(opts, + (((servers != NULL) && (serversCount <= 0)) || ((servers == NULL) && (serversCount != 0)))); if (servers != NULL) { - opts->servers = (char**) NATS_CALLOC(serversCount, sizeof(char*)); + opts->servers = (char **)nats_palloc(opts->pool, serversCount * sizeof(char *)); if (opts->servers == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); - for (i = 0; (s == NATS_OK) && (i < serversCount); i++) + for (i = 0; (STILL_OK(s)) && (i < serversCount); i++) { - s = nats_Trim(&(opts->servers[i]), servers[i]); - if (s == NATS_OK) + s = nats_Trim(&(opts->servers[i]), opts->pool, servers[i]); + if (STILL_OK(s)) opts->serversCount++; } } - if (s != NATS_OK) - _freeServers(opts); - - UNLOCK_OPTS(opts); - return NATS_UPDATE_ERR_STACK(s); } natsStatus -natsOptions_SetNoRandomize(natsOptions *opts, bool noRandomize) +nats_SetNoRandomize(natsOptions *opts, bool noRandomize) { - natsStatus s = NATS_OK; - - LOCK_AND_CHECK_OPTIONS(opts, 0); + natsStatus s = NATS_OK; - opts->noRandomize = noRandomize; + CHECK_OPTIONS(opts, 0); - UNLOCK_OPTS(opts); + opts->net.noRandomize = noRandomize; return s; } natsStatus -natsOptions_SetTimeout(natsOptions *opts, int64_t timeout) +nats_SetTimeout(natsOptions *opts, int64_t timeout) { - LOCK_AND_CHECK_OPTIONS(opts, (timeout < 0)); - - opts->timeout = timeout; + CHECK_OPTIONS(opts, (timeout < 0)); - UNLOCK_OPTS(opts); + opts->net.timeout = timeout; return NATS_OK; } - natsStatus -natsOptions_SetName(natsOptions *opts, const char *name) +nats_SetName(natsOptions *opts, const char *name) { - natsStatus s = NATS_OK; + natsStatus s = NATS_OK; - LOCK_AND_CHECK_OPTIONS(opts, 0); + CHECK_OPTIONS(opts, 0); - NATS_FREE(opts->name); opts->name = NULL; if (name != NULL) { - opts->name = NATS_STRDUP(name); + opts->name = nats_pstrdupC(opts->pool, name); if (opts->name == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); } - UNLOCK_OPTS(opts); - return s; } natsStatus -natsOptions_SetUserInfo(natsOptions *opts, const char *user, const char *password) +nats_SetUserInfo(natsOptions *opts, const char *user, const char *password) { - natsStatus s = NATS_OK; + natsStatus s = NATS_OK; - LOCK_AND_CHECK_OPTIONS(opts, 0); + CHECK_OPTIONS(opts, 0); - NATS_FREE(opts->user); - opts->user= NULL; - NATS_FREE(opts->password); + opts->user = NULL; opts->password = NULL; if (user != NULL) { - opts->user = NATS_STRDUP(user); - if (opts->user== NULL) + opts->user = nats_pstrdupC(opts->pool, user); + if (opts->user == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); } - if ((s == NATS_OK) && (password != NULL)) + if ((STILL_OK(s)) && (password != NULL)) { - opts->password = NATS_STRDUP(password); + opts->password = nats_pstrdupC(opts->pool, password); if (opts->password == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); } - UNLOCK_OPTS(opts); - return s; } natsStatus -natsOptions_SetToken(natsOptions *opts, const char *token) +nats_SetVerbose(natsOptions *opts, bool verbose) { - natsStatus s = NATS_OK; - - LOCK_AND_CHECK_OPTIONS(opts, 0); + CHECK_OPTIONS(opts, 0); - if ((token != NULL) && (opts->tokenCb != NULL)) - s = nats_setError(NATS_ILLEGAL_STATE, "%s", "Cannot set a token if a token handler has already been set"); - else - { - NATS_FREE(opts->token); - opts->token = NULL; - if (token != NULL) - { - opts->token = NATS_STRDUP(token); - if (opts->token == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - } + opts->proto.verbose = verbose; - UNLOCK_OPTS(opts); - - return s; + return NATS_OK; } natsStatus -natsOptions_SetTokenHandler(natsOptions *opts, natsTokenHandler tokenCb, void *closure) +nats_SetPedantic(natsOptions *opts, bool pedantic) { - natsStatus s = NATS_OK; - - LOCK_AND_CHECK_OPTIONS(opts, 0); + CHECK_OPTIONS(opts, 0); - if ((tokenCb != NULL) && (opts->token != NULL)) - s = nats_setError(NATS_ILLEGAL_STATE, "%s", "Cannot set a token handler if a token has already been set"); - else - { - opts->tokenCb = tokenCb; - opts->tokenCbClosure = closure; - } + opts->proto.pedantic = pedantic; - UNLOCK_OPTS(opts); - - return s; + return NATS_OK; } -static void -natsSSLCtx_release(natsSSLCtx *ctx) +natsStatus +nats_SetPingInterval(natsOptions *opts, int64_t interval) { - int refs; - - if (ctx == NULL) - return; - - natsMutex_Lock(ctx->lock); + CHECK_OPTIONS(opts, 0); - refs = --(ctx->refs); + opts->proto.pingInterval = interval; - natsMutex_Unlock(ctx->lock); - - if (refs == 0) - { - NATS_FREE(ctx->expectedHostname); - SSL_CTX_free(ctx->ctx); - natsMutex_Destroy(ctx->lock); - NATS_FREE(ctx); - } -} - -static natsSSLCtx* -natsSSLCtx_retain(natsSSLCtx *ctx) -{ - natsMutex_Lock(ctx->lock); - ctx->refs++; - natsMutex_Unlock(ctx->lock); - - return ctx; + return NATS_OK; } -#if defined(NATS_HAS_TLS) - -static natsStatus -_createSSLCtx(natsSSLCtx **newCtx) +natsStatus +nats_SetMaxPingsOut(natsOptions *opts, int maxPignsOut) { - natsStatus s = NATS_OK; - natsSSLCtx *ctx = NULL; - - ctx = (natsSSLCtx*) NATS_CALLOC(1, sizeof(natsSSLCtx)); - if (ctx == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - - if (s == NATS_OK) - { - ctx->refs = 1; - - s = natsMutex_Create(&(ctx->lock)); - } - if (s == NATS_OK) - { -#if defined(NATS_USE_OPENSSL_1_1) - ctx->ctx = SSL_CTX_new(TLS_client_method()); -#else - ctx->ctx = SSL_CTX_new(TLSv1_2_client_method()); -#endif - if (ctx->ctx == NULL) - s = nats_setError(NATS_SSL_ERROR, - "Unable to create SSL context: %s", - NATS_SSL_ERR_REASON_STRING); - } - - if (s == NATS_OK) - { - (void) SSL_CTX_set_mode(ctx->ctx, SSL_MODE_AUTO_RETRY); - -#if defined(NATS_USE_OPENSSL_1_1) - SSL_CTX_set_min_proto_version(ctx->ctx, TLS1_2_VERSION); -#else - SSL_CTX_set_options(ctx->ctx, SSL_OP_NO_SSLv2); - SSL_CTX_set_options(ctx->ctx, SSL_OP_NO_SSLv3); -#endif - SSL_CTX_set_default_verify_paths(ctx->ctx); + CHECK_OPTIONS(opts, 0); - *newCtx = ctx; - } - else if (ctx != NULL) - { - natsSSLCtx_release(ctx); - } + opts->proto.maxPingsOut = maxPignsOut; - return NATS_UPDATE_ERR_STACK(s); + return NATS_OK; } -static natsStatus -_getSSLCtx(natsOptions *opts) +natsStatus +nats_SetAllowReconnect(natsOptions *opts, bool allow) { - natsStatus s; - - s = nats_sslInit(); - if ((s == NATS_OK) && (opts->sslCtx != NULL)) - { - bool createNew = false; - - natsMutex_Lock(opts->sslCtx->lock); - - // If this context is retained by a cloned natsOptions, we need to - // release it and create a new context. - if (opts->sslCtx->refs > 1) - createNew = true; - - natsMutex_Unlock(opts->sslCtx->lock); + CHECK_OPTIONS(opts, 0); - if (createNew) - { - natsSSLCtx_release(opts->sslCtx); - opts->sslCtx = NULL; - } - else - { - // We can use this ssl context. - return NATS_OK; - } - } - - if (s == NATS_OK) - s = _createSSLCtx(&(opts->sslCtx)); + opts->net.allowReconnect = allow; - return NATS_UPDATE_ERR_STACK(s); + return NATS_OK; } natsStatus -natsOptions_SetSecure(natsOptions *opts, bool secure) +nats_SetMaxReconnect(natsOptions *opts, int maxReconnect) { - natsStatus s = NATS_OK; - - LOCK_AND_CHECK_OPTIONS(opts, 0); - - if (!secure && (opts->sslCtx != NULL)) - { - natsSSLCtx_release(opts->sslCtx); - opts->sslCtx = NULL; - } - else if (secure && (opts->sslCtx == NULL)) - { - s = _getSSLCtx(opts); - } - - if (s == NATS_OK) - opts->secure = secure; + CHECK_OPTIONS(opts, 0); - UNLOCK_OPTS(opts); + opts->net.maxReconnect = maxReconnect; - return NATS_UPDATE_ERR_STACK(s); + return NATS_OK; } natsStatus -natsOptions_LoadCATrustedCertificates(natsOptions *opts, const char *fileName) +nats_SetReconnectWait(natsOptions *opts, int64_t reconnectWait) { - natsStatus s = NATS_OK; - - LOCK_AND_CHECK_OPTIONS(opts, ((fileName == NULL) || (fileName[0] == '\0'))); - - s = _getSSLCtx(opts); - if (s == NATS_OK) - { - nats_sslRegisterThreadForCleanup(); - - if (SSL_CTX_load_verify_locations(opts->sslCtx->ctx, fileName, NULL) != 1) - { - s = nats_setError(NATS_SSL_ERROR, - "Error loading trusted certificates '%s': %s", - fileName, - NATS_SSL_ERR_REASON_STRING); - } - } + CHECK_OPTIONS(opts, (reconnectWait < 0)); - UNLOCK_OPTS(opts); + opts->net.reconnectWait = reconnectWait; - return s; + return NATS_OK; } natsStatus -natsOptions_SetCATrustedCertificates(natsOptions *opts, const char *certs) +nats_SetReconnectJitter(natsOptions *opts, int64_t jitter, int64_t jitterTLS) { - natsStatus s = NATS_OK; - - if (nats_IsStringEmpty(certs)) - { - return nats_setError(NATS_INVALID_ARG, "%s", - "CA certificates can't be NULL nor empty"); - } + CHECK_OPTIONS(opts, ((jitter < 0) || (jitterTLS < 0))); - LOCK_AND_CHECK_OPTIONS(opts, 0); + opts->net.reconnectJitter = jitter; + opts->net.reconnectJitterTLS = jitterTLS; - s = _getSSLCtx(opts); - if (s == NATS_OK) - { - BIO *bio = NULL; - X509_STORE *cts = NULL; - STACK_OF(X509_INFO) *inf = NULL; - int i; - - nats_sslRegisterThreadForCleanup(); - - cts = SSL_CTX_get_cert_store(opts->sslCtx->ctx); - if (cts == NULL) - { - s = nats_setError(NATS_SSL_ERROR, - "unable to get certificates store: %s", - NATS_SSL_ERR_REASON_STRING); - } - if (s == NATS_OK) - { - bio = BIO_new_mem_buf((char*) certs, -1); - if (bio != NULL) - inf = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL); - if ((inf == NULL) || (sk_X509_INFO_num(inf) == 0)) - { - s = nats_setError(NATS_SSL_ERROR, - "unable to get CA certificates: %s", - NATS_SSL_ERR_REASON_STRING); - } - } - for (i = 0; ((s == NATS_OK) && (i < (int)sk_X509_INFO_num(inf))); i++) - { - X509_INFO *itmp = sk_X509_INFO_value(inf, i); - if (itmp->x509) - { - if (X509_STORE_add_cert(cts, itmp->x509) != 1) - { - s = nats_setError(NATS_SSL_ERROR, - "error adding CA certificates: %s", - NATS_SSL_ERR_REASON_STRING); - } - } - if ((s == NATS_OK) && (itmp->crl)) - { - if (X509_STORE_add_crl(cts, itmp->crl) != 1) - { - s = nats_setError(NATS_SSL_ERROR, - "error adding CA CRL: %s", - NATS_SSL_ERR_REASON_STRING); - } - } - } - - if (inf != NULL) - sk_X509_INFO_pop_free(inf, X509_INFO_free); - - if (bio != NULL) - BIO_free(bio); - } - - UNLOCK_OPTS(opts); - - return s; + return NATS_OK; } + natsStatus -natsOptions_LoadCertificatesChain(natsOptions *opts, - const char *certFileName, - const char *keyFileName) +nats_SetIgnoreDiscoveredServers(natsOptions *opts, bool ignore) { - natsStatus s = NATS_OK; - - if ((certFileName == NULL) || (certFileName[0] == '\0') - || (keyFileName == NULL) || (keyFileName[0] == '\0')) - { - return nats_setError(NATS_INVALID_ARG, "%s", - "certificate and key file names can't be NULL nor empty"); - } - - LOCK_AND_CHECK_OPTIONS(opts, 0); - - s = _getSSLCtx(opts); - if (s == NATS_OK) - { - nats_sslRegisterThreadForCleanup(); - - if (SSL_CTX_use_certificate_chain_file(opts->sslCtx->ctx, certFileName) != 1) - { - s = nats_setError(NATS_SSL_ERROR, - "Error loading certificate chain '%s': %s", - certFileName, - NATS_SSL_ERR_REASON_STRING); - } - } - if (s == NATS_OK) - { - if (SSL_CTX_use_PrivateKey_file(opts->sslCtx->ctx, keyFileName, SSL_FILETYPE_PEM) != 1) - { - s = nats_setError(NATS_SSL_ERROR, - "Error loading private key '%s': %s", - keyFileName, - NATS_SSL_ERR_REASON_STRING); - } - } + CHECK_OPTIONS(opts, 0); - UNLOCK_OPTS(opts); + opts->net.ignoreDiscoveredServers = ignore; - return s; + return NATS_OK; } natsStatus -natsOptions_SetCertificatesChain(natsOptions *opts, const char *certStr, const char *keyStr) +nats_SetIPResolutionOrder(natsOptions *opts, int order) { - natsStatus s = NATS_OK; - - if (nats_IsStringEmpty(certStr) || nats_IsStringEmpty(keyStr)) - { - return nats_setError(NATS_INVALID_ARG, "%s", - "certificate and key can't be NULL nor empty"); - } - - LOCK_AND_CHECK_OPTIONS(opts, 0); - - s = _getSSLCtx(opts); - if (s == NATS_OK) - { - X509 *cert = NULL; - BIO *bio = NULL; - - nats_sslRegisterThreadForCleanup(); - - bio = BIO_new_mem_buf((char*) certStr, -1); - if ((bio == NULL) || ((cert = PEM_read_bio_X509(bio, NULL, 0, NULL)) == NULL)) - { - s = nats_setError(NATS_SSL_ERROR, - "Error creating certificate: %s", - NATS_SSL_ERR_REASON_STRING); - } - if ((s == NATS_OK) && (SSL_CTX_use_certificate(opts->sslCtx->ctx, cert) != 1)) - { - s = nats_setError(NATS_SSL_ERROR, - "Error using certificate: %s", - NATS_SSL_ERR_REASON_STRING); - } - if (cert != NULL) - X509_free(cert); - if (bio != NULL) - BIO_free(bio); - } - if (s == NATS_OK) - { - BIO *bio = NULL; - EVP_PKEY *pkey = NULL; + CHECK_OPTIONS(opts, ((order != 0) && (order != 4) && (order != 6) && (order != 46) && (order != 64))); - bio = BIO_new_mem_buf((char*) keyStr, -1); - if ((bio == NULL) || ((pkey = PEM_read_bio_PrivateKey(bio, NULL, 0, NULL)) == NULL)) - { - s = nats_setError(NATS_SSL_ERROR, - "Error creating key: %s", - NATS_SSL_ERR_REASON_STRING); - } - - if ((s == NATS_OK) && (SSL_CTX_use_PrivateKey(opts->sslCtx->ctx, pkey) != 1)) - { - s = nats_setError(NATS_SSL_ERROR, - "Error using private key: %s", - NATS_SSL_ERR_REASON_STRING); - } - if (pkey != NULL) - EVP_PKEY_free(pkey); - if (bio != NULL) - BIO_free(bio); - } + opts->net.orderIP = order; - UNLOCK_OPTS(opts); - - return s; + return NATS_OK; } natsStatus -natsOptions_SetCiphers(natsOptions *opts, const char *ciphers) +nats_SetNoEcho(natsOptions *opts, bool noEcho) { - natsStatus s = NATS_OK; - - LOCK_AND_CHECK_OPTIONS(opts, ((ciphers == NULL) || (ciphers[0] == '\0'))); - - s = _getSSLCtx(opts); - if (s == NATS_OK) - { - nats_sslRegisterThreadForCleanup(); - - if (SSL_CTX_set_cipher_list(opts->sslCtx->ctx, ciphers) != 1) - { - s = nats_setError(NATS_SSL_ERROR, - "Error setting ciphers '%s': %s", - ciphers, - NATS_SSL_ERR_REASON_STRING); - } - } - - UNLOCK_OPTS(opts); + CHECK_OPTIONS(opts, 0); + opts->proto.noEcho = noEcho; - return s; + return NATS_OK; } natsStatus -natsOptions_SetCipherSuites(natsOptions *opts, const char *ciphers) +nats_SetFailRequestsOnDisconnect(natsOptions *opts, bool failRequests) { - natsStatus s = NATS_OK; - -#if defined(NATS_USE_OPENSSL_1_1) - LOCK_AND_CHECK_OPTIONS(opts, 0); - - s = _getSSLCtx(opts); - if (s == NATS_OK) - { - nats_sslRegisterThreadForCleanup(); + CHECK_OPTIONS(opts, 0); + opts->net.failRequestsOnDisconnect = failRequests; - if (SSL_CTX_set_ciphersuites(opts->sslCtx->ctx, ciphers) != 1) - { - s = nats_setError(NATS_SSL_ERROR, - "Error setting ciphers '%s': %s", - ciphers, - NATS_SSL_ERR_REASON_STRING); - } - } - - UNLOCK_OPTS(opts); -#else - s = nats_setError(NATS_ERR, "%s", "Setting TLSv1.3 ciphersuites requires OpenSSL 1.1+"); -#endif - - return s; + return NATS_OK; } natsStatus -natsOptions_SetExpectedHostname(natsOptions *opts, const char *hostname) +nats_SetWriteDeadline(natsOptions *opts, int64_t deadline) { - natsStatus s = NATS_OK; + CHECK_OPTIONS(opts, (deadline < 0)); - // Allow hostname to be empty in order to reset... - LOCK_AND_CHECK_OPTIONS(opts, 0); + opts->net.writeDeadline = deadline; - s = _getSSLCtx(opts); - if (s == NATS_OK) - { - NATS_FREE(opts->sslCtx->expectedHostname); - opts->sslCtx->expectedHostname = NULL; - - if (hostname != NULL) - { - opts->sslCtx->expectedHostname = NATS_STRDUP(hostname); - if (opts->sslCtx->expectedHostname == NULL) - { - s = nats_setDefaultError(NATS_NO_MEMORY); - } - } - } - - UNLOCK_OPTS(opts); - - return s; + return NATS_OK; } natsStatus -natsOptions_SkipServerVerification(natsOptions *opts, bool skip) +nats_SetDisableNoResponders(natsOptions *opts, bool disabled) { - natsStatus s = NATS_OK; - - LOCK_AND_CHECK_OPTIONS(opts, 0); - - s = _getSSLCtx(opts); - if (s == NATS_OK) - opts->sslCtx->skipVerify = skip; - - UNLOCK_OPTS(opts); - - return s; -} + CHECK_OPTIONS(opts, 0); -#else + opts->proto.disableNoResponders = disabled; -natsStatus -natsOptions_SetSecure(natsOptions *opts, bool secure) -{ - return nats_setError(NATS_ILLEGAL_STATE, "%s", NO_SSL_ERR); + return NATS_OK; } natsStatus -natsOptions_LoadCATrustedCertificates(natsOptions *opts, const char *fileName) +nats_SetOnConnected(natsOptions *opts, natsOnConnectionEventF cb, void *closure) { - return nats_setError(NATS_ILLEGAL_STATE, "%s", NO_SSL_ERR); -} + CHECK_OPTIONS(opts, 0); -natsStatus -natsOptions_SetCATrustedCertificates(natsOptions *opts, const char *certificates) -{ - return nats_setError(NATS_ILLEGAL_STATE, "%s", NO_SSL_ERR); -} + opts->net.connected = cb; + opts->net.connectedClosure = closure; -natsStatus -natsOptions_LoadCertificatesChain(natsOptions *opts, - const char *certFileName, - const char *keyFileName) -{ - return nats_setError(NATS_ILLEGAL_STATE, "%s", NO_SSL_ERR); + return NATS_OK; } natsStatus -natsOptions_SetCertificatesChain(natsOptions *opts, const char *certStr, const char *keyStr) +nats_SetOnConnectionClosed(natsOptions *opts, natsOnConnectionEventF cb, void *closure) { - return nats_setError(NATS_ILLEGAL_STATE, "%s", NO_SSL_ERR); -} + CHECK_OPTIONS(opts, 0); -natsStatus -natsOptions_SetCiphers(natsOptions *opts, const char *ciphers) -{ - return nats_setError(NATS_ILLEGAL_STATE, "%s", NO_SSL_ERR); -} + opts->net.closed = cb; + opts->net.closedClosure = closure; -natsStatus -natsOptions_SetExpectedHostname(natsOptions *opts, const char *hostname) -{ - return nats_setError(NATS_ILLEGAL_STATE, "%s", NO_SSL_ERR); + return NATS_OK; } natsStatus -natsOptions_SkipServerVerification(natsOptions *opts, bool skip) +nats_createOptions(natsOptions **newOpts, natsPool *pool) { - return nats_setError(NATS_ILLEGAL_STATE, "%s", NO_SSL_ERR); -} - -#endif + natsStatus s; + natsOptions *opts = NULL; -natsStatus -natsOptions_SetVerbose(natsOptions *opts, bool verbose) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); + // Ensure the library is loaded + s = nats_open(); + IFOK(s, CHECK_NO_MEMORY(opts = nats_palloc(pool, sizeof(natsOptions)))); + if (s != NATS_OK) + return s; - opts->verbose = verbose; + opts->pool = pool; - UNLOCK_OPTS(opts); + opts->net.allowReconnect = true; + opts->net.maxReconnect = NATS_OPTS_DEFAULT_MAX_RECONNECT; + opts->net.reconnectJitter = NATS_OPTS_DEFAULT_RECONNECT_JITTER; + opts->net.reconnectJitterTLS = NATS_OPTS_DEFAULT_RECONNECT_JITTER_TLS; + opts->net.reconnectWait = NATS_OPTS_DEFAULT_RECONNECT_WAIT; + opts->net.timeout = NATS_OPTS_DEFAULT_TIMEOUT; - return NATS_OK; -} + opts->proto.maxPingsOut = NATS_OPTS_DEFAULT_MAX_PING_OUT; + opts->proto.pingInterval = NATS_OPTS_DEFAULT_PING_INTERVAL; -natsStatus -natsOptions_SetPedantic(natsOptions *opts, bool pedantic) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); + opts->secure.secure = false; - opts->pedantic = pedantic; + opts->mem = nats_defaultMemOptions; - UNLOCK_OPTS(opts); + *newOpts = opts; return NATS_OK; } -natsStatus -natsOptions_SetPingInterval(natsOptions *opts, int64_t interval) +natsOptions * +nats_GetDefaultOptions(void) { - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->pingInterval = interval; - - UNLOCK_OPTS(opts); - - return NATS_OK; + if (NOT_OK(nats_open())) + return NULL; + natsOptions *opts = NULL; + nats_createOptions(&opts, nats_globalPool()); + return opts; } natsStatus -natsOptions_SetMaxPingsOut(natsOptions *opts, int maxPignsOut) +nats_cloneOptions(natsOptions **newOptions, natsPool *pool, natsOptions *opts) { - LOCK_AND_CHECK_OPTIONS(opts, 0); + natsStatus s = NATS_OK; + natsOptions *cloned = NULL; - opts->maxPingsOut = maxPignsOut; + if ((s = nats_createOptions(&cloned, pool)) != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); - UNLOCK_OPTS(opts); + // Make a blind copy first... - return NATS_OK; -} + size_t off = offsetof(natsOptions, __memcpy_from_here); + memset((char *)cloned, 0, off); + memcpy((char *)cloned + off, (char *)opts+off, sizeof(natsOptions)-off); + cloned->pool = pool; -natsStatus -natsOptions_SetIOBufSize(natsOptions *opts, int ioBufSize) -{ - LOCK_AND_CHECK_OPTIONS(opts, (ioBufSize < 0)); + if (opts->name != NULL) + s = nats_SetName(cloned, opts->name); - opts->ioBufSize = ioBufSize; + if ((STILL_OK(s)) && (opts->url != NULL)) + s = nats_SetURL(cloned, opts->url); - UNLOCK_OPTS(opts); + if ((STILL_OK(s)) && (opts->servers != NULL)) + s = nats_SetServers(cloned, + (const char **)opts->servers, + opts->serversCount); - return NATS_OK; -} - -natsStatus -natsOptions_SetAllowReconnect(natsOptions *opts, bool allow) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->allowReconnect = allow; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetMaxReconnect(natsOptions *opts, int maxReconnect) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->maxReconnect = maxReconnect; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetReconnectWait(natsOptions *opts, int64_t reconnectWait) -{ - LOCK_AND_CHECK_OPTIONS(opts, (reconnectWait < 0)); - - opts->reconnectWait = reconnectWait; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetReconnectJitter(natsOptions *opts, int64_t jitter, int64_t jitterTLS) -{ - LOCK_AND_CHECK_OPTIONS(opts, ((jitter < 0) || (jitterTLS < 0))); - - opts->reconnectJitter = jitter; - opts->reconnectJitterTLS = jitterTLS; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetCustomReconnectDelay(natsOptions *opts, - natsCustomReconnectDelayHandler cb, - void *closure) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->customReconnectDelayCB = cb; - opts->customReconnectDelayCBClosure = closure; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetReconnectBufSize(natsOptions *opts, int reconnectBufSize) -{ - LOCK_AND_CHECK_OPTIONS(opts, (reconnectBufSize < 0)); - - opts->reconnectBufSize = reconnectBufSize; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetMaxPendingMsgs(natsOptions *opts, int maxPending) -{ - LOCK_AND_CHECK_OPTIONS(opts, (maxPending <= 0)); - - opts->maxPendingMsgs = maxPending; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetMaxPendingBytes(natsOptions* opts, int64_t maxPending) -{ - LOCK_AND_CHECK_OPTIONS(opts, (maxPending <= 0)); - - opts->maxPendingBytes = maxPending; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetErrorHandler(natsOptions *opts, natsErrHandler errHandler, - void *closure) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->asyncErrCb = errHandler; - opts->asyncErrCbClosure = closure; - - if (opts->asyncErrCb == NULL) - opts->asyncErrCb = natsConn_defaultErrHandler; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetClosedCB(natsOptions *opts, natsConnectionHandler closedCb, - void *closure) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->closedCb = closedCb; - opts->closedCbClosure = closure; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_setMicroCallbacks(natsOptions *opts, natsConnectionHandler closed, natsErrHandler errHandler) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->microClosedCb = closed; - opts->microAsyncErrCb = errHandler; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetDisconnectedCB(natsOptions *opts, - natsConnectionHandler disconnectedCb, - void *closure) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->disconnectedCb = disconnectedCb; - opts->disconnectedCbClosure = closure; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetReconnectedCB(natsOptions *opts, - natsConnectionHandler reconnectedCb, - void *closure) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->reconnectedCb = reconnectedCb; - opts->reconnectedCbClosure = closure; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetDiscoveredServersCB(natsOptions *opts, - natsConnectionHandler discoveredServersCb, - void *closure) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->discoveredServersCb = discoveredServersCb; - opts->discoveredServersClosure = closure; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetIgnoreDiscoveredServers(natsOptions *opts, bool ignore) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->ignoreDiscoveredServers = ignore; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetLameDuckModeCB(natsOptions *opts, - natsConnectionHandler lameDuckCb, - void *closure) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->lameDuckCb = lameDuckCb; - opts->lameDuckClosure = closure; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetEventLoop(natsOptions *opts, - void *loop, - natsEvLoop_Attach attachCb, - natsEvLoop_ReadAddRemove readCb, - natsEvLoop_WriteAddRemove writeCb, - natsEvLoop_Detach detachCb) -{ - LOCK_AND_CHECK_OPTIONS(opts, (loop == NULL) - || (attachCb == NULL) - || (readCb == NULL) - || (writeCb == NULL) - || (detachCb == NULL)); - - opts->evLoop = loop; - opts->evCbs.attach = attachCb; - opts->evCbs.read = readCb; - opts->evCbs.write = writeCb; - opts->evCbs.detach = detachCb; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_UseGlobalMessageDelivery(natsOptions *opts, bool global) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - // Sets if the subscriptions created from the connection will - // create their own delivery thread or use the one(s) from - // the library. - opts->libMsgDelivery = global; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_IPResolutionOrder(natsOptions *opts, int order) -{ - LOCK_AND_CHECK_OPTIONS(opts, ((order != 0) - && (order != 4) - && (order != 6) - && (order != 46) - && (order != 64))); - - opts->orderIP = order; + if ((STILL_OK(s)) && (opts->user != NULL)) + s = nats_SetUserInfo(cloned, opts->user, opts->password); - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetSendAsap(natsOptions *opts, bool sendAsap) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - opts->sendAsap = sendAsap; - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetNoEcho(natsOptions *opts, bool noEcho) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - opts->noEcho = noEcho; - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetRetryOnFailedConnect(natsOptions *opts, bool retry, - natsConnectionHandler connectedCb, void *closure) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - opts->retryOnFailedConnect = retry; - if (!retry) - { - opts->connectedCb = NULL; - opts->connectedCbClosure = NULL; - } - else - { - opts->connectedCb = connectedCb; - opts->connectedCbClosure = closure; - } - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_UseOldRequestStyle(natsOptions *opts, bool useOldStype) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - opts->useOldRequestStyle = useOldStype; - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetFailRequestsOnDisconnect(natsOptions *opts, bool failRequests) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - opts->failRequestsOnDisconnect = failRequests; - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -static void -_freeUserCreds(userCreds *uc) -{ - if (uc == NULL) - return; - - NATS_FREE(uc->userOrChainedFile); - NATS_FREE(uc->seedFile); - NATS_FREE(uc->jwtAndSeedContent); - NATS_FREE(uc); -} - -static natsStatus -_createUserCreds(userCreds **puc, const char *uocf, const char *sf, const char* jwtAndSeedContent) -{ - natsStatus s = NATS_OK; - userCreds *uc = NULL; - - uc = NATS_CALLOC(1, sizeof(userCreds)); - if (uc == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - // in case of content, we do not need to read the files anymore - if (jwtAndSeedContent != NULL) - { - uc->jwtAndSeedContent = NATS_STRDUP(jwtAndSeedContent); - if (uc->jwtAndSeedContent == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - else - { - if (uocf) - { - uc->userOrChainedFile = NATS_STRDUP(uocf); - if (uc->userOrChainedFile == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - if ((s == NATS_OK) && sf != NULL) - { - uc->seedFile = NATS_STRDUP(sf); - if (uc->seedFile == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - } if (s != NATS_OK) - _freeUserCreds(uc); - else - *puc = uc; - - return NATS_UPDATE_ERR_STACK(s); -} - -static void -_setAndUnlockOptsFromUserCreds(natsOptions *opts, userCreds *uc) -{ - // Free previous object - _freeUserCreds(opts->userCreds); - // Set to new one (possibly NULL) - opts->userCreds = uc; - - if (uc != NULL) - { - opts->userJWTHandler = natsConn_userCreds; - opts->userJWTClosure = (void*) uc; - - opts->sigHandler = natsConn_signatureHandler; - opts->sigClosure = (void*) uc; - - // NKey and UserCreds are mutually exclusive. - if (opts->nkey != NULL) - { - NATS_FREE(opts->nkey); - opts->nkey = NULL; - } - } - else - { - opts->userJWTHandler = NULL; - opts->userJWTClosure = NULL; - - opts->sigHandler = NULL; - opts->sigClosure = NULL; - } - - UNLOCK_OPTS(opts); -} - -natsStatus -natsOptions_SetUserCredentialsFromFiles(natsOptions *opts, const char *userOrChainedFile, const char *seedFile) -{ - natsStatus s = NATS_OK; - userCreds *uc = NULL; - - LOCK_AND_CHECK_OPTIONS(opts, 0); - - // Both files can be NULL (to unset), but if seeFile can't - // be set if userOrChainedFile is not. - if (nats_IsStringEmpty(userOrChainedFile) && !nats_IsStringEmpty(seedFile)) - { - UNLOCK_OPTS(opts); - return nats_setError(NATS_INVALID_ARG, "%s", "user or chained file need to be specified"); - } - - if (!nats_IsStringEmpty(userOrChainedFile)) - { - s = _createUserCreds(&uc, userOrChainedFile, seedFile, NULL); - if (s != NATS_OK) - { - UNLOCK_OPTS(opts); - return NATS_UPDATE_ERR_STACK(s); - } - } - - _setAndUnlockOptsFromUserCreds(opts, uc); - - return NATS_OK; -} - -natsStatus -natsOptions_SetUserCredentialsFromMemory(natsOptions *opts, const char *jwtAndSeedContent) -{ - natsStatus s = NATS_OK; - userCreds *uc = NULL; - - LOCK_AND_CHECK_OPTIONS(opts, 0); - - // if content is not NULL create user creds from it; - // otherwise NULL will later lead to setting handlers to NULL - if (jwtAndSeedContent != NULL) - { - s = _createUserCreds(&uc, NULL, NULL, jwtAndSeedContent); - if (s != NATS_OK) - { - UNLOCK_OPTS(opts); - return NATS_UPDATE_ERR_STACK(s); - } - } - - _setAndUnlockOptsFromUserCreds(opts, uc); - - return NATS_OK; -} - -natsStatus -natsOptions_SetUserCredentialsCallbacks(natsOptions *opts, - natsUserJWTHandler ujwtCB, - void *ujwtClosure, - natsSignatureHandler sigCB, - void *sigClosure) -{ - // Callbacks can all be NULL (to unset), however, if one is set, - // the other must be. - LOCK_AND_CHECK_OPTIONS(opts, - (((ujwtCB != NULL) && (sigCB == NULL)) || - ((ujwtCB == NULL) && (sigCB != NULL)))); - - _freeUserCreds(opts->userCreds); - opts->userCreds = NULL; - - opts->userJWTHandler = ujwtCB; - opts->userJWTClosure = ujwtClosure; - - opts->sigHandler = sigCB; - opts->sigClosure = sigClosure; - - // If setting callbacks and there is an NKey, erase it - // (NKey and UserCreds are mutually exclusive). - if ((ujwtCB != NULL) && (opts->nkey != NULL)) - { - NATS_FREE(opts->nkey); - opts->nkey = NULL; - } - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_SetNKey(natsOptions *opts, - const char *pubKey, - natsSignatureHandler sigCB, - void *sigClosure) -{ - char *nk = NULL; - - // If pubKey is not empty, then signature must be specified - LOCK_AND_CHECK_OPTIONS(opts, - (!nats_IsStringEmpty(pubKey) && (sigCB == NULL))); - - if (!nats_IsStringEmpty(pubKey)) - { - nk = NATS_STRDUP(pubKey); - if (nk == NULL) - { - UNLOCK_OPTS(opts); - return nats_setDefaultError(NATS_NO_MEMORY); - } - } - - // Free previous value - NATS_FREE(opts->nkey); - - // Set new values - opts->nkey = nk; - opts->sigHandler = sigCB; - opts->sigClosure = sigClosure; - - // If we set an NKey, make sure that userJWT is unset - // since the two are mutually exclusive. - if (nk != NULL) { - if (opts->userCreds != NULL) - { - _freeUserCreds(opts->userCreds); - opts->userCreds = NULL; - } - opts->userJWTHandler = NULL; - opts->userJWTClosure = NULL; - } - UNLOCK_OPTS(opts); - return NATS_OK; -} - -natsStatus -natsOptions_SetNKeyFromSeed(natsOptions *opts, - const char *pubKey, - const char *seedFile) -{ - natsStatus s; - char *nk = NULL; - userCreds *uc = NULL; - - LOCK_AND_CHECK_OPTIONS(opts, - (!nats_IsStringEmpty(pubKey) && nats_IsStringEmpty(seedFile))); - - if (!nats_IsStringEmpty(pubKey)) - { - nk = NATS_STRDUP(pubKey); - if (nk == NULL) - { - UNLOCK_OPTS(opts); - return nats_setDefaultError(NATS_NO_MEMORY); - } - s = _createUserCreds(&uc, NULL, seedFile, NULL); - if (s != NATS_OK) - { - NATS_FREE(nk); - UNLOCK_OPTS(opts); - return NATS_UPDATE_ERR_STACK(s); - } - } - - // Free previous values - NATS_FREE(opts->nkey); - _freeUserCreds(opts->userCreds); - - // Set new values - opts->nkey = nk; - opts->sigHandler = (nk == NULL ? NULL : natsConn_signatureHandler); - opts->sigClosure = (nk == NULL ? NULL : (void*) uc); - opts->userCreds = (nk == NULL ? NULL : uc); - // NKey and JWT mutually exclusive - opts->userJWTHandler = NULL; - opts->userJWTClosure = NULL; - - UNLOCK_OPTS(opts); - return NATS_OK; -} - -natsStatus -natsOptions_SetWriteDeadline(natsOptions *opts, int64_t deadline) -{ - LOCK_AND_CHECK_OPTIONS(opts, (deadline < 0)); - - opts->writeDeadline = deadline; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -natsOptions_DisableNoResponders(natsOptions *opts, bool disabled) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->disableNoResponders = disabled; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -static natsStatus -_setCustomInboxPrefix(natsOptions *opts, const char *inboxPrefix, bool check) -{ - natsStatus s = NATS_OK; - - LOCK_AND_CHECK_OPTIONS(opts, 0); - - NATS_FREE(opts->inboxPfx); - opts->inboxPfx = NULL; - - if (!nats_IsStringEmpty(inboxPrefix)) - { - // If not called from clone(), we need to check the validity of the - // inbox prefix. - if (check && !nats_IsSubjectValid(inboxPrefix, false)) - s = nats_setError(NATS_INVALID_ARG, "Invalid inbox prefix '%s'", inboxPrefix); - - if (s == NATS_OK) - { - // If invoked from user, there will not be the last '.', which - // we will add here. - if (check) - { - if (nats_asprintf(&opts->inboxPfx, "%s.", inboxPrefix) < 0) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - else - { - // We are invoked from clone(), simply duplicate the string. - DUP_STRING(s, opts->inboxPfx, inboxPrefix); - } - } - } - - UNLOCK_OPTS(opts); - - return s; -} - -natsStatus -natsOptions_SetCustomInboxPrefix(natsOptions *opts, const char *inboxPrefix) -{ - natsStatus s = _setCustomInboxPrefix(opts, inboxPrefix, true); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsOptions_SetMessageBufferPadding(natsOptions *opts, int paddingSize) -{ - LOCK_AND_CHECK_OPTIONS(opts, (paddingSize < 0)); - - opts->payloadPaddingSize = paddingSize; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -static void -_freeOptions(natsOptions *opts) -{ - if (opts == NULL) - return; - - NATS_FREE(opts->url); - NATS_FREE(opts->name); - _freeServers(opts); - NATS_FREE(opts->user); - NATS_FREE(opts->password); - NATS_FREE(opts->token); - NATS_FREE(opts->nkey); - natsSSLCtx_release(opts->sslCtx); - _freeUserCreds(opts->userCreds); - NATS_FREE(opts->inboxPfx); - natsMutex_Destroy(opts->mu); - NATS_FREE(opts); -} - -natsStatus -natsOptions_Create(natsOptions **newOpts) -{ - natsStatus s; - natsOptions *opts = NULL; - - // Ensure the library is loaded - s = nats_Open(-1); - if (s != NATS_OK) - return s; - - opts = (natsOptions*) NATS_CALLOC(1, sizeof(natsOptions)); - if (opts == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - if (natsMutex_Create(&(opts->mu)) != NATS_OK) - { - NATS_FREE(opts); - return NATS_UPDATE_ERR_STACK(NATS_NO_MEMORY); - } - - opts->allowReconnect = true; - opts->secure = false; - opts->maxReconnect = NATS_OPTS_DEFAULT_MAX_RECONNECT; - opts->reconnectWait = NATS_OPTS_DEFAULT_RECONNECT_WAIT; - opts->pingInterval = NATS_OPTS_DEFAULT_PING_INTERVAL; - opts->maxPingsOut = NATS_OPTS_DEFAULT_MAX_PING_OUT; - opts->ioBufSize = NATS_OPTS_DEFAULT_IO_BUF_SIZE; - opts->maxPendingMsgs = NATS_OPTS_DEFAULT_MAX_PENDING_MSGS; - opts->maxPendingBytes = -1; - opts->timeout = NATS_OPTS_DEFAULT_TIMEOUT; - opts->libMsgDelivery = natsLib_isLibHandlingMsgDeliveryByDefault(); - opts->writeDeadline = natsLib_defaultWriteDeadline(); - opts->reconnectBufSize = NATS_OPTS_DEFAULT_RECONNECT_BUF_SIZE; - opts->reconnectJitter = NATS_OPTS_DEFAULT_RECONNECT_JITTER; - opts->reconnectJitterTLS = NATS_OPTS_DEFAULT_RECONNECT_JITTER_TLS; - opts->asyncErrCb = natsConn_defaultErrHandler; - - *newOpts = opts; - - return NATS_OK; -} - -natsOptions* -natsOptions_clone(natsOptions *opts) -{ - natsStatus s = NATS_OK; - natsOptions *cloned = NULL; - int muSize; - - if ((s = natsOptions_Create(&cloned)) != NATS_OK) - { - NATS_UPDATE_ERR_STACK(s); - return NULL; - } - - natsMutex_Lock(opts->mu); - - muSize = sizeof(cloned->mu); - - // Make a blind copy first... - memcpy((char*)cloned + muSize, (char*)opts + muSize, - sizeof(natsOptions) - muSize); - - // Then remove all pointers, so that if we fail while - // strduping them, and free the cloned, we don't free the strings - // from the original. - cloned->name = NULL; - cloned->servers = NULL; - cloned->url = NULL; - cloned->sslCtx = NULL; - cloned->user = NULL; - cloned->password= NULL; - cloned->token = NULL; - cloned->nkey = NULL; - cloned->userCreds = NULL; - cloned->inboxPfx = NULL; - - // Also, set the number of servers count to 0, until we update - // it (if necessary) when calling SetServers. - cloned->serversCount = 0; - - if (opts->name != NULL) - s = natsOptions_SetName(cloned, opts->name); - - if ((s == NATS_OK) && (opts->url != NULL)) - s = natsOptions_SetURL(cloned, opts->url); - - if ((s == NATS_OK) && (opts->servers != NULL)) - s = natsOptions_SetServers(cloned, - (const char**)opts->servers, - opts->serversCount); - - if ((s == NATS_OK) && (opts->user != NULL)) - s = natsOptions_SetUserInfo(cloned, opts->user, opts->password); - - if ((s == NATS_OK) && (opts->token != NULL)) - s = natsOptions_SetToken(cloned, opts->token); - - if ((s == NATS_OK) && (opts->sslCtx != NULL)) - cloned->sslCtx = natsSSLCtx_retain(opts->sslCtx); - - if ((s == NATS_OK) && (opts->nkey != NULL)) - { - if (opts->userCreds != NULL) - s = natsOptions_SetNKeyFromSeed(cloned, opts->nkey, opts->userCreds->seedFile); - else - s = natsOptions_SetNKey(cloned, opts->nkey, opts->sigHandler, opts->sigClosure); - } - else if ((s == NATS_OK) && (opts->userCreds != NULL)) - { - if (opts->userCreds->jwtAndSeedContent != NULL) - { - s = natsOptions_SetUserCredentialsFromMemory(cloned, - opts->userCreds->jwtAndSeedContent); - } - else - { - s = natsOptions_SetUserCredentialsFromFiles(cloned, - opts->userCreds->userOrChainedFile, - opts->userCreds->seedFile); - } - } - if ((s == NATS_OK) && (opts->inboxPfx != NULL)) - s = _setCustomInboxPrefix(cloned, opts->inboxPfx, false); - - if (s != NATS_OK) - { - _freeOptions(cloned); cloned = NULL; NATS_UPDATE_ERR_STACK(s); } - natsMutex_Unlock(opts->mu); - - return cloned; -} - -void -natsOptions_Destroy(natsOptions *opts) -{ - if (opts == NULL) - return; - - _freeOptions(opts); + *newOptions = cloned; + return NATS_OK; } diff --git a/src/opts.h b/src/opts.h index 81a39aa27..5fc91ba79 100644 --- a/src/opts.h +++ b/src/opts.h @@ -14,42 +14,143 @@ #ifndef OPTS_H_ #define OPTS_H_ -#include "natsp.h" +#define CHECK_OPTIONS(o, c) \ + if (((o) == NULL) || ((c))) \ + return nats_setDefaultError(NATS_INVALID_ARG); -static inline void natsOptions_lock(natsOptions *opts) +typedef struct __natsMemOptions_s { - natsMutex_Lock(opts->mu); -} + // Heap memory is generally allocated in chunks of this size, with the + // exception of rawAllocs + size_t heapPageSize; + + // The smallest amount of free space in a read buffer that warrants using it + // for a read operation. If the buffer does not have this much space, do not + // use it and get a new one to read into. + size_t readBufferMin; + + // The size of a single read buffer. + size_t readBufferSize; + + // The initial size of the write queue (number of buffer references to + // hold). + size_t writeQueueBuffers; + + // The maximum number of buffers that can be held pending in the write + // queue. + size_t writeQueueMaxBuffers; -static inline void natsOptions_unlock(natsOptions *opts) + // The maximum number of bytes that can be held pending in the write queue. + int64_t writeQueueMaxBytes; + + // Attempt to batch up to this many bytes (from small buffers) when writing + // to the socket. + size_t writeMinBytes; +} natsMemOptions; + +static natsMemOptions nats_defaultMemOptions = { + .heapPageSize = 4096, + .readBufferMin = 1024, + .readBufferSize = (64 * 1024), + .writeQueueBuffers = 16, + .writeQueueMaxBuffers = 1024, + .writeQueueMaxBytes = 16 * 1024 * 1024, + .writeMinBytes = (32 * 1024), +}; + +typedef struct __natsNetOptions_s { - natsMutex_Unlock(opts->mu); -} + bool noRandomize; + int64_t timeout; + bool allowReconnect; + int maxReconnect; + int64_t reconnectWait; + int64_t writeDeadline; + int orderIP; // possible values: 0,4,6,46,64 + // If set to true, pending requests will fail with + // NATS_CONNECTION_DISCONNECTED when the library detects a disconnection. + // Otherwise, the requests will remain pending and responses will be + // processed upon reconnect. + bool failRequestsOnDisconnect; + // If set to true, in case of failed connect, tries again using + // reconnect options values. + bool retryOnFailedConnect; + // Reconnect jitter added to reconnect wait + int64_t reconnectJitter; + int64_t reconnectJitterTLS; + bool ignoreDiscoveredServers; -#define LOCK_AND_CHECK_OPTIONS(o, c) \ - if (((o) == NULL) || ((c))) \ - return nats_setDefaultError(NATS_INVALID_ARG); \ - natsMutex_Lock((o)->mu); + natsOnConnectionEventF connected; + void *connectedClosure; + natsOnConnectionEventF closed; + void *closedClosure; + natsOnConnectionEventF error; + void *errorClosure; + natsOnConnectionEventF disconnected; + void *disconnectedClosure; + +} natsNetOptions; +typedef struct __natsSecureOptions_s +{ + bool secure; + +} natsSecureOptions; + +typedef struct __natsProtocolOptions_s +{ + bool verbose; + bool pedantic; + int64_t pingInterval; + int maxPingsOut; + + // NoEcho configures whether the server will echo back messages + // that are sent on this connection if we also have matching subscriptions. + // Note this is supported on servers >= version 1.2. Proto 1 or greater. + bool noEcho; + + // Disable the "no responders" feature. + bool disableNoResponders; + +} natsProtocolOptions; + +struct __natsOptions +{ + // All options values are allocated in this pool, which is passed into + // natsOpts_create. + natsPool *pool; -#define UNLOCK_OPTS(o) natsMutex_Unlock((o)->mu) + // All fields that are not "simple" types, including counters kuke + // serversCount must be declared before __memcpy_from_here, and should be + // manually added to nats_cloneOptions() to duplicate into the target opts' + // pool. + char *name; + char *password; + char **servers; + int serversCount; + char *token; + char *user; + char *url; + uint8_t __memcpy_from_here; -#define NATS_OPTS_DEFAULT_MAX_RECONNECT (60) -#define NATS_OPTS_DEFAULT_TIMEOUT (2 * 1000) // 2 seconds -#define NATS_OPTS_DEFAULT_RECONNECT_WAIT (2 * 1000) // 2 seconds -#define NATS_OPTS_DEFAULT_PING_INTERVAL (2 * 60 * 1000) // 2 minutes -#define NATS_OPTS_DEFAULT_MAX_PING_OUT (2) -#define NATS_OPTS_DEFAULT_IO_BUF_SIZE (32 * 1024) // 32 KB -#define NATS_OPTS_DEFAULT_MAX_PENDING_MSGS (65536) // 65536 messages -#define NATS_OPTS_DEFAULT_MAX_PENDING_BYTES (64 * 1024 * 1024) // 64 MB -#define NATS_OPTS_DEFAULT_RECONNECT_BUF_SIZE (8 * 1024 * 1024) // 8 MB -#define NATS_OPTS_DEFAULT_RECONNECT_JITTER (100) // 100 ms -#define NATS_OPTS_DEFAULT_RECONNECT_JITTER_TLS (1000) // 1 second + natsMemOptions mem; + natsNetOptions net; + natsSecureOptions secure; + natsProtocolOptions proto; +}; -natsOptions* -natsOptions_clone(natsOptions *opts); +#define NATS_OPTS_DEFAULT_MAX_RECONNECT (60) +#define NATS_OPTS_DEFAULT_TIMEOUT (2 * 1000) // 2 seconds +#define NATS_OPTS_DEFAULT_RECONNECT_WAIT (2 * 1000) // 2 seconds +#define NATS_OPTS_DEFAULT_PING_INTERVAL (2 * 60 * 1000) // 2 minutes +#define NATS_OPTS_DEFAULT_MAX_PING_OUT (2) +#define NATS_OPTS_DEFAULT_IO_BUF_SIZE (32 * 1024) // 32 KB +#define NATS_OPTS_DEFAULT_MAX_PENDING_MSGS (65536) // 65536 messages +#define NATS_OPTS_DEFAULT_MAX_PENDING_BYTES (64 * 1024 * 1024) // 64 MB +#define NATS_OPTS_DEFAULT_RECONNECT_JITTER (100) // 100 ms +#define NATS_OPTS_DEFAULT_RECONNECT_JITTER_TLS (1000) // 1 second -natsStatus -natsOptions_setMicroCallbacks(natsOptions *opts, natsConnectionHandler closedCb, natsErrHandler errCb); +natsStatus nats_cloneOptions(natsOptions **newOptions, natsPool *pool, natsOptions *opts); +natsStatus nats_createOptions(natsOptions **newOptions, natsPool *pool); #endif /* OPTS_H_ */ diff --git a/src/parser.c b/src/parser.c deleted file mode 100644 index eb523971c..000000000 --- a/src/parser.c +++ /dev/null @@ -1,940 +0,0 @@ -// Copyright 2015-2020 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 -#include - -#include "natsp.h" -#include "conn.h" -#include "util.h" -#include "mem.h" - -// cloneMsgArg is used when the split buffer scenario has the pubArg in the existing read buffer, but -// we need to hold onto it into the next read. -static natsStatus -_cloneMsgArg(natsConnection *nc) -{ - natsStatus s; - int subjLen = natsBuf_Len(nc->ps->ma.subject); - - s = natsBuf_InitWithBackend(&(nc->ps->argBufRec), - nc->ps->scratch, - 0, - sizeof(nc->ps->scratch)); - if (s == NATS_OK) - { - nc->ps->argBuf = &(nc->ps->argBufRec); - - s = natsBuf_Append(nc->ps->argBuf, - natsBuf_Data(nc->ps->ma.subject), - natsBuf_Len(nc->ps->ma.subject)); - if (s == NATS_OK) - { - natsBuf_Destroy(nc->ps->ma.subject); - nc->ps->ma.subject = NULL; - - s = natsBuf_InitWithBackend(&(nc->ps->ma.subjectRec), - nc->ps->scratch, - subjLen, - subjLen); - if (s == NATS_OK) - nc->ps->ma.subject = &(nc->ps->ma.subjectRec); - } - } - if ((s == NATS_OK) && (nc->ps->ma.reply != NULL)) - { - s = natsBuf_Append(nc->ps->argBuf, - natsBuf_Data(nc->ps->ma.reply), - natsBuf_Len(nc->ps->ma.reply)); - if (s == NATS_OK) - { - int replyLen = natsBuf_Len(nc->ps->ma.reply); - - natsBuf_Destroy(nc->ps->ma.reply); - nc->ps->ma.reply = NULL; - - s = natsBuf_InitWithBackend(&(nc->ps->ma.replyRec), - nc->ps->scratch + subjLen, - replyLen, - replyLen); - if (s == NATS_OK) - nc->ps->ma.reply = &(nc->ps->ma.replyRec); - } - } - - return s; -} - -struct slice -{ - char *start; - int len; -}; - -static natsStatus -_processMsgArgs(natsConnection *nc, char *buf, int bufLen) -{ - natsStatus s = NATS_OK; - int start = -1; - int index = 0; - int i; - char b; - struct slice slices[5]; - char errTxt[256]; - int indexLimit = 3; - int minArgs = 3; - int maxArgs = 4; - bool hasHeaders = (nc->ps->hdr >= 0 ? true : false); - - // If headers, the content should be: - // [reply] - // otherwise: - // [reply] - if (hasHeaders) - { - indexLimit = 4; - minArgs = 4; - maxArgs = 5; - } - - for (i = 0; i < bufLen; i++) - { - b = buf[i]; - - if (((b == ' ') || (b == '\t') || (b == '\r') || (b == '\n'))) - { - if (start >=0) - { - if (index > indexLimit) - { - s = NATS_PROTOCOL_ERROR; - break; - } - slices[index].start = buf + start; - slices[index].len = i - start; - index++; - start = -1; - } - } - else if (start < 0) - { - start = i; - } - } - if ((s == NATS_OK) && (start >= 0)) - { - if (index > indexLimit) - { - s = NATS_PROTOCOL_ERROR; - } - else - { - slices[index].start = buf + start; - slices[index].len = i - start; - index++; - } - } - if ((s == NATS_OK) && ((index == minArgs) || (index == maxArgs))) - { - int maSizeIndex = index-1; // position of size is always last. - int hdrSizeIndex = index-2; // position of hdr size is always before last. - - s = natsBuf_InitWithBackend(&(nc->ps->ma.subjectRec), - slices[0].start, - slices[0].len, - slices[0].len); - if (s == NATS_OK) - { - nc->ps->ma.subject = &(nc->ps->ma.subjectRec); - - nc->ps->ma.sid = nats_ParseInt64(slices[1].start, slices[1].len); - - if (index == minArgs) - { - nc->ps->ma.reply = NULL; - } - else - { - s = natsBuf_InitWithBackend(&(nc->ps->ma.replyRec), - slices[2].start, - slices[2].len, - slices[2].len); - if (s == NATS_OK) - { - nc->ps->ma.reply = &(nc->ps->ma.replyRec); - } - } - } - if (s == NATS_OK) - { - if (hasHeaders) - { - nc->ps->ma.hdr = (int) nats_ParseInt64(slices[hdrSizeIndex].start, - slices[hdrSizeIndex].len); - } - nc->ps->ma.size = (int) nats_ParseInt64(slices[maSizeIndex].start, - slices[maSizeIndex].len); - } - } - else - { - snprintf(errTxt, sizeof(errTxt), "%s", "processMsgArgs Parse Error: wrong number of arguments"); - s = NATS_PROTOCOL_ERROR; - } - if (nc->ps->ma.sid < 0) - { - snprintf(errTxt, sizeof(errTxt), "processMsgArgs Bad or Missing Sid: '%.*s'", - bufLen, buf); - s = NATS_PROTOCOL_ERROR; - } - if (nc->ps->ma.size < 0) - { - snprintf(errTxt, sizeof(errTxt), "processMsgArgs Bad or Missing Size: '%.*s'", - bufLen, buf); - s = NATS_PROTOCOL_ERROR; - } - if (hasHeaders && ((nc->ps->ma.hdr < 0) || (nc->ps->ma.hdr > nc->ps->ma.size))) - { - snprintf(errTxt, sizeof(errTxt), "processMsgArgs Bad or Missing Header Size: '%.*s'", - bufLen, buf); - s = NATS_PROTOCOL_ERROR; - } - - if (s != NATS_OK) - { - natsConn_Lock(nc); - snprintf(nc->errStr, sizeof(nc->errStr), "%s", errTxt); - nc->err = s; - natsConn_Unlock(nc); - } - - return s; -} - -// parse is the fast protocol parser engine. -natsStatus -natsParser_Parse(natsConnection *nc, char* buf, int bufLen) -{ - natsStatus s = NATS_OK; - int i; - char b; - - for (i = 0; (s == NATS_OK) && (i < bufLen); i++) - { - b = buf[i]; - - switch (nc->ps->state) - { - case OP_START: - { - switch (b) - { - case 'M': - case 'm': - nc->ps->state = OP_M; - nc->ps->hdr = -1; - nc->ps->ma.hdr = -1; - break; - case 'H': - case 'h': - nc->ps->state = OP_H; - nc->ps->hdr = 0; - nc->ps->ma.hdr = 0; - break; - case 'P': - case 'p': - nc->ps->state = OP_P; - break; - case '+': - nc->ps->state = OP_PLUS; - break; - case '-': - nc->ps->state = OP_MINUS; - break; - case 'I': - case 'i': - nc->ps->state = OP_I; - break; - default: - goto parseErr; - } - break; - } - case OP_H: - { - switch (b) - { - case 'M': - case 'm': - nc->ps->state = OP_M; - break; - default: - goto parseErr; - } - break; - } - case OP_M: - { - switch (b) - { - case 'S': - case 's': - nc->ps->state = OP_MS; - break; - default: - goto parseErr; - } - break; - } - case OP_MS: - { - switch (b) - { - case 'G': - case 'g': - nc->ps->state = OP_MSG; - break; - default: - goto parseErr; - } - break; - } - case OP_MSG: - { - switch (b) - { - case ' ': - case '\t': - nc->ps->state = OP_MSG_SPC; - break; - default: - goto parseErr; - } - break; - } - case OP_MSG_SPC: - { - switch (b) - { - case ' ': - case '\t': - continue; - default: - nc->ps->state = MSG_ARG; - nc->ps->afterSpace = i; - break; - } - break; - } - case MSG_ARG: - { - switch (b) - { - case '\r': - nc->ps->drop = 1; - break; - case '\n': - { - char *start = NULL; - int len = 0; - - if (nc->ps->argBuf != NULL) - { - start = natsBuf_Data(nc->ps->argBuf); - len = natsBuf_Len(nc->ps->argBuf); - } - else - { - start = buf + nc->ps->afterSpace; - len = (i - nc->ps->drop) - nc->ps->afterSpace; - } - - s = _processMsgArgs(nc, start, len); - if (s == NATS_OK) - { - nc->ps->drop = 0; - nc->ps->afterSpace = i+1; - nc->ps->state = MSG_PAYLOAD; - - // jump ahead with the index. If this overruns - // what is left we fall out and process split - // buffer. - i = nc->ps->afterSpace + nc->ps->ma.size - 1; - } - break; - } - default: - { - if (nc->ps->argBuf != NULL) - s = natsBuf_AppendByte(nc->ps->argBuf, b); - break; - } - } - break; - } - case MSG_PAYLOAD: - { - bool done = false; - - if (nc->ps->msgBuf != NULL) - { - if (natsBuf_Len(nc->ps->msgBuf) >= nc->ps->ma.size) - { - s = natsConn_processMsg(nc, - natsBuf_Data(nc->ps->msgBuf), - natsBuf_Len(nc->ps->msgBuf)); - done = true; - } - else - { - // copy as much as we can to the buffer and skip ahead. - int toCopy = nc->ps->ma.size - natsBuf_Len(nc->ps->msgBuf); - int avail = bufLen - i; - - if (avail < toCopy) - toCopy = avail; - - if (toCopy > 0) - { - s = natsBuf_Append(nc->ps->msgBuf, buf+i, toCopy); - if (s == NATS_OK) - i += toCopy - 1; - } - else - { - s = natsBuf_AppendByte(nc->ps->msgBuf, b); - } - } - } - else if (i-nc->ps->afterSpace >= nc->ps->ma.size) - { - char *start = NULL; - int len = 0; - - start = buf + nc->ps->afterSpace; - len = (i - nc->ps->afterSpace); - - s = natsConn_processMsg(nc, start, len); - - done = true; - } - - if (done) - { - natsBuf_Destroy(nc->ps->argBuf); - nc->ps->argBuf = NULL; - natsBuf_Destroy(nc->ps->msgBuf); - nc->ps->msgBuf = NULL; - nc->ps->state = MSG_END; - } - - break; - } - case MSG_END: - { - switch (b) - { - case '\n': - nc->ps->drop = 0; - nc->ps->afterSpace = i+1; - nc->ps->state = OP_START; - break; - default: - continue; - } - break; - } - case OP_PLUS: - { - switch (b) - { - case 'O': - case 'o': - nc->ps->state = OP_PLUS_O; - break; - default: - goto parseErr; - } - break; - } - case OP_PLUS_O: - { - switch (b) - { - case 'K': - case 'k': - nc->ps->state = OP_PLUS_OK; - break; - default: - goto parseErr; - } - break; - } - case OP_PLUS_OK: - { - switch (b) - { - case '\n': - natsConn_processOK(nc); - nc->ps->drop = 0; - nc->ps->state = OP_START; - break; - } - break; - } - case OP_MINUS: - { - switch (b) - { - case 'E': - case 'e': - nc->ps->state = OP_MINUS_E; - break; - default: - goto parseErr; - } - break; - } - case OP_MINUS_E: - { - switch (b) - { - case 'R': - case 'r': - nc->ps->state = OP_MINUS_ER; - break; - default: - goto parseErr; - } - break; - } - case OP_MINUS_ER: - { - switch (b) - { - case 'R': - case 'r': - nc->ps->state = OP_MINUS_ERR; - break; - default: - goto parseErr; - } - break; - } - case OP_MINUS_ERR: - { - switch (b) - { - case ' ': - case '\t': - nc->ps->state = OP_MINUS_ERR_SPC; - break; - default: - goto parseErr; - } - break; - } - case OP_MINUS_ERR_SPC: - { - switch (b) - { - case ' ': - case '\t': - continue; - default: - nc->ps->state = MINUS_ERR_ARG; - nc->ps->afterSpace = i; - break; - } - break; - } - case MINUS_ERR_ARG: - { - switch (b) - { - case '\r': - nc->ps->drop = 1; - break; - case '\n': - { - char *start = NULL; - int len = 0; - - if (nc->ps->argBuf != NULL) - { - start = natsBuf_Data(nc->ps->argBuf); - len = natsBuf_Len(nc->ps->argBuf); - } - else - { - start = buf + nc->ps->afterSpace; - len = (i - nc->ps->drop) - nc->ps->afterSpace; - } - - natsConn_processErr(nc, start, len); - - nc->ps->drop = 0; - nc->ps->afterSpace = i+1; - nc->ps->state = OP_START; - - if (nc->ps->argBuf != NULL) - { - natsBuf_Destroy(nc->ps->argBuf); - nc->ps->argBuf = NULL; - } - - break; - } - default: - { - if (nc->ps->argBuf != NULL) - s = natsBuf_AppendByte(nc->ps->argBuf, b); - - break; - } - } - break; - } - case OP_P: - { - switch (b) - { - case 'I': - case 'i': - nc->ps->state = OP_PI; - break; - case 'O': - case 'o': - nc->ps->state = OP_PO; - break; - default: - goto parseErr; - } - break; - } - case OP_PO: - { - switch (b) - { - case 'N': - case 'n': - nc->ps->state = OP_PON; - break; - default: - goto parseErr; - } - break; - } - case OP_PON: - { - switch (b) - { - case 'G': - case 'g': - nc->ps->state = OP_PONG; - break; - default: - goto parseErr; - } - break; - } - case OP_PONG: - { - switch (b) - { - case '\n': - natsConn_processPong(nc); - - nc->ps->drop = 0; - nc->ps->afterSpace = i+1; - nc->ps->state = OP_START; - break; - } - break; - } - case OP_PI: - { - switch (b) - { - case 'N': - case 'n': - nc->ps->state = OP_PIN; - break; - default: - goto parseErr; - } - break; - } - case OP_PIN: - { - switch (b) - { - case 'G': - case 'g': - nc->ps->state = OP_PING; - break; - default: - goto parseErr; - } - break; - } - case OP_PING: - { - switch (b) - { - case '\n': - natsConn_processPing(nc); - - nc->ps->drop = 0; - nc->ps->afterSpace = i+1; - nc->ps->state = OP_START; - break; - } - break; - } - case OP_I: - { - switch (b) - { - case 'N': - case 'n': - nc->ps->state = OP_IN; - break; - default: - goto parseErr; - } - break; - } - case OP_IN: - { - switch (b) - { - case 'F': - case 'f': - nc->ps->state = OP_INF; - break; - default: - goto parseErr; - } - break; - } - case OP_INF: - { - switch (b) - { - case 'O': - case 'o': - nc->ps->state = OP_INFO; - break; - default: - goto parseErr; - } - break; - } - case OP_INFO: - { - switch (b) - { - case ' ': - case '\t': - nc->ps->state = OP_INFO_SPC; - break; - default: - goto parseErr; - } - break; - } - case OP_INFO_SPC: - { - switch (b) - { - case ' ': - case '\t': - continue; - default: - nc->ps->state = INFO_ARG; - nc->ps->afterSpace = i; - break; - } - break; - } - case INFO_ARG: - { - switch (b) - { - case '\r': - nc->ps->drop = 1; - break; - case '\n': - { - char *start = NULL; - int len = 0; - - if (nc->ps->argBuf != NULL) - { - start = natsBuf_Data(nc->ps->argBuf); - len = natsBuf_Len(nc->ps->argBuf); - } - else - { - start = buf + nc->ps->afterSpace; - len = (i - nc->ps->drop) - nc->ps->afterSpace; - } - natsConn_processAsyncINFO(nc, start, len); - nc->ps->drop = 0; - nc->ps->afterSpace = i+1; - nc->ps->state = OP_START; - - if (nc->ps->argBuf != NULL) - { - natsBuf_Destroy(nc->ps->argBuf); - nc->ps->argBuf = NULL; - } - break; - } - default: - { - if (nc->ps->argBuf != NULL) - s = natsBuf_AppendByte(nc->ps->argBuf, b); - break; - } - } - break; - } - default: - goto parseErr; - } - } - - // Check for split buffer scenarios - if ((s == NATS_OK) - && ((nc->ps->state == MSG_ARG) - || (nc->ps->state == MINUS_ERR_ARG) - || (nc->ps->state == INFO_ARG)) - && (nc->ps->argBuf == NULL)) - { - s = natsBuf_InitWithBackend(&(nc->ps->argBufRec), - nc->ps->scratch, - 0, - sizeof(nc->ps->scratch)); - if (s == NATS_OK) - { - nc->ps->argBuf = &(nc->ps->argBufRec); - s = natsBuf_Append(nc->ps->argBuf, - buf + nc->ps->afterSpace, - (i - nc->ps->drop) - nc->ps->afterSpace); - } - } - // Check for split msg - if ((s == NATS_OK) - && (nc->ps->state == MSG_PAYLOAD) && (nc->ps->msgBuf == NULL)) - { - // We need to clone the msgArg if it is still referencing the - // read buffer and we are not able to process the msg. - if (nc->ps->argBuf == NULL) - s = _cloneMsgArg(nc); - - if (s == NATS_OK) - { - int remainingInScratch; - int toCopy; - -#ifdef _WIN32 -// Suppresses the warning that nc->ps->argBuf may be NULL. -// If nc->ps->argBuf is NULL above, then _cloneMsgArg() will set it. If 's' -// is NATS_OK here, then nc->ps->argBuf can't be NULL. -#pragma warning(suppress: 6011) -#endif - - // If we will overflow the scratch buffer, just create a - // new buffer to hold the split message. - remainingInScratch = sizeof(nc->ps->scratch) - natsBuf_Len(nc->ps->argBuf); - toCopy = bufLen - nc->ps->afterSpace; - - if (nc->ps->ma.size > remainingInScratch) - { - s = natsBuf_Create(&(nc->ps->msgBuf), nc->ps->ma.size); - } - else - { - s = natsBuf_InitWithBackend(&(nc->ps->msgBufRec), - nc->ps->scratch + natsBuf_Len(nc->ps->argBuf), - 0, remainingInScratch); - if (s == NATS_OK) - nc->ps->msgBuf = &(nc->ps->msgBufRec); - } - if (s == NATS_OK) - s = natsBuf_Append(nc->ps->msgBuf, - buf + nc->ps->afterSpace, - toCopy); - } - } - - if (s != NATS_OK) - { - // Let's clear all our pointers... - natsBuf_Destroy(nc->ps->argBuf); - nc->ps->argBuf = NULL; - natsBuf_Destroy(nc->ps->msgBuf); - nc->ps->msgBuf = NULL; - natsBuf_Destroy(nc->ps->ma.subject); - nc->ps->ma.subject = NULL; - natsBuf_Destroy(nc->ps->ma.reply); - nc->ps->ma.reply = NULL; - } - - return s; - -parseErr: - if (s == NATS_OK) - s = NATS_PROTOCOL_ERROR; - - natsMutex_Lock(nc->mu); - - snprintf(nc->errStr, sizeof(nc->errStr), - "Parse Error [%u]: '%.*s'", - nc->ps->state, - bufLen - i, - buf + i); - - natsMutex_Unlock(nc->mu); - - return s; -} - -natsStatus -natsParser_Create(natsParser **newParser) -{ - natsParser *parser = (natsParser *) NATS_CALLOC(1, sizeof(natsParser)); - - if (parser == NULL) - return NATS_NO_MEMORY; - - *newParser = parser; - - return NATS_OK; -} - -void -natsParser_Destroy(natsParser *parser) -{ - if (parser == NULL) - return; - - natsBuf_Cleanup(&(parser->ma.subjectRec)); - natsBuf_Cleanup(&(parser->ma.replyRec)); - natsBuf_Cleanup(&(parser->argBufRec)); - natsBuf_Cleanup(&(parser->msgBufRec)); - - NATS_FREE(parser); -} diff --git a/src/parser.h b/src/parser.h deleted file mode 100644 index 4a8bbf3cb..000000000 --- a/src/parser.h +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2015-2020 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. - -#ifndef PARSER_H_ -#define PARSER_H_ - -#include - -//#include "natsp.h" -#include "status.h" -#include "buf.h" - -typedef enum -{ - OP_START = 0, - OP_PLUS, - OP_PLUS_O, - OP_PLUS_OK, - OP_MINUS, - OP_MINUS_E, - OP_MINUS_ER, - OP_MINUS_ERR, - OP_MINUS_ERR_SPC, - MINUS_ERR_ARG, - OP_M, - OP_MS, - OP_MSG, - OP_MSG_SPC, - MSG_ARG, - MSG_PAYLOAD, - MSG_END, - OP_H, - OP_P, - OP_PI, - OP_PIN, - OP_PING, - OP_PO, - OP_PON, - OP_PONG, - OP_I, - OP_IN, - OP_INF, - OP_INFO, - OP_INFO_SPC, - INFO_ARG - -} natsOp; - -typedef struct __natsMsgArg -{ - natsBuffer subjectRec; - natsBuffer *subject; - natsBuffer replyRec; - natsBuffer *reply; - int64_t sid; - int hdr; - int size; - -} natsMsgArg; - -#define MAX_CONTROL_LINE_SIZE (4096) - -typedef struct __natsParser -{ - natsOp state; - int afterSpace; - int drop; - int hdr; - natsMsgArg ma; - natsBuffer argBufRec; - natsBuffer *argBuf; - natsBuffer msgBufRec; - natsBuffer *msgBuf; - char scratch[MAX_CONTROL_LINE_SIZE]; - -} natsParser; - -// This is defined in natsp.h, natsp.h includes us. Alternatively, we can move -// all the parser defines in natsp.h -struct __natsConnection; - -natsStatus -natsParser_Create(natsParser **newParser); - -natsStatus -natsParser_Parse(struct __natsConnection *nc, char *buf, int bufLen); - -void -natsParser_Destroy(natsParser *parser); - -#endif /* PARSER_H_ */ diff --git a/src/pub.c b/src/pub.c index c865fb2c5..df838939a 100644 --- a/src/pub.c +++ b/src/pub.c @@ -16,558 +16,181 @@ #include #include "conn.h" -#include "sub.h" #include "msg.h" #include "nuid.h" -#include "mem.h" static const char *digits = "0123456789"; -#define _publishMsg(n, m, r) natsConn_publish((n), (m), (r), false) - -#define GETBYTES_SIZE(len, b, i) {\ - if ((len) > 0)\ - {\ - int l;\ - for (l = (len); l > 0; l /= 10)\ - {\ - (i) -= 1;\ - (b)[(i)] = digits[l%10];\ - }\ - }\ - else\ - {\ - (i) -= 1;\ - (b)[(i)] = digits[0];\ - }\ -} +#define GETBYTES_SIZE(len, b, i) \ + { \ + if ((len) > 0) \ + { \ + int l; \ + for (l = (len); l > 0; l /= 10) \ + { \ + (i) -= 1; \ + (b)[(i)] = digits[l % 10]; \ + } \ + } \ + else \ + { \ + (i) -= 1; \ + (b)[(i)] = digits[0]; \ + } \ + } // This represents the maximum size of a byte array containing the // string representation of a hdr/msg size. See GETBYTES_SIZE. #define BYTES_SIZE_MAX (12) -// _publish is the internal function to publish messages to a nats server. -// Sends a protocol data message by queueing into the bufio writer -// and kicking the flusher thread. These writes should be protected. -natsStatus -natsConn_publish(natsConnection *nc, natsMsg *msg, const char *reply, bool directFlush) +static void _onMessagePublished(natsConnection *nc, uint8_t *buffer, void *closure) { - natsStatus s = NATS_OK; - int msgHdSize = 0; - char dlb[BYTES_SIZE_MAX]; - int dli = BYTES_SIZE_MAX; - int dlSize = 0; - char hlb[BYTES_SIZE_MAX]; - int hli = BYTES_SIZE_MAX; - int hlSize = 0; - int subjLen = 0; - int replyLen = 0; - bool reconnecting = false; - int ppo = 1; // pub proto offset - int hdrl = 0; - int totalLen = 0; - - if (nc == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - if ((msg->subject == NULL) - || ((subjLen = (int) strlen(msg->subject)) == 0)) - { - return nats_setDefaultError(NATS_INVALID_SUBJECT); - } - - // If a reply is provided through params, use that one, - // otherwise fallback to msg->reply. - if (reply == NULL) - reply = msg->reply; + natsMessage *m = (natsMessage *)closure; + if (m == NULL) + return; - replyLen = ((reply != NULL) ? (int) strlen(reply) : 0); + if (m->donef != NULL) + m->donef(nc, m, m->doneClosure); + if (m->freef != NULL) + m->freef(m->freeClosure); - natsConn_Lock(nc); + nats_releasePool(m->pool); // will free the message +} +static natsStatus +nats_asyncPublish(natsConnection *nc, natsMessage *msg, bool copyData) +{ + natsStatus s = NATS_OK; + size_t totalLen = 0; + int headerLen = 0; + const natsString *proto = &nats_PUB; + natsString headerLenStr = NATS_EMPTY_STR; + natsString dataLenStr = NATS_EMPTY_STR; + size_t headerLineLen = 0; + char dlb[BYTES_SIZE_MAX]; + int dli = BYTES_SIZE_MAX; + char hlb[BYTES_SIZE_MAX]; + int hli = BYTES_SIZE_MAX; + natsBuf *scratch = NULL; + natsString *liftedHeader = NULL; + + if ((nc == NULL) || (msg == NULL)) + return nats_setDefaultError(NATS_INVALID_ARG); + if (nats_IsStringEmpty(msg->subject) || !nats_isSubjectValid(msg->subject->data, msg->subject->len, false)) + return nats_setDefaultError(NATS_INVALID_SUBJECT); if (natsConn_isClosed(nc)) - { - natsConn_Unlock(nc); - return nats_setDefaultError(NATS_CONNECTION_CLOSED); - } - if (natsConn_isDrainingPubs(nc)) - { - natsConn_Unlock(nc); - return nats_setDefaultError(NATS_DRAINING); - } - // We can have headers NULL but needsLift which means we are in special - // situation where a message was received and is sent back without the user - // accessing the headers. It should still be considered having headers. - if ((msg->headers != NULL) || natsMsg_needsLift(msg)) + // If there are explicit headers, we will use them. + if ((msg->headers != NULL)) { - // Do the check for server's headers support only after we have completed - // the initial connect (we could be here with initc true - that is, initial - // connect in progress - when using natsOptions_SetRetryOnFailedConnect - // option). - if (!nc->initc && !nc->info.headers) - { - natsConn_Unlock(nc); - + // If we have a hash of headers, we will use that. + if (natsConn_initialConnectDone(nc) && !nc->info->headers) return nats_setDefaultError(NATS_NO_SERVER_SUPPORT); - } - hdrl = natsMsgHeader_encodedLen(msg); - if (hdrl > 0) + headerLen = natsMessageHeader_encodedLen(msg); + if (headerLen > 0) { - GETBYTES_SIZE(hdrl, hlb, hli) - hlSize = (BYTES_SIZE_MAX - hli); - ppo = 0; - totalLen = hdrl; + GETBYTES_SIZE(headerLen, hlb, hli) + headerLenStr.data = (uint8_t *)(hlb + hli); + headerLenStr.len = (BYTES_SIZE_MAX - hli); + proto = &nats_HPUB; } } - // This will represent headers + data - totalLen += msg->dataLen; - - if (!nc->initc && ((int64_t) totalLen > nc->info.maxPayload)) + else if (msg->in != NULL) { - natsConn_Unlock(nc); - - return nats_setError(NATS_MAX_PAYLOAD, - "Payload %d greater than maximum allowed: %" PRId64, - totalLen, nc->info.maxPayload); - } - - // Check if we are reconnecting, and if so check if - // we have exceeded our reconnect outbound buffer limits. - if ((reconnecting = natsConn_isReconnecting(nc))) - { - // Check if we are over - if (natsBuf_Len(nc->pending) >= nc->opts->reconnectBufSize) - { - natsConn_Unlock(nc); - return nats_setDefaultError(NATS_INSUFFICIENT_BUFFER); - } - } - - GETBYTES_SIZE(totalLen, dlb, dli) - dlSize = (BYTES_SIZE_MAX - dli); - - // We include the NATS headers in the message header scratch. - msgHdSize = (_HPUB_P_LEN_ - ppo) - + subjLen + 1 - + (replyLen > 0 ? replyLen + 1 : 0) - + (hdrl > 0 ? hlSize + 1 + hdrl : 0) - + dlSize + _CRLF_LEN_; - - natsBuf_MoveTo(nc->scratch, _HPUB_P_LEN_); - - if (natsBuf_Capacity(nc->scratch) < msgHdSize) - { - // Although natsBuf_Append() would make sure that the buffer - // grows, it is better to make sure that the buffer is big - // enough for the pre-calculated size. We make it even a bit bigger. - s = natsBuf_Expand(nc->scratch, (int) ((float)msgHdSize * 1.1)); - } - - if (s == NATS_OK) - s = natsBuf_Append(nc->scratch, msg->subject, subjLen); - if (s == NATS_OK) - s = natsBuf_Append(nc->scratch, _SPC_, _SPC_LEN_); - if ((s == NATS_OK) && (reply != NULL)) - { - s = natsBuf_Append(nc->scratch, reply, replyLen); - if (s == NATS_OK) - s = natsBuf_Append(nc->scratch, _SPC_, _SPC_LEN_); - } - if ((s == NATS_OK) && (hdrl > 0)) - { - s = natsBuf_Append(nc->scratch, (hlb+hli), hlSize); - if (s == NATS_OK) - s = natsBuf_Append(nc->scratch, _SPC_, _SPC_LEN_); + // <>/<> FIXME lift headers from msg->in into liftedHeader + if (natsConn_initialConnectDone(nc) && nc->info->headers) + return nats_setDefaultError(NATS_NO_SERVER_SUPPORT); } - if (s == NATS_OK) - s = natsBuf_Append(nc->scratch, (dlb+dli), dlSize); - if (s == NATS_OK) - s = natsBuf_Append(nc->scratch, _CRLF_, _CRLF_LEN_); - if ((s == NATS_OK) && hdrl > 0) - s = natsMsgHeader_encode(nc->scratch, msg); - if (s == NATS_OK) - { - int pos = 0; - - if (reconnecting) - pos = natsBuf_Len(nc->pending); - else - SET_WRITE_DEADLINE(nc); - - s = natsConn_bufferWrite(nc, natsBuf_Data(nc->scratch)+ppo, msgHdSize); + // This will represent headers + data + totalLen = headerLen + nats_StringLen(msg->out); - if (s == NATS_OK) - s = natsConn_bufferWrite(nc, msg->data, msg->dataLen); + if (natsConn_initialConnectDone(nc) && ((int64_t)totalLen > nc->info->maxPayload)) + return nats_setError(NATS_MAX_PAYLOAD, "Payload %d greater than maximum allowed: %zu", totalLen, nc->info->maxPayload); - if (s == NATS_OK) - s = natsConn_bufferWrite(nc, _CRLF_, _CRLF_LEN_); + // <>/<> FIXME make sure we have enough space to queue for writes, against + // the limits etc. Used to be for reconnection only, but really, on a slow + // connection we should limit it also. - if ((s != NATS_OK) && reconnecting) - natsBuf_MoveTo(nc->pending, pos); - } + GETBYTES_SIZE(totalLen, dlb, dli) + dataLenStr.data = (uint8_t *)(dlb + dli); + dataLenStr.len = (BYTES_SIZE_MAX - dli); - if ((s == NATS_OK) && !reconnecting) - { - if (directFlush) - s = natsConn_bufferFlush(nc); + // We include the NATS headers in the message header scratch. + headerLineLen = proto->len + 1; + headerLineLen += msg->subject->len + 1; + if (!nats_IsStringEmpty(msg->reply)) + headerLineLen += msg->reply->len + 1; + if (headerLen > 0) + headerLineLen += headerLenStr.len + 1 + headerLen; + headerLineLen += dataLenStr.len; + headerLineLen += nats_CRLF.len; + + s = natsPool_getFixedBuf(&scratch, msg->pool, headerLineLen); + IFOK(s, natsBuf_addString(scratch, proto)); + IFOK(s, natsBuf_addString(scratch, &nats_SPACE)); + IFOK(s, natsBuf_addString(scratch, msg->subject)); + IFOK(s, natsBuf_addString(scratch, &nats_SPACE)); + if (!nats_IsStringEmpty(msg->reply)) + { + IFOK(s, natsBuf_addString(scratch, msg->reply)); + IFOK(s, natsBuf_addString(scratch, &nats_SPACE)); + } + if (headerLen > 0) + { + IFOK(s, natsBuf_addString(scratch, &headerLenStr)); + IFOK(s, natsBuf_addString(scratch, &nats_SPACE)); + } + IFOK(s, natsBuf_addString(scratch, &dataLenStr)); + IFOK(s, natsBuf_addString(scratch, &nats_CRLF)); + if (headerLen > 0) + { + if (liftedHeader == NULL) + { + IFOK(s, natsMessageHeader_encode(scratch, msg)); + } else - s = natsConn_flushOrKickFlusher(nc); - } - - if (s == NATS_OK) - { - nc->stats.outMsgs += 1; - nc->stats.outBytes += totalLen; - } - - natsConn_Unlock(nc); - - return NATS_UPDATE_ERR_STACK(s); -} - -/* - * Publishes the data argument to the given subject. The data argument is left - * untouched and needs to be correctly interpreted on the receiver. - */ -natsStatus -natsConnection_Publish(natsConnection *nc, const char *subj, - const void *data, int dataLen) -{ - natsStatus s; - natsMsg msg; - - natsMsg_init(&msg, subj, (const char*) data, dataLen); - s = _publishMsg(nc, &msg, NULL); - - return NATS_UPDATE_ERR_STACK(s); -} - -/* - * Convenient function to publish a string. This call is equivalent to: - * - * const char* myString = "hello"; - * - * natsPublish(nc, subj, (const void*) myString, (int) strlen(myString)); - */ -natsStatus -natsConnection_PublishString(natsConnection *nc, const char *subj, - const char *str) -{ - natsStatus s; - natsMsg msg; - int dataLen = 0; - - if (str != NULL) - dataLen = (int) strlen(str); - - natsMsg_init(&msg, subj, str, dataLen); - s = _publishMsg(nc, &msg, NULL); - - return NATS_UPDATE_ERR_STACK(s); -} - -/* - * Publishes the natsMsg structure, which includes the subject, an optional - * reply and optional data. - */ -natsStatus -natsConnection_PublishMsg(natsConnection *nc, natsMsg *msg) -{ - const char *reply = (msg != NULL ? msg->reply : NULL); - natsStatus s; - - s = _publishMsg(nc, msg, reply); - return NATS_UPDATE_ERR_STACK(s); -} - -/* - * Publishes the data argument to the given subject expecting a response on - * the reply subject. Use natsConnection_Request() for automatically waiting for a - * response inline. - */ -natsStatus -natsConnection_PublishRequest(natsConnection *nc, const char *subj, - const char *reply, const void *data, int dataLen) -{ - natsStatus s; - natsMsg msg; - - if ((reply == NULL) || (strlen(reply) == 0)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsMsg_init(&msg, subj, (const char*) data, dataLen); - s = _publishMsg(nc, &msg, reply); - - return NATS_UPDATE_ERR_STACK(s); -} - -/* - * Convenient function to publish a request as a string. This call is - * equivalent to: - * - * const char* myString = "hello"; - * - * natsPublishRequest(nc, subj, reply, (const void*) myString, - * (int) strlen(myString)); - */ -natsStatus -natsConnection_PublishRequestString(natsConnection *nc, const char *subj, - const char *reply, const char *str) -{ - natsStatus s; - natsMsg msg; - int dataLen = 0; - - if ((reply == NULL) || (strlen(reply) == 0)) - return nats_setDefaultError(NATS_INVALID_ARG); - - if (str != NULL) - dataLen = (int) strlen(str); - - natsMsg_init(&msg, subj, str, dataLen); - s = _publishMsg(nc, &msg, reply); - - return NATS_UPDATE_ERR_STACK(s); -} - -// Old way of sending a request... -static natsStatus -_oldRequestMsg(natsMsg **replyMsg, natsConnection *nc, - natsMsg *requestMsg, int64_t timeout) -{ - natsStatus s = NATS_OK; - natsSubscription *sub = NULL; - char inboxBuf[32 + NUID_BUFFER_LEN + 1]; - char *inbox = NULL; - bool freeIbx = false; - - s = natsConn_initInbox(nc, inboxBuf, sizeof(inboxBuf), &inbox, &freeIbx); - if (s == NATS_OK) - s = natsConn_subscribeSyncNoPool(&sub, nc, inbox); - if (s == NATS_OK) - s = natsSubscription_AutoUnsubscribe(sub, 1); - if (s == NATS_OK) - s = natsConn_publish(nc, requestMsg, (const char*) inbox, true); - if (s == NATS_OK) - s = natsSubscription_NextMsg(replyMsg, sub, timeout); - - if (freeIbx) - NATS_FREE(inbox); - natsSubscription_Destroy(sub); - - return NATS_UPDATE_ERR_STACK(s); -} - -static void -_respHandler(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - char *rt = NULL; - const char *subj = NULL; - respInfo *resp = NULL; - bool dmsg = true; - - natsConn_Lock(nc); - if (natsConn_isClosed(nc)) - { - natsConn_Unlock(nc); - natsMsg_Destroy(msg); - return; - } - subj = natsMsg_GetSubject(msg); - // We look for the reply token by first checking that the message subject - // prefix matches the subscription's subject (without the last '*'). - // It is possible that it does not due to subject rewrite (JetStream). - if (((int) strlen(subj) > nc->reqIdOffset) - && (memcmp((const void*) sub->subject, (const void*) subj, strlen(sub->subject) - 1) == 0)) - { - rt = (char*) (natsMsg_GetSubject(msg) + nc->reqIdOffset); - resp = (respInfo*) natsStrHash_Remove(nc->respMap, rt); - } - else if (natsStrHash_Count(nc->respMap) == 1) - { - // Only if the subject is completely different, we assume that it - // could be the server that has rewritten the subject and so if there - // is a single entry, use that. - void *value = NULL; - natsStrHash_RemoveSingle(nc->respMap, NULL, &value); - resp = (respInfo*) value; - } - if (resp != NULL) - { - natsMutex_Lock(resp->mu); - // Check for the race where the requestor has already timed-out. - // If so, resp->removed will be true, in which case simply discard - // the message. - if (!resp->removed) { - // Do not destroy the message since it is being used. - dmsg = false; - resp->msg = msg; - resp->removed = true; - natsCondition_Signal(resp->cond); + IFOK(s, natsBuf_addString(scratch, liftedHeader)); } - natsMutex_Unlock(resp->mu); } - natsConn_Unlock(nc); - - if (dmsg) - natsMsg_Destroy(msg); -} -/* - * Sends a request and waits for the first reply, up to the provided timeout. - * This is optimized for the case of multiple responses. - */ -natsStatus -natsConnection_RequestMsg(natsMsg **replyMsg, natsConnection *nc, - natsMsg *m, int64_t timeout) -{ - natsStatus s = NATS_OK; - respInfo *resp = NULL; - bool needsRemoval= true; - char respInboxBuf[32 + NUID_BUFFER_LEN + NATS_MAX_REQ_ID_LEN + 1]; // .. - char *respInbox = respInboxBuf; - - if ((replyMsg == NULL) || (nc == NULL) || (m == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - *replyMsg = NULL; - - natsConn_Lock(nc); - if (natsConn_isClosed(nc)) - { - natsConn_Unlock(nc); - return nats_setDefaultError(NATS_CONNECTION_CLOSED); - } - if (nc->opts->useOldRequestStyle) - { - natsConn_Unlock(nc); - return _oldRequestMsg(replyMsg, nc, m, timeout); - } + IFOK(s, natsConn_asyncWrite(nc, natsBuf_string(scratch), NULL, NULL)); - // If the custom inbox prefix is more than the reserved 32 characters - // in respInboxBuf, then we need to allocate... - if (nc->inboxPfxLen > 32) + if (!nats_IsStringEmpty(msg->out)) { - respInbox = NATS_MALLOC(nc->inboxPfxLen + NUID_BUFFER_LEN + NATS_MAX_REQ_ID_LEN + 1); - if (respInbox == NULL) + uint8_t *data = msg->out->data; + if (copyData) { - natsConn_Unlock(nc); - return nats_setDefaultError(NATS_NO_MEMORY); + IFOK(s, CHECK_NO_MEMORY(data = nats_palloc(msg->pool, msg->out->len))); + IFOK(s, ALWAYS_OK(memcpy(data, msg->out->data, msg->out->len))); } + IFOK(s, natsConn_asyncWrite(nc, msg->out, NULL, NULL)); } - // Since we are going to release the lock and connection - // may be closed while we wait for reply, we need to retain - // the connection object. - natsConn_retain(nc); - - // Setup only once (but could be more if natsConn_initResp() returns != OK) - if (nc->respMux == NULL) - s = natsConn_initResp(nc, _respHandler); - if (s == NATS_OK) - s = natsConn_addRespInfo(&resp, nc, respInbox, sizeof(respInbox)); - - natsConn_Unlock(nc); - - if (s == NATS_OK) - { - s = natsConn_publish(nc, m, (const char*) respInbox, true); - if (s == NATS_OK) - { - natsMutex_Lock(resp->mu); - while ((s != NATS_TIMEOUT) && (resp->msg == NULL) && !resp->closed) - s = natsCondition_TimedWait(resp->cond, resp->mu, timeout); - - // If we have a message, deliver it. - if (resp->msg != NULL) - { - // In case of race where s != NATS_OK but we got the message, - // we need to override status and set it to OK. - s = NATS_OK; + // Final write, add the callback + IFOK(s, ALWAYS_OK(nats_RetainPool(msg->pool))); + IFOK(s, natsConn_asyncWrite(nc, &nats_CRLF, _onMessagePublished, msg)); - // For servers that support it, we may receive an empty message - // with a 503 status header. If that is the case, return NULL - // message and NATS_NO_RESPONDERS error. - if (natsMsg_IsNoResponders(resp->msg)) - { - natsMsg_Destroy(resp->msg); - s = NATS_NO_RESPONDERS; - } - else - *replyMsg = resp->msg; - } - else - { - // Set the correct error status that we return to the user - if (resp->closed) - s = resp->closedSts; - else - s = NATS_TIMEOUT; - } - resp->msg = NULL; - needsRemoval = !resp->removed; - // Signal to _respHandler that we are no longer interested. - resp->removed = true; - natsMutex_Unlock(resp->mu); - } - } - // Common to success or if we failed to create the sub, send the request... - if (needsRemoval) + if (STILL_OK(s)) { - natsConn_Lock(nc); - if (nc->respMap != NULL) - natsStrHash_Remove(nc->respMap, respInbox+nc->reqIdOffset); - natsConn_Unlock(nc); + nc->stats.outMsgs += 1; + nc->stats.outBytes += totalLen; } - natsConn_disposeRespInfo(nc, resp, true); - - natsConn_release(nc); - - if (respInbox != respInboxBuf) - NATS_FREE(respInbox); return NATS_UPDATE_ERR_STACK(s); } -/* - * Convenient function to send a request as a string. This call is - * equivalent to: - * - * const char* myString = "hello"; - * - * natsConnection_Request(nc, subj, reply, (const void*) myString, - * (int) strlen(myString)); - */ -natsStatus -natsConnection_RequestString(natsMsg **replyMsg, natsConnection *nc, - const char *subj, const char *str, - int64_t timeout) +natsStatus nats_AsyncPublish(natsConnection *nc, natsMessage *msg) { - natsStatus s; - natsMsg msg; - - natsMsg_init(&msg, subj, str, (str == NULL ? 0 : (int) strlen(str))); - s = natsConnection_RequestMsg(replyMsg, nc, &msg, timeout); - - return NATS_UPDATE_ERR_STACK(s); + return nats_asyncPublish(nc, msg, true); } -natsStatus -natsConnection_Request(natsMsg **replyMsg, natsConnection *nc, const char *subj, - const void *data, int dataLen, int64_t timeout) +natsStatus nats_AsyncPublishNoCopy(natsConnection *nc, natsMessage *msg) { - natsStatus s; - natsMsg msg; - - natsMsg_init(&msg, subj, (const char*) data, dataLen); - s = natsConnection_RequestMsg(replyMsg, nc, &msg, timeout); - - return NATS_UPDATE_ERR_STACK(s); + return nats_asyncPublish(nc, msg, false); } diff --git a/src/servers.c b/src/servers.c new file mode 100644 index 000000000..8eeca2d72 --- /dev/null +++ b/src/servers.c @@ -0,0 +1,359 @@ +// Copyright 2015-2024 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 "hash.h" +#include "servers.h" +#include "conn.h" +#include "opts.h" + +static natsStatus +_createSrv(natsServer **newSrv, natsPool *pool, char *url, bool implicit, const char *tlsName) +{ + natsStatus s = NATS_OK; + natsServer *srv = nats_palloc(pool, sizeof(natsServer)); + if (srv == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + + srv->isImplicit = implicit; + + s = natsUrl_Create(&(srv->url), pool, url); + if ((STILL_OK(s)) && (tlsName != NULL)) + { + srv->tlsName = nats_pstrdupC(pool, tlsName); + if (srv->tlsName == NULL) + s = nats_setDefaultError(NATS_NO_MEMORY); + } + if (STILL_OK(s)) + *newSrv = srv; + + return NATS_UPDATE_ERR_STACK(s); +} + +static natsStatus +_addURLToServers(natsServers *servers, char *sURL, bool implicit, const char *tlsName) +{ + natsStatus s; + natsServer *srv = NULL; + + s = _createSrv(&srv, servers->pool, sURL, implicit, tlsName); + if (s != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); + + // For and explicit URL, we will save the user info if one is provided + // and if not already done. + if (!implicit && (servers->user == NULL) && (srv->url->username != NULL)) + { + s = CHECK_NO_MEMORY(servers->user = nats_pstrdupC(servers->pool, srv->url->username)); + IFOK(s, ALWAYS_OK(servers->pwd = nats_pstrdupC(servers->pool, srv->url->password))); // password can be NULL + if (s != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); + } + + if (servers->size + 1 > servers->cap) + { + natsServer **newArray = NULL; + int newCap = 2 * servers->cap; + + newArray = nats_palloc(servers->pool, newCap * sizeof(natsServer *)); + memcpy(newArray, servers->srvrs, servers->size * sizeof(*newArray)); + if (newArray == NULL) + s = nats_setDefaultError(NATS_NO_MEMORY); + + if (STILL_OK(s)) + { + servers->cap = newCap; + servers->srvrs = newArray; + } + } + if (STILL_OK(s)) + servers->srvrs[servers->size++] = srv; + + return NATS_UPDATE_ERR_STACK(s); +} + +static void +_shuffleServers(natsServers *pool, int offset) +{ + int i, j; + natsServer *tmp; + + if (pool->size <= offset + 1) + return; + + srand((unsigned int)nats_nowInNanoSeconds()); + + for (i = offset; i < pool->size; i++) + { + j = offset + rand() % (i + 1 - offset); + tmp = pool->srvrs[i]; + pool->srvrs[i] = pool->srvrs[j]; + pool->srvrs[j] = tmp; + } +} + +// Create the list of servers using the options given. +// We will place a Url option first, followed by any +// Server Options. We will randomize the server pool unlesss +// the NoRandomize flag is set. +natsStatus +natsServers_Create(natsServers **newServers, natsPool *pool, natsOptions *opts) +{ + natsStatus s = NATS_OK; + natsServers *servers = NULL; + int i; + int c; + + c = (opts->url != NULL ? 1 : 0); + c += opts->serversCount; + + // If the pool is going to be empty, we will add the default URL. + if (c == 0) + c = 1; + + IFOK(s, CHECK_NO_MEMORY(servers = nats_palloc(pool, sizeof(natsServers)))); + IFOK(s, CHECK_NO_MEMORY(servers->srvrs = nats_palloc(pool, c * sizeof(natsServer *)))); + + // Set the current capacity. The array of urls may have to grow in + // the future. + if (STILL_OK(s)) + { + servers->pool = pool; + servers->cap = c; + servers->randomize = !opts->net.noRandomize; + } + + // Add URLs from Options' Servers + for (i = 0; (STILL_OK(s)) && (i < opts->serversCount); i++) + s = _addURLToServers(servers, opts->servers[i], false, NULL); + + if (STILL_OK(s)) + { + // Randomize if allowed to + if (servers->randomize) + _shuffleServers(servers, 0); + } + + // Normally, if this one is set, Options.Servers should not be, + // but we always allowed that, so continue to do so. + if ((STILL_OK(s)) && (opts->url != NULL)) + { + // Add to the end of the array + s = _addURLToServers(servers, opts->url, false, NULL); + if ((STILL_OK(s)) && (servers->size > 1)) + { + // Then swap it with first to guarantee that Options.Url is tried first. + natsServer *opstUrl = servers->srvrs[servers->size - 1]; + + servers->srvrs[servers->size - 1] = servers->srvrs[0]; + servers->srvrs[0] = opstUrl; + } + } + else if ((STILL_OK(s)) && (servers->size == 0)) + { + // Place default URL if servers is empty. + s = _addURLToServers(servers, (char *)NATS_DEFAULT_URL, false, NULL); + } + + if (STILL_OK(s)) + *newServers = servers; + + return NATS_UPDATE_ERR_STACK(s); +} + +natsServer * +natsServers_GetCurrentServer(natsServers *servers, const natsServer *cur, int *index) +{ + natsServer *s = NULL; + int i; + + for (i = 0; i < servers->size; i++) + { + s = servers->srvrs[i]; + if (s == cur) + { + if (index != NULL) + *index = i; + + return s; + } + } + + if (index != NULL) + *index = -1; + + return NULL; +} + +// Pop the current server and put onto the end of the list. Select head of list as long +// as number of reconnect attempts under MaxReconnect. +natsServer * +natsServers_GetNextServer(natsServers *servers, natsOptions *opts, const natsServer *cur) +{ + natsServer *s = NULL; + int i, j; + + s = natsServers_GetCurrentServer(servers, cur, &i); + if (i < 0) + return NULL; + + // Shift left servers past current to the current's position + for (j = i; j < servers->size - 1; j++) + servers->srvrs[j] = servers->srvrs[j + 1]; + + if ((opts->net.maxReconnect < 0) || (s->reconnects < opts->net.maxReconnect)) + { + // Move the current server to the back of the list + servers->srvrs[servers->size - 1] = s; + } + else + { + // Remove the server from the list + servers->size--; + } + + if (servers->size <= 0) + return NULL; + + return servers->srvrs[0]; +} + +natsStatus +natsServers_addNewURLs(natsServers *servers, + const natsUrl *curUrl, const char **urls, int urlCount, const char *tlsName, bool *added) +{ + natsStatus s = NATS_OK; + char url[256]; + char *sport; + int portPos; + bool found; + bool isLH; + natsServer *srv = NULL; + const char **infoURLs = NULL; + int infoURLCount = urlCount; + + // Note about pool randomization: when the pool was first created, + // it was randomized (if allowed). We keep the order the same (removing + // implicit servers that are no longer sent to us). New URLs are sent + // to us in no specific order so don't need extra randomization. + + *added = false; + + // Clone the INFO urls so we can modify the list. + infoURLs = nats_palloc(servers->pool, sizeof(const char *) * urlCount); + if (infoURLs == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + for (int i = 0; i < urlCount; i++) + infoURLs[i] = urls[i]; + + // Walk the pool and removed the implicit servers that are no longer in the + // given array/map + for (int i = 0; i < servers->size; i++) + { + bool inInfo; + int n; + + srv = servers->srvrs[i]; + snprintf(url, sizeof(url), "%s:%d", srv->url->host, srv->url->port); + + // Remove from the temp list so that at the end we are left with only + // new (or restarted) servers that need to be added to the pool. + n = nats_strarray_remove((char **)infoURLs, infoURLCount, url); + inInfo = (n != infoURLCount); + infoURLCount = n; + + // Keep servers that were set through Options, but also the one that + // we are currently connected to (even if it is a discovered server). + if (!(srv->isImplicit) || (srv->url == curUrl)) + { + continue; + } + if (!inInfo) + { + // Remove from servers. Keep current order. + + // Shift left servers past current to the current's position + for (int j = i; j < servers->size - 1; j++) + { + servers->srvrs[j] = servers->srvrs[j + 1]; + } + servers->size--; + i--; + } + } + + // If there are any left in infoURLs, these are new (or restarted) servers + // and need to be added to the pool. + if (STILL_OK(s)) + { + for (int i = 0; (STILL_OK(s)) && (i < infoURLCount); i++) + { + const char *curl = infoURLs[i]; + + // Before adding, check if this is a new (as in never seen) URL. + // This is used to figure out if we invoke the DiscoveredServersCB + + isLH = false; + found = false; + + // Consider localhost:, 127.0.0.1: and [::1]: + // all the same. + sport = strrchr(curl, ':'); + portPos = (int)(sport - curl); + if (((nats_strcasestr(curl, "localhost") == curl) && (portPos == 9)) || (strncmp(curl, "127.0.0.1", portPos) == 0) || (strncmp(curl, "[::1]", portPos) == 0)) + { + isLH = ((curl[0] == 'l') || (curl[0] == 'L')); + + for (int j = 0; j < servers->size; j++) + { + srv = servers->srvrs[j]; + if (natsUrl_IsLocalhost(srv->url)) + { + found = true; + break; + } + } + } + else + { + for (int j = 0; j < servers->size; j++) + { + srv = servers->srvrs[j]; + snprintf(url, sizeof(url), "nats://%s", curl); + if (strcmp(srv->url->fullUrl, url) == 0) + { + found = true; + break; + } + } + } + + snprintf(url, sizeof(url), "nats://%s", curl); + if (!found) + { + // Make sure that localhost URL is always stored in lower case. + if (isLH) + snprintf(url, sizeof(url), "nats://localhost%s", sport); + + *added = true; + } + s = _addURLToServers(servers, url, true, tlsName); + } + + if ((STILL_OK(s)) && *added && servers->randomize) + _shuffleServers(servers, 1); + } + + return NATS_UPDATE_ERR_STACK(s); +} diff --git a/src/servers.h b/src/servers.h new file mode 100644 index 000000000..dc20e3570 --- /dev/null +++ b/src/servers.h @@ -0,0 +1,71 @@ +// Copyright 2015-2024 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. + +#ifndef SERVERS_H_ +#define SERVERS_H_ + +#include "url.h" + +// Tracks individual backend servers. +struct __natsServer_s +{ + natsUrl *url; + bool didConnect; + bool isImplicit; + int reconnects; + char *tlsName; + int lastAuthErrCode; +}; + +struct __natsServers_s +{ + natsPool *pool; + natsServer **srvrs; + int size; + int cap; + bool randomize; + char *user; + char *pwd; +}; + +#define natsServers_Count(p) ((p)->size) +#define natsServers_Get(p, i) ((p)->srvrs[(i)]) +#define natsServers_SetSrvDidConnect(p, i, c) (natsServers_Get((p), (i))->didConnect = (c)) +#define natsServers_SetSrvReconnects(p, i, r) (natsServers_Get((p), (i))->reconnects = (r)) + +// Create a servers list using the options given. We will place a Url option +// first, followed by any Server Options. We will randomize the list unlesss the +// NoRandomize flag is set. +natsStatus +natsServers_Create(natsServers **newServers, natsPool *pool, struct __natsOptions *opts); + +// Return the server corresponding to given `cur` with current position +// in the list. +natsServer * +natsServers_GetCurrentServer(natsServers *servers, const natsServer *cur, int *index); + +// Pop the current server and put onto the end of the list. Select head of list as long +// as number of reconnect attempts under MaxReconnect. +natsServer * +natsServers_GetNextServer(natsServers *servers, struct __natsOptions *opts, const natsServer *cur); + +// Go through the list of the given URLs and add them to the list if not already +// present. +natsStatus +natsServers_addNewURLs(natsServers *servers, const natsUrl *curUrl, const char **urls, int urlCount, const char *tlsName, bool *added); + +// Returns an array of servers (as a copy). User is responsible to free the memory. +natsStatus +natsServers_GetServers(natsServers *servers, bool implicitOnly, char ***out, int *count); + +#endif /* SERVERS_H_ */ diff --git a/src/srvpool.c b/src/srvpool.c deleted file mode 100644 index 46b0a1e7b..000000000 --- a/src/srvpool.c +++ /dev/null @@ -1,468 +0,0 @@ -// Copyright 2015-2020 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 "url.h" - -static void -_freeSrv(natsSrv *srv) -{ - if (srv == NULL) - return; - - natsUrl_Destroy(srv->url); - NATS_FREE(srv->tlsName); - NATS_FREE(srv); -} - -static natsStatus -_createSrv(natsSrv **newSrv, char *url, bool implicit, const char *tlsName) -{ - natsStatus s = NATS_OK; - natsSrv *srv = (natsSrv*) NATS_CALLOC(1, sizeof(natsSrv)); - - if (srv == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - srv->isImplicit = implicit; - - s = natsUrl_Create(&(srv->url), url); - if ((s == NATS_OK) && (tlsName != NULL)) - { - srv->tlsName = NATS_STRDUP(tlsName); - if (srv->tlsName == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - if (s == NATS_OK) - *newSrv = srv; - else - _freeSrv(srv); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsSrv* -natsSrvPool_GetCurrentServer(natsSrvPool *pool, const natsSrv *cur, int *index) -{ - natsSrv *s = NULL; - int i; - - for (i = 0; i < pool->size; i++) - { - s = pool->srvrs[i]; - if (s == cur) - { - if (index != NULL) - *index = i; - - return s; - } - } - - if (index != NULL) - *index = -1; - - return NULL; -} - -// Pop the current server and put onto the end of the list. Select head of list as long -// as number of reconnect attempts under MaxReconnect. -natsSrv* -natsSrvPool_GetNextServer(natsSrvPool *pool, natsOptions *opts, const natsSrv *cur) -{ - natsSrv *s = NULL; - int i, j; - - s = natsSrvPool_GetCurrentServer(pool, cur, &i); - if (i < 0) - return NULL; - - // Shift left servers past current to the current's position - for (j = i; j < pool->size - 1; j++) - pool->srvrs[j] = pool->srvrs[j+1]; - - if ((opts->maxReconnect < 0) - || (s->reconnects < opts->maxReconnect)) - { - // Move the current server to the back of the list - pool->srvrs[pool->size - 1] = s; - } - else - { - // Remove the server from the list - _freeSrv(s); - pool->size--; - } - - if (pool->size <= 0) - return NULL; - - return pool->srvrs[0]; -} - -void -natsSrvPool_Destroy(natsSrvPool *pool) -{ - natsSrv *srv; - int i; - - if (pool == NULL) - return; - - for (i = 0; i < pool->size; i++) - { - srv = pool->srvrs[i]; - _freeSrv(srv); - } - natsStrHash_Destroy(pool->urls); - pool->urls = NULL; - - NATS_FREE(pool->srvrs); - pool->srvrs = NULL; - pool->size = 0; - NATS_FREE(pool->user); - NATS_FREE(pool->pwd); - NATS_FREE(pool); -} - -static natsStatus -_addURLToPool(natsSrvPool *pool, char *sURL, bool implicit, const char *tlsName) -{ - natsStatus s; - natsSrv *srv = NULL; - bool addedToMap = false; - char bareURL[256]; - - s = _createSrv(&srv, sURL, implicit, tlsName); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - // For and explicit URL, we will save the user info if one is provided - // and if not already done. - if (!implicit && (pool->user == NULL) && (srv->url->username != NULL)) - { - DUP_STRING(s, pool->user, srv->url->username); - if ((s == NATS_OK) && !nats_IsStringEmpty(srv->url->password)) - DUP_STRING(s, pool->pwd, srv->url->password); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - } - - // In the map, we need to add an URL that is just host:port - snprintf(bareURL, sizeof(bareURL), "%s:%d", srv->url->host, srv->url->port); - s = natsStrHash_Set(pool->urls, bareURL, true, (void*)1, NULL); - if (s == NATS_OK) - { - addedToMap = true; - if (pool->size + 1 > pool->cap) - { - natsSrv **newArray = NULL; - int newCap = 2 * pool->cap; - - newArray = (natsSrv**) NATS_REALLOC(pool->srvrs, newCap * sizeof(char*)); - if (newArray == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - - if (s == NATS_OK) - { - pool->cap = newCap; - pool->srvrs = newArray; - } - } - if (s == NATS_OK) - pool->srvrs[pool->size++] = srv; - } - if (s != NATS_OK) - { - if (addedToMap) - natsStrHash_Remove(pool->urls, sURL); - - _freeSrv(srv); - } - - return NATS_UPDATE_ERR_STACK(s); -} - -static void -_shufflePool(natsSrvPool *pool, int offset) -{ - int i, j; - natsSrv *tmp; - - if (pool->size <= offset+1) - return; - - srand((unsigned int) nats_NowInNanoSeconds()); - - for (i = offset; i < pool->size; i++) - { - j = offset + rand() % (i + 1 - offset); - tmp = pool->srvrs[i]; - pool->srvrs[i] = pool->srvrs[j]; - pool->srvrs[j] = tmp; - } -} - -natsStatus -natsSrvPool_addNewURLs(natsSrvPool *pool, const natsUrl *curUrl, char **urls, int urlCount, const char *tlsName, bool *added) -{ - natsStatus s = NATS_OK; - char url[256]; - int i, j; - char *sport; - int portPos; - bool found; - bool isLH; - natsStrHash *tmp = NULL; - natsSrv *srv = NULL; - - // Note about pool randomization: when the pool was first created, - // it was randomized (if allowed). We keep the order the same (removing - // implicit servers that are no longer sent to us). New URLs are sent - // to us in no specific order so don't need extra randomization. - - *added = false; - - // Transform what we got to a map for easy lookups - s = natsStrHash_Create(&tmp, urlCount); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - for (i=0; (s == NATS_OK) && (isize; i++) - { - void *inInfo= NULL; - - srv = pool->srvrs[i]; - snprintf(url, sizeof(url), "%s:%d", srv->url->host, srv->url->port); - // Check if this URL is in the INFO protocol - inInfo = natsStrHash_Get(tmp, url); - // Remove from the temp map so that at the end we are left with only - // new (or restarted) servers that need to be added to the pool. - natsStrHash_Remove(tmp, url); - // Keep servers that were set through Options, but also the one that - // we are currently connected to (even if it is a discovered server). - if (!(srv->isImplicit) || (srv->url == curUrl)) - { - continue; - } - if (!inInfo) - { - // Remove from server pool. Keep current order. - - // Shift left servers past current to the current's position - for (j = i; j < pool->size - 1; j++) - { - pool->srvrs[j] = pool->srvrs[j+1]; - } - _freeSrv(srv); - pool->size--; - i--; - } - } - - // If there are any left in the tmp map, these are new (or restarted) servers - // and need to be added to the pool. - if (s == NATS_OK) - { - natsStrHashIter iter; - char *curl = NULL; - - natsStrHashIter_Init(&iter, tmp); - while ((s == NATS_OK) && natsStrHashIter_Next(&iter, &curl, NULL)) - { - // Before adding, check if this is a new (as in never seen) URL. - // This is used to figure out if we invoke the DiscoveredServersCB - - isLH = false; - found = false; - - // Consider localhost:, 127.0.0.1: and [::1]: - // all the same. - sport = strrchr(curl, ':'); - portPos = (int) (sport - curl); - if (((nats_strcasestr(curl, "localhost") == curl) && (portPos == 9)) - || (strncmp(curl, "127.0.0.1", portPos) == 0) - || (strncmp(curl, "[::1]", portPos) == 0)) - { - isLH = ((curl[0] == 'l') || (curl[0] == 'L')); - - snprintf(url, sizeof(url), "localhost%s", sport); - found = (natsStrHash_Get(pool->urls, url) != NULL); - if (!found) - { - snprintf(url, sizeof(url), "127.0.0.1%s", sport); - found = (natsStrHash_Get(pool->urls, url) != NULL); - } - if (!found) - { - snprintf(url, sizeof(url), "[::1]%s", sport); - found = (natsStrHash_Get(pool->urls, url) != NULL); - } - } - else - { - found = (natsStrHash_Get(pool->urls, curl) != NULL); - } - - snprintf(url, sizeof(url), "nats://%s", curl); - if (!found) - { - // Make sure that localhost URL is always stored in lower case. - if (isLH) - snprintf(url, sizeof(url), "nats://localhost%s", sport); - - *added = true; - } - s = _addURLToPool(pool, url, true, tlsName); - } - natsStrHashIter_Done(&iter); - if ((s == NATS_OK) && added && pool->randomize) - _shufflePool(pool, 1); - } - - natsStrHash_Destroy(tmp); - - return NATS_UPDATE_ERR_STACK(s); -} - -// Create the server pool using the options given. -// We will place a Url option first, followed by any -// Server Options. We will randomize the server pool unlesss -// the NoRandomize flag is set. -natsStatus -natsSrvPool_Create(natsSrvPool **newPool, natsOptions *opts) -{ - natsStatus s = NATS_OK; - natsSrvPool *pool = NULL; - int poolSize; - int i; - - poolSize = (opts->url != NULL ? 1 : 0); - poolSize += opts->serversCount; - - // If the pool is going to be empty, we will add the default URL. - if (poolSize == 0) - poolSize = 1; - - pool = (natsSrvPool*) NATS_CALLOC(1, sizeof(natsSrvPool)); - if (pool == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - pool->srvrs = (natsSrv**) NATS_CALLOC(poolSize, sizeof(natsSrv*)); - if (pool->srvrs == NULL) - { - NATS_FREE(pool); - return nats_setDefaultError(NATS_NO_MEMORY); - } - // Set the current capacity. The array of urls may have to grow in - // the future. - pool->cap = poolSize; - pool->randomize = !opts->noRandomize; - - // Map that helps find out if an URL is already known. - s = natsStrHash_Create(&(pool->urls), poolSize); - - // Add URLs from Options' Servers - for (i=0; (s == NATS_OK) && (i < opts->serversCount); i++) - s = _addURLToPool(pool, opts->servers[i], false, NULL); - - if (s == NATS_OK) - { - // Randomize if allowed to - if (pool->randomize) - _shufflePool(pool, 0); - } - - // Normally, if this one is set, Options.Servers should not be, - // but we always allowed that, so continue to do so. - if ((s == NATS_OK) && (opts->url != NULL)) - { - // Add to the end of the array - s = _addURLToPool(pool, opts->url, false, NULL); - if ((s == NATS_OK) && (pool->size > 1)) - { - // Then swap it with first to guarantee that Options.Url is tried first. - natsSrv *opstUrl = pool->srvrs[pool->size-1]; - - pool->srvrs[pool->size-1] = pool->srvrs[0]; - pool->srvrs[0] = opstUrl; - } - } - else if ((s == NATS_OK) && (pool->size == 0)) - { - // Place default URL if pool is empty. - s = _addURLToPool(pool, (char*) NATS_DEFAULT_URL, false, NULL); - } - - if (s == NATS_OK) - *newPool = pool; - else - natsSrvPool_Destroy(pool); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsSrvPool_GetServers(natsSrvPool *pool, bool implicitOnly, char ***servers, int *count) -{ - natsStatus s = NATS_OK; - char **srvrs = NULL; - natsSrv *srv; - natsUrl *url; - int i; - int discovered = 0; - - if (pool->size == 0) - { - *servers = NULL; - *count = 0; - return NATS_OK; - } - - srvrs = (char **) NATS_CALLOC(pool->size, sizeof(char*)); - if (srvrs == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - for (i=0; ((s == NATS_OK) && (isize)); i++) - { - srv = pool->srvrs[i]; - if (implicitOnly && !srv->isImplicit) - continue; - url = srv->url; - if (nats_asprintf(&(srvrs[discovered]), "nats://%s:%d", url->host, url->port) == -1) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - discovered++; - } - if (s == NATS_OK) - { - *servers = srvrs; - *count = discovered; - } - else - { - for (i=0; isize) -#define natsSrvPool_GetSrv(p,i) ((p)->srvrs[(i)]) -#define natsSrvPool_SetSrvDidConnect(p,i,c) (natsSrvPool_GetSrv((p),(i))->didConnect=(c)) -#define natsSrvPool_SetSrvReconnects(p,i,r) (natsSrvPool_GetSrv((p),(i))->reconnects=(r)) - -// Create the server pool using the options given. -// We will place a Url option first, followed by any -// Server Options. We will randomize the server pool unlesss -// the NoRandomize flag is set. -natsStatus -natsSrvPool_Create(natsSrvPool **newPool, struct __natsOptions *opts); - -// Return the server corresponding to given `cur` with current position -// in the pool. -natsSrv* -natsSrvPool_GetCurrentServer(natsSrvPool *pool, const natsSrv *cur, int *index); - -// Pop the current server and put onto the end of the list. Select head of list as long -// as number of reconnect attempts under MaxReconnect. -natsSrv* -natsSrvPool_GetNextServer(natsSrvPool *pool, struct __natsOptions *opts, const natsSrv *cur); - -// Go through the list of the given URLs and add them to the pool if not already -// present. -natsStatus -natsSrvPool_addNewURLs(natsSrvPool *pool, const natsUrl *curUrl, char **urls, int urlCount, const char *tlsName, bool *added); - -// Returns an array of servers (as a copy). User is responsible to free the memory. -natsStatus -natsSrvPool_GetServers(natsSrvPool *pool, bool implicitOnly, char ***servers, int *count); - -// Destroy the pool, freeing up all memory used. -void -natsSrvPool_Destroy(natsSrvPool *pool); - - -#endif /* SRVPOOL_H_ */ diff --git a/src/stan/conn.c b/src/stan/conn.c deleted file mode 100644 index 50aaec982..000000000 --- a/src/stan/conn.c +++ /dev/null @@ -1,871 +0,0 @@ -// Copyright 2018-2019 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 "conn.h" -#include "copts.h" -#include "pub.h" - -#include "../asynccb.h" -#include "../conn.h" -#include "../sub.h" - -// Client send connID in ConnectRequest and PubMsg, and server -// listens and responds to client PINGs. The validity of the -// connection (based on connID) is checked on incoming PINGs. -#define PROTOCOL_ONE (1) - -#ifdef DEV_MODE -// For type safety - -static void _retain(stanConnection *sc) { sc->refs++; } -static void _release(stanConnection *sc) { sc->refs--; } - -void stanConn_Lock(stanConnection *nc) { natsMutex_Lock(nc->mu); } -void stanConn_Unlock(stanConnection *nc) { natsMutex_Unlock(nc->mu); } - -#else -// We know what we are doing :-) - -#define _retain(c) ((c)->refs++) -#define _release(c) ((c)->refs--) - -#endif // DEV_MODE - -bool testAllowMillisecInPings = false; - -#if defined(__arm__) -int MEMALIGN = (int)(__alignof(void*)); -#else -int MEMALIGN = 1; -#endif - -static void -_freeConn(stanConnection *sc) -{ - if (sc == NULL) - return; - - natsSubscription_Destroy(sc->hbSubscription); - natsSubscription_Destroy(sc->ackSubscription); - natsSubscription_Destroy(sc->pingSub); - natsConn_destroy(sc->nc, false); - natsInbox_Destroy(sc->hbInbox); - natsStrHash_Destroy(sc->pubAckMap); - natsCondition_Destroy(sc->pubAckCond); - natsCondition_Destroy(sc->pubAckMaxInflightCond); - stanConnOptions_Destroy(sc->opts); - NATS_FREE(sc->pubMsgBuf); - NATS_FREE(sc->pubSubjBuf); - natsMutex_Destroy(sc->pubAckMu); - natsTimer_Destroy(sc->pubAckTimer); - natsPBufAllocator_Destroy(sc->pubAckAllocator); - natsTimer_Destroy(sc->pingTimer); - natsMutex_Destroy(sc->pingMu); - natsMutex_Destroy(sc->mu); - NATS_FREE(sc->clusterID); - NATS_FREE(sc->clientID); - NATS_FREE(sc->connID); - NATS_FREE(sc->pubPrefix); - NATS_FREE(sc->subRequests); - NATS_FREE(sc->unsubRequests); - NATS_FREE(sc->subCloseRequests); - NATS_FREE(sc->closeRequests); - NATS_FREE(sc->ackSubject); - NATS_FREE(sc->pingBytes); - NATS_FREE(sc->pingRequests); - NATS_FREE(sc->pingInbox); - NATS_FREE(sc->connLostErrTxt); - - NATS_FREE(sc); - - natsLib_Release(); -} - -void -stanConn_retain(stanConnection *sc) -{ - if (sc == NULL) - return; - - stanConn_Lock(sc); - sc->refs++; - stanConn_Unlock(sc); -} - -void -stanConn_release(stanConnection *sc) -{ - int refs = 0; - - if (sc == NULL) - return; - - stanConn_Lock(sc); - refs = --(sc->refs); - stanConn_Unlock(sc); - - if (refs == 0) - _freeConn(sc); -} - - -static void -_releaseStanConnCB(void *closure) -{ - stanConnection *sc = (stanConnection*) closure; - stanConn_release(sc); -} - -static void -_processHeartBeat(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - // No payload assumed, just reply. - natsConnection_Publish(nc, natsMsg_GetReply(msg), NULL, 0); - natsMsg_Destroy(msg); -} - -static void -_closeDueToPing(stanConnection *sc, const char* errTxt) -{ - natsStatus s = NATS_OK; - - stanConnClose(sc, false); - - stanConn_Lock(sc); - // Make a copy - DUP_STRING(s, sc->connLostErrTxt, errTxt); - stanConn_Unlock(sc); - - if (s == NATS_OK) - natsAsyncCb_PostStanConnLostHandler(sc); -} - -void -stanConn_defaultConnLostHandler(stanConnection *sc, const char* errorTxt, void *closure) -{ - stanConn_Lock(sc); - fprintf(stderr, "Connection permanently lost: clusterID=%s clientID=%s error=%s\n", sc->clusterID, sc->clientID, errorTxt); - stanConn_Unlock(sc); -} - -static void -_processPingResponse(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - stanConnection *sc = (stanConnection*) closure; - - if (natsMsg_GetDataLength(msg) > 0) - { - Pb__PingResponse *pingResp = NULL; - char *errTxt = NULL; - - pingResp = pb__ping_response__unpack(NULL, - (size_t) natsMsg_GetDataLength(msg), - (const uint8_t*) natsMsg_GetData(msg)); - if ((pingResp != NULL) && (strlen(pingResp->error) > 0)) - errTxt = NATS_STRDUP(pingResp->error); - - pb__ping_response__free_unpacked(pingResp, NULL); - - if (errTxt != NULL) - { - _closeDueToPing(sc, (const char*) errTxt); - NATS_FREE(errTxt); - natsMsg_Destroy(msg); - return; - } - } - // Check for no responders - else if (natsMsg_IsNoResponders(msg)) - { - natsMsg_Destroy(msg); - return; - } - // Do not attempt to decrement, simply reset to 0. - natsMutex_Lock(sc->pingMu); - sc->pingOut = 0; - natsMutex_Unlock(sc->pingMu); - natsMsg_Destroy(msg); -} - -static void -_pingServer(natsTimer *timer, void* closure) -{ - natsStatus s; - stanConnection *sc = (stanConnection*) closure; - - natsMutex_Lock(sc->pingMu); - if (sc->closed) - { - natsMutex_Unlock(sc->pingMu); - return; - } - sc->pingOut++; - if (sc->pingOut > sc->opts->pingMaxOut) - { - natsMutex_Unlock(sc->pingMu); - _closeDueToPing(sc, STAN_ERR_MAX_PINGS); - return; - } - natsMutex_Unlock(sc->pingMu); - - // Ok to reference these connection's fields outside of the lock since they - // are immutable and valid until the connection is freed. - - s = natsConnection_PublishRequest(sc->nc, sc->pingRequests, sc->pingInbox, - (const void*) sc->pingBytes, sc->pingBytesLen); - if (s == NATS_CONNECTION_CLOSED) - _closeDueToPing(sc, natsStatus_GetText(s)); -} - -static void -_pingTimerStopCB(natsTimer *timer, void* closure) -{ - stanConnection *sc = (stanConnection*) closure; - stanConn_release(sc); -} - -static natsStatus -_createPingBytes(stanConnection *sc) -{ - Pb__Ping ping; - int size = 0; - int packedSize = 0; - - pb__ping__init(&ping); - ping.connid.data = (uint8_t*) sc->connID; - ping.connid.len = (size_t) sc->connIDLen; - size = (int) pb__ping__get_packed_size(&ping); - if (size == 0) - return nats_setError(NATS_ERR, "%s", "ping protocol packed size is 0"); - - sc->pingBytes = NATS_MALLOC(size); - if (sc->pingBytes == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - packedSize = (int) pb__ping__pack(&ping, (uint8_t*) sc->pingBytes); - if (size != packedSize) - return nats_setError(NATS_ERR, "ping protocol computed packed size is %d, got %v", - size, packedSize); - - sc->pingBytesLen = size; - return NATS_OK; -} - -natsStatus -stanConnection_Connect(stanConnection **newConn, const char* clusterID, const char* clientID, stanConnOptions *opts) -{ - stanConnection *sc = NULL; - natsStatus s; - natsSubscription *pingSub = NULL; - char *pingInbox = NULL; - bool unsubPingSub = false; - - if ((newConn == NULL) - || (clusterID == NULL) - || (clusterID[0] == '\0') - || (clientID == NULL) - || (clientID[0] == '\0')) - { - return nats_setDefaultError(NATS_INVALID_ARG); - } - - s = nats_Open(-1); - if (s != NATS_OK) - return s; - - sc = NATS_CALLOC(1, sizeof(stanConnection)); - if (sc == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - s = natsMutex_Create(&sc->mu); - if (s != NATS_OK) - { - NATS_FREE(sc); - return NATS_UPDATE_ERR_STACK(s); - } - - natsLib_Retain(); - - // Set to 1 so that release free the connection in case of error after this point. - sc->refs = 1; - - // Set options - if (opts != NULL) - s = stanConnOptions_clone(&sc->opts, opts); - else - s = stanConnOptions_Create(&sc->opts); - - if ((s == NATS_OK) && (sc->opts->ncOpts == NULL)) - s = natsOptions_Create(&sc->opts->ncOpts); - - // Override NATS connections (but we work on our clone or private one, - // so that does not affect user's provided options). - if (s == NATS_OK) - s = natsOptions_SetName(sc->opts->ncOpts, clientID); - if (s == NATS_OK) - s = natsOptions_SetReconnectBufSize(sc->opts->ncOpts, 0); - if (s == NATS_OK) - s = natsOptions_SetMaxReconnect(sc->opts->ncOpts, -1); - if (s == NATS_OK) - s = natsOptions_SetAllowReconnect(sc->opts->ncOpts, true); - - // We don't support SetRetryOnFailedConnect for now - if ((s == NATS_OK) && (opts != NULL)) - s = natsOptions_SetRetryOnFailedConnect(sc->opts->ncOpts, false, NULL, NULL); - - // Set the URL if provided through STAN options - if ((s == NATS_OK) && (sc->opts->url != NULL)) - s = natsOptions_SetURL(sc->opts->ncOpts, sc->opts->url); - - // Connect to NATS - if (s == NATS_OK) - s = natsConnection_Connect(&sc->nc, sc->opts->ncOpts); - - if (s == NATS_OK) - { - natsConn_Lock(sc->nc); - sc->nc->stanOwned = true; - natsConn_Unlock(sc->nc); - - sc->pubAckMaxInflightThreshold = (int) ((float) sc->opts->maxPubAcksInflight * sc->opts->maxPubAcksInFlightPercentage); - if (sc->pubAckMaxInflightThreshold <= 0) - sc->pubAckMaxInflightThreshold = 1; - } - - // Make a copy of user provided clientID - IF_OK_DUP_STRING(s, sc->clientID, clientID); - IF_OK_DUP_STRING(s, sc->clusterID, clusterID); - - if (s == NATS_OK) - { - char tmpNUID[NUID_BUFFER_LEN + 1]; - - s = natsNUID_Next(tmpNUID, NUID_BUFFER_LEN + 1); - IF_OK_DUP_STRING(s, sc->connID, tmpNUID); - if (s == NATS_OK) - sc->connIDLen = (int) strlen(sc->connID); - } - - // Create maps, etc.. - if (s == NATS_OK) - s = natsStrHash_Create(&sc->pubAckMap, 16); - if (s == NATS_OK) - s = natsCondition_Create(&sc->pubAckCond); - if (s == NATS_OK) - s = natsCondition_Create(&sc->pubAckMaxInflightCond); - if (s == NATS_OK) - s = natsMutex_Create(&sc->pubAckMu); - if (s == NATS_OK) - s = natsPBufAllocator_Create(&sc->pubAckAllocator, sizeof(Pb__PubAck), 2); - if (s == NATS_OK) - s = natsMutex_Create(&sc->pingMu); - - // Create HB inbox and a subscription on that - if (s == NATS_OK) - s = natsConn_newInbox(sc->nc, &sc->hbInbox); - if (s == NATS_OK) - { - s = natsConn_subscribeNoPool(&sc->hbSubscription, sc->nc, sc->hbInbox, _processHeartBeat, NULL); - if (s == NATS_OK) - { - natsSubscription_SetPendingLimits(sc->hbSubscription, -1, -1); - sc->refs++; - s = natsSubscription_SetOnCompleteCB(sc->hbSubscription, _releaseStanConnCB, (void*) sc); - if (s != NATS_OK) - sc->refs--; - } - } - // Prepare a subscription on ping responses - if (s == NATS_OK) - { - s = natsConn_newInbox(sc->nc, (natsInbox**) &pingInbox); - if (s == NATS_OK) - s = natsConn_subscribeNoPool(&pingSub, sc->nc, pingInbox, _processPingResponse, (void*) sc); - if (s == NATS_OK) - { - // Mark this as needing a destroy if we end up not using PINGs. - unsubPingSub = true; - - natsSubscription_SetPendingLimits(pingSub, -1, -1); - sc->refs++; - s = natsSubscription_SetOnCompleteCB(pingSub, _releaseStanConnCB, (void*) sc); - if (s != NATS_OK) - sc->refs--; - } - } - - // Send the connection request - if (s == NATS_OK) - { - Pb__ConnectRequest connReq; - int reqSize = 0; - char *reqBytes = NULL; - natsMsg *replyMsg = NULL; - char discoverySubj[256]; - - pb__connect_request__init(&connReq); - connReq.clientid = sc->clientID; - connReq.connid.data = (uint8_t*) sc->connID; - connReq.connid.len = sc->connIDLen; - connReq.heartbeatinbox = sc->hbInbox; - connReq.protocol = PROTOCOL_ONE; - connReq.pinginterval = sc->opts->pingInterval; - connReq.pingmaxout = sc->opts->pingMaxOut; - - reqSize = (int) pb__connect_request__get_packed_size(&connReq); - if (reqSize == 0) - { - s = nats_setError(NATS_ERR, "%s", "connection request protocol packed size is 0"); - } - else - { - reqBytes = NATS_MALLOC(reqSize); - if (reqBytes == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - if (s == NATS_OK) - { - int packedSize = (int) pb__connect_request__pack(&connReq, (uint8_t*) reqBytes); - if (reqSize != packedSize) - { - s = nats_setError(NATS_ERR, "connect request computed packed size is %d, got %d", - reqSize, packedSize); - } - else - { - snprintf(discoverySubj, sizeof(discoverySubj), "%s.%s", sc->opts->discoveryPrefix, clusterID); - s = natsConnection_Request(&replyMsg, sc->nc, discoverySubj, reqBytes, reqSize, sc->opts->connTimeout); - if (s == NATS_TIMEOUT) - NATS_UPDATE_ERR_TXT("%s", STAN_ERR_CONNECT_REQUEST_TIMEOUT); - else if (s == NATS_NO_RESPONDERS) - NATS_UPDATE_ERR_TXT("%s", STAN_ERR_CONNECT_REQUEST_NO_RESP); - } - NATS_FREE(reqBytes); - } - if (s == NATS_OK) - { - Pb__ConnectResponse *connResp = NULL; - - connResp = pb__connect_response__unpack(NULL, - (size_t) natsMsg_GetDataLength(replyMsg), - (const uint8_t*) natsMsg_GetData(replyMsg)); - if (connResp == NULL) - s = nats_setError(NATS_ERR, "%s", "unable to decode connection response"); - - if ((s == NATS_OK) && (strlen(connResp->error) > 0)) - s = nats_setError(NATS_ERR, "%s", connResp->error); - - // Duplicate strings - IF_OK_DUP_STRING(s, sc->pubPrefix, connResp->pubprefix); - IF_OK_DUP_STRING(s, sc->subRequests, connResp->subrequests); - IF_OK_DUP_STRING(s, sc->unsubRequests, connResp->unsubrequests); - IF_OK_DUP_STRING(s, sc->subCloseRequests, connResp->subcloserequests); - IF_OK_DUP_STRING(s, sc->closeRequests, connResp->closerequests); - - if (s == NATS_OK) - sc->pubPrefixLen = (int) strlen(sc->pubPrefix); - - // Do this with servers which are at least at PROTOCOL_ONE. - if ((s == NATS_OK) && (connResp->protocol >= PROTOCOL_ONE)) - { - // Note that in the future server may override client ping - // interval value sent in ConnectRequest, so use the - // value in ConnectResponse to decide if we send PINGs - // and at what interval. - // In tests, the interval could be negative to indicate - // milliseconds. - if (connResp->pinginterval != 0) - { - int64_t interval = 0; - - // These will be immutable. - DUP_STRING(s, sc->pingRequests, connResp->pingrequests); - IF_OK_DUP_STRING(s, sc->pingInbox, pingInbox); - if (s == NATS_OK) - s = _createPingBytes(sc); - - if (s == NATS_OK) - { - // In test, it is possible that we get a negative value - // to represent milliseconds. - if (testAllowMillisecInPings && (connResp->pinginterval < 0)) - interval = sc->opts->pingInterval * -1; - else - interval = sc->opts->pingInterval * 1000; - - sc->opts->pingMaxOut = (int) connResp->pingmaxout; - } - - if (s == NATS_OK) - { - // Set the timer now that we are set. Use lock to create - // synchronization point. - natsMutex_Lock(sc->pingMu); - s = natsTimer_Create(&sc->pingTimer, _pingServer, _pingTimerStopCB, - interval, (void*) sc); - if (s == NATS_OK) - sc->refs++; - natsMutex_Unlock(sc->pingMu); - } - - if (s == NATS_OK) - { - sc->pingSub = pingSub; - unsubPingSub = false; - } - } - } - - pb__connect_response__free_unpacked(connResp, NULL); - - natsMsg_Destroy(replyMsg); - } - } - // Setup (pub) ACK subscription - if (s == NATS_OK) - { - char tmp[11 + NUID_BUFFER_LEN + 1]; - - snprintf(tmp, sizeof(tmp), "%s", "_STAN.acks."); - s = natsNUID_Next(tmp+11, NUID_BUFFER_LEN + 1); - IF_OK_DUP_STRING(s, sc->ackSubject, (char*)tmp); - - if (s == NATS_OK) - s = natsConn_subscribeNoPool(&sc->ackSubscription, sc->nc, sc->ackSubject, stanProcessPubAck, (void*) sc); - if (s == NATS_OK) - { - natsSubscription_SetPendingLimits(sc->ackSubscription, -1, -1); - sc->refs++; - s = natsSubscription_SetOnCompleteCB(sc->ackSubscription, _releaseStanConnCB, (void*) sc); - if (s != NATS_OK) - sc->refs--; - } - } - - if (unsubPingSub) - natsSubscription_Destroy(pingSub); - - if (s == NATS_OK) - *newConn = sc; - else - { - if (sc->nc != NULL) - natsConn_close(sc->nc); - - stanConn_release(sc); - } - - NATS_FREE(pingInbox); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -stanConnection_GetNATSConnection(stanConnection *sc, natsConnection **nc) -{ - natsConnection *snc = NULL; - - if ((sc == NULL) || (nc == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - stanConn_Lock(sc); - if (sc->closed) - { - stanConn_Unlock(sc); - return nats_setDefaultError(NATS_CONNECTION_CLOSED); - } - snc = sc->nc; - // For the first, retain the stan connection - if (sc->ncRefs++ == 0) - _retain(sc); - stanConn_Unlock(sc); - - *nc = snc; - return NATS_OK; -} - -void -stanConnection_ReleaseNATSConnection(stanConnection *sc) -{ - bool doRelease = false; - - if (sc == NULL) - return; - - stanConn_Lock(sc); - // Make sure this does not go below zero - if (sc->ncRefs > 0) - doRelease = (--sc->ncRefs == 0); - stanConn_Unlock(sc); - - if (doRelease) - stanConn_release(sc); -} - -natsStatus -stanConnClose(stanConnection *sc, bool sendProto) -{ - natsStatus s = NATS_OK; - Pb__CloseRequest closeReq; - int reqSize = 0; - char *reqBytes = NULL; - natsMsg *replyMsg = NULL; - natsConnection *nc = NULL; - char *cid = NULL; - char *closeSubj= NULL; - int64_t timeout = 0; - - // Need to release publish call if applicable. - - // Do not grab the connection lock yet since a publish call - // may be holding the connection lock but wait on the - // pubAckMaxInflightCond condition variable. - natsMutex_Lock(sc->pubAckMu); - if (!sc->pubAckClosed) - { - sc->pubAckClosed = true; - natsCondition_Broadcast(sc->pubAckMaxInflightCond); - } - natsMutex_Unlock(sc->pubAckMu); - - stanConn_Lock(sc); - if (sc->closed) - { - stanConn_Unlock(sc); - return NATS_OK; - } - natsMutex_Lock(sc->pubAckMu); - natsMutex_Lock(sc->pingMu); - sc->closed = true; - natsMutex_Unlock(sc->pingMu); - // Release possible blocked publish calls - natsCondition_Broadcast(sc->pubAckCond); - natsMutex_Unlock(sc->pubAckMu); - - natsSubscription_Unsubscribe(sc->hbSubscription); - natsSubscription_Unsubscribe(sc->ackSubscription); - - // If there is a timer set, make it trigger soon, this will - // release the pending pubAcks for async publish calls. - if (sc->pubAckTimer != NULL) - natsTimer_Reset(sc->pubAckTimer, 1); - - if (sc->pingTimer != NULL) - natsTimer_Stop(sc->pingTimer); - - nc = sc->nc; - cid = sc->clientID; - closeSubj = sc->closeRequests; - timeout = sc->opts->connTimeout; - stanConn_Unlock(sc); - - if (sendProto) - { - pb__close_request__init(&closeReq); - closeReq.clientid = cid; - - reqSize = (int) pb__close_request__get_packed_size(&closeReq); - if (reqSize == 0) - { - s = nats_setError(NATS_ERR, "%s", "connection close protocol packed size is 0"); - } - else - { - reqBytes = NATS_MALLOC(reqSize); - if (reqBytes == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - if (s == NATS_OK) - { - int packedSize = (int) pb__close_request__pack(&closeReq, (uint8_t*) reqBytes); - if (reqSize != packedSize) - { - s = nats_setError(NATS_ERR, "connection close request computed packed size is %d, got %v", - reqSize, packedSize); - } - else - { - s = natsConnection_Request(&replyMsg, nc, closeSubj, reqBytes, reqSize, timeout); - if (s == NATS_TIMEOUT) - NATS_UPDATE_ERR_TXT("%s", STAN_ERR_CLOSE_REQUEST_TIMEOUT); - else if (s == NATS_NO_RESPONDERS) - NATS_UPDATE_ERR_TXT("%s", STAN_ERR_CLOSE_REQUEST_NO_RESP); - } - - NATS_FREE(reqBytes); - } - if (s == NATS_OK) - { - Pb__CloseResponse *closeResp = NULL; - - closeResp = pb__close_response__unpack(NULL, - (size_t) natsMsg_GetDataLength(replyMsg), - (const uint8_t*) natsMsg_GetData(replyMsg)); - - if ((closeResp != NULL) && (strlen(closeResp->error) > 0)) - s = nats_setError(NATS_ERR, "%s", closeResp->error); - - pb__close_response__free_unpacked(closeResp, NULL); - natsMsg_Destroy(replyMsg); - } - } - } - - natsConn_close(sc->nc); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -stanConnection_Close(stanConnection *sc) -{ - natsStatus s; - - if (sc == NULL) - return NATS_OK; - - s = stanConnClose(sc, true); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -stanConnection_Destroy(stanConnection *sc) -{ - natsStatus s; - - if (sc == NULL) - return NATS_OK; - - s = stanConnClose(sc, true); - stanConn_release(sc); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -expandBuf(char **buf, int *cap, int newcap) -{ - char *newBuf = NULL; - - if (*buf == NULL) - newBuf = NATS_MALLOC(newcap); - else - newBuf = NATS_REALLOC(*buf, newcap); - if (newBuf == NULL) - return nats_setError(NATS_NO_MEMORY, "unable to expand buffer from %d to %d", *cap, newcap); - - *buf = newBuf; // possibly same if realloc did it in place - *cap = newcap; - - return NATS_OK; -} - -static int -_roundUp(int val) -{ - return ((val+(MEMALIGN-1))/MEMALIGN)*MEMALIGN; -} - -static void* -_pbufAlloc(void *allocator, size_t size) -{ - natsPBufAllocator *a = (natsPBufAllocator*) allocator; - int needed = MEMALIGN + _roundUp((int) size); - char *ptr; - - if (needed > a->remaining) - { - ptr = NATS_MALLOC(needed); - ptr[0] = '1'; - } - else - { - ptr = a->buf+a->used; - - a->used += needed; - a->remaining -= needed; - - ptr[0] = '0'; - } - return (void*) (ptr+MEMALIGN); -} - -static void -_pbufFree(void *allocator, void *ptr) -{ - char *real = ((char*)ptr - MEMALIGN); - - if (real[0] == '1') - NATS_FREE(real); -} - -// Creates a new protobuf allocator with given protobuf object size and overhead. -// When calling pb__xxx__unpack() functions, we will pass such allocator. -// An allocator is created for a specific protobuf object. The protobuf library -// will call the alloc function with, at the very least, the size of the protobuf -// object (protoSize), and for each field that is a string or byte array. -// For strings, the protobuf library asks for 1 more byte. The overhead is -// to count the number of expected strings or byte arrays in the protobuf object -// the allocator is created for. -// -// An allocator once created is not thread-safe and expected to be used in a -// single thread this way: -// -// natsPBufAllocator_Prepare(alloc, msg->dataLen); -// pbMsg = pb__msg_proto__unpack(alloc, (size_t) msg->dataLen, (const uint8_t*) msg->data); -// ... -// pb__msg_proto__free_unpacked(pbMsg, alloc); -// -natsStatus -natsPBufAllocator_Create(natsPBufAllocator **newAllocator, int protoSize, int overhead) -{ - natsPBufAllocator *a = NULL; - - a = NATS_CALLOC(1, sizeof(natsPBufAllocator)); - if (a == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - a->protoSize = MEMALIGN + _roundUp(protoSize); - a->overhead = (overhead * MEMALIGN) + overhead + (overhead * (MEMALIGN-1)); - - a->base.alloc = _pbufAlloc; - a->base.free = _pbufFree; - a->base.allocator_data = a; - - *newAllocator = a; - - return NATS_OK; -} - -// Prepare resets some internal counters and allocate or expand the buffer -// based on the known size of the protobuf object and the given buffer size -// that is going to be unpacked. -void -natsPBufAllocator_Prepare(natsPBufAllocator *allocator, int bufSize) -{ - int needed = allocator->protoSize + allocator->overhead + bufSize; - - if (needed > allocator->cap) - expandBuf(&allocator->buf, &allocator->cap, needed); - - allocator->remaining = allocator->cap; - allocator->used = 0; -} - -void -natsPBufAllocator_Destroy(natsPBufAllocator *allocator) -{ - if (allocator == NULL) - return; - - NATS_FREE(allocator->buf); - NATS_FREE(allocator); -} diff --git a/src/stan/conn.h b/src/stan/conn.h deleted file mode 100644 index d797319f0..000000000 --- a/src/stan/conn.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2018 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. - -#ifndef STAN_CONN_H_ -#define STAN_CONN_H_ - -#include "stanp.h" - -#ifdef DEV_MODE -// For type safety - -void stanConn_Lock(stanConnection *sc); -void stanConn_Unlock(stanConnection *sc); - -#else -// We know what we are doing :-) - -#define stanConn_Lock(c) (natsMutex_Lock((c)->mu)) -#define stanConn_Unlock(c) (natsMutex_Unlock((c)->mu)) - -#endif // DEV_MODE - -#define STAN_ERR_CONNECT_REQUEST_TIMEOUT "connect request timeout" -#define STAN_ERR_CONNECT_REQUEST_NO_RESP "no streaming server was listening for this connect request" -#define STAN_ERR_CLOSE_REQUEST_TIMEOUT "close request timeout" -#define STAN_ERR_CLOSE_REQUEST_NO_RESP "no streaming server was listening for this close request" -#define STAN_ERR_MAX_PINGS "connection lost due to PING failure" - -extern int MEMALIGN; - -void -stanConn_retain(stanConnection *nc); - -void -stanConn_release(stanConnection *nc); - -void -stanConn_defaultConnLostHandler(stanConnection *sc, const char* errorTxt, void *closure); - -natsStatus -stanConnClose(stanConnection *sc, bool sendProto); - -natsStatus -expandBuf(char **buf, int *cap, int newcap); - -natsStatus -natsPBufAllocator_Create(natsPBufAllocator **newAllocator, int protoSize, int overhead); - -void -natsPBufAllocator_Prepare(natsPBufAllocator *allocator, int bufSize); - -void -natsPBufAllocator_Destroy(natsPBufAllocator *allocator); - -#endif /* STAN_CONN_H_ */ diff --git a/src/stan/copts.c b/src/stan/copts.c deleted file mode 100644 index 908b2fbce..000000000 --- a/src/stan/copts.c +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright 2018-2019 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 "conn.h" -#include "copts.h" -#include "../opts.h" - -static void -_stanConnOpts_free(stanConnOptions *opts) -{ - if (opts == NULL) - return; - - NATS_FREE(opts->url); - NATS_FREE(opts->discoveryPrefix); - natsOptions_Destroy(opts->ncOpts); - natsMutex_Destroy(opts->mu); - NATS_FREE(opts); -} - -natsStatus -stanConnOptions_Create(stanConnOptions **newOpts) -{ - natsStatus s = NATS_OK; - stanConnOptions *opts = NULL; - - // Ensure the library is loaded - s = nats_Open(-1); - if (s != NATS_OK) - return s; - - opts = (stanConnOptions*) NATS_CALLOC(1, sizeof(stanConnOptions)); - if (opts == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - if (natsMutex_Create(&(opts->mu)) != NATS_OK) - { - NATS_FREE(opts); - return NATS_UPDATE_ERR_STACK(NATS_NO_MEMORY); - } - - DUP_STRING(s, opts->discoveryPrefix, STAN_CONN_OPTS_DEFAULT_DISCOVERY_PREFIX); - if (s == NATS_OK) - { - opts->pubAckTimeout = STAN_CONN_OPTS_DEFAULT_PUB_ACK_TIMEOUT; - opts->connTimeout = STAN_CONN_OPTS_DEFAULT_CONN_TIMEOUT; - opts->maxPubAcksInflight = STAN_CONN_OPTS_DEFAULT_MAX_PUB_ACKS_INFLIGHT; - opts->maxPubAcksInFlightPercentage = STAN_CONN_OPTS_DEFAULT_MAX_PUB_ACKS_INFLIGHT_PERCENTAGE; - opts->pingInterval = STAN_CONN_OPTS_DEFAULT_PING_INTERVAL; - opts->pingMaxOut = STAN_CONN_OPTS_DEFAULT_PING_MAX_OUT; - opts->connectionLostCB = stanConn_defaultConnLostHandler; - } - - if (s == NATS_OK) - *newOpts = opts; - else - _stanConnOpts_free(opts); - - return s; -} - -natsStatus -stanConnOptions_SetURL(stanConnOptions *opts, const char *url) -{ - natsStatus s = NATS_OK; - - LOCK_AND_CHECK_OPTIONS(opts, 0); - - if (opts->url != NULL) - { - NATS_FREE(opts->url); - opts->url = NULL; - } - if ((url != NULL) && (url[0] != '\0')) - DUP_STRING(s, opts->url, url); - - UNLOCK_OPTS(opts); - - return s; -} - -natsStatus -stanConnOptions_SetNATSOptions(stanConnOptions *opts, natsOptions *nOpts) -{ - natsStatus s = NATS_OK; - - LOCK_AND_CHECK_OPTIONS(opts, 0); - - if (opts->ncOpts != NULL) - { - natsOptions_Destroy(opts->ncOpts); - opts->ncOpts = NULL; - } - if (nOpts != NULL) - { - opts->ncOpts = natsOptions_clone(nOpts); - if (opts->ncOpts == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - - UNLOCK_OPTS(opts); - - return s; -} - -natsStatus -stanConnOptions_SetConnectionWait(stanConnOptions *opts, int64_t wait) -{ - LOCK_AND_CHECK_OPTIONS(opts, (wait <= 0)); - - opts->connTimeout = wait; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -stanConnOptions_SetPubAckWait(stanConnOptions *opts, int64_t wait) -{ - LOCK_AND_CHECK_OPTIONS(opts, (wait <= 0)); - - opts->pubAckTimeout = wait; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -stanConnOptions_SetDiscoveryPrefix(stanConnOptions *opts, const char *prefix) -{ - natsStatus s = NATS_OK; - - LOCK_AND_CHECK_OPTIONS(opts, ((prefix == NULL) || (prefix[0]=='\0'))); - - if (opts->discoveryPrefix != NULL) - { - NATS_FREE(opts->discoveryPrefix); - opts->discoveryPrefix = NULL; - } - DUP_STRING(s, opts->discoveryPrefix, prefix); - - UNLOCK_OPTS(opts); - - return s; -} - -natsStatus -stanConnOptions_SetMaxPubAcksInflight(stanConnOptions *opts, int maxPubAcksInflight, float percentage) -{ - LOCK_AND_CHECK_OPTIONS(opts, ((maxPubAcksInflight <= 0) || (percentage <= 0.0) || (percentage > 1.0))); - - opts->maxPubAcksInflight = maxPubAcksInflight; - opts->maxPubAcksInFlightPercentage = percentage; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -stanConnOptions_SetPings(stanConnOptions *opts, int interval, int maxOut) -{ - if (testAllowMillisecInPings) - { - if ((interval == 0) || (maxOut < 2)) - return nats_setDefaultError(NATS_INVALID_ARG); - } - else - { - if ((interval <= 0) || (maxOut < 2)) - return nats_setDefaultError(NATS_INVALID_ARG); - } - - natsMutex_Lock(opts->mu); - - opts->pingInterval = interval; - opts->pingMaxOut = maxOut; - - natsMutex_Unlock(opts->mu); - - return NATS_OK; -} - -natsStatus -stanConnOptions_SetConnectionLostHandler(stanConnOptions *opts, stanConnectionLostHandler handler, void *closure) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->connectionLostCB = handler; - opts->connectionLostCBClosure = closure; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -stanConnOptions_clone(stanConnOptions **clonedOpts, stanConnOptions *opts) -{ - natsStatus s = NATS_OK; - stanConnOptions *cloned = NULL; - int muSize; - - s = stanConnOptions_Create(&cloned); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - // The Create call sets (strdup) the default discovery prefix. - // So free those now. - NATS_FREE(cloned->discoveryPrefix); - cloned->discoveryPrefix = NULL; - - natsMutex_Lock(opts->mu); - - muSize = sizeof(cloned->mu); - - // Make a blind copy first... - memcpy((char*)cloned + muSize, (char*)opts + muSize, - sizeof(stanConnOptions) - muSize); - - // Then remove all pointers, so that if we fail while - // copying them, and free the cloned, we don't free the pointers - // from the original. - cloned->url = NULL; - cloned->discoveryPrefix = NULL; - cloned->ncOpts = NULL; - - s = stanConnOptions_SetURL(cloned, opts->url); - if (s == NATS_OK) - s = stanConnOptions_SetDiscoveryPrefix(cloned, opts->discoveryPrefix); - if (s == NATS_OK) - s = stanConnOptions_SetNATSOptions(cloned, opts->ncOpts); - - if (s == NATS_OK) - *clonedOpts = cloned; - else - _stanConnOpts_free(cloned); - - natsMutex_Unlock(opts->mu); - - return s; -} - -void -stanConnOptions_Destroy(stanConnOptions *opts) -{ - if (opts == NULL) - return; - - _stanConnOpts_free(opts); -} diff --git a/src/stan/copts.h b/src/stan/copts.h deleted file mode 100644 index e9dbaf20c..000000000 --- a/src/stan/copts.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018 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. - -#ifndef STAN_CONN_OPTS_H_ -#define STAN_CONN_OPTS_H_ - -#include "stanp.h" - -#define STAN_CONN_OPTS_DEFAULT_DISCOVERY_PREFIX "_STAN.discover" -#define STAN_CONN_OPTS_DEFAULT_PUB_ACK_TIMEOUT (30 * 1000) // 30 seconds -#define STAN_CONN_OPTS_DEFAULT_CONN_TIMEOUT (2 * 1000) // 2 seconds -#define STAN_CONN_OPTS_DEFAULT_MAX_PUB_ACKS_INFLIGHT (16384) -#define STAN_CONN_OPTS_DEFAULT_MAX_PUB_ACKS_INFLIGHT_PERCENTAGE (float) (0.5) // 50% of MaxPubAcksInflight -#define STAN_CONN_OPTS_DEFAULT_PING_INTERVAL (5) // 5 seconds -#define STAN_CONN_OPTS_DEFAULT_PING_MAX_OUT (88) - -natsStatus -stanConnOptions_clone(stanConnOptions **clonedOpts, stanConnOptions *opts); - -#endif /* STAN_CONN_OPTS_H_ */ diff --git a/src/stan/msg.c b/src/stan/msg.c deleted file mode 100644 index 3ce2d5269..000000000 --- a/src/stan/msg.c +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2018 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 "msg.h" - -uint64_t -stanMsg_GetSequence(const stanMsg *msg) -{ - if (msg == NULL) - return 0; - - return msg->seq; -} - -const char* -stanMsg_GetData(const stanMsg *msg) -{ - if (msg == NULL) - return NULL; - - return (const char*) msg->data; -} - -int -stanMsg_GetDataLength(const stanMsg *msg) -{ - if (msg == NULL) - return 0; - - return msg->dataLen; -} - -int64_t -stanMsg_GetTimestamp(const stanMsg *msg) -{ - if (msg == NULL) - return 0; - - return msg->timestamp; -} - -bool -stanMsg_IsRedelivered(const stanMsg *msg) -{ - if (msg == NULL) - return false; - - return msg->redelivered; -} - -static void -stanMsg_free(void *object) -{ - stanMsg *msg; - - if (object == NULL) - return; - - msg = (stanMsg*) object; - - NATS_FREE(msg); -} - -void -stanMsg_Destroy(stanMsg *msg) -{ - if (msg == NULL) - return; - - if (natsGC_collect((natsGCItem *) msg)) - return; - - stanMsg_free((void*) msg); -} - -natsStatus -stanMsg_create(stanMsg **newMsg, stanSubscription *sub, Pb__MsgProto *pb) -{ - stanMsg *msg = NULL; - char *ptr = NULL; - int payloadSize = 0; - - payloadSize = (int) pb->data.len; - msg = NATS_MALLOC(sizeof(stanMsg) + payloadSize + 1); - if (msg == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - // To be safe, we could 'memset' the message up to sizeof(stanMsg), - // but since we are explicitly initializing most of the fields, we save - // on that call, but we need to make sure what we initialize all fields!!! - - // That being said, we memset the 'gc' structure to protect us in case - // some fields are later added to this 'external' structure and we forget - // about updating this initialization code. - memset(&(msg->gc), 0, sizeof(natsGCItem)); - - msg->seq = pb->sequence; - msg->timestamp = pb->timestamp; - msg->redelivered = pb->redelivered; - msg->sub = sub; - - ptr = (char*) (((char*) &(msg->sub)) + sizeof(msg->sub)); - msg->data = (const char*) ptr; - msg->dataLen = payloadSize; - memcpy(ptr, (char*) pb->data.data, payloadSize); - ptr += payloadSize; - *(ptr) = '\0'; - - // Setting the callback will trigger garbage collection when - // stanMsg_Destroy() is invoked. - msg->gc.freeCb = stanMsg_free; - - *newMsg = msg; - - return NATS_OK; -} diff --git a/src/stan/protocol.pb-c.c b/src/stan/protocol.pb-c.c deleted file mode 100644 index 24f046d25..000000000 --- a/src/stan/protocol.pb-c.c +++ /dev/null @@ -1,1672 +0,0 @@ -/* Generated by the protocol buffer compiler. DO NOT EDIT! */ -/* Generated from: protocol.proto */ - -/* Do not generate deprecated warnings for self */ -#ifndef PROTOBUF_C__NO_DEPRECATED -#define PROTOBUF_C__NO_DEPRECATED -#endif - -#include "protocol.pb-c.h" -void pb__pub_msg__init - (Pb__PubMsg *message) -{ - static const Pb__PubMsg init_value = PB__PUB_MSG__INIT; - *message = init_value; -} -size_t pb__pub_msg__get_packed_size - (const Pb__PubMsg *message) -{ - assert(message->base.descriptor == &pb__pub_msg__descriptor); - return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); -} -size_t pb__pub_msg__pack - (const Pb__PubMsg *message, - uint8_t *out) -{ - assert(message->base.descriptor == &pb__pub_msg__descriptor); - return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); -} -size_t pb__pub_msg__pack_to_buffer - (const Pb__PubMsg *message, - ProtobufCBuffer *buffer) -{ - assert(message->base.descriptor == &pb__pub_msg__descriptor); - return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); -} -Pb__PubMsg * - pb__pub_msg__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data) -{ - return (Pb__PubMsg *) - protobuf_c_message_unpack (&pb__pub_msg__descriptor, - allocator, len, data); -} -void pb__pub_msg__free_unpacked - (Pb__PubMsg *message, - ProtobufCAllocator *allocator) -{ - if(!message) - return; - assert(message->base.descriptor == &pb__pub_msg__descriptor); - protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); -} -void pb__pub_ack__init - (Pb__PubAck *message) -{ - static const Pb__PubAck init_value = PB__PUB_ACK__INIT; - *message = init_value; -} -size_t pb__pub_ack__get_packed_size - (const Pb__PubAck *message) -{ - assert(message->base.descriptor == &pb__pub_ack__descriptor); - return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); -} -size_t pb__pub_ack__pack - (const Pb__PubAck *message, - uint8_t *out) -{ - assert(message->base.descriptor == &pb__pub_ack__descriptor); - return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); -} -size_t pb__pub_ack__pack_to_buffer - (const Pb__PubAck *message, - ProtobufCBuffer *buffer) -{ - assert(message->base.descriptor == &pb__pub_ack__descriptor); - return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); -} -Pb__PubAck * - pb__pub_ack__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data) -{ - return (Pb__PubAck *) - protobuf_c_message_unpack (&pb__pub_ack__descriptor, - allocator, len, data); -} -void pb__pub_ack__free_unpacked - (Pb__PubAck *message, - ProtobufCAllocator *allocator) -{ - if(!message) - return; - assert(message->base.descriptor == &pb__pub_ack__descriptor); - protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); -} -void pb__msg_proto__init - (Pb__MsgProto *message) -{ - static const Pb__MsgProto init_value = PB__MSG_PROTO__INIT; - *message = init_value; -} -size_t pb__msg_proto__get_packed_size - (const Pb__MsgProto *message) -{ - assert(message->base.descriptor == &pb__msg_proto__descriptor); - return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); -} -size_t pb__msg_proto__pack - (const Pb__MsgProto *message, - uint8_t *out) -{ - assert(message->base.descriptor == &pb__msg_proto__descriptor); - return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); -} -size_t pb__msg_proto__pack_to_buffer - (const Pb__MsgProto *message, - ProtobufCBuffer *buffer) -{ - assert(message->base.descriptor == &pb__msg_proto__descriptor); - return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); -} -Pb__MsgProto * - pb__msg_proto__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data) -{ - return (Pb__MsgProto *) - protobuf_c_message_unpack (&pb__msg_proto__descriptor, - allocator, len, data); -} -void pb__msg_proto__free_unpacked - (Pb__MsgProto *message, - ProtobufCAllocator *allocator) -{ - if(!message) - return; - assert(message->base.descriptor == &pb__msg_proto__descriptor); - protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); -} -void pb__ack__init - (Pb__Ack *message) -{ - static const Pb__Ack init_value = PB__ACK__INIT; - *message = init_value; -} -size_t pb__ack__get_packed_size - (const Pb__Ack *message) -{ - assert(message->base.descriptor == &pb__ack__descriptor); - return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); -} -size_t pb__ack__pack - (const Pb__Ack *message, - uint8_t *out) -{ - assert(message->base.descriptor == &pb__ack__descriptor); - return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); -} -size_t pb__ack__pack_to_buffer - (const Pb__Ack *message, - ProtobufCBuffer *buffer) -{ - assert(message->base.descriptor == &pb__ack__descriptor); - return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); -} -Pb__Ack * - pb__ack__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data) -{ - return (Pb__Ack *) - protobuf_c_message_unpack (&pb__ack__descriptor, - allocator, len, data); -} -void pb__ack__free_unpacked - (Pb__Ack *message, - ProtobufCAllocator *allocator) -{ - if(!message) - return; - assert(message->base.descriptor == &pb__ack__descriptor); - protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); -} -void pb__connect_request__init - (Pb__ConnectRequest *message) -{ - static const Pb__ConnectRequest init_value = PB__CONNECT_REQUEST__INIT; - *message = init_value; -} -size_t pb__connect_request__get_packed_size - (const Pb__ConnectRequest *message) -{ - assert(message->base.descriptor == &pb__connect_request__descriptor); - return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); -} -size_t pb__connect_request__pack - (const Pb__ConnectRequest *message, - uint8_t *out) -{ - assert(message->base.descriptor == &pb__connect_request__descriptor); - return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); -} -size_t pb__connect_request__pack_to_buffer - (const Pb__ConnectRequest *message, - ProtobufCBuffer *buffer) -{ - assert(message->base.descriptor == &pb__connect_request__descriptor); - return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); -} -Pb__ConnectRequest * - pb__connect_request__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data) -{ - return (Pb__ConnectRequest *) - protobuf_c_message_unpack (&pb__connect_request__descriptor, - allocator, len, data); -} -void pb__connect_request__free_unpacked - (Pb__ConnectRequest *message, - ProtobufCAllocator *allocator) -{ - if(!message) - return; - assert(message->base.descriptor == &pb__connect_request__descriptor); - protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); -} -void pb__connect_response__init - (Pb__ConnectResponse *message) -{ - static const Pb__ConnectResponse init_value = PB__CONNECT_RESPONSE__INIT; - *message = init_value; -} -size_t pb__connect_response__get_packed_size - (const Pb__ConnectResponse *message) -{ - assert(message->base.descriptor == &pb__connect_response__descriptor); - return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); -} -size_t pb__connect_response__pack - (const Pb__ConnectResponse *message, - uint8_t *out) -{ - assert(message->base.descriptor == &pb__connect_response__descriptor); - return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); -} -size_t pb__connect_response__pack_to_buffer - (const Pb__ConnectResponse *message, - ProtobufCBuffer *buffer) -{ - assert(message->base.descriptor == &pb__connect_response__descriptor); - return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); -} -Pb__ConnectResponse * - pb__connect_response__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data) -{ - return (Pb__ConnectResponse *) - protobuf_c_message_unpack (&pb__connect_response__descriptor, - allocator, len, data); -} -void pb__connect_response__free_unpacked - (Pb__ConnectResponse *message, - ProtobufCAllocator *allocator) -{ - if(!message) - return; - assert(message->base.descriptor == &pb__connect_response__descriptor); - protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); -} -void pb__ping__init - (Pb__Ping *message) -{ - static const Pb__Ping init_value = PB__PING__INIT; - *message = init_value; -} -size_t pb__ping__get_packed_size - (const Pb__Ping *message) -{ - assert(message->base.descriptor == &pb__ping__descriptor); - return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); -} -size_t pb__ping__pack - (const Pb__Ping *message, - uint8_t *out) -{ - assert(message->base.descriptor == &pb__ping__descriptor); - return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); -} -size_t pb__ping__pack_to_buffer - (const Pb__Ping *message, - ProtobufCBuffer *buffer) -{ - assert(message->base.descriptor == &pb__ping__descriptor); - return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); -} -Pb__Ping * - pb__ping__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data) -{ - return (Pb__Ping *) - protobuf_c_message_unpack (&pb__ping__descriptor, - allocator, len, data); -} -void pb__ping__free_unpacked - (Pb__Ping *message, - ProtobufCAllocator *allocator) -{ - if(!message) - return; - assert(message->base.descriptor == &pb__ping__descriptor); - protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); -} -void pb__ping_response__init - (Pb__PingResponse *message) -{ - static const Pb__PingResponse init_value = PB__PING_RESPONSE__INIT; - *message = init_value; -} -size_t pb__ping_response__get_packed_size - (const Pb__PingResponse *message) -{ - assert(message->base.descriptor == &pb__ping_response__descriptor); - return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); -} -size_t pb__ping_response__pack - (const Pb__PingResponse *message, - uint8_t *out) -{ - assert(message->base.descriptor == &pb__ping_response__descriptor); - return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); -} -size_t pb__ping_response__pack_to_buffer - (const Pb__PingResponse *message, - ProtobufCBuffer *buffer) -{ - assert(message->base.descriptor == &pb__ping_response__descriptor); - return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); -} -Pb__PingResponse * - pb__ping_response__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data) -{ - return (Pb__PingResponse *) - protobuf_c_message_unpack (&pb__ping_response__descriptor, - allocator, len, data); -} -void pb__ping_response__free_unpacked - (Pb__PingResponse *message, - ProtobufCAllocator *allocator) -{ - if(!message) - return; - assert(message->base.descriptor == &pb__ping_response__descriptor); - protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); -} -void pb__subscription_request__init - (Pb__SubscriptionRequest *message) -{ - static const Pb__SubscriptionRequest init_value = PB__SUBSCRIPTION_REQUEST__INIT; - *message = init_value; -} -size_t pb__subscription_request__get_packed_size - (const Pb__SubscriptionRequest *message) -{ - assert(message->base.descriptor == &pb__subscription_request__descriptor); - return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); -} -size_t pb__subscription_request__pack - (const Pb__SubscriptionRequest *message, - uint8_t *out) -{ - assert(message->base.descriptor == &pb__subscription_request__descriptor); - return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); -} -size_t pb__subscription_request__pack_to_buffer - (const Pb__SubscriptionRequest *message, - ProtobufCBuffer *buffer) -{ - assert(message->base.descriptor == &pb__subscription_request__descriptor); - return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); -} -Pb__SubscriptionRequest * - pb__subscription_request__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data) -{ - return (Pb__SubscriptionRequest *) - protobuf_c_message_unpack (&pb__subscription_request__descriptor, - allocator, len, data); -} -void pb__subscription_request__free_unpacked - (Pb__SubscriptionRequest *message, - ProtobufCAllocator *allocator) -{ - if(!message) - return; - assert(message->base.descriptor == &pb__subscription_request__descriptor); - protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); -} -void pb__subscription_response__init - (Pb__SubscriptionResponse *message) -{ - static const Pb__SubscriptionResponse init_value = PB__SUBSCRIPTION_RESPONSE__INIT; - *message = init_value; -} -size_t pb__subscription_response__get_packed_size - (const Pb__SubscriptionResponse *message) -{ - assert(message->base.descriptor == &pb__subscription_response__descriptor); - return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); -} -size_t pb__subscription_response__pack - (const Pb__SubscriptionResponse *message, - uint8_t *out) -{ - assert(message->base.descriptor == &pb__subscription_response__descriptor); - return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); -} -size_t pb__subscription_response__pack_to_buffer - (const Pb__SubscriptionResponse *message, - ProtobufCBuffer *buffer) -{ - assert(message->base.descriptor == &pb__subscription_response__descriptor); - return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); -} -Pb__SubscriptionResponse * - pb__subscription_response__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data) -{ - return (Pb__SubscriptionResponse *) - protobuf_c_message_unpack (&pb__subscription_response__descriptor, - allocator, len, data); -} -void pb__subscription_response__free_unpacked - (Pb__SubscriptionResponse *message, - ProtobufCAllocator *allocator) -{ - if(!message) - return; - assert(message->base.descriptor == &pb__subscription_response__descriptor); - protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); -} -void pb__unsubscribe_request__init - (Pb__UnsubscribeRequest *message) -{ - static const Pb__UnsubscribeRequest init_value = PB__UNSUBSCRIBE_REQUEST__INIT; - *message = init_value; -} -size_t pb__unsubscribe_request__get_packed_size - (const Pb__UnsubscribeRequest *message) -{ - assert(message->base.descriptor == &pb__unsubscribe_request__descriptor); - return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); -} -size_t pb__unsubscribe_request__pack - (const Pb__UnsubscribeRequest *message, - uint8_t *out) -{ - assert(message->base.descriptor == &pb__unsubscribe_request__descriptor); - return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); -} -size_t pb__unsubscribe_request__pack_to_buffer - (const Pb__UnsubscribeRequest *message, - ProtobufCBuffer *buffer) -{ - assert(message->base.descriptor == &pb__unsubscribe_request__descriptor); - return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); -} -Pb__UnsubscribeRequest * - pb__unsubscribe_request__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data) -{ - return (Pb__UnsubscribeRequest *) - protobuf_c_message_unpack (&pb__unsubscribe_request__descriptor, - allocator, len, data); -} -void pb__unsubscribe_request__free_unpacked - (Pb__UnsubscribeRequest *message, - ProtobufCAllocator *allocator) -{ - if(!message) - return; - assert(message->base.descriptor == &pb__unsubscribe_request__descriptor); - protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); -} -void pb__close_request__init - (Pb__CloseRequest *message) -{ - static const Pb__CloseRequest init_value = PB__CLOSE_REQUEST__INIT; - *message = init_value; -} -size_t pb__close_request__get_packed_size - (const Pb__CloseRequest *message) -{ - assert(message->base.descriptor == &pb__close_request__descriptor); - return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); -} -size_t pb__close_request__pack - (const Pb__CloseRequest *message, - uint8_t *out) -{ - assert(message->base.descriptor == &pb__close_request__descriptor); - return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); -} -size_t pb__close_request__pack_to_buffer - (const Pb__CloseRequest *message, - ProtobufCBuffer *buffer) -{ - assert(message->base.descriptor == &pb__close_request__descriptor); - return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); -} -Pb__CloseRequest * - pb__close_request__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data) -{ - return (Pb__CloseRequest *) - protobuf_c_message_unpack (&pb__close_request__descriptor, - allocator, len, data); -} -void pb__close_request__free_unpacked - (Pb__CloseRequest *message, - ProtobufCAllocator *allocator) -{ - if(!message) - return; - assert(message->base.descriptor == &pb__close_request__descriptor); - protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); -} -void pb__close_response__init - (Pb__CloseResponse *message) -{ - static const Pb__CloseResponse init_value = PB__CLOSE_RESPONSE__INIT; - *message = init_value; -} -size_t pb__close_response__get_packed_size - (const Pb__CloseResponse *message) -{ - assert(message->base.descriptor == &pb__close_response__descriptor); - return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); -} -size_t pb__close_response__pack - (const Pb__CloseResponse *message, - uint8_t *out) -{ - assert(message->base.descriptor == &pb__close_response__descriptor); - return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); -} -size_t pb__close_response__pack_to_buffer - (const Pb__CloseResponse *message, - ProtobufCBuffer *buffer) -{ - assert(message->base.descriptor == &pb__close_response__descriptor); - return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); -} -Pb__CloseResponse * - pb__close_response__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data) -{ - return (Pb__CloseResponse *) - protobuf_c_message_unpack (&pb__close_response__descriptor, - allocator, len, data); -} -void pb__close_response__free_unpacked - (Pb__CloseResponse *message, - ProtobufCAllocator *allocator) -{ - if(!message) - return; - assert(message->base.descriptor == &pb__close_response__descriptor); - protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); -} -static const ProtobufCFieldDescriptor pb__pub_msg__field_descriptors[7] = -{ - { - "clientID", - 1, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__PubMsg, clientid), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "guid", - 2, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__PubMsg, guid), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "subject", - 3, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__PubMsg, subject), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "reply", - 4, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__PubMsg, reply), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "data", - 5, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_BYTES, - 0, /* quantifier_offset */ - offsetof(Pb__PubMsg, data), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "connID", - 6, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_BYTES, - 0, /* quantifier_offset */ - offsetof(Pb__PubMsg, connid), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "sha256", - 10, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_BYTES, - 0, /* quantifier_offset */ - offsetof(Pb__PubMsg, sha256), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, -}; -static const unsigned pb__pub_msg__field_indices_by_name[] = { - 0, /* field[0] = clientID */ - 5, /* field[5] = connID */ - 4, /* field[4] = data */ - 1, /* field[1] = guid */ - 3, /* field[3] = reply */ - 6, /* field[6] = sha256 */ - 2, /* field[2] = subject */ -}; -static const ProtobufCIntRange pb__pub_msg__number_ranges[2 + 1] = -{ - { 1, 0 }, - { 10, 6 }, - { 0, 7 } -}; -const ProtobufCMessageDescriptor pb__pub_msg__descriptor = -{ - PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, - "pb.PubMsg", - "PubMsg", - "Pb__PubMsg", - "pb", - sizeof(Pb__PubMsg), - 7, - pb__pub_msg__field_descriptors, - pb__pub_msg__field_indices_by_name, - 2, pb__pub_msg__number_ranges, - (ProtobufCMessageInit) pb__pub_msg__init, - NULL,NULL,NULL /* reserved[123] */ -}; -static const ProtobufCFieldDescriptor pb__pub_ack__field_descriptors[2] = -{ - { - "guid", - 1, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__PubAck, guid), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "error", - 2, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__PubAck, error), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, -}; -static const unsigned pb__pub_ack__field_indices_by_name[] = { - 1, /* field[1] = error */ - 0, /* field[0] = guid */ -}; -static const ProtobufCIntRange pb__pub_ack__number_ranges[1 + 1] = -{ - { 1, 0 }, - { 0, 2 } -}; -const ProtobufCMessageDescriptor pb__pub_ack__descriptor = -{ - PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, - "pb.PubAck", - "PubAck", - "Pb__PubAck", - "pb", - sizeof(Pb__PubAck), - 2, - pb__pub_ack__field_descriptors, - pb__pub_ack__field_indices_by_name, - 1, pb__pub_ack__number_ranges, - (ProtobufCMessageInit) pb__pub_ack__init, - NULL,NULL,NULL /* reserved[123] */ -}; -static const ProtobufCFieldDescriptor pb__msg_proto__field_descriptors[7] = -{ - { - "sequence", - 1, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_UINT64, - 0, /* quantifier_offset */ - offsetof(Pb__MsgProto, sequence), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "subject", - 2, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__MsgProto, subject), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "reply", - 3, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__MsgProto, reply), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "data", - 4, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_BYTES, - 0, /* quantifier_offset */ - offsetof(Pb__MsgProto, data), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "timestamp", - 5, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_INT64, - 0, /* quantifier_offset */ - offsetof(Pb__MsgProto, timestamp), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "redelivered", - 6, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_BOOL, - 0, /* quantifier_offset */ - offsetof(Pb__MsgProto, redelivered), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "CRC32", - 10, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_UINT32, - 0, /* quantifier_offset */ - offsetof(Pb__MsgProto, crc32), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, -}; -static const unsigned pb__msg_proto__field_indices_by_name[] = { - 6, /* field[6] = CRC32 */ - 3, /* field[3] = data */ - 5, /* field[5] = redelivered */ - 2, /* field[2] = reply */ - 0, /* field[0] = sequence */ - 1, /* field[1] = subject */ - 4, /* field[4] = timestamp */ -}; -static const ProtobufCIntRange pb__msg_proto__number_ranges[2 + 1] = -{ - { 1, 0 }, - { 10, 6 }, - { 0, 7 } -}; -const ProtobufCMessageDescriptor pb__msg_proto__descriptor = -{ - PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, - "pb.MsgProto", - "MsgProto", - "Pb__MsgProto", - "pb", - sizeof(Pb__MsgProto), - 7, - pb__msg_proto__field_descriptors, - pb__msg_proto__field_indices_by_name, - 2, pb__msg_proto__number_ranges, - (ProtobufCMessageInit) pb__msg_proto__init, - NULL,NULL,NULL /* reserved[123] */ -}; -static const ProtobufCFieldDescriptor pb__ack__field_descriptors[2] = -{ - { - "subject", - 1, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__Ack, subject), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "sequence", - 2, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_UINT64, - 0, /* quantifier_offset */ - offsetof(Pb__Ack, sequence), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, -}; -static const unsigned pb__ack__field_indices_by_name[] = { - 1, /* field[1] = sequence */ - 0, /* field[0] = subject */ -}; -static const ProtobufCIntRange pb__ack__number_ranges[1 + 1] = -{ - { 1, 0 }, - { 0, 2 } -}; -const ProtobufCMessageDescriptor pb__ack__descriptor = -{ - PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, - "pb.Ack", - "Ack", - "Pb__Ack", - "pb", - sizeof(Pb__Ack), - 2, - pb__ack__field_descriptors, - pb__ack__field_indices_by_name, - 1, pb__ack__number_ranges, - (ProtobufCMessageInit) pb__ack__init, - NULL,NULL,NULL /* reserved[123] */ -}; -static const ProtobufCFieldDescriptor pb__connect_request__field_descriptors[6] = -{ - { - "clientID", - 1, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectRequest, clientid), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "heartbeatInbox", - 2, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectRequest, heartbeatinbox), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "protocol", - 3, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_INT32, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectRequest, protocol), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "connID", - 4, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_BYTES, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectRequest, connid), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "pingInterval", - 5, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_INT32, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectRequest, pinginterval), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "pingMaxOut", - 6, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_INT32, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectRequest, pingmaxout), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, -}; -static const unsigned pb__connect_request__field_indices_by_name[] = { - 0, /* field[0] = clientID */ - 3, /* field[3] = connID */ - 1, /* field[1] = heartbeatInbox */ - 4, /* field[4] = pingInterval */ - 5, /* field[5] = pingMaxOut */ - 2, /* field[2] = protocol */ -}; -static const ProtobufCIntRange pb__connect_request__number_ranges[1 + 1] = -{ - { 1, 0 }, - { 0, 6 } -}; -const ProtobufCMessageDescriptor pb__connect_request__descriptor = -{ - PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, - "pb.ConnectRequest", - "ConnectRequest", - "Pb__ConnectRequest", - "pb", - sizeof(Pb__ConnectRequest), - 6, - pb__connect_request__field_descriptors, - pb__connect_request__field_indices_by_name, - 1, pb__connect_request__number_ranges, - (ProtobufCMessageInit) pb__connect_request__init, - NULL,NULL,NULL /* reserved[123] */ -}; -static const ProtobufCFieldDescriptor pb__connect_response__field_descriptors[11] = -{ - { - "pubPrefix", - 1, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectResponse, pubprefix), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "subRequests", - 2, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectResponse, subrequests), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "unsubRequests", - 3, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectResponse, unsubrequests), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "closeRequests", - 4, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectResponse, closerequests), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "error", - 5, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectResponse, error), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "subCloseRequests", - 6, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectResponse, subcloserequests), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "pingRequests", - 7, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectResponse, pingrequests), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "pingInterval", - 8, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_INT32, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectResponse, pinginterval), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "pingMaxOut", - 9, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_INT32, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectResponse, pingmaxout), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "protocol", - 10, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_INT32, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectResponse, protocol), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "publicKey", - 100, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__ConnectResponse, publickey), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, -}; -static const unsigned pb__connect_response__field_indices_by_name[] = { - 3, /* field[3] = closeRequests */ - 4, /* field[4] = error */ - 7, /* field[7] = pingInterval */ - 8, /* field[8] = pingMaxOut */ - 6, /* field[6] = pingRequests */ - 9, /* field[9] = protocol */ - 0, /* field[0] = pubPrefix */ - 10, /* field[10] = publicKey */ - 5, /* field[5] = subCloseRequests */ - 1, /* field[1] = subRequests */ - 2, /* field[2] = unsubRequests */ -}; -static const ProtobufCIntRange pb__connect_response__number_ranges[2 + 1] = -{ - { 1, 0 }, - { 100, 10 }, - { 0, 11 } -}; -const ProtobufCMessageDescriptor pb__connect_response__descriptor = -{ - PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, - "pb.ConnectResponse", - "ConnectResponse", - "Pb__ConnectResponse", - "pb", - sizeof(Pb__ConnectResponse), - 11, - pb__connect_response__field_descriptors, - pb__connect_response__field_indices_by_name, - 2, pb__connect_response__number_ranges, - (ProtobufCMessageInit) pb__connect_response__init, - NULL,NULL,NULL /* reserved[123] */ -}; -static const ProtobufCFieldDescriptor pb__ping__field_descriptors[1] = -{ - { - "connID", - 1, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_BYTES, - 0, /* quantifier_offset */ - offsetof(Pb__Ping, connid), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, -}; -static const unsigned pb__ping__field_indices_by_name[] = { - 0, /* field[0] = connID */ -}; -static const ProtobufCIntRange pb__ping__number_ranges[1 + 1] = -{ - { 1, 0 }, - { 0, 1 } -}; -const ProtobufCMessageDescriptor pb__ping__descriptor = -{ - PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, - "pb.Ping", - "Ping", - "Pb__Ping", - "pb", - sizeof(Pb__Ping), - 1, - pb__ping__field_descriptors, - pb__ping__field_indices_by_name, - 1, pb__ping__number_ranges, - (ProtobufCMessageInit) pb__ping__init, - NULL,NULL,NULL /* reserved[123] */ -}; -static const ProtobufCFieldDescriptor pb__ping_response__field_descriptors[1] = -{ - { - "error", - 1, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__PingResponse, error), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, -}; -static const unsigned pb__ping_response__field_indices_by_name[] = { - 0, /* field[0] = error */ -}; -static const ProtobufCIntRange pb__ping_response__number_ranges[1 + 1] = -{ - { 1, 0 }, - { 0, 1 } -}; -const ProtobufCMessageDescriptor pb__ping_response__descriptor = -{ - PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, - "pb.PingResponse", - "PingResponse", - "Pb__PingResponse", - "pb", - sizeof(Pb__PingResponse), - 1, - pb__ping_response__field_descriptors, - pb__ping_response__field_indices_by_name, - 1, pb__ping_response__number_ranges, - (ProtobufCMessageInit) pb__ping_response__init, - NULL,NULL,NULL /* reserved[123] */ -}; -static const ProtobufCFieldDescriptor pb__subscription_request__field_descriptors[10] = -{ - { - "clientID", - 1, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__SubscriptionRequest, clientid), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "subject", - 2, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__SubscriptionRequest, subject), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "qGroup", - 3, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__SubscriptionRequest, qgroup), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "inbox", - 4, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__SubscriptionRequest, inbox), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "maxInFlight", - 5, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_INT32, - 0, /* quantifier_offset */ - offsetof(Pb__SubscriptionRequest, maxinflight), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "ackWaitInSecs", - 6, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_INT32, - 0, /* quantifier_offset */ - offsetof(Pb__SubscriptionRequest, ackwaitinsecs), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "durableName", - 7, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__SubscriptionRequest, durablename), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "startPosition", - 10, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_ENUM, - 0, /* quantifier_offset */ - offsetof(Pb__SubscriptionRequest, startposition), - &pb__start_position__descriptor, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "startSequence", - 11, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_UINT64, - 0, /* quantifier_offset */ - offsetof(Pb__SubscriptionRequest, startsequence), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "startTimeDelta", - 12, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_INT64, - 0, /* quantifier_offset */ - offsetof(Pb__SubscriptionRequest, starttimedelta), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, -}; -static const unsigned pb__subscription_request__field_indices_by_name[] = { - 5, /* field[5] = ackWaitInSecs */ - 0, /* field[0] = clientID */ - 6, /* field[6] = durableName */ - 3, /* field[3] = inbox */ - 4, /* field[4] = maxInFlight */ - 2, /* field[2] = qGroup */ - 7, /* field[7] = startPosition */ - 8, /* field[8] = startSequence */ - 9, /* field[9] = startTimeDelta */ - 1, /* field[1] = subject */ -}; -static const ProtobufCIntRange pb__subscription_request__number_ranges[2 + 1] = -{ - { 1, 0 }, - { 10, 7 }, - { 0, 10 } -}; -const ProtobufCMessageDescriptor pb__subscription_request__descriptor = -{ - PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, - "pb.SubscriptionRequest", - "SubscriptionRequest", - "Pb__SubscriptionRequest", - "pb", - sizeof(Pb__SubscriptionRequest), - 10, - pb__subscription_request__field_descriptors, - pb__subscription_request__field_indices_by_name, - 2, pb__subscription_request__number_ranges, - (ProtobufCMessageInit) pb__subscription_request__init, - NULL,NULL,NULL /* reserved[123] */ -}; -static const ProtobufCFieldDescriptor pb__subscription_response__field_descriptors[2] = -{ - { - "ackInbox", - 2, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__SubscriptionResponse, ackinbox), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "error", - 3, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__SubscriptionResponse, error), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, -}; -static const unsigned pb__subscription_response__field_indices_by_name[] = { - 0, /* field[0] = ackInbox */ - 1, /* field[1] = error */ -}; -static const ProtobufCIntRange pb__subscription_response__number_ranges[1 + 1] = -{ - { 2, 0 }, - { 0, 2 } -}; -const ProtobufCMessageDescriptor pb__subscription_response__descriptor = -{ - PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, - "pb.SubscriptionResponse", - "SubscriptionResponse", - "Pb__SubscriptionResponse", - "pb", - sizeof(Pb__SubscriptionResponse), - 2, - pb__subscription_response__field_descriptors, - pb__subscription_response__field_indices_by_name, - 1, pb__subscription_response__number_ranges, - (ProtobufCMessageInit) pb__subscription_response__init, - NULL,NULL,NULL /* reserved[123] */ -}; -static const ProtobufCFieldDescriptor pb__unsubscribe_request__field_descriptors[4] = -{ - { - "clientID", - 1, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__UnsubscribeRequest, clientid), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "subject", - 2, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__UnsubscribeRequest, subject), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "inbox", - 3, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__UnsubscribeRequest, inbox), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, - { - "durableName", - 4, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__UnsubscribeRequest, durablename), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, -}; -static const unsigned pb__unsubscribe_request__field_indices_by_name[] = { - 0, /* field[0] = clientID */ - 3, /* field[3] = durableName */ - 2, /* field[2] = inbox */ - 1, /* field[1] = subject */ -}; -static const ProtobufCIntRange pb__unsubscribe_request__number_ranges[1 + 1] = -{ - { 1, 0 }, - { 0, 4 } -}; -const ProtobufCMessageDescriptor pb__unsubscribe_request__descriptor = -{ - PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, - "pb.UnsubscribeRequest", - "UnsubscribeRequest", - "Pb__UnsubscribeRequest", - "pb", - sizeof(Pb__UnsubscribeRequest), - 4, - pb__unsubscribe_request__field_descriptors, - pb__unsubscribe_request__field_indices_by_name, - 1, pb__unsubscribe_request__number_ranges, - (ProtobufCMessageInit) pb__unsubscribe_request__init, - NULL,NULL,NULL /* reserved[123] */ -}; -static const ProtobufCFieldDescriptor pb__close_request__field_descriptors[1] = -{ - { - "clientID", - 1, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__CloseRequest, clientid), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, -}; -static const unsigned pb__close_request__field_indices_by_name[] = { - 0, /* field[0] = clientID */ -}; -static const ProtobufCIntRange pb__close_request__number_ranges[1 + 1] = -{ - { 1, 0 }, - { 0, 1 } -}; -const ProtobufCMessageDescriptor pb__close_request__descriptor = -{ - PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, - "pb.CloseRequest", - "CloseRequest", - "Pb__CloseRequest", - "pb", - sizeof(Pb__CloseRequest), - 1, - pb__close_request__field_descriptors, - pb__close_request__field_indices_by_name, - 1, pb__close_request__number_ranges, - (ProtobufCMessageInit) pb__close_request__init, - NULL,NULL,NULL /* reserved[123] */ -}; -static const ProtobufCFieldDescriptor pb__close_response__field_descriptors[1] = -{ - { - "error", - 1, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_STRING, - 0, /* quantifier_offset */ - offsetof(Pb__CloseResponse, error), - NULL, - &protobuf_c_empty_string, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, -}; -static const unsigned pb__close_response__field_indices_by_name[] = { - 0, /* field[0] = error */ -}; -static const ProtobufCIntRange pb__close_response__number_ranges[1 + 1] = -{ - { 1, 0 }, - { 0, 1 } -}; -const ProtobufCMessageDescriptor pb__close_response__descriptor = -{ - PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, - "pb.CloseResponse", - "CloseResponse", - "Pb__CloseResponse", - "pb", - sizeof(Pb__CloseResponse), - 1, - pb__close_response__field_descriptors, - pb__close_response__field_indices_by_name, - 1, pb__close_response__number_ranges, - (ProtobufCMessageInit) pb__close_response__init, - NULL,NULL,NULL /* reserved[123] */ -}; -static const ProtobufCEnumValue pb__start_position__enum_values_by_number[5] = -{ - { "NewOnly", "PB__START_POSITION__NewOnly", 0 }, - { "LastReceived", "PB__START_POSITION__LastReceived", 1 }, - { "TimeDeltaStart", "PB__START_POSITION__TimeDeltaStart", 2 }, - { "SequenceStart", "PB__START_POSITION__SequenceStart", 3 }, - { "First", "PB__START_POSITION__First", 4 }, -}; -static const ProtobufCIntRange pb__start_position__value_ranges[] = { -{0, 0},{0, 5} -}; -static const ProtobufCEnumValueIndex pb__start_position__enum_values_by_name[5] = -{ - { "First", 4 }, - { "LastReceived", 1 }, - { "NewOnly", 0 }, - { "SequenceStart", 3 }, - { "TimeDeltaStart", 2 }, -}; -const ProtobufCEnumDescriptor pb__start_position__descriptor = -{ - PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, - "pb.StartPosition", - "StartPosition", - "Pb__StartPosition", - "pb", - 5, - pb__start_position__enum_values_by_number, - 5, - pb__start_position__enum_values_by_name, - 1, - pb__start_position__value_ranges, - NULL,NULL,NULL,NULL /* reserved[1234] */ -}; diff --git a/src/stan/protocol.pb-c.h b/src/stan/protocol.pb-c.h deleted file mode 100644 index 5df724e4c..000000000 --- a/src/stan/protocol.pb-c.h +++ /dev/null @@ -1,739 +0,0 @@ -/* Generated by the protocol buffer compiler. DO NOT EDIT! */ -/* Generated from: protocol.proto */ - -#ifndef PROTOBUF_C_protocol_2eproto__INCLUDED -#define PROTOBUF_C_protocol_2eproto__INCLUDED - -#include - -PROTOBUF_C__BEGIN_DECLS - -#if PROTOBUF_C_VERSION_NUMBER < 1003000 -# error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers. -#elif 1003000 < PROTOBUF_C_MIN_COMPILER_VERSION -# error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c. -#endif - -//#include "github.com/gogo/protobuf/gogoproto/gogo.pb-c.h" - -typedef struct _Pb__PubMsg Pb__PubMsg; -typedef struct _Pb__PubAck Pb__PubAck; -typedef struct _Pb__MsgProto Pb__MsgProto; -typedef struct _Pb__Ack Pb__Ack; -typedef struct _Pb__ConnectRequest Pb__ConnectRequest; -typedef struct _Pb__ConnectResponse Pb__ConnectResponse; -typedef struct _Pb__Ping Pb__Ping; -typedef struct _Pb__PingResponse Pb__PingResponse; -typedef struct _Pb__SubscriptionRequest Pb__SubscriptionRequest; -typedef struct _Pb__SubscriptionResponse Pb__SubscriptionResponse; -typedef struct _Pb__UnsubscribeRequest Pb__UnsubscribeRequest; -typedef struct _Pb__CloseRequest Pb__CloseRequest; -typedef struct _Pb__CloseResponse Pb__CloseResponse; - - -/* --- enums --- */ - -/* - * Enum for start position type. - */ -typedef enum _Pb__StartPosition { - PB__START_POSITION__NewOnly = 0, - PB__START_POSITION__LastReceived = 1, - PB__START_POSITION__TimeDeltaStart = 2, - PB__START_POSITION__SequenceStart = 3, - PB__START_POSITION__First = 4 - PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(PB__START_POSITION) -} Pb__StartPosition; - -/* --- messages --- */ - -/* - * How messages are delivered to the STAN cluster - */ -struct _Pb__PubMsg -{ - ProtobufCMessage base; - /* - * ClientID - */ - char *clientid; - /* - * guid - */ - char *guid; - /* - * subject - */ - char *subject; - /* - * optional reply - */ - char *reply; - /* - * payload - */ - ProtobufCBinaryData data; - /* - * Connection ID. For servers that know about this field, clientID can be omitted - */ - ProtobufCBinaryData connid; - /* - * optional sha256 of data - */ - ProtobufCBinaryData sha256; -}; -#define PB__PUB_MSG__INIT \ - { PROTOBUF_C_MESSAGE_INIT (&pb__pub_msg__descriptor) \ - , (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, {0,NULL}, {0,NULL}, {0,NULL} } - - -/* - * Used to ACK to publishers - */ -struct _Pb__PubAck -{ - ProtobufCMessage base; - /* - * guid - */ - char *guid; - /* - * err string, empty/omitted if no error - */ - char *error; -}; -#define PB__PUB_ACK__INIT \ - { PROTOBUF_C_MESSAGE_INIT (&pb__pub_ack__descriptor) \ - , (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string } - - -/* - * Msg struct. Sequence is assigned for global ordering by - * the cluster after the publisher has been acknowledged. - */ -struct _Pb__MsgProto -{ - ProtobufCMessage base; - /* - * globally ordered sequence number for the subject's channel - */ - uint64_t sequence; - /* - * subject - */ - char *subject; - /* - * optional reply - */ - char *reply; - /* - * payload - */ - ProtobufCBinaryData data; - /* - * received timestamp - */ - int64_t timestamp; - /* - * Flag specifying if the message is being redelivered - */ - protobuf_c_boolean redelivered; - /* - * optional IEEE CRC32 - */ - uint32_t crc32; -}; -#define PB__MSG_PROTO__INIT \ - { PROTOBUF_C_MESSAGE_INIT (&pb__msg_proto__descriptor) \ - , 0, (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, {0,NULL}, 0, 0, 0 } - - -/* - * Ack will deliver an ack for a delivered msg. - */ -struct _Pb__Ack -{ - ProtobufCMessage base; - /* - * Subject - */ - char *subject; - /* - * Sequence to acknowledge - */ - uint64_t sequence; -}; -#define PB__ACK__INIT \ - { PROTOBUF_C_MESSAGE_INIT (&pb__ack__descriptor) \ - , (char *)protobuf_c_empty_string, 0 } - - -/* - * Connection Request - */ -struct _Pb__ConnectRequest -{ - ProtobufCMessage base; - /* - * Client name/identifier. - */ - char *clientid; - /* - * Inbox for server initiated heartbeats. - */ - char *heartbeatinbox; - /* - * Protocol the client is at. - */ - int32_t protocol; - /* - * Connection ID, a way to uniquely identify a connection (no connection should ever have the same) - */ - ProtobufCBinaryData connid; - /* - * Interval at which client wishes to send PINGs (expressed in seconds). - */ - int32_t pinginterval; - /* - * Maximum number of PINGs without a response after which the connection can be considered lost. - */ - int32_t pingmaxout; -}; -#define PB__CONNECT_REQUEST__INIT \ - { PROTOBUF_C_MESSAGE_INIT (&pb__connect_request__descriptor) \ - , (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, 0, {0,NULL}, 0, 0 } - - -/* - * Response to a client connect - */ -struct _Pb__ConnectResponse -{ - ProtobufCMessage base; - /* - * Prefix to use when publishing to this STAN cluster - */ - char *pubprefix; - /* - * Subject to use for subscription requests - */ - char *subrequests; - /* - * Subject to use for unsubscribe requests - */ - char *unsubrequests; - /* - * Subject for closing the stan connection - */ - char *closerequests; - /* - * err string, empty/omitted if no error - */ - char *error; - /* - * Subject to use for subscription close requests - */ - char *subcloserequests; - /* - * Subject to use for PING requests - */ - char *pingrequests; - /* - * Interval at which client should send PINGs (expressed in seconds). - */ - int32_t pinginterval; - /* - * Maximum number of PINGs without a response after which the connection can be considered lost - */ - int32_t pingmaxout; - /* - * Protocol version the server is at - */ - int32_t protocol; - /* - * Possibly used to sign acks, etc. - */ - char *publickey; -}; -#define PB__CONNECT_RESPONSE__INIT \ - { PROTOBUF_C_MESSAGE_INIT (&pb__connect_response__descriptor) \ - , (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, 0, 0, 0, (char *)protobuf_c_empty_string } - - -/* - * PING from client to server - */ -struct _Pb__Ping -{ - ProtobufCMessage base; - /* - * Connection ID - */ - ProtobufCBinaryData connid; -}; -#define PB__PING__INIT \ - { PROTOBUF_C_MESSAGE_INIT (&pb__ping__descriptor) \ - , {0,NULL} } - - -/* - * PING response from the server - */ -struct _Pb__PingResponse -{ - ProtobufCMessage base; - /* - * Error string, empty/omitted if no error - */ - char *error; -}; -#define PB__PING_RESPONSE__INIT \ - { PROTOBUF_C_MESSAGE_INIT (&pb__ping_response__descriptor) \ - , (char *)protobuf_c_empty_string } - - -/* - * Protocol for a client to subscribe - */ -struct _Pb__SubscriptionRequest -{ - ProtobufCMessage base; - /* - * ClientID - */ - char *clientid; - /* - * Formal subject to subscribe to, e.g. foo.bar - */ - char *subject; - /* - * Optional queue group - */ - char *qgroup; - /* - * Inbox subject to deliver messages on - */ - char *inbox; - /* - * Maximum inflight messages without an ack allowed - */ - int32_t maxinflight; - /* - * Timeout for receiving an ack from the client - */ - int32_t ackwaitinsecs; - /* - * Optional durable name which survives client restarts - */ - char *durablename; - /* - * Start position - */ - Pb__StartPosition startposition; - /* - * Optional start sequence number - */ - uint64_t startsequence; - /* - * Optional start time - */ - int64_t starttimedelta; -}; -#define PB__SUBSCRIPTION_REQUEST__INIT \ - { PROTOBUF_C_MESSAGE_INIT (&pb__subscription_request__descriptor) \ - , (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, 0, 0, (char *)protobuf_c_empty_string, PB__START_POSITION__NewOnly, 0, 0 } - - -/* - * Response for SubscriptionRequest and UnsubscribeRequests - */ -struct _Pb__SubscriptionResponse -{ - ProtobufCMessage base; - /* - * ackInbox for sending acks - */ - char *ackinbox; - /* - * err string, empty/omitted if no error - */ - char *error; -}; -#define PB__SUBSCRIPTION_RESPONSE__INIT \ - { PROTOBUF_C_MESSAGE_INIT (&pb__subscription_response__descriptor) \ - , (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string } - - -/* - * Protocol for a clients to unsubscribe. Will return a SubscriptionResponse - */ -struct _Pb__UnsubscribeRequest -{ - ProtobufCMessage base; - /* - * ClientID - */ - char *clientid; - /* - * subject for the subscription - */ - char *subject; - /* - * Inbox subject to identify subscription - */ - char *inbox; - /* - * Optional durable name which survives client restarts - */ - char *durablename; -}; -#define PB__UNSUBSCRIBE_REQUEST__INIT \ - { PROTOBUF_C_MESSAGE_INIT (&pb__unsubscribe_request__descriptor) \ - , (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string } - - -/* - * Protocol for a client to close a connection - */ -struct _Pb__CloseRequest -{ - ProtobufCMessage base; - /* - * Client name provided to Connect() requests - */ - char *clientid; -}; -#define PB__CLOSE_REQUEST__INIT \ - { PROTOBUF_C_MESSAGE_INIT (&pb__close_request__descriptor) \ - , (char *)protobuf_c_empty_string } - - -/* - * Response for CloseRequest - */ -struct _Pb__CloseResponse -{ - ProtobufCMessage base; - /* - * err string, empty/omitted if no error - */ - char *error; -}; -#define PB__CLOSE_RESPONSE__INIT \ - { PROTOBUF_C_MESSAGE_INIT (&pb__close_response__descriptor) \ - , (char *)protobuf_c_empty_string } - - -/* Pb__PubMsg methods */ -void pb__pub_msg__init - (Pb__PubMsg *message); -size_t pb__pub_msg__get_packed_size - (const Pb__PubMsg *message); -size_t pb__pub_msg__pack - (const Pb__PubMsg *message, - uint8_t *out); -size_t pb__pub_msg__pack_to_buffer - (const Pb__PubMsg *message, - ProtobufCBuffer *buffer); -Pb__PubMsg * - pb__pub_msg__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data); -void pb__pub_msg__free_unpacked - (Pb__PubMsg *message, - ProtobufCAllocator *allocator); -/* Pb__PubAck methods */ -void pb__pub_ack__init - (Pb__PubAck *message); -size_t pb__pub_ack__get_packed_size - (const Pb__PubAck *message); -size_t pb__pub_ack__pack - (const Pb__PubAck *message, - uint8_t *out); -size_t pb__pub_ack__pack_to_buffer - (const Pb__PubAck *message, - ProtobufCBuffer *buffer); -Pb__PubAck * - pb__pub_ack__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data); -void pb__pub_ack__free_unpacked - (Pb__PubAck *message, - ProtobufCAllocator *allocator); -/* Pb__MsgProto methods */ -void pb__msg_proto__init - (Pb__MsgProto *message); -size_t pb__msg_proto__get_packed_size - (const Pb__MsgProto *message); -size_t pb__msg_proto__pack - (const Pb__MsgProto *message, - uint8_t *out); -size_t pb__msg_proto__pack_to_buffer - (const Pb__MsgProto *message, - ProtobufCBuffer *buffer); -Pb__MsgProto * - pb__msg_proto__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data); -void pb__msg_proto__free_unpacked - (Pb__MsgProto *message, - ProtobufCAllocator *allocator); -/* Pb__Ack methods */ -void pb__ack__init - (Pb__Ack *message); -size_t pb__ack__get_packed_size - (const Pb__Ack *message); -size_t pb__ack__pack - (const Pb__Ack *message, - uint8_t *out); -size_t pb__ack__pack_to_buffer - (const Pb__Ack *message, - ProtobufCBuffer *buffer); -Pb__Ack * - pb__ack__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data); -void pb__ack__free_unpacked - (Pb__Ack *message, - ProtobufCAllocator *allocator); -/* Pb__ConnectRequest methods */ -void pb__connect_request__init - (Pb__ConnectRequest *message); -size_t pb__connect_request__get_packed_size - (const Pb__ConnectRequest *message); -size_t pb__connect_request__pack - (const Pb__ConnectRequest *message, - uint8_t *out); -size_t pb__connect_request__pack_to_buffer - (const Pb__ConnectRequest *message, - ProtobufCBuffer *buffer); -Pb__ConnectRequest * - pb__connect_request__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data); -void pb__connect_request__free_unpacked - (Pb__ConnectRequest *message, - ProtobufCAllocator *allocator); -/* Pb__ConnectResponse methods */ -void pb__connect_response__init - (Pb__ConnectResponse *message); -size_t pb__connect_response__get_packed_size - (const Pb__ConnectResponse *message); -size_t pb__connect_response__pack - (const Pb__ConnectResponse *message, - uint8_t *out); -size_t pb__connect_response__pack_to_buffer - (const Pb__ConnectResponse *message, - ProtobufCBuffer *buffer); -Pb__ConnectResponse * - pb__connect_response__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data); -void pb__connect_response__free_unpacked - (Pb__ConnectResponse *message, - ProtobufCAllocator *allocator); -/* Pb__Ping methods */ -void pb__ping__init - (Pb__Ping *message); -size_t pb__ping__get_packed_size - (const Pb__Ping *message); -size_t pb__ping__pack - (const Pb__Ping *message, - uint8_t *out); -size_t pb__ping__pack_to_buffer - (const Pb__Ping *message, - ProtobufCBuffer *buffer); -Pb__Ping * - pb__ping__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data); -void pb__ping__free_unpacked - (Pb__Ping *message, - ProtobufCAllocator *allocator); -/* Pb__PingResponse methods */ -void pb__ping_response__init - (Pb__PingResponse *message); -size_t pb__ping_response__get_packed_size - (const Pb__PingResponse *message); -size_t pb__ping_response__pack - (const Pb__PingResponse *message, - uint8_t *out); -size_t pb__ping_response__pack_to_buffer - (const Pb__PingResponse *message, - ProtobufCBuffer *buffer); -Pb__PingResponse * - pb__ping_response__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data); -void pb__ping_response__free_unpacked - (Pb__PingResponse *message, - ProtobufCAllocator *allocator); -/* Pb__SubscriptionRequest methods */ -void pb__subscription_request__init - (Pb__SubscriptionRequest *message); -size_t pb__subscription_request__get_packed_size - (const Pb__SubscriptionRequest *message); -size_t pb__subscription_request__pack - (const Pb__SubscriptionRequest *message, - uint8_t *out); -size_t pb__subscription_request__pack_to_buffer - (const Pb__SubscriptionRequest *message, - ProtobufCBuffer *buffer); -Pb__SubscriptionRequest * - pb__subscription_request__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data); -void pb__subscription_request__free_unpacked - (Pb__SubscriptionRequest *message, - ProtobufCAllocator *allocator); -/* Pb__SubscriptionResponse methods */ -void pb__subscription_response__init - (Pb__SubscriptionResponse *message); -size_t pb__subscription_response__get_packed_size - (const Pb__SubscriptionResponse *message); -size_t pb__subscription_response__pack - (const Pb__SubscriptionResponse *message, - uint8_t *out); -size_t pb__subscription_response__pack_to_buffer - (const Pb__SubscriptionResponse *message, - ProtobufCBuffer *buffer); -Pb__SubscriptionResponse * - pb__subscription_response__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data); -void pb__subscription_response__free_unpacked - (Pb__SubscriptionResponse *message, - ProtobufCAllocator *allocator); -/* Pb__UnsubscribeRequest methods */ -void pb__unsubscribe_request__init - (Pb__UnsubscribeRequest *message); -size_t pb__unsubscribe_request__get_packed_size - (const Pb__UnsubscribeRequest *message); -size_t pb__unsubscribe_request__pack - (const Pb__UnsubscribeRequest *message, - uint8_t *out); -size_t pb__unsubscribe_request__pack_to_buffer - (const Pb__UnsubscribeRequest *message, - ProtobufCBuffer *buffer); -Pb__UnsubscribeRequest * - pb__unsubscribe_request__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data); -void pb__unsubscribe_request__free_unpacked - (Pb__UnsubscribeRequest *message, - ProtobufCAllocator *allocator); -/* Pb__CloseRequest methods */ -void pb__close_request__init - (Pb__CloseRequest *message); -size_t pb__close_request__get_packed_size - (const Pb__CloseRequest *message); -size_t pb__close_request__pack - (const Pb__CloseRequest *message, - uint8_t *out); -size_t pb__close_request__pack_to_buffer - (const Pb__CloseRequest *message, - ProtobufCBuffer *buffer); -Pb__CloseRequest * - pb__close_request__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data); -void pb__close_request__free_unpacked - (Pb__CloseRequest *message, - ProtobufCAllocator *allocator); -/* Pb__CloseResponse methods */ -void pb__close_response__init - (Pb__CloseResponse *message); -size_t pb__close_response__get_packed_size - (const Pb__CloseResponse *message); -size_t pb__close_response__pack - (const Pb__CloseResponse *message, - uint8_t *out); -size_t pb__close_response__pack_to_buffer - (const Pb__CloseResponse *message, - ProtobufCBuffer *buffer); -Pb__CloseResponse * - pb__close_response__unpack - (ProtobufCAllocator *allocator, - size_t len, - const uint8_t *data); -void pb__close_response__free_unpacked - (Pb__CloseResponse *message, - ProtobufCAllocator *allocator); -/* --- per-message closures --- */ - -typedef void (*Pb__PubMsg_Closure) - (const Pb__PubMsg *message, - void *closure_data); -typedef void (*Pb__PubAck_Closure) - (const Pb__PubAck *message, - void *closure_data); -typedef void (*Pb__MsgProto_Closure) - (const Pb__MsgProto *message, - void *closure_data); -typedef void (*Pb__Ack_Closure) - (const Pb__Ack *message, - void *closure_data); -typedef void (*Pb__ConnectRequest_Closure) - (const Pb__ConnectRequest *message, - void *closure_data); -typedef void (*Pb__ConnectResponse_Closure) - (const Pb__ConnectResponse *message, - void *closure_data); -typedef void (*Pb__Ping_Closure) - (const Pb__Ping *message, - void *closure_data); -typedef void (*Pb__PingResponse_Closure) - (const Pb__PingResponse *message, - void *closure_data); -typedef void (*Pb__SubscriptionRequest_Closure) - (const Pb__SubscriptionRequest *message, - void *closure_data); -typedef void (*Pb__SubscriptionResponse_Closure) - (const Pb__SubscriptionResponse *message, - void *closure_data); -typedef void (*Pb__UnsubscribeRequest_Closure) - (const Pb__UnsubscribeRequest *message, - void *closure_data); -typedef void (*Pb__CloseRequest_Closure) - (const Pb__CloseRequest *message, - void *closure_data); -typedef void (*Pb__CloseResponse_Closure) - (const Pb__CloseResponse *message, - void *closure_data); - -/* --- services --- */ - - -/* --- descriptors --- */ - -extern const ProtobufCEnumDescriptor pb__start_position__descriptor; -extern const ProtobufCMessageDescriptor pb__pub_msg__descriptor; -extern const ProtobufCMessageDescriptor pb__pub_ack__descriptor; -extern const ProtobufCMessageDescriptor pb__msg_proto__descriptor; -extern const ProtobufCMessageDescriptor pb__ack__descriptor; -extern const ProtobufCMessageDescriptor pb__connect_request__descriptor; -extern const ProtobufCMessageDescriptor pb__connect_response__descriptor; -extern const ProtobufCMessageDescriptor pb__ping__descriptor; -extern const ProtobufCMessageDescriptor pb__ping_response__descriptor; -extern const ProtobufCMessageDescriptor pb__subscription_request__descriptor; -extern const ProtobufCMessageDescriptor pb__subscription_response__descriptor; -extern const ProtobufCMessageDescriptor pb__unsubscribe_request__descriptor; -extern const ProtobufCMessageDescriptor pb__close_request__descriptor; -extern const ProtobufCMessageDescriptor pb__close_response__descriptor; - -PROTOBUF_C__END_DECLS - - -#endif /* PROTOBUF_C_protocol_2eproto__INCLUDED */ diff --git a/src/stan/pub.c b/src/stan/pub.c deleted file mode 100644 index 86f84bc92..000000000 --- a/src/stan/pub.c +++ /dev/null @@ -1,536 +0,0 @@ -// Copyright 2018-2020 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 "pub.h" -#include "conn.h" - -#include "../conn.h" - -static void -_pubAckFree(_pubAck *pa) -{ - if ((pa->error != NULL) && !pa->dontFreeError) - NATS_FREE(pa->error); - NATS_FREE(pa->guid); - NATS_FREE(pa); -} - -static void -_pubAckRemoveFromList(stanConnection *sc, _pubAck *pa) -{ - if ((pa->prev != NULL) || (pa->next != NULL)) - { - if (pa->prev != NULL) - pa->prev->next = pa->next; - if (pa->next != NULL) - pa->next->prev = pa->prev; - } - if (pa == sc->pubAckHead) - sc->pubAckHead = pa->next; - if (pa == sc->pubAckTail) - sc->pubAckTail = pa->prev; - - pa->prev = NULL; - pa->next = NULL; -} - -static void -_stanPossiblyReleasePublishCall(stanConnection *sc) -{ - if (natsStrHash_Count(sc->pubAckMap) < sc->pubAckMaxInflightThreshold) - natsCondition_Broadcast(sc->pubAckMaxInflightCond); -} - -void -stanProcessPubAck(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - stanConnection *sc = (stanConnection*) closure; - uint8_t *data = NULL; - size_t dataLen = 0; - - data = (uint8_t*) natsMsg_GetData(msg); - dataLen = (size_t) natsMsg_GetDataLength(msg); - - if (dataLen > 0) - { - ProtobufCAllocator *alloc = (ProtobufCAllocator*) sc->pubAckAllocator; - Pb__PubAck *pubAck; - - natsPBufAllocator_Prepare(sc->pubAckAllocator, (int) dataLen); - - pubAck = pb__pub_ack__unpack(alloc, dataLen, data); - if (pubAck != NULL) - { - _pubAck *pa; - char *error = NULL; - bool ic = false; - - if ((pubAck->error != NULL) && (pubAck->error[0] != '\0')) - error = pubAck->error; - - natsMutex_Lock(sc->pubAckMu); - pa = natsStrHash_Remove(sc->pubAckMap, pubAck->guid); - // It could have been removed by the publish calls. - if (pa != NULL) - { - // For sync publish, we need to update `pa` - // and wake up caller. - if (pa->isSync) - { - // Mark that the pub ack was received - pa->received = true; - // Get the error if any - if (error != NULL) - { - pa->error = NATS_STRDUP(error); - if (pa->error == NULL) - { - pa->error = (char*) "no memory copying error"; - pa->dontFreeError = true; - } - } - // Wake up caller if needed. - if (sc->pubAckInWait > 0) - natsCondition_Broadcast(sc->pubAckCond); - } - else - { - // Remove from list - _pubAckRemoveFromList(sc, pa); - - // Indicate that we need to invoke the callback - ic = true; - } - - // Check for possible blocked publish call and release if needed - if (sc->pubAckMaxInflightInWait) - _stanPossiblyReleasePublishCall(sc); - } - natsMutex_Unlock(sc->pubAckMu); - - // Asynchronous publish calls only.. - // We don't check for ((pa != NULL) && !pa->isSync)) here - // because for sync calls, `pa` may have been freed by - // now in stanConnection_Publish(). - if (ic) - { - // If handler specified, invoke here. - if (pa->ah != NULL) - (*pa->ah)(pubAck->guid, error, pa->ahClosure); - - // Done with `pa`. - _pubAckFree(pa); - } - - pb__pub_ack__free_unpacked(pubAck, alloc); - } - } - - natsMsg_Destroy(msg); -} - -static void -_pubAckTimerCB(natsTimer *timer, void* closure) -{ - stanConnection *sc = (stanConnection*) closure; - _pubAck *pa = NULL; - bool ic = false; - const char *err = NULL; - bool closed = false; - bool done = false; - - for (;!done;) - { - ic = false; - - natsMutex_Lock(sc->pubAckMu); - closed = sc->pubAckClosed; - if (sc->pubAckHead != NULL) - { - int64_t now = nats_Now(); - - pa = sc->pubAckHead; - - // Check that we are at or past the deadline - if (closed || (now >= pa->deadline)) - { - natsStrHash_Remove(sc->pubAckMap, pa->guid); - // This will update the head - _pubAckRemoveFromList(sc, pa); - // Check for possible blocked publish call and release if needed - if (!sc->pubAckClosed && sc->pubAckMaxInflightInWait) - _stanPossiblyReleasePublishCall(sc); - // We should invoke the callback - ic = true; - } - - if (!closed) - { - // Reset timer, either with current head but new timeout - // or to the new head's deadline. - if (sc->pubAckHead != NULL) - { - // If next deadline is really close, consider that - // it will expire in this iteration. Set its deadline - // to now and don't reset timer yet. - if (sc->pubAckHead->deadline-now <= 5) - { - sc->pubAckHead->deadline = now; - } - else - { - natsTimer_Reset(sc->pubAckTimer, sc->pubAckHead->deadline-now); - // Stop the 'for' loop - done = true; - } - } - else - { - // Set to an hour, but mark that this need a reset when - // a new async message is published. - natsTimer_Reset(sc->pubAckTimer, 60*60*1000); - sc->pubAckTimerNeedReset = true; - // Stop the 'for' loop - done = true; - } - } - } - else - { - done = true; - } - natsMutex_Unlock(sc->pubAckMu); - - if (ic && (pa != NULL)) - { - // Handler may not be set. - if (pa->ah != NULL) - { - if (closed) - err = natsStatus_GetText(NATS_CONNECTION_CLOSED); - else - err = STAN_ERR_PUB_ACK_TIMEOUT; - (*pa->ah)(pa->guid, (char*) err, pa->ahClosure); - } - // Done with `pa`. - _pubAckFree(pa); - } - } - - if (closed) - { - natsMutex_Lock(sc->mu); - natsTimer_Stop(sc->pubAckTimer); - natsMutex_Unlock(sc->mu); - } -} - -static void -_pubAckTimerStopCB(natsTimer *timer, void* closure) -{ - stanConnection *sc = (stanConnection*) closure; - stanConn_release(sc); -} - -#define GUID_LEN (23) - -static natsStatus -_stanPublish(stanConnection *sc, const char *channel, const void *data, int dataLen, - bool isSync, int64_t *deadline, _pubAck *pa) -{ - natsStatus s = NATS_OK; - int64_t ackTimeout = 0; - - if (sc == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - if (channel == NULL) - return nats_setDefaultError(NATS_INVALID_SUBJECT); - - stanConn_Lock(sc); - if (sc->closed) - { - stanConn_Unlock(sc); - return nats_setDefaultError(NATS_CONNECTION_CLOSED); - } - - pa->guid = NATS_MALLOC(GUID_LEN); - if (pa->guid == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - - if (s == NATS_OK) - s = natsNUID_Next(pa->guid, GUID_LEN); - if (s == NATS_OK) - { - Pb__PubMsg pubReq; - int pubSize = 0; - - pb__pub_msg__init(&pubReq); - pubReq.clientid = sc->clientID; - pubReq.connid.data = (uint8_t*) sc->connID; - pubReq.connid.len = sc->connIDLen; - pubReq.subject = (char*) channel; - pubReq.guid = pa->guid; - pubReq.data.data = (uint8_t*) data; - pubReq.data.len = dataLen; - - pubSize = (int) pb__pub_msg__get_packed_size(&pubReq); - if (pubSize == 0) - { - s = nats_setError(NATS_ERR, "%s", "publish message protocol packed size is 0"); - } - else - { - int chanLen = (int) strlen(channel); - int subjLen = sc->pubPrefixLen + 1 + chanLen + 1; - - if (subjLen > sc->pubSubjBufCap) - s = expandBuf(&sc->pubSubjBuf, &sc->pubSubjBufCap, subjLen); - - if (pubSize > sc->pubMsgBufCap) - s = expandBuf(&sc->pubMsgBuf, &sc->pubMsgBufCap, (int)((float)pubSize * 1.1)); - - if (s == NATS_OK) - { - char *ptr; - - ptr = sc->pubSubjBuf; - // We know the buffer is big enough, so copy directly - memcpy(ptr, sc->pubPrefix, sc->pubPrefixLen); - ptr += sc->pubPrefixLen; - ptr[0]='.'; - ptr++; - memcpy(ptr, channel, chanLen); - ptr += chanLen; - ptr[0]='\0'; - } - if (s == NATS_OK) - { - natsMutex_Lock(sc->pubAckMu); - - // If calling Close() while stuck in the condition wait below, this - // flag will be set to true (under pubAckMu) by Close() to kick us - // out and make sure we don't go back right at it. - - // Check if we should block due to maxInflight. Since we are under - // connection's lock, there can be at most one Publis[Async]() call - // blocked here (the other would be blocked at top of function trying - // to grab the connection's lock). - while (!sc->pubAckClosed && (natsStrHash_Count(sc->pubAckMap) == sc->opts->maxPubAcksInflight)) - { - sc->pubAckMaxInflightInWait = true; - natsCondition_Wait(sc->pubAckMaxInflightCond, sc->pubAckMu); - sc->pubAckMaxInflightInWait = false; - } - - // We could be closing, but stanConnection_Close() is waiting for - // sc->mu to be released. Still, we can fail this publish call. - if (sc->pubAckClosed) - s = nats_setDefaultError(NATS_CONNECTION_CLOSED); - - if (s == NATS_OK) - { - // Compute absolute time based on current time and the pub ack timeout. - ackTimeout = nats_setTargetTime(sc->opts->pubAckTimeout); - - // For Publish() calls, store in map, no need to copy keep since it is in pa. - if (isSync) - { - s = natsStrHash_Set(sc->pubAckMap, pa->guid, false, (void*) pa, NULL); - } - else - { - pa->deadline = ackTimeout; - - s = natsStrHash_Set(sc->pubAckMap, pa->guid, false, (void*) pa, NULL); - - // Add to list and create timer if needed - if (s == NATS_OK) - { - // If timer was never created, create now - if (sc->pubAckTimer == NULL) - { - s = natsTimer_Create(&sc->pubAckTimer, _pubAckTimerCB, _pubAckTimerStopCB, sc->opts->pubAckTimeout, (void*) sc); - if (s == NATS_OK) - sc->refs++; - } - else if (sc->pubAckTimerNeedReset) - { - natsTimer_Reset(sc->pubAckTimer, sc->opts->pubAckTimeout); - sc->pubAckTimerNeedReset = false; - } - if (s == NATS_OK) - { - // Add to list - if (sc->pubAckTail != NULL) - { - pa->prev = sc->pubAckTail; - sc->pubAckTail->next = pa; - } - else - { - sc->pubAckHead = pa; - } - sc->pubAckTail = pa; - } - else - { - natsStrHash_Remove(sc->pubAckMap, pa->guid); - } - } - } - } - natsMutex_Unlock(sc->pubAckMu); - } - if (s == NATS_OK) - { - int packedSize; - - packedSize = (int) pb__pub_msg__pack(&pubReq, (uint8_t*) sc->pubMsgBuf); - if (pubSize != packedSize) - { - s = nats_setError(NATS_ERR, "publish message protocol computed packed size is %d, got %d", - pubSize, packedSize); - } - else - { - natsMsg msg; - - natsMsg_init(&msg, (const char*) sc->pubSubjBuf, - (const char*) sc->pubMsgBuf, pubSize); - // Use private function to cause flush of buffer in place if sync call - s = natsConn_publish(sc->nc, &msg, (const char*) sc->ackSubject, isSync); - } - if ((s != NATS_OK) && (pa != NULL)) - { - // Since we may not have sent the message, remove the pa from map - natsMutex_Lock(sc->pubAckMu); - natsStrHash_Remove(sc->pubAckMap, pa->guid); - // Only PublishAsync() calls add `pa` to the list - if (!isSync) - _pubAckRemoveFromList(sc, pa); - natsMutex_Unlock(sc->pubAckMu); - } - } - } - } - // On success, retain for sync calls. - if ((s == NATS_OK) && isSync) - sc->refs++; - stanConn_Unlock(sc); - - if ((s == NATS_OK) && isSync) - *deadline = ackTimeout; - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -stanConnection_Publish(stanConnection *sc, const char *channel, const void *data, int dataLen) -{ - natsStatus s; - int64_t deadline = 0; - _pubAck *pa = NULL; - bool releaseConn = false; - - pa = NATS_CALLOC(1, sizeof(_pubAck)); - if (pa == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - pa->isSync = true; - - s = _stanPublish(sc, channel, data, dataLen, true, &deadline, pa); - if (s == NATS_OK) - { - bool received = false; - bool closed = false; - - // On _stanPublish success, we need to release connection at the end - // of this function. - releaseConn = true; - - while ((s != NATS_TIMEOUT) && !received && !closed) - { - natsMutex_Lock(sc->pubAckMu); - received = pa->received; - closed = sc->pubAckClosed; - if (!closed && !received) - { - sc->pubAckInWait++; - s = natsCondition_AbsoluteTimedWait(sc->pubAckCond, sc->pubAckMu, deadline); - sc->pubAckInWait--; - } - natsMutex_Unlock(sc->pubAckMu); - } - if ((s == NATS_OK) && !received && closed) - s = nats_setDefaultError(NATS_CONNECTION_CLOSED); - if (s != NATS_OK) - { - // Regardless the error, we need to remove from map - natsMutex_Lock(sc->pubAckMu); - // If we cannot remove, it means we just received the ack - // and need to proceed with "success" branch. - if (natsStrHash_Remove(sc->pubAckMap, pa->guid) != NULL) - { - // For timeout, augment the error text - if (s == NATS_TIMEOUT) - NATS_UPDATE_ERR_TXT("%s", STAN_ERR_PUB_ACK_TIMEOUT); - } - else - { - s = NATS_OK; - // Error was set in condition wait, so clear. - nats_clearLastError(); - } - natsMutex_Unlock(sc->pubAckMu); - } - if (s == NATS_OK) - { - // PubAck was received, if error report that error. - if (pa->error != NULL) - s = nats_setError(NATS_ERR, "%s", pa->error); - } - } - // Done with `pa`. - _pubAckFree(pa); - - if (releaseConn) - stanConn_release(sc); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -stanConnection_PublishAsync(stanConnection *sc, const char *channel, - const void *data, int dataLen, - stanPubAckHandler ah, void *ahClosure) -{ - natsStatus s; - _pubAck *pa = NULL; - - pa = NATS_CALLOC(1, sizeof(_pubAck)); - if (pa == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - // These are possibly NULL. - pa->ah = ah; - pa->ahClosure = ahClosure; - - s = _stanPublish(sc, channel, data, dataLen, false, NULL, pa); - // If error, `pa` has not been stored (or stored, but then removed). - // It is our responsibility to free it here. - if (s != NATS_OK) - _pubAckFree(pa); - - return NATS_UPDATE_ERR_STACK(s); -} diff --git a/src/stan/pub.h b/src/stan/pub.h deleted file mode 100644 index 4acb2e9c2..000000000 --- a/src/stan/pub.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018 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. - -#ifndef STAN_PUB_H_ -#define STAN_PUB_H_ - -#include "stanp.h" - -#define STAN_ERR_PUB_ACK_TIMEOUT "publish ack timeout" - -void -stanProcessPubAck(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); - -#endif /* STAN_PUB_H_ */ diff --git a/src/stan/sopts.c b/src/stan/sopts.c deleted file mode 100644 index 041333ef0..000000000 --- a/src/stan/sopts.c +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright 2018 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 "sopts.h" -#include "../opts.h" - -static void -_stanSubOpts_free(stanSubOptions *opts) -{ - if (opts == NULL) - return; - - NATS_FREE(opts->durableName); - natsMutex_Destroy(opts->mu); - NATS_FREE(opts); -} - -natsStatus -stanSubOptions_Create(stanSubOptions **newOpts) -{ - natsStatus s = NATS_OK; - stanSubOptions *opts = NULL; - - // Ensure the library is loaded - s = nats_Open(-1); - if (s != NATS_OK) - return s; - - opts = (stanSubOptions*) NATS_CALLOC(1, sizeof(stanSubOptions)); - if (opts == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - if (natsMutex_Create(&(opts->mu)) != NATS_OK) - { - NATS_FREE(opts); - return NATS_UPDATE_ERR_STACK(NATS_NO_MEMORY); - } - - opts->maxInflight = STAN_SUB_OPTS_DEFAULT_MAX_INFLIGHT; - opts->ackWait = STAN_SUB_OPTS_DEFAULT_ACK_WAIT; - opts->startAt = PB__START_POSITION__NewOnly; - - if (s == NATS_OK) - *newOpts = opts; - else - _stanSubOpts_free(opts); - - return s; -} - -natsStatus -stanSubOptions_SetDurableName(stanSubOptions *opts, const char *durableName) -{ - natsStatus s = NATS_OK; - - LOCK_AND_CHECK_OPTIONS(opts, 0); - - if (opts->durableName != NULL) - { - NATS_FREE(opts->durableName); - opts->durableName = NULL; - } - if (durableName != NULL) - DUP_STRING(s, opts->durableName, durableName); - - UNLOCK_OPTS(opts); - - return s; -} - -natsStatus -stanSubOptions_SetAckWait(stanSubOptions *opts, int64_t wait) -{ - LOCK_AND_CHECK_OPTIONS(opts, (wait <= 0)); - - opts->ackWait = wait; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -stanSubOptions_SetMaxInflight(stanSubOptions *opts, int maxInflight) -{ - LOCK_AND_CHECK_OPTIONS(opts, (maxInflight < 1)); - - opts->maxInflight = maxInflight; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -stanSubOptions_StartAtSequence(stanSubOptions *opts, uint64_t seq) -{ - LOCK_AND_CHECK_OPTIONS(opts, (seq < 1)); - - opts->startAt = PB__START_POSITION__SequenceStart; - opts->startSequence = seq; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -stanSubOptions_StartAtTime(stanSubOptions *opts, int64_t time) -{ - LOCK_AND_CHECK_OPTIONS(opts, (time < 0)); - - opts->startAt = PB__START_POSITION__TimeDeltaStart; - opts->startTime = time; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -stanSubOptions_StartAtTimeDelta(stanSubOptions *opts, int64_t delta) -{ - LOCK_AND_CHECK_OPTIONS(opts, (delta < 0)); - - opts->startAt = PB__START_POSITION__TimeDeltaStart; - opts->startTime = nats_Now() - delta; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -stanSubOptions_StartWithLastReceived(stanSubOptions *opts) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->startAt = PB__START_POSITION__LastReceived; - - UNLOCK_OPTS(opts); - - return NATS_OK; - -} - -natsStatus -stanSubOptions_DeliverAllAvailable(stanSubOptions *opts) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->startAt = PB__START_POSITION__First; - - UNLOCK_OPTS(opts); - - return NATS_OK; - -} - -natsStatus -stanSubOptions_SetManualAckMode(stanSubOptions *opts, bool manual) -{ - LOCK_AND_CHECK_OPTIONS(opts, 0); - - opts->manualAcks = manual; - - UNLOCK_OPTS(opts); - - return NATS_OK; -} - -natsStatus -stanSubOptions_clone(stanSubOptions **clonedOpts, stanSubOptions *opts) -{ - natsStatus s = NATS_OK; - stanSubOptions *cloned = NULL; - int muSize; - - s = stanSubOptions_Create(&cloned); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - natsMutex_Lock(opts->mu); - - muSize = sizeof(cloned->mu); - - // Make a blind copy first... - memcpy((char*)cloned + muSize, (char*)opts + muSize, - sizeof(stanSubOptions) - muSize); - - // Then remove all pointers, so that if we fail while - // copying them, and free the cloned, we don't free the pointers - // from the original. - cloned->durableName = NULL; - - s = stanSubOptions_SetDurableName(cloned, opts->durableName); - - if (s == NATS_OK) - *clonedOpts = cloned; - else - _stanSubOpts_free(cloned); - - natsMutex_Unlock(opts->mu); - - return s; -} - -void -stanSubOptions_Destroy(stanSubOptions *opts) -{ - if (opts == NULL) - return; - - _stanSubOpts_free(opts); -} diff --git a/src/stan/sopts.h b/src/stan/sopts.h deleted file mode 100644 index 1fba8b606..000000000 --- a/src/stan/sopts.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018 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. - -#ifndef SOPTS_H_ -#define SOPTS_H_ - -#include "stanp.h" - -#define STAN_SUB_OPTS_DEFAULT_MAX_INFLIGHT (1024) -#define STAN_SUB_OPTS_DEFAULT_ACK_WAIT (30 * 1000) // 30 seconds - -natsStatus -stanSubOptions_clone(stanSubOptions **clonedOpts, stanSubOptions *opts); - -#endif /* SOPTS_H_ */ diff --git a/src/stan/stanp.h b/src/stan/stanp.h deleted file mode 100644 index 8189b2913..000000000 --- a/src/stan/stanp.h +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2018-2019 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. - -#ifndef STANP_H_ -#define STANP_H_ - -#include "../natsp.h" -#include "../mem.h" -#include "protocol.pb-c.h" -#include "protobuf-c/protobuf-c.h" - -extern bool testAllowMillisecInPings; - -struct __stanConnOptions -{ - natsMutex *mu; - - // URL to connect to. Takes precedence to any URL set in ncOpts. - char *url; - - // Low level NATS connection options to use to create the NATS connection. - natsOptions *ncOpts; - - // Discovery prefix. The connect request is sent to that + "." + name of cluster. - char *discoveryPrefix; - - // Connection create/close request timeout (in milliseconds). - int64_t connTimeout; - - // How long (in milliseconds) to wait for a published message ack. - int64_t pubAckTimeout; - // Max number of messages that can be sent without receiving corresponding ack from server. - int maxPubAcksInflight; - // Percentage of above value to decide when to release a blocked publish call. - float maxPubAcksInFlightPercentage; - - // Ping interval (number of seconds, except in tests where it can be interpreted as milliseconds) - int pingInterval; - // Max number of PINGs without receiving any PONG - int pingMaxOut; - - // Callback and closure to invoke when connection is permanently lost. - stanConnectionLostHandler connectionLostCB; - void *connectionLostCBClosure; -}; - -struct __stanSubOptions -{ - natsMutex *mu; - - // DurableName, if set will survive client restarts. - char *durableName; - - // Controls the number of messages the cluster will have inflight without an ACK. - int maxInflight; - - // Controls the time the cluster will wait for an ACK for a given message. - // This is in milliseconds. - int64_t ackWait; - - // StartPosition enum from proto. - Pb__StartPosition startAt; - - // Optional start sequence number. - uint64_t startSequence; - - // Optional start time (expressed in milliseconds) - int64_t startTime; - - // Option to do Manual Acks - bool manualAcks; -}; - - -typedef struct __pubAck -{ - char *guid; - int64_t deadline; - stanPubAckHandler ah; - void *ahClosure; - char *error; - bool dontFreeError; - bool received; - bool isSync; - struct __pubAck *prev; - struct __pubAck *next; - -} _pubAck; - -typedef struct __natsPBufAllocator -{ - ProtobufCAllocator base; - - char *buf; - int cap; - int used; - int remaining; - int protoSize; - int overhead; - -} natsPBufAllocator; - -struct __stanConnection -{ - natsMutex *mu; - int refs; - - stanConnOptions *opts; - - natsConnection *nc; - - char *clusterID; - char *clientID; - char *connID; - int connIDLen; - - char *pubPrefix; - char *subRequests; - char *unsubRequests; - char *subCloseRequests; - char *closeRequests; - - char *ackSubject; - natsSubscription *ackSubscription; - - natsInbox *hbInbox; - natsSubscription *hbSubscription; - - natsMutex *pubAckMu; - natsStrHash *pubAckMap; - natsCondition *pubAckCond; - int pubAckInWait; - natsCondition *pubAckMaxInflightCond; - int pubAckMaxInflightThreshold; - bool pubAckMaxInflightInWait; - bool pubAckClosed; - - _pubAck *pubAckHead; - _pubAck *pubAckTail; - natsTimer *pubAckTimer; - bool pubAckTimerNeedReset; - natsPBufAllocator *pubAckAllocator; - - char *pubMsgBuf; - int pubMsgBufCap; - - int pubPrefixLen; - char *pubSubjBuf; - int pubSubjBufCap; - - natsMutex *pingMu; - natsSubscription *pingSub; - natsTimer *pingTimer; - char *pingBytes; - int pingBytesLen; - char *pingRequests; - char *pingInbox; - int pingOut; - - char *connLostErrTxt; - - bool closed; - - // This is used when user calls stanConnection_GetNATSConnection(). - // User is supposed to call stanConnection_ReleaseNATSConnection() - // when done with it. When ncRefs == 1, the stan connection is - // retained, when ncRefs == 0, it is released. - // We could simply retain on each Get call and release on Release, - // but this offers a bit more protection against mismatch between - // Get() and Release() calls. - int ncRefs; -}; - -struct __stanMsg -{ - natsGCItem gc; - - // The message is allocated as a single memory block that contains - // this structure and enough space for the payload. The msg payload - // starts after the 'next' pointer. - uint64_t seq; - int64_t timestamp; - const char *data; - int dataLen; - bool redelivered; - // Must be last field! - stanSubscription *sub; - - // Nothing after this: the message payload goes there. -}; - -struct __stanSubscription -{ - natsMutex *mu; - int refs; - - stanSubOptions *opts; - - stanConnection *sc; - - char *channel; - - char *qgroup; - - char *inbox; - natsSubscription *inboxSub; - - char *ackInbox; - - stanMsgHandler cb; - void *cbClosure; - - // count messages received, and use it compared to MaxInflight - // to cause a low level buffer flush when sending ACK. - int msgs; - - char *ackBuf; - int ackBufCap; - - natsPBufAllocator *allocator; - - bool closed; - - natsOnCompleteCB onCompleteCB; - void *onCompleteCBClosure; -}; - -#endif /* STANP_H_ */ diff --git a/src/stan/sub.c b/src/stan/sub.c deleted file mode 100644 index a363bda63..000000000 --- a/src/stan/sub.c +++ /dev/null @@ -1,696 +0,0 @@ -// Copyright 2018-2020 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 "conn.h" -#include "sub.h" -#include "sopts.h" -#include "msg.h" - -#include "../conn.h" -#include "../sub.h" -#include "../buf.h" - -#ifdef DEV_MODE -// For type safety -void stanSub_Lock(stanSubscription *sub) { natsMutex_Lock(sub->mu); } -void stanSub_Unlock(stanSubscription *sub) { natsMutex_Unlock(sub->mu); } -#endif // DEV_MODE - -static void -_freeStanSub(stanSubscription *sub) -{ - if (sub == NULL) - return; - - NATS_FREE(sub->ackInbox); - NATS_FREE(sub->channel); - NATS_FREE(sub->inbox); - NATS_FREE(sub->qgroup); - NATS_FREE(sub->ackBuf); - natsSubscription_Destroy(sub->inboxSub); - stanSubOptions_Destroy(sub->opts); - natsPBufAllocator_Destroy(sub->allocator); - natsMutex_Destroy(sub->mu); - - NATS_FREE(sub); -} - -void -stanSub_release(stanSubscription *sub) -{ - int refs = 0; - - if (sub == NULL) - return; - - stanSub_Lock(sub); - refs = --(sub->refs); - stanSub_Unlock(sub); - - if (refs == 0) - _freeStanSub(sub); -} - -static void -_stanProcessMsg(natsConnection *nc, natsSubscription *ignored, natsMsg *msg, void *closure) -{ - natsStatus s = NATS_OK; - stanSubscription *sub = (stanSubscription*) closure; - Pb__MsgProto *pbMsg = NULL; - stanMsg *sMsg = NULL; - ProtobufCAllocator *alloc = (ProtobufCAllocator*) sub->allocator; - - natsPBufAllocator_Prepare(sub->allocator, msg->dataLen); - - pbMsg = pb__msg_proto__unpack(alloc, (size_t) msg->dataLen, (const uint8_t*) msg->data); - if (pbMsg == NULL) - { - natsMsg_Destroy(msg); - return; - } - - s = stanMsg_create(&sMsg, sub, pbMsg); - if (s == NATS_OK) - { - stanMsgHandler cb = NULL; - void *cbClosure = NULL; - stanConnection *sc = NULL; - char *channel = NULL; - bool sendAck = false; - char *ackSubject = NULL; - bool flush = false; - char *ackBuf = NULL; - int ackSize = 0; - Pb__Ack ack; - - stanSub_Lock(sub); - if (sub->closed) - s = NATS_INVALID_SUBSCRIPTION; - if (s == NATS_OK) - { - sc = sub->sc; - cb = sub->cb; - cbClosure = sub->cbClosure; - channel = sub->channel; - sendAck = sub->opts->manualAcks == false; - ackSubject = sub->ackInbox; - // Prepare buf for ack - if (sendAck) - { - if (++sub->msgs == sub->opts->maxInflight) - { - sub->msgs = 0; - flush = true; - } - pb__ack__init(&ack); - ack.subject = channel; - ack.sequence = sMsg->seq; - - ackSize = (int) pb__ack__get_packed_size(&ack); - if (ackSize > sub->ackBufCap) - s = expandBuf(&sub->ackBuf, &sub->ackBufCap, 2*ackSize); - - if (s == NATS_OK) - ackBuf = sub->ackBuf; - } - } - stanSub_Unlock(sub); - - if (s == NATS_OK) - { - (*cb)(sc, sub, channel, sMsg, cbClosure); - - if (sendAck) - { - int packedSize = 0; - - packedSize = (int) pb__ack__pack(&ack, (uint8_t*) ackBuf); - if (ackSize == packedSize) - { - natsMsg ackMsg; - - natsMsg_init(&ackMsg, (const char*) ackSubject, (const char*) ackBuf, ackSize); - natsConn_publish(nc, &ackMsg, NULL, flush); - } - } - } - else - { - // Since we didn't pass to callback, need to destroy. - stanMsg_Destroy(sMsg); - } - } - - natsMsg_Destroy(msg); - - pb__msg_proto__free_unpacked(pbMsg, alloc); -} - -natsStatus -stanSubscription_AckMsg(stanSubscription *sub, stanMsg *msg) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - bool flush = false; - char *ackSub = NULL; - int ackSize = 0; - Pb__Ack ack; - - if ((sub == NULL) || (msg == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - stanSub_Lock(sub); - if (sub->closed) - { - stanSub_Unlock(sub); - return nats_setDefaultError(NATS_INVALID_SUBSCRIPTION); - } - if (!sub->opts->manualAcks) - { - stanSub_Unlock(sub); - return nats_setError(NATS_ERR, "%s", STAN_ERR_MANUAL_ACK); - } - if (msg->sub != sub) - { - stanSub_Unlock(sub); - return nats_setError(NATS_ILLEGAL_STATE, "%s", STAN_ERR_SUB_NOT_OWNER); - } - - if (++sub->msgs == sub->opts->maxInflight) - { - sub->msgs = 0; - flush = true; - } - - nc = sub->sc->nc; - ackSub = sub->ackInbox; - - pb__ack__init(&ack); - ack.subject = sub->channel; - ack.sequence = msg->seq; - - stanSub_Unlock(sub); - - ackSize = (int) pb__ack__get_packed_size(&ack); - if (ackSize == 0) - { - s = nats_setError(NATS_ERR, "%s", "message acknowledgment protocol packed size is 0"); - } - else - { - char ackBuf[1024]; - char *ackBytes = NULL; - int packedSize = 0; - bool needFree = false; - - if (ackSize > (int) sizeof(ackBuf)) - { - ackBytes = NATS_MALLOC(ackSize); - if (ackBytes == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - else - needFree = true; - } - else - { - ackBytes = (char*) ackBuf; - } - if (s == NATS_OK) - { - packedSize = (int) pb__ack__pack(&ack, (uint8_t*) ackBuf); - if (ackSize != packedSize) - s = nats_setError(NATS_ERR, "message acknowledgment protocol computed packed size is %d, got %d", - ackSize, packedSize); - else - { - natsMsg ackMsg; - - natsMsg_init(&ackMsg, ackSub, (const void*) ackBuf, ackSize); - s = natsConn_publish(nc, &ackMsg, NULL, flush); - } - - if (needFree) - NATS_FREE(ackBytes); - } - } - - return NATS_UPDATE_ERR_STACK(s); -} - -static void -_releaseStanSubCB(void *closure) -{ - stanSubscription *sub = (stanSubscription*) closure; - stanConnection *sc = NULL; - natsOnCompleteCB cb = NULL; - void *cbc = NULL; - int refs; - - stanSub_Lock(sub); - cb = sub->onCompleteCB; - cbc = sub->onCompleteCBClosure; - sc = sub->sc; - refs = --sub->refs; - stanSub_Unlock(sub); - - if (cb != NULL) - (cb)(cbc); - - if (refs == 0) - _freeStanSub(sub); - - stanConn_release(sc); -} - -// Sends a subscription close protocol with provided information. -// Best effort: does not wait for the reply and ignore any error. -static void -_sendCloseSub(natsConnection *nc, char *closeSubj, char *cid, char *channel, char*inbox) -{ - Pb__UnsubscribeRequest usr; - int usrSize = 0; - char *usrBytes = NULL; - int packedSize= 0; - - pb__unsubscribe_request__init(&usr); - usr.clientid = cid; - usr.subject = channel; - usr.inbox = inbox; - - usrSize = (int) pb__unsubscribe_request__get_packed_size(&usr); - if (usrSize == 0) - return; - - usrBytes = NATS_MALLOC(usrSize); - if (usrBytes == NULL) - return; - - packedSize = (int) pb__unsubscribe_request__pack(&usr, (uint8_t*) usrBytes); - if (usrSize != packedSize) - return; - - natsConnection_Publish(nc, closeSubj, (const void*) usrBytes, usrSize); - - NATS_FREE(usrBytes); -} - -static natsStatus -stanConn_subscribe(stanSubscription **newSub, stanConnection *sc, - const char *channel, const char *queue, - stanMsgHandler cb, void *cbClosure, - stanSubOptions *opts) -{ - natsStatus s = NATS_OK; - stanSubscription *sub = NULL; - natsConnection *nc = NULL; - char *cid = NULL; - char *rSubj = NULL; - int64_t timeout= 0; - char *closeSubj = NULL; - - if ((newSub == NULL) - || (sc == NULL) - || (channel == NULL) - || (cb == NULL)) - { - return nats_setDefaultError(NATS_INVALID_ARG); - } - - stanConn_Lock(sc); - if (sc->closed) - { - stanConn_Unlock(sc); - return nats_setDefaultError(NATS_CONNECTION_CLOSED); - } - - sub = NATS_CALLOC(1, sizeof(stanSubscription)); - if (sub == NULL) - { - stanConn_Unlock(sc); - return nats_setDefaultError(NATS_NO_MEMORY); - } - - s = natsMutex_Create(&sub->mu); - if (s != NATS_OK) - { - stanConn_Unlock(sc); - NATS_FREE(sub); - return NATS_UPDATE_ERR_STACK(s); - } - - // Retain the connection until we have fully setup the subscription - // since we will release the lock at one point. - sc->refs++; - - // Capture some stan connection fields. We know they will be valid - // even if the connection is closed, because we have retained the - // object. - nc = sc->nc; - cid = sc->clientID; - rSubj = sc->subRequests; - timeout = sc->opts->connTimeout; - closeSubj = sc->subCloseRequests; - - stanConn_Unlock(sc); - - // Lock the subscription while we set it up. - stanSub_Lock(sub); - - sub->refs = 1; - sub->sc = sc; - sub->cb = cb; - sub->cbClosure = cbClosure; - - if (opts != NULL) - s = stanSubOptions_clone(&sub->opts, opts); - else - s = stanSubOptions_Create(&sub->opts); - - IF_OK_DUP_STRING(s, sub->channel, channel); - if ((s == NATS_OK) && queue != NULL) - DUP_STRING(s, sub->qgroup, queue); - if (s == NATS_OK) - s = natsPBufAllocator_Create(&sub->allocator, sizeof(Pb__MsgProto), 3); - if (s == NATS_OK) - s = natsConn_newInbox(nc, (natsInbox**) &sub->inbox); - - if (s == NATS_OK) - { - s = natsConnection_Subscribe(&sub->inboxSub, nc, sub->inbox, _stanProcessMsg, (void*) sub); - if (s == NATS_OK) - { - natsSubscription_SetPendingLimits(sub->inboxSub, -1, -1); - // Retain both sub and sc - sub->refs++; - stanConn_retain(sc); - s = natsSubscription_SetOnCompleteCB(sub->inboxSub, _releaseStanSubCB, (void*) sub); - if (s != NATS_OK) - { - sub->refs--; - stanConn_release(sc); - } - } - if (s == NATS_OK) - { - Pb__SubscriptionRequest subReq; - int reqSize = 0; - char *reqBytes = NULL; - natsMsg *replyMsg = NULL; - - pb__subscription_request__init(&subReq); - subReq.clientid = cid; - subReq.subject = sub->channel; - subReq.qgroup = sub->qgroup; - subReq.inbox = sub->inbox; - subReq.maxinflight = sub->opts->maxInflight; - subReq.ackwaitinsecs = (int32_t)(sub->opts->ackWait / 1000); - subReq.startposition = sub->opts->startAt; - subReq.durablename = sub->opts->durableName; - - if (subReq.startposition == PB__START_POSITION__TimeDeltaStart) - { - subReq.starttimedelta = (nats_Now() - sub->opts->startTime) * (int64_t) 1000000; - } - else if (subReq.startposition == PB__START_POSITION__SequenceStart) - { - subReq.startsequence = sub->opts->startSequence; - } - - reqSize = (int) pb__subscription_request__get_packed_size(&subReq); - if (reqSize == 0) - { - s = nats_setError(NATS_ERR, "%s", "subscription request protocol packed size is 0"); - } - else - { - reqBytes = NATS_MALLOC(reqSize); - if (reqBytes == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - if (s == NATS_OK) - { - int packedSize = (int) pb__subscription_request__pack(&subReq, (uint8_t*) reqBytes); - if (reqSize != packedSize) - { - s = nats_setError(NATS_ERR, "subscription request protocol computed packed size is %d, got %d", - reqSize, packedSize); - } - else - { - s = natsConnection_Request(&replyMsg, nc, rSubj, (const void*) reqBytes, reqSize, timeout); - if (s == NATS_TIMEOUT) - NATS_UPDATE_ERR_TXT("%s", STAN_ERR_SUBSCRIBE_REQUEST_TIMEOUT); - else if (s == NATS_NO_RESPONDERS) - NATS_UPDATE_ERR_TXT("%s", STAN_ERR_SUBSCRIBE_REQUEST_NO_RESP); - } - - NATS_FREE(reqBytes); - } - if (s == NATS_OK) - { - Pb__SubscriptionResponse *subResp = NULL; - - subResp = pb__subscription_response__unpack(NULL, - (size_t) natsMsg_GetDataLength(replyMsg), - (const uint8_t*) natsMsg_GetData(replyMsg)); - if (subResp == NULL) - s = nats_setError(NATS_ERR, "%s", "unable to decode subscription response"); - - if ((s == NATS_OK) && (strlen(subResp->error) > 0)) - s = nats_setError(NATS_ERR, "%s", subResp->error); - - IF_OK_DUP_STRING(s, sub->ackInbox, subResp->ackinbox); - - pb__subscription_response__free_unpacked(subResp, NULL); - - natsMsg_Destroy(replyMsg); - } - - // If there was an error, need to unsub. - if (s != NATS_OK) - { - natsSubscription_Unsubscribe(sub->inboxSub); - if (s == NATS_TIMEOUT) - { - // On timeout, we don't know if the server got the request or - // not. So we will do best effort and send a "subscription close" - // request. However, since we don't have the AckInbox that is - // normally used to close a subscription, we will use the sub's - // inbox. Newer servers will fallback to lookup by inbox if they - // don't find the sub from the "AckInbox" lookup. - _sendCloseSub(nc, closeSubj, cid, sub->channel, sub->inbox); - } - } - } - } - stanSub_Unlock(sub); - - if (s == NATS_OK) - *newSub = sub; - else - stanSub_release(sub); - - stanConn_release(sc); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -stanConnection_Subscribe(stanSubscription **newSub, stanConnection *sc, - const char *channel, - stanMsgHandler cb, void *cbClosure, - stanSubOptions *opts) -{ - natsStatus s; - - s = stanConn_subscribe(newSub, sc, channel, NULL, cb, cbClosure, opts); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -stanConnection_QueueSubscribe(stanSubscription **newSub, stanConnection *sc, - const char *channel, const char *queueGroup, - stanMsgHandler cb, void *cbClosure, - stanSubOptions *opts) -{ - natsStatus s; - - s = stanConn_subscribe(newSub, sc, channel, queueGroup, cb, cbClosure, opts); - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_closeOrUnsubscribeStanSub(stanSubscription *sub, bool doClose) -{ - natsStatus s = NATS_OK; - stanConnection *sc = NULL; - natsConnection *nc = NULL; - char *reqSubj = NULL; - char *cid = NULL; - char *subj = NULL; - char *ackInbox = NULL; - int64_t timeout = 0; - Pb__UnsubscribeRequest usr; - int usrSize = 0; - - stanSub_Lock(sub); - if (sub->closed) - { - stanSub_Unlock(sub); - return nats_setDefaultError(NATS_INVALID_SUBSCRIPTION); - } - sub->closed = true; - natsSubscription_Unsubscribe(sub->inboxSub); - sc = sub->sc; - ackInbox = sub->ackInbox; - subj = sub->channel; - stanSub_Unlock(sub); - - stanConn_Lock(sc); - if (sc->closed) - { - stanConn_Unlock(sc); - return nats_setDefaultError(NATS_CONNECTION_CLOSED); - } - reqSubj = sc->unsubRequests; - if (doClose) - { - reqSubj = sc->subCloseRequests; - if (reqSubj == NULL) - { - stanConn_Unlock(sc); - s = nats_setError(NATS_NO_SERVER_SUPPORT, "%s", STAN_ERR_SUB_CLOSE_NOT_SUPPORTED); - return s; - } - } - nc = sc->nc; - cid = sc->clientID; - timeout = sc->opts->connTimeout; - stanConn_Unlock(sc); - - pb__unsubscribe_request__init(&usr); - usr.clientid = cid; - usr.subject = subj; - usr.inbox = ackInbox; - - usrSize = (int) pb__unsubscribe_request__get_packed_size(&usr); - if (usrSize == 0) - { - s = nats_setError(NATS_ERR, "%s subscription request protocol packed size is 0", - (doClose ? "close" : "unsubscribe")); - } - else - { - natsMsg *replyMsg = NULL; - char *usrBytes = NATS_MALLOC(usrSize); - - if (usrBytes == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - - if (s == NATS_OK) - { - int packedSize = (int) pb__unsubscribe_request__pack(&usr, (uint8_t*) usrBytes); - if (usrSize != packedSize) - { - s = nats_setError(NATS_ERR, "%s subscription protocol computed packed size is %v, got %v", - (doClose ? "close" : "unsubscribe"), usrSize, packedSize); - } - else - { - s = natsConnection_Request(&replyMsg, nc, reqSubj, (const void*) usrBytes, usrSize, timeout); - if (s == NATS_TIMEOUT) - NATS_UPDATE_ERR_TXT("%s", (doClose ? STAN_ERR_CLOSE_REQUEST_TIMEOUT : STAN_ERR_UNSUBSCRIBE_REQUEST_TIMEOUT)); - else if (s == NATS_NO_RESPONDERS) - NATS_UPDATE_ERR_TXT("%s", (doClose ? STAN_ERR_CLOSE_REQUEST_NO_RESP : STAN_ERR_UNSUBSCRIBE_REQUEST_NO_RESP)); - } - - NATS_FREE(usrBytes); - - if (s == NATS_OK) - { - Pb__SubscriptionResponse *resp = NULL; - - resp = pb__subscription_response__unpack(NULL, - (size_t) natsMsg_GetDataLength(replyMsg), - (const uint8_t*) natsMsg_GetData(replyMsg)); - - if (resp == NULL) - s = nats_setError(NATS_ERR, "%s", "unable to decode subscription response"); - - if ((s == NATS_OK) && (strlen(resp->error) > 0)) - s = nats_setError(NATS_ERR, "%s", resp->error); - - - pb__subscription_response__free_unpacked(resp, NULL); - - natsMsg_Destroy(replyMsg); - } - } - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -stanSubscription_SetOnCompleteCB(stanSubscription *sub, natsOnCompleteCB cb, void *closure) -{ - natsStatus s = NATS_OK; - - if (sub == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - stanSub_Lock(sub); - if (sub->closed) - s = nats_setDefaultError(NATS_INVALID_SUBSCRIPTION); - else - { - sub->onCompleteCB = cb; - sub->onCompleteCBClosure = closure; - } - stanSub_Unlock(sub); - - return s; -} - -natsStatus -stanSubscription_Unsubscribe(stanSubscription *sub) -{ - natsStatus s; - - if (sub == NULL) - return NATS_OK; - - s = _closeOrUnsubscribeStanSub(sub, false); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -stanSubscription_Close(stanSubscription *sub) -{ - natsStatus s; - - if (sub == NULL) - return NATS_OK; - - s = _closeOrUnsubscribeStanSub(sub, true); - return NATS_UPDATE_ERR_STACK(s); -} - -void -stanSubscription_Destroy(stanSubscription *sub) -{ - if (sub == NULL) - return; - - _closeOrUnsubscribeStanSub(sub, true); - stanSub_release(sub); -} diff --git a/src/stan/sub.h b/src/stan/sub.h deleted file mode 100644 index 872ccca22..000000000 --- a/src/stan/sub.h +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018 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. - -#ifndef STAN_SUB_H_ -#define STAN_SUB_H_ - -#include "stanp.h" - -#ifdef DEV_MODE -// For type safety - -void stanSub_Lock(stanSubscription *sub); -void stanSub_Unlock(stanSubscription *sub); - -#else -// We know what we are doing :-) - -#define stanSub_Lock(c) (natsMutex_Lock((c)->mu)) -#define stanSub_Unlock(c) (natsMutex_Unlock((c)->mu)) - -#endif // DEV_MODE - -#define STAN_ERR_SUBSCRIBE_REQUEST_TIMEOUT "subscribe request timeout" -#define STAN_ERR_SUBSCRIBE_REQUEST_NO_RESP "no streaming server was listening for this subscribe request" -#define STAN_ERR_UNSUBSCRIBE_REQUEST_TIMEOUT "unsubscribe request timeout" -#define STAN_ERR_UNSUBSCRIBE_REQUEST_NO_RESP "no streaming server was listening for this unsubscribe request" -#define STAN_ERR_MANUAL_ACK "cannot manually ack in auto-ack mode" -#define STAN_ERR_SUB_CLOSE_NOT_SUPPORTED "server does not support subscription close" -#define STAN_ERR_SUB_NOT_OWNER "subscription is not the owner of this message" - -void -stanSub_release(stanSubscription *sub); - - -#endif /* STAN_SUB_H_ */ diff --git a/src/stats.c b/src/stats.c deleted file mode 100644 index 301bac0b5..000000000 --- a/src/stats.c +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2015-2018 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 -#include "status.h" -#include "stats.h" -#include "mem.h" - -natsStatus -natsStatistics_Create(natsStatistics **newStats) -{ - natsStatistics *stats = NULL; - - stats = (natsStatistics*) NATS_CALLOC(1, sizeof(natsStatistics)); - if (stats == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - *newStats = stats; - - return NATS_OK; -} - -natsStatus -natsStatistics_GetCounts(const natsStatistics *stats, - uint64_t *inMsgs, uint64_t *inBytes, - uint64_t *outMsgs, uint64_t *outBytes, - uint64_t *reconnects) -{ - if (stats == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - if (inMsgs != NULL) - *inMsgs = stats->inMsgs; - if (inBytes != NULL) - *inBytes = stats->inBytes; - if (outMsgs != NULL) - *outMsgs = stats->outMsgs; - if (outBytes != NULL) - *outBytes = stats->outBytes; - if (reconnects != NULL) - *reconnects = stats->reconnects; - - return NATS_OK; -} - -void -natsStatistics_Destroy(natsStatistics *stats) -{ - if (stats == NULL) - return; - - NATS_FREE(stats); -} diff --git a/src/stats.h b/src/stats.h deleted file mode 100644 index 9c50f935a..000000000 --- a/src/stats.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2015-2018 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. - - -#ifndef STATS_H_ -#define STATS_H_ - -#include - -#include "status.h" - -struct __natsStatistics -{ - uint64_t inMsgs; - uint64_t outMsgs; - uint64_t inBytes; - uint64_t outBytes; - uint64_t reconnects; - -}; - -#endif /* STATS_H_ */ diff --git a/src/sub.c b/src/sub.c deleted file mode 100644 index 499909797..000000000 --- a/src/sub.c +++ /dev/null @@ -1,1486 +0,0 @@ -// Copyright 2015-2021 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 -#include - -#include "mem.h" -#include "conn.h" -#include "sub.h" -#include "msg.h" -#include "util.h" -#include "js.h" -#include "opts.h" - -#ifdef DEV_MODE - -static void _retain(natsSubscription *sub) { sub->refs++; } -static void _release(natsSubscription *sub) { sub->refs--; } - -void natsSub_Lock(natsSubscription *sub) { natsMutex_Lock(sub->mu); } -void natsSub_Unlock(natsSubscription *sub) { natsMutex_Unlock(sub->mu); } - -#else - -#define _retain(s) ((s)->refs++) -#define _release(s) ((s)->refs--) - -#endif // DEV_MODE - -#define SUB_DLV_WORKER_LOCK(s) if ((s)->libDlvWorker != NULL) \ - natsMutex_Lock((s)->libDlvWorker->lock) - -#define SUB_DLV_WORKER_UNLOCK(s) if ((s)->libDlvWorker != NULL) \ - natsMutex_Unlock((s)->libDlvWorker->lock) - -bool testDrainAutoUnsubRace = false; - -static void -_freeSubscription(natsSubscription *sub) -{ - natsMsg *m; - - if (sub == NULL) - return; - - while ((m = sub->msgList.head) != NULL) - { - sub->msgList.head = m->next; - natsMsg_Destroy(m); - } - - NATS_FREE(sub->subject); - NATS_FREE(sub->queue); - - if (sub->deliverMsgsThread != NULL) - { - natsThread_Detach(sub->deliverMsgsThread); - natsThread_Destroy(sub->deliverMsgsThread); - } - natsTimer_Destroy(sub->timeoutTimer); - natsCondition_Destroy(sub->cond); - natsMutex_Destroy(sub->mu); - jsSub_free(sub->jsi); - - natsConn_release(sub->conn); - - NATS_FREE(sub); -} - -void -natsSub_retain(natsSubscription *sub) -{ - natsSub_Lock(sub); - - sub->refs++; - - natsSub_Unlock(sub); -} - -void -natsSub_release(natsSubscription *sub) -{ - int refs = 0; - - if (sub == NULL) - return; - - natsSub_Lock(sub); - - refs = --(sub->refs); - - natsSub_Unlock(sub); - - if (refs == 0) - _freeSubscription(sub); -} - -void -natsSubAndLdw_Lock(natsSubscription *sub) -{ - natsMutex_Lock(sub->mu); - SUB_DLV_WORKER_LOCK(sub); -} - -void -natsSubAndLdw_LockAndRetain(natsSubscription *sub) -{ - natsMutex_Lock(sub->mu); - sub->refs++; - SUB_DLV_WORKER_LOCK(sub); -} - -void -natsSubAndLdw_Unlock(natsSubscription *sub) -{ - SUB_DLV_WORKER_UNLOCK(sub); - natsMutex_Unlock(sub->mu); -} - -void -natsSubAndLdw_UnlockAndRelease(natsSubscription *sub) -{ - int refs = 0; - - SUB_DLV_WORKER_UNLOCK(sub); - - refs = --(sub->refs); - natsMutex_Unlock(sub->mu); - - if (refs == 0) - _freeSubscription(sub); -} - -// Runs under the subscription lock but will release it for a JS subscription -// if the JS consumer needs to be deleted. -static void -_setDrainCompleteState(natsSubscription *sub) -{ - // It is possible that we are here without being in "drain in progress" - // or event "started" due to auto-unsubscribe. So unless we already - // switched to "drain complete", swith the state. - if (!natsSub_drainComplete(sub)) - { - // For JS subscription we may need to delete the JS consumer, but - // we want to do so here ONLY if there was really a drain started. - // So need to check on drain started state. Also, note that if - // jsSub_deleteConsumerAfterDrain is invoked, the lock may be - // released/reacquired in that function. - if ((sub->jsi != NULL) && natsSub_drainStarted(sub) && sub->jsi->dc) - { - jsSub_deleteConsumerAfterDrain(sub); - // Check drainCompete state again, since another thread may have - // beat us to it while lock was released. - if (natsSub_drainComplete(sub)) - return; - } - - // If drain status is not already set (could be done in _flushAndDrain - // if flush fails, or timeout occurs), set it here to report if the - // connection or subscription has been closed prior to drain completion. - if (sub->drainStatus == NATS_OK) - { - if (sub->connClosed) - sub->drainStatus = NATS_CONNECTION_CLOSED; - else if (sub->closed) - sub->drainStatus = NATS_INVALID_SUBSCRIPTION; - } - sub->drainState |= SUB_DRAIN_COMPLETE; - natsCondition_Broadcast(sub->cond); - } -} - -void -natsSub_setDrainCompleteState(natsSubscription *sub) -{ - natsSub_Lock(sub); - _setDrainCompleteState(sub); - natsSub_Unlock(sub); -} - -// _deliverMsgs is used to deliver messages to asynchronous subscribers. -void -natsSub_deliverMsgs(void *arg) -{ - natsSubscription *sub = (natsSubscription*) arg; - natsConnection *nc = sub->conn; - natsMsgHandler mcb = sub->msgCb; - void *mcbClosure = sub->msgCbClosure; - uint64_t delivered; - uint64_t max; - natsMsg *msg; - int64_t timeout; - natsStatus s = NATS_OK; - bool draining = false; - bool rmSub = false; - natsOnCompleteCB onCompleteCB = NULL; - void *onCompleteCBClosure = NULL; - char *fcReply = NULL; - jsSub *jsi = NULL; - - // This just serves as a barrier for the creation of this thread. - natsConn_Lock(nc); - natsConn_Unlock(nc); - - natsSub_Lock(sub); - timeout = sub->timeout; - jsi = sub->jsi; - natsSub_Unlock(sub); - - while (true) - { - natsSub_Lock(sub); - - s = NATS_OK; - while (((msg = sub->msgList.head) == NULL) && !(sub->closed) && !(sub->draining) && (s != NATS_TIMEOUT)) - { - if (timeout != 0) - s = natsCondition_TimedWait(sub->cond, sub->mu, timeout); - else - natsCondition_Wait(sub->cond, sub->mu); - } - - if (sub->closed) - { - natsSub_Unlock(sub); - break; - } - draining = sub->draining; - - // Will happen with timeout subscription - if (msg == NULL) - { - natsSub_Unlock(sub); - if (draining) - { - rmSub = true; - break; - } - // If subscription timed-out, invoke callback with NULL message. - if (s == NATS_TIMEOUT) - (*mcb)(nc, sub, NULL, mcbClosure); - continue; - } - - delivered = ++(sub->delivered); - - sub->msgList.head = msg->next; - - if (sub->msgList.tail == msg) - sub->msgList.tail = NULL; - - sub->msgList.msgs--; - sub->msgList.bytes -= natsMsg_dataAndHdrLen(msg); - - msg->next = NULL; - - // Capture this under lock. - max = sub->max; - - // Check for JS flow control - fcReply = (jsi == NULL ? NULL : jsSub_checkForFlowControlResponse(sub)); - - natsSub_Unlock(sub); - - if ((max == 0) || (delivered <= max)) - { - (*mcb)(nc, sub, msg, mcbClosure); - } - else - { - // We need to destroy the message since the user can't do it - natsMsg_Destroy(msg); - } - - if (fcReply != NULL) - { - natsConnection_Publish(nc, fcReply, NULL, 0); - NATS_FREE(fcReply); - } - - // Don't do 'else' because we need to remove when we have hit - // the max (after the callback returns). - if ((max > 0) && (delivered >= max)) - { - // If we have hit the max for delivered msgs, remove sub. - rmSub = true; - break; - } - } - - natsSub_Lock(sub); - onCompleteCB = sub->onCompleteCB; - onCompleteCBClosure = sub->onCompleteCBClosure; - _setDrainCompleteState(sub); - natsSub_Unlock(sub); - - if (rmSub) - natsConn_removeSubscription(nc, sub); - - if (onCompleteCB != NULL) - (*onCompleteCB)(onCompleteCBClosure); - - natsSub_release(sub); -} - -bool -natsSub_setMax(natsSubscription *sub, uint64_t max) -{ - bool accepted = false; - - natsSub_Lock(sub); - SUB_DLV_WORKER_LOCK(sub); - sub->max = (max <= sub->delivered ? 0 : max); - accepted = sub->max != 0; - SUB_DLV_WORKER_UNLOCK(sub); - natsSub_Unlock(sub); - return accepted; -} - -natsStatus -natsSubscription_SetOnCompleteCB(natsSubscription *sub, natsOnCompleteCB cb, void *closure) -{ - natsStatus s = NATS_OK; - - if (sub == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsSub_Lock(sub); - if ((sub->closed) || (sub->msgCb == NULL)) - s = nats_setDefaultError(NATS_INVALID_SUBSCRIPTION); - else - { - sub->onCompleteCB = cb; - sub->onCompleteCBClosure = closure; - } - natsSub_Unlock(sub); - - return s; -} - -void -natsSub_close(natsSubscription *sub, bool connectionClosed) -{ - natsSub_Lock(sub); - - SUB_DLV_WORKER_LOCK(sub); - - if (!(sub->closed)) - { - sub->closed = true; - sub->connClosed = connectionClosed; - - if ((sub->jsi != NULL) && (sub->jsi->hbTimer != NULL)) - natsTimer_Stop(sub->jsi->hbTimer); - - if (sub->libDlvWorker != NULL) - { - // If this is a subscription with timeout, stop the timer. - if (sub->timeout != 0) - natsTimer_Stop(sub->timeoutTimer); - - // Post a control message to wake-up the worker which will - // ensure that all pending messages for this subscription - // are removed and the subscription will ultimately be - // released in the worker thread. - natsLib_msgDeliveryPostControlMsg(sub); - } - else - natsCondition_Broadcast(sub->cond); - } - - SUB_DLV_WORKER_UNLOCK(sub); - - natsSub_Unlock(sub); -} - -static void -_asyncTimeoutCb(natsTimer *timer, void* closure) -{ - natsSubscription *sub = (natsSubscription*) closure; - - // Should not happen, but in case - if (sub->libDlvWorker == NULL) - return; - - SUB_DLV_WORKER_LOCK(sub); - - // If the subscription is closed, or if we are prevented from posting - // a "timeout" control message, do nothing. - if (!sub->closed && !sub->timedOut && !sub->timeoutSuspended) - { - // Prevent from scheduling another control message while we are not - // done with previous one. - sub->timedOut = true; - - // Set the timer to a very high value, it will be reset from the - // worker thread. - natsTimer_Reset(sub->timeoutTimer, 60*60*1000); - - // Post a control message to the worker thread. - natsLib_msgDeliveryPostControlMsg(sub); - } - - SUB_DLV_WORKER_UNLOCK(sub); -} - -static void -_asyncTimeoutStopCb(natsTimer *timer, void* closure) -{ - natsSubscription *sub = (natsSubscription*) closure; - - natsSub_release(sub); -} - -natsStatus -natsSub_create(natsSubscription **newSub, natsConnection *nc, const char *subj, - const char *queueGroup, int64_t timeout, natsMsgHandler cb, void *cbClosure, - bool preventUseOfLibDlvPool, jsSub *jsi) -{ - natsStatus s = NATS_OK; - natsSubscription *sub = NULL; - - sub = (natsSubscription*) NATS_CALLOC(1, sizeof(natsSubscription)); - if (sub == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - s = natsMutex_Create(&(sub->mu)); - if (s != NATS_OK) - { - NATS_FREE(sub); - return NATS_UPDATE_ERR_STACK(s); - } - - natsConn_retain(nc); - - sub->refs = 1; - sub->conn = nc; - sub->timeout = timeout; - sub->msgCb = cb; - sub->msgCbClosure = cbClosure; - sub->msgsLimit = nc->opts->maxPendingMsgs; - sub->bytesLimit = nc->opts->maxPendingBytes == -1 ? nc->opts->maxPendingMsgs * 1024 : nc->opts->maxPendingBytes;; - sub->jsi = jsi; - - sub->subject = NATS_STRDUP(subj); - if (sub->subject == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - - if ((s == NATS_OK) && (queueGroup != NULL) && (strlen(queueGroup) > 0)) - { - sub->queue = NATS_STRDUP(queueGroup); - if (sub->queue == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - if (s == NATS_OK) - s = natsCondition_Create(&(sub->cond)); - if ((s == NATS_OK) && (cb != NULL)) - { - if (!(nc->opts->libMsgDelivery) || preventUseOfLibDlvPool) - { - // Let's not rely on the created thread acquiring the lock that - // would make it safe to retain only on success. - _retain(sub); - - // If we have an async callback, start up a sub specific - // thread to deliver the messages. - s = natsThread_Create(&(sub->deliverMsgsThread), natsSub_deliverMsgs, - (void*) sub); - if (s != NATS_OK) - _release(sub); - } - else - { - _retain(sub); - s = natsLib_msgDeliveryAssignWorker(sub); - if ((s == NATS_OK) && (timeout > 0)) - { - _retain(sub); - s = natsTimer_Create(&sub->timeoutTimer, _asyncTimeoutCb, - _asyncTimeoutStopCb, timeout, (void*) sub); - if (s != NATS_OK) - _release(sub); - } - if (s != NATS_OK) - _release(sub); - } - } - - if (s == NATS_OK) - *newSub = sub; - else - natsSub_release(sub); - - return NATS_UPDATE_ERR_STACK(s); -} - -/* - * Expresses interest in the given subject. The subject can have wildcards - * (partial:*, full:>). Messages will be delivered to the associated - * natsMsgHandler. If no natsMsgHandler is given, the subscription is a - * synchronous subscription and can be polled via natsSubscription_NextMsg(). - */ -natsStatus -natsConnection_Subscribe(natsSubscription **sub, natsConnection *nc, const char *subject, - natsMsgHandler cb, void *cbClosure) -{ - natsStatus s; - - if (cb == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - s = natsConn_subscribe(sub, nc, subject, cb, cbClosure); - - return NATS_UPDATE_ERR_STACK(s); -} - -/* - * Similar to natsConnection_Subscribe() except that a timeout is given. - * If the subscription has not receive any message for the given timeout, - * the callback is invoked with a `NULL` message. The subscription can - * then be destroyed, if not, the callback will be invoked again when - * a message is received or the subscription times-out again. - */ -natsStatus -natsConnection_SubscribeTimeout(natsSubscription **sub, natsConnection *nc, const char *subject, - int64_t timeout, natsMsgHandler cb, void *cbClosure) -{ - natsStatus s; - - if ((cb == NULL) || (timeout <= 0)) - return nats_setDefaultError(NATS_INVALID_ARG); - - s = natsConn_subscribeWithTimeout(sub, nc, subject, timeout, cb, cbClosure); - - return NATS_UPDATE_ERR_STACK(s); -} - - -/* - * natsSubscribeSync is syntactic sugar for natsSubscribe(&sub, nc, subject, NULL). - */ -natsStatus -natsConnection_SubscribeSync(natsSubscription **sub, natsConnection *nc, const char *subject) -{ - natsStatus s; - - s = natsConn_subscribeSync(sub, nc, subject); - - return NATS_UPDATE_ERR_STACK(s); -} - -/* - * Creates an asynchronous queue subscriber on the given subject. - * All subscribers with the same queue name will form the queue group and - * only one member of the group will be selected to receive any given - * message asynchronously. - */ -natsStatus -natsConnection_QueueSubscribe(natsSubscription **sub, natsConnection *nc, - const char *subject, const char *queueGroup, - natsMsgHandler cb, void *cbClosure) -{ - natsStatus s; - - if ((queueGroup == NULL) || (strlen(queueGroup) == 0) || (cb == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - s = natsConn_queueSubscribe(sub, nc, subject, queueGroup, cb, cbClosure); - - return NATS_UPDATE_ERR_STACK(s); -} - -/* - * Similar to natsConnection_QueueSubscribe() except that a timeout is given. - * If the subscription has not receive any message for the given timeout, - * the callback is invoked with a `NULL` message. The subscription can - * then be destroyed, if not, the callback will be invoked again when - * a message is received or the subscription times-out again. - */ -natsStatus -natsConnection_QueueSubscribeTimeout(natsSubscription **sub, natsConnection *nc, - const char *subject, const char *queueGroup, - int64_t timeout, natsMsgHandler cb, void *cbClosure) -{ - natsStatus s; - - if ((queueGroup == NULL) || (strlen(queueGroup) == 0) || (cb == NULL) - || (timeout <= 0)) - { - return nats_setDefaultError(NATS_INVALID_ARG); - } - - s = natsConn_queueSubscribeWithTimeout(sub, nc, subject, queueGroup, timeout, cb, cbClosure); - - return NATS_UPDATE_ERR_STACK(s); -} - -/* - * Similar to natsQueueSubscribe except that the subscription is synchronous. - */ -natsStatus -natsConnection_QueueSubscribeSync(natsSubscription **sub, natsConnection *nc, - const char *subject, const char *queueGroup) -{ - natsStatus s; - - if ((queueGroup == NULL) || (strlen(queueGroup) == 0)) - return nats_setDefaultError(NATS_INVALID_ARG); - - s = natsConn_queueSubscribeSync(sub, nc, subject, queueGroup); - - return NATS_UPDATE_ERR_STACK(s); -} - -/* - * By default, messages that arrive are not immediately delivered. This - * generally improves performance. However, in case of request-reply, - * this delay has a negative impact. In such case, call this function - * to have the subscriber be notified immediately each time a message - * arrives. - * - * DEPRECATED - */ -natsStatus -natsSubscription_NoDeliveryDelay(natsSubscription *sub) -{ - if (sub == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - return NATS_OK; -} - -natsStatus -natsSub_nextMsg(natsMsg **nextMsg, natsSubscription *sub, int64_t timeout, bool pullSubInternal) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - natsMsg *msg = NULL; - bool removeSub = false; - int64_t target = 0; - jsSub *jsi = NULL; - char *fcReply = NULL; - - if ((sub == NULL) || (nextMsg == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsSub_Lock(sub); - - if (sub->connClosed) - { - natsSub_Unlock(sub); - - return nats_setDefaultError(NATS_CONNECTION_CLOSED); - } - if (sub->closed) - { - if ((sub->max > 0) && (sub->delivered >= sub->max)) - s = NATS_MAX_DELIVERED_MSGS; - else - s = NATS_INVALID_SUBSCRIPTION; - - natsSub_Unlock(sub); - - return nats_setDefaultError(s); - } - if (sub->msgCb != NULL) - { - natsSub_Unlock(sub); - - return nats_setDefaultError(NATS_ILLEGAL_STATE); - } - if (sub->slowConsumer) - { - sub->slowConsumer = false; - natsSub_Unlock(sub); - - return nats_setDefaultError(NATS_SLOW_CONSUMER); - } - if (sub->jsi != NULL) - { - if (sub->jsi->sm) - { - sub->jsi->sm = false; - natsSub_Unlock(sub); - - return nats_setError(NATS_MISMATCH, "%s", jsErrConsumerSeqMismatch); - } - else if (!pullSubInternal && sub->jsi->pull) - { - natsSub_Unlock(sub); - return nats_setError(NATS_INVALID_SUBSCRIPTION, "%s", jsErrNotApplicableToPullSub); - } - } - - nc = sub->conn; - jsi= sub->jsi; - - if (timeout > 0) - { - while ((sub->msgList.msgs == 0) - && (s != NATS_TIMEOUT) - && !(sub->closed) - && !(sub->draining)) - { - if (target == 0) - target = nats_setTargetTime(timeout); - - s = natsCondition_AbsoluteTimedWait(sub->cond, sub->mu, target); - if (s != NATS_OK) - s = nats_setDefaultError(s); - } - - if (sub->connClosed) - s = nats_setDefaultError(NATS_CONNECTION_CLOSED); - else if (sub->closed) - s = nats_setDefaultError(NATS_INVALID_SUBSCRIPTION); - } - else - { - s = (sub->msgList.msgs == 0 ? NATS_TIMEOUT : NATS_OK); - if ((s != NATS_OK) && !pullSubInternal) - s = nats_setDefaultError(s); - } - - if (s == NATS_OK) - { - msg = sub->msgList.head; - if ((msg == NULL) && sub->draining) - { - removeSub = true; - s = NATS_TIMEOUT; - } - else - { - sub->msgList.head = msg->next; - - if (sub->msgList.tail == msg) - sub->msgList.tail = NULL; - - sub->msgList.msgs--; - sub->msgList.bytes -= natsMsg_dataAndHdrLen(msg); - - msg->next = NULL; - - sub->delivered++; - fcReply = (jsi == NULL ? NULL : jsSub_checkForFlowControlResponse(sub)); - - if (sub->max > 0) - { - if (sub->delivered > sub->max) - s = nats_setDefaultError(NATS_MAX_DELIVERED_MSGS); - else if (sub->delivered == sub->max) - removeSub = true; - } - - if (sub->draining && (sub->msgList.msgs == 0)) - removeSub = true; - } - if (removeSub) - _retain(sub); - } - if ((s == NATS_OK) && natsMsg_IsNoResponders(msg)) - { - natsMsg_Destroy(msg); - s = NATS_NO_RESPONDERS; - } - else if (s == NATS_OK) - *nextMsg = msg; - - natsSub_Unlock(sub); - - if (fcReply != NULL) - { - natsConnection_Publish(nc, fcReply, NULL, 0); - NATS_FREE(fcReply); - } - - if (removeSub) - { - natsSub_setDrainCompleteState(sub); - natsConn_removeSubscription(nc, sub); - natsSub_release(sub); - } - - if (pullSubInternal && (s == NATS_TIMEOUT)) - return s; - - return NATS_UPDATE_ERR_STACK(s); -} - -/* - * Return the next message available to a synchronous subscriber or block until - * one is available. A timeout can be used to return when no message has been - * delivered. - */ -natsStatus -natsSubscription_NextMsg(natsMsg **nextMsg, natsSubscription *sub, int64_t timeout) -{ - natsStatus s = natsSub_nextMsg(nextMsg, sub, timeout, false); - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_unsubscribe(natsSubscription *sub, int max, bool drainMode, int64_t timeout) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - bool dc = false; - jsSub *jsi; - - if (sub == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsSub_Lock(sub); - nc = sub->conn; - _retain(sub); - - if ((jsi = sub->jsi) != NULL) - { - if (jsi->hbTimer != NULL) - natsTimer_Stop(jsi->hbTimer); - - dc = jsi->dc; - } - - natsSub_Unlock(sub); - - s = natsConn_unsubscribe(nc, sub, max, drainMode, timeout); - - // If user calls natsSubscription_Unsubscribe() and this - // is a JS subscription that is supposed to delete the JS - // consumer, do so now. - if ((s == NATS_OK) && (max == 0) && !drainMode && dc) - s = jsSub_deleteConsumer(sub); - - natsSub_release(sub); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsSubscription_Unsubscribe(natsSubscription *sub) -{ - natsStatus s = _unsubscribe(sub, 0, false, 0); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsSubscription_AutoUnsubscribe(natsSubscription *sub, int max) -{ - natsStatus s = _unsubscribe(sub, max, false, 0); - return NATS_UPDATE_ERR_STACK(s); -} - -void -natsSub_drain(natsSubscription *sub) -{ - natsSub_Lock(sub); - SUB_DLV_WORKER_LOCK(sub); - if (sub->closed) - { - SUB_DLV_WORKER_UNLOCK(sub); - natsSub_Unlock(sub); - return; - } - sub->draining = true; - if (sub->libDlvWorker != NULL) - { - // If this is a subscription with timeout, stop the timer. - if (sub->timeout != 0) - { - natsTimer_Stop(sub->timeoutTimer); - // Prevent code to reset this timer - sub->timeoutSuspended = true; - } - - // Set this to true. It will be set to false in the - // worker delivery thread when the control message is - // processed. - sub->libDlvDraining = true; - - // Post a control message to wake-up the worker which will - // ensure that all pending messages for this subscription - // are removed and the subscription will ultimately be - // released in the worker thread. - natsLib_msgDeliveryPostControlMsg(sub); - } - else - natsCondition_Broadcast(sub->cond); - SUB_DLV_WORKER_UNLOCK(sub); - natsSub_Unlock(sub); -} - -static void -_updateDrainStatus(natsSubscription *sub, natsStatus s) -{ - // Do not override a drain status if already set. - if (sub->drainStatus == NATS_OK) - sub->drainStatus = s; -} - -void -natsSub_updateDrainStatus(natsSubscription *sub, natsStatus s) -{ - natsSub_Lock(sub); - _updateDrainStatus(sub, s); - natsSub_Unlock(sub); -} - -// Mark the subscription such that connection stops to try to push messages into its list. -void -natsSub_setDrainSkip(natsSubscription *sub, natsStatus s) -{ - natsSub_Lock(sub); - SUB_DLV_WORKER_LOCK(sub); - _updateDrainStatus(sub, s); - sub->drainSkip = true; - SUB_DLV_WORKER_UNLOCK(sub); - natsSub_Unlock(sub); -} - -static void -_flushAndDrain(void *closure) -{ - natsSubscription *sub = (natsSubscription*) closure; - natsConnection *nc = NULL; - natsThread *t = NULL; - int64_t timeout = 0; - int64_t deadline = 0; - bool sync = false; - natsStatus s; - - natsSub_Lock(sub); - nc = sub->conn; - t = sub->drainThread; - timeout = sub->drainTimeout; - sync = (sub->msgCb == NULL ? true : false); - natsSub_Unlock(sub); - - // Make sure that negative value is considered no timeout. - if (timeout < 0) - timeout = 0; - else - deadline = nats_setTargetTime(timeout); - - // Flush to make sure server has processed UNSUB and no new messages are coming. - if (timeout == 0) - s = natsConnection_Flush(nc); - else - s = natsConnection_FlushTimeout(nc, timeout); - - // If flush failed, update drain status and prevent connection from - // pushing new messages to this subscription. - if (s != NATS_OK) - natsSub_setDrainSkip(sub, s); - - // Switch to drain regardless of status - natsSub_drain(sub); - - // We are going to check for completion only if a timeout is specified. - // If that is the case, the library will forcibly close the subscription. - if (timeout > 0) - { - // Reset status from possibly failed flush. We are now checking for - // the drain timeout. - s = NATS_OK; - // Wait for drain to complete or deadline is reached. - natsSub_Lock(sub); - // For sync subs, it is possible that we get here and users have - // already called NextMsg() for all pending messages before the sub - // was marked as "draining", so if we detect this situation, we need - // to switch status to complete here. - if (sync && !natsSub_drainComplete(sub) && (sub->msgList.msgs == 0)) - { - _setDrainCompleteState(sub); - } - else - { - while ((s != NATS_TIMEOUT) && !natsSub_drainComplete(sub)) - s = natsCondition_AbsoluteTimedWait(sub->cond, sub->mu, deadline); - } - natsSub_Unlock(sub); - - if (s != NATS_OK) - { - natsSub_updateDrainStatus(sub, s); - natsConn_removeSubscription(nc, sub); - } - } - - natsThread_Detach(t); - natsThread_Destroy(t); - natsSub_release(sub); -} - -// Switch subscription's drain state to "started". -void -natsSub_initDrain(natsSubscription *sub) -{ - natsSub_Lock(sub); - sub->drainState |= SUB_DRAIN_STARTED; - natsSub_Unlock(sub); -} - -// Initiates draining, unless already done. -// Note that this runs under the associated connection lock. -natsStatus -natsSub_startDrain(natsSubscription *sub, int64_t timeout) -{ - natsStatus s; - - if (testDrainAutoUnsubRace) - nats_Sleep(1); - - natsSub_Lock(sub); - if (natsSub_drainStarted(sub)) - { - natsSub_Unlock(sub); - return NATS_OK; - } - // Make sure that we just add to buffer but we don't flush it in place - // to make sure that this call will not block. - s = natsConn_enqueueUnsubProto(sub->conn, sub->sid); - if (s == NATS_OK) - s = natsThread_Create(&(sub->drainThread), _flushAndDrain, (void*) sub); - if (s == NATS_OK) - { - sub->drainTimeout = timeout; - sub->drainState |= SUB_DRAIN_STARTED; - _retain(sub); - } - natsSub_Unlock(sub); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsSubscription_Drain(natsSubscription *sub) -{ - natsStatus s; - - s = _unsubscribe(sub, 0, true, DEFAULT_DRAIN_TIMEOUT); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsSubscription_DrainTimeout(natsSubscription *sub, int64_t timeout) -{ - natsStatus s; - - s = _unsubscribe(sub, 0, true, timeout); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -natsSubscription_WaitForDrainCompletion(natsSubscription *sub, int64_t timeout) -{ - natsStatus s = NATS_OK; - int64_t deadline = 0; - bool dc = false; - - if (sub == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsSub_Lock(sub); - if (!natsSub_drainStarted(sub)) - { - natsSub_Unlock(sub); - return nats_setError(NATS_ILLEGAL_STATE, "%s", "Subscription not in draining mode"); - } - _retain(sub); - - dc = (sub->jsi != NULL ? sub->jsi->dc : false); - - if (timeout > 0) - deadline = nats_setTargetTime(timeout); - - while ((s != NATS_TIMEOUT) && !natsSub_drainComplete(sub)) - { - if (timeout > 0) - s = natsCondition_AbsoluteTimedWait(sub->cond, sub->mu, deadline); - else - natsCondition_Wait(sub->cond, sub->mu); - } - natsSub_Unlock(sub); - - if ((s == NATS_OK) && dc) - s = jsSub_deleteConsumer(sub); - - natsSub_release(sub); - - // Here, we return a status as a result, not as if there was something wrong - // with the execution of this function. So we do not update the error stack. - return s; -} - -natsStatus -natsSubscription_DrainCompletionStatus(natsSubscription *sub) -{ - natsStatus s; - - if (sub == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsSub_Lock(sub); - if (!natsSub_drainComplete(sub)) - s = NATS_ILLEGAL_STATE; - else - s = sub->drainStatus; - natsSub_Unlock(sub); - - return s; -} - -/* - * Returns the number of queued messages in the client for this subscription. - */ -natsStatus -natsSubscription_QueuedMsgs(natsSubscription *sub, uint64_t *queuedMsgs) -{ - natsStatus s; - int msgs = 0; - - if (queuedMsgs == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - s = natsSubscription_GetPending(sub, &msgs, NULL); - if (s == NATS_OK) - *queuedMsgs = (uint64_t) msgs; - - return s; -} - -int64_t -natsSubscription_GetID(natsSubscription* sub) -{ - int64_t id = 0; - - if (sub == NULL) - return 0; - - natsSub_Lock(sub); - - if (sub->closed) - { - natsSub_Unlock(sub); - return 0; - } - - id = sub->sid; - - natsSub_Unlock(sub); - - return id; -} - -const char* -natsSubscription_GetSubject(natsSubscription* sub) -{ - const char* subject = NULL; - - if (sub == NULL) - return NULL; - - natsSub_Lock(sub); - - if (sub->closed) - { - natsSub_Unlock(sub); - return NULL; - } - - subject = (const char*)sub->subject; - - natsSub_Unlock(sub); - - return subject; -} - -natsStatus -natsSubscription_GetPending(natsSubscription *sub, int *msgs, int *bytes) -{ - if (sub == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsSub_Lock(sub); - - if (sub->closed) - { - natsSub_Unlock(sub); - return nats_setDefaultError(NATS_INVALID_SUBSCRIPTION); - } - - SUB_DLV_WORKER_LOCK(sub); - - if (msgs != NULL) - *msgs = sub->msgList.msgs; - - if (bytes != NULL) - *bytes = sub->msgList.bytes; - - SUB_DLV_WORKER_UNLOCK(sub); - - natsSub_Unlock(sub); - - return NATS_OK; -} - -natsStatus -natsSubscription_SetPendingLimits(natsSubscription *sub, int msgLimit, int bytesLimit) -{ - if (sub == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - if ((msgLimit == 0) || (bytesLimit == 0)) - return nats_setError(NATS_INVALID_ARG, "%s", - "Limits must be either > 0 or negative to specify no limit"); - - natsSub_Lock(sub); - - if (sub->closed) - { - natsSub_Unlock(sub); - return nats_setDefaultError(NATS_INVALID_SUBSCRIPTION); - } - - SUB_DLV_WORKER_LOCK(sub); - - sub->msgsLimit = msgLimit; - sub->bytesLimit = bytesLimit; - - SUB_DLV_WORKER_UNLOCK(sub); - - natsSub_Unlock(sub); - - return NATS_OK; -} - -natsStatus -natsSubscription_GetPendingLimits(natsSubscription *sub, int *msgLimit, int *bytesLimit) -{ - if (sub == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsSub_Lock(sub); - - if (sub->closed) - { - natsSub_Unlock(sub); - return nats_setDefaultError(NATS_INVALID_SUBSCRIPTION); - } - - SUB_DLV_WORKER_LOCK(sub); - - if (msgLimit != NULL) - *msgLimit = sub->msgsLimit; - - if (bytesLimit != NULL) - *bytesLimit = sub->bytesLimit; - - SUB_DLV_WORKER_UNLOCK(sub); - - natsSub_Unlock(sub); - - return NATS_OK; -} - -natsStatus -natsSubscription_GetDelivered(natsSubscription *sub, int64_t *msgs) -{ - if ((sub == NULL) || (msgs == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsSub_Lock(sub); - - if (sub->closed) - { - natsSub_Unlock(sub); - return nats_setDefaultError(NATS_INVALID_SUBSCRIPTION); - } - - SUB_DLV_WORKER_LOCK(sub); - - *msgs = (int64_t) sub->delivered; - - SUB_DLV_WORKER_UNLOCK(sub); - - natsSub_Unlock(sub); - - return NATS_OK; -} - -natsStatus -natsSubscription_GetDropped(natsSubscription *sub, int64_t *msgs) -{ - if ((sub == NULL) || (msgs == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsSub_Lock(sub); - - if (sub->closed) - { - natsSub_Unlock(sub); - return nats_setDefaultError(NATS_INVALID_SUBSCRIPTION); - } - - SUB_DLV_WORKER_LOCK(sub); - - *msgs = sub->dropped; - - SUB_DLV_WORKER_UNLOCK(sub); - - natsSub_Unlock(sub); - - return NATS_OK; -} - -natsStatus -natsSubscription_GetMaxPending(natsSubscription *sub, int *msgs, int *bytes) -{ - if (sub == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsSub_Lock(sub); - - if (sub->closed) - { - natsSub_Unlock(sub); - return nats_setDefaultError(NATS_INVALID_SUBSCRIPTION); - } - - SUB_DLV_WORKER_LOCK(sub); - - if (msgs != NULL) - *msgs = sub->msgsMax; - - if (bytes != NULL) - *bytes = sub->bytesMax; - - SUB_DLV_WORKER_UNLOCK(sub); - - natsSub_Unlock(sub); - - return NATS_OK; -} - -natsStatus -natsSubscription_ClearMaxPending(natsSubscription *sub) -{ - if (sub == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsSub_Lock(sub); - - if (sub->closed) - { - natsSub_Unlock(sub); - return nats_setDefaultError(NATS_INVALID_SUBSCRIPTION); - } - - SUB_DLV_WORKER_LOCK(sub); - - sub->msgsMax = 0; - sub->bytesMax = 0; - - SUB_DLV_WORKER_UNLOCK(sub); - - natsSub_Unlock(sub); - - return NATS_OK; -} - -natsStatus -natsSubscription_GetStats(natsSubscription *sub, - int *pendingMsgs, - int *pendingBytes, - int *maxPendingMsgs, - int *maxPendingBytes, - int64_t *deliveredMsgs, - int64_t *droppedMsgs) -{ - if (sub == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsSub_Lock(sub); - - if (sub->closed) - { - natsSub_Unlock(sub); - return nats_setDefaultError(NATS_INVALID_SUBSCRIPTION); - } - - SUB_DLV_WORKER_LOCK(sub); - - if (pendingMsgs != NULL) - *pendingMsgs = sub->msgList.msgs; - - if (pendingBytes != NULL) - *pendingBytes = sub->msgList.bytes; - - if (maxPendingMsgs != NULL) - *maxPendingMsgs = sub->msgsMax; - - if (maxPendingBytes != NULL) - *maxPendingBytes = sub->bytesMax; - - if (deliveredMsgs != NULL) - *deliveredMsgs = (int) sub->delivered; - - if (droppedMsgs != NULL) - *droppedMsgs = sub->dropped; - - SUB_DLV_WORKER_UNLOCK(sub); - - natsSub_Unlock(sub); - - return NATS_OK; -} - -/* - * Returns a boolean indicating whether the subscription is still active. - * This will return false if the subscription has already been closed, - * or auto unsubscribed. - */ -bool -natsSubscription_IsValid(natsSubscription *sub) -{ - bool valid = false; - - if (sub == NULL) - return false; - - natsSub_Lock(sub); - - valid = !(sub->closed); - - natsSub_Unlock(sub); - - return valid; -} - -/* - * Destroys the subscription object, freeing up memory. - * If not already done, this call will removes interest on the subject. - */ -void -natsSubscription_Destroy(natsSubscription *sub) -{ - bool doUnsub = false; - - if (sub == NULL) - return; - - natsSub_Lock(sub); - - doUnsub = !(sub->closed); - // If not yet closed but user is closing from message callback but it - // happens that auto-unsub was used and the max number was delivered, then - // we can suppress the UNSUB protocol. - if (doUnsub && (sub->max > 0)) - doUnsub = sub->delivered < sub->max; - - // For a JetStream subscription, disable the "delete consumer" flag - // because we auto-delete only on explicit calls to unsub/drain. - if (sub->jsi != NULL) - sub->jsi->dc = false; - - natsSub_Unlock(sub); - - if (doUnsub) - (void) natsSubscription_Unsubscribe(sub); - - natsSub_release(sub); -} diff --git a/src/sub.h b/src/sub.h deleted file mode 100644 index 876fabe3b..000000000 --- a/src/sub.h +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2015-2021 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. - -#ifndef SUB_H_ -#define SUB_H_ - -#include "natsp.h" - -#ifdef DEV_MODE -// For type safety... - -void natsSub_Lock(natsSubscription *sub); -void natsSub_Unlock(natsSubscription *sub); - -#else - -#define natsSub_Lock(s) natsMutex_Lock((s)->mu) -#define natsSub_Unlock(s) natsMutex_Unlock((s)->mu) - -#endif // DEV_MODE - -#define SUB_DRAIN_STARTED ((uint8_t) 1) -#define SUB_DRAIN_COMPLETE ((uint8_t) 2) - -#define natsSub_drainStarted(s) (((s)->drainState & SUB_DRAIN_STARTED) != 0) -#define natsSub_drainComplete(s) (((s)->drainState & SUB_DRAIN_COMPLETE) != 0) - -extern bool testDrainAutoUnsubRace; - -void -natsSub_retain(natsSubscription *sub); - -void -natsSub_release(natsSubscription *sub); - -natsStatus -natsSub_create(natsSubscription **newSub, natsConnection *nc, const char *subj, - const char *queueGroup, int64_t timeout, natsMsgHandler cb, void *cbClosure, - bool noLibDlvPool, jsSub *jsi); - -bool -natsSub_setMax(natsSubscription *sub, uint64_t max); - -void -natsSub_initDrain(natsSubscription *sub); - -natsStatus -natsSub_startDrain(natsSubscription *sub, int64_t timeout); - -void -natsSub_setDrainCompleteState(natsSubscription *sub); - -void -natsSub_setDrainSkip(natsSubscription *sub, natsStatus s); - -void -natsSub_updateDrainStatus(natsSubscription *sub, natsStatus s); - -void -natsSub_drain(natsSubscription *sub); - -natsStatus -natsSub_nextMsg(natsMsg **nextMsg, natsSubscription *sub, int64_t timeout, bool pullSubInternal); - -void -natsSubAndLdw_Lock(natsSubscription *sub); - -void -natsSubAndLdw_Unlock(natsSubscription *sub); - -void -natsSubAndLdw_LockAndRetain(natsSubscription *sub); - -void -natsSubAndLdw_UnlockAndRelease(natsSubscription *sub); - -void -natsSub_close(natsSubscription *sub, bool connectionClosed); - -#endif /* SUB_H_ */ diff --git a/src/timer.c b/src/timer.c deleted file mode 100644 index 7a449456f..000000000 --- a/src/timer.c +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2015-2018 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" - -static void -_freeTimer(natsTimer *t) -{ - if (t == NULL) - return; - - natsMutex_Destroy(t->mu); - NATS_FREE(t); -} - -void -natsTimer_Release(natsTimer *t) -{ - int refs = 0; - - natsMutex_Lock(t->mu); - - refs = --(t->refs); - - natsMutex_Unlock(t->mu); - - if (refs == 0) - _freeTimer(t); -} - -natsStatus -natsTimer_Create(natsTimer **timer, natsTimerCb timerCb, natsTimerStopCb stopCb, - int64_t interval, void* closure) -{ - natsStatus s = NATS_OK; - natsTimer *t = (natsTimer*) NATS_CALLOC(1, sizeof(natsTimer)); - - if (t == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - t->refs = 1; - t->cb = timerCb; - t->stopCb = stopCb; - t->closure = closure; - - s = natsMutex_Create(&(t->mu)); - if (s == NATS_OK) - { - // Doing so, so that nats_resetTimer() does not try to remove the timer - // from the list (since it is new it would not be there!). - t->stopped = true; - - nats_resetTimer(t, interval); - - *timer = t; - } - else - _freeTimer(t); - - return NATS_UPDATE_ERR_STACK(s); -} - -void -natsTimer_Stop(natsTimer *timer) -{ - // Proxy for this call: - nats_stopTimer(timer); -} - -void -natsTimer_Reset(natsTimer *timer, int64_t interval) -{ - // Proxy for this call: - nats_resetTimer(timer, interval); -} - -void -natsTimer_Destroy(natsTimer *timer) -{ - if (timer == NULL) - return; - - nats_stopTimer(timer); - natsTimer_Release(timer); -} diff --git a/src/timer.h b/src/timer.h index db4464526..010e94413 100644 --- a/src/timer.h +++ b/src/timer.h @@ -16,9 +16,6 @@ #include -//#include "natsp.h" -#include "status.h" - struct __natsTimer; // Callback signature for timer @@ -33,7 +30,6 @@ typedef struct __natsTimer struct __natsTimer *prev; struct __natsTimer *next; - natsMutex *mu; int refs; natsTimerCb cb; diff --git a/src/unix/cond.c b/src/unix/cond.c deleted file mode 100644 index 2541ad9c3..000000000 --- a/src/unix/cond.c +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2015-2018 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 - -#include "../util.h" -#include "../mem.h" - -natsStatus -natsCondition_Create(natsCondition **cond) -{ - natsCondition *c = (natsCondition*) NATS_CALLOC(1, sizeof(natsCondition)); - natsStatus s = NATS_OK; - - if (c == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - if (pthread_cond_init(c, NULL) != 0) - s = nats_setError(NATS_SYS_ERROR, "pthread_cond_init error: %d", errno); - - if (s == NATS_OK) - *cond = c; - else - NATS_FREE(c); - - return s; -} - -void -natsCondition_Wait(natsCondition *cond, natsMutex *mutex) -{ - if (pthread_cond_wait(cond, mutex) != 0) - abort(); -} - -static natsStatus -_timedWait(natsCondition *cond, natsMutex *mutex, bool isAbsolute, int64_t timeout) -{ - int r; - struct timespec ts; - int64_t target; - - if (timeout <= 0) - return NATS_TIMEOUT; - - target = (isAbsolute ? timeout : nats_setTargetTime(timeout)); - - ts.tv_sec = target / 1000; - ts.tv_nsec = (target % 1000) * 1000000; - - if (ts.tv_nsec >= 1000000000L) - { - ts.tv_sec++; - ts.tv_nsec -= 1000000000L; - } - - r = pthread_cond_timedwait(cond, mutex, &ts); - - if (r == 0) - return NATS_OK; - - if (r == ETIMEDOUT) - return NATS_TIMEOUT; - - return nats_setError(NATS_SYS_ERROR, "pthread_cond_timedwait error: %d", errno); -} - -natsStatus -natsCondition_TimedWait(natsCondition *cond, natsMutex *mutex, int64_t timeout) -{ - return _timedWait(cond, mutex, false, timeout); -} - -natsStatus -natsCondition_AbsoluteTimedWait(natsCondition *cond, natsMutex *mutex, int64_t absoluteTime) -{ - return _timedWait(cond, mutex, true, absoluteTime); -} - -void -natsCondition_Signal(natsCondition *cond) -{ - if (pthread_cond_signal(cond) != 0) - abort(); -} - -void -natsCondition_Broadcast(natsCondition *cond) -{ - if (pthread_cond_broadcast(cond) != 0) - abort(); -} - -void -natsCondition_Destroy(natsCondition *cond) -{ - if (cond == NULL) - return; - - pthread_cond_destroy(cond); - NATS_FREE(cond); -} diff --git a/src/unix/mutex.c b/src/unix/mutex.c deleted file mode 100644 index ddc6a5664..000000000 --- a/src/unix/mutex.c +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2015-2018 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" - -natsStatus -natsMutex_Create(natsMutex **newMutex) -{ - natsStatus s = NATS_OK; - pthread_mutexattr_t attr; - natsMutex *m = NATS_CALLOC(1, sizeof(natsMutex)); - - if (m == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - if (pthread_mutexattr_init(&attr) != 0) - { - NATS_FREE(m); - return nats_setError(NATS_SYS_ERROR, "pthread_mutexattr_init error: %d", - errno); - } - - if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) - { - s = nats_setError(NATS_SYS_ERROR, "pthread_mutexattr_settype error: %d", - errno); - } - - if ((s == NATS_OK) - && (pthread_mutex_init(m, &attr) != 0)) - { - s = nats_setError(NATS_SYS_ERROR, "pthread_mutex_init error: %d", - errno); - } - - if (s == NATS_OK) - *newMutex = m; - else - { - NATS_FREE(m); - pthread_mutexattr_destroy(&attr); - } - - return s; -} - -bool -natsMutex_TryLock(natsMutex *m) -{ - if (pthread_mutex_trylock(m) == 0) - return true; - - return false; -} - -void -natsMutex_Lock(natsMutex *m) -{ -#if !defined(NATS_NO_SPIN) - if (gLockSpinCount > 0) - { - int64_t attempts = 0; - while (pthread_mutex_trylock(m) != 0) - { - if (++attempts <= gLockSpinCount) - { - #if defined(__x86_64__) || \ - defined(__mips__) - __asm__ __volatile__ ("pause" ::: "memory"); - #elif (defined(__arm__) && __ARM_ARCH >=6) || \ - defined(__aarch64__) - __asm__ __volatile__ ("yield" ::: "memory"); - #elif defined(__powerpc__) || \ - defined(__powerpc64__) - __asm__ __volatile__ ("or 27,27,27" ::: "memory"); - #else - usleep(0); - #endif - } - else - { - if (pthread_mutex_lock(m)) - abort(); - - break; - } - } - } - else -#endif - { - if (pthread_mutex_lock(m)) - abort(); - } -} - - -void -natsMutex_Unlock(natsMutex *m) -{ - if (pthread_mutex_unlock(m)) - abort(); -} - -void -natsMutex_Destroy(natsMutex *m) -{ - if (m == NULL) - return; - - pthread_mutex_destroy(m); - NATS_FREE(m); -} diff --git a/src/unix/sock.c b/src/unix/sock.c index 94e512c21..0714cd1df 100644 --- a/src/unix/sock.c +++ b/src/unix/sock.c @@ -12,11 +12,13 @@ // limitations under the License. #include "../natsp.h" + #include "../mem.h" +#include "../err.h" #include "../comsock.h" void -natsSys_Init(void) +nats_sysInit(void) { // Would do anything that needs to be initialized when // the library loads, specific to unix. @@ -25,7 +27,7 @@ natsSys_Init(void) natsStatus natsSock_WaitReady(int waitMode, natsSockCtx *ctx) { - natsDeadline *deadline = &(ctx->writeDeadline); + // natsDeadline *deadline = &(ctx->writeDeadline); struct pollfd pfd; int timeout = -1; int res; @@ -37,7 +39,7 @@ natsSock_WaitReady(int waitMode, natsSockCtx *ctx) switch (waitMode) { case WAIT_FOR_READ: - deadline = &(ctx->readDeadline); + // deadline = &(ctx->readDeadline); pfd.events = POLLIN; break; case WAIT_FOR_WRITE: @@ -48,8 +50,8 @@ natsSock_WaitReady(int waitMode, natsSockCtx *ctx) abort(); } - if (deadline != NULL) - timeout = natsDeadline_GetTimeout(deadline); + // if (deadline != NULL) + // timeout = natsDeadline_GetTimeout(deadline); res = poll(&pfd, 1, timeout); if (res == NATS_SOCK_ERROR) diff --git a/src/unix/thread.c b/src/unix/thread.c deleted file mode 100644 index 2f7773844..000000000 --- a/src/unix/thread.c +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright 2015-2018 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" - -bool -nats_InitOnce(natsInitOnceType *control, natsInitOnceCb cb) -{ - if (pthread_once(control, cb) != 0) - return false; - - return true; -} - -struct threadCtx -{ - natsThreadCb entry; - void *arg; -}; - -static void* -_threadStart(void *arg) -{ - struct threadCtx *c = (struct threadCtx*) arg; - - nats_setNATSThreadKey(); - - c->entry(c->arg); - - NATS_FREE(c); - - nats_ReleaseThreadMemory(); - natsLib_Release(); - - return NULL; -} - -natsStatus -natsThread_Create(natsThread **thread, natsThreadCb cb, void *arg) -{ - struct threadCtx *ctx = NULL; - natsThread *t = NULL; - natsStatus s = NATS_OK; - int err; - - natsLib_Retain(); - ctx = (struct threadCtx*) NATS_CALLOC(1, sizeof(*ctx)); - t = (natsThread*) NATS_CALLOC(1, sizeof(natsThread)); - - if ((ctx == NULL) || (t == NULL)) - s = nats_setDefaultError(NATS_NO_MEMORY); - - if (s == NATS_OK) - { - ctx->entry = cb; - ctx->arg = arg; - - err = pthread_create(t, NULL, _threadStart, ctx); - if (err) - s = nats_setError(NATS_SYS_ERROR, - "pthread_create error: %d", errno); - } - - if (s == NATS_OK) - { - *thread = t; - } - else - { - NATS_FREE(ctx); - NATS_FREE(t); - natsLib_Release(); - } - - return s; -} - -void -natsThread_Join(natsThread *t) -{ - // I think that 'join' should automatically detect if the call is made - // from the current thread. This simplify the use. That is, you don't - // need to do: - // if (!natsThread_IsCurrent(t)) - // natsThread_Join(t) - - if (!natsThread_IsCurrent(t)) - { - if (pthread_join(*t, NULL) != 0) - abort(); - } - else - { - pthread_detach(*t); - } -} - -void -natsThread_Detach(natsThread *t) -{ - if (pthread_detach(*t) !=0) - abort(); -} - -bool -natsThread_IsCurrent(natsThread *t) -{ - if (pthread_equal(pthread_self(), *t) == 0) - return false; - - return true; -} - -void -natsThread_Yield(void) -{ - sched_yield(); -} - -void -natsThread_Destroy(natsThread *t) -{ - if (t == NULL) - return; - - NATS_FREE(t); -} - -natsStatus -natsThreadLocal_CreateKey(natsThreadLocal *tl, void (*destructor)(void*)) -{ - int ret; - - if ((ret = pthread_key_create(tl, destructor)) != 0) - { - return nats_setError(NATS_SYS_ERROR, - "pthread_key_create error: %d", ret); - } - - return NATS_OK; -} - -void* -natsThreadLocal_Get(natsThreadLocal tl) -{ - return pthread_getspecific(tl); -} - -natsStatus -natsThreadLocal_SetEx(natsThreadLocal tl, const void *value, bool setErr) -{ - int ret; - - if ((ret = pthread_setspecific(tl, value)) != 0) - { - if (setErr) - return nats_setError(NATS_SYS_ERROR, - "pthread_setspecific: %d", - ret); - else - return NATS_SYS_ERROR; - } - - return NATS_OK; -} - -void -natsThreadLocal_DestroyKey(natsThreadLocal tl) -{ - pthread_key_delete(tl); -} - diff --git a/src/url.c b/src/url.c index ddcce91cc..5090f6bf8 100644 --- a/src/url.c +++ b/src/url.c @@ -12,102 +12,53 @@ // limitations under the License. #include "natsp.h" -#include "util.h" -#include -#include +#include "url.h" -#include "mem.h" - -void -natsUrl_Destroy(natsUrl *url) +bool natsUrl_IsLocalhost(natsUrl *url) { - if (url == NULL) - return; - - NATS_FREE(url->fullUrl); - NATS_FREE(url->host); - NATS_FREE(url->username); - NATS_FREE(url->password); - NATS_FREE(url); + return ((strcasecmp(url->host, "localhost") == 0) || + (strcasecmp(url->host, "127.0.0.1") == 0) || + (strcasecmp(url->host, "::1") == 0)); } static natsStatus _parsePort(int *port, const char *sport) { - natsStatus s = NATS_OK; - int64_t n = 0; + natsStatus s = NATS_OK; + int64_t n = 0; - n = nats_ParseInt64(sport, (int) strlen(sport)); + n = nats_ParseInt64(sport, strlen(sport)); if ((n < 0) || (n > INT32_MAX)) s = nats_setError(NATS_INVALID_ARG, "invalid port '%s'", sport); else - *port = (int) n; + *port = (int)n; return s; } -static natsStatus _decodeAndDup(char **decoded, const char *encoded) -{ - size_t len = strlen(encoded); - const char *p = encoded; - const char *e = encoded + len; - char *d; - - *decoded = NATS_MALLOC(len + 1); - if (*decoded == NULL) - { - return nats_setDefaultError(NATS_NO_MEMORY); - } - d = *decoded; - for (; p < e; p++) - { - if (*p != '%') - { - *d++ = *p; - continue; - } - - if (e - p < 3 || (!isxdigit(*(p + 1)) || !isxdigit(*(p + 2)))) - { - NATS_FREE(*decoded); - *decoded = NULL; - return nats_setError(NATS_ERR, "invalid percent encoding in URL: %s", encoded); - } - - char buf[3] = {p[1], p[2], '\0'}; - *d++ = (char)strtol(buf, NULL, 16); - p += 2; - } - *d = '\0'; - return NATS_OK; -} - natsStatus -natsUrl_Create(natsUrl **newUrl, const char *urlStr) +natsUrl_Create(natsUrl **newUrl, natsPool *pool, const char *urlStr) { - natsStatus s = NATS_OK; - char *copy = NULL; - char *ptr = NULL; - const char *scheme= NULL; - const char *user = NULL; - const char *pwd = NULL; - const char *host = NULL; - const char *port = NULL; - const char *path = NULL; - natsUrl *url = NULL; - - if (nats_IsStringEmpty(urlStr)) + natsStatus s = NATS_OK; + char *copy = NULL; + char *ptr = NULL; + const char *scheme = NULL; + const char *user = NULL; + const char *pwd = NULL; + const char *host = NULL; + const char *port = NULL; + const char *path = NULL; + natsUrl *url = NULL; + + if (nats_isCStringEmpty(urlStr)) return nats_setDefaultError(NATS_INVALID_ARG); - url = (natsUrl*) NATS_CALLOC(1, sizeof(natsUrl)); - if (url == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - s = nats_Trim(©, urlStr); + s = CHECK_NO_MEMORY(url = nats_palloc( pool, sizeof(natsUrl))); + IFOK(s, nats_Trim(©, pool, urlStr)); // Scheme - if (s == NATS_OK) + if (STILL_OK(s)) { ptr = strstr(copy, "://"); if (ptr == NULL) @@ -118,18 +69,18 @@ natsUrl_Create(natsUrl **newUrl, const char *urlStr) else { *ptr = '\0'; - scheme = (const char*) copy; + scheme = (const char *)copy; ptr += 3; } } // User info - if (s == NATS_OK) + if (STILL_OK(s)) { - char *sep = strrchr(ptr, '@'); + char *sep = strrchr(ptr, '@'); if (sep != NULL) { - host = (const char*) (sep+1); + host = (const char *)(sep + 1); *sep = '\0'; if (ptr != sep) @@ -139,41 +90,41 @@ natsUrl_Create(natsUrl **newUrl, const char *urlStr) { *sep = '\0'; if (sep != ptr) - user = (const char*) ptr; - if (sep+1 != host) - pwd = (const char*) (sep+1); + user = (const char *)ptr; + if (sep + 1 != host) + pwd = (const char *)(sep + 1); } else { - user = (const char*) ptr; + user = (const char *)ptr; } } } else { - host = (const char*) ptr; + host = (const char *)ptr; } } // Host - if (s == NATS_OK) + if (STILL_OK(s)) { // Search for end of IPv6 address (if applicable) ptr = strrchr(host, ']'); if (ptr == NULL) - ptr = (char*) host; + ptr = (char *)host; // From that point, search for the last ':' character ptr = strrchr(ptr, ':'); if (ptr != NULL) { *ptr = '\0'; - port = (const char*) (ptr+1); + port = (const char *)(ptr + 1); } - if (nats_IsStringEmpty(host)) + if (nats_isCStringEmpty(host)) host = "localhost"; } // Port - if (s == NATS_OK) + if (STILL_OK(s)) { if (port != NULL) { @@ -182,44 +133,55 @@ natsUrl_Create(natsUrl **newUrl, const char *urlStr) if (sep != NULL) { *sep = '\0'; - path = (const char*) (sep+1); + path = (const char *)(sep + 1); } } - if (nats_IsStringEmpty(port)) + if (nats_isCStringEmpty(port)) url->port = 4222; else s = _parsePort(&url->port, port); } // Assemble everything - if (s == NATS_OK) + if (STILL_OK(s)) { - const char *userval = (nats_IsStringEmpty(user) ? "" : user); - const char *usep = (nats_IsStringEmpty(pwd) ? "" : ":"); - const char *pwdval = (nats_IsStringEmpty(pwd) ? "" : pwd); - const char *hsep = (nats_IsStringEmpty(user) ? "" : "@"); - const char *pathsep = (nats_IsStringEmpty(path) ? "" : "/"); - const char *pathval = (nats_IsStringEmpty(path) ? "" : path); - - DUP_STRING(s, url->host, host); - - if (user != NULL) - IFOK(s, _decodeAndDup(&url->username, user)); - if (pwd != NULL) - IFOK(s, _decodeAndDup(&url->password, pwd)); - - if ((s == NATS_OK) && nats_asprintf(&url->fullUrl, "%s://%s%s%s%s%s:%d%s%s", - scheme, userval, usep, pwdval, hsep, host, url->port, pathsep, pathval) < 0) + const char *userval = (nats_isCStringEmpty(user) ? "" : user); + const char *usep = (nats_isCStringEmpty(pwd) ? "" : ":"); + const char *pwdval = (nats_isCStringEmpty(pwd) ? "" : pwd); + const char *hsep = (nats_isCStringEmpty(user) ? "" : "@"); + const char *pathsep = (nats_isCStringEmpty(path) ? "" : "/"); + const char *pathval = (nats_isCStringEmpty(path) ? "" : path); + + IFOK(s, ALWAYS_OK(url->host = nats_pstrdupC(pool, host))); + IFOK(s, ALWAYS_OK(url->username = nats_pstrdupC(pool, user))); + IFOK(s, ALWAYS_OK(url->password = nats_pstrdupC(pool, pwd))); + + if (STILL_OK(s)) { - s = nats_setDefaultError(NATS_NO_MEMORY); + size_t need = 0; + size_t cap = 0; + char *buf = NULL; + for (int i = 0; i < 2; i++) + { + if (need != 0) + { + buf = nats_palloc(pool, need); + if (buf == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + cap = need; + } + need = snprintf(buf, cap, + "%s://%s%s%s%s%s:%d%s%s", + scheme, userval, usep, pwdval, hsep, host, url->port, pathsep, pathval); + need++; // For the '\0' + } + url->fullUrl = buf; } + else + s = nats_setDefaultError(NATS_NO_MEMORY); } - NATS_FREE(copy); - - if (s == NATS_OK) + if (STILL_OK(s)) *newUrl = url; - else - natsUrl_Destroy(url); return NATS_UPDATE_ERR_STACK(s); } diff --git a/src/url.h b/src/url.h index dd47f8698..ddd7f6c85 100644 --- a/src/url.h +++ b/src/url.h @@ -14,8 +14,6 @@ #ifndef URL_H_ #define URL_H_ -#include "status.h" - typedef struct __natsUrl { char *fullUrl; @@ -27,9 +25,9 @@ typedef struct __natsUrl } natsUrl; natsStatus -natsUrl_Create(natsUrl **newUrl, const char *urlStr); +natsUrl_Create(natsUrl **newUrl, natsPool *pool, const char *urlStr); -void -natsUrl_Destroy(natsUrl *url); +bool +natsUrl_IsLocalhost(natsUrl *url); #endif /* SRC_URL_H_ */ diff --git a/src/util.c b/src/util.c index 4510b4bfe..63432f7de 100644 --- a/src/util.c +++ b/src/util.c @@ -13,108 +13,31 @@ #include "natsp.h" -#include #include #include #include #include - -#include "util.h" -#include "mem.h" - -int jsonMaxNested = JSON_MAX_NEXTED; - -// A long double memory size is larger (or equal to) u/int64_t, so use that -// as the maximum size of a num element in an array. -#define JSON_MAX_NUM_SIZE ((int) sizeof(long double)) - -// Forward declarations due to recursive calls -static natsStatus _jsonParse(nats_JSON **newJSON, int *parsedLen, const char *jsonStr, int jsonLen, int nested); -static natsStatus _jsonParseValue(char **str, nats_JSONField *field, int nested); -static void _jsonFreeArray(nats_JSONArray *arr, bool freeObj); - -#define JSON_GET_AS(jt, t) \ -natsStatus s = NATS_OK; \ -nats_JSONField *field = NULL; \ -s = nats_JSONGetField(json, fieldName, (jt), &field); \ -if ((s == NATS_OK) && (field == NULL)) \ -{ \ - *value = 0; \ - return NATS_OK; \ -} \ -else if (s == NATS_OK) \ -{ \ - switch (field->numTyp) \ - { \ - case TYPE_INT: \ - *value = (t)field->value.vint; break; \ - case TYPE_UINT: \ - *value = (t)field->value.vuint; break; \ - default: \ - *value = (t)field->value.vdec; \ - } \ -} \ -return NATS_UPDATE_ERR_STACK(s); - -#define JSON_ARRAY_AS(t) \ -int i; \ -t* values = (t*) NATS_CALLOC(arr->size, sizeof(t)); \ -if (values == NULL) \ - return nats_setDefaultError(NATS_NO_MEMORY); \ -for (i=0; isize; i++) \ - values[i] = ((t*) arr->values)[i]; \ -*array = values; \ -*arraySize = arr->size; \ -return NATS_OK; - -#define JSON_ARRAY_AS_NUM(t) \ -int i; \ -t* values = (t*) NATS_CALLOC(arr->size, sizeof(t)); \ -if (values == NULL) \ - return nats_setDefaultError(NATS_NO_MEMORY); \ -for (i=0; isize; i++) \ -{ \ - void *ptr = NULL; \ - ptr = (void*) ((char*)(arr->values)+(i*JSON_MAX_NUM_SIZE)); \ - values[i] = *(t*) ptr; \ -} \ -*array = values; \ -*arraySize = arr->size; \ -return NATS_OK; - -#define JSON_GET_ARRAY(t, f) \ -natsStatus s = NATS_OK; \ -nats_JSONField *field = NULL; \ -s = nats_JSONGetArrayField(json, fieldName, (t), &field); \ -if ((s == NATS_OK) && (field == NULL)) \ -{ \ - *array = NULL; \ - *arraySize = 0; \ - return NATS_OK; \ -} \ -else if (s == NATS_OK) \ - s = (f)(field->value.varr, array, arraySize); \ -return NATS_UPDATE_ERR_STACK(s); - +#include "conn.h" +#include "hash.h" #define ASCII_0 (48) #define ASCII_9 (57) static char base32DecodeMap[256]; -static const char *base64EncodeURL= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; -static const char *base64EncodeStd= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -static char base64Padding = '='; +// static const char *base64EncodeURL= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +// static const char *base64EncodeStd= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +// static char base64Padding = '='; -static int base64Ints[] = { - 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, - 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, - -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, - -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, - 42, 43, 44, 45, 46, 47, 48, 49, 50, 51}; +// static int base64Ints[] = { +// 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, +// 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, +// -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, +// 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, +// 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, +// -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, +// 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, +// 42, 43, 44, 45, 46, 47, 48, 49, 50, 51}; // An implementation of crc16 according to CCITT standards for XMODEM. @@ -185,7 +108,7 @@ nats_ParseInt64(const char *d, int dLen) } natsStatus -nats_Trim(char **pres, const char *s) +nats_Trim(char **pres, natsPool *pool, const char *s) { int len = 0; char *res = NULL; @@ -204,7 +127,7 @@ nats_Trim(char **pres, const char *s) len = (int) (ptr-start) + 1; // Allocate for copy (add 1 for terminating 0) - res = NATS_MALLOC(len+1); + res = nats_palloc(pool, len+1); if (res == NULL) return nats_setDefaultError(NATS_NO_MEMORY); @@ -215,117 +138,6 @@ nats_Trim(char **pres, const char *s) return NATS_OK; } -natsStatus -nats_ParseControl(natsControl *control, const char *line) -{ - natsStatus s = NATS_OK; - char *tok = NULL; - int len = 0; - - if ((line == NULL) || (line[0] == '\0')) - return nats_setDefaultError(NATS_PROTOCOL_ERROR); - - tok = strchr(line, (int) ' '); - if (tok == NULL) - { - control->op = NATS_STRDUP(line); - if (control->op == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - return NATS_OK; - } - - len = (int) (tok - line); - control->op = NATS_MALLOC(len + 1); - if (control->op == NULL) - { - s = nats_setDefaultError(NATS_NO_MEMORY); - } - else - { - memcpy(control->op, line, len); - control->op[len] = '\0'; - } - - if (s == NATS_OK) - { - // Discard all spaces and the like in between the next token - while ((tok[0] != '\0') - && ((tok[0] == ' ') - || (tok[0] == '\r') - || (tok[0] == '\n') - || (tok[0] == '\t'))) - { - tok++; - } - } - - // If there is a token... - if (tok[0] != '\0') - { - char *tmp; - - len = (int) strlen(tok); - tmp = &(tok[len - 1]); - - // Remove trailing spaces and the like. - while ((tmp[0] != '\0') - && ((tmp[0] == ' ') - || (tmp[0] == '\r') - || (tmp[0] == '\n') - || (tmp[0] == '\t'))) - { - tmp--; - len--; - } - - // We are sure that len is > 0 because of the first while() loop. - - control->args = NATS_MALLOC(len + 1); - if (control->args == NULL) - { - s = nats_setDefaultError(NATS_NO_MEMORY); - } - else - { - memcpy(control->args, tok, len); - control->args[len] = '\0'; - } - } - - if (s != NATS_OK) - { - NATS_FREE(control->op); - control->op = NULL; - - NATS_FREE(control->args); - control->args = NULL; - } - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -nats_CreateStringFromBuffer(char **newStr, natsBuffer *buf) -{ - char *str = NULL; - int len = 0; - - if ((buf == NULL) || ((len = natsBuf_Len(buf)) == 0)) - return NATS_OK; - - str = NATS_MALLOC(len + 1); - if (str == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - memcpy(str, natsBuf_Data(buf), len); - str[len] = '\0'; - - *newStr = str; - - return NATS_OK; -} - void nats_Sleep(int64_t millisec) { @@ -353,8 +165,8 @@ nats_NormalizeErr(char *error) int len = (int) strlen(error); int i; - if (strncmp(error, _ERR_OP_, _ERR_OP_LEN_) == 0) - start = _ERR_OP_LEN_; + if (natsString_equalC(&nats_ERR, error)) + start = nats_ERR.len; for (i=start; iname = fieldName; - field->typ = TYPE_NOT_SET; - - *newField = field; - - return NATS_OK; -} - -static void -_jsonFreeArray(nats_JSONArray *arr, bool freeObj) -{ - if (arr == NULL) - return; - - if ((arr->typ == TYPE_OBJECT) || (arr->typ == TYPE_ARRAY)) - { - int i; - - for (i=0; isize; i++) - { - if (arr->typ == TYPE_OBJECT) - { - nats_JSON *fjson = ((nats_JSON**)arr->values)[i]; - nats_JSONDestroy(fjson); - } - else - { - nats_JSONArray *farr = ((nats_JSONArray**)arr->values)[i]; - _jsonFreeArray(farr, true); - } - } - } - NATS_FREE(arr->values); - if (freeObj) - NATS_FREE(arr); -} -static void -_jsonFreeField(nats_JSONField *field) +natsStatus +nats_parseTime(char *orgStr, int64_t *timeUTC) { - if (field->typ == TYPE_ARRAY) - _jsonFreeArray(field->value.varr, true); - else if (field->typ == TYPE_OBJECT) - nats_JSONDestroy(field->value.vobj); - NATS_FREE(field); -} + natsStatus s = NATS_OK; + char *dotPos = NULL; + char utcOff[7] = {'\0'}; + int64_t nanosecs = 0; + char *p = NULL; + char timeStr[42] = {'\0'}; + char tmpStr[36] = {'\0'}; + char *str = NULL; + char offSign = '+'; + int offHours = 0; + int offMin = 0; + int i, l; + struct tm tp; -static char* -_jsonTrimSpace(char *ptr) -{ - while ((*ptr != '\0') - && ((*ptr == ' ') || (*ptr == '\t') || (*ptr == '\r') || (*ptr == '\n'))) + // Check for "0" + if (strcmp(orgStr, "0001-01-01T00:00:00Z") == 0) { - ptr += 1; + *timeUTC = 0; + return NATS_OK; } - return ptr; -} -static natsStatus -_decodeUni(char **iPtr, char *val) -{ - int res = 0; - char *i = *iPtr; - int j; - - if (strlen(i) < 5) - return NATS_ERR; - - i++; - for (j=0; j<4; j++) + l = (int) strlen(orgStr); + // The smallest date/time should be: "YYYY:MM:DDTHH:MM:SSZ", which is 20 + // while the longest should be: "YYYY:MM:DDTHH:MM:SS.123456789-12:34" which is 35 + if ((l < 20) || (l > (int) (sizeof(tmpStr) - 1))) { - char c = i[j]; - if ((c >= '0') && (c <= '9')) - c = c - '0'; - else if ((c >= 'a') && (c <= 'f')) - c = c - 'a' + 10; - else if ((c >= 'A') && (c <= 'F')) - c = c - 'A' + 10; + if (l < 20) + s = nats_setError(NATS_INVALID_ARG, "time '%s' too small", orgStr); else - return NATS_ERR; - - res = (res << 4) + c; - } - *val = (char) res; - *iPtr += 5; - - return NATS_OK; -} - -static natsStatus -_jsonGetStr(char **ptr, char **value) -{ - char *p = *ptr; - char *o = *ptr; - - while ((*p != '\0') && (*p != '"')) - { - if (*p != '\\') - { - if (o != p) - *o = *p; - o++; - p++; - continue; - } - p++; - // Escaped character here... - if (*p == '\0') - { - *o = '\0'; - return nats_setError(NATS_ERR, - "error parsing string '%s': invalid control character at the end", - o); - } - // based on what http://www.json.org/ says a string should be - switch (*p) - { - case 'b': *o++ = '\b'; break; - case 'f': *o++ = '\f'; break; - case 'n': *o++ = '\n'; break; - case 'r': *o++ = '\r'; break; - case 't': *o++ = '\t'; break; - case '"': - case '\\': - case '/': - *o++ = *p; - break; - case 'u': - { - char val = 0; - natsStatus s = _decodeUni(&p, &val); - if (s != NATS_OK) - { - return nats_setError(NATS_ERR, - "error parsing string '%s': invalid unicode character", - p); - } - *o++ = val; - p--; - break; - } - default: - return nats_setError(NATS_ERR, - "error parsing string '%s': invalid control character", - p); - } - p++; - } - - if (*p != '\0') - { - *o = '\0'; - *value = *ptr; - *ptr = (char*) (p + 1); - return NATS_OK; + s = nats_setError(NATS_INVALID_ARG, "time '%s' too long", orgStr); + return NATS_UPDATE_ERR_STACK(s); } - return nats_setError(NATS_ERR, - "error parsing string '%s': unexpected end of JSON input", - *ptr); -} - -static natsStatus -_jsonGetNum(char **ptr, nats_JSONField *field) -{ - char *p = *ptr; - bool expIsNegative = false; - uint64_t uintVal = 0; - uint64_t decVal = 0; - uint64_t decPower = 1; - long double sign = 1.0; - long double ePower = 1.0; - int decPCount = 0; - int numTyp = 0; - - while (isspace((unsigned char) *p)) - p++; - - sign = (*p == '-' ? -1.0 : 1.0); - - if ((*p == '-') || (*p == '+')) - p++; - while (isdigit((unsigned char) *p)) - uintVal = uintVal * 10 + (*p++ - '0'); + // Copy the user provided string in our temporary buffer since we may alter + // the string as we parse. + snprintf(tmpStr, sizeof(tmpStr), "%s", orgStr); + str = (char*) tmpStr; + memset(&tp, 0, sizeof(struct tm)); - if (*p == '.') + // If ends with 'Z', the time is already UTC + if ((str[l-1] == 'Z') || (str[l-1] == 'z')) { - p++; - numTyp = TYPE_DOUBLE; + // Set the timezone to "+00:00" + snprintf(utcOff, sizeof(utcOff), "%s", "+00:00"); + str[l-1] = '\0'; } - - while (isdigit((unsigned char) *p)) + else { - decVal = decVal * 10 + (*p++ - '0'); - decPower *= 10; - decPCount++; + // Make sure the UTC offset comes as "+12:34" (or "-12:34"). + p = str+l-6; + if ((strlen(p) != 6) || ((*p != '+') && (*p != '-')) || (*(p+3) != ':')) + { + s = nats_setError(NATS_INVALID_ARG, "time '%s' has invalid UTC offset", orgStr); + return NATS_UPDATE_ERR_STACK(s); + } + snprintf(utcOff, sizeof(utcOff), "%s", p); + // Set end of 'str' to beginning of the offset. + *p = '\0'; } - if ((*p == 'e') || (*p == 'E')) + // Check if there is below seconds precision + dotPos = strstr(str, "."); + if (dotPos != NULL) { - int64_t eVal = 0; - - numTyp = TYPE_DOUBLE; - - p++; - - expIsNegative = (*p == '-' ? true : false); - - if ((*p == '-') || (*p == '+')) - p++; + int64_t val = 0; - while (isdigit((unsigned char) *p)) - eVal = eVal * 10 + (*p++ - '0'); + p = (char*) (dotPos+1); + // Need to recompute the length, since it has changed. + l = (int) strlen(p); - if (expIsNegative) - { - if (decPower > 0) - ePower = (long double) decPower; - } - else + val = nats_ParseInt64((const char*) p, l); + if (val == -1) { - if (decPCount > eVal) - { - eVal = decPCount - eVal; - expIsNegative = true; - } - else - { - eVal -= decPCount; - } + s = nats_setError(NATS_INVALID_ARG, "time '%s' is invalid", orgStr); + return NATS_UPDATE_ERR_STACK(s); } - while (eVal != 0) + + for (i=0; i<9-l; i++) + val *= 10; + + if (val > 999999999) { - ePower *= 10; - eVal--; + s = nats_setError(NATS_INVALID_ARG, "time '%s' second fraction too big", orgStr); + return NATS_UPDATE_ERR_STACK(s); } - } - // If we don't end with a ' ', ',', ']', or '}', this is syntax error. - if ((*p != ' ') && (*p != ',') && (*p != '}') && (*p != ']')) - return nats_setError(NATS_ERR, - "error parsing number '%s': missing separator or unexpected end of JSON input", - *ptr); + nanosecs = val; + // Set end of string at the place of the '.' + *dotPos = '\0'; + } - if (numTyp == TYPE_DOUBLE) + snprintf(timeStr, sizeof(timeStr), "%s%s", str, utcOff); + if (sscanf(timeStr, "%4d-%2d-%2dT%2d:%2d:%2d%c%2d:%2d", + &tp.tm_year, &tp.tm_mon, &tp.tm_mday, &tp.tm_hour, &tp.tm_min, &tp.tm_sec, + &offSign, &offHours, &offMin) == 9) { - long double res = 0.0; - - if (decVal > 0) - res = sign * (long double) (uintVal * decPower + decVal); - else - res = sign * (long double) uintVal; + int64_t res = 0; + int64_t off = 0; - if (ePower > 1) - { - if (expIsNegative) - res /= ePower; - else - res *= ePower; - } - else if (decVal > 0) + tp.tm_year -= 1900; + tp.tm_mon--; + tp.tm_isdst = 0; +#ifdef _WIN32 + res = (int64_t) _mkgmtime64(&tp); +#else + res = (int64_t) timegm(&tp); +#endif + if (res == -1) { - res /= decPower; + s = nats_setError(NATS_ERR, "error parsing time '%s'", orgStr); + return NATS_UPDATE_ERR_STACK(s); } - field->value.vdec = res; - } - else if (sign < 0) - { - numTyp = TYPE_INT; - field->value.vint = -((int64_t) uintVal); - } - else - { - numTyp = TYPE_UINT; - field->value.vuint = uintVal; - } - *ptr = p; - field->numTyp = numTyp; - return NATS_OK; -} + // Compute the offset + off = (int64_t) ((offHours * 60 * 60) + (offMin * 60)); + // If UTC offset is positive, then we need to remove to get down to UTC time, + // where as if negative, we need to add the offset to get up to UTC time. + if (offSign == '+') + off *= (int64_t) -1; -static natsStatus -_jsonGetBool(char **ptr, bool *val) -{ - if (strncmp(*ptr, "true", 4) == 0) - { - *val = true; - *ptr += 4; - return NATS_OK; + res *= (int64_t) 1E9; + res += (off * (int64_t) 1E9); + res += nanosecs; + *timeUTC = res; } - else if (strncmp(*ptr, "false", 5) == 0) + else { - *val = false; - *ptr += 5; - return NATS_OK; + s = nats_setError(NATS_ERR, "error parsing time '%s'", orgStr); } - return nats_setError(NATS_ERR, - "error parsing boolean, got: '%s'", *ptr); + return NATS_UPDATE_ERR_STACK(s); } -static natsStatus -_jsonGetArray(char **ptr, nats_JSONArray **newArray, int nested) +void +nats_Base32_Init(void) { - natsStatus s = NATS_OK; - char *p = *ptr; - bool end = false; - int typ = TYPE_NOT_SET; - nats_JSONField field; - nats_JSONArray array; - - if (nested >= jsonMaxNested) - return nats_setError(NATS_ERR, "json reached maximum nested arrays of %d", jsonMaxNested); + const char *alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + int alphaLen = (int) strlen(alphabet); + int i; - // Initialize our stack variable - memset(&array, 0, sizeof(nats_JSONArray)); + for (i=0; i<(int)sizeof(base32DecodeMap); i++) + base32DecodeMap[i] = (char) 0xFF; - while ((s == NATS_OK) && (*p != '\0')) - { - p = _jsonTrimSpace(p); - - if ((typ == TYPE_NOT_SET) && (*p == ']')) - { - array.typ = TYPE_NULL; - end = true; - break; - } - - // Initialize the field before parsing. - memset(&field, 0, sizeof(nats_JSONField)); - - s = _jsonParseValue(&p, &field, nested); - if (s == NATS_OK) - { - if (typ == TYPE_NOT_SET) - { - typ = field.typ; - array.typ = field.typ; - - // Set the element size based on type. - switch (typ) - { - case TYPE_STR: array.eltSize = sizeof(char*); break; - case TYPE_BOOL: array.eltSize = sizeof(bool); break; - case TYPE_NUM: array.eltSize = JSON_MAX_NUM_SIZE; break; - case TYPE_OBJECT: array.eltSize = sizeof(nats_JSON*); break; - case TYPE_ARRAY: array.eltSize = sizeof(nats_JSONArray*); break; - default: - s = nats_setError(NATS_ERR, - "array of type %d not supported", typ); - } - } - else if (typ != field.typ) - { - s = nats_setError(NATS_ERR, - "array content of different types '%s'", - *ptr); - } - } - if (s != NATS_OK) - break; - - if (array.size + 1 > array.cap) - { - char **newValues = NULL; - int newCap = 2 * array.cap; - - if (newCap == 0) - newCap = 4; - - newValues = (char**) NATS_REALLOC(array.values, newCap * array.eltSize); - if (newValues == NULL) - { - s = nats_setDefaultError(NATS_NO_MEMORY); - break; - } - array.values = (void**) newValues; - array.cap = newCap; - } - // Set value based on type - switch (typ) - { - case TYPE_STR: - ((char**)array.values)[array.size++] = field.value.vstr; - break; - case TYPE_BOOL: - ((bool*)array.values)[array.size++] = field.value.vbool; - break; - case TYPE_NUM: - { - void *numPtr = NULL; - size_t sz = 0; - - switch (field.numTyp) - { - case TYPE_INT: - numPtr = &(field.value.vint); - sz = sizeof(int64_t); - break; - case TYPE_UINT: - numPtr = &(field.value.vuint); - sz = sizeof(uint64_t); - break; - default: - numPtr = &(field.value.vdec); - sz = sizeof(long double); - } - memcpy((void*)(((char *)array.values)+(array.size*array.eltSize)), numPtr, sz); - array.size++; - break; - } - case TYPE_OBJECT: - ((nats_JSON**)array.values)[array.size++] = field.value.vobj; - break; - case TYPE_ARRAY: - ((nats_JSONArray**)array.values)[array.size++] = field.value.varr; - break; - } - - p = _jsonTrimSpace(p); - if (*p == '\0') - break; - - if (*p == ']') - { - end = true; - break; - } - else if (*p == ',') - { - p += 1; - } - else - { - s = nats_setError(NATS_ERR, "expected ',' got '%s'", p); - } - } - if ((s == NATS_OK) && !end) - { - s = nats_setError(NATS_ERR, - "unexpected end of array: '%s'", - (*p != '\0' ? p : "NULL")); - } - if (s == NATS_OK) - { - *newArray = NATS_MALLOC(sizeof(nats_JSONArray)); - if (*newArray == NULL) - { - s = nats_setDefaultError(NATS_NO_MEMORY); - } - else - { - memcpy(*newArray, &array, sizeof(nats_JSONArray)); - *ptr = (char*) (p + 1); - } - } - if (s != NATS_OK) - _jsonFreeArray(&array, false); - - return NATS_UPDATE_ERR_STACK(s); -} - -#define JSON_STATE_START (0) -#define JSON_STATE_NO_FIELD_YET (1) -#define JSON_STATE_FIELD (2) -#define JSON_STATE_SEPARATOR (3) -#define JSON_STATE_VALUE (4) -#define JSON_STATE_NEXT_FIELD (5) -#define JSON_STATE_END (6) - -static natsStatus -_jsonParseValue(char **str, nats_JSONField *field, int nested) -{ - natsStatus s = NATS_OK; - char *ptr = *str; - - // Parsing value here. Determine the type based on first character. - if (*ptr == '"') - { - ptr += 1; - field->typ = TYPE_STR; - s = _jsonGetStr(&ptr, &field->value.vstr); - } - else if ((*ptr == 't') || (*ptr == 'f')) - { - field->typ = TYPE_BOOL; - s = _jsonGetBool(&ptr, &field->value.vbool); - } - else if (isdigit((unsigned char) *ptr) || (*ptr == '-')) - { - field->typ = TYPE_NUM; - s = _jsonGetNum(&ptr, field); - } - else if (*ptr == '[') - { - ptr += 1; - field->typ = TYPE_ARRAY; - s = _jsonGetArray(&ptr, &field->value.varr, nested+1); - } - else if (*ptr == '{') - { - nats_JSON *object = NULL; - int objLen = 0; - - ptr += 1; - field->typ = TYPE_OBJECT; - s = _jsonParse(&object, &objLen, ptr, -1, nested+1); - if (s == NATS_OK) - { - field->value.vobj = object; - ptr += objLen; - } - } - else if ((*ptr == 'n') && (strstr(ptr, "null") == ptr)) - { - ptr += 4; - field->typ = TYPE_NULL; - } - else - { - s = nats_setError(NATS_ERR, - "looking for value, got: '%s'", ptr); - } - if (s == NATS_OK) - *str = ptr; - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_jsonParse(nats_JSON **newJSON, int *parsedLen, const char *jsonStr, int jsonLen, int nested) -{ - natsStatus s = NATS_OK; - nats_JSON *json = NULL; - nats_JSONField *field = NULL; - void *oldField = NULL; - char *ptr; - char *fieldName = NULL; - int state; - char *copyStr = NULL; - bool breakLoop = false; - - if (parsedLen != NULL) - *parsedLen = 0; - - if (nested >= jsonMaxNested) - return nats_setError(NATS_ERR, "json reached maximum nested objects of %d", jsonMaxNested); - - if (jsonLen < 0) - { - if (jsonStr == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - jsonLen = (int) strlen(jsonStr); - } - - json = NATS_CALLOC(1, sizeof(nats_JSON)); - if (json == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - s = natsStrHash_Create(&(json->fields), 4); - if (s == NATS_OK) - { - json->str = NATS_MALLOC(jsonLen + 1); - if (json->str == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - - if (s == NATS_OK) - { - memcpy(json->str, jsonStr, jsonLen); - json->str[jsonLen] = '\0'; - } - } - if (s != NATS_OK) - { - nats_JSONDestroy(json); - return NATS_UPDATE_ERR_STACK(s); - } - - ptr = json->str; - copyStr = NATS_STRDUP(ptr); - if (copyStr == NULL) - { - nats_JSONDestroy(json); - return nats_setDefaultError(NATS_NO_MEMORY); - } - state = (nested == 0 ? JSON_STATE_START : JSON_STATE_NO_FIELD_YET); - - while ((s == NATS_OK) && (*ptr != '\0') && !breakLoop) - { - ptr = _jsonTrimSpace(ptr); - if (*ptr == '\0') - break; - switch (state) - { - case JSON_STATE_START: - { - // Should be the start of the JSON string - if (*ptr != '{') - { - s = nats_setError(NATS_ERR, "incorrect JSON string: '%s'", ptr); - break; - } - ptr += 1; - state = JSON_STATE_NO_FIELD_YET; - break; - } - case JSON_STATE_NO_FIELD_YET: - case JSON_STATE_FIELD: - { - // Check for end, which is valid only in state == JSON_STATE_NO_FIELD_YET - if (*ptr == '}') - { - if (state == JSON_STATE_NO_FIELD_YET) - { - ptr += 1; - state = JSON_STATE_END; - break; - } - s = nats_setError(NATS_ERR, - "expected beginning of field, got: '%s'", - ptr); - break; - } - // Check for - // Should be the first quote of a field name - if (*ptr != '"') - { - s = nats_setError(NATS_ERR, "missing quote: '%s'", ptr); - break; - } - ptr += 1; - s = _jsonGetStr(&ptr, &fieldName); - if (s != NATS_OK) - break; - s = _jsonCreateField(&field, fieldName); - if (s != NATS_OK) - { - NATS_UPDATE_ERR_STACK(s); - break; - } - s = natsStrHash_Set(json->fields, fieldName, false, (void*) field, &oldField); - if (s != NATS_OK) - { - NATS_UPDATE_ERR_STACK(s); - break; - } - if (oldField != NULL) - { - NATS_FREE(oldField); - oldField = NULL; - } - state = JSON_STATE_SEPARATOR; - break; - } - case JSON_STATE_SEPARATOR: - { - // Should be the separation between field name and value. - if (*ptr != ':') - { - s = nats_setError(NATS_ERR, "missing value for field '%s': '%s'", fieldName, ptr); - break; - } - ptr += 1; - state = JSON_STATE_VALUE; - break; - } - case JSON_STATE_VALUE: - { - s = _jsonParseValue(&ptr, field, nested); - if (s == NATS_OK) - state = JSON_STATE_NEXT_FIELD; - break; - } - case JSON_STATE_NEXT_FIELD: - { - // We should have a ',' separator or be at the end of the string - if ((*ptr != ',') && (*ptr != '}')) - { - s = nats_setError(NATS_ERR, "missing separator: '%s' (%s)", ptr, copyStr); - break; - } - if (*ptr == ',') - state = JSON_STATE_FIELD; - else - state = JSON_STATE_END; - ptr += 1; - break; - } - case JSON_STATE_END: - { - if (nested > 0) - { - breakLoop = true; - break; - } - // If we are here it means that there was a character after the '}' - // so that's considered a failure. - s = nats_setError(NATS_ERR, - "invalid characters after end of JSON: '%s'", - ptr); - break; - } - } - } - if (s == NATS_OK) - { - if (state != JSON_STATE_END) - s = nats_setError(NATS_ERR, "%s", "JSON string not properly closed"); - } - if (s == NATS_OK) - { - if (parsedLen != NULL) - *parsedLen = (int) (ptr - json->str); - *newJSON = json; - } - else - nats_JSONDestroy(json); - - NATS_FREE(copyStr); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -nats_JSONParse(nats_JSON **newJSON, const char *jsonStr, int jsonLen) -{ - natsStatus s = _jsonParse(newJSON, NULL, jsonStr, jsonLen, 0); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -nats_JSONGetField(nats_JSON *json, const char *fieldName, int fieldType, nats_JSONField **retField) -{ - nats_JSONField *field = NULL; - - field = (nats_JSONField*) natsStrHash_Get(json->fields, (char*) fieldName); - if ((field == NULL) || (field->typ == TYPE_NULL)) - { - *retField = NULL; - return NATS_OK; - } - - // Check parsed type matches what is being asked. - switch (fieldType) - { - case TYPE_INT: - case TYPE_UINT: - case TYPE_DOUBLE: - if (field->typ != TYPE_NUM) - return nats_setError(NATS_INVALID_ARG, - "Asked for field '%s' as type %d, but got type %d when parsing", - field->name, fieldType, field->typ); - break; - case TYPE_BOOL: - case TYPE_STR: - case TYPE_OBJECT: - if (field->typ != fieldType) - return nats_setError(NATS_INVALID_ARG, - "Asked for field '%s' as type %d, but got type %d when parsing", - field->name, fieldType, field->typ); - break; - default: - return nats_setError(NATS_INVALID_ARG, - "Asked for field '%s' as type %d, but this type does not exist", - field->name, fieldType); - } - *retField = field; - return NATS_OK; -} - -natsStatus -nats_JSONGetStr(nats_JSON *json, const char *fieldName, char **value) -{ - natsStatus s = NATS_OK; - nats_JSONField *field = NULL; - - s = nats_JSONGetField(json, fieldName, TYPE_STR, &field); - if (s == NATS_OK) - { - if ((field == NULL) || (field->value.vstr == NULL)) - { - *value = NULL; - return NATS_OK; - } - else - { - char *tmp = NATS_STRDUP(field->value.vstr); - if (tmp == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - *value = tmp; - } - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -nats_JSONGetStrPtr(nats_JSON *json, const char *fieldName, const char **str) -{ - natsStatus s; - nats_JSONField *field = NULL; - - s = nats_JSONGetField(json, fieldName, TYPE_STR, &field); - if (s == NATS_OK) - { - if (field == NULL) - *str = NULL; - else - *str = field->value.vstr; - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -nats_JSONGetBytes(nats_JSON *json, const char *fieldName, unsigned char **value, int *len) -{ - natsStatus s; - const char *str = NULL; - - *value = NULL; - *len = 0; - - s = nats_JSONGetStrPtr(json, fieldName, &str); - if ((s == NATS_OK) && (str != NULL)) - s = nats_Base64_Decode(str, value, len); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -nats_JSONGetInt(nats_JSON *json, const char *fieldName, int *value) -{ - JSON_GET_AS(TYPE_INT, int); -} - -natsStatus -nats_JSONGetInt32(nats_JSON *json, const char *fieldName, int32_t *value) -{ - JSON_GET_AS(TYPE_INT, int32_t); -} - -natsStatus -nats_JSONGetUInt16(nats_JSON *json, const char *fieldName, uint16_t *value) -{ - JSON_GET_AS(TYPE_UINT, uint16_t); -} - -natsStatus -nats_JSONGetBool(nats_JSON *json, const char *fieldName, bool *value) -{ - natsStatus s = NATS_OK; - nats_JSONField *field = NULL; - - s = nats_JSONGetField(json, fieldName, TYPE_BOOL, &field); - if (s == NATS_OK) - { - *value = (field == NULL ? false : field->value.vbool); - return NATS_OK; - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -nats_JSONGetLong(nats_JSON *json, const char *fieldName, int64_t *value) -{ - JSON_GET_AS(TYPE_INT, int64_t); -} - -natsStatus -nats_JSONGetULong(nats_JSON *json, const char *fieldName, uint64_t *value) -{ - JSON_GET_AS(TYPE_UINT, uint64_t); -} - -natsStatus -nats_JSONGetDouble(nats_JSON *json, const char *fieldName, long double *value) -{ - JSON_GET_AS(TYPE_DOUBLE, long double); -} - -natsStatus -nats_JSONGetObject(nats_JSON *json, const char *fieldName, nats_JSON **value) -{ - natsStatus s = NATS_OK; - nats_JSONField *field = NULL; - - s = nats_JSONGetField(json, fieldName, TYPE_OBJECT, &field); - if (s == NATS_OK) - { - *value = (field == NULL ? NULL : field->value.vobj); - return NATS_OK; - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -nats_parseTime(char *orgStr, int64_t *timeUTC) -{ - natsStatus s = NATS_OK; - char *dotPos = NULL; - char utcOff[7] = {'\0'}; - int64_t nanosecs = 0; - char *p = NULL; - char timeStr[42] = {'\0'}; - char tmpStr[36] = {'\0'}; - char *str = NULL; - char offSign = '+'; - int offHours = 0; - int offMin = 0; - int i, l; - struct tm tp; - - // Check for "0" - if (strcmp(orgStr, "0001-01-01T00:00:00Z") == 0) - { - *timeUTC = 0; - return NATS_OK; - } - - l = (int) strlen(orgStr); - // The smallest date/time should be: "YYYY:MM:DDTHH:MM:SSZ", which is 20 - // while the longest should be: "YYYY:MM:DDTHH:MM:SS.123456789-12:34" which is 35 - if ((l < 20) || (l > (int) (sizeof(tmpStr) - 1))) - { - if (l < 20) - s = nats_setError(NATS_INVALID_ARG, "time '%s' too small", orgStr); - else - s = nats_setError(NATS_INVALID_ARG, "time '%s' too long", orgStr); - return NATS_UPDATE_ERR_STACK(s); - } - - // Copy the user provided string in our temporary buffer since we may alter - // the string as we parse. - snprintf(tmpStr, sizeof(tmpStr), "%s", orgStr); - str = (char*) tmpStr; - memset(&tp, 0, sizeof(struct tm)); - - // If ends with 'Z', the time is already UTC - if ((str[l-1] == 'Z') || (str[l-1] == 'z')) - { - // Set the timezone to "+00:00" - snprintf(utcOff, sizeof(utcOff), "%s", "+00:00"); - str[l-1] = '\0'; - } - else - { - // Make sure the UTC offset comes as "+12:34" (or "-12:34"). - p = str+l-6; - if ((strlen(p) != 6) || ((*p != '+') && (*p != '-')) || (*(p+3) != ':')) - { - s = nats_setError(NATS_INVALID_ARG, "time '%s' has invalid UTC offset", orgStr); - return NATS_UPDATE_ERR_STACK(s); - } - snprintf(utcOff, sizeof(utcOff), "%s", p); - // Set end of 'str' to beginning of the offset. - *p = '\0'; - } - - // Check if there is below seconds precision - dotPos = strstr(str, "."); - if (dotPos != NULL) - { - int64_t val = 0; - - p = (char*) (dotPos+1); - // Need to recompute the length, since it has changed. - l = (int) strlen(p); - - val = nats_ParseInt64((const char*) p, l); - if (val == -1) - { - s = nats_setError(NATS_INVALID_ARG, "time '%s' is invalid", orgStr); - return NATS_UPDATE_ERR_STACK(s); - } - - for (i=0; i<9-l; i++) - val *= 10; - - if (val > 999999999) - { - s = nats_setError(NATS_INVALID_ARG, "time '%s' second fraction too big", orgStr); - return NATS_UPDATE_ERR_STACK(s); - } - - nanosecs = val; - // Set end of string at the place of the '.' - *dotPos = '\0'; - } - - snprintf(timeStr, sizeof(timeStr), "%s%s", str, utcOff); - if (sscanf(timeStr, "%4d-%2d-%2dT%2d:%2d:%2d%c%2d:%2d", - &tp.tm_year, &tp.tm_mon, &tp.tm_mday, &tp.tm_hour, &tp.tm_min, &tp.tm_sec, - &offSign, &offHours, &offMin) == 9) - { - int64_t res = 0; - int64_t off = 0; - - tp.tm_year -= 1900; - tp.tm_mon--; - tp.tm_isdst = 0; -#ifdef _WIN32 - res = (int64_t) _mkgmtime64(&tp); -#else - res = (int64_t) timegm(&tp); -#endif - if (res == -1) - { - s = nats_setError(NATS_ERR, "error parsing time '%s'", orgStr); - return NATS_UPDATE_ERR_STACK(s); - } - // Compute the offset - off = (int64_t) ((offHours * 60 * 60) + (offMin * 60)); - // If UTC offset is positive, then we need to remove to get down to UTC time, - // where as if negative, we need to add the offset to get up to UTC time. - if (offSign == '+') - off *= (int64_t) -1; - - res *= (int64_t) 1E9; - res += (off * (int64_t) 1E9); - res += nanosecs; - *timeUTC = res; - } - else - { - s = nats_setError(NATS_ERR, "error parsing time '%s'", orgStr); - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -nats_JSONGetTime(nats_JSON *json, const char *fieldName, int64_t *timeUTC) -{ - natsStatus s = NATS_OK; - char *str = NULL; - - s = nats_JSONGetStr(json, fieldName, &str); - if ((s == NATS_OK) && (str == NULL)) - { - *timeUTC = 0; - return NATS_OK; - } - else if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - s = nats_parseTime(str, timeUTC); - NATS_FREE(str); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -nats_JSONGetArrayField(nats_JSON *json, const char *fieldName, int fieldType, nats_JSONField **retField) -{ - nats_JSONField *field = NULL; - - field = (nats_JSONField*) natsStrHash_Get(json->fields, (char*) fieldName); - if ((field == NULL) || (field->typ == TYPE_NULL)) - { - *retField = NULL; - return NATS_OK; - } - - // Check parsed type matches what is being asked. - if (field->typ != TYPE_ARRAY) - return nats_setError(NATS_INVALID_ARG, - "Field '%s' is not an array, it has type: %d", - field->name, field->typ); - // If empty array, return NULL/OK - if (field->value.varr->typ == TYPE_NULL) - { - *retField = NULL; - return NATS_OK; - } - if (fieldType != field->value.varr->typ) - return nats_setError(NATS_INVALID_ARG, - "Asked for field '%s' as an array of type: %d, but it is an array of type: %d", - field->name, fieldType, field->typ); - - *retField = field; - return NATS_OK; -} - -natsStatus -nats_JSONArrayAsStrings(nats_JSONArray *arr, char ***array, int *arraySize) -{ - natsStatus s = NATS_OK; - int i; - - char **values = NATS_CALLOC(arr->size, arr->eltSize); - if (values == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - for (i=0; isize; i++) - { - values[i] = NATS_STRDUP((char*)(arr->values[i])); - if (values[i] == NULL) - { - s = nats_setDefaultError(NATS_NO_MEMORY); - break; - } - } - if (s != NATS_OK) - { - int j; - - for (j=0; jsize; - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -nats_JSONGetArrayStr(nats_JSON *json, const char *fieldName, char ***array, int *arraySize) -{ - JSON_GET_ARRAY(TYPE_STR, nats_JSONArrayAsStrings); -} - -natsStatus -nats_JSONArrayAsBools(nats_JSONArray *arr, bool **array, int *arraySize) -{ - JSON_ARRAY_AS(bool); -} - -natsStatus -nats_JSONGetArrayBool(nats_JSON *json, const char *fieldName, bool **array, int *arraySize) -{ - JSON_GET_ARRAY(TYPE_BOOL, nats_JSONArrayAsBools); -} - -natsStatus -nats_JSONArrayAsDoubles(nats_JSONArray *arr, long double **array, int *arraySize) -{ - JSON_ARRAY_AS_NUM(long double); -} - -natsStatus -nats_JSONGetArrayDouble(nats_JSON *json, const char *fieldName, long double **array, int *arraySize) -{ - JSON_GET_ARRAY(TYPE_NUM, nats_JSONArrayAsDoubles); -} - -natsStatus -nats_JSONArrayAsInts(nats_JSONArray *arr, int **array, int *arraySize) -{ - JSON_ARRAY_AS_NUM(int); -} - -natsStatus -nats_JSONGetArrayInt(nats_JSON *json, const char *fieldName, int **array, int *arraySize) -{ - JSON_GET_ARRAY(TYPE_NUM, nats_JSONArrayAsInts); -} - -natsStatus -nats_JSONArrayAsLongs(nats_JSONArray *arr, int64_t **array, int *arraySize) -{ - JSON_ARRAY_AS_NUM(int64_t); -} - -natsStatus -nats_JSONGetArrayLong(nats_JSON *json, const char *fieldName, int64_t **array, int *arraySize) -{ - JSON_GET_ARRAY(TYPE_NUM, nats_JSONArrayAsLongs); -} - -natsStatus -nats_JSONArrayAsULongs(nats_JSONArray *arr, uint64_t **array, int *arraySize) -{ - JSON_ARRAY_AS_NUM(uint64_t); -} - -natsStatus -nats_JSONGetArrayULong(nats_JSON *json, const char *fieldName, uint64_t **array, int *arraySize) -{ - JSON_GET_ARRAY(TYPE_NUM, nats_JSONArrayAsULongs); -} - -natsStatus -nats_JSONArrayAsObjects(nats_JSONArray *arr, nats_JSON ***array, int *arraySize) -{ - JSON_ARRAY_AS(nats_JSON*); -} - -natsStatus -nats_JSONGetArrayObject(nats_JSON *json, const char *fieldName, nats_JSON ***array, int *arraySize) -{ - JSON_GET_ARRAY(TYPE_OBJECT, nats_JSONArrayAsObjects); -} - -natsStatus -nats_JSONArrayAsArrays(nats_JSONArray *arr, nats_JSONArray ***array, int *arraySize) -{ - JSON_ARRAY_AS(nats_JSONArray*); -} - -natsStatus -nats_JSONGetArrayArray(nats_JSON *json, const char *fieldName, nats_JSONArray ***array, int *arraySize) -{ - JSON_GET_ARRAY(TYPE_ARRAY, nats_JSONArrayAsArrays); -} - -natsStatus -nats_JSONRange(nats_JSON *json, int expectedType, int expectedNumType, jsonRangeCB cb, void *userInfo) -{ - natsStrHashIter iter; - char *fname = NULL; - void *val = NULL; - natsStatus s = NATS_OK; - - natsStrHashIter_Init(&iter, json->fields); - while ((s == NATS_OK) && natsStrHashIter_Next(&iter, &fname, &val)) - { - nats_JSONField *f = (nats_JSONField*) val; - - if (f->typ != expectedType) - s = nats_setError(NATS_ERR, "field '%s': expected value type of %d, got %d", - f->name, expectedType, f->typ); - else if ((f->typ == TYPE_NUM) && (f->numTyp != expectedNumType)) - s = nats_setError(NATS_ERR, "field '%s': expected numeric type of %d, got %d", - f->name, expectedNumType, f->numTyp); - else - s = cb(userInfo, (const char*) f->name, f); - } - natsStrHashIter_Done(&iter); - return NATS_UPDATE_ERR_STACK(s); -} - -void -nats_JSONDestroy(nats_JSON *json) -{ - natsStrHashIter iter; - void *field = NULL; - - if (json == NULL) - return; - - natsStrHashIter_Init(&iter, json->fields); - while (natsStrHashIter_Next(&iter, NULL, &field)) - { - natsStrHashIter_RemoveCurrent(&iter); - _jsonFreeField((nats_JSONField*) field); - } - natsStrHash_Destroy(json->fields); - NATS_FREE(json->str); - NATS_FREE(json); -} - -natsStatus -nats_EncodeTimeUTC(char *buf, size_t bufLen, int64_t timeUTC) -{ - int64_t t = timeUTC / (int64_t) 1E9; - int64_t ns = timeUTC - ((int64_t) t * (int64_t) 1E9); - struct tm tp; - int n; - - // We will encode at most: "YYYY:MM:DDTHH:MM:SS.123456789+12:34" - // so we need at least 35+1 characters. - if (bufLen < 36) - return nats_setError(NATS_INVALID_ARG, - "buffer to encode UTC time is too small (%d), needs 36", - (int) bufLen); - - if (timeUTC == 0) - { - snprintf(buf, bufLen, "%s", "0001-01-01T00:00:00Z"); - return NATS_OK; - } - - memset(&tp, 0, sizeof(struct tm)); -#ifdef _WIN32 - _gmtime64_s(&tp, (const __time64_t*) &t); -#else - gmtime_r((const time_t*) &t, &tp); -#endif - n = (int) strftime(buf, bufLen, "%FT%T", &tp); - if (n == 0) - return nats_setDefaultError(NATS_ERR); - - if (ns > 0) - { - char nsBuf[15]; - int i, nd; - - nd = snprintf(nsBuf, sizeof(nsBuf), ".%" PRId64, ns); - for (; (nd > 0) && (nsBuf[nd-1] == '0'); ) - nd--; - - for (i=0; i 0) { char dbuf[8]; - int dLen = 8; - int j; - int needs; - - for (j=0; j<8; ) - { - int in; - - if (remaining == 0) - { - dLen = j; - done = true; - break; - } - - in = (int) *ptr; - ptr++; - remaining--; - - dbuf[j] = base32DecodeMap[in]; - // If invalid character, report the position but as the number of character - // since beginning, not array index. - if (dbuf[j] == (char) 0xFF) - return nats_setError(NATS_ERR, "base32: invalid data at location %d", srcLen - remaining); - j++; - } - - needs = 0; - switch (dLen) - { - case 8: needs = 5; break; - case 7: needs = 4; break; - case 5: needs = 3; break; - case 4: needs = 2; break; - case 2: needs = 1; break; - } - if (n+needs > dstMax) - return nats_setError(NATS_INSUFFICIENT_BUFFER, "based32: needs %d bytes, max is %d", n+needs, dstMax); - - if (dLen == 8) - dst[4] = dbuf[6]<<5 | dbuf[7]; - if (dLen >= 7) - dst[3] = dbuf[4]<<7 | dbuf[5]<<2 | dbuf[6]>>3; - if (dLen >= 5) - dst[2] = dbuf[3]<<4 | dbuf[4]>>1; - if (dLen >= 4) - dst[1] = dbuf[1]<<6 | dbuf[2]<<1 | dbuf[3]>>4; - if (dLen >= 2) - dst[0] = dbuf[0]<<3 | dbuf[1]>>2; - - n += needs; - - if (!done) - dst += 5; - } - - *dstLen = n; - - return NATS_OK; -} - -static natsStatus -_base64Encode(const char *map, bool padding, const unsigned char *src, int srcLen, char **pDest) -{ - char *dst = NULL; - int dstLen = 0; - int n; - int di = 0; - int si = 0; - int remain = 0; - uint32_t val = 0; - - *pDest = NULL; - - if ((src == NULL) || (srcLen == 0)) - return NATS_OK; - - n = srcLen; - if (padding) - dstLen = (n + 2) / 3 * 4; - else - dstLen = (n * 8 + 5) / 6; - dst = NATS_CALLOC(1, dstLen + 1); - if (dst == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - n = ((srcLen / 3) * 3); - for (si = 0; si < n; ) - { - // Convert 3x 8bit source bytes into 4 bytes - val = (uint32_t)(src[si+0])<<16 | (uint32_t)(src[si+1])<<8 | (uint32_t)(src[si+2]); - - dst[di+0] = map[val >> 18 & 0x3F]; - dst[di+1] = map[val >> 12 & 0x3F]; - dst[di+2] = map[val >> 6 & 0x3F]; - dst[di+3] = map[val & 0x3F]; - - si += 3; - di += 4; - } - - remain = srcLen - si; - if (remain == 0) - { - *pDest = dst; - return NATS_OK; - } - - // Add the remaining small block - val = (uint32_t)src[si+0] << 16; - if (remain == 2) - val |= (uint32_t)src[si+1] << 8; - - dst[di+0] = map[val >> 18 & 0x3F]; - dst[di+1] = map[val >> 12 & 0x3F]; - - if (remain == 2) - { - dst[di+2] = map[val >> 6 & 0x3F]; - if (padding) - dst[di+3] = base64Padding; - } - else if (padding && (remain == 1)) - { - dst[di+2] = base64Padding; - dst[di+3] = base64Padding; - } - - *pDest = dst; - - return NATS_OK; -} - -natsStatus -nats_Base64RawURL_EncodeString(const unsigned char *src, int srcLen, char **pDest) -{ - natsStatus s = _base64Encode(base64EncodeURL, false, src, srcLen, pDest); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -nats_Base64_Encode(const unsigned char *src, int srcLen, char **pDest) -{ - natsStatus s = _base64Encode(base64EncodeStd, true, src, srcLen, pDest); - return NATS_UPDATE_ERR_STACK(s); -} - -static bool -_base64IsValidChar(char c, bool paddingOk) -{ - if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') - || (c >= '0' && c <= '9') || c == '+' || c == '/') - { - return true; - } - if (c == base64Padding && paddingOk) - return true; - return false; -} + int dLen = 8; + int j; + int needs; -natsStatus -nats_Base64_DecodeLen(const char *src, int *srcLen, int *dstLen) -{ - int l; - int dl; - int i; + for (j=0; j<8; ) + { + int in; - if (nats_IsStringEmpty(src)) - return nats_setError(NATS_INVALID_ARG, "%s", "base64 content cannot be empty"); + if (remaining == 0) + { + dLen = j; + done = true; + break; + } - l = (int) strlen(src); - if (l % 4 != 0) - return nats_setError(NATS_INVALID_ARG, "invalid base64 length: %d", l); + in = (int) *ptr; + ptr++; + remaining--; - dl = l / 4 * 3; - for (i=0; i=l-2)) - return nats_setError(NATS_INVALID_ARG, "invalid base64 character: '%c'", src[i]); + dbuf[j] = base32DecodeMap[in]; + // If invalid character, report the position but as the number of character + // since beginning, not array index. + if (dbuf[j] == (char) 0xFF) + return nats_setError(NATS_ERR, "base32: invalid data at location %d", srcLen - remaining); + j++; + } - if (src[i] == base64Padding) - dl--; - } + needs = 0; + switch (dLen) + { + case 8: needs = 5; break; + case 7: needs = 4; break; + case 5: needs = 3; break; + case 4: needs = 2; break; + case 2: needs = 1; break; + } + if (n+needs > dstMax) + return nats_setError(NATS_INSUFFICIENT_BUFFER, "based32: needs %d bytes, max is %d", n+needs, dstMax); - *srcLen = l; - *dstLen = dl; - return NATS_OK; -} + if (dLen == 8) + dst[4] = dbuf[6]<<5 | dbuf[7]; + if (dLen >= 7) + dst[3] = dbuf[4]<<7 | dbuf[5]<<2 | dbuf[6]>>3; + if (dLen >= 5) + dst[2] = dbuf[3]<<4 | dbuf[4]>>1; + if (dLen >= 4) + dst[1] = dbuf[1]<<6 | dbuf[2]<<1 | dbuf[3]>>4; + if (dLen >= 2) + dst[0] = dbuf[0]<<3 | dbuf[1]>>2; -void -nats_Base64_DecodeInPlace(const char *src, int l, unsigned char *dst) -{ - int i, j, v; + n += needs; - for (i=0, j=0; i> 16) & 0xFF; - if (src[i+2] != base64Padding) - dst[j+1] = (v >> 8) & 0xFF; - if (src[i+3] != base64Padding) - dst[j+2] = v & 0xFF; + if (!done) + dst += 5; } -} - -natsStatus -nats_Base64_Decode(const char *src, unsigned char **dst, int *dstLen) -{ - natsStatus s; - int sl = 0; - *dst = NULL; - *dstLen = 0; + *dstLen = n; - s = nats_Base64_DecodeLen(src, &sl, dstLen); - if (s == NATS_OK) - { - *dst = (unsigned char*) NATS_MALLOC(*dstLen); - if (*dst == NULL) - { - *dstLen = 0; - return nats_setDefaultError(NATS_NO_MEMORY); - } - nats_Base64_DecodeInPlace(src, sl, *dst); - } - return NATS_UPDATE_ERR_STACK(s); + return NATS_OK; } +// static natsStatus +// _base64Encode(const char *map, bool padding, const unsigned char *src, int srcLen, char **pDest) +// { +// char *dst = NULL; +// int dstLen = 0; +// int n; +// int di = 0; +// int si = 0; +// int remain = 0; +// uint32_t val = 0; + +// *pDest = NULL; + +// if ((src == NULL) || (srcLen == 0)) +// return NATS_OK; + +// n = srcLen; +// if (padding) +// dstLen = (n + 2) / 3 * 4; +// else +// dstLen = (n * 8 + 5) / 6; +// dst = NATS_CALLOC(1, dstLen + 1); +// if (dst == NULL) +// return nats_setDefaultError(NATS_NO_MEMORY); + +// n = ((srcLen / 3) * 3); +// for (si = 0; si < n; ) +// { +// // Convert 3x 8bit source bytes into 4 bytes +// val = (uint32_t)(src[si+0])<<16 | (uint32_t)(src[si+1])<<8 | (uint32_t)(src[si+2]); + +// dst[di+0] = map[val >> 18 & 0x3F]; +// dst[di+1] = map[val >> 12 & 0x3F]; +// dst[di+2] = map[val >> 6 & 0x3F]; +// dst[di+3] = map[val & 0x3F]; + +// si += 3; +// di += 4; +// } + +// remain = srcLen - si; +// if (remain == 0) +// { +// *pDest = dst; +// return NATS_OK; +// } + +// // Add the remaining small block +// val = (uint32_t)src[si+0] << 16; +// if (remain == 2) +// val |= (uint32_t)src[si+1] << 8; + +// dst[di+0] = map[val >> 18 & 0x3F]; +// dst[di+1] = map[val >> 12 & 0x3F]; + +// if (remain == 2) +// { +// dst[di+2] = map[val >> 6 & 0x3F]; +// if (padding) +// dst[di+3] = base64Padding; +// } +// else if (padding && (remain == 1)) +// { +// dst[di+2] = base64Padding; +// dst[di+3] = base64Padding; +// } + +// *pDest = dst; + +// return NATS_OK; +// } + +// natsStatus +// nats_Base64RawURL_EncodeString(const unsigned char *src, int srcLen, char **pDest) +// { +// natsStatus s = _base64Encode(base64EncodeURL, false, src, srcLen, pDest); +// return NATS_UPDATE_ERR_STACK(s); +// } + +// natsStatus +// nats_Base64_Encode(const unsigned char *src, int srcLen, char **pDest) +// { +// natsStatus s = _base64Encode(base64EncodeStd, true, src, srcLen, pDest); +// return NATS_UPDATE_ERR_STACK(s); +// } + +// static bool +// _base64IsValidChar(char c, bool paddingOk) +// { +// if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') +// || (c >= '0' && c <= '9') || c == '+' || c == '/') +// { +// return true; +// } +// if (c == base64Padding && paddingOk) +// return true; +// return false; +// } + +// natsStatus +// nats_Base64_DecodeLen(const char *src, int *srcLen, int *dstLen) +// { +// int l; +// int dl; +// int i; + +// if (nats_isCStringEmpty(src)) +// return nats_setError(NATS_INVALID_ARG, "%s", "base64 content cannot be empty"); + +// l = (int) strlen(src); +// if (l % 4 != 0) +// return nats_setError(NATS_INVALID_ARG, "invalid base64 length: %d", l); + +// dl = l / 4 * 3; +// for (i=0; i=l-2)) +// return nats_setError(NATS_INVALID_ARG, "invalid base64 character: '%c'", src[i]); + +// if (src[i] == base64Padding) +// dl--; +// } + +// *srcLen = l; +// *dstLen = dl; +// return NATS_OK; +// } + +// void +// nats_Base64_DecodeInPlace(const char *src, int l, unsigned char *dst) +// { +// int i, j, v; + +// for (i=0, j=0; i> 16) & 0xFF; +// if (src[i+2] != base64Padding) +// dst[j+1] = (v >> 8) & 0xFF; +// if (src[i+3] != base64Padding) +// dst[j+2] = v & 0xFF; +// } +// } + +// natsStatus +// nats_Base64_Decode(const char *src, unsigned char **dst, int *dstLen) +// { +// natsStatus s; +// int sl = 0; + +// *dst = NULL; +// *dstLen = 0; + +// s = nats_Base64_DecodeLen(src, &sl, dstLen); +// if (STILL_OK(s)) +// { +// *dst = (unsigned char*) NATS_MALLOC(*dstLen); +// if (*dst == NULL) +// { +// *dstLen = 0; +// return nats_setDefaultError(NATS_NO_MEMORY); +// } +// nats_Base64_DecodeInPlace(src, sl, *dst); +// } +// return NATS_UPDATE_ERR_STACK(s); +// } + // Returns the 2-byte crc for the data provided. uint16_t nats_CRC16_Compute(unsigned char *data, int len) @@ -1978,60 +616,60 @@ nats_CRC16_Validate(unsigned char *data, int len, uint16_t expected) return crc == expected; } -natsStatus -nats_ReadFile(natsBuffer **buffer, int initBufSize, const char *fn) -{ - natsStatus s; - FILE *f = NULL; - natsBuffer *buf = NULL; - char *ptr = NULL; - int total = 0; - - if ((initBufSize <= 0) || nats_IsStringEmpty(fn)) - return nats_setDefaultError(NATS_INVALID_ARG); - - f = fopen(fn, "r"); - if (f == NULL) - return nats_setError(NATS_ERR, "error opening file '%s': %s", fn, strerror(errno)); - - s = natsBuf_Create(&buf, initBufSize); - if (s == NATS_OK) - ptr = natsBuf_Data(buf); - while (s == NATS_OK) - { - int r = (int) fread(ptr, 1, (size_t) natsBuf_Available(buf), f); - if (r == 0) - break; - - total += r; - natsBuf_MoveTo(buf, total); - if (natsBuf_Available(buf) == 0) - s = natsBuf_Expand(buf, natsBuf_Capacity(buf)*2); - if (s == NATS_OK) - ptr = natsBuf_Data(buf) + total; - } - - // Close file. If there was an error, do not report possible closing error - // as the actual error - if (s != NATS_OK) - fclose(f); - else if (fclose(f) != 0) - s = nats_setError(NATS_ERR, "error closing file '%s': '%s", fn, strerror(errno)); - - IFOK(s, natsBuf_AppendByte(buf, '\0')); - - if (s == NATS_OK) - { - *buffer = buf; - } - else if (buf != NULL) - { - memset(natsBuf_Data(buf), 0, natsBuf_Capacity(buf)); - natsBuf_Destroy(buf); - } - - return NATS_UPDATE_ERR_STACK(s); -} +// natsStatus +// nats_ReadFile(natsBuf **buffer, int initBufSize, const char *fn) +// { +// natsStatus s; +// FILE *f = NULL; +// natsBuf *buf = NULL; +// uint8_t *ptr = NULL; +// int total = 0; + +// if ((initBufSize <= 0) || nats_isCStringEmpty(fn)) +// return nats_setDefaultError(NATS_INVALID_ARG); + +// f = fopen(fn, "r"); +// if (f == NULL) +// return nats_setError(NATS_ERR, "error opening file '%s': %s", fn, strerror(errno)); + +// s = natsBuf_Create(&buf, initBufSize); +// if (STILL_OK(s)) +// ptr = natsBuf_data(buf); +// while (STILL_OK(s)) +// { +// int r = (int) fread(ptr, 1, natsBuf_Available(buf), f); +// if (r == 0) +// break; + +// total += r; +// natsBuf_MoveTo(buf, total); +// if (natsBuf_Available(buf) == 0) +// s = natsBuf_Expand(buf, natsBuf_Capacity(buf)*2); +// if (STILL_OK(s)) +// ptr = natsBuf_data(buf) + total; +// } + +// // Close file. If there was an error, do not report possible closing error +// // as the actual error +// if (s != NATS_OK) +// fclose(f); +// else if (fclose(f) != 0) +// s = nats_setError(NATS_ERR, "error closing file '%s': '%s", fn, strerror(errno)); + +// IFOK(s, natsBuf_addB(buf, '\0')); + +// if (STILL_OK(s)) +// { +// *buffer = buf; +// } +// else if (buf != NULL) +// { +// memset(natsBuf_data(buf), 0, natsBuf_Capacity(buf)); +// natsBuf_Destroy(buf); +// } + +// return NATS_UPDATE_ERR_STACK(s); +// } void nats_FreeAddrInfo(struct addrinfo *res) @@ -2118,276 +756,16 @@ _isLineAnHeader(const char *ptr) return false; } -natsStatus -nats_GetJWTOrSeed(char **val, const char *content, int item) -{ - natsStatus s = NATS_OK; - char *pch = NULL; - char *str = NULL; - char *saved = NULL; - int curItem = 0; - int orgLen = 0; - char *nt = NULL; - - // First, make a copy of the original content since - // we are going to call strtok on it, which alters it. - str = NATS_STRDUP(content); - if (str == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - orgLen = (int) strlen(str); - - pch = nats_strtok(str, "\n", &nt); - while (pch != NULL) - { - if (_isLineAnHeader(pch)) - { - // We got the start of the section. Save the next line - // as the possible returned value if the following line - // is a header too. - pch = nats_strtok(NULL, "\n", &nt); - saved = pch; - - while (pch != NULL) - { - pch = nats_strtok(NULL, "\n", &nt); - if (pch == NULL) - break; - - // We tolerate empty string(s). - if (*pch == '\0') - continue; - - break; - } - if (pch == NULL) - break; - - if (_isLineAnHeader(pch)) - { - // Is this the item we were looking for? - if (curItem == item) - { - // Return a copy of the saved line - *val = NATS_STRDUP(saved); - if (*val == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - - break; - } - else if (++curItem > 1) - { - break; - } - } - } - pch = nats_strtok(NULL, "\n", &nt); - } - - memset(str, 0, orgLen); - NATS_FREE(str); - - // Nothing was found, return NATS_NOT_FOUND but don't set the stack error. - if ((s == NATS_OK) && (*val == NULL)) - return NATS_NOT_FOUND; - - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_marshalLongVal(natsBuffer *buf, bool comma, const char *fieldName, bool l, int64_t lval, uint64_t uval) -{ - natsStatus s = NATS_OK; - char temp[32]; - const char *start = (comma ? ",\"" : "\""); - - if (l) - snprintf(temp, sizeof(temp), "%" PRId64, lval); - else - snprintf(temp, sizeof(temp), "%" PRIi64, uval); - - s = natsBuf_Append(buf, start, -1); - IFOK(s, natsBuf_Append(buf, fieldName, -1)); - IFOK(s, natsBuf_Append(buf, "\":", -1)); - IFOK(s, natsBuf_Append(buf, temp, -1)); - - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -nats_marshalLong(natsBuffer *buf, bool comma, const char *fieldName, int64_t lval) -{ - natsStatus s = _marshalLongVal(buf, comma, fieldName, true, lval, 0); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -nats_marshalULong(natsBuffer *buf, bool comma, const char *fieldName, uint64_t uval) -{ - natsStatus s = _marshalLongVal(buf, comma, fieldName, false, 0, uval); - return NATS_UPDATE_ERR_STACK(s); -} - -// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the -// tail of buf, omitting trailing zeros. It omits the decimal -// point too when the fraction is 0. It returns the index where the -// output bytes begin and the value v/10**prec. -static void -fmt_frac(char *buf, int w, uint64_t v, int prec, int *nw, uint64_t *nv) -{ - // Omit trailing zeros up to and including decimal point. - bool print = false; - int i; - int digit; - - for (i = 0; i < prec; i++) - { - digit = v % 10; - print = print || digit != 0; - if (print) - { - w--; - buf[w] = digit + '0'; - } - v /= 10; - } - if (print) - { - w--; - buf[w] = '.'; - } - *nw = w; - *nv = v; -} - -// fmtInt formats v into the tail of buf. -// It returns the index where the output begins. -static int -fmt_int(char *buf, int w, uint64_t v) -{ - if (v == 0) - { - w--; - buf[w] = '0'; - } - else - { - while (v > 0) - { - w--; - buf[w] = v % 10 + '0'; - v /= 10; - } - } - return w; -} - -natsStatus -nats_marshalDuration(natsBuffer *out_buf, bool comma, const char *field_name, int64_t d) -{ - // Largest time is 2540400h10m10.000000000s - char buf[32]; - int w = 32; - bool neg = d < 0; - uint64_t u = (uint64_t) (neg ? -d : d); - int prec; - natsStatus s = NATS_OK; - const char *start = (comma ? ",\"" : "\""); - - if (u < 1000000000) - { - // Special case: if duration is smaller than a second, - // use smaller units, like 1.2ms - w--; - buf[w] = 's'; - w--; - if (u == 0) - { - s = natsBuf_Append(out_buf, start, -1); - IFOK(s, natsBuf_Append(out_buf, field_name, -1)); - IFOK(s, natsBuf_Append(out_buf, "\":\"0s\"", -1)); - return NATS_UPDATE_ERR_STACK(s); - } - else if (u < 1000) - { - // print nanoseconds - prec = 0; - buf[w] = 'n'; - } - else if (u < 1000000) - { - // print microseconds - prec = 3; - // U+00B5 'µ' micro sign == 0xC2 0xB5 (in reverse?) - buf[w] = '\xB5'; - w--; // Need room for two bytes. - buf[w] = '\xC2'; - } - else - { - // print milliseconds - prec = 6; - buf[w] = 'm'; - } - fmt_frac(buf, w, u, prec, &w, &u); - w = fmt_int(buf, w, u); - } - else - { - w--; - buf[w] = 's'; - - fmt_frac(buf, w, u, 9, &w, &u); - - // u is now integer seconds - w = fmt_int(buf, w, u % 60); - u /= 60; - - // u is now integer minutes - if (u > 0) - { - w--; - buf[w] = 'm'; - w = fmt_int(buf, w, u % 60); - u /= 60; - - // u is now integer hours - // Stop at hours because days can be different lengths. - if (u > 0) - { - w--; - buf[w] = 'h'; - w = fmt_int(buf, w, u); - } - } - } - - if (neg) - { - w--; - buf[w] = '-'; - } - - s = natsBuf_Append(out_buf, start, -1); - IFOK(s, natsBuf_Append(out_buf, field_name, -1)); - IFOK(s, natsBuf_Append(out_buf, "\":\"", -1)); - IFOK(s, natsBuf_Append(out_buf, buf + w, sizeof(buf) - w)); - IFOK(s, natsBuf_Append(out_buf, "\"", -1)); - return NATS_UPDATE_ERR_STACK(s); -} - -bool nats_IsSubjectValid(const char *subject, bool wcAllowed) +bool nats_isSubjectValid(const uint8_t *subject, size_t len, bool wcAllowed) { int i = 0; - int len = 0; int lastDot = -1; char c; - if (nats_IsStringEmpty(subject)) + if (nats_isCStringEmpty((const char *)subject)) return false; - len = (int) strlen(subject); - for (i=0; ivalue.vstr); - if ((name == NULL) || (value == NULL)) - { - NATS_FREE(name); - NATS_FREE(value); - return nats_setDefaultError(NATS_NO_MEMORY); - } - - md->List[md->Count * 2] = name; - md->List[md->Count * 2 + 1] = value; - md->Count++; - return NATS_OK; -} - -natsStatus -nats_unmarshalMetadata(nats_JSON *json, const char *fieldName, natsMetadata *md) -{ - natsStatus s = NATS_OK; - nats_JSON *mdJSON = NULL; - int n; - - md->List = NULL; - md->Count = 0; - if (json == NULL) - return NATS_OK; - - s = nats_JSONGetObject(json, fieldName, &mdJSON); - if ((s != NATS_OK) || (mdJSON == NULL)) - return NATS_OK; - - n = natsStrHash_Count(mdJSON->fields); - md->List = NATS_CALLOC(n * 2, sizeof(char *)); - if (md->List == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - IFOK(s, nats_JSONRange(mdJSON, TYPE_STR, 0, _addMD, md)); - - return s; -} - -natsStatus -nats_cloneMetadata(natsMetadata *clone, natsMetadata md) -{ - natsStatus s = NATS_OK; - int i = 0; - int n; - char **list = NULL; - - clone->Count = 0; - clone->List = NULL; - if (md.Count == 0) - return NATS_OK; - - n = md.Count * 2; - list = NATS_CALLOC(n, sizeof(char *)); - if (list == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - - for (i = 0; (s == NATS_OK) && (i < n); i++) - { - list[i] = NATS_STRDUP(md.List[i]); - if (list[i] == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); - } - - if (s == NATS_OK) - { - clone->List = (const char **)list; - clone->Count = md.Count; - } - else - { - for (n = i, i = 0; i < n; i++) - NATS_FREE(list[i]); - NATS_FREE(list); - } - return s; -} - -void -nats_freeMetadata(natsMetadata *md) -{ - if (md == NULL) - return; - - for (int i = 0; i < md->Count * 2; i++) - { - NATS_FREE((char *)md->List[i]); - } - NATS_FREE((char*) md->List); - md->List = NULL; - md->Count = 0; -} - // allocates a sufficiently large buffer and formats the strings into it, as a // ["unencoded-string-0","unencoded-string-1",...]. For an empty array of // strings returns "[]". -natsStatus nats_formatStringArray(char **out, const char **strings, int count) -{ - natsStatus s = NATS_OK; - natsBuffer buf; - int len = 0; - int i; - - len++; // For the '[' - for (i = 0; i < count; i++) - { - len += 2; // For the quotes - if (i > 0) - len++; // For the ',' - if (strings[i] == NULL) - len += strlen("(null)"); - else - len += strlen(strings[i]); - } - len++; // For the ']' - len++; // For the '\0' - - s = natsBuf_Init(&buf, len); - - natsBuf_AppendByte(&buf, '['); - for (i = 0; (s == NATS_OK) && (i < count); i++) - { - if (i > 0) - { - IFOK(s, natsBuf_AppendByte(&buf, ',')); - } - IFOK(s, natsBuf_AppendByte(&buf, '"')); - if (strings[i] == NULL) - { - IFOK(s, natsBuf_Append(&buf, "(null)", -1)); - } - else - { - IFOK(s, natsBuf_Append(&buf, strings[i], -1)); - } - IFOK(s, natsBuf_AppendByte(&buf, '"')); - } - - IFOK(s, natsBuf_AppendByte(&buf, ']')); - IFOK(s, natsBuf_AppendByte(&buf, '\0')); +// natsStatus nats_formatStringArray(char **out, const char **strings, int count) +// { +// natsStatus s = NATS_OK; +// natsBuf buf; +// int len = 0; +// int i; + +// len++; // For the '[' +// for (i = 0; i < count; i++) +// { +// len += 2; // For the quotes +// if (i > 0) +// len++; // For the ',' +// if (strings[i] == NULL) +// len += strlen("(null)"); +// else +// len += strlen(strings[i]); +// } +// len++; // For the ']' +// len++; // For the '\0' + +// s = natsBuf_InitCalloc(&buf, len); + +// natsBuf_addB(&buf, '['); +// for (i = 0; (STILL_OK(s)) && (i < count); i++) +// { +// if (i > 0) +// { +// IFOK(s, natsBuf_addB(&buf, ',')); +// } +// IFOK(s, natsBuf_addB(&buf, '"')); +// if (strings[i] == NULL) +// { +// IFOK(s, natsBuf_addCString(&buf, "(null)")); +// } +// else +// { +// IFOK(s, natsBuf_addCString(&buf, strings[i])); +// } +// IFOK(s, natsBuf_addB(&buf, '"')); +// } + +// IFOK(s, natsBuf_addB(&buf, ']')); +// IFOK(s, natsBuf_addB(&buf, '\0')); - if (s != NATS_OK) - { - natsBuf_Cleanup(&buf); - return s; - } +// if (s != NATS_OK) +// { +// natsBuf_Destroy(&buf); +// return s; +// } + +// *out = (char*)natsBuf_data(&buf); +// return NATS_OK; +// } - *out = natsBuf_Data(&buf); - return NATS_OK; -} diff --git a/src/util.h b/src/util.h index 17e2d7bd2..3e84a6e78 100644 --- a/src/util.h +++ b/src/util.h @@ -15,77 +15,14 @@ #define UTIL_H_ #include "natsp.h" -#include "mem.h" - -#define JSON_MAX_NEXTED 100 - -extern int jsonMaxNested; - -#define TYPE_NOT_SET (0) -#define TYPE_STR (1) -#define TYPE_BOOL (2) -#define TYPE_NUM (3) -#define TYPE_INT (4) -#define TYPE_UINT (5) -#define TYPE_DOUBLE (6) -#define TYPE_ARRAY (7) -#define TYPE_OBJECT (8) -#define TYPE_NULL (9) - -typedef struct -{ - void **values; - int typ; - int eltSize; - int size; - int cap; - -} nats_JSONArray; - -typedef struct -{ - char *str; - natsStrHash *fields; -} nats_JSON; -typedef struct -{ - char *name; - int typ; - union - { - char *vstr; - bool vbool; - uint64_t vuint; - int64_t vint; - long double vdec; - nats_JSONArray *varr; - nats_JSON *vobj; - } value; - int numTyp; - -} nats_JSONField; - -typedef natsStatus (*jsonRangeCB)(void *userInfo, const char *fieldName, nats_JSONField *f); - -#define snprintf_truncate(d, szd, f, ...) if (snprintf((d), (szd), (f), __VA_ARGS__) >= (int) (szd)) { \ - int offset = (int) (szd) - 2; \ - if (offset > 0) (d)[offset--] = '.'; \ - if (offset > 0) (d)[offset--] = '.'; \ - if (offset > 0) (d)[offset--] = '.'; \ -} +#include "mem.h" int64_t nats_ParseInt64(const char *d, int dLen); natsStatus -nats_Trim(char **pres, const char *s); - -natsStatus -nats_ParseControl(natsControl *control, const char *line); - -natsStatus -nats_CreateStringFromBuffer(char **newStr, natsBuffer *buf); +nats_Trim(char **pres, natsPool *pool, const char *s); const char* nats_GetBoolStr(bool value); @@ -93,108 +30,6 @@ nats_GetBoolStr(bool value); void nats_NormalizeErr(char *error); -natsStatus -nats_JSONParse(nats_JSON **json, const char *str, int strLen); - -natsStatus -nats_JSONGetField(nats_JSON *json, const char *fieldName, int fieldType, nats_JSONField **retField); - -natsStatus -nats_JSONGetStr(nats_JSON *json, const char *fieldName, char **value); - -natsStatus -nats_JSONGetStrPtr(nats_JSON *json, const char *fieldName, const char **str); - -natsStatus -nats_JSONGetBytes(nats_JSON *json, const char *fieldName, unsigned char **value, int *len); - -natsStatus -nats_JSONGetInt(nats_JSON *json, const char *fieldName, int *value); - -natsStatus -nats_JSONGetInt32(nats_JSON *json, const char *fieldName, int32_t *value); - -natsStatus -nats_JSONGetUInt16(nats_JSON *json, const char *fieldName, uint16_t *value); - -natsStatus -nats_JSONGetBool(nats_JSON *json, const char *fieldName, bool *value); - -natsStatus -nats_JSONGetLong(nats_JSON *json, const char *fieldName, int64_t *value); - -natsStatus -nats_JSONGetULong(nats_JSON *json, const char *fieldName, uint64_t *value); - -natsStatus -nats_JSONGetDouble(nats_JSON *json, const char *fieldName, long double *value); - -natsStatus -nats_JSONGetObject(nats_JSON *json, const char *fieldName, nats_JSON **value); - -natsStatus -nats_JSONGetTime(nats_JSON *json, const char *fieldName, int64_t *timeUTC); - -natsStatus -nats_JSONGetArrayField(nats_JSON *json, const char *fieldName, int fieldType, nats_JSONField **retField); - -natsStatus -nats_JSONArrayAsStrings(nats_JSONArray *arr, char ***array, int *arraySize); - -natsStatus -nats_JSONGetArrayStr(nats_JSON *json, const char *fieldName, char ***array, int *arraySize); - -natsStatus -nats_JSONArrayAsBools(nats_JSONArray *arr, bool **array, int *arraySize); - -natsStatus -nats_JSONGetArrayBool(nats_JSON *json, const char *fieldName, bool **array, int *arraySize); - -natsStatus -nats_JSONArrayAsDoubles(nats_JSONArray *arr, long double **array, int *arraySize); - -natsStatus -nats_JSONGetArrayDouble(nats_JSON *json, const char *fieldName, long double **array, int *arraySize); - -natsStatus -nats_JSONArrayAsInts(nats_JSONArray *arr, int **array, int *arraySize); - -natsStatus -nats_JSONGetArrayInt(nats_JSON *json, const char *fieldName, int **array, int *arraySize); - -natsStatus -nats_JSONArrayAsLongs(nats_JSONArray *arr, int64_t **array, int *arraySize); - -natsStatus -nats_JSONGetArrayLong(nats_JSON *json, const char *fieldName, int64_t **array, int *arraySize); - -natsStatus -nats_JSONArrayAsULongs(nats_JSONArray *arr, uint64_t **array, int *arraySize); - -natsStatus -nats_JSONGetArrayULong(nats_JSON *json, const char *fieldName, uint64_t **array, int *arraySize); - -natsStatus -nats_JSONArrayAsObjects(nats_JSONArray *arr, nats_JSON ***array, int *arraySize); - -natsStatus -nats_JSONGetArrayObject(nats_JSON *json, const char *fieldName, nats_JSON ***array, int *arraySize); - -natsStatus -nats_JSONArrayAsArrays(nats_JSONArray *arr, nats_JSONArray ***array, int *arraySize); - -natsStatus -nats_JSONGetArrayArray(nats_JSON *json, const char *fieldName, nats_JSONArray ***array, int *arraySize); - -natsStatus -nats_JSONRange(nats_JSON *json, int expectedType, int expectedNumType, jsonRangeCB cb, void *userInfo); - -void -nats_JSONDestroy(nats_JSON *json); - -natsStatus -nats_EncodeTimeUTC(char *buf, size_t bufLen, int64_t timeUTC); - void nats_Base32_Init(void); @@ -223,45 +58,21 @@ bool nats_CRC16_Validate(unsigned char *data, int len, uint16_t expected); natsStatus -nats_ReadFile(natsBuffer **buffer, int initBufSize, const char *fn); +nats_ReadFile(natsBuf **buffer, int initBufSize, const char *fn); bool nats_HostIsIP(const char *host); -natsStatus -nats_GetJWTOrSeed(char **val, const char *content, int item); - void nats_FreeAddrInfo(struct addrinfo *res); -natsStatus -nats_marshalLong(natsBuffer *buf, bool comma, const char *fieldName, int64_t lval); - -natsStatus -nats_marshalULong(natsBuffer *buf, bool comma, const char *fieldName, uint64_t uval); - -natsStatus -nats_marshalDuration(natsBuffer *out_buf, bool comma, const char *field_name, int64_t d); - -natsStatus -nats_marshalMetadata(natsBuffer *buf, bool comma, const char *fieldName, natsMetadata md); - -natsStatus -nats_unmarshalMetadata(nats_JSON *json, const char *fieldName, natsMetadata *md); - -natsStatus -nats_cloneMetadata(natsMetadata *clone, natsMetadata md); - -void -nats_freeMetadata(natsMetadata *md); - bool -nats_IsSubjectValid(const char *subject, bool wcAllowed); +nats_isSubjectValid(const uint8_t *subject, size_t len, bool wcAllowed); natsStatus -nats_parseTime(char *str, int64_t *timeUTC); +nats_formatStringArray(char **out, const char **strings, int count); natsStatus -nats_formatStringArray(char **out, const char **strings, int count); +nats_parseTime(char *str, int64_t *timeUTC); #endif /* UTIL_H_ */ diff --git a/src/win/cond.c b/src/win/cond.c deleted file mode 100644 index 74b14649c..000000000 --- a/src/win/cond.c +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2015-2018 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" - -natsStatus -natsCondition_Create(natsCondition **cond) -{ - natsCondition *c = (natsCondition*) NATS_CALLOC(1, sizeof(natsCondition)); - natsStatus s = NATS_OK; - - if (c == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - InitializeConditionVariable(c); - *cond = c; - - return s; -} - -void -natsCondition_Wait(natsCondition *cond, natsMutex *mutex) -{ - if (SleepConditionVariableCS(cond, mutex, INFINITE) == 0) - abort(); -} - -natsStatus -natsCondition_TimedWait(natsCondition *cond, natsMutex *mutex, int64_t timeout) -{ - if (timeout <= 0) - return NATS_TIMEOUT; - - if (SleepConditionVariableCS(cond, mutex, (DWORD) timeout) == 0) - { - if (GetLastError() == ERROR_TIMEOUT) - return NATS_TIMEOUT; - - return nats_setError(NATS_SYS_ERROR, - "SleepConditionVariableCS error: %d", - GetLastError()); - } - - return NATS_OK; -} - -natsStatus -natsCondition_AbsoluteTimedWait(natsCondition *cond, natsMutex *mutex, int64_t absoluteTime) -{ - int64_t now = nats_Now();; - int64_t sleepTime = absoluteTime - now; - - if (sleepTime <= 0) - return NATS_TIMEOUT; - - if (SleepConditionVariableCS(cond, mutex, (DWORD) sleepTime) == 0) - { - if (GetLastError() == ERROR_TIMEOUT) - return NATS_TIMEOUT; - - return nats_setError(NATS_SYS_ERROR, - "SleepConditionVariableCS error: %d", - GetLastError()); - } - - return NATS_OK; -} - -void -natsCondition_Signal(natsCondition *cond) -{ - WakeConditionVariable(cond); -} - -void -natsCondition_Broadcast(natsCondition *cond) -{ - WakeAllConditionVariable(cond); -} - -void -natsCondition_Destroy(natsCondition *cond) -{ - if (cond == NULL) - return; - - NATS_FREE(cond); -} diff --git a/src/win/mutex.c b/src/win/mutex.c deleted file mode 100644 index 376f55e7e..000000000 --- a/src/win/mutex.c +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2015-2018 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" - -natsStatus -natsMutex_Create(natsMutex **newMutex) -{ - natsMutex *m = NATS_CALLOC(1, sizeof(natsMutex)); - - if (m == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - - if (gLockSpinCount > 0) - InitializeCriticalSectionAndSpinCount(m, (DWORD) gLockSpinCount); - else - InitializeCriticalSection(m); - *newMutex = m; - - return NATS_OK; -} - -bool -natsMutex_TryLock(natsMutex *m) -{ - if (TryEnterCriticalSection(m) == 0) - return false; - - return true; -} - -void -natsMutex_Lock(natsMutex *m) -{ - // If gLockSpinCount > 0, the mutex has been created as as spin lock, - // so we don't have to do anything special here. - EnterCriticalSection(m); -} - -void -natsMutex_Unlock(natsMutex *m) -{ - LeaveCriticalSection(m); -} - -void -natsMutex_Destroy(natsMutex *m) -{ - if (m == NULL) - return; - - DeleteCriticalSection(m); - NATS_FREE(m); -} diff --git a/src/win/sock.c b/src/win/sock.c index ea2845e07..fcef86b77 100644 --- a/src/win/sock.c +++ b/src/win/sock.c @@ -20,7 +20,7 @@ #include "../comsock.h" void -natsSys_Init(void) +nats_sysInit(void) { WSADATA wsaData; int errorno; diff --git a/src/win/strings.c b/src/win/strings.c index e39629acb..4fe6a21af 100644 --- a/src/win/strings.c +++ b/src/win/strings.c @@ -15,77 +15,6 @@ #include "../mem.h" -int -nats_asprintf(char **newStr, const char *fmt, ...) -{ - char tmp[256]; - char *str; - int n, size; - va_list ap; - - size = sizeof(tmp); - str = (char*) tmp; - - do - { - va_start(ap, fmt); - n = vsnprintf(str, size, fmt, ap); - va_end(ap); - - if ((n < 0) || (n >= size)) - { - // We failed, but we don't know how much we need, so start with - // doubling the size and see if it's better. - if (n < 0) - { - size *= 2; - } - else - { - // We know exactly how much we need. - size = (n + 1); - - // now set n to -1 so that we loop again. - n = -1; - } - - if (str != tmp) - { - char *realloced = NULL; - - realloced = NATS_REALLOC(str, size); - if (realloced == NULL) - { - NATS_FREE(str); - str = NULL; - } - else - { - str = realloced; - } - } - else - { - str = NATS_MALLOC(size); - } - } - } - while ((n < 0) && (str != NULL)); - - if (str != NULL) - { - if (str != tmp) - *newStr = str; - else - *newStr = NATS_STRDUP(str); - - if (*newStr == NULL) - n = -1; - } - - return n; -} - char* nats_strcasestr(const char *haystack, const char *needle) { diff --git a/src/win/thread.c b/src/win/thread.c deleted file mode 100644 index c85e828ae..000000000 --- a/src/win/thread.c +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2015-2018 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 - -#include "../mem.h" - -static BOOL CALLBACK -_initHandleFunction(PINIT_ONCE InitOnce, PVOID Parameter, PVOID *lpContext) -{ - natsInitOnceCb cb = (natsInitOnceCb) Parameter; - - (*cb)(); - - return TRUE; -} - -bool -nats_InitOnce(natsInitOnceType *control, natsInitOnceCb cb) -{ - BOOL bStatus; - - // Execute the initialization callback function - bStatus = InitOnceExecuteOnce(control, - _initHandleFunction, - (PVOID) cb, - NULL); - - // InitOnceExecuteOnce function succeeded. - if (bStatus) - return true; - - return false; -} - -struct threadCtx -{ - natsThreadCb entry; - void *arg; -}; - -static unsigned __stdcall _threadStart(void* arg) -{ - struct threadCtx *c = (struct threadCtx*) arg; - - nats_setNATSThreadKey(); - - c->entry(c->arg); - - NATS_FREE(c); - - nats_ReleaseThreadMemory(); - natsLib_Release(); - - return 0; -} - -natsStatus -natsThread_Create(natsThread **thread, natsThreadCb cb, void *arg) -{ - struct threadCtx *ctx = NULL; - natsThread *t = NULL; - natsStatus s = NATS_OK; - - natsLib_Retain(); - ctx = (struct threadCtx*) NATS_CALLOC(1, sizeof(*ctx)); - t = (natsThread*) NATS_CALLOC(1, sizeof(natsThread)); - - if ((ctx == NULL) || (t == NULL)) - s = nats_setDefaultError(NATS_NO_MEMORY); - - if (s == NATS_OK) - { - ctx->entry = cb; - ctx->arg = arg; - - t->t = (HANDLE) _beginthreadex(NULL, 0, _threadStart, ctx, 0, NULL); - if (t->t == NULL) - s = nats_setError(NATS_SYS_ERROR, - "_beginthreadex error: %d", - GetLastError()); - else - t->id = GetThreadId(t->t); - } - if (s == NATS_OK) - { - *thread = t; - } - else - { - NATS_FREE(ctx); - NATS_FREE(t); - natsLib_Release(); - } - - return s; -} - -void -natsThread_Join(natsThread *t) -{ - if (GetCurrentThreadId() != t->id) - { - if (WaitForSingleObject(t->t, INFINITE)) - abort(); - } -} - -void -natsThread_Detach(natsThread *t) -{ - // nothing for now. -} - -bool -natsThread_IsCurrent(natsThread *t) -{ - if (GetCurrentThreadId() == t->id) - return true; - - return false; -} - -void -natsThread_Yield() -{ - // The correct way would be to call the following function. - // However, it looks like the connection reconnect test - // is failing on Windows without a proper sleep (that is, - // even Sleep(0) does not help). -// SwitchToThread(); - nats_Sleep(1); -} - -void -natsThread_Destroy(natsThread *t) -{ - if (t == NULL) - return; - - CloseHandle(t->t); - - NATS_FREE(t); -} - -natsStatus -natsThreadLocal_CreateKey(natsThreadLocal *tl, void (*destructor)(void*)) -{ - if ((*tl = TlsAlloc()) == TLS_OUT_OF_INDEXES) - { - return nats_setError(NATS_SYS_ERROR, - "TlsAlloc error: %d", - GetLastError()); - } - - return NATS_OK; -} - -void* -natsThreadLocal_Get(natsThreadLocal tl) -{ - return (void*) TlsGetValue(tl); -} - -natsStatus -natsThreadLocal_SetEx(natsThreadLocal tl, const void *value, bool setErr) -{ - if (TlsSetValue(tl, (LPVOID) value) == 0) - { - if (setErr) - return nats_setError(NATS_SYS_ERROR, - "TlsSetValue error: %d", - GetLastError()); - else - return NATS_SYS_ERROR; - } - - return NATS_OK; -} - -void -natsThreadLocal_DestroyKey(natsThreadLocal tl) -{ - TlsFree(tl); -} - diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 58a4aa14f..7d5a671bd 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,3 +1,8 @@ +cmake_minimum_required(VERSION 3.10) + +# Uncomment to have the build process verbose +set(CMAKE_VERBOSE_MAKEFILE TRUE) + if(NOT BUILD_TESTING) return() endif() @@ -13,40 +18,30 @@ endif() # We need this to build the test program include_directories(${PROJECT_SOURCE_DIR}/src) -if(NATS_BUILD_WITH_TLS) - include_directories(${OPENSSL_INCLUDE_DIR}) -endif(NATS_BUILD_WITH_TLS) -if(NATS_BUILD_STREAMING) - include_directories(${NATS_PROTOBUF_INCLUDE_DIRS}) - include_directories(${PROJECT_SOURCE_DIR}/src/stan) -endif(NATS_BUILD_STREAMING) +file(GLOB TEST_SOURCES RELATIVE ${PROJECT_SOURCE_DIR}/test *.c) -# Build the test program -add_executable(testsuite test.c) +# Build the test program, link statically with the library. +add_executable(natstest ${TEST_SOURCES}) +target_link_libraries(natstest nats_static ${NATS_EXTRA_LIB}) -# Link statically with the library -target_link_libraries(testsuite nats_static ${NATS_EXTRA_LIB}) - -# Set the test index to 0 -set(testIndex 0) +set (LIST_FILE ${PROJECT_SOURCE_DIR}/test/all_tests.txt) # Read the file 'list.txt' to get all the test names -file(STRINGS list.txt listOfTestNames) +if(EXISTS ${LIST_FILE}) + file(STRINGS ${LIST_FILE} TEST_NAMES) +else() + set(TEST_NAMES) +endif() -# For each test name -foreach(name ${listOfTestNames}) +message("Test Names: ${TEST_NAMES}") - # Create a test and pass the index (start and end are the same) - # to the testsuite executable - add_test(NAME Test_${name} - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} - COMMAND testsuite ${testIndex} ${testIndex}) +foreach(name ${TEST_NAMES}) + # Remove the _tc() prefix + string(REGEX REPLACE "_tc\\(([^)]+)\\)" "\\1" TEST_NAME ${name}) + add_test(NAME Test_${TEST_NAME} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + COMMAND natstest ${TEST_NAME}) # Make sure the test passes - set_tests_properties(Test_${name} PROPERTIES PASS_REGULAR_EXPRESSION "ALL PASSED") - - # Bump the test index number - math(EXPR testIndex "${testIndex}+1") -endforeach() - - + set_tests_properties(Test_${TEST_NAME} PROPERTIES PASS_REGULAR_EXPRESSION "ALL PASSED") +endforeach() \ No newline at end of file diff --git a/src/stan/msg.h b/test/all_tests.h similarity index 71% rename from src/stan/msg.h rename to test/all_tests.h index ef85683aa..0d1ede5e2 100644 --- a/src/stan/msg.h +++ b/test/all_tests.h @@ -1,4 +1,4 @@ -// Copyright 2018 The NATS Authors +// Copyright 2015-2023 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 @@ -11,12 +11,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SMSG_H_ -#define SMSG_H_ +#ifdef _TEST_PROTO +#undef _tc +#define _tc(_n) void Test_##_n(void); +#endif -#include "stanp.h" +#ifdef _TEST_LIST +#undef _tc +#define _tc(_n) {#_n, Test_##_n}, +#endif -natsStatus -stanMsg_create(stanMsg **newMsg, stanSubscription *sub, Pb__MsgProto *pb); +#include "all_tests.txt" -#endif /* SMSG_H_ */ +#undef _tc diff --git a/test/all_tests.txt b/test/all_tests.txt new file mode 100644 index 000000000..0e70cb19a --- /dev/null +++ b/test/all_tests.txt @@ -0,0 +1,5 @@ +_tc(ConnWriteChain) +_tc(JSONStructure) +_tc(MemAlignment) +_tc(MemPoolAlloc) +_tc(MemPoolRecycle) diff --git a/test/certs/ca.pem b/test/certs/ca.pem deleted file mode 100644 index 911c486c1..000000000 --- a/test/certs/ca.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEkDCCA3igAwIBAgIUSZwW7btc9EUbrMWtjHpbM0C2bSEwDQYJKoZIhvcNAQEL -BQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNVBAoM -B1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmljYXRl -IEF1dGhvcml0eSAyMDIyLTA4LTI3MB4XDTIyMDgyNzIwMjMwMloXDTMyMDgyNDIw -MjMwMlowcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNV -BAoMB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmlj -YXRlIEF1dGhvcml0eSAyMDIyLTA4LTI3MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAqilVqyY8rmCpTwAsLF7DEtWEq37KbljBWVjmlp2Wo6TgMd3b537t -6iO8+SbI8KH75i63RcxV3Uzt1/L9Yb6enDXF52A/U5ugmDhaa+Vsoo2HBTbCczmp -qndp7znllQqn7wNLv6aGSvaeIUeYS5Dmlh3kt7Vqbn4YRANkOUTDYGSpMv7jYKSu -1ee05Rco3H674zdwToYto8L8V7nVMrky42qZnGrJTaze+Cm9tmaIyHCwUq362CxS -dkmaEuWx11MOIFZvL80n7ci6pveDxe5MIfwMC3/oGn7mbsSqidPMcTtjw6ey5NEu -Z0UrC/2lL1FtF4gnVMKUSaEhU2oKjj0ZAQIDAQABo4IBHjCCARowHQYDVR0OBBYE -FP7Pfz4u7sSt6ltviEVsx4hIFIs6MIGuBgNVHSMEgaYwgaOAFP7Pfz4u7sSt6ltv -iEVsx4hIFIs6oXWkczBxMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5p -YTEQMA4GA1UECgwHU3luYWRpYTEQMA4GA1UECwwHbmF0cy5pbzEpMCcGA1UEAwwg -Q2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMjItMDgtMjeCFEmcFu27XPRFG6zFrYx6 -WzNAtm0hMAwGA1UdEwQFMAMBAf8wOgYJYIZIAYb4QgENBC0WK25hdHMuaW8gbmF0 -cy1zZXJ2ZXIgdGVzdC1zdWl0ZSB0cmFuc2llbnQgQ0EwDQYJKoZIhvcNAQELBQAD -ggEBAHDCHLQklYZlnzHDaSwxgGSiPUrCf2zhk2DNIYSDyBgdzrIapmaVYQRrCBtA -j/4jVFesgw5WDoe4TKsyha0QeVwJDIN8qg2pvpbmD8nOtLApfl0P966vcucxDwqO -dQWrIgNsaUdHdwdo0OfvAlTfG0v/y2X0kbL7h/el5W9kWpxM/rfbX4IHseZL2sLq -FH69SN3FhMbdIm1ldrcLBQVz8vJAGI+6B9hSSFQWljssE0JfAX+8VW/foJgMSx7A -vBTq58rLkAko56Jlzqh/4QT+ckayg9I73v1Q5/44jP1mHw35s5ZrzpDQt2sVv4l5 -lwRPJFXMwe64flUs9sM+/vqJaIY= ------END CERTIFICATE----- diff --git a/test/certs/client-cert.pem b/test/certs/client-cert.pem deleted file mode 100644 index 1c748f086..000000000 --- a/test/certs/client-cert.pem +++ /dev/null @@ -1,99 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 38:4c:16:24:9b:04:1c:b3:db:e0:4c:3c:ed:b7:40:7d:68:b5:fa:1f - Signature Algorithm: sha256WithRSAEncryption - Issuer: C=US, ST=California, O=Synadia, OU=nats.io, CN=Certificate Authority 2022-08-27 - Validity - Not Before: Aug 27 20:23:02 2022 GMT - Not After : Aug 24 20:23:02 2032 GMT - Subject: C=US, ST=California, O=Synadia, OU=nats.io, CN=localhost - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:ac:9c:3e:9d:3b:7a:12:56:85:78:ca:df:9c:fc: - 0c:7e:5e:f2:4f:22:33:46:81:38:53:d7:a7:25:8f: - d7:ee:16:13:e2:67:49:88:f6:94:99:f0:a9:a6:db: - fe:7a:17:c9:e3:df:31:73:71:38:70:3a:96:1e:99: - 7b:5d:07:e3:63:e4:e8:bf:99:f7:3d:5c:27:f5:b7: - 37:29:da:ee:82:80:00:d4:c8:d3:1b:36:0d:8b:d3: - 8a:9b:8e:12:a1:4d:0c:c5:22:f8:56:3b:6a:1a:fb: - e9:3d:08:1e:13:7f:55:6e:2e:65:93:9a:90:54:03: - 6d:0d:e6:44:d6:f7:c0:d7:d8:e1:c7:1e:c2:9b:a3: - 6e:88:f1:7c:58:08:a2:9f:13:cc:5b:b9:11:2c:1d: - 23:6f:3a:ae:47:9a:0f:6a:ce:e5:80:34:09:e6:e3: - fd:76:4a:cf:5a:18:bb:9c:c5:c1:74:49:67:77:1b: - ba:28:86:31:a6:fc:12:af:4a:85:1b:73:5b:f4:d6: - 42:ff:0c:1c:49:e7:31:f2:5a:2a:1e:cd:87:cb:22: - ff:70:1c:48:ed:ba:e0:be:f0:bc:9e:e0:dc:59:db: - a5:74:25:58:b3:61:04:f6:33:28:6b:07:25:60:0f: - 72:93:16:6c:9f:b0:ad:4a:18:f7:9e:29:1e:b7:61: - 34:17 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: - CA:FALSE - Netscape Comment: - nats.io nats-server test-suite certificate - X509v3 Subject Key Identifier: - 1F:14:EF:2B:53:AB:28:4A:93:42:98:AE:85:06:0F:B4:7D:DC:36:AE - X509v3 Authority Key Identifier: - keyid:FE:CF:7F:3E:2E:EE:C4:AD:EA:5B:6F:88:45:6C:C7:88:48:14:8B:3A - DirName:/C=US/ST=California/O=Synadia/OU=nats.io/CN=Certificate Authority 2022-08-27 - serial:49:9C:16:ED:BB:5C:F4:45:1B:AC:C5:AD:8C:7A:5B:33:40:B6:6D:21 - - X509v3 Subject Alternative Name: - DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1, email:derek@nats.io - Netscape Cert Type: - SSL Client - X509v3 Key Usage: - Digital Signature, Key Encipherment - X509v3 Extended Key Usage: - TLS Web Client Authentication - Signature Algorithm: sha256WithRSAEncryption - 60:43:0b:c6:11:0b:96:ae:03:dc:77:26:9a:4a:bd:6a:d7:03: - ec:43:16:2d:ba:8c:e5:50:fa:57:a9:1f:2f:a4:15:c3:a8:13: - b9:d3:59:2a:97:7c:ae:ce:a9:f8:44:e4:97:ee:7d:09:dc:74: - 38:80:94:cf:47:e0:84:52:2a:91:44:8a:85:55:da:42:6a:f1: - 91:1a:6e:5a:63:e6:0b:61:3c:0d:b0:aa:17:b8:77:94:32:20: - 4d:20:8f:84:56:64:ae:ef:d8:8d:42:b5:52:4d:b0:1c:46:97: - bc:4c:77:8c:3f:a3:73:43:87:27:71:62:e7:fe:02:de:a1:27: - 77:be:86:29:8f:62:a1:d9:e7:ea:61:33:73:f4:1f:0a:12:14: - 68:eb:7d:8c:71:5b:42:e7:48:10:c9:df:30:3b:5b:eb:69:29: - b6:95:bc:09:fc:01:b0:be:fc:9f:ee:c4:f3:df:a0:01:c5:68: - 20:f5:2f:f8:e7:1c:a5:4c:a8:a8:a2:20:a1:d2:0f:f6:f6:c4: - 0d:f5:26:fd:ea:8b:b5:06:a9:9e:17:35:47:f7:fd:6e:78:3d: - 5f:7a:87:ed:21:b2:4e:e9:6a:d1:d9:ed:0e:cf:43:61:83:7c: - fe:0d:b1:ad:ff:fa:2d:2b:36:9d:99:9c:20:48:21:0d:36:c8: - dd:b6:0a:d8 ------BEGIN CERTIFICATE----- -MIIE5zCCA8+gAwIBAgIUOEwWJJsEHLPb4Ew87bdAfWi1+h8wDQYJKoZIhvcNAQEL -BQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNVBAoM -B1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmljYXRl -IEF1dGhvcml0eSAyMDIyLTA4LTI3MB4XDTIyMDgyNzIwMjMwMloXDTMyMDgyNDIw -MjMwMlowWjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNV -BAoMB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xEjAQBgNVBAMMCWxvY2FsaG9z -dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKycPp07ehJWhXjK35z8 -DH5e8k8iM0aBOFPXpyWP1+4WE+JnSYj2lJnwqabb/noXyePfMXNxOHA6lh6Ze10H -42Pk6L+Z9z1cJ/W3Nyna7oKAANTI0xs2DYvTipuOEqFNDMUi+FY7ahr76T0IHhN/ -VW4uZZOakFQDbQ3mRNb3wNfY4ccewpujbojxfFgIop8TzFu5ESwdI286rkeaD2rO -5YA0Cebj/XZKz1oYu5zFwXRJZ3cbuiiGMab8Eq9KhRtzW/TWQv8MHEnnMfJaKh7N -h8si/3AcSO264L7wvJ7g3FnbpXQlWLNhBPYzKGsHJWAPcpMWbJ+wrUoY954pHrdh -NBcCAwEAAaOCAYwwggGIMAkGA1UdEwQCMAAwOQYJYIZIAYb4QgENBCwWKm5hdHMu -aW8gbmF0cy1zZXJ2ZXIgdGVzdC1zdWl0ZSBjZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU -HxTvK1OrKEqTQpiuhQYPtH3cNq4wga4GA1UdIwSBpjCBo4AU/s9/Pi7uxK3qW2+I -RWzHiEgUizqhdaRzMHExCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh -MRAwDgYDVQQKDAdTeW5hZGlhMRAwDgYDVQQLDAduYXRzLmlvMSkwJwYDVQQDDCBD -ZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAyMi0wOC0yN4IUSZwW7btc9EUbrMWtjHpb -M0C2bSEwOwYDVR0RBDQwMoIJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAA -AAABgQ1kZXJla0BuYXRzLmlvMBEGCWCGSAGG+EIBAQQEAwIHgDALBgNVHQ8EBAMC -BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggEBAGBDC8YR -C5auA9x3JppKvWrXA+xDFi26jOVQ+lepHy+kFcOoE7nTWSqXfK7OqfhE5JfufQnc -dDiAlM9H4IRSKpFEioVV2kJq8ZEablpj5gthPA2wqhe4d5QyIE0gj4RWZK7v2I1C -tVJNsBxGl7xMd4w/o3NDhydxYuf+At6hJ3e+himPYqHZ5+phM3P0HwoSFGjrfYxx -W0LnSBDJ3zA7W+tpKbaVvAn8AbC+/J/uxPPfoAHFaCD1L/jnHKVMqKiiIKHSD/b2 -xA31Jv3qi7UGqZ4XNUf3/W54PV96h+0hsk7patHZ7Q7PQ2GDfP4Nsa3/+i0rNp2Z -nCBIIQ02yN22Ctg= ------END CERTIFICATE----- diff --git a/test/certs/client-key.pem b/test/certs/client-key.pem deleted file mode 100644 index 9f02b6cf5..000000000 --- a/test/certs/client-key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCsnD6dO3oSVoV4 -yt+c/Ax+XvJPIjNGgThT16clj9fuFhPiZ0mI9pSZ8Kmm2/56F8nj3zFzcThwOpYe -mXtdB+Nj5Oi/mfc9XCf1tzcp2u6CgADUyNMbNg2L04qbjhKhTQzFIvhWO2oa++k9 -CB4Tf1VuLmWTmpBUA20N5kTW98DX2OHHHsKbo26I8XxYCKKfE8xbuREsHSNvOq5H -mg9qzuWANAnm4/12Ss9aGLucxcF0SWd3G7oohjGm/BKvSoUbc1v01kL/DBxJ5zHy -WioezYfLIv9wHEjtuuC+8Lye4NxZ26V0JVizYQT2MyhrByVgD3KTFmyfsK1KGPee -KR63YTQXAgMBAAECggEBAKc6FHt2NPTxOAxn2C6aDmycBftesfiblnu8EWaVrmgu -oYMV+CsmYZ+mhmZu+mNFCsam5JzoUvp/+BKbNeZSjx2nl0qRmvOqhdhLcbkuLybl -ZmjAS64wNv2Bq+a6xRfaswWGtLuugkS0TCph4+mV0qmVb7mJ5ExQqWXu8kCl9QHn -uKacp1wVFok9rmEI+byL1+Z01feKrkf/hcF6dk62U7zHNPajViJFTDww7hiHyfUH -6qsxIe1UWSNKtE61haEHkzqbDIDAy79jX4t3JobLToeVNCbJ7BSPf2IQSPJxELVL -sidIJhndEjsbDR2CLpIF/EjsiSIaP7jh2zC9fxFpgSkCgYEA1qH0PH1JD5FqRV/p -n9COYa6EifvSymGo4u/2FHgtX7wNSIQvqAVXenrQs41mz9E65womeqFXT/AZglaM -1PEjjwcFlDuLvUEYYJNgdXrIC515ZXS6TdvJ0JpQJLx28GzZ7h31tZXfwn68C3/i -UGEHp+nN1BfBBQnsqvmGFFvHZFUCgYEAzeDlZHHijBlgHU+kGzKm7atJfAGsrv6/ -tw7CIMEsL+z/y7pl3nwDLdZF+mLIvGuKlwIRajEzbYcEuVymCyG2/SmPMQEUf6j+ -C1OmorX9CW8OwHmVCajkIgKn0ICFsF9iFv6aYZmm1kG48AIuYiQ7HOvY/MlilqFs -1p8sw6ZpQrsCgYEAj7Z9fQs+omfxymYAXnwc+hcKtAGkENL3bIzULryRVSrrkgTA -jDaXbnFR0Qf7MWedkxnezfm+Js5TpkwhnGuiLaC8AZclaCFwGypTShZeYDifEmno -XT2vkjfhNdfjo/Ser6vr3BxwaSDG9MQ6Wyu9HpeUtFD7c05D4++T8YnKpskCgYEA -pCkcoIAStcWSFy0m3K0B3+dBvAiVyh/FfNDeyEFf24Mt4CPsEIBwBH+j4ugbyeoy -YwC6JCPBLyeHA8q1d5DVmX4m+Fs1HioBD8UOzRUyA/CzIZSQ21f5OIlHiIDCmQUl -cNJpBUQAfT2AmpgSphzfqcsBhWeLHjLvVx8rEYLC0fsCgYAiHdPZ3C0f7rWZP93N -gY4DuldiO4d+KVsWAdBxeNgPznisUI7/ZZ/9NvCxGvA5NynyZr0qlpiKzVvtFJG8 -1ZPUuFFRMAaWn9h5C+CwMPgk65tFC6lw/el0hpmcocSXVdiJEbkV0rnv9iGh0CYX -HMACGrYlyZdDYM0CH/JAM+K/QQ== ------END PRIVATE KEY----- diff --git a/test/certs/server-cert.pem b/test/certs/server-cert.pem deleted file mode 100644 index 80a9d8fe0..000000000 --- a/test/certs/server-cert.pem +++ /dev/null @@ -1,99 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 1d:d9:1f:06:dd:fd:90:26:4e:27:ea:2e:01:4b:31:e6:d2:49:31:1f - Signature Algorithm: sha256WithRSAEncryption - Issuer: C=US, ST=California, O=Synadia, OU=nats.io, CN=Certificate Authority 2022-08-27 - Validity - Not Before: Aug 27 20:23:02 2022 GMT - Not After : Aug 24 20:23:02 2032 GMT - Subject: C=US, ST=California, O=Synadia, OU=nats.io, CN=localhost - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:e6:fb:47:65:cd:c9:a2:2d:af:8b:cd:d5:6a:79: - 54:3c:07:5f:eb:5a:71:2b:2b:e5:6f:be:31:fb:16: - 65:68:76:0e:59:e7:e4:57:ca:88:e9:77:d6:41:ad: - 57:7a:42:b2:d2:54:c4:0f:7c:5b:c1:bc:61:97:e3: - 22:3a:3e:1e:4a:5d:47:9f:6b:7d:6f:34:e3:8c:86: - 9d:85:19:29:9a:11:58:44:4c:a1:90:d3:14:61:e1: - 57:da:01:ea:ce:3f:90:ae:9e:5d:13:6d:2c:89:ca: - 39:15:6b:b6:9e:32:d7:2a:4c:48:85:2f:b0:1e:d8: - 4b:62:32:14:eb:32:b6:29:04:34:3c:af:39:b6:8b: - 52:32:4d:bf:43:5f:9b:fb:0d:43:a6:ad:2c:a7:41: - 29:55:c9:70:b3:b5:15:46:34:bf:e4:1e:52:2d:a4: - 49:2e:d5:21:ed:fc:00:f7:a2:0b:bc:12:0a:90:64: - 50:7c:c5:14:70:f5:fb:9b:62:08:78:43:49:31:f3: - 47:b8:93:d4:2d:4c:a9:dc:17:70:76:34:66:ff:65: - c1:39:67:e9:a6:1c:80:6a:f0:9d:b3:28:c8:a3:3a: - b7:5d:de:6e:53:6d:09:b3:0d:b1:13:10:e8:ec:e0: - bd:5e:a1:94:4b:70:bf:dc:bd:8b:b9:82:65:dd:af: - 81:7b - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: - CA:FALSE - Netscape Comment: - nats.io nats-server test-suite certificate - X509v3 Subject Key Identifier: - 2B:8C:A3:8B:DB:DB:5C:CE:18:DB:F6:A8:31:4E:C2:3E:EE:D3:40:7E - X509v3 Authority Key Identifier: - keyid:FE:CF:7F:3E:2E:EE:C4:AD:EA:5B:6F:88:45:6C:C7:88:48:14:8B:3A - DirName:/C=US/ST=California/O=Synadia/OU=nats.io/CN=Certificate Authority 2022-08-27 - serial:49:9C:16:ED:BB:5C:F4:45:1B:AC:C5:AD:8C:7A:5B:33:40:B6:6D:21 - - X509v3 Subject Alternative Name: - DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1 - Netscape Cert Type: - SSL Client, SSL Server - X509v3 Key Usage: - Digital Signature, Key Encipherment - X509v3 Extended Key Usage: - TLS Web Server Authentication, Netscape Server Gated Crypto, Microsoft Server Gated Crypto, TLS Web Client Authentication - Signature Algorithm: sha256WithRSAEncryption - 54:49:34:2b:38:d1:aa:3b:43:60:4c:3f:6a:f8:74:ca:49:53: - a1:af:12:d3:a8:17:90:7b:9d:a3:69:13:6e:da:2c:b7:61:31: - ac:eb:00:93:92:fc:0c:10:d4:18:a0:16:61:94:4b:42:cb:eb: - 7a:f6:80:c6:45:c0:9c:09:aa:a9:48:e8:36:e3:c5:be:36:e0: - e9:78:2a:bb:ab:64:9b:20:eb:e6:0f:63:2b:59:c3:58:0b:3a: - 84:15:04:c1:7e:12:03:1b:09:25:8d:4c:03:e8:18:26:c0:6c: - b7:90:b1:fd:bc:f1:cf:d0:d5:4a:03:15:71:0c:7d:c1:76:87: - 92:f1:3e:bc:75:51:5a:c4:36:a4:ff:91:98:df:33:5d:a7:38: - de:50:29:fd:0f:c8:55:e6:8f:24:c2:2e:98:ab:d9:5d:65:2f: - 50:cc:25:f6:84:f2:21:2e:5e:76:d0:86:1e:69:8b:cb:8a:3a: - 2d:79:21:5e:e7:f7:2d:06:18:a1:13:cb:01:c3:46:91:2a:de: - b4:82:d7:c3:62:6f:08:a1:d5:90:19:30:9d:64:8e:e4:f8:ba: - 4f:2f:ba:13:b4:a3:9f:d1:d5:77:64:8a:3e:eb:53:c5:47:ac: - ab:3e:0e:7a:9b:a6:f4:48:25:66:eb:c7:4c:f9:50:24:eb:71: - e0:75:ae:e6 ------BEGIN CERTIFICATE----- -MIIE+TCCA+GgAwIBAgIUHdkfBt39kCZOJ+ouAUsx5tJJMR8wDQYJKoZIhvcNAQEL -BQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNVBAoM -B1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmljYXRl -IEF1dGhvcml0eSAyMDIyLTA4LTI3MB4XDTIyMDgyNzIwMjMwMloXDTMyMDgyNDIw -MjMwMlowWjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNV -BAoMB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xEjAQBgNVBAMMCWxvY2FsaG9z -dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOb7R2XNyaItr4vN1Wp5 -VDwHX+tacSsr5W++MfsWZWh2Dlnn5FfKiOl31kGtV3pCstJUxA98W8G8YZfjIjo+ -HkpdR59rfW8044yGnYUZKZoRWERMoZDTFGHhV9oB6s4/kK6eXRNtLInKORVrtp4y -1ypMSIUvsB7YS2IyFOsytikENDyvObaLUjJNv0Nfm/sNQ6atLKdBKVXJcLO1FUY0 -v+QeUi2kSS7VIe38APeiC7wSCpBkUHzFFHD1+5tiCHhDSTHzR7iT1C1MqdwXcHY0 -Zv9lwTln6aYcgGrwnbMoyKM6t13eblNtCbMNsRMQ6OzgvV6hlEtwv9y9i7mCZd2v -gXsCAwEAAaOCAZ4wggGaMAkGA1UdEwQCMAAwOQYJYIZIAYb4QgENBCwWKm5hdHMu -aW8gbmF0cy1zZXJ2ZXIgdGVzdC1zdWl0ZSBjZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU -K4yji9vbXM4Y2/aoMU7CPu7TQH4wga4GA1UdIwSBpjCBo4AU/s9/Pi7uxK3qW2+I -RWzHiEgUizqhdaRzMHExCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh -MRAwDgYDVQQKDAdTeW5hZGlhMRAwDgYDVQQLDAduYXRzLmlvMSkwJwYDVQQDDCBD -ZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAyMi0wOC0yN4IUSZwW7btc9EUbrMWtjHpb -M0C2bSEwLAYDVR0RBCUwI4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAA -AAABMBEGCWCGSAGG+EIBAQQEAwIGwDALBgNVHQ8EBAMCBaAwNAYDVR0lBC0wKwYI -KwYBBQUHAwEGCWCGSAGG+EIEAQYKKwYBBAGCNwoDAwYIKwYBBQUHAwIwDQYJKoZI -hvcNAQELBQADggEBAFRJNCs40ao7Q2BMP2r4dMpJU6GvEtOoF5B7naNpE27aLLdh -MazrAJOS/AwQ1BigFmGUS0LL63r2gMZFwJwJqqlI6Dbjxb424Ol4KrurZJsg6+YP -YytZw1gLOoQVBMF+EgMbCSWNTAPoGCbAbLeQsf288c/Q1UoDFXEMfcF2h5LxPrx1 -UVrENqT/kZjfM12nON5QKf0PyFXmjyTCLpir2V1lL1DMJfaE8iEuXnbQhh5pi8uK -Oi15IV7n9y0GGKETywHDRpEq3rSC18Nibwih1ZAZMJ1kjuT4uk8vuhO0o5/R1Xdk -ij7rU8VHrKs+DnqbpvRIJWbrx0z5UCTrceB1ruY= ------END CERTIFICATE----- diff --git a/test/certs/server-key-noip.pem b/test/certs/server-key-noip.pem deleted file mode 100644 index aa29bb370..000000000 --- a/test/certs/server-key-noip.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCt8Ic/MmaHejGb -ylQKrqYayiXVxfxJayEL3qcVyJw8zUEdMiV3aHuD6F0Uei4L6kGRpCDsIBcPy41M -G4ig0ndGZX7RoOZMS8aMOaGzWzRXyKEQDBNUOnSQezu62kFigfXctXNsgzj0oVKr -vcKVPnn/r6Su39YR2SkguLQV4zKTXDbOVrQBAqFFMaOhHuq4xAEEVxFE9FXq4q5o -CHCFwFv/ur/ei7yhxgOiL4rrnrd5OmdqsHDT6AinEiTVu1eIcjfI5i7bh+AqcRos -kJyIKQx1KITWf3UtUAg2K8/zujNyHnoH2yDamDs5hpZM4kpCYRqbC2dNbRPRn0Df -EseNnVBpAgMBAAECggEAcmiqXRwmqmfqZ4Ge4+Pap/ZdCo6OkjAf7XHHTyHD+o47 -jRul3zPfQnU9fDGdRgMQm95sNUQqRx5pUy0tIjMtdyVdVD9UG80fzK4/uPx9olv5 -7Nc0g4trjnkwYYgbx9KZyFGlmTN67BWMjiBj88zDbDW4ybm7UcQYNEipU1g8tQW1 -tUwcZ1oahXfzO75vcMqDVlS2IE0s0AD9sh+AaJIwxV9kSLNjlSwkpsH6PBKKB/3r -WvG2p6Og1whdQ54PGADUVSx1yWFyXQDeygqLmryEWaHJQz1jt7bvaaAMy2PTdwVf -A5LVG3VHkoQOBv8imtpCbU2J7zAk9ypDuRUlpa8h/QKBgQDdCCCbV02BhrqDYchm -ojB95Vx8KtvQdXhvsxShxyuIktuB7W+NnheBmLY0TNcYSQyzithCUBhtmyaC5S4f -dHmT52e7HS0xaL9r9BhAQrtWReMcplKB1IIXtdYXEY3qOjZMxX3seJo0iBWS3hMH -EG6tC6tlr5ZXOKJOrBMGuMgplwKBgQDJdSYkC3AX2p+4BNf3hgQyzotuSVSbx/zu -0ZHhi8Wp7yF49c8+9+ahO9AMrVM0ZSh2buznfF46FNC/C55M7a9Rn60sFQQ16b5L -rJTzlPoUGTnPLt8C3TdMIFg/5cAW6ZgZWNlU3aVU0W34NVh/H2m/M72tGrk250zs -YhZ8/RGV/wKBgQCKlMfs3YXoyhIywaImR1Zj+ORNrYl4X86NKhirffbbgEhEZBvn -DNHsHVVP4UWTImnmQA1rNlC6l+ZDd3G9owd/Jj0xYg+txOEPzFFQKQbQBq1ojxd3 -80dFmmqKuCTkUG8vHzvegZcdjJ0KIlaHvVPHB2QFM1vtf8Kz1MtxEXXeLQKBgDn0 -Bm3WEH/8N3gzhIFDP0/yVO/8DmfmByAYj5PHpqw1C3cFl4HwxJrbXwVWkxn+g75W -OLZ684xX0pky2W4d7hJYEfQdc6GixUh1tD/COpKvkw7D2Am146N1po1zJWgx+LxJ -7/NW86nLuYvupK+lNMF5O/ZhOqjNrzZNHVUFZBq3AoGAPwixh7/ZMX6mmm8foImh -qibytx72gl1jhHWSaX3rwrSOO9dxO2rlI7LOZQrarU632Y9KMkP3HNbBHPRkA4MI -6I9wqawRzGjcpeXIMlPzOHDHYLyrTpEzo8nrSNk/cM8P4RxE12FqySzQIkiN06J7 -AxJ7hVqtX6wZIoqoOa9aK1E= ------END PRIVATE KEY----- diff --git a/test/certs/server-key.pem b/test/certs/server-key.pem deleted file mode 100644 index f2c2c6c2f..000000000 --- a/test/certs/server-key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQDm+0dlzcmiLa+L -zdVqeVQ8B1/rWnErK+VvvjH7FmVodg5Z5+RXyojpd9ZBrVd6QrLSVMQPfFvBvGGX -4yI6Ph5KXUefa31vNOOMhp2FGSmaEVhETKGQ0xRh4VfaAerOP5Cunl0TbSyJyjkV -a7aeMtcqTEiFL7Ae2EtiMhTrMrYpBDQ8rzm2i1IyTb9DX5v7DUOmrSynQSlVyXCz -tRVGNL/kHlItpEku1SHt/AD3ogu8EgqQZFB8xRRw9fubYgh4Q0kx80e4k9QtTKnc -F3B2NGb/ZcE5Z+mmHIBq8J2zKMijOrdd3m5TbQmzDbETEOjs4L1eoZRLcL/cvYu5 -gmXdr4F7AgMBAAECggEBAK4sr3MiEbjcsHJAvXyzjwRRH1Bu+8VtLW7swe2vvrpd -w4aiKXrV/BXpSsRtvPgxkXyvdMSkpuBZeFI7cVTwAJFc86RQPt77x9bwr5ltFwTZ -rXCbRH3b3ZPNhByds3zhS+2Q92itu5cPyanQdn2mor9/lHPyOOGZgobCcynELL6R -wRElkeDyf5ODuWEd7ADC5IFyZuwb3azNVexIK+0yqnMmv+QzEW3hsycFmFGAeB7v -MIMjb2BhLrRr6Y5Nh+k58yM5DCf9h/OJhDpeXwLkxyK4BFg+aZffEbUX0wHDMR7f -/nMv1g6cKvDWiLU8xLzez4t2qNIBNdxw5ZSLyQRRolECgYEA+ySTKrBAqI0Uwn8H -sUFH95WhWUXryeRyGyQsnWAjZGF1+d67sSY2un2W6gfZrxRgiNLWEFq9AaUs0MuH -6syF4Xwx/aZgU/gvsGtkgzuKw1bgvekT9pS/+opmHRCZyQAFEHj0IEpzyB6rW1u/ -LdlR3ShEENnmXilFv/uF/uXP5tMCgYEA63LiT0w46aGPA/E+aLRWU10c1eZ7KdhR -c3En6zfgIxgFs8J38oLdkOR0CF6T53DSuvGR/OprVKdlnUhhDxBgT1oQjK2GlhPx -JV5uMvarJDJxAwsF+7T4H2QtZ00BtEfpyp790+TlypSG1jo/BnSMmX2uEbV722lY -hzINLY49obkCgYBEpN2YyG4T4+PtuXznxRkfogVk+kiVeVx68KtFJLbnw//UGT4i -EHjbBmLOevDT+vTb0QzzkWmh3nzeYRM4aUiatjCPzP79VJPsW54whIDMHZ32KpPr -TQMgPt3kSdpO5zN7KiRIAzGcXE2n/e7GYGUQ1uWr2XMu/4byD5SzdCscQwJ/Ymii -LoKtRvk/zWYHr7uwWSeR5dVvpQ3E/XtONAImrIRd3cRqXfJUqTrTRKxDJXkCmyBc -5FkWg0t0LUkTSDiQCJqcUDA3EINFR1kwthxja72pfpwc5Be/nV9BmuuUysVD8myB -qw8A/KsXsHKn5QrRuVXOa5hvLEXbuqYw29mX6QKBgDGDzIzpR9uPtBCqzWJmc+IJ -z4m/1NFlEz0N0QNwZ/TlhyT60ytJNcmW8qkgOSTHG7RDueEIzjQ8LKJYH7kXjfcF -6AJczUG5PQo9cdJKo9JP3e1037P/58JpLcLe8xxQ4ce03zZpzhsxR2G/tz8DstJs -b8jpnLyqfGrcV2feUtIZ ------END PRIVATE KEY----- diff --git a/test/certs/server-noip.pem b/test/certs/server-noip.pem deleted file mode 100644 index 859215556..000000000 --- a/test/certs/server-noip.pem +++ /dev/null @@ -1,99 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 1d:5c:7c:59:0c:cd:27:83:dd:97:64:53:b0:44:3c:b4:5b:d4:fc:d1 - Signature Algorithm: sha256WithRSAEncryption - Issuer: C=US, ST=California, O=Synadia, OU=nats.io, CN=Certificate Authority 2022-08-27 - Validity - Not Before: Aug 27 20:23:02 2022 GMT - Not After : Aug 24 20:23:02 2032 GMT - Subject: C=US, ST=California, O=Synadia, OU=nats.io, CN=localhost - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:ad:f0:87:3f:32:66:87:7a:31:9b:ca:54:0a:ae: - a6:1a:ca:25:d5:c5:fc:49:6b:21:0b:de:a7:15:c8: - 9c:3c:cd:41:1d:32:25:77:68:7b:83:e8:5d:14:7a: - 2e:0b:ea:41:91:a4:20:ec:20:17:0f:cb:8d:4c:1b: - 88:a0:d2:77:46:65:7e:d1:a0:e6:4c:4b:c6:8c:39: - a1:b3:5b:34:57:c8:a1:10:0c:13:54:3a:74:90:7b: - 3b:ba:da:41:62:81:f5:dc:b5:73:6c:83:38:f4:a1: - 52:ab:bd:c2:95:3e:79:ff:af:a4:ae:df:d6:11:d9: - 29:20:b8:b4:15:e3:32:93:5c:36:ce:56:b4:01:02: - a1:45:31:a3:a1:1e:ea:b8:c4:01:04:57:11:44:f4: - 55:ea:e2:ae:68:08:70:85:c0:5b:ff:ba:bf:de:8b: - bc:a1:c6:03:a2:2f:8a:eb:9e:b7:79:3a:67:6a:b0: - 70:d3:e8:08:a7:12:24:d5:bb:57:88:72:37:c8:e6: - 2e:db:87:e0:2a:71:1a:2c:90:9c:88:29:0c:75:28: - 84:d6:7f:75:2d:50:08:36:2b:cf:f3:ba:33:72:1e: - 7a:07:db:20:da:98:3b:39:86:96:4c:e2:4a:42:61: - 1a:9b:0b:67:4d:6d:13:d1:9f:40:df:12:c7:8d:9d: - 50:69 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: - CA:FALSE - Netscape Comment: - nats.io nats-server test-suite certificate - X509v3 Subject Key Identifier: - C9:AA:3C:08:39:7E:C1:42:C0:3D:B7:2F:84:21:E7:8A:30:E7:C7:B1 - X509v3 Authority Key Identifier: - keyid:FE:CF:7F:3E:2E:EE:C4:AD:EA:5B:6F:88:45:6C:C7:88:48:14:8B:3A - DirName:/C=US/ST=California/O=Synadia/OU=nats.io/CN=Certificate Authority 2022-08-27 - serial:49:9C:16:ED:BB:5C:F4:45:1B:AC:C5:AD:8C:7A:5B:33:40:B6:6D:21 - - X509v3 Subject Alternative Name: - DNS:localhost - Netscape Cert Type: - SSL Client, SSL Server - X509v3 Key Usage: - Digital Signature, Key Encipherment - X509v3 Extended Key Usage: - TLS Web Server Authentication, Netscape Server Gated Crypto, Microsoft Server Gated Crypto, TLS Web Client Authentication - Signature Algorithm: sha256WithRSAEncryption - 9b:63:ae:ec:56:ec:0c:7a:d5:88:d1:0a:0a:81:29:37:4f:a6: - 08:b8:78:78:23:af:5b:b7:65:61:d7:64:2a:c9:e7:a6:d2:b1: - cb:36:bf:23:2e:2d:48:85:7f:16:0f:64:af:03:db:5d:0e:a7: - 14:c5:f6:04:b2:6b:92:27:ba:cb:d2:13:25:a2:15:b0:8e:4a: - 2d:eb:41:18:09:b1:68:d5:0f:6b:56:da:86:ed:4a:7a:29:30: - 09:77:63:a4:64:3d:e3:2e:d7:6f:1a:8c:96:c9:cb:81:fe:a3: - 6d:35:e3:09:ea:9b:2e:da:8c:8e:c8:c9:69:b1:83:e7:6f:2d: - 5f:a1:ac:32:ae:29:57:a9:5c:9b:7d:f0:fd:47:3c:f3:6a:d0: - eb:77:8d:70:06:a2:74:3d:d6:37:1e:7b:e7:d9:e4:33:c9:9d: - ad:fa:24:c6:4d:e2:2c:c9:25:cb:75:be:8d:e9:83:7e:ad:db: - 53:9e:97:be:d5:7f:83:90:fc:75:1d:02:29:b7:99:18:a3:39: - 25:a2:54:b7:21:7d:be:0b:4c:ea:ff:80:b9:4b:5e:21:ed:25: - ad:d4:62:52:59:79:83:32:df:30:a1:64:68:05:cc:35:ad:8b: - d3:66:6b:b1:31:b7:b3:b2:d8:0f:5b:96:40:ef:57:1d:7f:b0: - b0:f4:e9:db ------BEGIN CERTIFICATE----- -MIIE4TCCA8mgAwIBAgIUHVx8WQzNJ4Pdl2RTsEQ8tFvU/NEwDQYJKoZIhvcNAQEL -BQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNVBAoM -B1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmljYXRl -IEF1dGhvcml0eSAyMDIyLTA4LTI3MB4XDTIyMDgyNzIwMjMwMloXDTMyMDgyNDIw -MjMwMlowWjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNV -BAoMB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xEjAQBgNVBAMMCWxvY2FsaG9z -dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3whz8yZod6MZvKVAqu -phrKJdXF/ElrIQvepxXInDzNQR0yJXdoe4PoXRR6LgvqQZGkIOwgFw/LjUwbiKDS -d0ZlftGg5kxLxow5obNbNFfIoRAME1Q6dJB7O7raQWKB9dy1c2yDOPShUqu9wpU+ -ef+vpK7f1hHZKSC4tBXjMpNcNs5WtAECoUUxo6Ee6rjEAQRXEUT0VerirmgIcIXA -W/+6v96LvKHGA6Iviuuet3k6Z2qwcNPoCKcSJNW7V4hyN8jmLtuH4CpxGiyQnIgp -DHUohNZ/dS1QCDYrz/O6M3IeegfbINqYOzmGlkziSkJhGpsLZ01tE9GfQN8Sx42d -UGkCAwEAAaOCAYYwggGCMAkGA1UdEwQCMAAwOQYJYIZIAYb4QgENBCwWKm5hdHMu -aW8gbmF0cy1zZXJ2ZXIgdGVzdC1zdWl0ZSBjZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU -yao8CDl+wULAPbcvhCHnijDnx7Ewga4GA1UdIwSBpjCBo4AU/s9/Pi7uxK3qW2+I -RWzHiEgUizqhdaRzMHExCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh -MRAwDgYDVQQKDAdTeW5hZGlhMRAwDgYDVQQLDAduYXRzLmlvMSkwJwYDVQQDDCBD -ZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAyMi0wOC0yN4IUSZwW7btc9EUbrMWtjHpb -M0C2bSEwFAYDVR0RBA0wC4IJbG9jYWxob3N0MBEGCWCGSAGG+EIBAQQEAwIGwDAL -BgNVHQ8EBAMCBaAwNAYDVR0lBC0wKwYIKwYBBQUHAwEGCWCGSAGG+EIEAQYKKwYB -BAGCNwoDAwYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggEBAJtjruxW7Ax61YjR -CgqBKTdPpgi4eHgjr1u3ZWHXZCrJ56bSscs2vyMuLUiFfxYPZK8D210OpxTF9gSy -a5InusvSEyWiFbCOSi3rQRgJsWjVD2tW2obtSnopMAl3Y6RkPeMu128ajJbJy4H+ -o2014wnqmy7ajI7IyWmxg+dvLV+hrDKuKVepXJt98P1HPPNq0Ot3jXAGonQ91jce -e+fZ5DPJna36JMZN4izJJct1vo3pg36t21Oel77Vf4OQ/HUdAim3mRijOSWiVLch -fb4LTOr/gLlLXiHtJa3UYlJZeYMy3zChZGgFzDWti9Nma7Ext7Oy2A9blkDvVx1/ -sLD06ds= ------END CERTIFICATE----- diff --git a/test/conn_write_test.c b/test/conn_write_test.c new file mode 100644 index 000000000..4dc979642 --- /dev/null +++ b/test/conn_write_test.c @@ -0,0 +1,119 @@ +// Copyright 2015-2023 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 "test.h" +#include "conn.h" + +void Test_ConnWriteChain(void) +{ + natsStatus s; + natsWriteQueue w; + + natsMemOptions opts = { + .heapPageSize = 4 * sizeof(natsWriteBuffer), + .writeQueueBuffers = 4, + .writeQueueMaxBuffers = 7, + }; + char testName[256]; + sprintf(testName, "Set memory parameters: page size %zu, initial write buffers %zu, max write buffers %zu", + opts.heapPageSize, opts.writeQueueBuffers, opts.writeQueueMaxBuffers); + test(testName); + testCond(true); + + test("Initialize write chain"); + s = natsWriteChain_init(&w, &opts); + testCond((STILL_OK(s)) && + (w.capacity == opts.writeQueueBuffers) && + (w.start == 0) && + (w.end == 0) && + (w.chain != NULL)); + + test("Add 3 buffers"); + natsString s0 = NATS_STR("test0"); + natsString s1 = NATS_STR("test1"); + natsString s2 = NATS_STR("test2"); + s = natsWriteChain_add(&w, &s0, NULL, NULL); + s = natsWriteChain_add(&w, &s1, NULL, NULL); + s = natsWriteChain_add(&w, &s2, NULL, NULL); + testCond((STILL_OK(s)) && + (w.start == 0) && + (w.end == 3) && + (w.chain[0].buf.data == s0.data) && + (w.chain[0].buf.len == s0.len) && + (w.chain[1].buf.data == s1.data) && + (w.chain[1].buf.len == s1.len) && + (w.chain[2].buf.data == s2.data) && + (w.chain[2].buf.len == s2.len) && + (w.capacity == 4)); + + test("Get the current buffer, the first we added"); + natsWriteBuffer *wb = natsWriteChain_get(&w); + testCond((wb != NULL) && + (wb->buf.data == s0.data) && + (wb->buf.len == s0.len)); + + test("If we get again, we get the same one"); + wb = natsWriteChain_get(&w); + testCond((wb != NULL) && + (wb->buf.data == s0.data) && + (wb->buf.len == s0.len)); + + test("Done with the current buffer"); + s = natsWriteChain_done(NULL, &w); + testCond((STILL_OK(s)) && + (w.start == 1) && + (w.end == 3) && + natsWriteChain_len(&w) == 2); + + test("Get the current buffer, the second we added"); + wb = natsWriteChain_get(&w); + testCond((wb != NULL) && + (wb->buf.data == s1.data) && + (wb->buf.len == s1.len)); + + test("Add/remove 9 times, to accomplish a wraparound of 1 item"); + natsString s3s11[] = {NATS_STR("test3"), NATS_STR("test4"), NATS_STR("test5"), NATS_STR("test6"), + NATS_STR("test7"), NATS_STR("test8"), NATS_STR("test9"), NATS_STR("test10"), + NATS_STR("test11")}; + for (size_t i = 0; i < sizeof(s3s11) / sizeof(*s3s11); i++) + { + s = natsWriteChain_done(NULL, &w); + if (s != NATS_OK) + break; + s = natsWriteChain_add(&w, &s3s11[i], NULL, NULL); + if (s != NATS_OK) + break; + } + testCond((STILL_OK(s)) && + (w.start == 10) && + (w.end == 12) && + natsWriteChain_len(&w) == 2); + + test("Add one more"); + natsString s12 = NATS_STR("test12"); + s = natsWriteChain_add(&w, &s12, NULL, NULL); + testCond((STILL_OK(s)) && + (w.start == 10) && + (w.end == 13) && + natsWriteChain_len(&w) == 3); + + test("Add one more and make sure it grows and resets"); + natsString s13 = NATS_STR("test13"); + s = natsWriteChain_add(&w, &s13, NULL, NULL); + testCond((STILL_OK(s)) && + (w.start == 2) && + (w.end == 6) && + (natsWriteChain_len(&w) == 4) && + (w.capacity == opts.writeQueueMaxBuffers)); +} diff --git a/test/dylib/CMakeLists.txt b/test/dylib/CMakeLists.txt deleted file mode 100644 index 3bbc66ee8..000000000 --- a/test/dylib/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -if(NOT BUILD_TESTING) - return() -endif() -if(NOT NATS_BUILD_LIB_SHARED) - return() -endif() - -# We need this to build the test program -include_directories(${PROJECT_SOURCE_DIR}/src) - -# Build the test program -add_executable(nonats nonats.c) - -# Link dynamically with the library -add_definitions(-Dnats_IMPORTS) - -target_link_libraries(nonats nats ${NATS_EXTRA_LIB}) diff --git a/test/dylib/nonats.c b/test/dylib/nonats.c deleted file mode 100644 index 6ef07ea6f..000000000 --- a/test/dylib/nonats.c +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2017-2018 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 - -int main(int argc, char **argv) -{ - // Give a chance for the destructor/DllMain to be registered. - // Note that nats_Sleep() is not "opening" the library, so - // the cleanup code would still crash if we were not skipping - // the cleanup if we detect that library was never oepened. - nats_Sleep(1000); - return 0; -} diff --git a/test/json_test.c b/test/json_test.c new file mode 100644 index 000000000..16193831e --- /dev/null +++ b/test/json_test.c @@ -0,0 +1,912 @@ +// Copyright 2015-2023 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 "json.h" +#include "test.h" + +void Test_JSONStructure(void) +{ + natsStatus s = NATS_OK; + typedef struct TC + { + const char *name; + const char *json; + } TC; + const TC tests[] = { + {"empty object: ", "{}"}, + {"single: number: ", "{ \"test\":1}"}, + {"single: boolean true: ","{ \"test\":true}"}, + {"single: boolean false: ","{ \"test\":false}"}, + {"single: string: ","{ \"test\":\"abc\"}"}, + {"single: null: ","{ \"test\": null}"}, + {"multiple: numbers: ","{ \"test\":1, \"test2\":2}"}, + {"multiple: booleans: ","{ \"test\":true, \"test2\":false}"}, + {"multiple: strings: ","{ \"test\":\"a\", \"test2\":\"b\"}"}, + {"multiple: nulls: ","{ \"test\":null, \"test2\":null}"}, + {"multiple: mixed: ","{ \"test\":1, \"test2\":true, \"test3\":\"abc\", \"test4\":null}"}, + {"multiple: mixed different order: ","{ \"test2\":true, \"test3\":\"abc\", \"test4\":null, \"test\":1}"}, + + {"empty array: ","{ \"test\": []}"}, + {"array of empty arrays: ","{ \"test\": [[], [], []]}"}, + {"array of empty objects: ","{ \"test\": [{}, {}, {}]}"}, + {"array of strings: ","{ \"test\": [\"a\", \"b\", \"c\"]}"}, + {"array of objects: ","{ \"test\": [{\"a\": 1}, {\"b\": \"c\"}]}"}, + {"array of arrays: ","{ \"test\": [[{\"a\": 1}], [{\"b\": \"c\"}]]}"}, + {"array of numbers: ","{ \"test\": [1, 2, 3]}"}, + {"array of doubles: ","{ \"test\": [1.1, 2.2, 3.3]}"}, + {"array of booleans: ","{ \"test\": [true, false, true]}"}, + {"empty nested object","{ \n\"test\":\n{}}"}, + {"nested objects","{ \"test\":1, \"inner1\": {\"inner\":\"a\",\"inner2\":2,\"inner3\":false,\"inner4\":{\"inner2\" : 1.234}}}"}, + + {"ignored commas", "{ ,, \"test\":1,,,, }"}, + }; + const TC errorTests[] = { + {"error: starts with a letter", " A"}, + {"error: starts with a quote", " \""}, + {"error: value starts with a wrong char", " {\"test\" : XXX }"}, + {"error: array of nulls: ", "{ \"test\": [null, null, null]}"}, + {"error: mixed type array: ", "{ \"test\": [1, \"abc\", true]}"}, + }; + + natsPool *pool = NULL; + test("Create memory pool: "); + s = nats_createPool(&pool, &nats_defaultMemOptions, "json-test"); + testCond(STILL_OK(s)); + + for (int i = 0; i < (int)(sizeof(tests) / sizeof(*tests)); i++) + { + natsJSONParser *parser = NULL; + nats_JSON *json = NULL; + size_t consumed = 0; + TC tc = tests[i]; + + test(tc.name); + s = natsJSONParser_Create(&parser, pool); + const uint8_t *data = (const uint8_t *)tc.json; + const uint8_t *end = (const uint8_t *)tc.json + strlen(tc.json); + IFOK(s, natsJSONParser_Parse(&json, parser, data, end, &consumed)); + testCond((STILL_OK(s)) && (json != NULL) && (consumed == strlen(tc.json))); + } + + for (int i = 0; i < (int)(sizeof(errorTests) / sizeof(*errorTests)); i++) + { + natsJSONParser *parser = NULL; + nats_JSON *json = NULL; + TC tc = errorTests[i]; + + test(tc.name); + s = natsJSONParser_Create(&parser, pool); + const uint8_t *data = (const uint8_t *)tc.json; + const uint8_t *end = (const uint8_t *)tc.json + strlen(tc.json); + IFOK(s, natsJSONParser_Parse(&json, parser, data, end, NULL)); + testCond((s != NATS_OK) && (json == NULL)); + } + + nats_releasePool(pool); +} + + +// Test_JSON(void) +// { +// natsStatus s; +// nats_JSON *json = NULL; +// char buf[256]; +// int i; +// int intVal = 0; +// int64_t longVal = 0; +// char *strVal = NULL; +// bool boolVal = false; +// long double doubleVal = 0; +// char **arrVal = NULL; +// bool *arrBoolVal = NULL; +// long double *arrDoubleVal = NULL; +// int *arrIntVal = NULL; +// int64_t *arrLongVal = NULL; +// uint64_t *arrULongVal = NULL; +// nats_JSON **arrObjVal = NULL; +// nats_JSONArray **arrArrVal = NULL; +// int arrCount = 0; +// uint64_t ulongVal = 0; +// nats_JSON *obj1 = NULL; +// nats_JSON *obj2 = NULL; +// nats_JSON *obj3 = NULL; +// int32_t int32Val = 0; +// uint16_t uint16Val = 0; +// const char *wrong[] = { +// "{", +// "}", +// "{start quote missing\":0}", +// "{\"end quote missing: 0}", +// "{\"test\":start quote missing\"}", +// "{\"test\":\"end quote missing}", +// "{\"test\":1.2x}", +// "{\"test\":tRUE}", +// "{\"test\":true,}", +// "{\"test\":true}, xxx}", +// "{\"test\": \"abc\\error here\"}", +// "{\"test\": \"abc\\u123\"}", +// "{\"test\": \"abc\\u123g\"}", +// "{\"test\": \"abc\\u 23f\"}", +// ("{\"test\": \"abc\\" +// ""), +// "{\"test\": \"abc\\u1234", +// "{\"test\": \"abc\\uabc", +// "{\"test\" \"separator missing\"}", +// "{\"test\":[1, \"abc\", true]}", +// }; +// const char *good[] = { +// "{}", +// " {}", +// " { }", +// " { } ", +// "{ \"test\":{}}", +// "{ \"test\":1.2}", +// "{ \"test\" :1.2}", +// "{ \"test\" : 1.2}", +// "{ \"test\" : 1.2 }", +// "{ \"test\" : 1.2,\"test2\":1}", +// "{ \"test\" : 1.2, \"test2\":1}", +// "{ \"test\":0}", +// "{ \"test\" :0}", +// "{ \"test\" : 0}", +// "{ \"test\" : 0 }", +// "{ \"test\" : 0,\"test2\":1}", +// "{ \"test\" : 0, \"test2\":1}", +// "{ \"test\":true}", +// "{ \"test\": true}", +// "{ \"test\": true }", +// "{ \"test\":true,\"test2\":1}", +// "{ \"test\": true,\"test2\":1}", +// "{ \"test\": true ,\"test2\":1}", +// "{ \"test\":false}", +// "{ \"test\": false}", +// "{ \"test\": false }", +// "{ \"test\":false,\"test2\":1}", +// "{ \"test\": false,\"test2\":1}", +// "{ \"test\": false ,\"test2\":1}", +// "{ \"test\":\"abc\"}", +// "{ \"test\": \"abc\"}", +// "{ \"test\": \"abc\" }", +// "{ \"test\":\"abc\",\"test2\":1}", +// "{ \"test\": \"abc\",\"test2\":1}", +// "{ \"test\": \"abc\" ,\"test2\":1}", +// "{ \"test\": \"a\\\"b\\\"c\" }", +// "{ \"test\": [\"a\", \"b\", \"c\"]}", +// "{ \"test\": [\"a\\\"b\\\"c\"]}", +// "{ \"test\": [\"abc,def\"]}", +// "{ \"test\": [{\"a\": 1}, {\"b\": \"c\"}]}", +// "{ \"test\": [[{\"a\": 1}], [{\"b\": \"c\"}]]}", +// "{ \"test\": []}", +// "{ \"test\": {\"inner\":\"a\",\"inner2\":2,\"inner3\":false,\"inner4\":{\"inner_inner1\" : 1.234}}}", +// "{ \"test\": \"a\\\"b\\\"c\"}", +// "{ \"test\": \"\\\"\\\\/\b\f\n\r\t\\uabcd\"}", +// "{ \"test\": \"\\ua12f\"}", +// "{ \"test\": \"\\uA01F\"}", +// "{ \"test\": null}", +// }; +// nats_JSONField *f = NULL; +// unsigned char *bytes = NULL; +// int bl = 0; + +// for (i = 0; i < (int)(sizeof(wrong) / sizeof(char *)); i++) +// { +// snprintf(buf, sizeof(buf), "Negative test %d: ", (i + 1)); +// test(buf); +// s = nats_JSONParse(&json, wrong[i], -1); +// testCond((s != NATS_OK) && (json == NULL)); +// json = NULL; +// } +// nats_clearLastError(); + +// for (i = 0; i < (int)(sizeof(good) / sizeof(char *)); i++) +// { +// snprintf(buf, sizeof(buf), "Positive test %d: ", (i + 1)); +// test(buf); +// s = nats_JSONParse(&json, good[i], -1); +// testCond((STILL_OK(s)) && (json != NULL)); +// nats_JSONDestroy(json); +// json = NULL; +// } +// nats_clearLastError(); + +// // Check values +// test("Empty string: "); +// s = nats_JSONParse(&json, "{}", -1); +// IFOK(s, nats_JSONGetInt(json, "test", &intVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 0) && (intVal == 0)); +// nats_JSONDestroy(json); +// json = NULL; + +// test("Single field, string: "); +// s = nats_JSONParse(&json, "{\"test\":\"abc\"}", -1); +// IFOK(s, nats_JSONGetStr(json, "test", &strVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (strcmp(strVal, "abc") == 0)); +// nats_JSONDestroy(json); +// json = NULL; +// free(strVal); +// strVal = NULL; + +// test("Single field, string with escape chars: "); +// s = nats_JSONParse(&json, "{\"test\":\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"}", -1); +// IFOK(s, nats_JSONGetStr(json, "test", &strVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (strcmp(strVal, "\"\\/\b\f\n\r\t") == 0)); +// nats_JSONDestroy(json); +// json = NULL; +// free(strVal); +// strVal = NULL; + +// test("Single field, string with unicode: "); +// s = nats_JSONParse(&json, "{\"test\":\"\\u0026\\u003c\\u003e\"}", -1); +// IFOK(s, nats_JSONGetStr(json, "test", &strVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (strcmp(strVal, "&<>") == 0)); +// nats_JSONDestroy(json); +// json = NULL; +// free(strVal); +// strVal = NULL; + +// test("Single field, int: "); +// s = nats_JSONParse(&json, "{\"test\":1234}", -1); +// IFOK(s, nats_JSONGetInt(json, "test", &intVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (intVal == 1234)); +// nats_JSONDestroy(json); +// json = NULL; +// intVal = 0; + +// test("Single field, int32: "); +// s = nats_JSONParse(&json, "{\"test\":1234}", -1); +// IFOK(s, nats_JSONGetInt32(json, "test", &int32Val)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (int32Val == 1234)); +// nats_JSONDestroy(json); +// json = NULL; +// int32Val = 0; + +// test("Single field, uint16: "); +// s = nats_JSONParse(&json, "{\"test\":1234}", -1); +// IFOK(s, nats_JSONGetUInt16(json, "test", &uint16Val)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (uint16Val == 1234)); +// nats_JSONDestroy(json); +// json = NULL; +// uint16Val = 0; + +// test("Single field, long: "); +// s = nats_JSONParse(&json, "{\"test\":9223372036854775807}", -1); +// IFOK(s, nats_JSONGetLong(json, "test", &longVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (longVal == 9223372036854775807L)); +// nats_JSONDestroy(json); +// json = NULL; +// longVal = 0; + +// test("Single field, neg long: "); +// s = nats_JSONParse(&json, "{\"test\":-9223372036854775808}", -1); +// IFOK(s, nats_JSONGetLong(json, "test", &longVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (longVal == (int64_t)0x8000000000000000)); +// nats_JSONDestroy(json); +// json = NULL; +// longVal = 0; + +// test("Single field, neg long as ulong: "); +// s = nats_JSONParse(&json, "{\"test\":-123456789}", -1); +// IFOK(s, nats_JSONGetULong(json, "test", &ulongVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (ulongVal == 0xFFFFFFFFF8A432EB)); +// nats_JSONDestroy(json); +// json = NULL; +// ulongVal = 0; + +// test("Single field, ulong: "); +// s = nats_JSONParse(&json, "{\"test\":18446744073709551615}", -1); +// IFOK(s, nats_JSONGetULong(json, "test", &ulongVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (ulongVal == 0xFFFFFFFFFFFFFFFF)); +// nats_JSONDestroy(json); +// json = NULL; +// ulongVal = 0; + +// test("Single field, ulong: "); +// s = nats_JSONParse(&json, "{\"test\":9007199254740993}", -1); +// IFOK(s, nats_JSONGetULong(json, "test", &ulongVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (ulongVal == 9007199254740993)); +// nats_JSONDestroy(json); +// json = NULL; +// ulongVal = 0; + +// test("Single field, double: "); +// s = nats_JSONParse(&json, "{\"test\":1234.5e3}", -1); +// IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (doubleVal == (long double)1234.5e+3)); +// nats_JSONDestroy(json); +// json = NULL; +// doubleVal = 0; + +// test("Single field, double negative: "); +// s = nats_JSONParse(&json, "{\"test\":-1234}", -1); +// IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (doubleVal == (long double)-1234)); +// nats_JSONDestroy(json); +// json = NULL; +// doubleVal = 0; + +// test("Single field, double exp negative 1: "); +// s = nats_JSONParse(&json, "{\"test\":1234e-3}", -1); +// IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (doubleVal == (long double)1234.0 / 1000.0)); +// nats_JSONDestroy(json); +// json = NULL; +// doubleVal = 0; + +// test("Single field, double exp negative 2: "); +// s = nats_JSONParse(&json, "{\"test\":1234.5e-3}", -1); +// IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (doubleVal == (long double)12345.0 / 10000.0)); +// nats_JSONDestroy(json); +// json = NULL; +// doubleVal = 0; + +// test("Single field, double exp negative 3: "); +// s = nats_JSONParse(&json, "{\"test\":1234.5e-1}", -1); +// IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (doubleVal == (long double)12345.0 / 100.0)); +// nats_JSONDestroy(json); +// json = NULL; +// doubleVal = 0; + +// test("Single field, double exp negative 4: "); +// s = nats_JSONParse(&json, "{\"test\":1234.5e-0}", -1); +// IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (doubleVal == (long double)12345.0 / 10.0)); +// nats_JSONDestroy(json); +// json = NULL; +// doubleVal = 0; + +// test("Single field, double exp positive 1: "); +// s = nats_JSONParse(&json, "{\"test\":1234e+3}", -1); +// IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (doubleVal == (long double)1234.0 * 1000)); +// nats_JSONDestroy(json); +// json = NULL; +// doubleVal = 0; + +// test("Single field, double exp positive 2: "); +// s = nats_JSONParse(&json, "{\"test\":1234.5e+3}", -1); +// IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (doubleVal == (long double)12345.0 * 100.0)); +// nats_JSONDestroy(json); +// json = NULL; +// doubleVal = 0; + +// test("Single field, double exp positive 3: "); +// s = nats_JSONParse(&json, "{\"test\":1234.5678e+2}", -1); +// IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (doubleVal == (long double)12345678.0 / 100.0)); +// nats_JSONDestroy(json); +// json = NULL; +// doubleVal = 0; + +// test("Single field, double exp positive 4: "); +// s = nats_JSONParse(&json, "{\"test\":1234.5678e+4}", -1); +// IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (doubleVal == (long double)12345678.0 / 10000.0)); +// nats_JSONDestroy(json); +// json = NULL; +// doubleVal = 0; + +// test("Single field, double exp positive 5: "); +// s = nats_JSONParse(&json, "{\"test\":1234.5678e+5}", -1); +// IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (doubleVal == (long double)12345678.0 * 10.0)); +// nats_JSONDestroy(json); +// json = NULL; +// doubleVal = 0; + +// test("Single field, double exp positive 6: "); +// s = nats_JSONParse(&json, "{\"test\":1234.5678e+0}", -1); +// IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (doubleVal == (long double)12345678.0 / 10000.0)); +// nats_JSONDestroy(json); +// json = NULL; +// doubleVal = 0; + +// test("Single field, double exp positive 6: "); +// s = nats_JSONParse(&json, "{\"test\":1234.5678e1}", -1); +// IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (doubleVal == (long double)12345678.0 / 1000.0)); +// nats_JSONDestroy(json); +// json = NULL; +// doubleVal = 0; + +// test("Single field, bool: "); +// s = nats_JSONParse(&json, "{\"test\":true}", -1); +// IFOK(s, nats_JSONGetBool(json, "test", &boolVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && boolVal); +// nats_JSONDestroy(json); +// json = NULL; +// boolVal = false; + +// test("Single field, string array: "); +// s = nats_JSONParse(&json, "{\"test\":[\"a\",\"b\",\"c\",\"d\",\"e\"]}", -1); +// IFOK(s, nats_JSONDupStringArray(json, "test", &arrVal, &arrCount)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (arrCount == 5) && (strcmp(arrVal[0], "a") == 0) && (strcmp(arrVal[1], "b") == 0) && (strcmp(arrVal[2], "c") == 0) && (strcmp(arrVal[3], "d") == 0) && (strcmp(arrVal[4], "e") == 0)); +// nats_JSONDestroy(json); +// json = NULL; +// for (i = 0; i < arrCount; i++) +// free(arrVal[i]); +// free(arrVal); +// arrVal = NULL; +// arrCount = 0; + +// test("Single field, null string array: "); +// s = nats_JSONParse(&json, "{\"test\": null}", -1); +// IFOK(s, nats_JSONDupStringArray(json, "test", &arrVal, &arrCount)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (arrVal == NULL) && (arrCount == 0)); +// nats_JSONDestroy(json); +// json = NULL; + +// test("Single field, bool array: "); +// s = nats_JSONParse(&json, "{\"test\":[true, false, true]}", -1); +// IFOK(s, nats_JSONGetArrayBool(json, "test", &arrBoolVal, &arrCount)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (arrCount == 3) && arrBoolVal[0] && !arrBoolVal[1] && arrBoolVal[2]); +// nats_JSONDestroy(json); +// json = NULL; +// free(arrBoolVal); +// arrBoolVal = NULL; +// arrCount = 0; + +// test("Single field, double array: "); +// s = nats_JSONParse(&json, "{\"test\":[1.0, 2.0, 3.0]}", -1); +// IFOK(s, nats_JSONGetArrayDouble(json, "test", &arrDoubleVal, &arrCount)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (arrCount == 3) && (arrDoubleVal[0] == 1.0) && (arrDoubleVal[1] == 2.0) && (arrDoubleVal[2] == 3.0)); +// nats_JSONDestroy(json); +// json = NULL; +// free(arrDoubleVal); +// arrDoubleVal = NULL; +// arrCount = 0; + +// test("Single field, int array: "); +// s = nats_JSONParse(&json, "{\"test\":[1, 2, 3]}", -1); +// IFOK(s, nats_JSONGetArrayInt(json, "test", &arrIntVal, &arrCount)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (arrCount == 3) && (arrIntVal[0] == 1) && (arrIntVal[1] == 2) && (arrIntVal[2] == 3)); +// nats_JSONDestroy(json); +// json = NULL; +// free(arrIntVal); +// arrIntVal = NULL; +// arrCount = 0; + +// test("Single field, long array: "); +// s = nats_JSONParse(&json, "{\"test\":[1, 2, 3]}", -1); +// IFOK(s, nats_JSONGetArrayLong(json, "test", &arrLongVal, &arrCount)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (arrCount == 3) && (arrLongVal[0] == 1) && (arrLongVal[1] == 2) && (arrLongVal[2] == 3)); +// nats_JSONDestroy(json); +// json = NULL; +// free(arrLongVal); +// arrLongVal = NULL; +// arrCount = 0; + +// test("Single field, ulong array: "); +// s = nats_JSONParse(&json, "{\"test\":[1, 2, 3]}", -1); +// IFOK(s, nats_JSONGetArrayULong(json, "test", &arrULongVal, &arrCount)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (arrCount == 3) && (arrULongVal[0] == 1) && (arrULongVal[1] == 2) && (arrULongVal[2] == 3)); +// nats_JSONDestroy(json); +// json = NULL; +// free(arrULongVal); +// arrULongVal = NULL; +// arrCount = 0; + +// test("Single field, object array: "); +// s = nats_JSONParse(&json, "{\"test\":[{\"a\": 1},{\"b\": true}]}", -1); +// IFOK(s, nats_JSONGetArrayObject(json, "test", &arrObjVal, &arrCount)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (arrCount == 2) && (nats_JSONGetInt(arrObjVal[0], "a", &intVal) == NATS_OK) && (intVal == 1) && (nats_JSONGetBool(arrObjVal[1], "b", &boolVal) == NATS_OK) && boolVal); +// nats_JSONDestroy(json); +// json = NULL; +// free(arrObjVal); +// arrObjVal = NULL; +// arrCount = 0; +// intVal = 0; +// boolVal = false; + +// test("Single field, array null: "); +// s = nats_JSONParse(&json, "{\"test\":null}", -1); +// IFOK(s, nats_JSONGetArrayObject(json, "test", &arrObjVal, &arrCount)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (arrObjVal == NULL) && (arrCount == 0)); +// nats_JSONDestroy(json); +// json = NULL; + +// test("Single field, array array: "); +// s = nats_JSONParse(&json, "{\"test\":[[\"a\", \"b\"],[1, 2, 3],[{\"c\": true}]]}", -1); +// IFOK(s, nats_JSONGetArrayArray(json, "test", &arrArrVal, &arrCount)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (arrCount == 3) && (nats_JSONArrayAsStrings(arrArrVal[0], &arrVal, &arrCount) == NATS_OK) && (arrCount == 2) && (strcmp(arrVal[0], "a") == 0) && (strcmp(arrVal[1], "b") == 0) && (nats_JSONArrayAsInts(arrArrVal[1], &arrIntVal, &arrCount) == NATS_OK) && (arrCount == 3) && (arrIntVal[0] == 1) && (arrIntVal[1] == 2) && (arrIntVal[2] == 3) && (nats_JSONArrayAsObjects(arrArrVal[2], &arrObjVal, &arrCount) == NATS_OK) && (arrCount == 1) && (nats_JSONGetBool(arrObjVal[0], "c", &boolVal) == NATS_OK) && boolVal); +// nats_JSONDestroy(json); +// json = NULL; +// for (i = 0; i < 2; i++) +// free(arrVal[i]); +// free(arrVal); +// arrVal = NULL; +// free(arrIntVal); +// arrIntVal = NULL; +// free(arrArrVal); +// arrArrVal = NULL; +// free(arrObjVal); +// arrObjVal = NULL; +// boolVal = false; +// arrCount = 0; + +// test("Object: "); +// s = nats_JSONParse(&json, "{\"obj1\":{\"obj2\":{\"obj3\":{\"a\": 1},\"b\":true},\"c\":1.2},\"d\":3}", -1); +// IFOK(s, nats_JSONGetObject(json, "obj1", &obj1)); +// IFOK(s, nats_JSONGetObject(obj1, "obj2", &obj2)); +// IFOK(s, nats_JSONGetObject(obj2, "obj3", &obj3)); +// IFOK(s, nats_JSONGetInt(obj3, "a", &intVal)); +// IFOK(s, nats_JSONGetBool(obj2, "b", &boolVal)); +// IFOK(s, nats_JSONGetDouble(obj1, "c", &doubleVal)); +// IFOK(s, nats_JSONGetLong(json, "d", &longVal)); +// testCond((STILL_OK(s)) && (intVal == 1) && boolVal && (doubleVal == (long double)12.0 / 10.0) && (longVal == 3)); +// nats_JSONDestroy(json); +// json = NULL; +// intVal = 0; +// boolVal = false; +// doubleVal = 0.0; +// longVal = 0; + +// test("Object, null: "); +// s = nats_JSONParse(&json, "{\"obj\":null}", -1); +// IFOK(s, nats_JSONGetObject(json, "obj", &obj1)); +// testCond((STILL_OK(s)) && (obj1 == NULL)); +// nats_JSONDestroy(json); +// json = NULL; + +// test("All field types: "); +// s = nats_JSONParse(&json, "{\"bool\":true,\"str\":\"abc\",\"int\":123,\"long\":456,\"double\":123.5,\"array\":[\"a\"]}", -1); +// IFOK(s, nats_JSONGetBool(json, "bool", &boolVal)); +// IFOK(s, nats_JSONGetStr(json, "str", &strVal)); +// IFOK(s, nats_JSONGetInt(json, "int", &intVal)); +// IFOK(s, nats_JSONGetLong(json, "long", &longVal)); +// IFOK(s, nats_JSONGetDouble(json, "double", &doubleVal)); +// IFOK(s, nats_JSONDupStringArray(json, "array", &arrVal, &arrCount)); +// testCond((STILL_OK(s)) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 6) && boolVal && (strcmp(strVal, "abc") == 0) && (intVal == 123) && (longVal == 456) && (doubleVal == (long double)1235.0 / 10.0) && (arrCount == 1) && (strcmp(arrVal[0], "a") == 0)); +// test("Unknown field type: "); +// if (STILL_OK(s)) +// s = nats_JSONRefField(json, "int", 255, &f); +// testCond(s != NATS_OK); +// nats_JSONDestroy(json); +// json = NULL; +// free(strVal); +// strVal = NULL; +// boolVal = false; +// intVal = 0; +// longVal = 0; +// doubleVal = 0; +// for (i = 0; i < arrCount; i++) +// free(arrVal[i]); +// free(arrVal); +// arrVal = NULL; +// arrCount = 0; + +// test("Ask for wrong type: "); +// s = nats_JSONParse(&json, "{\"test\":true}", -1); +// IFOK(s, nats_JSONGetInt(json, "test", &intVal)); +// testCond((s != NATS_OK) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (intVal == 0)); +// nats_JSONDestroy(json); +// json = NULL; + +// test("Ask for wrong type (array): "); +// s = nats_JSONParse(&json, "{\"test\":[\"a\", \"b\"]}", -1); +// IFOK(s, nats_JSONRefArray(json, "test", TYPE_INT, &f)); +// testCond((s != NATS_OK) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (arrCount == 0) && (arrVal == NULL)); +// nats_JSONDestroy(json); +// json = NULL; + +// test("Ask for unknown type: "); +// s = nats_JSONParse(&json, "{\"test\":true}", -1); +// IFOK(s, nats_JSONRefField(json, "test", 9999, &f)); +// testCond((s == NATS_INVALID_ARG) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1)); +// nats_JSONDestroy(json); +// json = NULL; + +// test("Ask for unknown type (array): "); +// s = nats_JSONParse(&json, "{\"test\":true}", -1); +// IFOK(s, nats_JSONRefArray(json, "test", 9999, &f)); +// testCond((s == NATS_INVALID_ARG) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1)); +// nats_JSONDestroy(json); +// json = NULL; + +// test("Check no error and set to default for vars for unknown fields: "); +// { +// const char *initStr = "test"; +// const char *initStrArr[] = {"a", "b"}; + +// strVal = (char *)initStr; +// boolVal = true; +// intVal = 123; +// longVal = 456; +// doubleVal = 789; +// arrVal = (char **)initStrArr; +// arrCount = 2; +// s = nats_JSONParse(&json, "{\"test\":true}", -1); +// IFOK(s, nats_JSONGetStr(json, "str", &strVal)); +// IFOK(s, nats_JSONGetInt(json, "int", &intVal)); +// IFOK(s, nats_JSONGetLong(json, "long", &longVal)); +// IFOK(s, nats_JSONGetBool(json, "bool", &boolVal)); +// IFOK(s, nats_JSONGetDouble(json, "bool", &doubleVal)); +// IFOK(s, nats_JSONDupStringArray(json, "array", &arrVal, &arrCount)); +// testCond((STILL_OK(s)) && (strVal == NULL) && (boolVal == false) && (intVal == 0) && (longVal == 0) && (doubleVal == 0) && (arrCount == 0) && (arrVal == NULL)); +// nats_JSONDestroy(json); +// json = NULL; +// } + +// test("Wrong string type: "); +// strVal = NULL; +// s = nats_JSONParse(&json, "{\"test\":12345678901112}", -1); +// IFOK(s, nats_JSONGetStr(json, "test", &strVal)); +// testCond((s == NATS_INVALID_ARG) && (json != NULL) && (json->fields != NULL) && (json->fields->used == 1) && (strVal == NULL)); +// nats_JSONDestroy(json); +// json = NULL; + +// test("NULL string with -1 len: "); +// s = nats_JSONParse(&json, NULL, -1); +// testCond((s == NATS_INVALID_ARG) && (json == NULL)); +// nats_clearLastError(); + +// test("Field reused: "); +// s = nats_JSONParse(&json, "{\"field\":1,\"field\":2}", -1); +// IFOK(s, nats_JSONGetInt(json, "field", &intVal)); +// testCond((STILL_OK(s)) && (intVal == 2)); +// nats_JSONDestroy(json); +// json = NULL; + +// test("Nested arrays ok: "); +// jsonMaxNested = 10; +// s = nats_JSONParse(&json, "{\"test\":[[[1, 2]]]}", -1); +// testCond(STILL_OK(s)); +// nats_JSONDestroy(json); +// json = NULL; + +// test("Nested arrays not ok: "); +// jsonMaxNested = 10; +// s = nats_JSONParse(&json, "{\"test\":[[[[[[[[[[[[[1, 2]]]]]]]]]]]]]}", -1); +// testCond((s == NATS_ERR) && (json == NULL) && (strstr(nats_GetLastError(NULL), " nested arrays of 10") != NULL)); +// nats_clearLastError(); + +// test("Nested objects ok: "); +// s = nats_JSONParse(&json, "{\"test\":{\"a\":{\"b\":{\"c\":1}}}}", -1); +// testCond(STILL_OK(s)); +// nats_JSONDestroy(json); +// json = NULL; + +// test("Nested arrays not ok: "); +// jsonMaxNested = 10; +// s = nats_JSONParse(&json, "{\"test\":{\"a\":{\"b\":{\"c\":{\"d\":{\"e\":{\"f\":{\"g\":{\"h\":{\"i\":{\"j\":{\"k\":{\"l\":{\"m\":1}}}}}}}}}}}}}}", -1); +// testCond((s == NATS_ERR) && (json == NULL) && (strstr(nats_GetLastError(NULL), " nested objects of 10") != NULL)); +// nats_clearLastError(); +// jsonMaxNested = JSON_MAX_NEXTED; + +// // Negative tests +// { +// const char *badTimes[] = { +// "{\"time\":\"too small\"}", +// "{\"time\":\"2021-06-23T18:22:00.123456789-08:00X\"}", +// "{\"time\":\"2021-06-23T18:22:00X\"}", +// "{\"time\":\"2021-06-23T18:22:00-0800\"}", +// "{\"time\":\"2021-06-23T18:22:00-08.00\"}", +// "{\"time\":\"2021-06-23T18:22:00.Z\"}", +// "{\"time\":\"2021-06-23T18:22:00.abcZ\"}", +// "{\"time\":\"2021-06-23T18:22:00.abc-08:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.1234567890-08:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.1234567890Z\"}", +// "{\"time\":\"2021-06-23T18:22:00.123-0800\"}", +// }; +// const char *errorsTxt[] = { +// "too small", +// "too long", +// "invalid UTC offset", +// "invalid UTC offset", +// "invalid UTC offset", +// "is invalid", +// "is invalid", +// "is invalid", +// "too long", +// "second fraction", +// "invalid UTC offset", +// }; +// for (i = 0; i < (int)(sizeof(errorsTxt) / sizeof(char *)); i++) +// { +// longVal = 0; +// snprintf(buf, sizeof(buf), "Bad time '%s': ", badTimes[i]); +// test(buf); +// s = nats_JSONParse(&json, badTimes[i], -1); +// IFOK(s, nats_JSONGetTime(json, "time", &longVal)); +// testCond((s != NATS_OK) && (json != NULL) && (longVal == 0) && (strstr(nats_GetLastError(NULL), errorsTxt[i]) != NULL)); +// nats_clearLastError(); +// nats_JSONDestroy(json); +// json = NULL; +// } +// } + +// // Positive tests +// { +// const char *goodTimes[] = { +// "{\"time\":\"0001-01-01T00:00:00Z\"}", +// "{\"time\":\"1970-01-01T01:00:00+01:00\"}", +// "{\"time\":\"2021-06-23T18:22:00Z\"}", +// "{\"time\":\"2021-06-23T18:22:00.1Z\"}", +// "{\"time\":\"2021-06-23T18:22:00.12Z\"}", +// "{\"time\":\"2021-06-23T18:22:00.123Z\"}", +// "{\"time\":\"2021-06-23T18:22:00.1234Z\"}", +// "{\"time\":\"2021-06-23T18:22:00.12345Z\"}", +// "{\"time\":\"2021-06-23T18:22:00.123456Z\"}", +// "{\"time\":\"2021-06-23T18:22:00.1234567Z\"}", +// "{\"time\":\"2021-06-23T18:22:00.12345678Z\"}", +// "{\"time\":\"2021-06-23T18:22:00.123456789Z\"}", +// "{\"time\":\"2021-06-23T18:22:00-07:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.1-07:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.12-07:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.123-07:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.1234-07:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.12345-07:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.123456-07:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.1234567-07:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.12345678-07:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.123456789-07:00\"}", +// "{\"time\":\"2021-06-23T18:22:00+01:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.1+01:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.12+01:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.123+01:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.1234+01:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.12345+01:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.123456+01:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.1234567+01:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.12345678+01:00\"}", +// "{\"time\":\"2021-06-23T18:22:00.123456789+01:00\"}", +// }; +// int64_t results[] = { +// 0, +// 0, +// 1624472520000000000, +// 1624472520100000000, +// 1624472520120000000, +// 1624472520123000000, +// 1624472520123400000, +// 1624472520123450000, +// 1624472520123456000, +// 1624472520123456700, +// 1624472520123456780, +// 1624472520123456789, +// 1624497720000000000, +// 1624497720100000000, +// 1624497720120000000, +// 1624497720123000000, +// 1624497720123400000, +// 1624497720123450000, +// 1624497720123456000, +// 1624497720123456700, +// 1624497720123456780, +// 1624497720123456789, +// 1624468920000000000, +// 1624468920100000000, +// 1624468920120000000, +// 1624468920123000000, +// 1624468920123400000, +// 1624468920123450000, +// 1624468920123456000, +// 1624468920123456700, +// 1624468920123456780, +// 1624468920123456789, +// }; +// for (i = 0; i < (int)(sizeof(results) / sizeof(int64_t)); i++) +// { +// longVal = 0; +// snprintf(buf, sizeof(buf), "Time '%s' -> %" PRId64 ": ", goodTimes[i], results[i]); +// test(buf); +// s = nats_JSONParse(&json, goodTimes[i], -1); +// IFOK(s, nats_JSONGetTime(json, "time", &longVal)); +// testCond((STILL_OK(s)) && (json != NULL) && (longVal == results[i])); +// nats_JSONDestroy(json); +// json = NULL; +// } +// } + +// test("GetStr bad type: "); +// s = nats_JSONParse(&json, "{\"test\":true}", -1); +// IFOK(s, nats_JSONRefStr(json, "test", (const char **)&strVal)); +// testCond((s != NATS_OK) && (strVal == NULL)); +// nats_clearLastError(); +// nats_JSONDestroy(json); +// json = NULL; + +// test("GetStr: "); +// s = nats_JSONParse(&json, "{\"test\":\"direct\"}", -1); +// IFOK(s, nats_JSONRefStr(json, "test", (const char **)&strVal)); +// testCond((STILL_OK(s)) && (strVal != NULL) && (strcmp(strVal, "direct") == 0)); +// nats_JSONDestroy(json); +// json = NULL; + +// test("GetBytes bad type: "); +// s = nats_JSONParse(&json, "{\"test\":true}", -1); +// IFOK(s, nats_JSONGetBytes(json, "test", &bytes, &bl)); +// testCond((s != NATS_OK) && (bytes == NULL) && (bl == 0)); +// nats_clearLastError(); +// nats_JSONDestroy(json); +// json = NULL; + +// test("GetBytes: "); +// s = nats_JSONParse(&json, "{\"test\":\"dGhpcyBpcyB0ZXN0aW5nIGJhc2U2NCBlbmNvZGluZw==\"}", -1); +// IFOK(s, nats_JSONGetBytes(json, "test", &bytes, &bl)); +// testCond((STILL_OK(s)) && (bytes != NULL) && (bl == 31) && (strncmp((const char *)bytes, "this is testing base64 encoding", bl) == 0)); +// nats_clearLastError(); +// nats_JSONDestroy(json); +// json = NULL; +// free(bytes); + +// test("Range with wrong type: "); +// s = nats_JSONParse(&json, "{\"test\":123}", -1); +// IFOK(s, nats_JSONRange(json, TYPE_STR, 0, _dummyJSONCb, NULL)); +// testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "expected value type of"))); +// nats_clearLastError(); + +// test("Range with wrong num type: "); +// s = nats_JSONRange(json, TYPE_NUM, TYPE_INT, _dummyJSONCb, NULL); +// testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "expected numeric type of"))); +// nats_clearLastError(); + +// test("Range ok: "); +// ulongVal = 0; +// s = nats_JSONRange(json, TYPE_NUM, TYPE_UINT, _dummyJSONCb, &ulongVal); +// testCond((STILL_OK(s)) && (ulongVal == 123)); +// nats_JSONDestroy(json); +// json = NULL; + +// test("Range cb returns error: "); +// ulongVal = 0; +// s = nats_JSONParse(&json, "{\"fail\":123}", -1); +// IFOK(s, nats_JSONRange(json, TYPE_NUM, TYPE_UINT, _dummyJSONCb, &ulongVal)); +// testCond((s == NATS_INVALID_ARG) && (strstr(nats_GetLastError(NULL), "on purpose"))); +// nats_clearLastError(); +// nats_JSONDestroy(json); +// json = NULL; + +// test("Parse empty array: "); +// s = nats_JSONParse(&json, "{\"empty\":[]}", -1); +// testCond(STILL_OK(s)); + +// test("Get empty array array: "); +// s = nats_JSONGetArrayArray(json, "empty", &arrArrVal, &arrCount); +// testCond((STILL_OK(s)) && (arrArrVal == NULL) && (arrCount == 0)); + +// test("Get empty obj array: "); +// s = nats_JSONGetArrayObject(json, "empty", &arrObjVal, &arrCount); +// testCond((STILL_OK(s)) && (arrObjVal == NULL) && (arrCount == 0)); + +// test("Get empty ulong array: "); +// s = nats_JSONGetArrayULong(json, "empty", &arrULongVal, &arrCount); +// testCond((STILL_OK(s)) && (arrULongVal == NULL) && (arrCount == 0)); + +// test("Get empty long array: "); +// s = nats_JSONGetArrayLong(json, "empty", &arrLongVal, &arrCount); +// testCond((STILL_OK(s)) && (arrLongVal == NULL) && (arrCount == 0)); + +// test("Get empty int array: "); +// s = nats_JSONGetArrayInt(json, "empty", &arrIntVal, &arrCount); +// testCond((STILL_OK(s)) && (arrIntVal == NULL) && (arrCount == 0)); + +// test("Get empty double array: "); +// s = nats_JSONGetArrayDouble(json, "empty", &arrDoubleVal, &arrCount); +// testCond((STILL_OK(s)) && (arrDoubleVal == NULL) && (arrCount == 0)); + +// test("Get empty bool array: "); +// s = nats_JSONGetArrayBool(json, "empty", &arrBoolVal, &arrCount); +// testCond((STILL_OK(s)) && (arrBoolVal == NULL) && (arrCount == 0)); + +// test("Get empty string array: "); +// s = nats_JSONDupStringArray(json, "empty", &arrVal, &arrCount); +// testCond((STILL_OK(s)) && (arrVal == NULL) && (arrCount == 0)); + +// nats_JSONDestroy(json); +// json = NULL; +// } diff --git a/test/list.txt b/test/list.txt deleted file mode 100644 index 60be59288..000000000 --- a/test/list.txt +++ /dev/null @@ -1,301 +0,0 @@ -Version -VersionMatchesTag -OpenCloseAndWait -natsNowAndSleep -natsAllocSprintf -natsStrCaseStr -natsSnprintf -natsBuffer -natsParseInt64 -natsParseControl -natsNormalizeErr -natsMutex -natsThread -natsCondition -natsTimer -natsUrl -natsCreateStringFromBuffer -natsHash -natsHashing -natsStrHash -natsInbox -natsOptions -natsSock_ConnectTcp -natsSock_ShuffleIPs -natsSock_IPOrder -natsSock_ReadLine -natsJSON -natsEncodeTimeUTC -natsErrWithLongText -natsErrStackMoreThanMaxFrames -natsMsg -natsBase32 -natsBase64 -natsCRC16 -natsKeys -natsReadFile -natsGetJWTOrSeed -natsHostIsIP -natsWaitReady -natsSign -HeadersLift -HeadersAPIs -MsgIsJSControl -SrvVersionAtLeast -FormatStringArray -ReconnectServerStats -ParseStateReconnectFunctionality -ServersRandomize -SelectNextServer -ParserPing -ParserErr -ParserOK -ParseINFO -ParserShouldFail -ParserSplitMsg -ProcessMsgArgs -LibMsgDelivery -AsyncINFO -RequestPool -NoFlusherIfSendAsapOption -HeadersAndSubPendingBytes -DefaultConnection -SimplifiedURLs -IPResolutionOrder -UseDefaultURLIfNoServerSpecified -ConnectToWithMultipleURLs -ConnectionWithNULLOptions -ConnectionToWithNullURLs -ConnectionStatus -ConnClosedCB -CloseDisconnectedCB -ServerStopDisconnectedCB -ClosedConnections -ConnectVerboseOption -ReconnectThreadLeak -ReconnectTotalTime -ReconnectDisallowedFlags -ReconnectAllowedFlags -ConnCloseBreaksReconnectLoop -BasicReconnectFunctionality -ExtendedReconnectFunctionality -QueueSubsOnReconnect -IsClosed -IsReconnectingAndStatus -ReconnectBufSize -RetryOnFailedConnect -NoPartialOnReconnect -ReconnectFailsPendingRequests -ForcedReconnect -ErrOnConnectAndDeadlock -ErrOnMaxPayloadLimit -Auth -AuthFailNoDisconnectCB -AuthToken -AuthTokenHandler -PermViolation -AuthViolation -AuthenticationExpired -AuthenticationExpiredReconnect -ConnectedServer -MultipleClose -SimplePublish -SimplePublishNoData -PublishMsg -InvalidSubsArgs -AsyncSubscribe -AsyncSubscribeTimeout -SyncSubscribe -PubSubWithReply -NoResponders -Flush -ConnCloseDoesFlush -QueueSubscriber -ReplyArg -SyncReplyArg -Unsubscribe -DoubleUnsubscribe -SubRemovedWhileProcessingMsg -RequestTimeout -Request -RequestNoBody -RequestMuxWithMappedSubject -OldRequest -SimultaneousRequests -RequestClose -CustomInbox -MessagePadding -FlushInCb -ReleaseFlush -FlushErrOnDisconnect -Inbox -Stats -BadSubject -SubBadSubjectAndQueueNames -ClientAsyncAutoUnsub -ClientSyncAutoUnsub -ClientAutoUnsubAndReconnect -AutoUnsubNoUnsubOnDestroy -NextMsgOnClosedSub -CloseSubRelease -IsValidSubscriber -SlowSubscriber -SlowAsyncSubscriber -SlowConsumerCb -PendingLimitsDeliveredAndDropped -PendingLimitsWithSyncSub -AsyncSubscriptionPending -AsyncSubscriptionPendingDrain -SyncSubscriptionPending -SyncSubscriptionPendingDrain -AsyncErrHandlerMaxPendingMsgs -AsyncErrHandlerMaxPendingBytes -AsyncErrHandlerSubDestroyed -AsyncSubscriberStarvation -AsyncSubscriberOnClose -NextMsgCallOnAsyncSub -SubOnComplete -GetLastError -StaleConnection -ServerErrorClosesConnection -NoEcho -NoEchoOldServer -DrainSub -DrainSubStops -DrainSubRaceOnAutoUnsub -DrainSubNotResentOnReconnect -DrainConn -NoDoubleCloseCbOnDrain -GetClientID -GetClientIP -GetRTT -GetLocalIPAndPort -UserCredsCallbacks -UserCredsFromFiles -UserCredsFromMemory -NKey -NKeyFromSeed -ConnSign -WriteDeadline -HeadersNotSupported -HeadersBasic -MsgsFilter -EventLoop -EventLoopRetryOnFailedConnect -EventLoopTLS -SSLBasic -SSLVerify -SSLCAFromMemory -SSLCertAndKeyFromMemory -SSLVerifyHostname -SSLSkipServerVerification -SSLCiphers -SSLMultithreads -SSLConnectVerboseOption -SSLSocketLeakEventLoop -SSLReconnectWithAuthError -SSLAvailable -ServersOption -AuthServers -AuthFailToReconnect -ReconnectWithTokenHandler -BasicClusterReconnect -HotSpotReconnect -ProperReconnectDelay -ProperFalloutAfterMaxAttempts -StopReconnectAfterTwoAuthErr -TimeoutOnNoServer -PingReconnect -GetServers -GetDiscoveredServers -DiscoveredServersCb -IgnoreDiscoveredServers -INFOAfterFirstPONGisProcessedOK -ServerPoolUpdatedOnClusterUpdate -ReconnectJitter -CustomReconnectDelay -LameDuckMode -ReconnectImplicitUserInfo -JetStreamUnmarshalAccInfo -JetStreamUnmarshalStreamState -JetStreamUnmarshalStreamCfg -JetStreamUnmarshalStreamInfo -JetStreamMarshalStreamCfg -JetStreamUnmarshalConsumerInfo -JetStreamContext -JetStreamDomain -JetStreamMgtStreams -JetStreamMgtConsumers -JetStreamPublish -JetStreamPublishAsync -JetStreamPublishAckHandler -JetStreamSubscribe -JetStreamSubscribeSync -JetStreamSubscribeConfigCheck -JetStreamSubscribeIdleHeartbeat -JetStreamSubscribeFlowControl -JetStreamSubscribePull -JetStreamSubscribeHeadersOnly -JetStreamOrderedCons -JetStreamOrderedConsWithErrors -JetStreamOrderedConsAutoUnsub -JetStreamOrderedConsSrvRestart -JetStreamSubscribeWithFWC -JetStreamStreamsSealAndRollup -JetStreamGetMsgAndLastMsg -JetStreamConvertDirectMsg -JetStreamDirectGetMsg -JetStreamNakWithDelay -JetStreamBackOffRedeliveries -JetStreamInfoWithSubjects -JetStreamInfoAlternates -KeyValueManager -KeyValueBasics -KeyValueWatch -KeyValueWatchMulti -KeyValueHistory -KeyValueKeys -KeyValueDeleteVsPurge -KeyValueDeleteTombstones -KeyValueDeleteMarkerThreshold -KeyValueCrossAccount -KeyValueDiscardOldToNew -KeyValueRePublish -KeyValueMirrorDirectGet -KeyValueMirrorCrossDomains -MicroMatchEndpointSubject -MicroAddService -MicroGroups -MicroBasics -MicroStartStop -MicroServiceStopsOnClosedConn -MicroServiceStopsWhenServerStops -MicroAsyncErrorHandlerMaxPendingMsgs -MicroAsyncErrorHandlerMaxPendingBytes -StanPBufAllocator -StanConnOptions -StanSubOptions -StanMsg -StanServerNotReachable -StanBasicConnect -StanConnectError -StanBasicPublish -StanBasicPublishAsync -StanPublishTimeout -StanPublishMaxAcksInflight -StanBasicSubscription -StanSubscriptionCloseAndUnsub -StanDurableSubscription -StanBasicQueueSubscription -StanDurableQueueSubscription -StanCheckReceivedMsg -StanSubscriptionAckMsg -StanPings -StanPingsNoResponder -StanConnectionLostHandlerNotSet -StanPingsUnblockPublishCalls -StanGetNATSConnection -StanNoRetryOnFailedConnect -StanInternalSubsNotPooled -StanSubOnComplete -StanSubTimeout diff --git a/test/main_test.c b/test/main_test.c new file mode 100644 index 000000000..aac1efe5b --- /dev/null +++ b/test/main_test.c @@ -0,0 +1,151 @@ +// Copyright 2015-2023 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 + +#include "natsp.h" +#include "test.h" + +int __tests = 0; +bool __failed = false; +char __namebuf[1024]; + +struct test_s +{ + const char *name; + void (*f)(void); +}; + +#define _TEST_PROTO +#include "all_tests.h" +#undef _TEST_PROTO + +#define _TEST_LIST +struct test_s allTests[] = +{ +#include "all_tests.h" +}; +#undef _TEST_LIST + +#ifndef _WIN32 +static void _sigsegv_handler(int sig) +{ + void *array[20]; + int size = backtrace(array, 20); + + // print out all the frames to stderr + fprintf(stderr, "Error: signal %d:\n", sig); + backtrace_symbols_fd(array, size, STDERR_FILENO); + exit(1); +} +#endif // _WIN32 + +int main(int argc, char **argv) +{ + // const char *envStr; + + if (argc != 2) + { + fprintf(stderr, "Usage: %s [testname]\n", argv[0]); + return 0; + } + + if (strcmp(argv[1], "list") == 0) + { + for (int i = 0; i < (int) (sizeof(allTests) / sizeof(struct test_s)); i++) + printf("%s\n", allTests[i].name); + + return 0; + } + + const char *testname = argv[1]; + +#ifndef _WIN32 + // signal(SIGSEGV, _sigsegv_handler); +#endif // _WIN32 + + if (nats_open() != NATS_OK) + { + printf("@@ Unable to run tests: unable to initialize the library!\n"); + return 1; + } + + // Execute tests + for (int i = 0;i < (int)(sizeof(allTests) / sizeof(struct test_s)); i++) + { + if (strcmp(allTests[i].name, testname) != 0) + continue; + +#ifdef _WIN32 + printf("\n== %s ==\n", allTests[i].name); +#else + printf("\033[0;34m\n== %s ==\n\033[0;0m", allTests[i].name); +#endif + (*(allTests[i].f))(); + } + +#ifdef _WIN32 + if (logHandle != NULL) + { + CloseHandle(logHandle); + DeleteFile(LOGFILE_NAME); + } +#else + // remove(LOGFILE_NAME); +#endif + + // // Shutdown servers that are still running likely due to failed test + // { + // natsHash *pids = NULL; + // natsHashIter iter; + // int64_t key; + + // if (natsHash_Create(&pids, 16) == NATS_OK) + // { + // natsMutex_Lock(slMu); + // natsHashIter_Init(&iter, slMap); + // while (natsHashIter_Next(&iter, &key, NULL)) + // { + // natsHash_Set(pids, key, NULL, NULL); + // natsHashIter_RemoveCurrent(&iter); + // } + // natsHashIter_Done(&iter); + // natsHash_Destroy(slMap); + // slMap = NULL; + // natsMutex_Unlock(slMu); + + // natsHashIter_Init(&iter, pids); + // while (natsHashIter_Next(&iter, &key, NULL)) + // _stopServer((natsPid)key); + + // natsHash_Destroy(pids); + // } + // else + // { + // natsHash_Destroy(slMap); + // } + // natsMutex_Destroy(slMu); + // } + + // // Makes valgrind happy + // nats_CloseAndWait((failed ? 1 : 2000)); + + if (__failed) + { + printf("*** TEST FAILED ***\n"); + return 1; + } + + printf("ALL PASSED\n"); + return 0; +} diff --git a/test/mem_test.c b/test/mem_test.c new file mode 100644 index 000000000..99faa556d --- /dev/null +++ b/test/mem_test.c @@ -0,0 +1,253 @@ +// Copyright 2015-2023 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 "test.h" +#include "mem.h" + +void Test_MemAlignment(void) +{ + natsMemOptions *opts = &nats_defaultMemOptions; + test("Check memPages"); + testCond((nats_numPages(opts, 0) == 0) && + (nats_numPages(opts, 1) == 1) && + (nats_numPages(opts, opts->heapPageSize) == 1) && + (nats_numPages(opts, opts->heapPageSize + 1) == 2)); + + test("Check memPageAlignedSize"); + testCond((nats_pageAlignedSize(opts, 0) == 0) && + (nats_pageAlignedSize(opts, 1) == opts->heapPageSize) && + (nats_pageAlignedSize(opts, opts->heapPageSize - 1) == opts->heapPageSize) && + (nats_pageAlignedSize(opts, opts->heapPageSize) == opts->heapPageSize) && + (nats_pageAlignedSize(opts, opts->heapPageSize + 1) == 2 * opts->heapPageSize)); +} + +void Test_MemPoolAlloc(void) +{ + natsPool *pool = NULL; + natsMemOptions opts = nats_defaultMemOptions; + size_t expectedCurrentFreeBlockLen = 0; + + test("Set page size to 1024 bytes"); + opts.heapPageSize = 1024; + testCond(true); + + char name[] = "mem-test"; + test("Create pool"); + natsStatus s = nats_createPool(&pool, &opts, name); + size_t expectedLength = sizeof(natsPool) + sizeof(natsSmall) + strlen(name) + 1; + testCond((STILL_OK(s)) && + (pool != NULL) && + (pool->small != NULL) && + (pool->small->len == expectedLength)); + + // ------------------------------------------------ + // Small allocations. + + test("Allocate some small blocks in the first chunk"); + uint8_t *ptr1 = nats_palloc(pool, 10); + uint8_t *ptr2 = nats_palloc(pool, 20); + uint8_t *ptr3 = nats_palloc(pool, 30); + size_t prevLen = expectedLength; + expectedLength += 10 + 20 + 30; + testCond((ptr1 != NULL) && (ptr2 != NULL) && (ptr3 != NULL) && + (pool->small->next == NULL) && + (pool->small->len == expectedLength) && + (uint8_t *)pool->small + prevLen == ptr1 && + ptr2 == ptr1 + 10 && + ptr3 == ptr2 + 20); + + test("Allocate a block that fits exactly in the rest of the first chunk"); + uint8_t *ptr4 = nats_palloc(pool, opts.heapPageSize - expectedLength); + expectedLength = opts.heapPageSize; + testCond((ptr4 != NULL) && + (pool->small->next == NULL) && + (pool->small->len == expectedLength) && + ptr4 == ptr3 + 30); + + test("Allocate one more byte and see it make a new chunk"); + uint8_t *ptr5 = nats_palloc(pool, 1); + expectedLength = sizeof(natsSmall) + 1; + testCond((ptr5 != NULL) && + (pool->small->next != NULL) && + (pool->small->len == opts.heapPageSize) && + (pool->small->next->next == NULL) && + (pool->small->next->len == expectedLength) && + ptr5 == (uint8_t *)pool->small->next + sizeof(natsSmall)); + expectedCurrentFreeBlockLen = expectedLength; + + // ------------------------------------------------ + // natsBuf. + + test("Make a natsBuf and see it take another chunk"); + natsBuf *buf = NULL; + s = natsPool_getGrowableBuf(&buf, pool, 10); + expectedLength = opts.heapPageSize; + testCond((STILL_OK(s)) && + (buf != NULL) && + (pool->small->next != NULL) && + (pool->small->next->next != NULL) && + (pool->small->next->next->next == NULL) && + (pool->small->next->next->len == expectedLength) && + (buf->buf.data == (uint8_t *)pool->small->next->next + sizeof(natsSmall))); + + test("Check that natsBuf struct is allocated in the second chunk"); + expectedCurrentFreeBlockLen += sizeof(natsBuf); + testCond(pool->small->next->len == expectedCurrentFreeBlockLen); + + test("Fill up the second chunk"); + uint8_t *ptr6 = nats_palloc(pool, opts.heapPageSize - expectedCurrentFreeBlockLen); + expectedLength = opts.heapPageSize; + testCond((ptr6 != NULL) && + (pool->small->next->len == expectedLength) && + ptr6 == (uint8_t *)pool->small->next + expectedCurrentFreeBlockLen); + + test("Allocate more, to force another, 4th chunk"); + uint8_t *ptr7 = nats_palloc(pool, 10); + expectedLength = sizeof(natsSmall) + 10; + testCond((ptr7 != NULL) && + (pool->small->next->next->next != NULL) && + (pool->small->next->next->next->next == NULL) && + (pool->small->next->next->next->len == expectedLength) && + ptr7 == (uint8_t *)pool->small->next->next->next + sizeof(natsSmall)); + + test("Expand natsBuf into the heap, and allocate again, in the 3rd chunk that's returned"); + uint8_t aLotOfGarbage[2031]; + s = natsBuf_addBB(buf, aLotOfGarbage, sizeof(aLotOfGarbage)); + testCond((STILL_OK(s)) && + (pool->small->next->next->len == sizeof(natsSmall)) && + (pool->large != NULL) && (pool->large->prev == NULL) && + (buf->buf.data == pool->large->data) && + (memcmp(pool->large->data, aLotOfGarbage, sizeof(aLotOfGarbage)) == 0) && + (buf->buf.len == sizeof(aLotOfGarbage)) && + (buf->cap == nats_pageAlignedSize(&opts, sizeof(aLotOfGarbage)))); + + // ------------------------------------------------ + // Large allocations. + + test("Allocate 2 large blocks"); + uint8_t *ptr8 = nats_palloc(pool, opts.heapPageSize + 1); + uint8_t *ptr9 = nats_palloc(pool, opts.heapPageSize + 2); + testCond((ptr8 != NULL) && (ptr9 != NULL) && + (pool->large != NULL) && + (pool->large->data == ptr9) && + (pool->large->prev != NULL) && + (pool->large->prev->data == ptr8) && + (pool->large->prev->prev != NULL) && // natsBuf + (pool->large->prev->prev->prev == NULL)); + nats_releasePool(pool); + + // ------------------------------------------------ + // TODO: <>/<> Test ReadChain. + + // ------------------------------------------------ + // TODO: <>/<> Test recycle. + + // ------------------------------------------------ + // Error cases + + test("Set page size to 2 bytes"); + opts.heapPageSize = 2; + testCond(true); + + test("Fail to create pool"); + s = nats_createPool(&pool, &opts, "mem-test"); + testCond((s == NATS_INVALID_ARG)); +} + +static natsStatus _allocFilledChunk(natsPool *pool, size_t size, char fill) +{ + uint8_t *ptr = nats_palloc(pool, size); + if (ptr == NULL) + return NATS_NO_MEMORY; + memset(ptr, fill, size); + return NATS_OK; +} + +void Test_MemPoolRecycle(void) +{ + natsPool *pool = NULL; + natsMemOptions opts = nats_defaultMemOptions; + void *first, *second; + + test("Set page size to 1024 bytes"); + opts.heapPageSize = 1024; + testCond(true); + + test("Create pool"); + char name[] = "recycle-test"; + size_t expectedLengthFirst = sizeof(natsSmall) + sizeof(natsPool) + strlen(name) + 1; + natsStatus s = nats_createPool(&pool, &opts, name); + testCond(STILL_OK(s) && + pool->small->len == expectedLengthFirst); + + test("fill the rest of the first small chunk with 'A's"); + s = _allocFilledChunk(pool, opts.heapPageSize - pool->small->len, 'A'); + testCond(STILL_OK(s) && (pool->small->next == NULL) && (pool->small->len == opts.heapPageSize)); + first = pool->small; + + test("Allocate second small chunk wih B's"); + s = _allocFilledChunk(pool, opts.heapPageSize - sizeof(natsSmall), 'B'); + testCond(STILL_OK(s) && + (pool->small->next != NULL) && + (pool->small->next->len == opts.heapPageSize)); + second = pool->small->next; + + test("Allocate third small chunk wih C's"); + s = _allocFilledChunk(pool, opts.heapPageSize - sizeof(natsSmall), 'B'); + testCond(STILL_OK(s) && + (pool->small->next->next != NULL) && + (pool->small->next->next->len == opts.heapPageSize)); + + test("Get a read buffer"); + natsReadBuffer *rbuf = NULL; + s = natsPool_getReadBuffer(&rbuf, pool); + testCond(STILL_OK(s) && + (rbuf != NULL) && + (rbuf->buf.len == 0) && + (rbuf->readFrom == rbuf->buf.data)); + + test("Mark bytes 100:200 as remaining"); + memset(rbuf->buf.data, 'D', opts.readBufferSize); + rbuf->readFrom = rbuf->buf.data + 100; + rbuf->buf.len = 200; + testCond(true); + + test("Recycle pool"); + s = nats_recyclePool(&pool, &rbuf); + testCond(STILL_OK(s) && (pool != NULL) && (rbuf != NULL)); + + test("Check the first small's pointers"); + testCond((pool->small == first) && + (pool->small->len == expectedLengthFirst + sizeof(natsReadBuffer) + sizeof(natsReadChain)) && + (pool->small->next != NULL)); + + test("Check that the first small is zeroed out"); + bool zeroed = true; + uint8_t *ptr = (uint8_t *)pool->small + pool->small->len; + for (size_t i = 0; i < opts.heapPageSize - pool->small->len; i++, ptr++) + { + if (*ptr != 0) + { + printf("First small chunk not zeroed out at %zu %zu\n", i, i); + zeroed = false; + break; + } + } + testCond(zeroed); + + test("Check the second small's pointers"); + testCond( (pool->small->next == second) && + (pool->small->next->len == sizeof(natsSmall)) && + (pool->small->next->next == NULL)); +} diff --git a/test/permissions.conf b/test/permissions.conf deleted file mode 100644 index ee40d4c13..000000000 --- a/test/permissions.conf +++ /dev/null @@ -1,12 +0,0 @@ -authorization { - users = [ - { - user: ivan - password: pwd - permissions: { - publish="foo" - subscribe="bar" - } - } - ] -} diff --git a/test/test.c b/test/test.c deleted file mode 100644 index a2c898038..000000000 --- a/test/test.c +++ /dev/null @@ -1,36758 +0,0 @@ -// Copyright 2015-2023 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 -#include -#include -#ifdef _WIN32 -#else -#include -#include -#include -#endif - -#include "buf.h" -#include "timer.h" -#include "url.h" -#include "opts.h" -#include "../src/util.h" -#include "hash.h" -#include "conn.h" -#include "sub.h" -#include "msg.h" -#include "stats.h" -#include "comsock.h" -#include "crypto.h" -#include "nkeys.h" -#include "parser.h" -#include "js.h" -#include "kv.h" -#include "microp.h" -#if defined(NATS_HAS_STREAMING) -#include "stan/conn.h" -#include "stan/pub.h" -#include "stan/sub.h" -#include "stan/copts.h" -#include "stan/sopts.h" -#endif - -static int tests = 0; -static bool failed = false; - -static bool keepServerOutput = false; -static bool valgrind = false; -static bool runOnTravis = false; - -static const char *natsServerExe = "nats-server"; -static const char *serverVersion = NULL; - -static const char *natsStreamingServerExe = "nats-streaming-server"; - -static natsMutex *slMu = NULL; -static natsHash *slMap = NULL; - -#define test(s) { printf("#%02d ", ++tests); printf("%s", (s)); fflush(stdout); } -#ifdef _WIN32 -#define NATS_INVALID_PID (NULL) -#define testCond(c) if(c) { printf("PASSED\n"); fflush(stdout); } else { printf("FAILED\n"); nats_PrintLastErrorStack(stdout); fflush(stdout); failed=true; return; } -#define testCondNoReturn(c) if(c) { printf("PASSED\n"); fflush(stdout); } else { printf("FAILED\n"); nats_PrintLastErrorStack(stdout); fflush(stdout); failed=true; } -#define LOGFILE_NAME "wserver.log" -#else -#define NATS_INVALID_PID (-1) -#define testCond(c) if(c) { printf("\033[0;32mPASSED\033[0;0m\n"); fflush(stdout); } else { printf("\033[0;31mFAILED\033[0;0m\n"); nats_PrintLastErrorStack(stdout); fflush(stdout); failed=true; return; } -#define testCondNoReturn(c) if(c) { printf("\033[0;32mPASSED\033[0;0m\n"); fflush(stdout); } else { printf("\033[0;31mFAILED\033[0;0m\n"); nats_PrintLastErrorStack(stdout); fflush(stdout); failed=true; } -#define LOGFILE_NAME "server.log" -#endif -#define FAIL(m) { printf("@@ %s @@\n", (m)); failed=true; return; } - -#define CHECK_SERVER_STARTED(p) if ((p) == NATS_INVALID_PID) FAIL("Unable to start or verify that the server was started!") - -static const char *testServers[] = {"nats://127.0.0.1:1222", - "nats://127.0.0.1:1223", - "nats://127.0.0.1:1224", - "nats://127.0.0.1:1225", - "nats://127.0.0.1:1226", - "nats://127.0.0.1:1227", - "nats://127.0.0.1:1228"}; - -#if defined(NATS_HAS_STREAMING) -static const char *clusterName = "test-cluster"; -static const char *clientName = "client"; -#endif - -// Forward declaration -static void _startMockupServerThread(void *closure); -static void _createConfFile(char *buf, int bufLen, const char *content); - -typedef natsStatus (*testCheckInfoCB)(char *buffer); - -struct threadArg -{ - natsMutex *m; - natsThread *t; - natsCondition *c; - natsCondition *b; - int control; - bool current; - int sum; - int timerFired; - int timerStopped; - natsStrHash *inboxes; - natsStatus status; - const char* string; - bool connected; - bool disconnected; - int64_t disconnectedAt[4]; - int64_t disconnects; - bool closed; - bool reconnected; - int64_t reconnectedAt[4]; - int reconnects; - bool msgReceived; - int microRunningServiceCount; - bool microAllDone; - bool done; - int results[10]; - const char *tokens[3]; - int tokenCallCount; - testCheckInfoCB checkInfoCB; - natsSock sock; - - natsSubscription *sub; - natsOptions *opts; - natsConnection *nc; - jsCtx *js; - natsBuffer *buf; - -#if defined(NATS_HAS_STREAMING) - stanConnection *sc; - int redelivered; - const char* channel; - stanMsg *sMsg; -#endif - - int attached; - int detached; - bool evStop; - bool doRead; - bool doWrite; - -}; - -static bool -serverVersionAtLeast(int major, int minor, int update) -{ - int ma = 0; - int mi = 0; - int up = 0; - char *version = NULL; - - if (serverVersion == NULL) - return false; - - version = strstr(serverVersion, "version "); - if (version != NULL) - { - version += 8; - } - else - { - version = strstr(serverVersion, " v"); - if (version == NULL) - return false; - - version += 2; - } - - sscanf(version, "%d.%d.%d", &ma, &mi, &up); - if ((ma > major) || ((ma == major) && (mi > minor)) || ((ma == major) && (mi == minor) && (up >= update))) - return true; - - return false; -} - -static void -_waitSubPending(natsSubscription *sub, int expected) -{ - int mc = 0; - do - { - natsSubscription_GetPending(sub, &mc, NULL); - if (mc != expected) - nats_Sleep(15); - } - while (mc != expected); -} - -static natsStatus -_createDefaultThreadArgsForCbTests( - struct threadArg *arg) -{ - natsStatus s; - - memset(arg, 0, sizeof(struct threadArg)); - - s = natsMutex_Create(&(arg->m)); - if (s == NATS_OK) - s = natsCondition_Create(&(arg->c)); - - return s; -} - -void -_destroyDefaultThreadArgs(struct threadArg *args) -{ - if (valgrind) - nats_Sleep(100); - - natsMutex_Destroy(args->m); - natsCondition_Destroy(args->c); -} - -static void -test_natsNowAndSleep(void) -{ - int64_t start; - int64_t end; - - test("Check now and sleep: ") - start = nats_Now(); - nats_Sleep(1000); - end = nats_Now(); - testCond(((end - start) >= 990) && ((end - start) <= 1010)); -} - -static void -test_natsAllocSprintf(void) -{ - char smallStr[20]; - char mediumStr[256]; // This is the size of the temp buffer in nats_asprintf - char largeStr[1024]; - char *ptr = NULL; - int ret; - - memset(smallStr, 'A', sizeof(smallStr) - 1); - smallStr[sizeof(smallStr) - 1] = '\0'; - - memset(mediumStr, 'B', sizeof(mediumStr) - 1); - mediumStr[sizeof(mediumStr) - 1] = '\0'; - - memset(largeStr, 'C', sizeof(largeStr) - 1); - largeStr[sizeof(largeStr) - 1] = '\0'; - - test("Check alloc sprintf with small string: "); - ret = nats_asprintf(&ptr, "%s", smallStr); - testCond((ret >= 0) - && (strcmp(ptr, smallStr) == 0)); - - free(ptr); - ptr = NULL; - - test("Check alloc sprintf with medium string: "); - ret = nats_asprintf(&ptr, "%s", mediumStr); - testCond((ret >= 0) - && (strcmp(ptr, mediumStr) == 0)); - - free(ptr); - ptr = NULL; - - test("Check alloc sprintf with large string: "); - ret = nats_asprintf(&ptr, "%s", largeStr); - testCond((ret >= 0) - && (strcmp(ptr, largeStr) == 0)); - - free(ptr); - ptr = NULL; -} - -static void -test_natsStrCaseStr(void) -{ - const char *s1 = "Hello World!"; - const char *s2 = "wo"; - const char *res = NULL; - - test("StrStr case insensitive (equal): "); - res = nats_strcasestr(s1, s1); - testCond((res != NULL) - && (strcmp(res, s1) == 0) - && (res == s1)); - - test("StrStr case insensitive (match): "); - res = nats_strcasestr(s1, s2); - testCond((res != NULL) - && (strcmp(res, "World!") == 0) - && (res == (s1 + 6))); - - test("StrStr case insensitive (no match): "); - res = nats_strcasestr(s1, "xx"); - testCond(res == NULL); -} - -static void -test_natsSnprintf(void) -{ -#if _WIN32 - // This test is specific to older version of Windows - // that did not provide snprintf... - char buf[5]; - - test("snprintf over limit: "); - snprintf(buf, sizeof(buf), "%s", "abcdefghijklmnopqrstuvwxyz"); - testCond(strcmp(buf, "abcd") == 0); -#else - test("Skip when not running on Windows: "); - testCond(true); -#endif -} - -static void test_natsBuffer(void) -{ - natsStatus s; - char backend[10]; - natsBuffer *buf = NULL; - natsBuffer stackBuf; - int oldCapacity = 0; - - printf("== Buffer without data ==\n"); - - test("Create buffer owning its data: "); - s = natsBuf_Create(&buf, 1); - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 0) - && (natsBuf_Capacity(buf) == 1)); - - test("Append less than capacity does not expand buffer: "); - s = natsBuf_Append(buf, "a", 1); - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 1) - && (natsBuf_Capacity(buf) == 1) - && (natsBuf_Available(buf) == 0)); - - test("Appending one more (AppendByte) increases capacity: "); - oldCapacity = natsBuf_Capacity(buf); - s = natsBuf_AppendByte(buf, 'b'); - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 2) - && (natsBuf_Capacity(buf) > oldCapacity) - && (natsBuf_Available(buf) > 0)); - - test("Checking content: "); - testCond((s == NATS_OK) - && (natsBuf_Data(buf) != NULL) - && (strncmp(natsBuf_Data(buf), "ab", 2) == 0)); - - natsBuf_Destroy(buf); - buf = NULL; - - oldCapacity = 0; - test("Appending one more byte increases capacity: "); - s = natsBuf_Create(&buf, 1); - IFOK(s, natsBuf_Append(buf, "a", 1)); - if (s == NATS_OK) - { - oldCapacity = natsBuf_Capacity(buf); - - // Add one more! - s = natsBuf_Append(buf, "b", 1); - } - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 2) - && (natsBuf_Capacity(buf) > oldCapacity) - && (natsBuf_Available(buf) > 0)); - - natsBuf_Destroy(buf); - buf = NULL; - - printf("\n== Buffer with data ==\n"); - - memset(backend, 0, sizeof(backend)); - - test("Create buffer with backend: "); - s = natsBuf_CreateWithBackend(&buf, backend, 0, 5); - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 0) - && (natsBuf_Capacity(buf) == 5)); - - test("Check that changes are reflected in backend") - s = natsBuf_Append(buf, "abcd", 4); - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 4) - && (natsBuf_Capacity(buf) == 5) - && (natsBuf_Available(buf) > 0) - && (strcmp(backend, "abcd") == 0)); - - test("Changing backend is reflected in buffer: "); - backend[1] = 'x'; - testCond((s == NATS_OK) - && (natsBuf_Data(buf)[1] == 'x')); - - test("Append less than capacity does not expand buffer: "); - s = natsBuf_AppendByte(buf, 'e'); - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 5) - && (natsBuf_Capacity(buf) == 5) - && (natsBuf_Available(buf) == 0)); - - test("Check natsBuf_Expand returns error for invalid arguments: "); - { - natsStatus ls; - - ls = natsBuf_Expand(buf, -10); - if (ls != NATS_OK) - ls = natsBuf_Expand(buf, 0); - if (ls != NATS_OK) - ls = natsBuf_Expand(buf, natsBuf_Capacity(buf)); - testCond(ls != NATS_OK); - } - - test("Adding more causes expand: "); - oldCapacity = natsBuf_Capacity(buf); - s = natsBuf_Append(buf, "fghij", 5); - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 10) - && (natsBuf_Capacity(buf) > oldCapacity)); - - test("Check that the backend did not change"); - testCond((s == NATS_OK) - && (strcmp(backend, "axcde") == 0)); - - test("Checking content: "); - testCond((s == NATS_OK) - && (natsBuf_Data(buf) != NULL) - && (strncmp(natsBuf_Data(buf), "axcdefghij", 10) == 0)); - - test("Destroying buffer does not affect backend: "); - natsBuf_Destroy(buf); - buf = NULL; - testCond(strcmp(backend, "axcde") == 0); - - printf("\n== Buffer Init without data ==\n"); - - test("Create buffer owning its data: "); - s = natsBuf_Init(&stackBuf, 10); - testCond((s == NATS_OK) - && (buf = &stackBuf) - && (natsBuf_Len(buf) == 0) - && (natsBuf_Capacity(buf) == 10)); - - test("Append less than capacity does not expand buffer: "); - s = natsBuf_Append(buf, "abcdefghij", 10); - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 10) - && (natsBuf_Capacity(buf) == 10) - && (natsBuf_Available(buf) == 0)); - - test("Appending one more increases capacity: "); - oldCapacity = natsBuf_Capacity(buf); - s = natsBuf_AppendByte(buf, 'k'); - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 11) - && (natsBuf_Capacity(buf) > oldCapacity) - && (natsBuf_Available(buf) > 0)); - - test("Checking content: "); - testCond((s == NATS_OK) - && (natsBuf_Data(buf) != NULL) - && (strncmp(natsBuf_Data(buf), "abcdefghijk", 11) == 0)); - - test("Destroying buffer: "); - natsBuf_Destroy(buf); - testCond((natsBuf_Data(buf) == NULL) - && (natsBuf_Len(buf) == 0) - && (natsBuf_Capacity(buf) == 0) - && (natsBuf_Available(buf) == 0)); - buf = NULL; - - printf("\n== Buffer Init with data ==\n"); - - memset(backend, 0, sizeof(backend)); - - test("Create buffer with backend: "); - s = natsBuf_InitWithBackend(&stackBuf, backend, 0, 5); - testCond((s == NATS_OK) - && (buf = &stackBuf) - && (natsBuf_Len(buf) == 0) - && (natsBuf_Capacity(buf) == 5)); - - test("Check that changes are reflected in backend: ") - s = natsBuf_Append(buf, "abcd", 4); - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 4) - && (natsBuf_Capacity(buf) == 5) - && (natsBuf_Available(buf) > 0) - && (strcmp(backend, "abcd") == 0)); - - test("Changing backend is reflected in buffer: "); - testCond((s == NATS_OK) - && (backend[1] = 'x') - && (natsBuf_Data(buf)[1] == 'x')); - - test("Append less than capacity does not expand buffer: "); - s = natsBuf_AppendByte(buf, 'e'); - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 5) - && (natsBuf_Capacity(buf) == 5) - && (natsBuf_Available(buf) == 0)); - - test("Adding more causes expand: "); - s = natsBuf_Append(buf, "fghij", 5); - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 10) - && (natsBuf_Capacity(buf) >= 10)); - - test("Check that the backend did not change"); - testCond((s == NATS_OK) - && (strcmp(backend, "axcde") == 0)); - - test("Checking content: "); - testCond((s == NATS_OK) - && (natsBuf_Data(buf) != NULL) - && (strncmp(natsBuf_Data(buf), "axcdefghij", 10) == 0)); - - test("Destroying buffer does not affect backend: "); - natsBuf_Destroy(buf); - testCond(strcmp(backend, "axcde") == 0); - - test("Destroyed buffer state is clean: "); - testCond((s == NATS_OK) - && (natsBuf_Data(buf) == NULL) - && (natsBuf_Len(buf) == 0) - && (natsBuf_Capacity(buf) == 0) - && (natsBuf_Available(buf) == 0)); - - buf = NULL; - - test("Check maximum size: "); - s = natsBuf_Create(&buf, 5); - IFOK(s, natsBuf_Append(buf, "abcd", 4)); - IFOK(s, natsBuf_Append(buf, "fake size that goes over int max size", 0x7FFFFFFC)); - testCond(s == NATS_NO_MEMORY); - - test("Check maximum size (append byte): "); - buf->len = 0x7FFFFFFE; - s = natsBuf_Append(buf, "e", 1); - testCond(s == NATS_NO_MEMORY); - - natsBuf_Destroy(buf); - buf = NULL; - - test("Consume half: "); - s = natsBuf_Create(&buf, 10); - IFOK(s, natsBuf_Append(buf, "abcdefghij", 10)); - if (s == NATS_OK) - natsBuf_Consume(buf, 5); - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 5) - && (strncmp(natsBuf_Data(buf), "fghij", 5) == 0) - && (natsBuf_Available(buf) == 5) - && (*(buf->pos) == 'f')); - - test("Consume rest: "); - natsBuf_Consume(buf, 5); - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 0) - && (natsBuf_Available(buf) == 10) - && (*(buf->pos) == 'f')); - - natsBuf_Destroy(buf); - buf = NULL; - - test("MoveTo (forward): "); - s = natsBuf_Create(&buf, 100); - if (s == NATS_OK) - { - memcpy(natsBuf_Data(buf), "this is a test", 14); - natsBuf_MoveTo(buf, 14); - memcpy(natsBuf_Data(buf)+14, " of move by", 11); - natsBuf_MoveTo(buf, 14+11); - } - IFOK(s, natsBuf_AppendByte(buf, '\0')); - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 26) - && (strcmp(natsBuf_Data(buf), "this is a test of move by") == 0)); - - test("MoveTo (backward): "); - natsBuf_MoveTo(buf, 14); - s = natsBuf_AppendByte(buf, '\0'); - testCond((s == NATS_OK) - && (natsBuf_Len(buf) == 15) - && (strcmp(natsBuf_Data(buf), "this is a test") == 0)); - - natsBuf_Destroy(buf); - buf = NULL; -} - -static void -test_natsParseInt64(void) -{ - int64_t n; - - test("Parse with non numeric: "); - n = nats_ParseInt64("a", 1); - testCond(n == -1); - - test("Parse with NULL buffer: "); - n = nats_ParseInt64(NULL, 0); - testCond(n == -1); - - test("Parse with 0 buffer size: "); - n = nats_ParseInt64("whatever", 0); - testCond(n == -1); - - test("Parse with '0': "); - n = nats_ParseInt64("0", 1); - testCond(n == 0); - - test("Parse with '1': "); - n = nats_ParseInt64("1", 1); - testCond(n == 1); - - test("Parse with '12': "); - n = nats_ParseInt64("12", 2); - testCond(n == 12); - - test("Parse with '-12': "); - n = nats_ParseInt64("-12", 3); - testCond(n == -1); - - test("Parse with trailing spaces: "); - n = nats_ParseInt64("12 ", 3); - testCond(n == -1); - - test("Parse with leading spaces: "); - n = nats_ParseInt64(" 12", 3); - testCond(n == -1); - - test("Parse with 'INT64_MAX': "); - n = nats_ParseInt64("9223372036854775807", 19); - testCond(n == INT64_MAX); - - test("Parse with overflow(1): "); - n = nats_ParseInt64("9223372036854775809", 19); - testCond(n == -1); - - test("Parse with overflow(2): "); - n = nats_ParseInt64("92233720368547758099223372036854775809", 38); - testCond(n == -1); - - test("Parse with '12345': "); - n = nats_ParseInt64("12345", 5); - testCond(n == 12345); - - test("Parse with '123.45': "); - n = nats_ParseInt64("123.45", 6); - testCond(n == -1); -} - -static void -test_natsParseControl(void) -{ - natsStatus s; - natsControl c; - - c.op = NULL; - c.args = NULL; - - test("Test with NULL line: "); - s = nats_ParseControl(&c, NULL); - testCond(s == NATS_PROTOCOL_ERROR); - - test("Test line with single op: "); - s = nats_ParseControl(&c, "op"); - testCond((s == NATS_OK) - && (c.op != NULL) - && (strcmp(c.op, "op") == 0) - && (c.args == NULL)); - - free(c.op); - free(c.args); - c.op = NULL; - c.args = NULL; - - test("Test line with trailing spaces: "); - s = nats_ParseControl(&c, "op "); - testCond((s == NATS_OK) - && (c.op != NULL) - && (strcmp(c.op, "op") == 0) - && (c.args == NULL)); - - free(c.op); - free(c.args); - c.op = NULL; - c.args = NULL; - - test("Test line with op and args: "); - s = nats_ParseControl(&c, "op args"); - testCond((s == NATS_OK) - && (c.op != NULL) - && (strcmp(c.op, "op") == 0) - && (c.args != NULL) - && (strcmp(c.args, "args") == 0)); - - free(c.op); - free(c.args); - c.op = NULL; - c.args = NULL; - - test("Test line with op and args and trailing spaces: "); - s = nats_ParseControl(&c, "op args "); - testCond((s == NATS_OK) - && (c.op != NULL) - && (strcmp(c.op, "op") == 0) - && (c.args != NULL) - && (strcmp(c.args, "args") == 0)); - - free(c.op); - free(c.args); - c.op = NULL; - c.args = NULL; - - test("Test line with op and args args: "); - s = nats_ParseControl(&c, "op args args "); - testCond((s == NATS_OK) - && (c.op != NULL) - && (strcmp(c.op, "op") == 0) - && (c.args != NULL) - && (strcmp(c.args, "args args") == 0)); - - free(c.op); - free(c.args); - c.op = NULL; - c.args = NULL; -} - -static void -test_natsNormalizeErr(void) -{ - char error[1024]; - char expected[256]; - - test("Check typical -ERR: "); - - snprintf(expected, sizeof(expected), "%s", "Simple Error"); - snprintf(error, sizeof(error), "-ERR '%s'", expected); - nats_NormalizeErr(error); - testCond(strcmp(error, expected) == 0); - - test("Check -ERR without quotes: "); - snprintf(expected, sizeof(expected), "%s", "Error Without Quotes"); - snprintf(error, sizeof(error), "-ERR %s", expected); - nats_NormalizeErr(error); - testCond(strcmp(error, expected) == 0); - - test("Check -ERR with spaces: "); - snprintf(expected, sizeof(expected), "%s", "Error With Surrounding Spaces"); - snprintf(error, sizeof(error), "-ERR '%s' ", expected); - nats_NormalizeErr(error); - testCond(strcmp(error, expected) == 0); - - test("Check -ERR with spaces and without quotes: "); - snprintf(expected, sizeof(expected), "%s", "Error With Surrounding Spaces And Without Quotes"); - snprintf(error, sizeof(error), "-ERR %s ", expected); - nats_NormalizeErr(error); - testCond(strcmp(error, expected) == 0); - - test("Check -ERR with quote on the left: "); - snprintf(expected, sizeof(expected), "%s", "Error With Quote On Left"); - snprintf(error, sizeof(error), "-ERR '%s", expected); - nats_NormalizeErr(error); - testCond(strcmp(error, expected) == 0); - - test("Check -ERR with quote on right: "); - snprintf(expected, sizeof(expected), "%s", "Error With Quote On Right"); - snprintf(error, sizeof(error), "-ERR %s'", expected); - nats_NormalizeErr(error); - testCond(strcmp(error, expected) == 0); - - test("Check -ERR with spaces and single quote: "); - snprintf(error, sizeof(error), "%s", "-ERR ' "); - nats_NormalizeErr(error); - testCond(error[0] == '\0'); -} - -static void -test_natsMutex(void) -{ - natsStatus s; - natsMutex *m = NULL; - bool locked = false; - - test("Create mutex: "); - s = natsMutex_Create(&m); - testCond(s == NATS_OK); - - test("Lock: "); - natsMutex_Lock(m); - testCond(1); - - test("Recursive locking: "); - locked = natsMutex_TryLock(m); - testCond(locked); - - test("Release recursive lock: "); - natsMutex_Unlock(m); - testCond(1); - - test("Unlock: "); - natsMutex_Unlock(m); - testCond(1); - - test("Destroy: "); - natsMutex_Destroy(m); - testCond(1); -} - -static void -testThread(void *arg) -{ - struct threadArg *tArg = (struct threadArg*) arg; - - natsMutex_Lock(tArg->m); - - tArg->control = 1; - tArg->current = natsThread_IsCurrent(tArg->t); - - natsMutex_Unlock(tArg->m); -} - -static void -sumThread(void *arg) -{ - struct threadArg *tArg = (struct threadArg*) arg; - - natsMutex_Lock(tArg->m); - - tArg->sum++; - - natsMutex_Unlock(tArg->m); -} - -static int NUM_THREADS = 1000; - -static void -test_natsThread(void) -{ - natsStatus s = NATS_OK; - natsMutex *m = NULL; - natsThread *t = NULL; - bool current = false; - struct threadArg tArgs; - natsThread **threads = NULL; - int i,j; - - if (valgrind) - NUM_THREADS = 100; - - test("Create threads array: "); - threads = (natsThread**) calloc(NUM_THREADS, sizeof(natsThread*)); - if (threads == NULL) - s = NATS_NO_MEMORY; - IFOK(s, natsMutex_Create(&m)); - testCond(s == NATS_OK); - - natsMutex_Lock(m); - - tArgs.m = m; - tArgs.control = 0; - tArgs.current = false; - - test("Create thread: "); - s = natsThread_Create(&t, testThread, &tArgs); - testCond(s == NATS_OK); - - tArgs.t = t; - - test("Check if thread current from other thread: "); - current = natsThread_IsCurrent(t); - testCond(!current); - - natsMutex_Unlock(m); - - test("Joining thread: ") - natsThread_Join(t); - testCond(1); - - natsMutex_Lock(m); - - test("Control updated: "); - testCond(tArgs.control == 1); - - test("Check thread current works from current thread: "); - testCond(tArgs.current); - - test("Destroy thread: "); - natsThread_Destroy(t); - testCond(1); - - tArgs.sum = 0; - - test("Creating multiple threads: "); - for (i=0; (s == NATS_OK) && (im); - - tArg->control = 1; - natsCondition_Signal(tArg->c); - - natsMutex_Unlock(tArg->m); -} - -static void -testBroadcast(void *arg) -{ - struct threadArg *tArg = (struct threadArg*) arg; - - natsMutex_Lock(tArg->m); - - tArg->sum++; - natsCondition_Signal(tArg->c); - - while (tArg->control == 0) - natsCondition_Wait(tArg->b, tArg->m); - - tArg->sum--; - - natsMutex_Unlock(tArg->m); -} - -static void -_unblockLongWait(void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - - nats_Sleep(500); - natsMutex_Lock(args->m); - natsCondition_Signal(args->c); - natsMutex_Unlock(args->m); -} - -static void -test_natsCondition(void) -{ - natsStatus s; - natsMutex *m = NULL; - natsThread *t1 = NULL; - natsThread *t2 = NULL; - natsCondition *c1 = NULL; - natsCondition *c2 = NULL; - struct threadArg tArgs; - int64_t before = 0; - int64_t diff = 0; - int64_t target = 0; - - test("Create mutex: "); - s = natsMutex_Create(&m); - testCond(s == NATS_OK); - - test("Create condition variables: "); - s = natsCondition_Create(&c1); - IFOK(s, natsCondition_Create(&c2)); - testCond(s == NATS_OK); - - natsMutex_Lock(m); - - tArgs.m = m; - tArgs.c = c1; - tArgs.control = 0; - - test("Create thread: "); - s = natsThread_Create(&t1, testSignal, &tArgs); - testCond(s == NATS_OK); - - test("Wait for signal: "); - while (tArgs.control != 1) - natsCondition_Wait(c1, m); - - natsThread_Join(t1); - natsThread_Destroy(t1); - t1 = NULL; - testCond(tArgs.control == 1); - - test("Wait timeout: "); - before = nats_Now(); - s = natsCondition_TimedWait(c1, m, 1000); - diff = (nats_Now() - before); - testCond((s == NATS_TIMEOUT) - && (diff >= 985) && (diff <= 1015)); - - test("Wait timeout with 0: "); - before = nats_Now(); - s = natsCondition_TimedWait(c1, m, 0); - diff = (nats_Now() - before); - testCond((s == NATS_TIMEOUT) - && (diff >= 0) && (diff <= 10)); - - test("Wait timeout with negative: "); - before = nats_Now(); - s = natsCondition_TimedWait(c1, m, -10); - diff = (nats_Now() - before); - testCond((s == NATS_TIMEOUT) - && (diff >= 0) && (diff <= 10)); - - test("Wait absolute time: "); - before = nats_Now(); - target = nats_setTargetTime(1000); - s = natsCondition_AbsoluteTimedWait(c1, m, target); - diff = (nats_Now() - before); - testCond((s == NATS_TIMEOUT) - && (diff >= 985) && (diff <= 1015)); - - test("Wait absolute time in the past: "); - before = nats_Now(); - target = nats_setTargetTime(-1000); - s = natsCondition_AbsoluteTimedWait(c1, m, target); - diff = (nats_Now() - before); - testCond((s == NATS_TIMEOUT) - && (diff >= 0) && (diff <= 10)); - - test("Wait absolute time with very large value: "); - tArgs.control = 0; - s = natsThread_Create(&t1, _unblockLongWait, &tArgs); - if (s == NATS_OK) - { - before = nats_Now(); - target = nats_setTargetTime(0x7FFFFFFFFFFFFFFF); - s = natsCondition_AbsoluteTimedWait(c1, m, target); - diff = (nats_Now() - before); - } - testCond((s == NATS_OK) - && (diff >= 400) && (diff <= 600)); - - natsThread_Join(t1); - natsThread_Destroy(t1); - t1 = NULL; - - test("Signal before wait: "); - tArgs.control = 0; - - test("Create thread: "); - s = natsThread_Create(&t1, testSignal, &tArgs); - testCond(s == NATS_OK); - - while (tArgs.control == 0) - { - natsMutex_Unlock(m); - nats_Sleep(1000); - natsMutex_Lock(m); - } - - s = natsCondition_TimedWait(c1, m, 1000); - testCond(s == NATS_TIMEOUT); - - natsThread_Join(t1); - natsThread_Destroy(t1); - t1 = NULL; - - test("Broadcast: "); - tArgs.control = 0; - tArgs.sum = 0; - tArgs.b = c2; - - s = natsThread_Create(&t1, testBroadcast, &tArgs); - IFOK(s, natsThread_Create(&t2, testBroadcast, &tArgs)); - if (s != NATS_OK) - { - natsMutex_Unlock(m); - FAIL("Unable to run test_natsCondition because got an error while creating thread!"); - } - - while (tArgs.sum != 2) - natsCondition_Wait(c1, m); - - natsMutex_Unlock(m); - - nats_Sleep(1000); - - natsMutex_Lock(m); - - tArgs.control = 1; - natsCondition_Broadcast(c2); - - natsMutex_Unlock(m); - - natsThread_Join(t1); - natsThread_Destroy(t1); - t1 = NULL; - - natsThread_Join(t2); - natsThread_Destroy(t2); - t2 = NULL; - - testCond(tArgs.sum == 0); - - test("Destroy condition: "); - natsCondition_Destroy(c1); - natsCondition_Destroy(c2); - testCond(1); - - natsMutex_Destroy(m); -} - -static void -testTimerCb(natsTimer *timer, void *arg) -{ - struct threadArg *tArg = (struct threadArg*) arg; - - natsMutex_Lock(tArg->m); - - tArg->timerFired++; - natsCondition_Signal(tArg->c); - - natsMutex_Unlock(tArg->m); - - if (tArg->control == 1) - natsTimer_Reset(timer, 500); - else if (tArg->control == 2) - natsTimer_Stop(timer); - else if (tArg->control == 3) - nats_Sleep(500); - - natsMutex_Lock(tArg->m); - - natsCondition_Signal(tArg->c); - - natsMutex_Unlock(tArg->m); -} - -static void -stopTimerCb(natsTimer *timer, void *arg) -{ - struct threadArg *tArg = (struct threadArg*) arg; - - natsMutex_Lock(tArg->m); - - tArg->timerStopped++; - natsCondition_Signal(tArg->c); - - natsMutex_Unlock(tArg->m); -} - -static void -_dummyTimerCB(natsTimer *timer, void *arg) {} - -static void -_timerStopCB(natsTimer *timer, void *arg) -{ - natsTimer_Release(timer); -} - -#define STOP_TIMER_AND_WAIT_STOPPED \ - natsTimer_Stop(t); \ - natsMutex_Lock(tArg.m); \ - while (tArg.timerStopped == 0) \ - natsCondition_Wait(tArg.c, tArg.m); \ - natsMutex_Unlock(tArg.m) - -static void -test_natsTimer(void) -{ - natsStatus s; - natsTimer *t = NULL; - struct threadArg tArg; - int refs; - - test("Setup test: "); - s = _createDefaultThreadArgsForCbTests(&tArg); - testCond(s == NATS_OK); - - tArg.control = 0; - tArg.timerFired = 0; - tArg.timerStopped = 0; - - test("Create timer: "); - s = natsTimer_Create(&t, testTimerCb, stopTimerCb, 400, &tArg); - testCond(s == NATS_OK); - - test("Stop timer: "); - tArg.control = 0; - natsTimer_Stop(t); - nats_Sleep(600); - natsMutex_Lock(t->mu); - refs = t->refs; - natsMutex_Unlock(t->mu); - natsMutex_Lock(tArg.m); - testCond((tArg.timerFired == 0) - && (tArg.timerStopped == 1) - && (refs == 1) - && (nats_getTimersCount() == 0)); - natsMutex_Unlock(tArg.m); - - test("Firing of timer: ") - tArg.control = 0; - tArg.timerStopped = 0; - natsTimer_Reset(t, 200); - nats_Sleep(1100); - natsTimer_Stop(t); - nats_Sleep(600); - natsMutex_Lock(t->mu); - refs = t->refs; - natsMutex_Unlock(t->mu); - natsMutex_Lock(tArg.m); - testCond((tArg.timerFired > 0) - && (tArg.timerFired <= 5) - && (tArg.timerStopped == 1) - && (refs == 1) - && (nats_getTimersCount() == 0)); - natsMutex_Unlock(tArg.m); - - test("Stop stopped timer: "); - tArg.control = 0; - tArg.timerFired = 0; - tArg.timerStopped = 0; - natsTimer_Reset(t, 100); - nats_Sleep(300); - natsTimer_Stop(t); - nats_Sleep(100); - natsTimer_Stop(t); - nats_Sleep(100); - natsMutex_Lock(t->mu); - refs = t->refs; - natsMutex_Unlock(t->mu); - natsMutex_Lock(tArg.m); - testCond((tArg.timerFired > 0) - && (tArg.timerStopped == 1) - && (refs == 1) - && (nats_getTimersCount() == 0)); - natsMutex_Unlock(tArg.m); - - tArg.control = 1; - tArg.timerFired = 0; - tArg.timerStopped = 0; - test("Reset from callback: "); - natsTimer_Reset(t, 250); - nats_Sleep(900); - natsTimer_Stop(t); - nats_Sleep(600); - natsMutex_Lock(t->mu); - refs = t->refs; - natsMutex_Unlock(t->mu); - natsMutex_Lock(tArg.m); - testCond((tArg.timerFired == 2) - && (tArg.timerStopped == 1) - && (refs == 1) - && nats_getTimersCount() == 0); - natsMutex_Unlock(tArg.m); - - tArg.control = 0; - tArg.timerFired = 0; - tArg.timerStopped = 0; - test("Multiple Reset: "); - natsTimer_Reset(t, 1000); - natsTimer_Reset(t, 800); - natsTimer_Reset(t, 200); - natsTimer_Reset(t, 500); - nats_Sleep(600); - natsMutex_Lock(t->mu); - refs = t->refs; - natsMutex_Unlock(t->mu); - natsMutex_Lock(tArg.m); - testCond((tArg.timerFired == 1) - && (tArg.timerStopped == 0) - && (refs == 1) - && nats_getTimersCount() == 1); - natsMutex_Unlock(tArg.m); - - STOP_TIMER_AND_WAIT_STOPPED; - - tArg.control = 3; - tArg.timerFired = 0; - tArg.timerStopped = 0; - test("Check refs while in callback: "); - natsTimer_Reset(t, 1); - - // Wait that it is in callback - natsMutex_Lock(tArg.m); - while (tArg.timerFired != 1) - natsCondition_Wait(tArg.c, tArg.m); - natsMutex_Unlock(tArg.m); - - natsMutex_Lock(t->mu); - refs = t->refs; - natsMutex_Unlock(t->mu); - testCond((refs == 2) - && nats_getTimersCountInList() == 0 - && nats_getTimersCount() == 1); - - STOP_TIMER_AND_WAIT_STOPPED; - - tArg.control = 2; - tArg.timerFired = 0; - tArg.timerStopped = 0; - test("Stop from callback: "); - natsTimer_Reset(t, 250); - nats_Sleep(500); - natsMutex_Lock(t->mu); - refs = t->refs; - natsMutex_Unlock(t->mu); - natsMutex_Lock(tArg.m); - testCond((tArg.timerFired == 1) - && (tArg.timerStopped == 1) - && (refs == 1) - && (nats_getTimersCount() == 0)); - natsMutex_Unlock(tArg.m); - - tArg.control = 3; - tArg.timerFired = 0; - tArg.timerStopped = 0; - test("Slow callback: "); - natsTimer_Reset(t, 100); - nats_Sleep(800); - natsTimer_Stop(t); - nats_Sleep(500); - natsMutex_Lock(t->mu); - refs = t->refs; - natsMutex_Unlock(t->mu); - natsMutex_Lock(tArg.m); - testCond((tArg.timerFired <= 3) - && (tArg.timerStopped == 1) - && (refs == 1) - && (nats_getTimersCount() == 0)); - natsMutex_Unlock(tArg.m); - - tArg.control = 3; - tArg.timerFired = 0; - tArg.timerStopped = 0; - test("Stopped while in callback: "); - natsTimer_Reset(t, 100); - nats_Sleep(200); - natsTimer_Stop(t); - nats_Sleep(700); - natsMutex_Lock(t->mu); - refs = t->refs; - natsMutex_Unlock(t->mu); - natsMutex_Lock(tArg.m); - testCond((tArg.timerFired == 1) - && (tArg.timerStopped == 1) - && (refs == 1) - && (nats_getTimersCount() == 0)); - natsMutex_Unlock(tArg.m); - - tArg.control = 4; - tArg.timerFired = 0; - tArg.timerStopped = 0; - test("Use very large timeout: "); - natsTimer_Reset(t, 0x7FFFFFFFFFFFFFFF); - nats_Sleep(200); - natsTimer_Stop(t); - natsMutex_Lock(t->mu); - refs = t->refs; - natsMutex_Unlock(t->mu); - natsMutex_Lock(tArg.m); - testCond((tArg.timerFired == 0) - && (tArg.timerStopped == 1) - && (refs == 1) - && (nats_getTimersCount() == 0)); - natsMutex_Unlock(tArg.m); - - test("Destroy timer: "); - natsMutex_Lock(t->mu); - t->refs++; - natsMutex_Unlock(t->mu); - natsTimer_Destroy(t); - natsMutex_Lock(t->mu); - refs = t->refs; - natsMutex_Unlock(t->mu); - testCond(refs == 1); - natsTimer_Release(t); - - _destroyDefaultThreadArgs(&tArg); - - // Create a timer that will not be stopped here to exercise - // code that cleans up timers when library is unloaded. - test("Create timer: "); - s = natsTimer_Create(&t, _dummyTimerCB, _timerStopCB, 1000, NULL); - testCond(s == NATS_OK); -} - -static void -test_natsUrl(void) -{ - natsStatus s; - natsUrl *u = NULL; - - test("NULL: "); - s = natsUrl_Create(&u, NULL); - testCond((s != NATS_OK) && (u == NULL)); - - test("EMPTY: "); - s = natsUrl_Create(&u, ""); - testCond((s != NATS_OK) && (u == NULL)); - - nats_clearLastError(); - - test("'localhost:':"); - s = natsUrl_Create(&u, "localhost:"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4222) - && (strcmp(u->fullUrl, "nats://localhost:4222") == 0)); - natsUrl_Destroy(u); - u = NULL; - - test("'localhost:4223':"); - s = natsUrl_Create(&u, "localhost:4223"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4223) - && (strcmp(u->fullUrl, "nats://localhost:4223") == 0)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://localhost:4222':"); - s = natsUrl_Create(&u, "tcp://localhost:4222"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://localhost':"); - s = natsUrl_Create(&u, "tcp://localhost"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'localhost':"); - s = natsUrl_Create(&u, "localhost"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://[::1]:4222':"); - s = natsUrl_Create(&u, "tcp://[::1]:4222"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "[::1]") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://[::1]:':"); - s = natsUrl_Create(&u, "tcp://[::1]:"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "[::1]") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://[::1]':"); - s = natsUrl_Create(&u, "tcp://[::1]"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "[::1]") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://':"); - s = natsUrl_Create(&u, "tcp://"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://:':"); - s = natsUrl_Create(&u, "tcp://:"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://ivan:localhost:4222':"); - s = natsUrl_Create(&u, "tcp://ivan:localhost:4222"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "ivan:localhost") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://ivan:pwd:localhost:4222':"); - s = natsUrl_Create(&u, "tcp://ivan:pwd:localhost:4222"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "ivan:pwd:localhost") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://ivan@localhost:4222':"); - s = natsUrl_Create(&u, "tcp://ivan@localhost:4222"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username != NULL) - && (strcmp(u->username, "ivan") == 0) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://ivan:pwd@localhost:4222':"); - s = natsUrl_Create(&u, "tcp://ivan:pwd@localhost:4222"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username != NULL) - && (strcmp(u->username, "ivan") == 0) - && (u->password != NULL) - && (strcmp(u->password, "pwd") == 0) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://ivan:pwd@localhost':"); - s = natsUrl_Create(&u, "tcp://ivan:pwd@localhost"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username != NULL) - && (strcmp(u->username, "ivan") == 0) - && (u->password != NULL) - && (strcmp(u->password, "pwd") == 0) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://@localhost:4222':"); - s = natsUrl_Create(&u, "tcp://@localhost:4222"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://@@localhost:4222':"); - s = natsUrl_Create(&u, "tcp://@@localhost:4222"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username != NULL) - && (strcmp(u->username, "@") == 0) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://a:b:c@localhost:4222':"); - s = natsUrl_Create(&u, "tcp://a:b:c@localhost:4222"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username != NULL) - && (strcmp(u->username, "a") == 0) - && (u->password != NULL) - && (strcmp(u->password, "b:c") == 0) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://::a:b:c@localhost:4222':"); - s = natsUrl_Create(&u, "tcp://::a:b:c@localhost:4222"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username == NULL) - && (u->password != NULL) - && (strcmp(u->password, ":a:b:c") == 0) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://a:b@[::1]:4222':"); - s = natsUrl_Create(&u, "tcp://a:b@[::1]:4222"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "[::1]") == 0) - && (u->username != NULL) - && (strcmp(u->username, "a") == 0) - && (u->password != NULL) - && (strcmp(u->password, "b") == 0) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://a@[::1]:4222':"); - s = natsUrl_Create(&u, "tcp://a@[::1]:4222"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "[::1]") == 0) - && (u->username != NULL) - && (strcmp(u->username, "a") == 0) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://a:b@[::1]:4222':"); - s = natsUrl_Create(&u, "tcp://a:b@[::1]:4222"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "[::1]") == 0) - && (u->username != NULL) - && (strcmp(u->username, "a") == 0) - && (u->password != NULL) - && (strcmp(u->password, "b") == 0) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://a:b@[::1]':"); - s = natsUrl_Create(&u, "tcp://a:b@[::1]"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "[::1]") == 0) - && (u->username != NULL) - && (strcmp(u->username, "a") == 0) - && (u->password != NULL) - && (strcmp(u->password, "b") == 0) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("' tcp://localhost:4222':"); - s = natsUrl_Create(&u, " tcp://localhost:4222"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://localhost:4222 ':"); - s = natsUrl_Create(&u, "tcp://localhost:4222 "); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("' tcp://localhost:4222 ':"); - s = natsUrl_Create(&u, " tcp://localhost:4222 "); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'nats://localhost:4222/subject':"); - s = natsUrl_Create(&u, " nats://localhost:4222/subject"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username == NULL) - && (u->password == NULL) - && (u->port == 4222) - && (strcmp(u->fullUrl, "nats://localhost:4222/subject") == 0)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://%4C%65v%00ignored:p%77d%00ignoredalso@localhost':"); - s = natsUrl_Create(&u, "tcp://%4C%65v%00ignored:p%77d%00ignoredalso@localhost"); - testCond((s == NATS_OK) - && (u != NULL) - && (u->host != NULL) - && (strcmp(u->host, "localhost") == 0) - && (u->username != NULL) - && (strcmp(u->username, "Lev") == 0) - && (u->password != NULL) - && (strcmp(u->password, "pwd") == 0) - && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://%4C%65v:p%77d@localhost':"); - s = natsUrl_Create(&u, "tcp://%4C%65v:p%77d@localhost"); - testCond((s == NATS_OK) && (u != NULL) && (u->host != NULL) && (strcmp(u->host, "localhost") == 0) && (u->username != NULL) && (strcmp(u->username, "Lev") == 0) && (u->password != NULL) && (strcmp(u->password, "pwd") == 0) && (u->port == 4222)); - natsUrl_Destroy(u); - u = NULL; - - test("'tcp://%4c%65v:p%@localhost':"); - s = natsUrl_Create(&u, "tcp://%4c%65v:p%@localhost"); - testCond((s == NATS_ERR) && (u == NULL) && (strstr(nats_GetLastError(NULL), "invalid percent encoding in URL: p%") != NULL)); - nats_clearLastError(); - - test("'tcp://%4H%65v:p%@localhost':"); - s = natsUrl_Create(&u, "tcp://%4H%65v:p%@localhost"); - testCond((s == NATS_ERR) && (u == NULL) && (strstr(nats_GetLastError(NULL), "invalid percent encoding in URL: %4H%65v") != NULL)); - nats_clearLastError(); - - test("'tcp://localhost: 4222':"); - s = natsUrl_Create(&u, "tcp://localhost: 4222"); - testCond((s == NATS_INVALID_ARG) - && (u == NULL) - && (strstr(nats_GetLastError(NULL), "invalid port ' 4222'") != NULL)); - nats_clearLastError(); - - test("'tcp://localhost:a4222':"); - s = natsUrl_Create(&u, "tcp://localhost:a4222"); - testCond((s == NATS_INVALID_ARG) - && (u == NULL) - && (strstr(nats_GetLastError(NULL), "invalid port 'a4222'") != NULL)); - nats_clearLastError(); - - test("'tcp://localhost:2147483648':"); - s = natsUrl_Create(&u, "tcp://localhost:2147483648"); - testCond((s == NATS_INVALID_ARG) - && (u == NULL) - && (strstr(nats_GetLastError(NULL), "invalid port '2147483648'") != NULL)); -} - -static void -test_natsCreateStringFromBuffer(void) -{ - natsStatus s = NATS_OK; - natsBuffer buf; - char *str = NULL; - - test("NULL buffer: "); - s = nats_CreateStringFromBuffer(&str, NULL); - testCond((s == NATS_OK) - && (str == NULL)) - - test("Init buffer: "); - s = natsBuf_Init(&buf, 10); - testCond(s == NATS_OK); - - test("Empty buffer: "); - s = nats_CreateStringFromBuffer(&str, &buf); - testCond((s == NATS_OK) - && (str == NULL)) - - test("Append to buf: "); - s = natsBuf_Append(&buf, "123", 3); - testCond(s == NATS_OK); - - test("Buffer containing '123': "); - s = nats_CreateStringFromBuffer(&str, &buf); - testCond((s == NATS_OK) - && (str != NULL) - && (strlen(str) == 3) - && (strcmp(str, "123") == 0)); - - test("Destroying the buffer does not affect the created string: "); - natsBuf_Cleanup(&buf); - testCond((str != NULL) - && (strlen(str) == 3) - && (strcmp(str, "123") == 0)); - - free(str); -} - -#define INBOX_THREADS_COUNT (10) -#define INBOX_COUNT_PER_THREAD (10000) -#define INBOX_TOTAL (INBOX_THREADS_COUNT * INBOX_COUNT_PER_THREAD) - -static void -_testInbox(void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - natsStatus s = NATS_OK; - natsInbox *inbox; - void *oldValue; - - for (int i=0; (s == NATS_OK) && (iinboxes, inbox, true, (void*) 1, (void**) &oldValue); - if ((s == NATS_OK) && (oldValue != NULL)) - { - printf("Duplicate inbox: %s\n", inbox); - s = NATS_ERR; - } - - natsInbox_Destroy(inbox); - } - - args->status = s; -} - -static void -test_natsInbox(void) -{ - natsStatus s = NATS_OK; - natsThread *threads[INBOX_THREADS_COUNT]; - struct threadArg args[INBOX_THREADS_COUNT]; - int i, j; - natsInbox *key; - void *oldInbox; - natsStrHash *inboxes = NULL; - natsStrHashIter iter; - - test("Test inboxes are unique: "); - for (i=0; i 0) && (r != lr)) - { - s = NATS_ERR; - break; - } - lr = r; - } - end = nats_Now(); - testCond((s == NATS_OK) && ((end - start) < 1000)); -} - -static void -test_natsHash(void) -{ - natsStatus s; - natsHash *hash = NULL; - const char *t1 = "this is a test"; - const char *t2 = "this is another test"; - void *oldval = NULL; - int lastNumBkts = 0; - int i; - int64_t key; - int values[40]; - natsHashIter iter; - - for (int i=0; i<40; i++) - values[i] = (i+1); - - test("Create hash with invalid 0 size: "); - s = natsHash_Create(&hash, 0); - testCond((s != NATS_OK) && (hash == NULL)); - - test("Create hash with invalid negative size: "); - s = natsHash_Create(&hash, -2); - testCond((s != NATS_OK) && (hash == NULL)); - - nats_clearLastError(); - - test("Create hash ok: "); - s = natsHash_Create(&hash, 7); - testCond((s == NATS_OK) && (hash != NULL) && (hash->used == 0) - && (hash->numBkts == 8)); - - test("Set: "); - s = natsHash_Set(hash, 1234, (void*) t1, &oldval); - testCond((s == NATS_OK) && (oldval == NULL) && (hash->used == 1)); - - test("Set, get old value: "); - s = natsHash_Set(hash, 1234, (void*) t2, &oldval); - testCond((s == NATS_OK) && (oldval == t1) && (hash->used == 1)) - - test("Get, not found: "); - oldval = NULL; - oldval = natsHash_Get(hash, 3456); - testCond(oldval == NULL); - - test("Get, found: "); - oldval = NULL; - oldval = natsHash_Get(hash, 1234); - testCond(oldval == t2); - - test("Remove, not found: "); - oldval = NULL; - oldval = natsHash_Remove(hash, 3456); - testCond(oldval == NULL); - - test("Remove, found: "); - oldval = NULL; - oldval = natsHash_Remove(hash, 1234); - testCond((oldval == t2) && (hash->used == 0)); - - test("Test collision: "); - oldval = NULL; - s = natsHash_Set(hash, 2, (void*) t1, &oldval); - if ((s == NATS_OK) && (oldval == NULL)) - s = natsHash_Set(hash, 10, (void*) t2, &oldval); - testCond((s == NATS_OK) - && (oldval == NULL) - && (hash->used == 2) - && (hash->bkts[2] != NULL) - && (hash->bkts[2]->key == 10) - && (hash->bkts[2]->next != NULL) - && (hash->bkts[2]->next->key == 2)); - - test("Remove from collisions (front to back): "); - oldval = NULL; - oldval = natsHash_Remove(hash, 10); - if (oldval != t2) - s = NATS_ERR; - if (s == NATS_OK) - { - oldval = natsHash_Remove(hash, 2); - if (oldval != t1) - s = NATS_ERR; - } - testCond((s == NATS_OK) && (hash->used == 0)); - - test("Remove from collisions (back to front): "); - oldval = NULL; - s = natsHash_Set(hash, 2, (void*) t1, &oldval); - if ((s == NATS_OK) && (oldval == NULL)) - s = natsHash_Set(hash, 10, (void*) t2, &oldval); - if (s == NATS_OK) - { - oldval = natsHash_Remove(hash, 2); - if (oldval != t1) - s = NATS_ERR; - } - if (s == NATS_OK) - { - oldval = natsHash_Remove(hash, 10); - if (oldval != t2) - s = NATS_ERR; - } - testCond((s == NATS_OK) && (hash->used == 0)); - - test("Grow: "); - for (int i=0; i<40; i++) - { - s = natsHash_Set(hash, (i+1), &(values[i]), &oldval); - if (oldval != NULL) - s = NATS_ERR; - if (s != NATS_OK) - break; - } - if (s == NATS_OK) - { - for (int i=0; i<40; i++) - { - oldval = natsHash_Get(hash, (i+1)); - if ((oldval == NULL) - || ((*(int*)oldval) != values[i])) - { - s = NATS_ERR; - break; - } - } - } - testCond((s == NATS_OK) - && (hash->used == 40) - && (hash->numBkts > 8)); - lastNumBkts = hash->numBkts; - - test("Shrink: "); - for (int i=0; i<31; i++) - { - oldval = natsHash_Remove(hash, (i+1)); - if ((oldval == NULL) - || ((*(int*)oldval) != values[i])) - { - s = NATS_ERR; - break; - } - } - testCond((s == NATS_OK) - && (hash->used == 9) - && (hash->numBkts < lastNumBkts)); - - test("Iterator: "); - natsHashIter_Init(&iter, hash); - i = 0; - while (natsHashIter_Next(&iter, &key, &oldval)) - { - i++; - if (((key < 31) || (key > 40)) - || (oldval == NULL) - || ((*(int*)oldval) != values[key-1])) - { - s = NATS_ERR; - break; - } - } - natsHashIter_Done(&iter); - testCond((s == NATS_OK) && (i == natsHash_Count(hash))); - - test("Iterator, remove current: "); - natsHashIter_Init(&iter, hash); - while (natsHashIter_Next(&iter, &key, NULL)) - { - s = natsHashIter_RemoveCurrent(&iter); - if (s != NATS_OK) - break; - } - testCond((s == NATS_OK) - && (natsHash_Count(hash) == 0) - && (hash->canResize == false) - && (hash->numBkts > 8)); - - natsHashIter_Done(&iter); - - test("Grow again: "); - oldval = NULL; - for (int i=0; i<40; i++) - { - s = natsHash_Set(hash, (i+1), &(values[i]), &oldval); - if (oldval != NULL) - s = NATS_ERR; - if (s != NATS_OK) - break; - } - testCond((s == NATS_OK) - && (hash->used == 40) - && (hash->numBkts > 8)); - lastNumBkts = hash->numBkts; - - test("Iterator, remove current, hash does not shrink: "); - natsHashIter_Init(&iter, hash); - i = 0; - while (natsHashIter_Next(&iter, &key, NULL)) - { - s = natsHashIter_RemoveCurrent(&iter); - if ((s != NATS_OK) || (++i == 31)) - break; - } - testCond((s == NATS_OK) - && (natsHash_Count(hash) == 9) - && (hash->canResize == false) - && (hash->numBkts == lastNumBkts)); - - natsHashIter_Done(&iter); - - test("After iterator done, shrink works: "); - oldval = NULL; - s = natsHash_Set(hash, 100, (void*) "last", &oldval); - if ((s == NATS_OK) && (oldval == NULL)) - { - oldval = natsHash_Remove(hash, 100); - if ((oldval == NULL) - || (strcmp((const char*) oldval, "last") != 0)) - { - s = NATS_ERR; - } - } - testCond((s == NATS_OK) - && hash->canResize - && (hash->numBkts != lastNumBkts)); - - test("Destroy: "); - natsHash_Destroy(hash); - hash = NULL; - testCond(1); - - test("Create new: "); - s = natsHash_Create(&hash, 4); - testCond(s == NATS_OK); - - test("Populate: "); - s = natsHash_Set(hash, 1, (void*) 1, NULL); - IFOK(s, natsHash_Set(hash, 2, (void*) 2, NULL)); - IFOK(s, natsHash_Set(hash, 3, (void*) 3, NULL)); - testCond(s == NATS_OK); - - test("Remove one: "); - s = (natsHash_Remove(hash, 2) == (void*) 2) ? NATS_OK : NATS_ERR; - testCond(s == NATS_OK); - - test("RemoveSingle fails if more than one: "); - s = natsHash_RemoveSingle(hash, &key, NULL); - testCond(s == NATS_ERR); - nats_clearLastError(); - - test("Remove second: "); - s = (natsHash_Remove(hash, 1) == (void*) 1) ? NATS_OK : NATS_ERR; - testCond(s == NATS_OK); - - test("Remove single: "); - key = 0; - oldval = NULL; - s = natsHash_RemoveSingle(hash, &key, &oldval); - testCond((s == NATS_OK) - && (hash->used == 0) - && (key == 3) - && (oldval == (void*) 3)); - - natsHash_Destroy(hash); -} - -static void -test_natsStrHash(void) -{ - natsStatus s; - natsStrHash *hash = NULL; - const char *t1 = "this is a test"; - const char *t2 = "this is another test"; - void *oldval = NULL; - char *myKey = NULL; - int lastNumBkts = 0; - int i; - char *key; - int values[40]; - char k[64]; - uint32_t hk; - natsStrHashIter iter; - - for (int i=0; i<40; i++) - values[i] = (i+1); - - test("Create hash with invalid 0 size: "); - s = natsStrHash_Create(&hash, 0); - testCond((s != NATS_OK) && (hash == NULL)); - - test("Create hash with invalid negative size: "); - s = natsStrHash_Create(&hash, -2); - testCond((s != NATS_OK) && (hash == NULL)); - - nats_clearLastError(); - - test("Create hash ok: "); - s = natsStrHash_Create(&hash, 7); - testCond((s == NATS_OK) && (hash != NULL) && (hash->used == 0) - && (hash->numBkts == 8)); - - test("Set: "); - s = natsStrHash_Set(hash, (char*) "1234", false, (void*) t1, &oldval); - testCond((s == NATS_OK) && (oldval == NULL) && (hash->used == 1)); - - test("Set, get old value: "); - s = natsStrHash_Set(hash, (char*) "1234", false, (void*) t2, &oldval); - testCond((s == NATS_OK) && (oldval == t1) && (hash->used == 1)) - - test("Get, not found: "); - oldval = NULL; - oldval = natsStrHash_Get(hash, (char*) "3456"); - testCond(oldval == NULL); - - test("Get, found: "); - oldval = NULL; - oldval = natsStrHash_Get(hash, (char*) "1234"); - testCond(oldval == t2); - - test("Remove, not found: "); - oldval = NULL; - oldval = natsStrHash_Remove(hash, (char*) "3456"); - testCond(oldval == NULL); - - test("Remove, found: "); - oldval = NULL; - oldval = natsStrHash_Remove(hash, (char*) "1234"); - testCond((oldval == t2) && (hash->used == 0)); - - test("Grow: "); - for (int i=0; i<40; i++) - { - snprintf(k, sizeof(k), "%d", (i+1)); - s = natsStrHash_Set(hash, k, true, &(values[i]), &oldval); - if (oldval != NULL) - s = NATS_ERR; - if (s != NATS_OK) - break; - } - if (s == NATS_OK) - { - for (int i=0; i<40; i++) - { - snprintf(k, sizeof(k), "%d", (i+1)); - oldval = natsStrHash_Get(hash, k); - if ((oldval == NULL) - || ((*(int*)oldval) != values[i])) - { - s = NATS_ERR; - break; - } - } - } - testCond((s == NATS_OK) - && (hash->used == 40) - && (hash->numBkts > 8)); - lastNumBkts = hash->numBkts; - - test("Shrink: "); - for (int i=0; i<31; i++) - { - snprintf(k, sizeof(k), "%d", (i+1)); - oldval = natsStrHash_Remove(hash, k); - if ((oldval == NULL) - || ((*(int*)oldval) != values[i])) - { - s = NATS_ERR; - break; - } - } - testCond((s == NATS_OK) - && (hash->used == 9) - && (hash->numBkts < lastNumBkts)); - - test("Iterator: "); - natsStrHashIter_Init(&iter, hash); - i = 0; - while (natsStrHashIter_Next(&iter, &key, &oldval)) - { - i++; - if (((atoi(key) < 31) || (atoi(key) > 40)) - || (oldval == NULL) - || ((*(int*)oldval) != values[atoi(key)-1])) - { - s = NATS_ERR; - break; - } - } - natsStrHashIter_Done(&iter); - testCond((s == NATS_OK) && (i == natsStrHash_Count(hash))); - - test("Iterator, remove current: "); - natsStrHashIter_Init(&iter, hash); - while (natsStrHashIter_Next(&iter, &key, NULL)) - { - s = natsStrHashIter_RemoveCurrent(&iter); - if (s != NATS_OK) - break; - } - testCond((s == NATS_OK) - && (natsStrHash_Count(hash) == 0) - && (hash->canResize == false) - && (hash->numBkts > 8)); - - natsStrHashIter_Done(&iter); - - test("Grow again: "); - oldval = NULL; - for (int i=0; i<40; i++) - { - snprintf(k, sizeof(k), "%d", (i+1)); - s = natsStrHash_Set(hash, k, true, &(values[i]), &oldval); - if (oldval != NULL) - s = NATS_ERR; - if (s != NATS_OK) - break; - } - testCond((s == NATS_OK) - && (hash->used == 40) - && (hash->numBkts > 8)); - lastNumBkts = hash->numBkts; - - test("Iterator, remove current, hash does not shrink: "); - natsStrHashIter_Init(&iter, hash); - i = 0; - while (natsStrHashIter_Next(&iter, &key, NULL)) - { - s = natsStrHashIter_RemoveCurrent(&iter); - if ((s != NATS_OK) || (++i == 31)) - break; - } - testCond((s == NATS_OK) - && (natsStrHash_Count(hash) == 9) - && (hash->canResize == false) - && (hash->numBkts == lastNumBkts)); - - natsStrHashIter_Done(&iter); - - test("After iterator done, shrink works: "); - oldval = NULL; - s = natsStrHash_Set(hash, (char*) "100", true, (void*) "last", &oldval); - if ((s == NATS_OK) && (oldval == NULL)) - { - oldval = natsStrHash_Remove(hash, (char*) "100"); - if ((oldval == NULL) - || (strcmp((const char*) oldval, "last") != 0)) - { - s = NATS_ERR; - } - } - testCond((s == NATS_OK) - && hash->canResize - && (hash->numBkts != lastNumBkts)); - - test("Copy key: "); - snprintf(k, sizeof(k), "%s", "keycopied"); - hk = natsStrHash_Hash(k, (int) strlen(k)); - s = natsStrHash_Set(hash, k, true, (void*) t1, &oldval); - if (s == NATS_OK) - { - // Changing the key does not affect the hash - snprintf(k, sizeof(k), "%s", "keychanged"); - if (natsStrHash_Get(hash, (char*) "keycopied") != t1) - s = NATS_ERR; - } - testCond((s == NATS_OK) - && (oldval == NULL) - && (hash->bkts[hk & hash->mask]->hk == hk) - && (hash->bkts[hk & hash->mask]->freeKey == true)); - - test("Key referenced: "); - snprintf(k, sizeof(k), "%s", "keyreferenced"); - hk = natsStrHash_Hash(k, (int) strlen(k)); - s = natsStrHash_Set(hash, k, false, (void*) t2, &oldval); - if (s == NATS_OK) - { - // Changing the key affects the hash - snprintf(k, sizeof(k), "%s", "keychanged"); - if (natsStrHash_Get(hash, (char*) "keyreferenced") == t2) - s = NATS_ERR; - } - testCond((s == NATS_OK) - && (oldval == NULL) - && (hash->bkts[hk & hash->mask]->hk == hk) - && (hash->bkts[hk & hash->mask]->freeKey == false) - && (strcmp(hash->bkts[hk & hash->mask]->key, "keychanged") == 0)); - - test("Key not copied, but asking to free when destroyed: "); - myKey = strdup("mykey"); - hk = natsStrHash_Hash(myKey, (int) strlen(myKey)); - s = natsStrHash_SetEx(hash, myKey, false, true, (void*) t1, &oldval); - testCond((s == NATS_OK) - && (oldval == NULL) - && (hash->bkts[hk & hash->mask]->hk == hk) - && (hash->bkts[hk & hash->mask]->freeKey == true)); - - test("Destroy: "); - natsStrHash_Destroy(hash); - hash = NULL; - testCond(1); - - test("Create new: "); - s = natsStrHash_Create(&hash, 4); - testCond(s == NATS_OK); - - test("Populate: "); - s = natsStrHash_Set(hash, (char*) "1", true, (void*) 1, NULL); - IFOK(s, natsStrHash_Set(hash, (char*) "2", true, (void*) 2, NULL)); - IFOK(s, natsStrHash_Set(hash, (char*) "3", true, (void*) 3, NULL)); - testCond(s == NATS_OK); - - test("Remove one: "); - s = (natsStrHash_Remove(hash, (char*) "2") == (void*) 2) ? NATS_OK : NATS_ERR; - testCond(s == NATS_OK); - - test("RemoveSingle fails if more than one: "); - s = natsStrHash_RemoveSingle(hash, &key, NULL); - testCond(s == NATS_ERR); - nats_clearLastError(); - - test("Remove second: "); - s = (natsStrHash_Remove(hash, (char*) "1") == (void*) 1) ? NATS_OK : NATS_ERR; - testCond(s == NATS_OK); - - test("Remove single (copy of key): "); - key = NULL; - oldval = NULL; - s = natsStrHash_RemoveSingle(hash, &key, &oldval); - testCond((s == NATS_OK) - && (hash->used == 0) - && (strcmp(key, "3") == 0) - && (oldval == (void*) 3)); - free(key); - key = NULL; - oldval = NULL; - - test("Add key without copy: "); - s = natsStrHash_Set(hash, (char*) "4", false, (void*) 4, NULL); - testCond(s == NATS_OK); - - test("Remove single (no copy of key): "); - s = natsStrHash_RemoveSingle(hash, &key, &oldval); - testCond((s == NATS_OK) - && (hash->used == 0) - && (strcmp(key, "4") == 0) - && (oldval == (void*) 4)); - - natsStrHash_Destroy(hash); -} - -static const char* -_dummyTokenHandler(void *closure) -{ - return "token"; -} - -static void -_dummyErrHandler(natsConnection *nc, natsSubscription *sub, natsStatus err, - void *closure) -{ - // do nothing -} - -static void -_dummyConnHandler(natsConnection *nc, void *closure) -{ - // do nothing -} - -static natsStatus -_dummyUserJWTCb(char **userJWT, char **customErrTxt, void *closure) -{ - // do nothing - return NATS_OK; -} - -static natsStatus -_dummySigCb(char **customErrTxt, unsigned char **psig, int *sigLen, const char* nonce, void *closure) -{ - // do nothing - return NATS_OK; -} - -static void -test_natsOptions(void) -{ - natsStatus s; - natsOptions *opts = NULL; - natsOptions *cloned = NULL; - const char *servers[] = {"1", "2", "3"}; - const char *servers2[] = {"1", "2", "3", "4"}; - const char *servers3[] = {" nats://localhost:4222", "nats://localhost:4223 ", " nats://localhost:4224 "}; - const char *servers3t[] = {"nats://localhost:4222", "nats://localhost:4223", "nats://localhost:4224"}; - - test("Create options: "); - s = natsOptions_Create(&opts); - testCond(s == NATS_OK); - - test("Test defaults: "); - testCond((opts->allowReconnect == true) - && (opts->maxReconnect == 60) - && (opts->reconnectWait == 2 * 1000) - && (opts->timeout == 2 * 1000) - && (opts->pingInterval == 2 * 60 *1000) - && (opts->maxPingsOut == 2) - && (opts->ioBufSize == 32 * 1024) - && (opts->maxPendingMsgs == 65536) - && (opts->maxPendingBytes == -1) - && (opts->user == NULL) - && (opts->password == NULL) - && (opts->token == NULL) - && (opts->tokenCb == NULL) - && (opts->orderIP == 0) - && (opts->writeDeadline == natsLib_defaultWriteDeadline()) - && !opts->noEcho - && !opts->retryOnFailedConnect - && !opts->ignoreDiscoveredServers) - - test("Add URL: "); - s = natsOptions_SetURL(opts, "test"); - testCond((s == NATS_OK) - && (opts->url != NULL) - && (strcmp(opts->url, "test") == 0)); - - test("Replace URL: "); - s = natsOptions_SetURL(opts, "test2"); - testCond((s == NATS_OK) - && (opts->url != NULL) - && (strcmp(opts->url, "test2") == 0)); - - test("URL trimmed: "); - s = natsOptions_SetURL(opts, " nats://localhost:4222 "); - testCond((s == NATS_OK) - && (opts->url != NULL) - && (strcmp(opts->url, "nats://localhost:4222") == 0)); - - test("Remove URL: "); - s = natsOptions_SetURL(opts, NULL); - testCond((s == NATS_OK) - && (opts->url == NULL)); - - test("Set Servers (invalid args): "); - s = natsOptions_SetServers(opts, servers, -2); - if (s != NATS_OK) - s = natsOptions_SetServers(opts, servers, 0); - testCond(s != NATS_OK); - - test("Set Servers: "); - s = natsOptions_SetServers(opts, servers, 3); - testCond((s == NATS_OK) - && (opts->servers != NULL) - && (opts->serversCount == 3)); - - test("Replace Servers: "); - s = natsOptions_SetServers(opts, servers2, 4); - if ((s == NATS_OK) && (opts->servers != NULL) && (opts->serversCount == 4)) - { - for (int i=0; i<4; i++) - { - if (strcmp(opts->servers[i], servers2[i]) != 0) - { - s = NATS_ERR; - break; - } - } - } - testCond(s == NATS_OK); - - test("Trimmed servers: ") - s = natsOptions_SetServers(opts, servers3, 3); - if ((s == NATS_OK) && (opts->servers != NULL) && (opts->serversCount == 3)) - { - for (int i=0; i<3; i++) - { - if (strcmp(opts->servers[i], servers3t[i]) != 0) - { - s = NATS_ERR; - break; - } - } - } - testCond(s == NATS_OK); - - test("Remove servers: "); - s = natsOptions_SetServers(opts, NULL, 0); - testCond((s == NATS_OK) - && (opts->servers == NULL) - && (opts->serversCount == 0)); - - test("Set NoRandomize: "); - s = natsOptions_SetNoRandomize(opts, true); - testCond((s == NATS_OK) && (opts->noRandomize == true)); - - test("Remove NoRandomize: "); - s = natsOptions_SetNoRandomize(opts, false); - testCond((s == NATS_OK) && (opts->noRandomize == false)); - - test("Set Timeout (invalid args): "); - s = natsOptions_SetTimeout(opts, -10); - testCond(s != NATS_OK); - - test("Set Timeout to zero: "); - s = natsOptions_SetTimeout(opts, 0); - testCond((s == NATS_OK) && (opts->timeout == 0)); - - test("Set Timeout: "); - s = natsOptions_SetTimeout(opts, 2000); - testCond((s == NATS_OK) && (opts->timeout == 2000)); - - test("Set Name: "); - s = natsOptions_SetName(opts, "test"); - testCond((s == NATS_OK) && (opts->name != NULL) - && (strcmp(opts->name, "test") == 0)); - - test("Remove Name: "); - s = natsOptions_SetName(opts, NULL); - testCond((s == NATS_OK) && (opts->name == NULL)); - - test("Set Verbose: "); - s = natsOptions_SetVerbose(opts, true); - testCond((s == NATS_OK) && (opts->verbose == true)); - - test("Remove Verbose: "); - s = natsOptions_SetVerbose(opts, false); - testCond((s == NATS_OK) && (opts->verbose == false)); - - test("Set NoEcho: "); - s = natsOptions_SetNoEcho(opts, true); - testCond((s == NATS_OK) && (opts->noEcho == true)); - - test("Remove NoEcho: "); - s = natsOptions_SetNoEcho(opts, false); - testCond((s == NATS_OK) && (opts->noEcho == false)); - - test("Set RetryOnFailedConnect: "); - s = natsOptions_SetRetryOnFailedConnect(opts, true, _dummyConnHandler, (void*)1); - testCond((s == NATS_OK) - && (opts->retryOnFailedConnect == true) - && (opts->connectedCb == _dummyConnHandler) - && (opts->connectedCbClosure == (void*) 1)); - - test("Remove RetryOnFailedConnect: "); - // If `retry` is false, connect CB and closure are ignored and should - // be internally set to NULL. - s = natsOptions_SetRetryOnFailedConnect(opts, false, _dummyConnHandler, (void*)1); - testCond((s == NATS_OK) - && (opts->retryOnFailedConnect == false) - && (opts->connectedCb == NULL) - && (opts->connectedCbClosure == NULL)); - - test("Set Secure: "); - s = natsOptions_SetSecure(opts, true); -#if defined(NATS_HAS_TLS) - testCond((s == NATS_OK) && (opts->secure == true)); -#else - testCond((s == NATS_ILLEGAL_STATE) && (opts->secure == false)); -#endif - - test("Remove Secure: "); - s = natsOptions_SetSecure(opts, false); -#if defined(NATS_HAS_TLS) - testCond((s == NATS_OK) && (opts->secure == false)); -#else - testCond((s == NATS_ILLEGAL_STATE) && (opts->secure == false)); -#endif - - test("Set Pedantic: "); - s = natsOptions_SetPedantic(opts, true); - testCond((s == NATS_OK) && (opts->pedantic == true)); - - test("Remove Pedantic: "); - s = natsOptions_SetPedantic(opts, false); - testCond((s == NATS_OK) && (opts->pedantic == false)); - - test("Set Ping Interval (negative or 0 ok): "); - s = natsOptions_SetPingInterval(opts, -1000); - if ((s == NATS_OK) && (opts->pingInterval != -1000)) - s = NATS_ERR; - IFOK(s, natsOptions_SetPingInterval(opts, 0)); - if ((s == NATS_OK) && (opts->pingInterval != 0)) - s = NATS_ERR; - IFOK(s, natsOptions_SetPingInterval(opts, 1000)); - testCond((s == NATS_OK) && (opts->pingInterval == 1000)); - - test("Set MaxPingsOut: "); - s = natsOptions_SetMaxPingsOut(opts, -2); - IFOK(s, natsOptions_SetMaxPingsOut(opts, 0)); - IFOK(s, natsOptions_SetMaxPingsOut(opts, 1)); - IFOK(s, natsOptions_SetMaxPingsOut(opts, 10)); - testCond((s == NATS_OK) && (opts->maxPingsOut == 10)); - - test("Set IOBufSize: "); - s = natsOptions_SetIOBufSize(opts, -1); - if ((s != NATS_OK) && (opts->ioBufSize == NATS_OPTS_DEFAULT_IO_BUF_SIZE)) - s = natsOptions_SetIOBufSize(opts, 0); - if ((s == NATS_OK) && (opts->ioBufSize == 0)) - s = natsOptions_SetIOBufSize(opts, 1024 * 1024); - testCond((s == NATS_OK) && (opts->ioBufSize == 1024 * 1024)); - - test("Set AllowReconnect: "); - s = natsOptions_SetAllowReconnect(opts, true); - testCond((s == NATS_OK) && (opts->allowReconnect == true)); - - test("Remove AllowReconnect: "); - s = natsOptions_SetAllowReconnect(opts, false); - testCond((s == NATS_OK) && (opts->allowReconnect == false)); - - test("Set MaxReconnect (negative ok): "); - s = natsOptions_SetMaxReconnect(opts, -10); - if ((s == NATS_OK) && (opts->maxReconnect != -10)) - s = NATS_ERR; - IFOK(s, natsOptions_SetMaxReconnect(opts, 0)); - if ((s == NATS_OK) && (opts->maxReconnect != 0)) - s = NATS_ERR; - IFOK(s, natsOptions_SetMaxReconnect(opts, 10)); - testCond((s == NATS_OK) && (opts->maxReconnect == 10)); - - test("Set Reconnect Wait (invalid args: "); - s = natsOptions_SetReconnectWait(opts, -1000); - testCond(s != NATS_OK); - - test("Set Reconnect Wait: "); - s = natsOptions_SetReconnectWait(opts, 1000); - testCond((s == NATS_OK) && (opts->reconnectWait == 1000)); - - test("Remove Reconnect Wait: "); - s = natsOptions_SetReconnectWait(opts, 0); - testCond((s == NATS_OK) && (opts->reconnectWait == 0)); - - test("Set Max Pending Msgs (invalid args: "); - s = natsOptions_SetMaxPendingMsgs(opts, -1000); - if (s != NATS_OK) - s = natsOptions_SetMaxPendingMsgs(opts, 0); - testCond(s != NATS_OK); - - test("Set Max Pending Msgs : "); - s = natsOptions_SetMaxPendingMsgs(opts, 10000); - testCond((s == NATS_OK) && (opts->maxPendingMsgs == 10000)); - - test("Set Max Pending Bytes : "); - s = natsOptions_SetMaxPendingBytes(opts, 1000000); - testCond((s == NATS_OK) && (opts->maxPendingBytes == 1000000)) - - test("Set Error Handler: "); - s = natsOptions_SetErrorHandler(opts, _dummyErrHandler, NULL); - testCond((s == NATS_OK) && (opts->asyncErrCb == _dummyErrHandler)); - - test("Remove Error Handler: "); - s = natsOptions_SetErrorHandler(opts, NULL, NULL); - testCond((s == NATS_OK) && (opts->asyncErrCb == natsConn_defaultErrHandler)); - - test("Set ClosedCB: "); - s = natsOptions_SetClosedCB(opts, _dummyConnHandler, NULL); - testCond((s == NATS_OK) && (opts->closedCb == _dummyConnHandler)); - - test("Remove ClosedCB: "); - s = natsOptions_SetClosedCB(opts, NULL, NULL); - testCond((s == NATS_OK) && (opts->closedCb == NULL)); - - test("Set DisconnectedCB: "); - s = natsOptions_SetDisconnectedCB(opts, _dummyConnHandler, NULL); - testCond((s == NATS_OK) && (opts->disconnectedCb == _dummyConnHandler)); - - test("Remove DisconnectedCB: "); - s = natsOptions_SetDisconnectedCB(opts, NULL, NULL); - testCond((s == NATS_OK) && (opts->disconnectedCb == NULL)); - - test("Set ReconnectedCB: "); - s = natsOptions_SetReconnectedCB(opts, _dummyConnHandler, NULL); - testCond((s == NATS_OK) && (opts->reconnectedCb == _dummyConnHandler)); - - test("Remove ReconnectedCB: "); - s = natsOptions_SetReconnectedCB(opts, NULL, NULL); - testCond((s == NATS_OK) && (opts->reconnectedCb == NULL)); - - test("Set UserInfo: "); - s = natsOptions_SetUserInfo(opts, "ivan", "pwd"); - testCond((s == NATS_OK) - && (strcmp(opts->user, "ivan") == 0) - && (strcmp(opts->password, "pwd") == 0)); - - test("Remove UserInfo: "); - s = natsOptions_SetUserInfo(opts, NULL, NULL); - testCond((s == NATS_OK) && (opts->user == NULL) && (opts->password == NULL)); - - test("Set Token: "); - s = natsOptions_SetToken(opts, "token"); - testCond((s == NATS_OK) && (strcmp(opts->token, "token") == 0)); - - test("Remove Token: "); - s = natsOptions_SetToken(opts, NULL); - testCond((s == NATS_OK) && (opts->token == NULL)); - - test("Set TokenHandler: "); - s = natsOptions_SetTokenHandler(opts, _dummyTokenHandler, NULL); - testCond((s == NATS_OK) && (opts->tokenCb == _dummyTokenHandler) - && (strcmp(opts->tokenCb(NULL), "token") == 0)); - - test("Remove TokenHandler: "); - s = natsOptions_SetTokenHandler(opts, NULL, NULL); - testCond((s == NATS_OK) && (opts->tokenCb == NULL)); - - test("Set write deadline: "); - s = natsOptions_SetWriteDeadline(opts, 1000); - testCond((s == NATS_OK) && (opts->writeDeadline == 1000)); - - test("Remove write deadline: "); - s = natsOptions_SetWriteDeadline(opts, 0); - testCond((s == NATS_OK) && (opts->writeDeadline == 0)); - - test("IP order invalid values: "); - s = natsOptions_IPResolutionOrder(opts, -1); - if (s != NATS_OK) - s = natsOptions_IPResolutionOrder(opts, 1); - if (s != NATS_OK) - s = natsOptions_IPResolutionOrder(opts, 466); - if (s != NATS_OK) - s = natsOptions_IPResolutionOrder(opts, 644); - testCond(s != NATS_OK); - - test("IP order valid values: "); - s = natsOptions_IPResolutionOrder(opts, 0); - if ((s == NATS_OK) && (opts->orderIP == 0)) - s = natsOptions_IPResolutionOrder(opts, 4); - if ((s == NATS_OK) && (opts->orderIP == 4)) - s = natsOptions_IPResolutionOrder(opts, 6); - if ((s == NATS_OK) && (opts->orderIP == 6)) - s = natsOptions_IPResolutionOrder(opts, 46); - if ((s == NATS_OK) && (opts->orderIP == 46)) - s = natsOptions_IPResolutionOrder(opts, 64); - testCond((s == NATS_OK) && (opts->orderIP == 64)); - - test("Set UseOldRequestStyle: "); - s = natsOptions_UseOldRequestStyle(opts, true); - testCond((s == NATS_OK) && (opts->useOldRequestStyle == true)); - - test("Remove UseOldRequestStyle: "); - s = natsOptions_UseOldRequestStyle(opts, false); - testCond((s == NATS_OK) && (opts->useOldRequestStyle == false)); - - test("Set SendAsap: "); - s = natsOptions_SetSendAsap(opts, true); - testCond((s == NATS_OK) && (opts->sendAsap == true)); - - test("Remove SendAsap: "); - s = natsOptions_SetSendAsap(opts, false); - testCond((s == NATS_OK) && (opts->sendAsap == false)); - - test("Set UserCreds: "); - s = natsOptions_SetUserCredentialsCallbacks(opts, _dummyUserJWTCb, (void*) 1, _dummySigCb, (void*) 2); - testCond((s == NATS_OK) - && (opts->userJWTHandler == _dummyUserJWTCb) - && (opts->userJWTClosure == (void*) 1) - && (opts->sigHandler == _dummySigCb) - && (opts->sigClosure == (void*) 2)); - - test("Remove UserCreds: "); - s = natsOptions_SetUserCredentialsCallbacks(opts, NULL, NULL, NULL, NULL); - testCond((s == NATS_OK) - && (opts->userJWTHandler == NULL) - && (opts->userJWTClosure == NULL) - && (opts->sigHandler == NULL) - && (opts->sigClosure == NULL)); - - test("Set UserCredsFromFile: "); - s = natsOptions_SetUserCredentialsFromFiles(opts, "foo", "bar"); - testCond((s == NATS_OK) - && (opts->userCreds != NULL) - && (strcmp(opts->userCreds->userOrChainedFile, "foo") == 0) - && (strcmp(opts->userCreds->seedFile, "bar") == 0) - && (opts->userCreds->jwtAndSeedContent == NULL) - && (opts->userJWTHandler == natsConn_userCreds) - && (opts->userJWTClosure == (void*) opts->userCreds) - && (opts->sigHandler == natsConn_signatureHandler) - && (opts->sigClosure == (void*) opts->userCreds)); - - test("Remove UserCredsFromFile: "); - s = natsOptions_SetUserCredentialsFromFiles(opts, NULL, NULL); - testCond((s == NATS_OK) - && (opts->userCreds == NULL) - && (opts->userJWTHandler == NULL) - && (opts->userJWTClosure == NULL) - && (opts->sigHandler == NULL) - && (opts->sigClosure == NULL)); - - test("Set UserCredsFromMemory: "); - const char *jwtAndSeed = "-----BEGIN NATS USER JWT----\nsome user jwt\n-----END NATS USER JWT-----\n\n-----BEGIN USER NKEY SEED-----\nSUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\n-----END USER NKEY SEED-----\n"; - s = natsOptions_SetUserCredentialsFromMemory(opts, jwtAndSeed); - testCond((s == NATS_OK) - && (opts->userCreds != NULL) - && (opts->userCreds->userOrChainedFile == NULL) - && (opts->userCreds->seedFile == NULL) - && (strcmp(opts->userCreds->jwtAndSeedContent, jwtAndSeed) == 0) - && (opts->userJWTHandler == natsConn_userCreds) - && (opts->userJWTClosure == (void*) opts->userCreds) - && (opts->sigHandler == natsConn_signatureHandler) - && (opts->sigClosure == (void*) opts->userCreds)); - - test("Remove UserCredsFromMemory: "); - s = natsOptions_SetUserCredentialsFromMemory(opts, NULL); - testCond((s == NATS_OK) - && (opts->userCreds == NULL) - && (opts->userJWTHandler == NULL) - && (opts->userJWTClosure == NULL) - && (opts->sigHandler == NULL) - && (opts->sigClosure == NULL)); - - test("Set NKey: "); - s = natsOptions_SetNKey(opts, "pubkey", _dummySigCb, (void*) 1); - testCond((s == NATS_OK) - && (opts->nkey != NULL) - && (strcmp(opts->nkey, "pubkey") == 0) - && (opts->sigHandler == _dummySigCb) - && (opts->sigClosure == (void*) 1)); - - test("Remove NKey: "); - s = natsOptions_SetNKey(opts, NULL, NULL, NULL); - testCond((s == NATS_OK) - && (opts->nkey == NULL) - && (opts->sigHandler == NULL) - && (opts->sigClosure == NULL)); - - test("Set NKeyFromSeed: "); - s = natsOptions_SetNKeyFromSeed(opts, "pubkey", "seed.file"); - testCond((s == NATS_OK) - && (opts->nkey != NULL) - && (strcmp(opts->nkey, "pubkey") == 0) - && (opts->sigHandler == natsConn_signatureHandler) - && (opts->sigClosure == (void*) opts->userCreds) - && (opts->userCreds != NULL) - && (opts->userCreds->seedFile != NULL) - && (strcmp(opts->userCreds->seedFile, "seed.file") == 0)); - - test("Remove NKeyFromSeed: "); - s = natsOptions_SetNKeyFromSeed(opts, NULL, NULL); - testCond((s == NATS_OK) - && (opts->nkey == NULL) - && (opts->sigHandler == NULL) - && (opts->sigClosure == NULL) - && (opts->userCreds == NULL)); - - test("Disable no responders: "); - s = natsOptions_DisableNoResponders(opts, true); - testCond((s == NATS_OK) && opts->disableNoResponders); - - test("Enable no responders: "); - s = natsOptions_DisableNoResponders(opts, false); - testCond((s == NATS_OK) && !opts->disableNoResponders); - - test("Set custom inbox prefix: "); - s = natsOptions_SetCustomInboxPrefix(opts, "my.prefix"); - testCond((s == NATS_OK) - && (opts->inboxPfx != NULL) - && (strcmp(opts->inboxPfx, "my.prefix.") == 0)); - - test("Clear custom inbox prefix: "); - s = natsOptions_SetCustomInboxPrefix(opts, NULL); - if ((s == NATS_OK) && (opts->inboxPfx == NULL)) - s = natsOptions_SetCustomInboxPrefix(opts, ""); - testCond((s == NATS_OK) && (opts->inboxPfx == NULL)); - - test("Set ignore discovered servers: "); - s = natsOptions_SetIgnoreDiscoveredServers(opts, true); - testCond((s == NATS_OK) && opts->ignoreDiscoveredServers); - - test("Reset ignore discovered servers: "); - s = natsOptions_SetIgnoreDiscoveredServers(opts, false); - testCond((s == NATS_OK) && !opts->ignoreDiscoveredServers); - - // Prepare some values for the clone check - s = natsOptions_SetURL(opts, "url"); - IFOK(s, natsOptions_SetServers(opts, servers, 3)); - IFOK(s, natsOptions_SetName(opts, "name")); - IFOK(s, natsOptions_SetPingInterval(opts, 3000)); - IFOK(s, natsOptions_SetErrorHandler(opts, _dummyErrHandler, NULL)); - IFOK(s, natsOptions_SetUserInfo(opts, "ivan", "pwd")); - IFOK(s, natsOptions_SetToken(opts, "token")); - IFOK(s, natsOptions_IPResolutionOrder(opts, 46)); - IFOK(s, natsOptions_SetNoEcho(opts, true)); - IFOK(s, natsOptions_SetRetryOnFailedConnect(opts, true, _dummyConnHandler, NULL)); - if (s != NATS_OK) - FAIL("Unable to test natsOptions_clone() because of failure while setting"); - - test("Cloning: "); - s = NATS_OK; - cloned = natsOptions_clone(opts); - if (cloned == NULL) - s = NATS_NO_MEMORY; - else if ((cloned->pingInterval != 3000) - || (cloned->asyncErrCb != _dummyErrHandler) - || (cloned->name == NULL) - || (strcmp(cloned->name, "name") != 0) - || (cloned->url == NULL) - || (strcmp(cloned->url, "url") != 0) - || (cloned->servers == NULL) - || (cloned->serversCount != 3) - || (strcmp(cloned->user, "ivan") != 0) - || (strcmp(cloned->password, "pwd") != 0) - || (strcmp(cloned->token, "token") != 0) - || (cloned->orderIP != 46) - || (!cloned->noEcho) - || (!cloned->retryOnFailedConnect) - || (cloned->connectedCb != _dummyConnHandler)) - { - s = NATS_ERR; - } - if (s == NATS_OK) - { - for (int i=0; i<3; i++) - { - if (strcmp(cloned->servers[i], servers[i]) != 0) - { - s = NATS_ERR; - break; - } - } - } - testCond(s == NATS_OK); - - test("Destroy original does not affect clone: "); - natsOptions_Destroy(opts); - testCond((cloned != NULL) - && (cloned->url != NULL) - && (strcmp(cloned->url, "url") == 0)); - - natsOptions_Destroy(cloned); - opts = NULL; - cloned = NULL; - - test("If original has default err handler, cloned has it too: "); - s = natsOptions_Create(&opts); - if (s == NATS_OK) - cloned = natsOptions_clone(opts); - testCond((s == NATS_OK) && (cloned != NULL) - && (cloned->asyncErrCb == natsConn_defaultErrHandler) - && (cloned->asyncErrCbClosure == NULL)); - natsOptions_Destroy(cloned); - natsOptions_Destroy(opts); -} - -static void -test_natsSock_ReadLine(void) -{ - char buffer[20]; - natsStatus s; - natsSockCtx ctx; - - memset(&ctx, 0, sizeof(natsSockCtx)); - - snprintf(buffer, sizeof(buffer), "%s", "+OK\r\nPONG\r\nFOO\r\nxxx"); - buffer[3] = '\0'; - - test("Read second line from buffer: "); - s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer)); - testCond((s == NATS_OK) && (strcmp(buffer, "PONG") == 0)); - - test("Read third line from buffer: "); - s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer)); - testCond((s == NATS_OK) && (strcmp(buffer, "FOO") == 0)); - - test("Next call should trigger recv, which is expected to fail: "); - s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer)); - testCond(s != NATS_OK); -} - -static natsStatus -_dummyJSONCb(void *userInfo, const char *fieldName, nats_JSONField *f) -{ - if (userInfo == NULL) - return NATS_OK; - - if (strcmp(fieldName, "fail") == 0) - return nats_setError(NATS_INVALID_ARG, "%s", "on purpose"); - - *(uint64_t*)userInfo = f->value.vuint; - return NATS_OK; -} - -static void -test_natsJSON(void) -{ - natsStatus s; - nats_JSON *json = NULL; - char buf[256]; - int i; - int intVal = 0; - int64_t longVal = 0; - char *strVal = NULL; - bool boolVal = false; - long double doubleVal = 0; - char **arrVal = NULL; - bool *arrBoolVal = NULL; - long double *arrDoubleVal = NULL; - int *arrIntVal = NULL; - int64_t *arrLongVal = NULL; - uint64_t *arrULongVal = NULL; - nats_JSON **arrObjVal = NULL; - nats_JSONArray **arrArrVal = NULL; - int arrCount = 0; - uint64_t ulongVal = 0; - nats_JSON *obj1 = NULL; - nats_JSON *obj2 = NULL; - nats_JSON *obj3 = NULL; - int32_t int32Val = 0; - uint16_t uint16Val = 0; - const char *wrong[] = { - "{", - "}", - "{start quote missing\":0}", - "{\"end quote missing: 0}", - "{\"test\":start quote missing\"}", - "{\"test\":\"end quote missing}", - "{\"test\":1.2x}", - "{\"test\":tRUE}", - "{\"test\":true,}", - "{\"test\":true}, xxx}", - "{\"test\": \"abc\\error here\"}", - "{\"test\": \"abc\\u123\"}", - "{\"test\": \"abc\\u123g\"}", - "{\"test\": \"abc\\u 23f\"}", - ("{\"test\": \"abc\\"""), - "{\"test\": \"abc\\u1234", - "{\"test\": \"abc\\uabc", - "{\"test\" \"separator missing\"}", - "{\"test\":[1, \"abc\", true]}", - }; - const char *good[] = { - "{}", - " {}", - " { }", - " { } ", - "{ \"test\":{}}", - "{ \"test\":1.2}", - "{ \"test\" :1.2}", - "{ \"test\" : 1.2}", - "{ \"test\" : 1.2 }", - "{ \"test\" : 1.2,\"test2\":1}", - "{ \"test\" : 1.2, \"test2\":1}", - "{ \"test\":0}", - "{ \"test\" :0}", - "{ \"test\" : 0}", - "{ \"test\" : 0 }", - "{ \"test\" : 0,\"test2\":1}", - "{ \"test\" : 0, \"test2\":1}", - "{ \"test\":true}", - "{ \"test\": true}", - "{ \"test\": true }", - "{ \"test\":true,\"test2\":1}", - "{ \"test\": true,\"test2\":1}", - "{ \"test\": true ,\"test2\":1}", - "{ \"test\":false}", - "{ \"test\": false}", - "{ \"test\": false }", - "{ \"test\":false,\"test2\":1}", - "{ \"test\": false,\"test2\":1}", - "{ \"test\": false ,\"test2\":1}", - "{ \"test\":\"abc\"}", - "{ \"test\": \"abc\"}", - "{ \"test\": \"abc\" }", - "{ \"test\":\"abc\",\"test2\":1}", - "{ \"test\": \"abc\",\"test2\":1}", - "{ \"test\": \"abc\" ,\"test2\":1}", - "{ \"test\": \"a\\\"b\\\"c\" }", - "{ \"test\": [\"a\", \"b\", \"c\"]}", - "{ \"test\": [\"a\\\"b\\\"c\"]}", - "{ \"test\": [\"abc,def\"]}", - "{ \"test\": [{\"a\": 1}, {\"b\": \"c\"}]}", - "{ \"test\": [[{\"a\": 1}], [{\"b\": \"c\"}]]}", - "{ \"test\": []}", - "{ \"test\": {\"inner\":\"a\",\"inner2\":2,\"inner3\":false,\"inner4\":{\"inner_inner1\" : 1.234}}}", - "{ \"test\": \"a\\\"b\\\"c\"}", - "{ \"test\": \"\\\"\\\\/\b\f\n\r\t\\uabcd\"}", - "{ \"test\": \"\\ua12f\"}", - "{ \"test\": \"\\uA01F\"}", - "{ \"test\": null}", - }; - nats_JSONField *f = NULL; - unsigned char *bytes = NULL; - int bl = 0; - - for (i=0; i<(int)(sizeof(wrong)/sizeof(char*)); i++) - { - snprintf(buf, sizeof(buf), "Negative test %d: ", (i+1)); - test(buf); - s = nats_JSONParse(&json, wrong[i], -1); - testCond((s != NATS_OK) && (json == NULL)); - json = NULL; - } - nats_clearLastError(); - - for (i=0; i<(int)(sizeof(good)/sizeof(char*)); i++) - { - snprintf(buf, sizeof(buf), "Positive test %d: ", (i+1)); - test(buf); - s = nats_JSONParse(&json, good[i], -1); - testCond((s == NATS_OK) && (json != NULL)); - nats_JSONDestroy(json); - json = NULL; - } - nats_clearLastError(); - - // Check values - test("Empty string: "); - s = nats_JSONParse(&json, "{}", -1); - IFOK(s, nats_JSONGetInt(json, "test", &intVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 0) - && (intVal == 0)); - nats_JSONDestroy(json); - json = NULL; - - test("Single field, string: "); - s = nats_JSONParse(&json, "{\"test\":\"abc\"}", -1); - IFOK(s, nats_JSONGetStr(json, "test", &strVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (strcmp(strVal, "abc") == 0)); - nats_JSONDestroy(json); - json = NULL; - free(strVal); - strVal = NULL; - - test("Single field, string with escape chars: "); - s = nats_JSONParse(&json, "{\"test\":\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"}", -1); - IFOK(s, nats_JSONGetStr(json, "test", &strVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (strcmp(strVal, "\"\\/\b\f\n\r\t") == 0)); - nats_JSONDestroy(json); - json = NULL; - free(strVal); - strVal = NULL; - - test("Single field, string with unicode: "); - s = nats_JSONParse(&json, "{\"test\":\"\\u0026\\u003c\\u003e\"}", -1); - IFOK(s, nats_JSONGetStr(json, "test", &strVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (strcmp(strVal, "&<>") == 0)); - nats_JSONDestroy(json); - json = NULL; - free(strVal); - strVal = NULL; - - test("Single field, int: "); - s = nats_JSONParse(&json, "{\"test\":1234}", -1); - IFOK(s, nats_JSONGetInt(json, "test", &intVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (intVal == 1234)); - nats_JSONDestroy(json); - json = NULL; - intVal = 0; - - test("Single field, int32: "); - s = nats_JSONParse(&json, "{\"test\":1234}", -1); - IFOK(s, nats_JSONGetInt32(json, "test", &int32Val)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (int32Val == 1234)); - nats_JSONDestroy(json); - json = NULL; - int32Val = 0; - - test("Single field, uint16: "); - s = nats_JSONParse(&json, "{\"test\":1234}", -1); - IFOK(s, nats_JSONGetUInt16(json, "test", &uint16Val)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (uint16Val == 1234)); - nats_JSONDestroy(json); - json = NULL; - uint16Val = 0; - - test("Single field, long: "); - s = nats_JSONParse(&json, "{\"test\":9223372036854775807}", -1); - IFOK(s, nats_JSONGetLong(json, "test", &longVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (longVal == 9223372036854775807L)); - nats_JSONDestroy(json); - json = NULL; - longVal = 0; - - test("Single field, neg long: "); - s = nats_JSONParse(&json, "{\"test\":-9223372036854775808}", -1); - IFOK(s, nats_JSONGetLong(json, "test", &longVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (longVal == (int64_t) 0x8000000000000000)); - nats_JSONDestroy(json); - json = NULL; - longVal = 0; - - test("Single field, neg long as ulong: "); - s = nats_JSONParse(&json, "{\"test\":-123456789}", -1); - IFOK(s, nats_JSONGetULong(json, "test", &ulongVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (ulongVal == 0xFFFFFFFFF8A432EB)); - nats_JSONDestroy(json); - json = NULL; - ulongVal = 0; - - test("Single field, ulong: "); - s = nats_JSONParse(&json, "{\"test\":18446744073709551615}", -1); - IFOK(s, nats_JSONGetULong(json, "test", &ulongVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (ulongVal == 0xFFFFFFFFFFFFFFFF)); - nats_JSONDestroy(json); - json = NULL; - ulongVal = 0; - - test("Single field, ulong: "); - s = nats_JSONParse(&json, "{\"test\":9007199254740993}", -1); - IFOK(s, nats_JSONGetULong(json, "test", &ulongVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (ulongVal == 9007199254740993)); - nats_JSONDestroy(json); - json = NULL; - ulongVal = 0; - - test("Single field, double: "); - s = nats_JSONParse(&json, "{\"test\":1234.5e3}", -1); - IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (doubleVal == (long double) 1234.5e+3)); - nats_JSONDestroy(json); - json = NULL; - doubleVal = 0; - - test("Single field, double negative: "); - s = nats_JSONParse(&json, "{\"test\":-1234}", -1); - IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (doubleVal == (long double) -1234)); - nats_JSONDestroy(json); - json = NULL; - doubleVal = 0; - - test("Single field, double exp negative 1: "); - s = nats_JSONParse(&json, "{\"test\":1234e-3}", -1); - IFOK(s, nats_JSONGetDouble(json, "test",&doubleVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (doubleVal == (long double) 1234.0/1000.0)); - nats_JSONDestroy(json); - json = NULL; - doubleVal = 0; - - test("Single field, double exp negative 2: "); - s = nats_JSONParse(&json, "{\"test\":1234.5e-3}", -1); - IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (doubleVal == (long double) 12345.0/10000.0)); - nats_JSONDestroy(json); - json = NULL; - doubleVal = 0; - - test("Single field, double exp negative 3: "); - s = nats_JSONParse(&json, "{\"test\":1234.5e-1}", -1); - IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (doubleVal == (long double) 12345.0/100.0)); - nats_JSONDestroy(json); - json = NULL; - doubleVal = 0; - - test("Single field, double exp negative 4: "); - s = nats_JSONParse(&json, "{\"test\":1234.5e-0}", -1); - IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (doubleVal == (long double) 12345.0/10.0)); - nats_JSONDestroy(json); - json = NULL; - doubleVal = 0; - - test("Single field, double exp positive 1: "); - s = nats_JSONParse(&json, "{\"test\":1234e+3}", -1); - IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (doubleVal == (long double) 1234.0*1000)); - nats_JSONDestroy(json); - json = NULL; - doubleVal = 0; - - test("Single field, double exp positive 2: "); - s = nats_JSONParse(&json, "{\"test\":1234.5e+3}", -1); - IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (doubleVal == (long double) 12345.0*100.0)); - nats_JSONDestroy(json); - json = NULL; - doubleVal = 0; - - test("Single field, double exp positive 3: "); - s = nats_JSONParse(&json, "{\"test\":1234.5678e+2}", -1); - IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (doubleVal == (long double) 12345678.0/100.0)); - nats_JSONDestroy(json); - json = NULL; - doubleVal = 0; - - test("Single field, double exp positive 4: "); - s = nats_JSONParse(&json, "{\"test\":1234.5678e+4}", -1); - IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (doubleVal == (long double) 12345678.0/10000.0)); - nats_JSONDestroy(json); - json = NULL; - doubleVal = 0; - - test("Single field, double exp positive 5: "); - s = nats_JSONParse(&json, "{\"test\":1234.5678e+5}", -1); - IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (doubleVal == (long double) 12345678.0*10.0)); - nats_JSONDestroy(json); - json = NULL; - doubleVal = 0; - - test("Single field, double exp positive 6: "); - s = nats_JSONParse(&json, "{\"test\":1234.5678e+0}", -1); - IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (doubleVal == (long double) 12345678.0/10000.0)); - nats_JSONDestroy(json); - json = NULL; - doubleVal = 0; - - test("Single field, double exp positive 6: "); - s = nats_JSONParse(&json, "{\"test\":1234.5678e1}", -1); - IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (doubleVal == (long double) 12345678.0/1000.0)); - nats_JSONDestroy(json); - json = NULL; - doubleVal = 0; - - test("Single field, bool: "); - s = nats_JSONParse(&json, "{\"test\":true}", -1); - IFOK(s, nats_JSONGetBool(json, "test", &boolVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && boolVal); - nats_JSONDestroy(json); - json = NULL; - boolVal = false; - - test("Single field, string array: "); - s = nats_JSONParse(&json, "{\"test\":[\"a\",\"b\",\"c\",\"d\",\"e\"]}", -1); - IFOK(s, nats_JSONGetArrayStr(json, "test", &arrVal, &arrCount)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (arrCount == 5) - && (strcmp(arrVal[0], "a") == 0) - && (strcmp(arrVal[1], "b") == 0) - && (strcmp(arrVal[2], "c") == 0) - && (strcmp(arrVal[3], "d") == 0) - && (strcmp(arrVal[4], "e") == 0)); - nats_JSONDestroy(json); - json = NULL; - for (i=0; ifields != NULL) - && (json->fields->used == 1) - && (arrVal == NULL) - && (arrCount == 0)); - nats_JSONDestroy(json); - json = NULL; - - test("Single field, bool array: "); - s = nats_JSONParse(&json, "{\"test\":[true, false, true]}", -1); - IFOK(s, nats_JSONGetArrayBool(json, "test", &arrBoolVal, &arrCount)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (arrCount == 3) - && arrBoolVal[0] - && !arrBoolVal[1] - && arrBoolVal[2]); - nats_JSONDestroy(json); - json = NULL; - free(arrBoolVal); - arrBoolVal = NULL; - arrCount = 0; - - test("Single field, double array: "); - s = nats_JSONParse(&json, "{\"test\":[1.0, 2.0, 3.0]}", -1); - IFOK(s, nats_JSONGetArrayDouble(json, "test", &arrDoubleVal, &arrCount)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (arrCount == 3) - && (arrDoubleVal[0] == 1.0) - && (arrDoubleVal[1] == 2.0) - && (arrDoubleVal[2] == 3.0)); - nats_JSONDestroy(json); - json = NULL; - free(arrDoubleVal); - arrDoubleVal = NULL; - arrCount = 0; - - test("Single field, int array: "); - s = nats_JSONParse(&json, "{\"test\":[1, 2, 3]}", -1); - IFOK(s, nats_JSONGetArrayInt(json, "test", &arrIntVal, &arrCount)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (arrCount == 3) - && (arrIntVal[0] == 1) - && (arrIntVal[1] == 2) - && (arrIntVal[2] == 3)); - nats_JSONDestroy(json); - json = NULL; - free(arrIntVal); - arrIntVal = NULL; - arrCount = 0; - - test("Single field, long array: "); - s = nats_JSONParse(&json, "{\"test\":[1, 2, 3]}", -1); - IFOK(s, nats_JSONGetArrayLong(json, "test", &arrLongVal, &arrCount)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (arrCount == 3) - && (arrLongVal[0] == 1) - && (arrLongVal[1] == 2) - && (arrLongVal[2] == 3)); - nats_JSONDestroy(json); - json = NULL; - free(arrLongVal); - arrLongVal = NULL; - arrCount = 0; - - test("Single field, ulong array: "); - s = nats_JSONParse(&json, "{\"test\":[1, 2, 3]}", -1); - IFOK(s, nats_JSONGetArrayULong(json, "test", &arrULongVal, &arrCount)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (arrCount == 3) - && (arrULongVal[0] == 1) - && (arrULongVal[1] == 2) - && (arrULongVal[2] == 3)); - nats_JSONDestroy(json); - json = NULL; - free(arrULongVal); - arrULongVal = NULL; - arrCount = 0; - - test("Single field, object array: "); - s = nats_JSONParse(&json, "{\"test\":[{\"a\": 1},{\"b\": true}]}", -1); - IFOK(s, nats_JSONGetArrayObject(json, "test", &arrObjVal, &arrCount)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (arrCount == 2) - && (nats_JSONGetInt(arrObjVal[0], "a", &intVal) == NATS_OK) - && (intVal == 1) - && (nats_JSONGetBool(arrObjVal[1], "b", &boolVal) == NATS_OK) - && boolVal); - nats_JSONDestroy(json); - json = NULL; - free(arrObjVal); - arrObjVal = NULL; - arrCount = 0; - intVal = 0; - boolVal = false; - - test("Single field, array null: "); - s = nats_JSONParse(&json, "{\"test\":null}", -1); - IFOK(s, nats_JSONGetArrayObject(json, "test", &arrObjVal, &arrCount)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (arrObjVal == NULL) - && (arrCount == 0)); - nats_JSONDestroy(json); - json = NULL; - - test("Single field, array array: "); - s = nats_JSONParse(&json, "{\"test\":[[\"a\", \"b\"],[1, 2, 3],[{\"c\": true}]]}", -1); - IFOK(s, nats_JSONGetArrayArray(json, "test", &arrArrVal, &arrCount)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (arrCount == 3) - && (nats_JSONArrayAsStrings(arrArrVal[0], &arrVal, &arrCount) == NATS_OK) - && (arrCount == 2) - && (strcmp(arrVal[0], "a") == 0) - && (strcmp(arrVal[1], "b") == 0) - && (nats_JSONArrayAsInts(arrArrVal[1], &arrIntVal, &arrCount) == NATS_OK) - && (arrCount == 3) - && (arrIntVal[0] == 1) - && (arrIntVal[1] == 2) - && (arrIntVal[2] == 3) - && (nats_JSONArrayAsObjects(arrArrVal[2], &arrObjVal, &arrCount) == NATS_OK) - && (arrCount == 1) - && (nats_JSONGetBool(arrObjVal[0], "c", &boolVal) == NATS_OK) - && boolVal); - nats_JSONDestroy(json); - json = NULL; - for (i=0; i<2; i++) - free(arrVal[i]); - free(arrVal); - arrVal = NULL; - free(arrIntVal); - arrIntVal = NULL; - free(arrArrVal); - arrArrVal = NULL; - free(arrObjVal); - arrObjVal = NULL; - boolVal = false; - arrCount = 0; - - test("Object: "); - s = nats_JSONParse(&json, "{\"obj1\":{\"obj2\":{\"obj3\":{\"a\": 1},\"b\":true},\"c\":1.2},\"d\":3}", -1); - IFOK(s, nats_JSONGetObject(json, "obj1", &obj1)); - IFOK(s, nats_JSONGetObject(obj1, "obj2", &obj2)); - IFOK(s, nats_JSONGetObject(obj2, "obj3", &obj3)); - IFOK(s, nats_JSONGetInt(obj3, "a", &intVal)); - IFOK(s, nats_JSONGetBool(obj2, "b", &boolVal)); - IFOK(s, nats_JSONGetDouble(obj1, "c", &doubleVal)); - IFOK(s, nats_JSONGetLong(json, "d", &longVal)); - testCond((s == NATS_OK) - && (intVal == 1) - && boolVal - && (doubleVal == (long double) 12.0/10.0) - && (longVal == 3)); - nats_JSONDestroy(json); - json = NULL; - intVal = 0; - boolVal = false; - doubleVal = 0.0; - longVal = 0; - - test("Object, null: "); - s = nats_JSONParse(&json, "{\"obj\":null}", -1); - IFOK(s, nats_JSONGetObject(json, "obj", &obj1)); - testCond((s == NATS_OK) && (obj1 == NULL)); - nats_JSONDestroy(json); - json = NULL; - - test("All field types: "); - s = nats_JSONParse(&json, "{\"bool\":true,\"str\":\"abc\",\"int\":123,\"long\":456,\"double\":123.5,\"array\":[\"a\"]}", -1); - IFOK(s, nats_JSONGetBool(json, "bool", &boolVal)); - IFOK(s, nats_JSONGetStr(json, "str", &strVal)); - IFOK(s, nats_JSONGetInt(json, "int", &intVal)); - IFOK(s, nats_JSONGetLong(json, "long", &longVal)); - IFOK(s, nats_JSONGetDouble(json, "double", &doubleVal)); - IFOK(s, nats_JSONGetArrayStr(json, "array", &arrVal, &arrCount)); - testCond((s == NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 6) - && boolVal - && (strcmp(strVal, "abc") == 0) - && (intVal == 123) - && (longVal == 456) - && (doubleVal == (long double) 1235.0/10.0) - && (arrCount == 1) - && (strcmp(arrVal[0], "a") == 0)); - test("Unknown field type: "); - if (s == NATS_OK) - s = nats_JSONGetField(json, "int", 255, &f); - testCond(s != NATS_OK); - nats_JSONDestroy(json); - json = NULL; - free(strVal); - strVal = NULL; - boolVal = false; - intVal = 0; - longVal = 0; - doubleVal = 0; - for (i=0; ifields != NULL) - && (json->fields->used == 1) - && (intVal == 0)); - nats_JSONDestroy(json); - json = NULL; - - test("Ask for wrong type (array): "); - s = nats_JSONParse(&json, "{\"test\":[\"a\", \"b\"]}", -1); - IFOK(s, nats_JSONGetArrayField(json, "test", TYPE_INT, &f)); - testCond((s != NATS_OK) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (arrCount == 0) - && (arrVal == NULL)); - nats_JSONDestroy(json); - json = NULL; - - test("Ask for unknown type: "); - s = nats_JSONParse(&json, "{\"test\":true}", -1); - IFOK(s, nats_JSONGetField(json, "test", 9999, &f)); - testCond((s == NATS_INVALID_ARG) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1)); - nats_JSONDestroy(json); - json = NULL; - - test("Ask for unknown type (array): "); - s = nats_JSONParse(&json, "{\"test\":true}", -1); - IFOK(s, nats_JSONGetArrayField(json, "test", 9999, &f)); - testCond((s == NATS_INVALID_ARG) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1)); - nats_JSONDestroy(json); - json = NULL; - - test("Check no error and set to default for vars for unknown fields: "); - { - const char *initStr = "test"; - const char *initStrArr[] = {"a", "b"}; - - strVal = (char*) initStr; - boolVal = true; - intVal = 123; - longVal = 456; - doubleVal = 789; - arrVal = (char**)initStrArr; - arrCount = 2; - s = nats_JSONParse(&json, "{\"test\":true}", -1); - IFOK(s, nats_JSONGetStr(json, "str", &strVal)); - IFOK(s, nats_JSONGetInt(json, "int", &intVal)); - IFOK(s, nats_JSONGetLong(json, "long", &longVal)); - IFOK(s, nats_JSONGetBool(json, "bool", &boolVal)); - IFOK(s, nats_JSONGetDouble(json, "bool", &doubleVal)); - IFOK(s, nats_JSONGetArrayStr(json, "array", &arrVal, &arrCount)); - testCond((s == NATS_OK) - && (strVal == NULL) - && (boolVal == false) - && (intVal == 0) - && (longVal == 0) - && (doubleVal == 0) - && (arrCount == 0) - && (arrVal == NULL)); - nats_JSONDestroy(json); - json = NULL; - } - - test("Wrong string type: "); - strVal = NULL; - s = nats_JSONParse(&json, "{\"test\":12345678901112}", -1); - IFOK(s, nats_JSONGetStr(json, "test", &strVal)); - testCond((s == NATS_INVALID_ARG) - && (json != NULL) - && (json->fields != NULL) - && (json->fields->used == 1) - && (strVal == NULL)); - nats_JSONDestroy(json); - json = NULL; - - test("NULL string with -1 len: "); - s = nats_JSONParse(&json, NULL, -1); - testCond((s == NATS_INVALID_ARG) && (json == NULL)); - nats_clearLastError(); - - test("Field reused: "); - s = nats_JSONParse(&json, "{\"field\":1,\"field\":2}", -1); - IFOK(s, nats_JSONGetInt(json, "field", &intVal)); - testCond((s == NATS_OK) && (intVal == 2)); - nats_JSONDestroy(json); - json = NULL; - - test("Nested arrays ok: "); - jsonMaxNested = 10; - s = nats_JSONParse(&json, "{\"test\":[[[1, 2]]]}", -1); - testCond(s == NATS_OK); - nats_JSONDestroy(json); - json = NULL; - - test("Nested arrays not ok: "); - jsonMaxNested = 10; - s = nats_JSONParse(&json, "{\"test\":[[[[[[[[[[[[[1, 2]]]]]]]]]]]]]}", -1); - testCond((s == NATS_ERR) && (json == NULL) - && (strstr(nats_GetLastError(NULL), " nested arrays of 10") != NULL)); - nats_clearLastError(); - - test("Nested objects ok: "); - s = nats_JSONParse(&json, "{\"test\":{\"a\":{\"b\":{\"c\":1}}}}", -1); - testCond(s == NATS_OK); - nats_JSONDestroy(json); - json = NULL; - - test("Nested arrays not ok: "); - jsonMaxNested = 10; - s = nats_JSONParse(&json, "{\"test\":{\"a\":{\"b\":{\"c\":{\"d\":{\"e\":{\"f\":{\"g\":{\"h\":{\"i\":{\"j\":{\"k\":{\"l\":{\"m\":1}}}}}}}}}}}}}}", -1); - testCond((s == NATS_ERR) && (json == NULL) - && (strstr(nats_GetLastError(NULL), " nested objects of 10") != NULL)); - nats_clearLastError(); - jsonMaxNested = JSON_MAX_NEXTED; - - // Negative tests - { - const char *badTimes[] = { - "{\"time\":\"too small\"}", - "{\"time\":\"2021-06-23T18:22:00.123456789-08:00X\"}", - "{\"time\":\"2021-06-23T18:22:00X\"}", - "{\"time\":\"2021-06-23T18:22:00-0800\"}", - "{\"time\":\"2021-06-23T18:22:00-08.00\"}", - "{\"time\":\"2021-06-23T18:22:00.Z\"}", - "{\"time\":\"2021-06-23T18:22:00.abcZ\"}", - "{\"time\":\"2021-06-23T18:22:00.abc-08:00\"}", - "{\"time\":\"2021-06-23T18:22:00.1234567890-08:00\"}", - "{\"time\":\"2021-06-23T18:22:00.1234567890Z\"}", - "{\"time\":\"2021-06-23T18:22:00.123-0800\"}", - }; - const char *errorsTxt[] = { - "too small", - "too long", - "invalid UTC offset", - "invalid UTC offset", - "invalid UTC offset", - "is invalid", - "is invalid", - "is invalid", - "too long", - "second fraction", - "invalid UTC offset", - }; - for (i=0; i<(int)(sizeof(errorsTxt)/sizeof(char*)); i++) - { - longVal = 0; - snprintf(buf, sizeof(buf), "Bad time '%s': ", badTimes[i]); - test(buf); - s = nats_JSONParse(&json, badTimes[i], -1); - IFOK(s, nats_JSONGetTime(json, "time", &longVal)); - testCond((s != NATS_OK) - && (json != NULL) - && (longVal == 0) - && (strstr(nats_GetLastError(NULL), errorsTxt[i]) != NULL)); - nats_clearLastError(); - nats_JSONDestroy(json); - json = NULL; - } - } - - // Positive tests - { - const char *goodTimes[] = { - "{\"time\":\"0001-01-01T00:00:00Z\"}", - "{\"time\":\"1970-01-01T01:00:00+01:00\"}", - "{\"time\":\"2021-06-23T18:22:00Z\"}", - "{\"time\":\"2021-06-23T18:22:00.1Z\"}", - "{\"time\":\"2021-06-23T18:22:00.12Z\"}", - "{\"time\":\"2021-06-23T18:22:00.123Z\"}", - "{\"time\":\"2021-06-23T18:22:00.1234Z\"}", - "{\"time\":\"2021-06-23T18:22:00.12345Z\"}", - "{\"time\":\"2021-06-23T18:22:00.123456Z\"}", - "{\"time\":\"2021-06-23T18:22:00.1234567Z\"}", - "{\"time\":\"2021-06-23T18:22:00.12345678Z\"}", - "{\"time\":\"2021-06-23T18:22:00.123456789Z\"}", - "{\"time\":\"2021-06-23T18:22:00-07:00\"}", - "{\"time\":\"2021-06-23T18:22:00.1-07:00\"}", - "{\"time\":\"2021-06-23T18:22:00.12-07:00\"}", - "{\"time\":\"2021-06-23T18:22:00.123-07:00\"}", - "{\"time\":\"2021-06-23T18:22:00.1234-07:00\"}", - "{\"time\":\"2021-06-23T18:22:00.12345-07:00\"}", - "{\"time\":\"2021-06-23T18:22:00.123456-07:00\"}", - "{\"time\":\"2021-06-23T18:22:00.1234567-07:00\"}", - "{\"time\":\"2021-06-23T18:22:00.12345678-07:00\"}", - "{\"time\":\"2021-06-23T18:22:00.123456789-07:00\"}", - "{\"time\":\"2021-06-23T18:22:00+01:00\"}", - "{\"time\":\"2021-06-23T18:22:00.1+01:00\"}", - "{\"time\":\"2021-06-23T18:22:00.12+01:00\"}", - "{\"time\":\"2021-06-23T18:22:00.123+01:00\"}", - "{\"time\":\"2021-06-23T18:22:00.1234+01:00\"}", - "{\"time\":\"2021-06-23T18:22:00.12345+01:00\"}", - "{\"time\":\"2021-06-23T18:22:00.123456+01:00\"}", - "{\"time\":\"2021-06-23T18:22:00.1234567+01:00\"}", - "{\"time\":\"2021-06-23T18:22:00.12345678+01:00\"}", - "{\"time\":\"2021-06-23T18:22:00.123456789+01:00\"}", - }; - int64_t results[] = { - 0, - 0, - 1624472520000000000, - 1624472520100000000, - 1624472520120000000, - 1624472520123000000, - 1624472520123400000, - 1624472520123450000, - 1624472520123456000, - 1624472520123456700, - 1624472520123456780, - 1624472520123456789, - 1624497720000000000, - 1624497720100000000, - 1624497720120000000, - 1624497720123000000, - 1624497720123400000, - 1624497720123450000, - 1624497720123456000, - 1624497720123456700, - 1624497720123456780, - 1624497720123456789, - 1624468920000000000, - 1624468920100000000, - 1624468920120000000, - 1624468920123000000, - 1624468920123400000, - 1624468920123450000, - 1624468920123456000, - 1624468920123456700, - 1624468920123456780, - 1624468920123456789, - }; - for (i=0; i<(int)(sizeof(results)/sizeof(int64_t)); i++) - { - longVal = 0; - snprintf(buf, sizeof(buf), "Time '%s' -> %" PRId64 ": ", goodTimes[i], results[i]); - test(buf); - s = nats_JSONParse(&json, goodTimes[i], -1); - IFOK(s, nats_JSONGetTime(json, "time", &longVal)); - testCond((s == NATS_OK) - && (json != NULL) - && (longVal == results[i])); - nats_JSONDestroy(json); - json = NULL; - } - } - - test("GetStr bad type: "); - s = nats_JSONParse(&json, "{\"test\":true}", -1); - IFOK(s, nats_JSONGetStrPtr(json, "test", (const char**) &strVal)); - testCond((s != NATS_OK) && (strVal == NULL)); - nats_clearLastError(); - nats_JSONDestroy(json); - json = NULL; - - test("GetStr: "); - s = nats_JSONParse(&json, "{\"test\":\"direct\"}", -1); - IFOK(s, nats_JSONGetStrPtr(json, "test", (const char**) &strVal)); - testCond((s == NATS_OK) && (strVal != NULL) && (strcmp(strVal, "direct") == 0)); - nats_JSONDestroy(json); - json = NULL; - - test("GetBytes bad type: "); - s = nats_JSONParse(&json, "{\"test\":true}", -1); - IFOK(s, nats_JSONGetBytes(json, "test", &bytes, &bl)); - testCond((s != NATS_OK) && (bytes == NULL) && (bl == 0)); - nats_clearLastError(); - nats_JSONDestroy(json); - json = NULL; - - test("GetBytes: "); - s = nats_JSONParse(&json, "{\"test\":\"dGhpcyBpcyB0ZXN0aW5nIGJhc2U2NCBlbmNvZGluZw==\"}", -1); - IFOK(s, nats_JSONGetBytes(json, "test", &bytes, &bl)); - testCond((s == NATS_OK) && (bytes != NULL) && (bl == 31) - && (strncmp((const char*) bytes, "this is testing base64 encoding", bl) == 0)); - nats_clearLastError(); - nats_JSONDestroy(json); - json = NULL; - free(bytes); - - test("Range with wrong type: "); - s = nats_JSONParse(&json, "{\"test\":123}", -1); - IFOK(s, nats_JSONRange(json, TYPE_STR, 0, _dummyJSONCb, NULL)); - testCond((s == NATS_ERR) - && (strstr(nats_GetLastError(NULL), "expected value type of"))); - nats_clearLastError(); - - test("Range with wrong num type: "); - s = nats_JSONRange(json, TYPE_NUM, TYPE_INT, _dummyJSONCb, NULL); - testCond((s == NATS_ERR) - && (strstr(nats_GetLastError(NULL), "expected numeric type of"))); - nats_clearLastError(); - - test("Range ok: "); - ulongVal = 0; - s = nats_JSONRange(json, TYPE_NUM, TYPE_UINT, _dummyJSONCb, &ulongVal); - testCond((s == NATS_OK) && (ulongVal == 123)); - nats_JSONDestroy(json); - json = NULL; - - test("Range cb returns error: "); - ulongVal = 0; - s = nats_JSONParse(&json, "{\"fail\":123}", -1); - IFOK(s, nats_JSONRange(json, TYPE_NUM, TYPE_UINT, _dummyJSONCb, &ulongVal)); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), "on purpose"))); - nats_clearLastError(); - nats_JSONDestroy(json); - json = NULL; - - test("Parse empty array: "); - s = nats_JSONParse(&json, "{\"empty\":[]}", -1); - testCond(s == NATS_OK); - - test("Get empty array array: "); - s = nats_JSONGetArrayArray(json, "empty", &arrArrVal, &arrCount); - testCond((s == NATS_OK) && (arrArrVal == NULL) && (arrCount == 0)); - - test("Get empty obj array: "); - s = nats_JSONGetArrayObject(json, "empty", &arrObjVal, &arrCount); - testCond((s == NATS_OK) && (arrObjVal == NULL) && (arrCount == 0)); - - test("Get empty ulong array: "); - s = nats_JSONGetArrayULong(json, "empty", &arrULongVal, &arrCount); - testCond((s == NATS_OK) && (arrULongVal == NULL) && (arrCount == 0)); - - test("Get empty long array: "); - s = nats_JSONGetArrayLong(json, "empty", &arrLongVal, &arrCount); - testCond((s == NATS_OK) && (arrLongVal == NULL) && (arrCount == 0)); - - test("Get empty int array: "); - s = nats_JSONGetArrayInt(json, "empty", &arrIntVal, &arrCount); - testCond((s == NATS_OK) && (arrIntVal == NULL) && (arrCount == 0)); - - test("Get empty double array: "); - s = nats_JSONGetArrayDouble(json, "empty", &arrDoubleVal, &arrCount); - testCond((s == NATS_OK) && (arrDoubleVal == NULL) && (arrCount == 0)); - - test("Get empty bool array: "); - s = nats_JSONGetArrayBool(json, "empty", &arrBoolVal, &arrCount); - testCond((s == NATS_OK) && (arrBoolVal == NULL) && (arrCount == 0)); - - test("Get empty string array: "); - s = nats_JSONGetArrayStr(json, "empty", &arrVal, &arrCount); - testCond((s == NATS_OK) && (arrVal == NULL) && (arrCount == 0)); - - nats_JSONDestroy(json); - json = NULL; - -} - -static void -test_natsEncodeTimeUTC(void) -{ - natsStatus s; - char buf[36] = {'\0'}; - int i; - int64_t times[] = { - 0, - 1624472520000000000, - 1624472520100000000, - 1624472520120000000, - 1624472520123000000, - 1624472520123400000, - 1624472520123450000, - 1624472520123456000, - 1624472520123456700, - 1624472520123456780, - 1624472520123456789, - }; - const char *results[] = { - "0001-01-01T00:00:00Z", - "2021-06-23T18:22:00Z", - "2021-06-23T18:22:00.1Z", - "2021-06-23T18:22:00.12Z", - "2021-06-23T18:22:00.123Z", - "2021-06-23T18:22:00.1234Z", - "2021-06-23T18:22:00.12345Z", - "2021-06-23T18:22:00.123456Z", - "2021-06-23T18:22:00.1234567Z", - "2021-06-23T18:22:00.12345678Z", - "2021-06-23T18:22:00.123456789Z", - }; - - test("Buffer too small: "); - s = nats_EncodeTimeUTC(buf, 10, 0); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), "too small") != NULL)); - nats_clearLastError(); - - for (i=0; i<(int)(sizeof(times)/sizeof(int64_t)); i++) - { - char txt[100]; - - snprintf(txt, sizeof(txt), "Time %" PRId64 " -> '%s': ", times[i], results[i]); - test(txt); - s = nats_EncodeTimeUTC(buf, sizeof(buf), times[i]); - testCond((s == NATS_OK) && (strcmp(buf, results[i]) == 0)); - } -} - -static void -test_natsErrWithLongText(void) -{ - natsStatus s; - char errTxt[300]; - const char *output = NULL; - int i; - - nats_clearLastError(); - for (i=0; i<(int) sizeof(errTxt)-1; i++) - errTxt[i] = 'A'; - errTxt[i-1] = '\0'; - - test("nats_setError with long text: "); - s = nats_setError(NATS_ERR, "This is the error: %s", errTxt); - if (s == NATS_ERR) - output = nats_GetLastError(&s); - if (output != NULL) - { - int pos = ((int) strlen(output))-1; - - // End of text should contain `...` to indicate that it was truncated. - for (i=0; i<3; i++) - { - if (output[pos--] != '.') - { - s = NATS_ILLEGAL_STATE; - break; - } - } - } - else - { - s = NATS_ILLEGAL_STATE; - } - testCond(s == NATS_ERR); - nats_clearLastError(); -} - -static void -test_natsErrStackMoreThanMaxFrames(void) -{ - int i; - const int total = MAX_FRAMES+10; - char funcName[MAX_FRAMES+10][64]; - char result[(MAX_FRAMES+10)*100]; - natsStatus s = NATS_OK; - - test("Check natsUpdateErrStack called more than MAX_FRAMES: "); - // When a stack trace is formed, it goes from the most recent - // function called to the oldest. We are going to call more than - // MAX_FRAMES with function names being numbers from total down - // to 0. We expect not to crash and have at least from total to - // total-MAX_FRAMES. - for (i=total-1;i>=0;i--) - { - snprintf(funcName[i], sizeof(funcName[i]), "%d", (i+1)); - nats_updateErrStack(NATS_ERR, funcName[i]); - } - s = nats_GetLastErrorStack(result, sizeof(result)); - if (s == NATS_OK) - { - char *ptr = result; - int funcID; - char expected[64]; - - snprintf(expected, sizeof(expected), "%d more...", total-MAX_FRAMES); - - for (i=total;i>total-MAX_FRAMES;i--) - { - if (sscanf(ptr, "%d", &funcID) != 1) - { - s = NATS_ERR; - break; - } - if (funcID != i) - { - s = NATS_ERR; - break; - } - if (funcID > 10) - ptr += 3; - else - ptr +=2; - } - // The last should be something like: xx more... - // where xx is total-MAX_FRAMES - if ((s == NATS_OK) && (strcmp(ptr, expected) != 0)) - s = NATS_ERR; - } - testCond(s == NATS_OK); -} - -static void -test_natsMsg(void) -{ - natsMsg *msg = NULL; - natsStatus s = NATS_OK; - - test("Check invalid subj (NULL): "); - s = natsMsg_Create(&msg, NULL, "reply", "data", 4); - testCond((msg == NULL) && (s == NATS_INVALID_ARG)); - - test("Check invalid subj (empty): "); - s = natsMsg_Create(&msg, "", "reply", "data", 4); - testCond((msg == NULL) && (s == NATS_INVALID_ARG)); - - test("Check invalid reply (empty): "); - s = natsMsg_Create(&msg, "foo", "", "data", 4); - testCond((msg == NULL) && (s == NATS_INVALID_ARG)); - - test("GetSubject with NULL msg: "); - testCond(natsMsg_GetSubject(NULL) == NULL); - - test("GetReply with NULL msg: "); - testCond(natsMsg_GetReply(NULL) == NULL); - - test("GetData with NULL msg: "); - testCond(natsMsg_GetData(NULL) == NULL); - - test("GetDataLength with NULL msg: "); - testCond(natsMsg_GetDataLength(NULL) == 0); - - test("Create ok: "); - s = natsMsg_Create(&msg, "foo", "reply", "data", 4); - testCond((s == NATS_OK) && (msg != NULL)); - - natsMsg_Destroy(msg); -} - -static void -test_natsBase32Decode(void) -{ - natsStatus s; - const char *src = "KRUGS4ZANFZSA5DIMUQHEZLTOVWHIIDPMYQGCIDCMFZWKMZSEBSGKY3PMRUW4ZY"; - const char *expected = "This is the result of a base32 decoding"; - char dst[256]; - int dstLen = 0; - - test("Decode: "); - s = nats_Base32_DecodeString((char*) src, dst, (int) sizeof(dst), &dstLen); - testCond((s == NATS_OK) - && (dstLen == (int) strlen(expected)) - && (memcmp((void*) expected, (void*) dst, dstLen) == 0)); - - test("Dest too small: "); - s = nats_Base32_DecodeString((char*) src, dst, 10, &dstLen); - testCond((s == NATS_INSUFFICIENT_BUFFER) && (dstLen == 0)); - nats_clearLastError(); - - test("Invalid string: "); - s = nats_Base32_DecodeString((char*)"This is invalid content", dst, (int) sizeof(dst), &dstLen); - testCond((s == NATS_ERR) - && (nats_GetLastError(NULL) != NULL) - && (strstr(nats_GetLastError(NULL), "invalid") != NULL)); -} - -static void -test_natsBase64Encode(void) -{ - natsStatus s; - char *enc = NULL; - int i; - const char *testStrings[] = { - "this is testing base64 encoding", - "dfslfdlkjsfdllkjfds dfsjlklkfsda dfsalkjklfdsalkj adfskjllkjfdaslkjfdslk", - "This is another with numbers like 12345678.90 and special characters !@#$%^&*()-=+/", - }; - const char *expectedResults[] = { - "dGhpcyBpcyB0ZXN0aW5nIGJhc2U2NCBlbmNvZGluZw", - "ZGZzbGZkbGtqc2ZkbGxramZkcyBkZnNqbGtsa2ZzZGEgZGZzYWxramtsZmRzYWxraiBhZGZza2psbGtqZmRhc2xramZkc2xr", - "VGhpcyBpcyBhbm90aGVyIHdpdGggbnVtYmVycyBsaWtlIDEyMzQ1Njc4LjkwIGFuZCBzcGVjaWFsIGNoYXJhY3RlcnMgIUAjJCVeJiooKS09Ky8", - }; - const char *expectedResultsStd[] = { - "dGhpcyBpcyB0ZXN0aW5nIGJhc2U2NCBlbmNvZGluZw==", - "ZGZzbGZkbGtqc2ZkbGxramZkcyBkZnNqbGtsa2ZzZGEgZGZzYWxramtsZmRzYWxraiBhZGZza2psbGtqZmRhc2xramZkc2xr", - "VGhpcyBpcyBhbm90aGVyIHdpdGggbnVtYmVycyBsaWtlIDEyMzQ1Njc4LjkwIGFuZCBzcGVjaWFsIGNoYXJhY3RlcnMgIUAjJCVeJiooKS09Ky8=", - }; - const uint8_t someBytes[] = {0, 2, 0, 3, 4, 5, 0, 6, 7, 8, 0, 9, 0}; - const char *sbe = "AAIAAwQFAAYHCAAJAA=="; - int sbl = 13; - int sl = 0; - int dl = 0; - unsigned char *dec = NULL; - - test("EncodeURL nil: "); - s = nats_Base64RawURL_EncodeString(NULL, 0, &enc); - testCond((s == NATS_OK) && (enc == NULL)); - - test("EncodeURL empty: "); - s = nats_Base64RawURL_EncodeString((const unsigned char*) "", 0, &enc); - testCond((s == NATS_OK) && (enc == NULL)); - - test("EncodeURL strings: "); - for (i=0; i<(int)(sizeof(testStrings)/sizeof(char*)); i++) - { - s = nats_Base64RawURL_EncodeString((const unsigned char*) testStrings[i], (int)strlen(testStrings[i]), &enc); - if ((s == NATS_OK) && ((enc == NULL) || (strcmp(enc, expectedResults[i]) != 0))) - s = NATS_ERR; - - free(enc); - enc = NULL; - if (s != NATS_OK) - break; - } - testCond(s == NATS_OK); - - test("EncodeURL bytes: "); - { - s = nats_Base64RawURL_EncodeString((const unsigned char*) &someBytes, sbl, &enc); - if ((s == NATS_OK) && ((enc == NULL) || (strcmp(enc, "AAIAAwQFAAYHCAAJAA") != 0))) - s = NATS_ERR; - - free(enc); - enc = NULL; - } - testCond(s == NATS_OK); - - test("Encode nil: "); - s = nats_Base64_Encode(NULL, 0, &enc); - testCond((s == NATS_OK) && (enc == NULL)); - - test("Encode empty: "); - s = nats_Base64_Encode((const unsigned char*) "", 0, &enc); - testCond((s == NATS_OK) && (enc == NULL)); - - test("Encode strings: "); - for (i=0; i<(int)(sizeof(testStrings)/sizeof(char*)); i++) - { - s = nats_Base64_Encode((const unsigned char*) testStrings[i], (int)strlen(testStrings[i]), &enc); - if ((s == NATS_OK) && ((enc == NULL) || (strcmp(enc, expectedResultsStd[i]) != 0))) - s = NATS_ERR; - - free(enc); - enc = NULL; - if (s != NATS_OK) - break; - } - testCond(s == NATS_OK); - - test("Encode bytes: "); - { - s = nats_Base64_Encode((const unsigned char*) &someBytes, sbl, &enc); - if ((s == NATS_OK) && ((enc == NULL) || (strcmp(enc, sbe) != 0))) - s = NATS_ERR; - - free(enc); - enc = NULL; - } - testCond(s == NATS_OK); - - test("DecodeLen src needed: "); - s = nats_Base64_DecodeLen(NULL, &sl, &dl); - if (s == NATS_INVALID_ARG) - s = nats_Base64_DecodeLen("", &sl, &dl); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("DecodeLen bad src len: "); - s = nats_Base64_DecodeLen("foo", &sl, &dl); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), "invalid base64 length") != NULL)); - nats_clearLastError(); - - test("DecodeLen bad content: "); - s = nats_Base64_DecodeLen("f=oo", &sl, &dl); - if (s == NATS_INVALID_ARG) - s = nats_Base64_DecodeLen("@!^*.#_$(foo", &sl, &dl); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), "invalid base64 character") != NULL)); - nats_clearLastError(); - - test("DecodeLen: "); - s = nats_Base64_DecodeLen(sbe, &sl, &dl); - testCond((s == NATS_OK) && (sl == (int) strlen(sbe)) && (dl == sbl)); - - test("Decode strings: "); - for (i=0; i<(int)(sizeof(expectedResultsStd)/sizeof(char*)); i++) - { - s = nats_Base64_Decode(expectedResultsStd[i], &dec, &dl); - if ((s == NATS_OK) - && ((dec == NULL) || (dl != (int) strlen(testStrings[i])) - || (strncmp((const char*) dec, testStrings[i], dl) != 0))) - { - s = NATS_ERR; - } - free(dec); - dec = NULL; - dl = 0; - } - testCond(s == NATS_OK); - - test("Decode bytes: "); - s = nats_Base64_Decode(sbe, &dec, &dl); - if ((s == NATS_OK) - && ((dec == NULL) || (dl != sbl) - || (memcmp((const void*) someBytes, (const void*) dec, sbl) != 0))) - { - s = NATS_ERR; - } - testCond(s == NATS_OK); - free(dec); -} - -static void -test_natsCRC16(void) -{ - unsigned char a[] = {153, 209, 36, 74, 103, 32, 65, 34, 111, 68, 104, 156, 50, 14, 164, 140, 144, 230}; - uint16_t crc = 0; - uint16_t expected = 10272; - - test("Compute: "); - crc = nats_CRC16_Compute(a, (int)sizeof(a)); - testCond(crc == expected); - - test("Verify: "); - testCond(nats_CRC16_Validate(a, (int)sizeof(a), expected)); - - test("Expect failure: "); - a[3] = 63; - testCond(!nats_CRC16_Validate(a, (int)sizeof(a), expected)); -} - -static void -test_natsKeys(void) -{ - natsStatus s; - unsigned char sig[NATS_CRYPTO_SIGN_BYTES]; - const char *nonceVal = "nonce"; - const unsigned char *nonce = (const unsigned char*) nonceVal; - const unsigned char expected[] = { - 155, 157, 8, 183, 93, 154, 78, 7, - 219, 39, 11, 16, 134, 231, 46, 142, - 168, 87, 110, 202, 187, 180, 179, 62, - 49, 255, 225, 74, 48, 80, 176, 111, - 248, 162, 121, 188, 203, 101, 100, 195, - 162, 70, 213, 182, 220, 14, 71, 113, - 93, 239, 141, 131, 66, 190, 237, 127, - 104, 191, 138, 217, 227, 1, 92, 14, - }; - - test("Invalid key: "); - s = natsKeys_Sign("ABC", nonce, 0, sig); - testCond((s == NATS_ERR) - && (nats_GetLastError(NULL) != NULL) - && (strstr(nats_GetLastError(NULL), NKEYS_INVALID_ENCODED_KEY) != NULL)); - nats_clearLastError(); - - // This is generated from XYTHISISNOTAVALIDSEED with correct checksum. - // Expect to get invalid seed - test("Invalid seed: "); - s = natsKeys_Sign("LBMVISCJKNEVGTSPKRAVMQKMJFCFGRKFIQ52C", nonce, 0, sig); - testCond((s == NATS_ERR) - && (nats_GetLastError(NULL) != NULL) - && (strstr(nats_GetLastError(NULL), NKEYS_INVALID_SEED) != NULL)); - nats_clearLastError(); - - test("Invalid prefix: "); - s = natsKeys_Sign("SBAUEQ2EIVDEOSCJJJFUYTKOJ5IFCUSTKRKVMV2YLFNECQSDIRCUMR2IJFFEWTCNJZHVAUKSKNKFKVSXLBMVUQKCINCEKRSHJBEUUS2MJVHE6UCRKJJVIVKWK5MFSWV2QA", nonce, 0, sig); - testCond((s == NATS_ERR) - && (nats_GetLastError(NULL) != NULL) - && (strstr(nats_GetLastError(NULL), NKEYS_INVALID_PREFIX) != NULL)); - nats_clearLastError(); - - // This is the valid seed: SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY - // Make the checksum incorrect by changing last 2 bytes. - test("Invalid checksum: "); - s = natsKeys_Sign("SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4AA", nonce, 0, sig); - testCond((s == NATS_ERR) - && (nats_GetLastError(NULL) != NULL) - && (strstr(nats_GetLastError(NULL), NKEYS_INVALID_CHECKSUM) != NULL)); - nats_clearLastError(); - - // Now use valid SEED - test("Sign ok: "); - s = natsKeys_Sign("SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY", nonce, 0, sig); - testCond((s == NATS_OK) - && (memcmp(sig, expected, sizeof(expected)) == 0)); -} - -static void -test_natsReadFile(void) -{ - natsStatus s = NATS_OK; - natsBuffer *buf = NULL; - FILE *f = NULL; - const char *fn = "test_readfile.txt"; - const char *content = "This is some content.\nThere are 2 lines in this file.\n"; - - test("Invalid arg 1: "); - s = nats_ReadFile(&buf, 0, "file.txt"); - testCond((s == NATS_INVALID_ARG) && (buf == NULL)); - - test("Invalid arg 2: "); - s = nats_ReadFile(&buf, -1, "file.txt"); - testCond((s == NATS_INVALID_ARG) && (buf == NULL)); - - test("Invalid arg 3: "); - s = nats_ReadFile(&buf, 100, NULL); - testCond((s == NATS_INVALID_ARG) && (buf == NULL)); - - test("Invalid arg 4: "); - s = nats_ReadFile(&buf, 100, ""); - testCond((s == NATS_INVALID_ARG) && (buf == NULL)); - - test("File not found: "); - s = nats_ReadFile(&buf, 100, "fileNotFound.txt"); - testCond((s == NATS_ERR) - && (strstr(nats_GetLastError(NULL), "fileNotFound.txt") != NULL) - && (buf == NULL)); - nats_clearLastError(); - - // Create temp file with some content... - f = fopen(fn, "w"); - if (f == NULL) - { - FAIL("Unable to create test file"); - } - else - { - int res = fputs(content, f); - if (res < 0) - FAIL("Unable to write content of test file"); - - fclose(f); - f = NULL; - } - - test("Read with large buffer: "); - s = nats_ReadFile(&buf, 1024, fn); - testCond((s == NATS_OK) - && (buf != NULL) - && (natsBuf_Capacity(buf) == 1024) - && (natsBuf_Len(buf) == (int) strlen(content)+1) - && (strcmp(natsBuf_Data(buf), content) == 0)); - natsBuf_Destroy(buf); - buf = NULL; - - test("Read with small buffer: "); - // The content is 55 bytes. We start with buffer of 10 and x2 - // when expanding, so capacity should be 10, 20, 40, 80. - s = nats_ReadFile(&buf, 10, fn); - testCond((s == NATS_OK) - && (buf != NULL) - && (natsBuf_Capacity(buf) == 80) - && (natsBuf_Len(buf) == (int) strlen(content)+1) - && (strcmp(natsBuf_Data(buf), content) == 0)); - natsBuf_Destroy(buf); - buf = NULL; - - test("Read with buffer of exact file content: "); - // Use a buf capacity that matches exactly the content on file. - // But since we need to add the terminating `\0`, then we will - // need to expand. - s = nats_ReadFile(&buf, (int)strlen(content), fn); - testCond((s == NATS_OK) - && (buf != NULL) - && (natsBuf_Capacity(buf) == (int)strlen(content)*2) - && (natsBuf_Len(buf) == (int) strlen(content)+1) - && (strcmp(natsBuf_Data(buf), content) == 0)); - natsBuf_Destroy(buf); - buf = NULL; - - s = nats_ReadFile(&buf, (int)strlen(content)+1, fn); - testCond((s == NATS_OK) - && (buf != NULL) - && (natsBuf_Capacity(buf) == (int)strlen(content)+1) - && (natsBuf_Len(buf) == (int) strlen(content)+1) - && (strcmp(natsBuf_Data(buf), content) == 0)); - natsBuf_Destroy(buf); - buf = NULL; - - remove(fn); -} - -static void -test_natsGetJWTOrSeed(void) -{ - natsStatus s; - char *val = NULL; - char buf[256]; - const char *valids[] = { - "--- START JWT ---\nsome value\n--- END JWT ---\n", - "--- ---\nsome value\n--- ---\n", - "------\nsome value\n------\n", - "---\nabc\n--\n---START---\nsome value\n---END---\n----\ndef\n--- ---\n", - "nothing first\nthen it starts\n --- START ---\nsome value\n--- END ---\n---START---\nof something else\n---END---\n", - "--- START ---\nsome value\n\n\n--- END ---\n", - }; - const char *invalids[] = { - "-- JWT -- START ----\nsome value\n--- END ---\n", - "--- START --- \nsome value\n--- END ---\n", - "--- START ---\nsome value\n-- END ---\n", - "--- START ---\nsome value\n---- END --- \n", - }; - int i; - int iter; - - for (iter=0; iter<2; iter++) - { - for (i=0; i<(int)(sizeof(valids)/sizeof(char*));i++) - { - snprintf(buf, sizeof(buf), "%s %d: ", (iter==0 ? "JWT" : "Seed"), (i+1)); - test(buf); - snprintf(buf, sizeof(buf), "%s%s", (iter == 0 ? "" : "------\njwt\n------\n"), valids[i]); - s = nats_GetJWTOrSeed(&val, buf, iter); - testCond((s == NATS_OK) && (val != NULL) && (strcmp(val, "some value") == 0)); - free(val); - val = NULL; - } - - for (i=0; i<(int)(sizeof(invalids)/sizeof(char*));i++) - { - snprintf(buf, sizeof(buf), "%s invalid %d: ", (iter == 0 ? "JWT" : "Seed"), (i+1)); - test(buf); - snprintf(buf, sizeof(buf), "%s%s", (iter == 0 ? "" : "------\njwt\n------\n"), invalids[i]); - s = nats_GetJWTOrSeed(&val, buf, iter); - testCond((s == NATS_NOT_FOUND) && (val == NULL)); - } - } -} - -static void -test_natsHostIsIP(void) -{ - struct _testHost { - const char *host; - bool isIP; - }; - const struct _testHost hosts[] = { - { "1.2.3.4", true }, - { "::1", true }, - { "localhost", false }, - { "www.host.name.com", false }, - }; - int i; - - for (i=0; i<(int)(sizeof(hosts)/sizeof(struct _testHost)); i++) - { - char buf[256]; - - snprintf(buf, sizeof(buf), "Check '%s': ", hosts[i].host); - test(buf); - testCond(nats_HostIsIP(hosts[i].host) == hosts[i].isIP) - } -} - -static void -_testWaitReadyServer(void *closure) -{ - struct addrinfo *servinfo = NULL; - natsStatus s = NATS_OK; - natsSock sock = NATS_SOCK_INVALID; - natsSock cliSock = NATS_SOCK_INVALID; - struct threadArg *arg = (struct threadArg*) closure; - struct addrinfo hints; - int res; - - memset(&hints,0,sizeof(hints)); - - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - hints.ai_flags = AI_PASSIVE; - - if ((res = getaddrinfo("127.0.0.1", "1234", &hints, &servinfo)) != 0) - s = NATS_SYS_ERROR; - if (s == NATS_OK) - { - sock = socket(servinfo->ai_family, servinfo->ai_socktype, - servinfo->ai_protocol); - if (sock == NATS_SOCK_INVALID) - s = NATS_SYS_ERROR; - - IFOK(s, natsSock_SetCommonTcpOptions(sock)); - IFOK(s, natsSock_SetBlocking(sock, true)); - } - if ((s == NATS_OK) - && (bind(sock, servinfo->ai_addr, (natsSockLen) servinfo->ai_addrlen) == NATS_SOCK_ERROR)) - { - s = NATS_SYS_ERROR; - } - - if ((s == NATS_OK) && (listen(sock, 100) == 0)) - { - cliSock = accept(sock, NULL, NULL); - if ((cliSock != NATS_SOCK_INVALID) - && (natsSock_SetCommonTcpOptions(cliSock) == NATS_OK)) - { - nats_Sleep(500); - - send(cliSock, "*", 1, 0); - - natsMutex_Lock(arg->m); - while ((s != NATS_TIMEOUT) && !arg->done) - s = natsCondition_TimedWait(arg->c, arg->m, 10000); - natsMutex_Unlock(arg->m); - - natsSock_Close(cliSock); - } - } - natsSock_Close(sock); - nats_FreeAddrInfo(servinfo); -} - -static void -_testSockShutdownThread(void *closure) -{ - natsSockCtx *ctx = (natsSockCtx*) closure; - - nats_Sleep(500); - natsSock_Shutdown(ctx->fd); -} - -static void -test_natsWaitReady(void) -{ - natsStatus s = NATS_OK; - natsThread *t = NULL; - natsThread *t2 = NULL; - natsSockCtx ctx; - int64_t start, dur; - char buffer[1]; - int i; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - if (natsThread_Create(&t, _testWaitReadyServer, &arg) != NATS_OK) - { - _destroyDefaultThreadArgs(&arg); - FAIL("Unable to setup test"); - } - - test("Connect: "); - natsSock_Init(&ctx); - ctx.orderIP = 4; - natsSock_ClearDeadline(&ctx); - for (i=0; i<20; i++) - { - s = natsSock_ConnectTcp(&ctx, "127.0.0.1", 1234); - if (s == NATS_OK) - break; - nats_Sleep(100); - } - testCond(s == NATS_OK); - - test("Set non blocking: "); - s = natsSock_SetCommonTcpOptions(ctx.fd); - IFOK(s, natsSock_SetBlocking(ctx.fd, false)); - testCond(s == NATS_OK); - - // Ensure that we get a would_block on read.. - while (recv(ctx.fd, buffer, 1, 0) != -1) {} - - test("WaitReady no deadline: "); - natsSock_ClearDeadline(&ctx); - start = nats_Now(); - s = natsSock_WaitReady(WAIT_FOR_READ, &ctx); - dur = nats_Now()-start; - testCond((s == NATS_OK) && (dur >= 450) && (dur <= 600)); - - // Ensure that we get a would_block on read.. - while (recv(ctx.fd, buffer, 1, 0) != -1) {} - - test("WaitReady deadline timeout: "); - natsSock_InitDeadline(&ctx, 50); - start = nats_Now(); - s = natsSock_WaitReady(WAIT_FOR_READ, &ctx); - dur = nats_Now()-start; - testCond((s == NATS_TIMEOUT) && (dur >= 40) && (dur <= 100)); - - // Ensure that we get a would_block on read.. - while (recv(ctx.fd, buffer, 1, 0) != -1) {} - - test("WaitReady kicked out by shutdown: "); - natsSock_ClearDeadline(&ctx); - start = nats_Now(); - s = natsThread_Create(&t2, _testSockShutdownThread, &ctx); - IFOK(s, natsSock_WaitReady(WAIT_FOR_READ, &ctx)); - dur = nats_Now()-start; - testCond((s == NATS_OK) && (dur <= 3000)); - - natsSock_Close(ctx.fd); - - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - natsThread_Join(t); - natsThread_Destroy(t); - - if (t2 != NULL) - { - natsThread_Join(t2); - natsThread_Destroy(t2); - } - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_natsSign(void) -{ - unsigned char *sig = NULL; - int sigLen = 0; - char *sig64 = NULL; - natsStatus s; - - test("nats_Sign invalid param 1: "); - s = nats_Sign(NULL, "nonce", &sig, &sigLen); - testCond(s == NATS_INVALID_ARG); - - test("nats_Sign invalid param 2: "); - s = nats_Sign("seed", NULL, &sig, &sigLen); - testCond(s == NATS_INVALID_ARG); - - test("nats_Sign invalid param 3: "); - s = nats_Sign("seed", "nonce", NULL, &sigLen); - testCond(s == NATS_INVALID_ARG); - - test("nats_Sign invalid param 4: "); - s = nats_Sign("seed", "nonce", &sig, NULL); - testCond(s == NATS_INVALID_ARG); - - nats_clearLastError(); - - test("Sign ok: "); - s = nats_Sign( - "SUACSSL3UAHUDXKFSNVUZRF5UHPMWZ6BFDTJ7M6USDXIEDNPPQYYYCU3VY", - "nonce", &sig, &sigLen); - IFOK(s, nats_Base64RawURL_EncodeString((const unsigned char*) sig, sigLen, &sig64)); - testCond((s == NATS_OK) - && (sig != NULL) - && (sig64 != NULL) - && (sigLen == NATS_CRYPTO_SIGN_BYTES) - && (memcmp((void*) sig64, - (void*) "AVfpO7Pw3rc56hoO1OJcFxXUCfBmO2qouchBchSlL45Fuur9zS15UzytEI1QC5wwVG7uiHIdqyfmOS6uPrwqCg", - NATS_CRYPTO_SIGN_BYTES) == 0)); - - free(sig); - free(sig64); -} - -static void -_testHeader(const char *testName, char *buf, natsStatus expected, const char *errTxt, - const char *key, const char *value) -{ - natsStatus s = NATS_OK; - natsMsg *msg = NULL; - const char *val = NULL; - const char *k = (key == NULL ? "k" : key); - - test(testName); - s = natsMsg_create(&msg, "foo", 3, NULL, 0, buf, (int)strlen(buf), (int) strlen(buf)); - IFOK(s, natsMsgHeader_Get(msg, k, &val)); - if (expected == NATS_OK) - { - testCond((s == NATS_OK) && (val != NULL) && (strcmp(val, value) == 0)); - } - else - { - const char *le = nats_GetLastError(&s); - testCond((s == expected) && (strstr(le, errTxt) != NULL)); - nats_clearLastError(); - } - - natsMsg_Destroy(msg); -} - -static void -_testStatus(const char *testName, char *buf, const char *expectedStatus, const char *expectedDescription) -{ - natsStatus s = NATS_OK; - natsMsg *msg = NULL; - const char *sts = NULL; - const char *desc = NULL; - - test(testName); - s = natsMsg_create(&msg, "foo", 3, NULL, 0, buf, (int)strlen(buf), (int) strlen(buf)); - IFOK(s, natsMsgHeader_Get(msg, STATUS_HDR, &sts)); - IFOK(s, natsMsgHeader_Get(msg, DESCRIPTION_HDR, &desc)); - testCond((s == (expectedDescription == NULL ? NATS_NOT_FOUND : NATS_OK) - && ((sts != NULL) && (strcmp(sts, expectedStatus) == 0)) - && (expectedDescription == NULL - ? desc == NULL - : ((desc != NULL) && (strcmp(desc, expectedDescription) == 0))))); - - natsMsg_Destroy(msg); -} - -static void -test_natsMsgHeadersLift(void) -{ - char buf[512]; - - snprintf(buf, sizeof(buf), "%sk:v\r\n\r\n", HDR_LINE); - _testHeader("Valid simple header: ", buf, NATS_OK, "", "k", "v"); - - snprintf(buf, sizeof(buf), "%sk e y:v\r\n\r\n", HDR_LINE); - _testHeader("Key with spaces ok: ", buf, NATS_OK, "", "k e y", "v"); - - snprintf(buf, sizeof(buf), "%sk e y :v\r\n\r\n", HDR_LINE); - _testHeader("Key with spaces (including traling) ok: ", buf, NATS_OK, "", "k e y ", "v"); - - snprintf(buf, sizeof(buf), "%sk: v \r\n\r\n", HDR_LINE); - _testHeader("Trim spaces for value: ", buf, NATS_OK, "", "k", "v"); - - snprintf(buf, sizeof(buf), "%sk: a\r\n bc\r\n def\r\n\r\n", HDR_LINE); - _testHeader("Multiline values: ", buf, NATS_OK, "", "k", "a bc def"); - - snprintf(buf, sizeof(buf), "%s", "NATS\r\nk:v\r\n\r\n"); - _testHeader("NATS header missing: ", buf, NATS_PROTOCOL_ERROR, "header prefix missing", NULL, NULL); - - snprintf(buf, sizeof(buf), "%s", HDR_LINE); - _testHeader("NATS header missing CRLF: ", buf, NATS_PROTOCOL_ERROR, "early termination of headers", NULL, NULL); - - snprintf(buf, sizeof(buf), "%sk:v\r\n\rbad\r\n", HDR_LINE); - _testHeader("Invalid key start: ", buf, NATS_PROTOCOL_ERROR, "invalid start of a key", NULL, NULL); - - snprintf(buf, sizeof(buf), "%s k:v\r\n\r\n", HDR_LINE); - _testHeader("Space in key name: ", buf, NATS_PROTOCOL_ERROR, "key cannot start with a space", NULL, NULL); - - snprintf(buf, sizeof(buf), "%sk\r\n\r\n", HDR_LINE); - _testHeader("Column missing: ", buf, NATS_PROTOCOL_ERROR, "column delimiter not found", NULL, NULL); - - snprintf(buf, sizeof(buf), "%sk:\r\n\r\n", HDR_LINE); - _testHeader("No value: ", buf, NATS_PROTOCOL_ERROR, "no value found for key", NULL, NULL); - - snprintf(buf, sizeof(buf), "%sk: \r\n\r\n", HDR_LINE); - _testHeader("No value (extra spaces): ", buf, NATS_PROTOCOL_ERROR, "no value found for key", NULL, NULL); - - // Check status description in header line prefix... - - snprintf(buf, sizeof(buf), "%s 503\r\n\r\n", HDR_LINE_PRE); - _testStatus("Status no description: ", buf, "503", NULL); - - snprintf(buf, sizeof(buf), "%s 503 \r\n\r\n", HDR_LINE_PRE); - _testStatus("Status no description (extra space): ", buf, "503", NULL); - - snprintf(buf, sizeof(buf), "%s 503 \r\n\r\n", HDR_LINE_PRE); - _testStatus("Status no description (extra spaces): ", buf, "503", NULL); - - snprintf(buf, sizeof(buf), "%s 503 No Responders\r\n\r\n", HDR_LINE_PRE); - _testStatus("Status with description: ", buf, "503", "No Responders"); - - snprintf(buf, sizeof(buf), "%s 404 No Messages \r\n\r\n", HDR_LINE_PRE); - _testStatus("Status with description (extra space): ", buf, "404", "No Messages"); - - snprintf(buf, sizeof(buf), "%s 404 No Messages \r\n\r\n", HDR_LINE_PRE); - _testStatus("Status with description (extra spaces): ", buf, "404", "No Messages"); -} - -static void -test_natsMsgHeaderAPIs(void) -{ - natsStatus s = NATS_OK; - natsMsg *msg = NULL; - const char *val = NULL; - const char* *values = NULL; - const char* *keys = NULL; - int count = 0; - const char *longKey = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - - test("Create message: "); - s = natsMsg_Create(&msg, "foo", NULL, "body", 4); - testCond(s == NATS_OK); - - test("Key cannot be NULL: "); - s = natsMsgHeader_Set(msg, NULL, "value"); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Key cannot be empty: "); - s = natsMsgHeader_Set(msg, "", "value"); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Set msg cannot be NULL: "); - s = natsMsgHeader_Set(NULL, "key", "value"); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Set value: "); - s = natsMsgHeader_Set(msg, "my-key", "value1"); - testCond(s == NATS_OK); - - test("Get msg cannot be NULL: "); - s = natsMsgHeader_Get(NULL, "my-key", &val); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Get must provide mem location: "); - s = natsMsgHeader_Get(msg, "my-key", NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Get: "); - s = natsMsgHeader_Get(msg, "my-key", &val); - testCond((s == NATS_OK) && - (val != NULL) && - (strcmp(val, "value1") == 0)); - val = NULL; - - test("Get value with different case: "); - s = natsMsgHeader_Get(msg, "my-Key", &val); - testCond((s == NATS_NOT_FOUND) && (val == NULL)); - val = NULL; - - test("Key not found: "); - s = natsMsgHeader_Get(msg, "unknown-key", &val); - testCond((s == NATS_NOT_FOUND) && (val == NULL)); - val = NULL; - - test("Set value replace old: "); - s = natsMsgHeader_Set(msg, "my-key", "value2"); - testCond(s == NATS_OK); - - test("Get value: "); - s = natsMsgHeader_Get(msg, "my-key", &val); - testCond((s == NATS_OK) && - (val != NULL) && - (strcmp(val, "value2") == 0)); - val = NULL; - - test("Set NULL value: "); - s = natsMsgHeader_Set(msg, "my-key", NULL); - testCond(s == NATS_OK); - - test("Get value: "); - s = natsMsgHeader_Get(msg, "my-key", &val); - testCond((s == NATS_OK) && (val == NULL)); - val = NULL; - - test("Set empty value: "); - s = natsMsgHeader_Set(msg, "my-key", ""); - testCond(s == NATS_OK); - - test("Get value: "); - s = natsMsgHeader_Get(msg, "my-key", &val); - testCond((s == NATS_OK) && (val != NULL) && (val[0] == '\0')); - val = NULL; - - test("Add msg cannot be NULL: "); - s = natsMsgHeader_Add(NULL, "key", "value"); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Add first: "); - s = natsMsgHeader_Add(msg, "two-fields", "val1"); - testCond(s == NATS_OK); - - test("Add second: "); - s = natsMsgHeader_Add(msg, "two-fields", "val2"); - testCond(s == NATS_OK); - - test("Get should return first: "); - s = natsMsgHeader_Get(msg, "two-fields", &val); - testCond((s == NATS_OK) && - (val != NULL) && - (strcmp(val, "val1") == 0)); - val = NULL; - - test("Values: "); - s = natsMsgHeader_Values(msg, "two-fields", &values, &count); - testCond((s == NATS_OK) && (values != NULL) && (count == 2) && - (strcmp(values[0], "val1") == 0) && - (strcmp(values[1], "val2") == 0)); - - if (values != NULL) - free((void*) values); - values = NULL; - count = 0; - - test("Add after a Set: "); - s = natsMsgHeader_Set(msg, "my-other-key", "val3"); - IFOK(s, natsMsgHeader_Add(msg, "my-other-key", "val4")); - IFOK(s, natsMsgHeader_Values(msg, "my-other-key", &values, &count)); - testCond((s == NATS_OK) && (values != NULL) && (count == 2) && - (strcmp(values[0], "val3") == 0) && - (strcmp(values[1], "val4") == 0)); - - if (values != NULL) - free((void*) values); - values = NULL; - count = 0; - - test("Keys msg cannot be NULL: "); - s = natsMsgHeader_Keys(NULL, &keys, &count); - testCond((s == NATS_INVALID_ARG) && (keys == NULL) && (count == 0)); - if (s == NATS_INVALID_ARG) - { - s = NATS_OK; - nats_clearLastError(); - } - - test("Keys keys cannot be NULL: "); - s = natsMsgHeader_Keys(msg, NULL, &count); - testCond((s == NATS_INVALID_ARG) && (keys == NULL) && (count == 0)); - nats_clearLastError(); - - test("Keys count cannot be NULL: "); - s = natsMsgHeader_Keys(msg, &keys, NULL); - testCond((s == NATS_INVALID_ARG) && (keys == NULL) && (count == 0)); - nats_clearLastError(); - - test("Keys: "); - s = natsMsgHeader_Keys(msg, &keys, &count); - if ((s == NATS_OK) && ((keys == NULL) || (count != 3))) - { - s = NATS_ERR; - } - else - { - int i; - bool ok1 = false; - bool ok2 = false; - bool ok3 = false; - - for (i=0; ips))); - if (s != NATS_OK) - FAIL("Unable to setup the test"); - - test("Check version parsing: "); - for (i=0; (s == NATS_OK) && (i<(int)(sizeof(infos)/sizeof(char*))); i++) - { - IFOK(s, natsParser_Parse(nc, (char*) infos[i], (int) strlen(infos[i]))); - if (s == NATS_OK) - { - natsConn_Lock(nc); - if ((nc->srvVersion.ma != res[i][0]) || (nc->srvVersion.mi != res[i][1]) - || (nc->srvVersion.up != res[i][2])) - { - s = NATS_ERR; - } - } - } - testCond(s == NATS_OK); - - // Server version is 2.7.3 from last parsing - - test("Check OK: "); - for (i=0; (s == NATS_OK) && (i<(int)(sizeof(checksOK)/sizeof(int[3]))); i++) - s = natsConn_srvVersionAtLeast(nc, checksOK[i][0], checksOK[i][1], checksOK[i][2]) ? NATS_OK : NATS_ERR; - testCond(s == NATS_OK); - - test("Check Bad: "); - for (i=0; (s == NATS_OK) && (i<(int)(sizeof(checksBad)/sizeof(int[3]))); i++) - s = natsConn_srvVersionAtLeast(nc, checksBad[i][0], checksBad[i][1], checksBad[i][2]) ? NATS_ERR : NATS_OK; - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); -} - -static void -test_natsFormatStringArray(void) -{ - natsStatus s; - size_t i, N; - - typedef struct - { - const char *name; - const char **in; - int inLen; - const char *out; - } TC; - - TC tcs[] = { - {"Empty", NULL, 0, "[]"}, - {"One", (const char *[]){"one"}, 1, "[\"one\"]"}, - {"Three", (const char *[]){"one", "two", "three"}, 3, "[\"one\",\"two\",\"three\"]"}, - {"NULL", (const char *[]){NULL}, 1, "[\"(null)\"]"}, - }; - - char *out[sizeof(tcs) / sizeof(TC)]; - memset(out, 0, sizeof(out)); - - N = sizeof(tcs) / sizeof(TC); - for (i = 0; i < N; i++) - { - test(tcs[i].name); - s = nats_formatStringArray(&out[i], tcs[i].in, tcs[i].inLen); - testCond((s == NATS_OK) && (out[i] != NULL) && (strcmp(out[i], tcs[i].out) == 0)); - } - - NATS_FREE_STRINGS(out, N); -} - -static natsStatus -_checkStart(const char *url, int orderIP, int maxAttempts) -{ - natsStatus s = NATS_OK; - natsUrl *nUrl = NULL; - int attempts = 0; - natsSockCtx ctx; - - natsSock_Init(&ctx); - ctx.orderIP = orderIP; - - natsDeadline_Init(&(ctx.writeDeadline), 2000); - - s = natsUrl_Create(&nUrl, url); - if (s == NATS_OK) - { - while (((s = natsSock_ConnectTcp(&ctx, - nUrl->host, nUrl->port)) != NATS_OK) - && (attempts++ < maxAttempts)) - { - nats_Sleep(200); - } - - natsUrl_Destroy(nUrl); - - if (s == NATS_OK) - natsSock_Close(ctx.fd); - else - s = NATS_NO_SERVER; - } - - nats_clearLastError(); - - return s; -} - -static natsStatus -_checkStreamingStart(const char *url, int maxAttempts) -{ - natsStatus s = NATS_NOT_PERMITTED; - -#if defined(NATS_HAS_STREAMING) - - stanConnOptions *opts = NULL; - stanConnection *sc = NULL; - int attempts = 0; - - s = stanConnOptions_Create(&opts); - IFOK(s, stanConnOptions_SetURL(opts, url)); - IFOK(s, stanConnOptions_SetConnectionWait(opts, 250)); - if (s == NATS_OK) - { - while (((s = stanConnection_Connect(&sc, clusterName, "checkStart", opts)) != NATS_OK) - && (attempts++ < maxAttempts)) - { - nats_Sleep(200); - } - } - - stanConnection_Destroy(sc); - stanConnOptions_Destroy(opts); - - if (s != NATS_OK) - nats_clearLastError(); -#else -#endif - return s; -} - -#ifdef _WIN32 - -typedef PROCESS_INFORMATION *natsPid; - -static HANDLE logHandle = NULL; - -static void -_stopServer(natsPid pid) -{ - if (pid == NATS_INVALID_PID) - return; - - TerminateProcess(pid->hProcess, 0); - WaitForSingleObject(pid->hProcess, INFINITE); - - CloseHandle(pid->hProcess); - CloseHandle(pid->hThread); - - natsMutex_Lock(slMu); - if (slMap != NULL) - natsHash_Remove(slMap, (int64_t) pid); - natsMutex_Unlock(slMu); - - free(pid); -} - -static natsPid -_startServerImpl(const char *serverExe, const char *url, const char *cmdLineOpts, bool checkStart) -{ - SECURITY_ATTRIBUTES sa; - STARTUPINFO si; - HANDLE h; - PROCESS_INFORMATION *pid; - DWORD flags = 0; - BOOL createdOk = FALSE; - BOOL hInheritance = FALSE; - char *exeAndCmdLine = NULL; - int ret; - - pid = calloc(1, sizeof(PROCESS_INFORMATION)); - if (pid == NULL) - return NATS_INVALID_PID; - - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - - ret = nats_asprintf(&exeAndCmdLine, "%s%s%s", serverExe, - (cmdLineOpts != NULL ? " " : ""), - (cmdLineOpts != NULL ? cmdLineOpts : "")); - if (ret < 0) - { - printf("No memory allocating command line string!\n"); - free(pid); - return NATS_INVALID_PID; - } - - if (!keepServerOutput) - { - ZeroMemory(&sa, sizeof(sa)); - sa.nLength = sizeof(sa); - sa.lpSecurityDescriptor = NULL; - sa.bInheritHandle = TRUE; - - h = logHandle; - if (h == NULL) - { - h = CreateFile(LOGFILE_NAME, - GENERIC_WRITE, - FILE_SHARE_WRITE | FILE_SHARE_READ, - &sa, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL); - } - - si.dwFlags |= STARTF_USESTDHANDLES; - si.hStdInput = NULL; - si.hStdError = h; - si.hStdOutput = h; - - hInheritance = TRUE; - flags = CREATE_NO_WINDOW; - - if (logHandle == NULL) - logHandle = h; - } - - // Start the child process. - if (!CreateProcess(NULL, - (LPSTR) exeAndCmdLine, - NULL, // Process handle not inheritable - NULL, // Thread handle not inheritable - hInheritance, // Set handle inheritance - flags, // Creation flags - NULL, // Use parent's environment block - NULL, // Use parent's starting directory - &si, // Pointer to STARTUPINFO structure - pid)) // Pointer to PROCESS_INFORMATION structure - { - - printf("Unable to start '%s': error (%d).\n", - exeAndCmdLine, GetLastError()); - free(exeAndCmdLine); - return NATS_INVALID_PID; - } - - free(exeAndCmdLine); - - if (checkStart) - { - natsStatus s; - - if (strcmp(serverExe, natsServerExe) == 0) - s = _checkStart(url, 46, 10); - else - s = _checkStreamingStart(url, 10); - - if (s != NATS_OK) - { - _stopServer(pid); - return NATS_INVALID_PID; - } - } - - natsMutex_Lock(slMu); - if (slMap != NULL) - natsHash_Set(slMap, (int64_t) pid, NULL, NULL); - natsMutex_Unlock(slMu); - - return (natsPid) pid; -} - -#else - -typedef pid_t natsPid; - -static void -_stopServer(natsPid pid) -{ - int status = 0; - - if (pid == NATS_INVALID_PID) - return; - - if (kill(pid, SIGINT) < 0) - { - perror("kill with SIGINT"); - if (kill(pid, SIGKILL) < 0) - { - perror("kill with SIGKILL"); - } - } - - waitpid(pid, &status, 0); - - natsMutex_Lock(slMu); - if (slMap != NULL) - natsHash_Remove(slMap, (int64_t) pid); - natsMutex_Unlock(slMu); -} - -static natsPid -_startServerImpl(const char *serverExe, const char *url, const char *cmdLineOpts, bool checkStart) -{ - natsPid pid = fork(); - if (pid == -1) - { - perror("fork"); - return NATS_INVALID_PID; - } - - if (pid == 0) - { - char *exeAndCmdLine = NULL; - char *argvPtrs[64]; - char *line = NULL; - int index = 0; - int ret = 0; - bool overrideAddr = false; - - if ((cmdLineOpts == NULL) || (strstr(cmdLineOpts, "-a ") == NULL)) - overrideAddr = true; - - ret = nats_asprintf(&exeAndCmdLine, "%s%s%s%s%s", serverExe, - (cmdLineOpts != NULL ? " " : ""), - (cmdLineOpts != NULL ? cmdLineOpts : ""), - (overrideAddr ? " -a 127.0.0.1" : ""), - (keepServerOutput ? "" : " -l " LOGFILE_NAME)); - if (ret < 0) - { - perror("No memory allocating command line string!\n"); - exit(1); - } - - memset(argvPtrs, 0, sizeof(argvPtrs)); - line = exeAndCmdLine; - - while (*line != '\0') - { - while ((*line == ' ') || (*line == '\t') || (*line == '\n')) - *line++ = '\0'; - - argvPtrs[index++] = line; - while ((*line != '\0') && (*line != ' ') - && (*line != '\t') && (*line != '\n')) - { - line++; - } - } - argvPtrs[index++] = NULL; - - // Child process. Replace with NATS server - execvp(argvPtrs[0], argvPtrs); - perror("Exec failed: "); - exit(1); - } - else if (checkStart) - { - natsStatus s; - - if (strcmp(serverExe, natsServerExe) == 0) - s = _checkStart(url, 46, 10); - else - s = _checkStreamingStart(url, 10); - - if (s != NATS_OK) - { - _stopServer(pid); - return NATS_INVALID_PID; - } - } - - natsMutex_Lock(slMu); - if (slMap != NULL) - natsHash_Set(slMap, (int64_t) pid, NULL, NULL); - natsMutex_Unlock(slMu); - - // parent, return the child's PID back. - return pid; -} -#endif - -static natsPid -_startServer(const char *url, const char *cmdLineOpts, bool checkStart) -{ - return _startServerImpl(natsServerExe, url, cmdLineOpts, checkStart); -} - -static natsPid -_startStreamingServer(const char* url, const char *cmdLineOpts, bool checkStart) -{ - return _startServerImpl(natsStreamingServerExe, url, cmdLineOpts, checkStart); -} - -static void -test_natsSock_IPOrder(void) -{ - natsStatus s; - natsPid serverPid; - - test("Server listen to IPv4: "); - serverPid = _startServer("", "-a 127.0.0.1 -p 4222", false); - testCond(true); - test("IPv4 only: "); - s = _checkStart("nats://localhost:4222", 4, 5); - testCond(s == NATS_OK); - test("IPv4+v6: "); - s = _checkStart("nats://localhost:4222", 46, 5); - testCond(s == NATS_OK); - test("IPv6+v4: "); - s = _checkStart("nats://localhost:4222", 64, 5); - testCond(s == NATS_OK); - test("IP any: "); - s = _checkStart("nats://localhost:4222", 0, 5); - testCond(s == NATS_OK); - // This one should fail. - test("IPv6 only: "); - s = _checkStart("nats://localhost:4222", 6, 5); - testCond(s != NATS_OK); - _stopServer(serverPid); - serverPid = NATS_INVALID_PID; - - if (!runOnTravis) - { - test("Server listen to IPv6: "); - serverPid = _startServer("", "-a :: -p 4222", false); - testCond(true); - test("IPv6 only: "); - s = _checkStart("nats://localhost:4222", 6, 5); - testCond(s == NATS_OK); - test("IPv4+v6: "); - s = _checkStart("nats://localhost:4222", 46, 5); - testCond(s == NATS_OK); - test("IPv6+v4: "); - s = _checkStart("nats://localhost:4222", 64, 5); - testCond(s == NATS_OK); - test("IP any: "); - s = _checkStart("nats://localhost:4222", 0, 5); - testCond(s == NATS_OK); - // This one should fail, but the server when listening - // to [::] is actually accepting IPv4 connections, - // so be tolerant of that. - test("IPv4 only: "); - s = _checkStart("nats://localhost:4222", 4, 5); - if (s == NATS_OK) - fprintf(stderr, ">>>> IPv4 to [::] should have failed, but server accepted it\n"); - else - s = NATS_OK; - testCond(s == NATS_OK); - _stopServer(serverPid); - } -} - -static void -test_natsSock_ConnectTcp(void) -{ - natsPid serverPid = NATS_INVALID_PID; - - test("Check connect tcp: "); - serverPid = _startServer("nats://127.0.0.1:4222", "-p 4222", true); - testCond(serverPid != NATS_INVALID_PID); - _stopServer(serverPid); - serverPid = NATS_INVALID_PID; - - test("Check connect tcp hostname: "); - serverPid = _startServer("nats://localhost:4222", "-p 4222", true); - testCond(serverPid != NATS_INVALID_PID); - _stopServer(serverPid); - serverPid = NATS_INVALID_PID; - - test("Check connect tcp (force server to listen to IPv4): "); - serverPid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222", true); - testCond(serverPid != NATS_INVALID_PID); - _stopServer(serverPid); - serverPid = NATS_INVALID_PID; -} - -static bool -listOrder(struct addrinfo *head, bool ordered) -{ - struct addrinfo *p; - int i; - - p = head; - for (i=0; i<10; i++) - { - if (ordered && (p->ai_flags != (i+1))) - return false; - p = p->ai_next; - } - return true; -} - -static void -test_natsSock_ShuffleIPs(void) -{ - struct addrinfo *tmp[10]; - struct addrinfo *head = NULL; - struct addrinfo *tail = NULL; - struct addrinfo *list = NULL; - struct addrinfo *p; - natsSockCtx ctx; - int i=0; - - // Create a fake list that has `ai_flags` set to 1 to 10. - // We will use that to check that the list is shuffled or not. - for (i=0; i<10; i++) - { - p = calloc(1, sizeof(struct addrinfo)); - p->ai_flags = (i+1); - if (head == NULL) - head=p; - if (tail != NULL) - tail->ai_next = p; - tail = p; - } - - test("No randomize, so no shuffling: "); - natsSock_Init(&ctx); - ctx.noRandomize = true; - list = head; - natsSock_ShuffleIPs(&ctx, tmp, sizeof(tmp), &list, 10); - testCond((list == head) && listOrder(list, true)); - - test("Shuffling bad args 2: "); - natsSock_Init(&ctx); - list = head; - natsSock_ShuffleIPs(&ctx, tmp, sizeof(tmp), NULL, 10); - testCond((list == head) && listOrder(list, true)); - - test("Shuffling bad args 1: "); - natsSock_Init(&ctx); - list = head; - natsSock_ShuffleIPs(&ctx, tmp, sizeof(tmp), &list, 0); - testCond((list == head) && listOrder(list, true)); - - test("No shuffling count==1: "); - natsSock_Init(&ctx); - list = head; - natsSock_ShuffleIPs(&ctx, tmp, sizeof(tmp), &list, 1); - testCond((list == head) && listOrder(list, true)); - - test("Shuffling: "); - natsSock_Init(&ctx); - list = head; - natsSock_ShuffleIPs(&ctx, tmp, sizeof(tmp), &list, 10); - testCond(listOrder(list, false)); - - // Reorder the list, and we will try with a tmp buffer too small, - // so API is going to allocate memory. - p = list; - for (i=0; i<10; i++) - p->ai_flags = (i+1); - head = list; - test("Shuffling mem alloc: "); - natsSock_Init(&ctx); - natsSock_ShuffleIPs(&ctx, tmp, 5, &list, 10); - testCond(listOrder(list, false)); - - for (p = list; p != NULL; p = list) - { - list = list->ai_next; - free(p); - } -} - -static natsOptions* -_createReconnectOptions(void) -{ - natsStatus s; - natsOptions *opts = NULL; - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:22222")); - IFOK(s, natsOptions_SetAllowReconnect(opts, true)); - IFOK(s, natsOptions_SetMaxReconnect(opts, 10)); - IFOK(s, natsOptions_SetReconnectWait(opts, 100)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); - if (s == NATS_OK) -#ifdef WIN32 - s = natsOptions_SetTimeout(opts, 500); -#else - s = natsOptions_SetTimeout(opts, NATS_OPTS_DEFAULT_TIMEOUT); -#endif - - if (s != NATS_OK) - { - natsOptions_Destroy(opts); - opts = NULL; - } - - return opts; -} - -static void -_reconnectedCb(natsConnection *nc, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - int64_t now = nats_Now(); - - natsMutex_Lock(arg->m); - arg->reconnected = true; - arg->reconnects++; - if (arg->control == 9) - { - if (arg->reconnects <= 4) - arg->reconnectedAt[arg->reconnects-1] = now; - } - natsCondition_Broadcast(arg->c); - natsMutex_Unlock(arg->m); -} - -static void -test_ReconnectServerStats(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsSrv *srv = NULL; - natsPid serverPid = NATS_INVALID_PID; - natsStatistics *stats = NULL; - uint64_t reconnects= 0; - struct threadArg args; - - test("Reconnect Server Stats: "); - - s = _createDefaultThreadArgsForCbTests(&args); - if (s == NATS_OK) - opts = _createReconnectOptions(); - if (opts == NULL) - FAIL("Unable to create reconnect options!"); - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - s = natsOptions_SetDisconnectedCB(opts, _reconnectedCb, &args); - IFOK(s, natsConnection_Connect(&nc, opts)); - IFOK(s, natsConnection_Flush(nc)); - - _stopServer(serverPid); - serverPid = NATS_INVALID_PID; - - if (s == NATS_OK) - { - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.reconnected) - s = natsCondition_TimedWait(args.c, args.m, 5000); - natsMutex_Unlock(args.m); - - IFOK(s, natsConnection_FlushTimeout(nc, 5000)); - } - - if (s == NATS_OK) - { - srv = natsSrvPool_GetCurrentServer(nc->srvPool, nc->cur, NULL); - if (srv == NULL) - s = NATS_ILLEGAL_STATE; - } - - testCond((s == NATS_OK) && (srv->reconnects == 0)); - - test("Tracking Reconnects stats: "); - s = natsStatistics_Create(&stats); - IFOK(s, natsConnection_GetStats(nc, stats)); - IFOK(s, natsStatistics_GetCounts(stats, NULL, NULL, NULL, NULL, &reconnects)); - testCond((s == NATS_OK) && (reconnects == 1)); - - natsStatistics_Destroy(stats); - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _stopServer(serverPid); - - _destroyDefaultThreadArgs(&args); -} - -static void -_disconnectedCb(natsConnection *nc, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - int64_t now = nats_Now(); - - natsMutex_Lock(arg->m); - arg->disconnected = true; - arg->disconnects++; - if ((arg->control == 9) && (arg->disconnects > 1)) - { - if (arg->disconnects <= 5) - arg->disconnectedAt[arg->disconnects-2] = now; - } - natsCondition_Broadcast(arg->c); - natsMutex_Unlock(arg->m); -} - -static void -_recvTestString(natsConnection *nc, natsSubscription *sub, natsMsg *msg, - void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - bool doSignal = true; - - natsMutex_Lock(arg->m); - - switch (arg->control) - { - case 0: - { - if (strncmp(arg->string, - natsMsg_GetData(msg), - natsMsg_GetDataLength(msg)) != 0) - { - arg->status = NATS_ERR; - } - break; - } - case 1: - { - if (sub == NULL) - arg->status = NATS_ERR; - else if (strncmp(arg->string, - natsMsg_GetData(msg), - natsMsg_GetDataLength(msg)) != 0) - { - arg->status = NATS_ERR; - } - break; - } - case 2: - { - if (strcmp(arg->string, natsMsg_GetReply(msg)) != 0) - { - arg->status = NATS_ERR; - } - break; - } - case 3: - case 9: - { - doSignal = false; - arg->sum++; - - if ((arg->control != 9) && (arg->sum == 10)) - { - arg->status = natsSubscription_Unsubscribe(sub); - doSignal = true; - } - break; - } - case 11: - case 4: - { - arg->status = natsConnection_PublishString(nc, - natsMsg_GetReply(msg), - arg->string); - if (arg->status == NATS_OK) - arg->status = natsConnection_Flush(nc); - if (arg->control == 11) - arg->sum++; - break; - } - case 5: - { - arg->status = natsConnection_Flush(nc); - break; - } - case 6: - { - char seqnoStr[10]; - int seqno = 0; - - doSignal = false; - - snprintf(seqnoStr, sizeof(seqnoStr), "%.*s", natsMsg_GetDataLength(msg), - natsMsg_GetData(msg)); - - seqno = atoi(seqnoStr); - if (seqno >= 10) - arg->status = NATS_ERR; - else - arg->results[seqno] = (arg->results[seqno] + 1); - - break; - } - case 7: - { - arg->msgReceived = true; - natsCondition_Signal(arg->c); - - while (!arg->closed) - natsCondition_Wait(arg->c, arg->m); - - break; - } - case 8: - { - arg->sum++; - - while (!arg->closed) - natsCondition_Wait(arg->c, arg->m); - - break; - } - case 10: - { - arg->status = (natsMsg_IsNoResponders(msg) ? NATS_OK : NATS_ERR); - break; - } - } - - natsMsg_Destroy(msg); - - if (doSignal) - { - arg->msgReceived = true; - natsCondition_Broadcast(arg->c); - } - natsMutex_Unlock(arg->m); -} - -static void -_closedCb(natsConnection *nc, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - arg->closed = true; - natsCondition_Broadcast(arg->c); - natsMutex_Unlock(arg->m); -} - -static natsStatus -_waitForConnClosed(struct threadArg *arg) -{ - natsStatus s = NATS_OK; - - natsMutex_Lock(arg->m); - while ((s != NATS_TIMEOUT) && !arg->closed) - s = natsCondition_TimedWait(arg->c, arg->m, 2000); - arg->closed = false; - natsMutex_Unlock(arg->m); - - return s; -} - -static void -test_ParseStateReconnectFunctionality(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - test("Parse State Reconnect Functionality: "); - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - arg.string = "bar"; - arg.status = NATS_OK; - } - if (s == NATS_OK) - opts = _createReconnectOptions(); - - if ((opts == NULL) - || (natsOptions_SetDisconnectedCB(opts, _disconnectedCb, &arg) != NATS_OK) - || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK)) - { - FAIL("Unable to create reconnect options!"); - } - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, &arg)); - IFOK(s, natsConnection_Flush(nc)); - - if (s == NATS_OK) - { - // Simulate partialState, this needs to be cleared - natsConn_Lock(nc); - nc->ps->state = OP_PON; - natsConn_Unlock(nc); - } - - _stopServer(serverPid); - serverPid = NATS_INVALID_PID; - - if (s == NATS_OK) - { - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.disconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 500); - natsMutex_Unlock(arg.m); - } - - IFOK(s, natsConnection_PublishString(nc, "foo", arg.string)); - - if (s == NATS_OK) - { - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - } - - IFOK(s, natsConnection_FlushTimeout(nc, 5000)); - - if (s == NATS_OK) - { - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.msgReceived) - s = natsCondition_TimedWait(arg.c, arg.m, 1500); - natsMutex_Unlock(arg.m); - - if (s == NATS_OK) - s = arg.status; - } - - testCond((s == NATS_OK) && (nc->stats.reconnects == 1)); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _waitForConnClosed(&arg); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_ServersRandomize(void) -{ - natsStatus s; - natsOptions *opts = NULL; - natsConnection *nc = NULL; - natsPid pid = NATS_INVALID_PID; - int serversCount; - - serversCount = sizeof(testServers) / sizeof(char *); - - test("Server Pool with Randomize: "); - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetServers(opts, testServers, serversCount)); - if (s == NATS_OK) - { - int same = 0; - int allSame = 0; - - for (int iter=0; (s == NATS_OK) && (iter<1000); iter++) - { - s = natsConn_create(&nc, natsOptions_clone(opts)); - if (s == NATS_OK) - { - // In theory, this could happen... - for (int i=0; isrvPool->srvrs[i]->url->fullUrl) == 0) - { - same++; - } - } - if (same == serversCount) - allSame++; - } - natsConn_release(nc); - nc = NULL; - } - - if (allSame > 10) - s = NATS_ERR; - } - testCond(s == NATS_OK); - - // Now test that we do not randomize if proper flag is set. - test("Server Pool With NoRandomize: ") - s = natsOptions_SetNoRandomize(opts, true); - IFOK(s, natsConn_create(&nc, natsOptions_clone(opts))); - if (s == NATS_OK) - { - for (int i=0; isrvPool->srvrs[i]->url->fullUrl) != 0) - { - s = NATS_ERR; - break; - } - } - testCond(s == NATS_OK); - natsConn_release(nc); - nc = NULL; - - // Although the original intent was that if Opts.Url is - // set, Opts.Servers is not (and vice versa), the behavior - // is that Opts.Url is always first, even when randomization - // is enabled. So make sure that this is still the case. - test("If Options.URL is set, it should be first: ") - s = natsOptions_SetNoRandomize(opts, false); - IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL)); - IFOK(s, natsConn_create(&nc, natsOptions_clone(opts))); - if (s == NATS_OK) - { - int same = 0; - // In theory, this could happen... - for (int i=0; isrvPool->srvrs[i+1]->url->fullUrl) == 0) - { - same++; - } - } - if (same == serversCount) - s = NATS_ERR; - } - if ((s == NATS_OK) - && strcmp(nc->srvPool->srvrs[0]->url->fullUrl, - NATS_DEFAULT_URL) != 0) - { - s = NATS_ERR; - } - testCond(s == NATS_OK); - - natsConn_release(nc); - nc = NULL; - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("NoRandomize==true passed to context: "); - s = natsOptions_SetNoRandomize(opts, true); - IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL)); - IFOK(s, natsConnection_Connect(&nc, opts)); - if (s == NATS_OK) - { - natsConn_Lock(nc); - if (!nc->sockCtx.noRandomize) - s = NATS_ERR; - natsConn_Unlock(nc); - } - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - nc = NULL; - - test("NoRandomize==false passed to context: "); - s = natsOptions_SetNoRandomize(opts, false); - IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL)); - IFOK(s, natsConnection_Connect(&nc, opts)); - if (s == NATS_OK) - { - natsConn_Lock(nc); - if (nc->sockCtx.noRandomize) - s = NATS_ERR; - natsConn_Unlock(nc); - } - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - _stopServer(pid); -} - -static void -test_SelectNextServer(void) -{ - natsStatus s; - natsOptions *opts = NULL; - natsConnection *nc = NULL; - natsSrv *srv = NULL; - int serversCount; - - serversCount = sizeof(testServers) / sizeof(char *); - - test("Test default server pool selection: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetServers(opts, testServers, serversCount)); - IFOK(s, natsOptions_SetNoRandomize(opts, true)); - IFOK(s, natsConn_create(&nc, natsOptions_clone(opts))); - testCond((s == NATS_OK) - && (nc->cur->url == nc->srvPool->srvrs[0]->url)); - - test("Get next server: "); - if (s == NATS_OK) - { - srv = natsSrvPool_GetNextServer(nc->srvPool, nc->opts, nc->cur); - if (srv != NULL) - nc->cur = srv; - } - testCond((s == NATS_OK) - && (nc != NULL) - && (nc->cur != NULL)); - - test("Check list size: "); - testCond((s == NATS_OK) - && (nc != NULL) - && (nc->srvPool != NULL) - && (nc->srvPool->size == serversCount)); - - test("Check selection: "); - testCond((s == NATS_OK) - && (nc != NULL) - && (nc->cur->url != NULL) - && (nc->cur->url->fullUrl != NULL) - && (strcmp(nc->cur->url->fullUrl, testServers[1]) == 0)); - - test("Check old was pushed to last position: "); - testCond((s == NATS_OK) - && (nc != NULL) - && (nc->srvPool != NULL) - && (nc->srvPool->srvrs != NULL) - && (nc->srvPool->size > 0) - && (nc->srvPool->srvrs[nc->srvPool->size - 1] != NULL) - && (nc->srvPool->srvrs[nc->srvPool->size - 1]->url != NULL) - && (nc->srvPool->srvrs[nc->srvPool->size - 1]->url->fullUrl != NULL) - && (strcmp(nc->srvPool->srvrs[nc->srvPool->size - 1]->url->fullUrl, - testServers[0]) == 0)); - - test("Got correct server: "); - testCond((s == NATS_OK) - && (srv != NULL) - && (nc != NULL) - && (nc->srvPool != NULL) - && (nc->srvPool->srvrs != NULL) - && (nc->srvPool->size > 0) - && (srv == (nc->srvPool->srvrs[0]))); - - // Test that we do not keep servers where we have tried to reconnect past our limit. - if (s == NATS_OK) - { - test("Get next server: "); - if ((nc == NULL) - || (nc->srvPool == NULL) - || (nc->srvPool->srvrs == NULL) - || (nc->srvPool->srvrs[0] == NULL)) - { - s = NATS_ERR; - } - else - { - nc->srvPool->srvrs[0]->reconnects = nc->opts->maxReconnect; - } - if (s == NATS_OK) - { - srv = natsSrvPool_GetNextServer(nc->srvPool, nc->opts, nc->cur); - if (srv != NULL) - nc->cur = srv; - } - testCond((s == NATS_OK) && (nc->cur != NULL)); - } - - // Check that we are now looking at #3, and current is not in the list. - test("Check list size: "); - testCond((s == NATS_OK) - && (nc->srvPool->size == (serversCount - 1))); - - test("Check selection: "); - testCond((s == NATS_OK) - && (nc != NULL) - && (nc->cur != NULL) - && (nc->cur->url != NULL) - && (nc->cur->url->fullUrl != NULL) - && (strcmp(nc->cur->url->fullUrl, testServers[2]) == 0)); - - test("Check last server was discarded: "); - testCond((s == NATS_OK) - && (nc != NULL) - && (nc->srvPool != NULL) - && (nc->srvPool->srvrs != NULL) - && (nc->srvPool->size > 0) - && (nc->srvPool->srvrs[nc->srvPool->size - 1] != NULL) - && (nc->srvPool->srvrs[nc->srvPool->size - 1]->url != NULL) - && (nc->srvPool->srvrs[nc->srvPool->size - 1]->url->fullUrl != NULL) - && (strcmp(nc->srvPool->srvrs[nc->srvPool->size - 1]->url->fullUrl, - testServers[1]) != 0)); - - natsConn_release(nc); - natsOptions_Destroy(opts); -} - -static void -parserNegTest(int lineNum) -{ - char txt[64]; - - snprintf(txt, sizeof(txt), "Test line %d: ", lineNum); - test(txt); -} - -#define PARSER_START_TEST parserNegTest(__LINE__) - -static void -test_ParserPing(void) -{ - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsStatus s; - char ping[64]; - - s = natsOptions_Create(&opts); - IFOK(s, natsConn_create(&nc, opts)); - IFOK(s, natsParser_Create(&(nc->ps))); - IFOK(s, natsBuf_Create(&(nc->pending), 1000)); - if (s == NATS_OK) - nc->usePending = true; - if (s != NATS_OK) - FAIL("Unable to setup test"); - - - PARSER_START_TEST; - testCond(nc->ps->state == OP_START); - - snprintf(ping, sizeof(ping), "PING\r\n"); - - PARSER_START_TEST; - s = natsParser_Parse(nc, ping, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_P)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, ping + 1, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_PI)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, ping + 2, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_PIN)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, ping + 3, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_PING)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, ping + 4, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_PING)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, ping + 5, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_START)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, ping, (int)strlen(ping)); - testCond((s == NATS_OK) && (nc->ps->state == OP_START)); - - // Should tolerate spaces - snprintf(ping, sizeof(ping), "%s", "PING \r"); - PARSER_START_TEST; - s = natsParser_Parse(nc, ping, (int)strlen(ping)); - testCond((s == NATS_OK) && (nc->ps->state == OP_PING)); - - nc->ps->state = OP_START; - snprintf(ping, sizeof(ping), "%s", "PING \r \n"); - PARSER_START_TEST; - s = natsParser_Parse(nc, ping, (int)strlen(ping)); - testCond((s == NATS_OK) && (nc->ps->state == OP_START)); - - natsConnection_Destroy(nc); -} - -static void -test_ParserErr(void) -{ - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsStatus s; - char errProto[1024]; - char expected[256]; - int len; - - s = natsOptions_Create(&opts); - IFOK(s, natsConn_create(&nc, opts)); - IFOK(s, natsParser_Create(&(nc->ps))); - IFOK(s, natsBuf_Create(&(nc->pending), 1000)); - if (s == NATS_OK) - { - nc->usePending = true; - nc->status = NATS_CONN_STATUS_CLOSED; - } - if (s != NATS_OK) - FAIL("Unable to setup test"); - - // This test focuses on the parser only, not how the error is - // actually processed by the upper layer. - - PARSER_START_TEST; - testCond(nc->ps->state == OP_START); - - snprintf(expected, sizeof(expected), "%s", "'Any kind of error'"); - snprintf(errProto, sizeof(errProto), "-ERR %s\r\n", expected); - len = (int) strlen(errProto); - - PARSER_START_TEST; - s = natsParser_Parse(nc, errProto, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_MINUS)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, errProto + 1, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_MINUS_E)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, errProto + 2, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_MINUS_ER)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, errProto + 3, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_MINUS_ERR)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, errProto + 4, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_MINUS_ERR_SPC)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, errProto + 5, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_MINUS_ERR_SPC)); - - // Check with split arg buffer - PARSER_START_TEST; - s = natsParser_Parse(nc, errProto + 6, 1); - testCond((s == NATS_OK) && (nc->ps->state == MINUS_ERR_ARG)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, errProto + 7, 3); - testCond((s == NATS_OK) && (nc->ps->state == MINUS_ERR_ARG)); - - // Verify content - PARSER_START_TEST; - s = natsParser_Parse(nc, errProto + 10, len - 10 - 2); - testCond((s == NATS_OK) - && (nc->ps->state == MINUS_ERR_ARG) - && (nc->ps->argBuf != NULL) - && (strncmp(nc->ps->argBuf->data, expected, nc->ps->argBuf->len) == 0)); - - // Finish parsing - PARSER_START_TEST; - s = natsParser_Parse(nc, errProto + len - 1, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_START)); - - // Check without split arg buffer - snprintf(errProto, sizeof(errProto), "-ERR '%s'\r\n", "Any Error"); - PARSER_START_TEST; - s = natsParser_Parse(nc, errProto, (int)strlen(errProto)); - testCond((s == NATS_OK) && (nc->ps->state == OP_START)); - - natsConnection_Destroy(nc); -} - -static void -test_ParserOK(void) -{ - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsStatus s; - char okProto[256]; - - s = natsOptions_Create(&opts); - IFOK(s, natsConn_create(&nc, opts)); - IFOK(s, natsParser_Create(&(nc->ps))); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - PARSER_START_TEST; - testCond(nc->ps->state == OP_START); - - snprintf(okProto, sizeof(okProto), "+OKay\r\n"); - - PARSER_START_TEST; - s = natsParser_Parse(nc, okProto, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_PLUS)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, okProto + 1, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_PLUS_O)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, okProto + 2, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_PLUS_OK)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, okProto + 3, (int)strlen(okProto) - 3); - testCond((s == NATS_OK) && (nc->ps->state == OP_START)); - - natsConnection_Destroy(nc); -} - -static void -test_ParseINFO(void) -{ - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsStatus s; - char infoProto[256]; - - s = natsOptions_Create(&opts); - IFOK(s, natsConn_create(&nc, opts)); - IFOK(s, natsParser_Create(&(nc->ps))); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - PARSER_START_TEST; - testCond(nc->ps->state == OP_START); - - snprintf(infoProto, sizeof(infoProto), "INFO \t{\"server_id\": \"abc\"}\r\n"); - - PARSER_START_TEST; - s = natsParser_Parse(nc, infoProto, 7); - testCond((s == NATS_OK) && (nc->ps->state == INFO_ARG) && (infoProto[nc->ps->afterSpace] == '{')); - - PARSER_START_TEST; - s = natsParser_Parse(nc, infoProto +7, (int)strlen(infoProto) - 7); - testCond((s == NATS_OK) - && (nc->ps->state == OP_START) - && (nc->info.id != NULL) && (strcmp(nc->info.id, "abc") == 0)); - - natsConnection_Destroy(nc); -} - -static void -test_ParserShouldFail(void) -{ - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsStatus s; - char buf[64]; - - s = natsOptions_Create(&opts); - IFOK(s, natsConn_create(&nc, opts)); - IFOK(s, natsParser_Create(&(nc->ps))); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - // Negative tests: - - PARSER_START_TEST; - snprintf(buf, sizeof(buf), "%s", " PING"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "POO"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "Px"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "PIx"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "PINx"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - // Stop here because 'PING' protos are tolerant for anything between PING and \n - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "POx"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "PONx"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - // Stop here because 'PONG' protos are tolerant for anything between PONG and \n - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "ZOO"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "Mx\r\n"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "MSx\r\n"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "MSGx\r\n"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "MSG foo\r\n"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "MSG \r\n"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "MSG foo 1\r\n"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "MSG foo bar 1\r\n"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "MSG foo bar 1 baz\r\n"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "MSG foo 1 bar baz\r\n"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "+x\r\n"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "+Ox\r\n"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "-x\r\n"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "-Ex\r\n"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "-ERx\r\n"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - PARSER_START_TEST; - nc->ps->state = OP_START; - snprintf(buf, sizeof(buf), "%s", "-ERRx\r\n"); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond(s != NATS_OK); - - natsConnection_Destroy(nc); -} - -static void -test_ParserSplitMsg(void) -{ - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsStatus s; - char buf[2*MAX_CONTROL_LINE_SIZE]; - uint64_t expectedCount; - uint64_t expectedSize; - int msgSize, start, i; - - s = natsOptions_Create(&opts); - IFOK(s, natsConn_create(&nc, opts)); - IFOK(s, natsParser_Create(&(nc->ps))); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - expectedCount = 1; - expectedSize = 3; - - snprintf(buf, sizeof(buf), "%s", "MSG a 1 3\r\nfoo\r\n"); - - // parsing: 'MSG a' - PARSER_START_TEST; - s = natsParser_Parse(nc, buf, 5); - testCond((s == NATS_OK) && (nc->ps->argBuf != NULL)); - - // parsing: ' 1 3\r\nf' - PARSER_START_TEST; - s = natsParser_Parse(nc, buf + 5, 7); - testCond((s == NATS_OK) - && (nc->ps->ma.size == 3) - && (nc->ps->ma.sid == 1) - && (nc->ps->ma.subject->len == 1) - && (strncmp(nc->ps->ma.subject->data, "a", 1) == 0) - && (nc->ps->msgBuf != NULL)); - - // parsing: 'oo\r\n' - PARSER_START_TEST; - s = natsParser_Parse(nc, buf + 12, (int)strlen(buf) - 12); - testCond((s == NATS_OK) - && (nc->stats.inMsgs == expectedCount) - && (nc->stats.inBytes == expectedSize) - && (nc->ps->argBuf == NULL) - && (nc->ps->msgBuf == NULL) - && (nc->ps->state == OP_START)); - - // parsing: 'MSG a 1 3\r\nfo' - PARSER_START_TEST; - s = natsParser_Parse(nc, buf, 13); - testCond((s == NATS_OK) - && (nc->ps->ma.size == 3) - && (nc->ps->ma.sid == 1) - && (nc->ps->ma.subject->len == 1) - && (strncmp(nc->ps->ma.subject->data, "a", 1) == 0) - && (nc->ps->argBuf != NULL) - && (nc->ps->msgBuf != NULL)); - - expectedCount++; - expectedSize += 3; - - // parsing: 'o\r\n' - PARSER_START_TEST; - s = natsParser_Parse(nc, buf + 13, (int)strlen(buf) - 13); - testCond((s == NATS_OK) - && (nc->stats.inMsgs == expectedCount) - && (nc->stats.inBytes == expectedSize) - && (nc->ps->argBuf == NULL) - && (nc->ps->msgBuf == NULL) - && (nc->ps->state == OP_START)); - - snprintf(buf, sizeof(buf), "%s", "MSG a 1 6\r\nfoobar\r\n"); - - // parsing: 'MSG a 1 6\r\nfo' - PARSER_START_TEST; - s = natsParser_Parse(nc, buf, 13); - testCond((s == NATS_OK) - && (nc->ps->ma.size == 6) - && (nc->ps->ma.sid == 1) - && (nc->ps->ma.subject->len == 1) - && (strncmp(nc->ps->ma.subject->data, "a", 1) == 0) - && (nc->ps->argBuf != NULL) - && (nc->ps->msgBuf != NULL)); - - // parsing: 'ob' - PARSER_START_TEST; - s = natsParser_Parse(nc, buf + 13, 2); - testCond(s == NATS_OK) - - expectedCount++; - expectedSize += 6; - - // parsing: 'ar\r\n' - PARSER_START_TEST; - s = natsParser_Parse(nc, buf + 15, (int)strlen(buf) - 15); - testCond((s == NATS_OK) - && (nc->stats.inMsgs == expectedCount) - && (nc->stats.inBytes == expectedSize) - && (nc->ps->argBuf == NULL) - && (nc->ps->msgBuf == NULL) - && (nc->ps->state == OP_START)); - - // Let's have a msg that is bigger than the parser's scratch size. - // Since we prepopulate the msg with 'foo', adding 3 to the size. - msgSize = sizeof(nc->ps->scratch) + 100 + 3; - - snprintf(buf, sizeof(buf), "MSG a 1 b %d\r\nfoo", msgSize); - start = (int)strlen(buf); - for (i=0; i\r\nfoo' - PARSER_START_TEST; - s = natsParser_Parse(nc, buf, start); - testCond((s == NATS_OK) - && (nc->ps->ma.size == msgSize) - && (nc->ps->ma.sid == 1) - && (nc->ps->ma.subject->len == 1) - && (strncmp(nc->ps->ma.subject->data, "a", 1) == 0) - && (nc->ps->ma.reply->len == 1) - && (strncmp(nc->ps->ma.reply->data, "b", 1) == 0) - && (nc->ps->argBuf != NULL) - && (nc->ps->msgBuf != NULL)); - - expectedCount++; - expectedSize += (uint64_t)msgSize; - - // parsing: 'abcde...' - PARSER_START_TEST; - s = natsParser_Parse(nc, buf + start, (int)strlen(buf) - start - 2); - testCond((s == NATS_OK) - && (nc->ps->argBuf != NULL) - && (nc->ps->msgBuf != NULL) - && (nc->ps->state == MSG_PAYLOAD)); - - // Verify content - PARSER_START_TEST; - s = ((strncmp(nc->ps->msgBuf->data, "foo", 3) == 0) ? NATS_OK : NATS_ERR); - if (s == NATS_OK) - { - int k; - - for (k=3; (s == NATS_OK) && (kps->ma.size); k++) - s = (nc->ps->msgBuf->data[k] == (char)('a' + ((k-3) % 26)) ? NATS_OK : NATS_ERR); - } - testCond(s == NATS_OK) - - PARSER_START_TEST; - s = natsParser_Parse(nc, buf + (int)strlen(buf) - 2, 2); - testCond((s == NATS_OK) - && (nc->stats.inMsgs == expectedCount) - && (nc->stats.inBytes == expectedSize) - && (nc->ps->argBuf == NULL) - && (nc->ps->msgBuf == NULL) - && (nc->ps->state == OP_START)); - - natsConnection_Destroy(nc); -} - -#define RECREATE_PARSER \ - natsParser_Destroy(nc->ps); \ - s = natsParser_Create(&(nc->ps)); \ - if (s != NATS_OK) \ - FAIL("Unable to setup test"); \ - -static void -test_ProcessMsgArgs(void) -{ - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsStatus s; - char buf[2048]; - const char* le = NULL; - - s = natsOptions_Create(&opts); - IFOK(s, natsConn_create(&nc, opts)); - IFOK(s, natsParser_Create(&(nc->ps))); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - snprintf(buf, sizeof(buf), "%s", "MSG a b c d e\r\n"); - - test("Parsing MSG with too many arguments: ") - // parsing: 'MSG a' - natsParser_Parse(nc, buf, 5); - // parse the rest.. - natsParser_Parse(nc, buf + 5, 10); - s = natsConnection_GetLastError(nc, &le); - testCond((s == NATS_PROTOCOL_ERROR) - && (nc->ps->argBuf == NULL) - && (nc->ps->msgBuf == NULL) - && (nc->ps->ma.subject == NULL) - && (nc->ps->ma.reply == NULL) - && (le != NULL) - && (strstr(le, "wrong number of arguments") != NULL)); - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "MSG foo 1\r\n"); - test("Parsing MSG with not enough arguments: ") - natsParser_Parse(nc, buf, (int) strlen(buf)); - s = natsConnection_GetLastError(nc, &le); - testCond((s == NATS_PROTOCOL_ERROR) - && (nc->ps->argBuf == NULL) - && (nc->ps->msgBuf == NULL) - && (nc->ps->ma.subject == NULL) - && (nc->ps->ma.reply == NULL) - && (le != NULL) - && (strstr(le, "wrong number of arguments") != NULL)); - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "MSG foo abc 2\r\n"); - test("Parsing MSG with bad sid: ") - natsParser_Parse(nc, buf, (int) strlen(buf)); - s = natsConnection_GetLastError(nc, &le); - testCond((s == NATS_PROTOCOL_ERROR) - && (nc->ps->argBuf == NULL) - && (nc->ps->msgBuf == NULL) - && (nc->ps->ma.subject == NULL) - && (nc->ps->ma.reply == NULL) - && (le != NULL) - && (strstr(le, "Bad or Missing Sid") != NULL)); - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "MSG foo 1 abc\r\n"); - test("Parsing MSG with bad size: ") - natsParser_Parse(nc, buf, (int) strlen(buf)); - s = natsConnection_GetLastError(nc, &le); - testCond((s == NATS_PROTOCOL_ERROR) - && (nc->ps->argBuf == NULL) - && (nc->ps->msgBuf == NULL) - && (nc->ps->ma.subject == NULL) - && (nc->ps->ma.reply == NULL) - && (le != NULL) - && (strstr(le, "Bad or Missing Size") != NULL)); - - snprintf(buf, sizeof(buf), "%s", "MSG foo 1 bar abc\r\n"); - test("Parsing MSG with bad size (with reply): ") - natsParser_Parse(nc, buf, (int) strlen(buf)); - s = natsConnection_GetLastError(nc, &le); - testCond((s == NATS_PROTOCOL_ERROR) - && (nc->ps->argBuf == NULL) - && (nc->ps->msgBuf == NULL) - && (nc->ps->ma.subject == NULL) - && (nc->ps->ma.reply == NULL) - && (le != NULL) - && (strstr(le, "Bad or Missing Size") != NULL)); - - // Test extra spaces first without reply - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "MSG foo 1 2\r\n"); - test("Parsing MSG with extra space before sid: ") - s = natsParser_Parse(nc, buf, (int) strlen(buf)); - testCond((s == NATS_OK) - && (natsBuf_Len(nc->ps->ma.subject) == 3) - && (strncmp(natsBuf_Data(nc->ps->ma.subject), "foo", 3) == 0) - && (nc->ps->ma.sid == 1) - && (nc->ps->ma.reply == NULL) - && (nc->ps->ma.size == 2)); - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "MSG bar 1 2\r\n"); - test("Parsing MSG with extra space before size: ") - s = natsParser_Parse(nc, buf, (int) strlen(buf)); - testCond((s == NATS_OK) - && (natsBuf_Len(nc->ps->ma.subject) == 3) - && (strncmp(natsBuf_Data(nc->ps->ma.subject), "bar", 3) == 0) - && (nc->ps->ma.sid == 1) - && (nc->ps->ma.reply == NULL) - && (nc->ps->ma.size == 2)); - - // Test extra spaces first with reply - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "MSG baz 3 bat 4\r\n"); - test("Parsing MSG with extra space before sid: ") - s = natsParser_Parse(nc, buf, (int) strlen(buf)); - testCond((s == NATS_OK) - && (natsBuf_Len(nc->ps->ma.subject) == 3) - && (strncmp(natsBuf_Data(nc->ps->ma.subject), "baz", 3) == 0) - && (nc->ps->ma.sid == 3) - && (natsBuf_Len(nc->ps->ma.reply) == 3) - && (strncmp(natsBuf_Data(nc->ps->ma.reply), "bat", 3) == 0) - && (nc->ps->ma.size == 4)); - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "MSG boo 5 baa 6\r\n"); - test("Parsing MSG with extra space before reply: ") - s = natsParser_Parse(nc, buf, (int) strlen(buf)); - testCond((s == NATS_OK) - && (natsBuf_Len(nc->ps->ma.subject) == 3) - && (strncmp(natsBuf_Data(nc->ps->ma.subject), "boo", 3) == 0) - && (nc->ps->ma.sid == 5) - && (natsBuf_Len(nc->ps->ma.reply) == 3) - && (strncmp(natsBuf_Data(nc->ps->ma.reply), "baa", 3) == 0) - && (nc->ps->ma.size == 6)); - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "MSG coo 7 caa 8\r\n"); - test("Parsing MSG with extra space before size: ") - s = natsParser_Parse(nc, buf, (int) strlen(buf)); - testCond((s == NATS_OK) - && (natsBuf_Len(nc->ps->ma.subject) == 3) - && (strncmp(natsBuf_Data(nc->ps->ma.subject), "coo", 3) == 0) - && (nc->ps->ma.sid == 7) - && (natsBuf_Len(nc->ps->ma.reply) == 3) - && (strncmp(natsBuf_Data(nc->ps->ma.reply), "caa", 3) == 0) - && (nc->ps->ma.size == 8)); - - // Test with extra space everywhere - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "MSG doo 8 daa 9 \r\n"); - test("Parsing MSG with extra space everywhere: ") - s = natsParser_Parse(nc, buf, (int) strlen(buf)); - testCond((s == NATS_OK) - && (natsBuf_Len(nc->ps->ma.subject) == 3) - && (strncmp(natsBuf_Data(nc->ps->ma.subject), "doo", 3) == 0) - && (nc->ps->ma.sid == 8) - && (natsBuf_Len(nc->ps->ma.reply) == 3) - && (strncmp(natsBuf_Data(nc->ps->ma.reply), "daa", 3) == 0) - && (nc->ps->ma.size == 9)); - - // Test HMSG - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "HMSG foo 1 bar 2 3\r\n"); - test("Parsing HMSG: "); - s = natsParser_Parse(nc, buf, (int) strlen(buf)); - testCond((s == NATS_OK) - && (natsBuf_Len(nc->ps->ma.subject) == 3) - && (strncmp(natsBuf_Data(nc->ps->ma.subject), "foo", 3) == 0) - && (nc->ps->ma.sid == 1) - && (natsBuf_Len(nc->ps->ma.reply) == 3) - && (strncmp(natsBuf_Data(nc->ps->ma.reply), "bar", 3) == 0) - && (nc->ps->ma.hdr == 2) - && (nc->ps->ma.size == 3)); - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "HMSG foo 1 3\r\n"); - test("Parsing HMSG not enough args: "); - natsParser_Parse(nc, buf, (int) strlen(buf)); - s = natsConnection_GetLastError(nc, &le); - testCond((s == NATS_PROTOCOL_ERROR) - && (nc->ps->argBuf == NULL) - && (nc->ps->msgBuf == NULL) - && (nc->ps->ma.subject == NULL) - && (nc->ps->ma.reply == NULL) - && (le != NULL) - && (strstr(le, "wrong number of arguments") != NULL)); - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "HMSG a b c d e f\r\n"); - test("Parsing HMSG too many args: "); - natsParser_Parse(nc, buf, (int) strlen(buf)); - s = natsConnection_GetLastError(nc, &le); - s = natsConnection_GetLastError(nc, &le); - testCond((s == NATS_PROTOCOL_ERROR) - && (nc->ps->argBuf == NULL) - && (nc->ps->msgBuf == NULL) - && (nc->ps->ma.subject == NULL) - && (nc->ps->ma.reply == NULL) - && (le != NULL) - && (strstr(le, "wrong number of arguments") != NULL)); - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "HMSG foo abc 2 4\r\n"); - test("Parsing HMSG with bad sid: "); - natsParser_Parse(nc, buf, (int) strlen(buf)); - s = natsConnection_GetLastError(nc, &le); - testCond((s == NATS_PROTOCOL_ERROR) - && (nc->ps->argBuf == NULL) - && (nc->ps->msgBuf == NULL) - && (nc->ps->ma.subject == NULL) - && (nc->ps->ma.reply == NULL) - && (le != NULL) - && (strstr(le, "Bad or Missing Sid") != NULL)); - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "HMSG foo 1 baz 10\r\n"); - test("Parsing HMSG with bad header size: "); - natsParser_Parse(nc, buf, (int) strlen(buf)); - s = natsConnection_GetLastError(nc, &le); - testCond((s == NATS_PROTOCOL_ERROR) - && (nc->ps->argBuf == NULL) - && (nc->ps->msgBuf == NULL) - && (nc->ps->ma.subject == NULL) - && (nc->ps->ma.reply == NULL) - && (le != NULL) - && (strstr(le, "Bad or Missing Header Size") != NULL)); - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "HMSG foo 1 bar baz 10\r\n"); - test("Parsing HMSG with bad header size (with reply): "); - natsParser_Parse(nc, buf, (int) strlen(buf)); - s = natsConnection_GetLastError(nc, &le); - testCond((s == NATS_PROTOCOL_ERROR) - && (nc->ps->argBuf == NULL) - && (nc->ps->msgBuf == NULL) - && (nc->ps->ma.subject == NULL) - && (nc->ps->ma.reply == NULL) - && (le != NULL) - && (strstr(le, "Bad or Missing Header Size") != NULL)); - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "HMSG foo 1 10 4\r\n"); - test("Parsing HMSG with bad header size (out of range): "); - natsParser_Parse(nc, buf, (int) strlen(buf)); - s = natsConnection_GetLastError(nc, &le); - testCond((s == NATS_PROTOCOL_ERROR) - && (nc->ps->argBuf == NULL) - && (nc->ps->msgBuf == NULL) - && (nc->ps->ma.subject == NULL) - && (nc->ps->ma.reply == NULL) - && (le != NULL) - && (strstr(le, "Bad or Missing Header Size") != NULL)); - - RECREATE_PARSER; - - snprintf(buf, sizeof(buf), "%s", "HMSG foo 1 bar 10 4\r\n"); - test("Parsing HMSG with bad header size (out of range with reply): "); - natsParser_Parse(nc, buf, (int) strlen(buf)); - s = natsConnection_GetLastError(nc, &le); - testCond((s == NATS_PROTOCOL_ERROR) - && (nc->ps->argBuf == NULL) - && (nc->ps->msgBuf == NULL) - && (nc->ps->ma.subject == NULL) - && (nc->ps->ma.reply == NULL) - && (le != NULL) - && (strstr(le, "Bad or Missing Header Size") != NULL)); - - natsConnection_Destroy(nc); -} - -static natsStatus -_checkPool(natsConnection *nc, char **expectedURLs, int expectedURLsCount) -{ - int i, j, attempts; - natsSrv *srv; - char *url = NULL; - char buf[64]; - bool ok = false; - - natsMutex_Lock(nc->mu); - for (attempts = 0; (!ok) && (attempts < 20); attempts++) - { - ok = (nc->srvPool->size == expectedURLsCount); - for (i = 0; i < expectedURLsCount; i++) - { - bool foundInPool = false; - url = expectedURLs[i]; - for (j = 0; j < nc->srvPool->size; j++) - { - srv = nc->srvPool->srvrs[j]; - snprintf(buf, sizeof(buf), "%s:%d", srv->url->host, srv->url->port); - if (strcmp(buf, url)) - { - foundInPool = true; - break; - } - } - if (!foundInPool) - { - ok = false; - break; - } - } - - if (ok) - break; - - natsMutex_Unlock(nc->mu); - nats_Sleep(100); - natsMutex_Lock(nc->mu); - } - - if (!ok) - { - if (nc->srvPool->size != expectedURLsCount) - printf("After 20 retries expected pool size to be %d, got %d\n", expectedURLsCount, nc->srvPool->size); - else if (url != NULL) - printf("After 20 retries did not find %s in pool\n", url); - } - - natsMutex_Unlock(nc->mu); - return ok ? NATS_OK : NATS_ERR; -} - -static natsStatus -checkNewURLsAddedRandomly(natsConnection *nc, char **urlsAfterPoolSetup, int initialPoolSize) -{ - natsStatus s; - int i; - char **currentPool = NULL; - int currentPoolSize = 0; - - s = natsConnection_GetServers(nc, ¤tPool, ¤tPoolSize); - if (s == NATS_OK) - { - // Reset status to error by default. If we find a new URL - // before the end of the initial list, we consider success - // and return. - s = NATS_ERR; - for (i= 0; i < initialPoolSize; i++) - { - // If one of the position in initial list is occupied - // by a new URL, we are ok. - if (strcmp(urlsAfterPoolSetup[i], currentPool[i])) - { - s = NATS_OK; - break; - } - } - } - if (currentPool != NULL) - { - for (i=0; ips))); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - snprintf(buf, sizeof(buf), "%s", "INFO {\"test\":\"abcde\"x\r\n"); - PARSER_START_TEST; - s = natsParser_Parse(nc, buf, 9); - testCond((s == NATS_OK) - && (nc->ps->state == INFO_ARG) - && (nc->ps->argBuf != NULL)); - - PARSER_START_TEST; - natsParser_Parse(nc, buf+9, 30); - lastErr = nats_GetLastError(&s); - testCond((s == NATS_ERR) - && (lastErr != NULL) - && (strstr(lastErr, "missing") != NULL)); - nats_clearLastError(); - - snprintf(buf, sizeof(buf), "%s", "INFO {}\r\n"); - - PARSER_START_TEST; - s = natsParser_Parse(nc, buf, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_I)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, buf+1, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_IN)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, buf+2, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_INF)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, buf+3, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_INFO)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, buf+4, 1); - testCond((s == NATS_OK) && (nc->ps->state == OP_INFO_SPC)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, buf+5, (int)strlen(buf)-5); - testCond((s == NATS_OK) && (nc->ps->state == OP_START)); - - // All at once - PARSER_START_TEST; - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - testCond((s == NATS_OK) && (nc->ps->state == OP_START)); - - // Server pool was setup in natsConn_create() - - // Partials requiring argBuf - snprintf(buf, sizeof(buf), "INFO {\"server_id\":\"%s\", \"host\":\"%s\", \"port\": %d, " \ - "\"auth_required\":%s, \"tls_required\": %s, \"max_payload\":%d}\r\n", - "test", "localhost", 4222, "true", "true", 2*1024*1024); - - PARSER_START_TEST; - testCond((s == NATS_OK) && (nc->ps->state == OP_START)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, buf, 9); - testCond((s == NATS_OK) - && (nc->ps->state == INFO_ARG) - && (nc->ps->argBuf != NULL)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, buf+9, 2); - testCond((s == NATS_OK) - && (nc->ps->state == INFO_ARG) - && (nc->ps->argBuf != NULL)); - - PARSER_START_TEST; - s = natsParser_Parse(nc, buf+11, (int)strlen(buf)-11); - testCond((s == NATS_OK) - && (nc->ps->state == OP_START) - && (nc->ps->argBuf == NULL)); - - test("Check INFO is correct: "); - testCond((s == NATS_OK) - && (strcmp(nc->info.id, "test") == 0) - && (strcmp(nc->info.host, "localhost") == 0) - && (nc->info.port == 4222) - && nc->info.authRequired - && nc->info.tlsRequired - && (nc->info.maxPayload == 2*1024*1024)); - - // Destroy parser, it will be recreated in the loops. - natsParser_Destroy(nc->ps); - nc->ps = NULL; - - // Good INFOs - for (i=0; i<(int) (sizeof(good)/sizeof(char*)); i++) - { - snprintf(buf, sizeof(buf), "Test with good INFO proto number %d: ", (i+1)); - test(buf); - s = natsParser_Create(&(nc->ps)); - IFOK(s, natsParser_Parse(nc, (char*) good[i], (int)strlen(good[i]))); - testCond((s == NATS_OK) - && (nc->ps->state == OP_START) - && (nc->ps->argBuf == NULL)); - natsParser_Destroy(nc->ps); - nc->ps = NULL; - } - - // Wrong INFOs - for (i=0; i<(int) (sizeof(wrong)/sizeof(char*)); i++) - { - snprintf(buf, sizeof(buf), "Test with wrong INFO proto number %d: ", (i+1)); - test(buf); - s = natsParser_Create(&(nc->ps)); - IFOK(s, natsParser_Parse(nc, (char*) wrong[i], (int)strlen(wrong[i]))); - testCond(!((s == NATS_OK) && (nc->ps->state == OP_START))); - natsParser_Destroy(nc->ps); - nc->ps = NULL; - } - nats_clearLastError(); - - // Now test the decoding of "connect_urls" - - // Destroy, we create a new one - natsConnection_Destroy(nc); - nc = NULL; - - s = natsOptions_Create(&opts); - IFOK(s, natsConn_create(&nc, opts)); - IFOK(s, natsParser_Create(&(nc->ps))); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - snprintf(buf, sizeof(buf), "%s", "INFO {\"connect_urls\":[\"localhost:4222\",\"localhost:5222\"]}\r\n"); - PARSER_START_TEST; - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - if (s == NATS_OK) - { - // Pool now should contain localhost:4222 (the default URL) and localhost:5222 - const char *urls[] = {"localhost:4222", "localhost:5222"}; - - s = _checkPool(nc, (char**)urls, (int)(sizeof(urls)/sizeof(char*))); - } - testCond((s == NATS_OK) && (nc->ps->state == OP_START)); - - // Make sure that if client receives the same, it is not added again. - PARSER_START_TEST; - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - if (s == NATS_OK) - { - // Pool should still contain localhost:4222 (the default URL) and localhost:5222 - const char *urls[] = {"localhost:4222", "localhost:5222"}; - - s = _checkPool(nc, (char**)urls, (int)(sizeof(urls)/sizeof(char*))); - } - testCond((s == NATS_OK) && (nc->ps->state == OP_START)); - - // Receive a new URL - snprintf(buf, sizeof(buf), "%s", "INFO {\"connect_urls\":[\"localhost:4222\",\"localhost:5222\",\"localhost:6222\"]}\r\n"); - PARSER_START_TEST; - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - if (s == NATS_OK) - { - // Pool now should contain localhost:4222 (the default URL) localhost:5222 and localhost:6222 - const char *urls[] = {"localhost:4222", "localhost:5222", "localhost:6222"}; - - s = _checkPool(nc, (char**)urls, (int)(sizeof(urls)/sizeof(char*))); - } - testCond((s == NATS_OK) && (nc->ps->state == OP_START)); - - natsConnection_Destroy(nc); - nc = NULL; - - // Check that new URLs are added at random places in the pool - if (s == NATS_OK) - { - int initialPoolSize = 0; - char **urlsAfterPoolSetup = NULL; - const char *newURLs = "\"impA:4222\", \"impB:4222\", \"impC:4222\", "\ - "\"impD:4222\", \"impE:4222\", \"impF:4222\", \"impG:4222\", "\ - "\"impH:4222\", \"impI:4222\", \"impJ:4222\""; - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetNoRandomize(opts, false)); - IFOK(s, natsOptions_SetServers(opts, testServers, sizeof(testServers)/sizeof(char*))); - IFOK(s, natsConn_create(&nc, opts)); - IFOK(s, natsParser_Create(&(nc->ps))); - // Capture the pool sequence after randomization - IFOK(s, natsConnection_GetServers(nc, &urlsAfterPoolSetup, &initialPoolSize)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - // Add new urls - snprintf(buf, sizeof(buf), "INFO {\"connect_urls\":[%s]}\r\n", newURLs); - test("New URLs are added randomly: "); - s = natsParser_Parse(nc, buf, (int)strlen(buf)); - IFOK(s, checkNewURLsAddedRandomly(nc, urlsAfterPoolSetup, initialPoolSize)); - testCond((s == NATS_OK) && (nc->ps->state == OP_START)); - - test("First URL should not have been changed: ") - testCond((s == NATS_OK) && !strcmp(nc->srvPool->srvrs[0]->url->fullUrl, urlsAfterPoolSetup[0])); - - if (urlsAfterPoolSetup != NULL) - { - for (i=0; imu); - testCond(nc->respPoolSize == 1); - natsMutex_Unlock(nc->mu); - - test("Pool max size: "); - for (i=0; imu); - testCond((s == NATS_OK) && (nc->respPoolSize == RESP_INFO_POOL_MAX_SIZE)); - natsMutex_Unlock(nc->mu); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - _stopServer(pid); -} - -static void -test_NoFlusherIfSendAsap(void) -{ - natsStatus s; - natsPid pid = NATS_INVALID_PID; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - struct threadArg arg; - int i; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - opts = _createReconnectOptions(); - if ((opts == NULL) - || (natsOptions_SetURL(opts, "nats://127.0.0.1:4222") != NATS_OK) - || (natsOptions_SetSendAsap(opts, true) != NATS_OK) - || (natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg))) - { - FAIL("Failed to setup test"); - } - arg.string = "test"; - arg.control = 1; - - pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222", true); - CHECK_SERVER_STARTED(pid); - - test("Connect/subscribe ok: "); - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg)); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - for (i=0; i<2; i++) - { - test("Send ok: "); - s = natsConnection_PublishString(nc, "foo", "test"); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.msgReceived) - s = natsCondition_TimedWait(arg.c, arg.m, 1500); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Flusher does not exist: "); - natsMutex_Lock(nc->mu); - s = (nc->flusherThread == NULL ? NATS_OK : NATS_ERR); - natsMutex_Unlock(nc->mu); - testCond(s == NATS_OK); - - if (i == 0) - { - _stopServer(pid); - pid = NATS_INVALID_PID; - pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222", true); - CHECK_SERVER_STARTED(pid); - } - } - - natsSubscription_Destroy(sub); - natsConnection_Close(nc); - _waitForConnClosed(&arg); - natsConnection_Destroy(nc); - - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - - _stopServer(pid); -} - -static void -test_HeadersAndSubPendingBytes(void) -{ - natsStatus s; - natsPid pid = NATS_INVALID_PID; - natsConnection *nc = NULL; - natsSubscription *sub1 = NULL; - natsSubscription *sub2 = NULL; - natsMsg *msg = NULL; - natsMsg *smsg = NULL; - int msgs = 0; - int bytes = 0; - struct threadArg arg; - int i; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test"); - arg.string = "test"; - - pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222", true); - CHECK_SERVER_STARTED(pid); - - test("Connect/subscribe ok: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_Subscribe(&sub1, nc, "foo", _recvTestString, (void*) &arg)); - IFOK(s, natsSubscription_SetPendingLimits(sub1, 1000, 100)); - IFOK(s, natsConnection_SubscribeSync(&sub2, nc, "foo")); - IFOK(s, natsSubscription_SetPendingLimits(sub2, 1000, 100)); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Create message with header: "); - s = natsMsg_Create(&msg, "foo", NULL, "hello", 5); - // Set a header with "large" value (50 bytes) - IFOK(s, natsMsgHeader_Set(msg, "Key", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); - testCond(s == NATS_OK); - - for (i=0; i<10; i++) { - test("Publish and receive message: "); - s = natsConnection_PublishMsg(nc, msg); - IFOK(s, natsSubscription_NextMsg(&smsg, sub2, 1000)); - if (s == NATS_OK) - { - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.msgReceived) - s = natsCondition_TimedWait(arg.c, arg.m, 1000); - arg.msgReceived = false; - natsMutex_Unlock(arg.m); - } - natsMsg_Destroy(smsg); - smsg = NULL; - testCond(s == NATS_OK); - } - - test("Check sub1's pending: "); - s = natsSubscription_GetPending(sub1, &msgs, &bytes); - testCond((s == NATS_OK) && (msgs == 0) && (bytes == 0)); - - test("Check sub2's pending: "); - s = natsSubscription_GetPending(sub1, &msgs, &bytes); - testCond((s == NATS_OK) && (msgs == 0) && (bytes == 0)); - - natsMsg_Destroy(msg); - natsSubscription_Destroy(sub1); - natsSubscription_Destroy(sub2); - natsConnection_Destroy(nc); - _destroyDefaultThreadArgs(&arg); - _stopServer(pid); -} - -static void -_dummyMsgHandler(natsConnection *nc, natsSubscription *sub, natsMsg *msg, - void *closure) -{ - // do nothing - - natsMsg_Destroy(msg); -} - -static void -test_LibMsgDelivery(void) -{ - natsStatus s; - natsPid serverPid = NATS_INVALID_PID; - natsOptions *opts = NULL; - natsConnection *nc = NULL; - natsSubscription *s1 = NULL; - natsSubscription *s2 = NULL; - natsSubscription *s3 = NULL; - natsSubscription *s4 = NULL; - natsSubscription *s5 = NULL; - natsMsgDlvWorker *lmd1 = NULL; - natsMsgDlvWorker *lmd2 = NULL; - natsMsgDlvWorker *lmd3 = NULL; - natsMsgDlvWorker *lmd4 = NULL; - natsMsgDlvWorker *lmd5 = NULL; - natsMsgDlvWorker **pwks = NULL; - int psize = 0; - int pmaxSize = 0; - int pidx = 0; - - // First, close the library and re-open, to reset things - nats_Close(); - - nats_Sleep(100); - - nats_Open(-1); - - // Check some pre-conditions that need to be met for the test to work. - test("Check initial values: ") - natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks); - testCond((pmaxSize == 1) && (psize == 0) && (pidx == 0)); - - test("Check pool size not negative: ") - s = nats_SetMessageDeliveryPoolSize(-1); - testCond(s != NATS_OK); - - test("Check pool size not zero: ") - s = nats_SetMessageDeliveryPoolSize(0); - testCond(s != NATS_OK); - - // Reset stack since we know the above generated errors. - nats_clearLastError(); - - test("Increase size to 2: ") - s = nats_SetMessageDeliveryPoolSize(2); - natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks); - testCond((s == NATS_OK) && (pmaxSize == 2) && (psize == 0)); - - test("Check pool size decreased (no error): ") - s = nats_SetMessageDeliveryPoolSize(1); - natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks); - testCond((s == NATS_OK) && (pmaxSize == 2) && (psize == 0)); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_UseGlobalMessageDelivery(opts, true)); - IFOK(s, natsConnection_Connect(&nc, opts)); - IFOK(s, natsConnection_Subscribe(&s1, nc, "foo", _dummyMsgHandler, NULL)); - if (s == NATS_OK) - { - natsMutex_Lock(s1->mu); - lmd1 = s1->libDlvWorker; - natsMutex_Unlock(s1->mu); - } - natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks); - test("Check 1st sub assigned 1st worker: ") - testCond((s == NATS_OK) && (psize == 1) && (lmd1 != NULL) - && (pidx == 1) && (pwks != NULL) && (lmd1 == pwks[0])); - - s = natsConnection_Subscribe(&s2, nc, "foo", _dummyMsgHandler, NULL); - if (s == NATS_OK) - { - natsMutex_Lock(s2->mu); - lmd2 = s2->libDlvWorker; - natsMutex_Unlock(s2->mu); - } - natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks); - test("Check 2nd sub assigned 2nd worker: ") - testCond((s == NATS_OK) && (psize == 2) && (lmd2 != lmd1) - && (pidx == 0) && (pwks != NULL) && (lmd2 == pwks[1])); - - s = natsConnection_Subscribe(&s3, nc, "foo", _dummyMsgHandler, NULL); - if (s == NATS_OK) - { - natsMutex_Lock(s3->mu); - lmd3 = s3->libDlvWorker; - natsMutex_Unlock(s3->mu); - } - natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks); - test("Check 3rd sub assigned 1st worker: ") - testCond((s == NATS_OK) && (psize == 2) && (lmd3 == lmd1) - && (pidx == 1) && (pwks != NULL) && (lmd3 == pwks[0])); - - // Bump the pool size to 4 - s = nats_SetMessageDeliveryPoolSize(4); - natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks); - test("Check increase of pool size: "); - testCond((s == NATS_OK) && (psize == 2) && (pidx == 1) - && (pmaxSize == 4) && (pwks != NULL)); - - s = natsConnection_Subscribe(&s4, nc, "foo", _dummyMsgHandler, NULL); - if (s == NATS_OK) - { - natsMutex_Lock(s4->mu); - lmd4 = s4->libDlvWorker; - natsMutex_Unlock(s4->mu); - } - natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks); - test("Check 4th sub assigned 2nd worker: ") - testCond((s == NATS_OK) && (psize == 2) && (lmd4 == lmd2) - && (pidx == 2) && (pwks != NULL) && (lmd4 == pwks[1])); - - s = natsConnection_Subscribe(&s5, nc, "foo", _dummyMsgHandler, NULL); - if (s == NATS_OK) - { - natsMutex_Lock(s5->mu); - lmd5 = s5->libDlvWorker; - natsMutex_Unlock(s5->mu); - } - natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks); - test("Check 5th sub assigned 3rd worker: ") - testCond((s == NATS_OK) && (psize == 3) && (lmd5 != lmd2) - && (pidx == 3) && (pwks != NULL) && (lmd5 == pwks[2])); - - natsSubscription_Destroy(s5); - natsSubscription_Destroy(s4); - natsSubscription_Destroy(s3); - natsSubscription_Destroy(s2); - natsSubscription_Destroy(s1); - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - _stopServer(serverPid); - - // Close the library and re-open, to reset things - nats_Close(); - - nats_Sleep(100); - - nats_Open(-1); -} - -static void -test_DefaultConnection(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - natsOptions *opts = NULL; - int i; - const char *urls[] = { - NATS_DEFAULT_URL, - "tcp://", - "nats://localhost", - ":4222", - }; - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetTimeout(opts, 500)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - test("Check connection fails without running server: "); -#ifndef _WIN32 - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - if (s != NATS_OK) -#endif - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_NO_SERVER); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - for (i=0; i<(int)(sizeof(urls)/sizeof(char*)); i++) - { - char buf[256]; - - snprintf(buf, sizeof(buf), "Test connect with '%s':", urls[i]); - test(buf); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - testCond(s == NATS_OK); - natsConnection_Destroy(nc); - nc = NULL; - } - - natsOptions_Destroy(opts); - - _stopServer(serverPid); -} - -static void -test_SimplifiedURLs(void) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; -#if defined(NATS_HAS_TLS) - natsOptions *opts = NULL; -#endif - const char *urls[] = { - "nats://127.0.0.1:4222", - "nats://127.0.0.1:", - "nats://127.0.0.1", - "127.0.0.1:", - "127.0.0.1" - }; - int urlsCount = sizeof(urls) / sizeof(char *); - int i; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Test simplified URLs to non TLS server: "); - for (i=0; ((s == NATS_OK) && (i>>> Server listening on [::] accepted an IPv4 connection"); - natsConnection_Destroy(nc); - nc = NULL; - } - else - { - s = NATS_OK; - } - } - testCond(s == NATS_OK); - - _stopServer(serverPid); - serverPid = NATS_INVALID_PID; - } - - natsOptions_Destroy(opts); -} - -static void -test_UseDefaultURLIfNoServerSpecified(void) -{ - natsStatus s; - natsOptions *opts = NULL; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - - s = natsOptions_Create(&opts); - if (s != NATS_OK) - FAIL("Unable to create options!"); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Check we can connect even if no server is specified: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_ConnectToWithMultipleURLs(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - char buf[256]; - - buf[0] = '\0'; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Check multiple URLs work: "); - s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4444,nats://127.0.0.1:4443,nats://127.0.0.1:4222"); - IFOK(s, natsConnection_Flush(nc)); - IFOK(s, natsConnection_GetConnectedUrl(nc, buf, sizeof(buf))); - testCond((s == NATS_OK) - && (strcmp(buf, "nats://127.0.0.1:4222") == 0)); - natsConnection_Destroy(nc); - - test("Check multiple URLs work, even with spaces: "); - s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4444 , nats://127.0.0.1:4443 , nats://127.0.0.1:4222 "); - IFOK(s, natsConnection_Flush(nc)); - IFOK(s, natsConnection_GetConnectedUrl(nc, buf, sizeof(buf))); - testCond((s == NATS_OK) - && (strcmp(buf, "nats://127.0.0.1:4222") == 0)); - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_ConnectionToWithNullURLs(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - char buf[256]; - - test("Check NULL URLs: "); - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, NULL); - IFOK(s, natsConnection_Flush(nc)); - IFOK(s, natsConnection_GetConnectedUrl(nc, buf, sizeof(buf))); - - testCond((s == NATS_OK) && (strcmp(buf, NATS_DEFAULT_URL) == 0)); - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_ConnectionWithNullOptions(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Check connect with NULL options is allowed: "); - s = natsConnection_Connect(&nc, NULL); - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_ConnectionStatus(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - test("Test connection status should be CONNECTED: "); - testCond((s == NATS_OK) - && (natsConnection_Status(nc) == NATS_CONN_STATUS_CONNECTED)); - - if (s == NATS_OK) - { - natsConnection_Close(nc); - test("Test connection status should be CLOSED: "); - testCond(natsConnection_Status(nc) == NATS_CONN_STATUS_CLOSED); - } - - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_ConnClosedCB(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - opts = _createReconnectOptions(); - - if ((opts == NULL) - || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) - || (natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg) != NATS_OK)) - { - FAIL("Unable to setup test for ConnClosedCB!"); - } - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_Connect(&nc, opts); - if (s == NATS_OK) - natsConnection_Close(nc); - - test("Test connection closed CB invoked: "); - - natsMutex_Lock(arg.m); - s = NATS_OK; - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 1000); - natsMutex_Unlock(arg.m); - - testCond((s == NATS_OK) && arg.closed); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_CloseDisconnectedCB(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - opts = _createReconnectOptions(); - - if ((opts == NULL) - || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) - || (natsOptions_SetAllowReconnect(opts, false) != NATS_OK) - || (natsOptions_SetDisconnectedCB(opts, _closedCb, (void*) &arg) != NATS_OK)) - { - FAIL("Unable to setup test for ConnClosedCB!"); - } - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_Connect(&nc, opts); - if (s == NATS_OK) - natsConnection_Close(nc); - - test("Test connection disconnected CB invoked: "); - - natsMutex_Lock(arg.m); - s = NATS_OK; - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 1000); - natsMutex_Unlock(arg.m); - - testCond((s == NATS_OK) && arg.closed); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_ServerStopDisconnectedCB(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - opts = _createReconnectOptions(); - - if ((opts == NULL) - || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) - || (natsOptions_SetAllowReconnect(opts, false) != NATS_OK) - || (natsOptions_SetDisconnectedCB(opts, _closedCb, (void*) &arg) != NATS_OK)) - { - FAIL("Unable to setup test for ConnClosedCB!"); - } - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_Connect(&nc, opts); - - _stopServer(serverPid); - - test("Test connection disconnected CB invoked on server shutdown: "); - - natsMutex_Lock(arg.m); - s = NATS_OK; - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - - testCond((s == NATS_OK) && arg.closed); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_ClosedConnections(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *goodsub = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsPid serverPid = NATS_INVALID_PID; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_SubscribeSync(&goodsub, nc, "foo")); - if (s == NATS_OK) - natsConnection_Close(nc); - - // Test all API endpoints do the right thing with a closed connection. - - test("Publish on closed should fail: ") - s = natsConnection_Publish(nc, "foo", NULL, 0); - testCond(s == NATS_CONNECTION_CLOSED); - - test("PublishMsg on closed should fail: ") - s = natsMsg_Create(&msg, "foo", NULL, NULL, 0); - IFOK(s, natsConnection_PublishMsg(nc, msg)); - testCond(s == NATS_CONNECTION_CLOSED); - - natsMsg_Destroy(msg); - msg = NULL; - - test("Flush on closed should fail: ") - s = natsConnection_Flush(nc); - testCond(s == NATS_CONNECTION_CLOSED); - - test("Subscribe on closed should fail: ") - s = natsConnection_Subscribe(&sub, nc, "foo", _dummyMsgHandler, NULL); - testCond(s == NATS_CONNECTION_CLOSED); - - test("SubscribeSync on closed should fail: ") - s = natsConnection_SubscribeSync(&sub, nc, "foo"); - testCond(s == NATS_CONNECTION_CLOSED); - - test("QueueSubscribe on closed should fail: ") - s = natsConnection_QueueSubscribe(&sub, nc, "foo", "bar", _dummyMsgHandler, NULL); - testCond(s == NATS_CONNECTION_CLOSED); - - test("QueueSubscribeSync on closed should fail: ") - s = natsConnection_QueueSubscribeSync(&sub, nc, "foo", "bar"); - testCond(s == NATS_CONNECTION_CLOSED); - - test("Request on closed should fail: ") - s = natsConnection_Request(&msg, nc, "foo", NULL, 0, 10); - testCond(s == NATS_CONNECTION_CLOSED); - - test("NextMsg on closed should fail: ") - s = natsSubscription_NextMsg(&msg, goodsub, 10); - testCond(s == NATS_CONNECTION_CLOSED); - - test("Unsubscribe on closed should fail: ") - s = natsSubscription_Unsubscribe(goodsub); - testCond(s == NATS_CONNECTION_CLOSED); - - natsSubscription_Destroy(goodsub); - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_ConnectVerboseOption(void) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg args; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s == NATS_OK) - { - opts = _createReconnectOptions(); - if (opts == NULL) - s = NATS_ERR; - } - IFOK(s, natsOptions_SetVerbose(opts, true)); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - test("Check connect OK with Verbose option: "); - - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_Flush(nc)); - - testCond(s == NATS_OK) - - _stopServer(serverPid); - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - test("Check reconnect OK with Verbose option: "); - - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.reconnected) - s = natsCondition_TimedWait(args.c, args.m, 5000); - natsMutex_Unlock(args.m); - - IFOK(s, natsConnection_Flush(nc)); - - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&args); - - _stopServer(serverPid); -} - -static void -test_ReconnectThreadLeak(void) -{ - natsStatus s; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - natsConnection *nc = NULL; - int i; - struct threadArg arg; - - serverPid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222", true); - CHECK_SERVER_STARTED(serverPid); - - s = _createDefaultThreadArgsForCbTests(&arg); - - opts = _createReconnectOptions(); - if ((opts == NULL) - || (natsOptions_SetURL(opts, "nats://127.0.0.1:4222") != NATS_OK) - || (natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg) != NATS_OK) - || (natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg) != NATS_OK) - || (natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg) != NATS_OK)) - { - FAIL("Unable to setup test"); - } - - test("Connect ok: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - for (i=0; i<10; i++) - { - natsMutex_Lock(nc->mu); - natsSock_Shutdown(nc->sockCtx.fd); - natsMutex_Unlock(nc->mu); - - test("Waiting for disconnect: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.disconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - arg.disconnected = false; - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Waiting for reconnect: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.reconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - arg.reconnected = false; - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - } - - natsConnection_Close(nc); - _waitForConnClosed(&arg); - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_ReconnectTotalTime(void) -{ - natsStatus s; - natsOptions *opts = NULL; - - test("Check reconnect time: "); - s = natsOptions_Create(&opts); - testCond((s == NATS_OK) - && ((opts->maxReconnect * opts->reconnectWait) >= (2 * 60 * 1000))); - - natsOptions_Destroy(opts); -} - -static void -test_ReconnectDisallowedFlags(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect: "); - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:22222")); - IFOK(s, natsOptions_SetAllowReconnect(opts, false)); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - - _stopServer(serverPid); - - test("Test connection closed CB invoked: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_ReconnectAllowedFlags(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - test("Create options and connect: "); - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:22222")); - IFOK(s, natsOptions_SetAllowReconnect(opts, true)); - IFOK(s, natsOptions_SetMaxReconnect(opts, 2)); - IFOK(s, natsOptions_SetReconnectWait(opts, 1000)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - - _stopServer(serverPid); - - test("Test reconnecting in progress: "); - - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 500); - natsMutex_Unlock(arg.m); - - testCond((s == NATS_TIMEOUT) - && !arg.disconnected - && natsConnection_IsReconnecting(nc)); - - natsConnection_Close(nc); - natsMutex_Lock(arg.m); - s = NATS_OK; - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 500); - natsMutex_Unlock(arg.m); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); -} - -static void -_closeConn(void *arg) -{ - natsConnection *nc = (natsConnection*) arg; - - natsConnection_Close(nc); -} - -static void -test_ConnCloseBreaksReconnectLoop(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsThread *t = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - opts = _createReconnectOptions(); - if (opts == NULL) - s = NATS_NO_MEMORY; - } - IFOK(s, natsOptions_SetMaxReconnect(opts, 1000)); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, &arg)); - IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, &arg)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - test("Connection close breaks out reconnect loop: "); - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_Flush(nc)); - - // Shutdown the server - _stopServer(serverPid); - - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.disconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 3000); - natsMutex_Unlock(arg.m); - - // Wait a bit before trying to close the connection to make sure - // that the reconnect loop has started. - nats_Sleep(1000); - - // Close the connection, this should break the reconnect loop. - // Do this in a go routine since the issue was that Close() - // would block until the reconnect loop is done. - s = natsThread_Create(&t, _closeConn, (void*) nc); - - // Even on Windows (where a createConn takes more than a second) - // we should be able to break the reconnect loop with the following - // timeout. - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 3000); - natsMutex_Unlock(arg.m); - - testCond((s == NATS_OK) && arg.closed); - - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_BasicReconnectFunctionality(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - arg.string = "bar"; - arg.status = NATS_OK; - } - if (s == NATS_OK) - opts = _createReconnectOptions(); - - if ((opts == NULL) - || (natsOptions_SetDisconnectedCB(opts, _disconnectedCb, &arg) != NATS_OK) - || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK)) - { - FAIL("Unable to create reconnect options!"); - } - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, &arg)); - IFOK(s, natsConnection_Flush(nc)); - - _stopServer(serverPid); - serverPid = NATS_INVALID_PID; - - test("Disconnected CB invoked: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.disconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 500); - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && arg.disconnected); - - test("Publish message: "); - s = natsConnection_PublishString(nc, "foo", arg.string); - if (s == NATS_OK) - { - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - } - IFOK(s, natsConnection_FlushTimeout(nc, 5000)); - testCond(s == NATS_OK); - - test("Check message received after reconnect: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.msgReceived) - s = natsCondition_TimedWait(arg.c, arg.m, 1500); - natsMutex_Unlock(arg.m); - - if (s == NATS_OK) - s = arg.status; - testCond((s == NATS_OK) && (nc->stats.reconnects == 1)); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _waitForConnClosed(&arg); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -_doneCb(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - arg->done = true; - natsCondition_Signal(arg->c); - natsMutex_Unlock(arg->m); - - natsMsg_Destroy(msg); -} - -static void -test_ExtendedReconnectFunctionality(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsSubscription *sub2 = NULL; - natsSubscription *sub3 = NULL; - natsSubscription *sub4 = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - arg.string = "bar"; - arg.status = NATS_OK; - arg.control=3; - } - if (s == NATS_OK) - opts = _createReconnectOptions(); - - if ((opts == NULL) - || (natsOptions_SetReconnectedCB(opts, _reconnectedCb, &arg) != NATS_OK) - || (natsOptions_SetDisconnectedCB(opts, _disconnectedCb, &arg) != NATS_OK) - || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK)) - { - FAIL("Unable to create reconnect options!"); - } - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - test("Setup: "); - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, &arg)); - IFOK(s, natsConnection_Subscribe(&sub2, nc, "foobar", _recvTestString, &arg)); - IFOK(s, natsConnection_PublishString(nc, "foo", arg.string)); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - _stopServer(serverPid); - serverPid = NATS_INVALID_PID; - - test("Disconnected CB invoked: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.disconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && arg.disconnected); - - test("Some protos while disconnected: "); - // Sub while disconnected - s = natsConnection_Subscribe(&sub3, nc, "bar", _recvTestString, &arg); - - // Unsubscribe foo and bar while disconnected - IFOK(s, natsSubscription_Unsubscribe(sub2)); - IFOK(s, natsConnection_PublishString(nc, "foo", arg.string)); - IFOK(s, natsConnection_PublishString(nc, "bar", arg.string)); - testCond(s == NATS_OK); - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - // Server is restarted, wait for reconnect - test("Check reconnected: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.reconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && arg.reconnected); - - test("Publish more: "); - s = natsConnection_PublishString(nc, "foobar", arg.string); - IFOK(s, natsConnection_PublishString(nc, "foo", arg.string)); - IFOK(s, natsConnection_Subscribe(&sub4, nc, "done", _doneCb, &arg)); - IFOK(s, natsConnection_PublishString(nc, "done", "done")); - testCond(s == NATS_OK); - - test("Done msg received: ") - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && arg.done); - - nats_Sleep(50); - - test("All msgs were received: "); - testCond(arg.sum == 4); - - natsSubscription_Destroy(sub); - natsSubscription_Destroy(sub2); - natsSubscription_Destroy(sub3); - natsSubscription_Destroy(sub4);; - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _waitForConnClosed(&arg); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_QueueSubsOnReconnect(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub1 = NULL; - natsSubscription *sub2 = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - arg.string = "bar"; - arg.status = NATS_OK; - arg.control= 6; - } - if (s == NATS_OK) - opts = _createReconnectOptions(); - - if ((opts == NULL) - || (natsOptions_SetReconnectedCB(opts, _reconnectedCb, &arg) != NATS_OK)) - { - FAIL("Unable to create reconnect options!"); - } - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_QueueSubscribe(&sub1, nc, "foo.bar", "workers", - _recvTestString, &arg)); - IFOK(s, natsConnection_QueueSubscribe(&sub2, nc, "foo.bar", "workers", - _recvTestString, &arg)); - IFOK(s, natsConnection_Flush(nc)); - - for (int i=0; (s == NATS_OK) && (i < 10); i++) - { - char seq[20]; - - snprintf(seq, sizeof(seq), "%d", i); - s = natsConnection_PublishString(nc, "foo.bar", seq); - } - - IFOK(s, natsConnection_Flush(nc)); - - nats_Sleep(50); - - natsMutex_Lock(arg.m); - for (int i=0; (s == NATS_OK) && (i<10); i++) - { - if (arg.results[i] != 1) - s = NATS_ERR; - } - IFOK(s, arg.status); - - memset(&arg.results, 0, sizeof(arg.results)); - natsMutex_Unlock(arg.m); - - test("Base results: "); - testCond(s == NATS_OK); - - _stopServer(serverPid); - serverPid = NATS_INVALID_PID; - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - test("Reconnects: ") - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.reconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && arg.reconnected); - - for (int i=0; (s == NATS_OK) && (i < 10); i++) - { - char seq[20]; - - snprintf(seq, sizeof(seq), "%d", i); - s = natsConnection_PublishString(nc, "foo.bar", seq); - } - - IFOK(s, natsConnection_Flush(nc)); - - nats_Sleep(50); - - natsMutex_Lock(arg.m); - for (int i=0; (s == NATS_OK) && (i<10); i++) - { - if (arg.results[i] != 1) - s = NATS_ERR; - } - IFOK(s, arg.status); - - memset(&arg.results, 0, sizeof(arg.results)); - natsMutex_Unlock(arg.m); - - test("Reconnect results: "); - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub1); - natsSubscription_Destroy(sub2); - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_IsClosed(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:22222"); - test("Check IsClosed is correct: "); - testCond((s == NATS_OK) && !natsConnection_IsClosed(nc)); - - _stopServer(serverPid); - serverPid = NATS_INVALID_PID; - - test("Check IsClosed after server shutdown: "); - testCond((s == NATS_OK) && !natsConnection_IsClosed(nc)); - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - test("Check IsClosed after server restart: "); - testCond((s == NATS_OK) && !natsConnection_IsClosed(nc)); - - natsConnection_Close(nc); - test("Check IsClosed after connection closed: "); - testCond((s == NATS_OK) && natsConnection_IsClosed(nc)); - - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_IsReconnectingAndStatus(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - test("Check reconnecting state: "); - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:22222")); - IFOK(s, natsOptions_SetAllowReconnect(opts, true)); - IFOK(s, natsOptions_SetMaxReconnect(opts, 10000)); - IFOK(s, natsOptions_SetReconnectWait(opts, 100)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); - IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg)); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg)); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)); - - // Connect, verify initial reconnecting state check, then stop the server - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond((s == NATS_OK) && !natsConnection_IsReconnecting(nc)); - - test("Check status: "); - testCond((s == NATS_OK) && (natsConnection_Status(nc) == NATS_CONN_STATUS_CONNECTED)); - - _stopServer(serverPid); - serverPid = NATS_INVALID_PID; - - // Wait until we get the disconnected callback - test("Check we are disconnected: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.disconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 1000); - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && arg.disconnected); - - test("Check IsReconnecting is correct: "); - testCond(natsConnection_IsReconnecting(nc)); - - test("Check Status is correct: "); - testCond(natsConnection_Status(nc) == NATS_CONN_STATUS_RECONNECTING); - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - // Wait until we get the reconnect callback - test("Check we are reconnected: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.reconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 1000); - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && arg.reconnected); - - test("Check IsReconnecting is correct: "); - testCond(!natsConnection_IsReconnecting(nc)); - - test("Check Status is correct: "); - testCond(natsConnection_Status(nc) == NATS_CONN_STATUS_CONNECTED); - - // Close the connection, reconnecting should still be false - natsConnection_Close(nc); - - test("Check IsReconnecting is correct: "); - testCond(!natsConnection_IsReconnecting(nc)); - - test("Check Status is correct: "); - testCond(natsConnection_Status(nc) == NATS_CONN_STATUS_CLOSED); - - natsMutex_Lock(arg.m); - while (!arg.closed) - natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_ReconnectBufSize(void) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - opts = _createReconnectOptions(); - if (opts == NULL) - s = NATS_ERR; - } - if (s == NATS_OK) - s = natsOptions_SetDisconnectedCB(opts, _disconnectedCb, &arg); - - if (s != NATS_OK) - FAIL("Unable to setup test"); - - test("Option invalid settings. NULL options: "); - s = natsOptions_SetReconnectBufSize(NULL, 1); - testCond(s != NATS_OK); - - test("Option invalid settings. Negative value: "); - s = natsOptions_SetReconnectBufSize(opts, -1); - testCond(s != NATS_OK); - - test("Option valid settings. Zero: "); - s = natsOptions_SetReconnectBufSize(opts, 0); - testCond(s == NATS_OK); - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - // For this test, set to a low value. - s = natsOptions_SetReconnectBufSize(opts, 32); - IFOK(s, natsConnection_Connect(&nc, opts)); - IFOK(s, natsConnection_Flush(nc)); - - _stopServer(serverPid); - - // Wait until we get the disconnected callback - test("Check we are disconnected: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.disconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 1000); - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && arg.disconnected); - - // Publish 2 messages, they should be accepted - test("Can publish while server is down: "); - s = natsConnection_PublishString(nc, "foo", "abcd"); - IFOK(s, natsConnection_PublishString(nc, "foo", "abcd")); - testCond(s == NATS_OK); - - // This publish should fail - test("Exhausted buffer should return an error: "); - s = natsConnection_PublishString(nc, "foo", "abcd"); - testCond(s == NATS_INSUFFICIENT_BUFFER); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); -} - -static void -_startServerForRetryOnConnect(void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - natsPid pid = NATS_INVALID_PID; - - // Delay start a bit... - nats_Sleep(300); - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - natsMutex_Lock(arg->m); - while (!arg->done) - natsCondition_Wait(arg->c, arg->m); - natsMutex_Unlock(arg->m); - - _stopServer(pid); -} - -static void -_connectedCb(natsConnection *nc, void* closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - arg->connected = true; - natsCondition_Broadcast(arg->c); - natsMutex_Unlock(arg->m); -} - -static int64_t -_testCustomReconnectDelayOnInitialConnect(natsConnection *nc, int attempts, void *closure) -{ - if (attempts == 10) - natsConnection_Close(nc); - return 50; -} - -static void -test_RetryOnFailedConnect(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - int64_t start = 0; - int64_t end = 0; - natsThread *t = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsMsg *rmsg = NULL; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetRetryOnFailedConnect(opts, true, NULL, NULL)); - IFOK(s, natsOptions_SetMaxReconnect(opts, 10)); - IFOK(s, natsOptions_SetReconnectWait(opts, 100)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); -#ifdef _WIN32 - // Windows takes the full timeout to report connect failure, so reduce - // timeout here. - IFOK(s, natsOptions_SetTimeout(opts, 100)); -#endif - if (s != NATS_OK) - { - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - FAIL("Unable to setup test"); - } - - start = nats_Now(); - test("Connect failed: "); - s = natsConnection_Connect(&nc, opts); - end = nats_Now(); - testCond(s == NATS_NO_SERVER); - nats_clearLastError(); - - test("Retried: ") -#ifdef _WIN32 - testCond((((end-start) >= 1000) && ((end-start) <= 2800))); -#else - testCond((((end-start) >= 300) && ((end-start) <= 1500))); -#endif - - test("Connects ok: "); - s = natsOptions_SetMaxReconnect(opts, 20); - IFOK(s, natsThread_Create(&t, _startServerForRetryOnConnect, (void*) &arg)); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - - // close to avoid reconnect when shutting down server. - natsConnection_Close(nc); - - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - natsThread_Join(t); - natsThread_Destroy(t); - t = NULL; - - natsConnection_Destroy(nc); - nc = NULL; - - // Try with async connect - test("Connect does not block: "); - s = natsOptions_SetRetryOnFailedConnect(opts, true, _connectedCb, (void*)&arg); - // Set disconnected/reconnected to make sure that these are not - // invoked as part of async connect. - IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg)); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg)); - IFOK(s, natsOptions_SetMaxReconnect(opts, -1)); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond((s == NATS_NOT_YET_CONNECTED) && (nc != NULL)); - nats_clearLastError(); - - // Request with a message with headers does timeout as opposed to returning - // the NATS_NO_SERVER_SUPPORT error. - test("Request with message with headers: "); - s = natsMsg_Create(&msg, "request.headers", NULL, "hello", 5); - IFOK(s, natsMsgHeader_Set(msg, "some", "header")); - IFOK(s, natsConnection_RequestMsg(&rmsg, nc, msg, 250)); - testCond(s == NATS_TIMEOUT); - nats_clearLastError(); - natsMsg_Destroy(msg); - - test("Subscription ok: "); - arg.control = 99; - s = natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*)&arg); - testCond(s == NATS_OK); - - test("Publish ok: "); - s = natsConnection_Publish(nc, "foo", (const void*) "hello", 5); - testCond(s == NATS_OK); - - // Start server - arg.done = false; - s = natsThread_Create(&t, _startServerForRetryOnConnect, (void*) &arg); - - test("Connected: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.connected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("No disconnected and reconnected callbacks: "); - natsMutex_Lock(arg.m); - s = ((arg.disconnected || arg.reconnected) ? NATS_ERR : NATS_OK); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Message received: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.msgReceived) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - // Close nc to avoid reconnect when shutting down server - natsConnection_Close(nc); - - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Broadcast(arg.c); - natsMutex_Unlock(arg.m); - - natsThread_Join(t); - natsThread_Destroy(t); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - nc = NULL; - - // Try the custom reconnect handler and close the connection after - // certain number of attempts. - test("Close in custom reconnect delay: ") - s = natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg); - IFOK(s, natsOptions_SetCustomReconnectDelay(opts, _testCustomReconnectDelayOnInitialConnect, NULL)); - IFOK(s, natsConnection_Connect(&nc, opts)); - if (s == NATS_NOT_YET_CONNECTED) - s = NATS_OK; - - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - nc = NULL; - natsOptions_Destroy(opts); - opts = NULL; - - _destroyDefaultThreadArgs(&arg); - - // Make sure that closing the connection while not connected returns fast - test("Create opts: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetRetryOnFailedConnect(opts, true, _connectedCb, NULL)); - IFOK(s, natsOptions_SetURL(opts, "nats://localhost:54321")); - testCond(s == NATS_OK); - - test("Start connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_NOT_YET_CONNECTED); - nats_clearLastError(); - s = NATS_OK; - - test("Close does not take too long: "); - start = nats_Now(); - natsConnection_Close(nc); - testCond(nats_Now()-start <1500); - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); -} - -static void -_closeConnWithDelay(void *arg) -{ - natsConnection *nc = (natsConnection*) arg; - - nats_Sleep(200); - natsConnection_Close(nc); -} - -static void -_connectToMockupServer(void *closure) -{ - struct threadArg *arg = (struct threadArg *) closure; - natsConnection *nc = NULL; - natsOptions *opts = arg->opts; - natsStatus s = NATS_OK; - int control; - - // Make sure that the server is ready to accept our connection. - nats_Sleep(100); - - if (opts == NULL) - { - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetAllowReconnect(opts, false)); - } - IFOK(s, natsConnection_Connect(&nc, opts)); - - natsOptions_Destroy(opts); - - natsMutex_Lock(arg->m); - control = arg->control; - natsMutex_Unlock(arg->m); - - if (control == 2) - { - int64_t payload = 0; - - if (s == NATS_OK) - { - test("Check expected max payload: ") - payload = natsConnection_GetMaxPayload(nc); - if (payload != 10) - s = NATS_ERR; - testCondNoReturn(s == NATS_OK); - } - if (s == NATS_OK) - { - test("Expect getting an error when publish more than max payload: "); - s = natsConnection_PublishString(nc, "hello", "Hello World!"); - testCondNoReturn(s != NATS_OK); - - // reset status - s = NATS_OK; - } - if (s == NATS_OK) - { - test("Expect success if publishing less than max payload: "); - s = natsConnection_PublishString(nc, "hello", "a"); - testCondNoReturn(s == NATS_OK); - } - - natsMutex_Lock(arg->m); - arg->closed = true; - natsCondition_Signal(arg->c); - natsMutex_Unlock(arg->m); - } - else if (control == 3) - { - natsThread *t = NULL; - - s = natsThread_Create(&t, _closeConnWithDelay, (void*) nc); - if (s == NATS_OK) - { - s = natsConnection_Flush(nc); - - natsThread_Join(t); - natsThread_Destroy(t); - } - } - else if (control == 4) - { - s = natsConnection_Flush(nc); - } - else if ((control == 5) || (control == 6)) - { - // Wait for disconnect Cb - natsMutex_Lock(arg->m); - while ((s != NATS_TIMEOUT) && !arg->disconnected) - s = natsCondition_TimedWait(arg->c, arg->m, 5000); - natsMutex_Unlock(arg->m); - - if ((s == NATS_OK) && (control == 5)) - { - // Should reconnect - natsMutex_Lock(arg->m); - while ((s != NATS_TIMEOUT) && !arg->reconnected) - s = natsCondition_TimedWait(arg->c, arg->m, 5000); - natsMutex_Unlock(arg->m); - - natsConnection_Close(nc); - } - else if (s == NATS_OK) - { - // Wait that we are closed, then check nc's last error. - natsMutex_Lock(arg->m); - while ((s != NATS_TIMEOUT) && !arg->closed) - s = natsCondition_TimedWait(arg->c, arg->m, 5000); - natsMutex_Unlock(arg->m); - if (s == NATS_OK) - { - const char* lastErr = NULL; - - s = natsConnection_GetLastError(nc, &lastErr); - if (strcmp(lastErr, arg->string) != 0) - s = NATS_ILLEGAL_STATE; - } - } - } - else if (control == 7) - { - natsMutex_Lock(arg->m); - while ((s != NATS_TIMEOUT) && !arg->done) - s = natsCondition_TimedWait(arg->c, arg->m, 5000); - natsMutex_Unlock(arg->m); - } - - natsConnection_Destroy(nc); - - natsMutex_Lock(arg->m); - arg->status = s; - natsCondition_Signal(arg->c); - natsMutex_Unlock(arg->m); -} - -static natsStatus -_startMockupServer(natsSock *serverSock, const char *host, const char *port) -{ - struct addrinfo hints; - struct addrinfo *servinfo = NULL; - int res; - natsStatus s = NATS_OK; - natsSock sock = NATS_SOCK_INVALID; - - memset(&hints,0,sizeof(hints)); - - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - hints.ai_flags = AI_PASSIVE; - - if ((res = getaddrinfo(host, port, &hints, &servinfo)) != 0) - { - hints.ai_family = AF_INET6; - - if ((res = getaddrinfo(host, port, &hints, &servinfo)) != 0) - s = NATS_SYS_ERROR; - } - if (s == NATS_OK) - { - sock = socket(servinfo->ai_family, servinfo->ai_socktype, - servinfo->ai_protocol); - if (sock == NATS_SOCK_INVALID) - s = NATS_SYS_ERROR; - - IFOK(s, natsSock_SetCommonTcpOptions(sock)); - IFOK(s, natsSock_SetBlocking(sock, true)); - } - if ((s == NATS_OK) - && (bind(sock, servinfo->ai_addr, (natsSockLen) servinfo->ai_addrlen) == NATS_SOCK_ERROR)) - { - s = NATS_SYS_ERROR; - } - - if ((s == NATS_OK) && (listen(sock, 100) == NATS_SOCK_ERROR)) - s = NATS_SYS_ERROR; - - if (s == NATS_OK) - *serverSock = sock; - else - natsSock_Close(sock); - - nats_FreeAddrInfo(servinfo); - - return s; -} - -static void -test_ErrOnConnectAndDeadlock(void) -{ - natsStatus s = NATS_OK; - natsSock sock = NATS_SOCK_INVALID; - natsThread *t = NULL; - struct threadArg arg; - natsSockCtx ctx; - - memset(&ctx, 0, sizeof(natsSockCtx)); - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("@@ Unable to setup test!"); - - arg.control = 1; - - test("Verify that bad INFO does not cause deadlock in client: "); - - // We will hand run a fake server that will timeout and not return a proper - // INFO proto. This is to test that we do not deadlock. - - s = _startMockupServer(&sock, "localhost", "4222"); - - // Start the thread that will try to connect to our server... - IFOK(s, natsThread_Create(&t, _connectToMockupServer, (void*) &arg)); - - if ((s == NATS_OK) - && (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID) - || (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK))) - { - s = NATS_SYS_ERROR; - } - - if (s == NATS_OK) - { - const char* badInfo = "INFOZ \r\n"; - - // Send back a mal-formed INFO. - s = natsSock_WriteFully(&ctx, badInfo, (int) strlen(badInfo)); - } - - if (s == NATS_OK) - { - natsMutex_Lock(arg.m); - - while ((s != NATS_TIMEOUT) && (arg.status == NATS_OK)) - s = natsCondition_TimedWait(arg.c, arg.m, 3000); - - natsMutex_Unlock(arg.m); - } - - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - - testCond((s == NATS_OK) && (arg.status != NATS_OK)); - - _destroyDefaultThreadArgs(&arg); - - natsSock_Close(ctx.fd); - natsSock_Close(sock); -} - -static void -test_ErrOnMaxPayloadLimit(void) -{ - natsStatus s = NATS_OK; - natsSock sock = NATS_SOCK_INVALID; - natsThread *t = NULL; - int expectedMaxPayLoad = 10; - struct threadArg arg; - natsSockCtx ctx; - - memset(&ctx, 0, sizeof(natsSockCtx)); - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("@@ Unable to setup test!"); - - arg.control = 2; - - s = _startMockupServer(&sock, "localhost", "4222"); - - if ((s == NATS_OK) && (listen(sock, 100) == NATS_SOCK_ERROR)) - s = NATS_SYS_ERROR; - - // Start the thread that will try to connect to our server... - IFOK(s, natsThread_Create(&t, _connectToMockupServer, (void*) &arg)); - - if ((s == NATS_OK) - && (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID) - || (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK))) - { - s = NATS_SYS_ERROR; - } - if (s == NATS_OK) - { - char info[1024]; - - snprintf(info, sizeof(info), - "INFO {\"server_id\":\"foobar\",\"version\":\"latest\",\"go\":\"latest\",\"host\":\"localhost\",\"port\":4222,\"auth_required\":false,\"tls_required\":false,\"max_payload\":%d}\r\n", - expectedMaxPayLoad); - - // Send INFO. - s = natsSock_WriteFully(&ctx, info, (int) strlen(info)); - if (s == NATS_OK) - { - char buffer[1024]; - - memset(buffer, 0, sizeof(buffer)); - - // Read connect and ping commands sent from the client - s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer)); - IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer))); - } - // Send PONG - IFOK(s, natsSock_WriteFully(&ctx, _PONG_PROTO_, _PONG_PROTO_LEN_)); - } - - // Wait for the client to be about to close the connection. - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 3000); - natsMutex_Unlock(arg.m); - - natsSock_Close(ctx.fd); - natsSock_Close(sock); - - // Wait for the client to finish. - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - - test("Test completed ok: "); - testCond(s == NATS_OK); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_Auth(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - natsOptions *opts = NULL; - - test("Server with auth on, client without should fail: "); - - serverPid = _startServer("nats://127.0.0.1:8232", "--user ivan --pass foo -p 8232", false); - CHECK_SERVER_STARTED(serverPid); - - nats_Sleep(1000); - - s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:8232"); - testCond((s == NATS_CONNECTION_AUTH_FAILED) - && (nats_strcasestr(nats_GetLastError(NULL), "Authorization Violation") != NULL)); - - test("Server with auth on, client with proper auth should succeed: "); - - s = natsConnection_ConnectTo(&nc, "nats://ivan:foo@127.0.0.1:8232"); - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - nc = NULL; - - // Use Options - test("Connect using SetUserInfo: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:8232")); - IFOK(s, natsOptions_SetUserInfo(opts, "ivan", "foo")); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - natsConnection_Destroy(nc); - nc = NULL; - - // Verify that credentials in URL take precedence. - test("URL takes precedence: "); - s = natsOptions_SetURL(opts, "nats://ivan:foo@127.0.0.1:8232"); - IFOK(s, natsOptions_SetUserInfo(opts, "foo", "bar")); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _stopServer(serverPid); -} - -static void -test_AuthFailNoDisconnectCB(void) -{ - natsStatus s; - natsOptions *opts = NULL; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - serverPid = _startServer("nats://127.0.0.1:8232", "--user ivan --pass foo -p 8232", true); - CHECK_SERVER_STARTED(serverPid); - - opts = _createReconnectOptions(); - if (opts == NULL) - FAIL("Unable to create options!"); - - test("Connect should fail: "); - s = natsOptions_SetDisconnectedCB(opts, _disconnectedCb, &arg); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s != NATS_OK); - - test("DisconnectCb should not be invoked on auth failure: "); - natsMutex_Lock(arg.m); - s = NATS_OK; - while ((s != NATS_TIMEOUT) && !arg.disconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 1000); - natsMutex_Unlock(arg.m); - testCond((s == NATS_TIMEOUT) && !arg.disconnected); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_AuthToken(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - natsOptions *opts = NULL; - - serverPid = _startServer("nats://testSecret@127.0.0.1:8232", "-auth testSecret -p 8232", true); - CHECK_SERVER_STARTED(serverPid); - - test("Server with token authorization, client without should fail: "); - s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:8232"); - testCond(s != NATS_OK); - - test("Server with token authorization, client with proper auth should succeed: "); - s = natsConnection_ConnectTo(&nc, "nats://testSecret@127.0.0.1:8232"); - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - nc = NULL; - - // Use Options - test("Connect using SetUserInfo: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:8232")); - IFOK(s, natsOptions_SetToken(opts, "testSecret")); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - natsConnection_Destroy(nc); - nc = NULL; - - // Verify that token in URL take precedence. - test("URL takes precedence: "); - s = natsOptions_SetURL(opts, "nats://testSecret@127.0.0.1:8232"); - IFOK(s, natsOptions_SetToken(opts, "badtoken")); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _stopServer(serverPid); -} - -static const char* -_tokenHandler(void* closure) -{ - return (char*) closure; -} - -static void -test_AuthTokenHandler(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - natsOptions *opts = NULL; - - serverPid = _startServer("nats://testSecret@127.0.0.1:8232", "-auth testSecret -p 8232", true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect using SetTokenHandler: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:8232")); - IFOK(s, natsOptions_SetTokenHandler(opts, _tokenHandler, (char*) "testSecret")); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - natsConnection_Destroy(nc); - nc = NULL; - - test("cannot set a tokenHandler when token set: "); - s = natsOptions_SetTokenHandler(opts, NULL, NULL); - IFOK(s, natsOptions_SetToken(opts, "token")); - IFOK(s, natsOptions_SetTokenHandler(opts, _tokenHandler, (char*) "testSecret")); - testCond(s == NATS_ILLEGAL_STATE); - - test("cannot set a token when tokenHandler set: "); - s = natsOptions_SetToken(opts, NULL); - IFOK(s, natsOptions_SetTokenHandler(opts, _tokenHandler, (char*) "testSecret")); - IFOK(s, natsOptions_SetToken(opts, "token")); - testCond(s == NATS_ILLEGAL_STATE); - - test("token in URL not valid with tokenHandler: "); - s = natsOptions_SetURL(opts, "nats://testSecret@127.0.0.1:8232"); - IFOK(s, natsOptions_SetTokenHandler(opts, _dummyTokenHandler, NULL)); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_ILLEGAL_STATE); - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _stopServer(serverPid); -} - -static void -_permsViolationHandler(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - const char *lastError = NULL; - - // Technically, there is no guarantee that the connection's last error - // be still the one that is given to this callback. - if (err == NATS_NOT_PERMITTED) - { - bool ok = true; - - // So consider ok if currently not the same or if the same, the - // error string is as expected. - if (natsConnection_GetLastError(nc, &lastError) == NATS_NOT_PERMITTED) - ok = (nats_strcasestr(lastError, args->string) != NULL ? true : false); - - if (ok) - { - natsMutex_Lock(args->m); - args->done = true; - natsCondition_Broadcast(args->c); - natsMutex_Unlock(args->m); - } - } -} - -static void -test_PermViolation(void) -{ - natsStatus s; - natsConnection *conn = NULL; - natsSubscription *sub = NULL; - natsOptions *opts = NULL; - natsPid pid = NATS_INVALID_PID; - int i; - bool cbCalled; - struct threadArg args; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s == NATS_OK) - { - args.string = PERMISSIONS_ERR; - s = natsOptions_Create(&opts); - } - IFOK(s, natsOptions_SetURL(opts, "nats://ivan:pwd@127.0.0.1:8232")); - IFOK(s, natsOptions_SetErrorHandler(opts, _permsViolationHandler, &args)); - if (s != NATS_OK) - FAIL("Error setting up test"); - - pid = _startServer("nats://127.0.0.1:8232", "-c permissions.conf -a 127.0.0.1 -p 8232", false); - CHECK_SERVER_STARTED(pid); - s = _checkStart("nats://ivan:pwd@127.0.0.1:8232", 4, 10); - if (s != NATS_OK) - { - _stopServer(pid); - FAIL("Error starting server!"); - } - test("Check connection created: "); - s = natsConnection_Connect(&conn, opts); - testCond(s == NATS_OK); - - for (i=0; i<2; i++) - { - cbCalled = false; - - test("Should get perm violation: "); - if (i == 0) - s = natsConnection_PublishString(conn, "bar", "fail"); - else - s = natsConnection_Subscribe(&sub, conn, "foo", _dummyMsgHandler, NULL); - - if (s == NATS_OK) - { - natsMutex_Lock(args.m); - while (!args.done && s == NATS_OK) - s = natsCondition_TimedWait(args.c, args.m, 2000); - cbCalled = args.done; - args.done = false; - natsMutex_Unlock(args.m); - } - testCond((s == NATS_OK) && cbCalled); - } - - test("Connection not closed: "); - testCond((s == NATS_OK) && !natsConnection_IsClosed(conn)); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&args); - - _stopServer(pid); -} - -static void -_authViolationHandler(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - const char *lastError = NULL; - - // Technically, there is no guarantee that the connection's last error - // be still the one that is given to this callback. - if (err == NATS_CONNECTION_AUTH_FAILED) - { - bool ok = true; - - // So consider ok if currently not the same or if the same, the - // error string is as expected. - if (natsConnection_GetLastError(nc, &lastError) == NATS_CONNECTION_AUTH_FAILED) - ok = (nats_strcasestr(nc->errStr, AUTHORIZATION_ERR) != NULL ? true : false); - if (ok) - { - natsMutex_Lock(args->m); - args->results[0]++; - args->done = true; - natsCondition_Broadcast(args->c); - natsMutex_Unlock(args->m); - } - } -} - -static void -test_AuthViolation(void) -{ - natsStatus s = NATS_OK; - natsSock sock = NATS_SOCK_INVALID; - natsThread *t = NULL; - struct threadArg arg; - natsSockCtx ctx; - - memset(&ctx, 0, sizeof(natsSockCtx)); - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&(arg.opts))); - IFOK(s, natsOptions_SetAllowReconnect(arg.opts, false)); - IFOK(s, natsOptions_SetErrorHandler(arg.opts, _authViolationHandler, &arg)); - IFOK(s, natsOptions_SetClosedCB(arg.opts, _closedCb, &arg)); - if (s != NATS_OK) - FAIL("@@ Unable to setup test!"); - - arg.control = 7; - arg.string = AUTHORIZATION_ERR; - - test("Behavior of connection on Server Error: ") - - s = _startMockupServer(&sock, "localhost", "4222"); - - // Start the thread that will try to connect to our server... - IFOK(s, natsThread_Create(&t, _connectToMockupServer, (void*) &arg)); - - if ((s == NATS_OK) - && (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID) - || (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK))) - { - s = NATS_SYS_ERROR; - } - if (s == NATS_OK) - { - char info[1024]; - - strncpy(info, - "INFO {\"server_id\":\"foobar\",\"version\":\"latest\",\"go\":\"latest\",\"host\":\"localhost\",\"port\":4222,\"auth_required\":false,\"tls_required\":false,\"max_payload\":1048576}\r\n", - sizeof(info)); - - // Send INFO. - s = natsSock_WriteFully(&ctx, info, (int) strlen(info)); - if (s == NATS_OK) - { - char buffer[1024]; - - memset(buffer, 0, sizeof(buffer)); - - // Read connect and ping commands sent from the client - s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer)); - IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer))); - } - // Send PONG - IFOK(s, natsSock_WriteFully(&ctx, - _PONG_PROTO_, _PONG_PROTO_LEN_)); - - if (s == NATS_OK) - { - // Wait a tiny, and simulate an error sent by the server - nats_Sleep(50); - - snprintf(info, sizeof(info), "-ERR '%s'\r\n", arg.string); - s = natsSock_WriteFully(&ctx, info, (int)strlen(info)); - } - } - if (s == NATS_OK) - { - // Wait for the client to process the async err - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - natsMutex_Unlock(arg.m); - - natsSock_Close(ctx.fd); - } - - natsSock_Close(sock); - - // Wait for the client to finish. - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - - // Wait for closed CB - if (s == NATS_OK) - { - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - if ((s == NATS_OK) && arg.reconnects != 0) - s = NATS_ERR; - natsMutex_Unlock(arg.m); - } - testCond(s == NATS_OK); - - _destroyDefaultThreadArgs(&arg); -} - -static void -_startServerSendErrThread(void *closure) -{ - natsStatus s = NATS_OK; - natsSock sock = NATS_SOCK_INVALID; - struct threadArg *arg = (struct threadArg*) closure; - natsSockCtx ctx; - char buffer[1024]; - int connect = 0; - - memset(&ctx, 0, sizeof(natsSockCtx)); - - _startMockupServer(&sock, "localhost", "4222"); - - for (connect=1; connect<4; connect++) - { - if (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID) - || (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK)) - { - s = NATS_SYS_ERROR; - } - if (s == NATS_OK) - { - const char info[] = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n"; - - natsMutex_Lock(arg->m); - arg->control++; - natsMutex_Unlock(arg->m); - - // Send INFO. - s = natsSock_WriteFully(&ctx, info, (int) strlen(info)); - if (s == NATS_OK) - { - memset(buffer, 0, sizeof(buffer)); - - // Read connect and ping commands sent from the client - s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer)); - IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer))); - } - } - if ((s == NATS_OK) && connect == 1) - { - // Send PONG - s = natsSock_WriteFully(&ctx, _PONG_PROTO_, _PONG_PROTO_LEN_); - nats_Sleep(500); - snprintf(buffer, sizeof(buffer), "-ERR '%s'\r\n", AUTHENTICATION_EXPIRED_ERR); - } - else if (s == NATS_OK) - { - snprintf(buffer, sizeof(buffer), "-ERR '%s'\r\n", AUTHORIZATION_ERR); - } - if (s == NATS_OK) - { - s = natsSock_WriteFully(&ctx, buffer, (int)strlen(buffer)); - nats_Sleep(200); - } - natsSock_Close(ctx.fd); - } - - natsMutex_Lock(arg->m); - while ((s != NATS_TIMEOUT) && !arg->disconnected) - s = natsCondition_TimedWait(arg->c, arg->m, 5000); - natsMutex_Unlock(arg->m); - - natsSock_Close(sock); -} - -static void -_authExpiredHandler(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - const char *lastError = NULL; - - // Technically, there is no guarantee that the connection's last error - // be still the one that is given to this callback. - if (err == NATS_CONNECTION_AUTH_FAILED) - { - bool ok = true; - - natsMutex_Lock(args->m); - - // So consider ok if currently not the same or if the same, the - // error string is as expected. - if (natsConnection_GetLastError(nc, &lastError) == NATS_CONNECTION_AUTH_FAILED) - { - if (args->control == 1) - ok = (nats_strcasestr(nc->errStr, AUTHENTICATION_EXPIRED_ERR) != NULL ? true : false); - else - ok = (nats_strcasestr(nc->errStr, AUTHORIZATION_ERR) != NULL ? true : false); - } - if (ok) - { - args->results[0]++; - args->done = true; - natsCondition_Broadcast(args->c); - } - - natsMutex_Unlock(args->m); - } -} - -static void -test_AuthenticationExpired(void) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - natsOptions *opts= NULL; - natsThread *t = NULL; - struct threadArg arg; - natsSockCtx ctx; - int i; - - memset(&ctx, 0, sizeof(natsSockCtx)); - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetMaxReconnect(opts, -1)); - IFOK(s, natsOptions_SetReconnectWait(opts, 25)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); - IFOK(s, natsOptions_SetErrorHandler(opts, _authExpiredHandler, &arg)); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, &arg)); - if (s != NATS_OK) - FAIL("@@ Unable to setup test!"); - - s = natsThread_Create(&t, _startServerSendErrThread, (void*) &arg); - if (s != NATS_OK) - { - _destroyDefaultThreadArgs(&arg); - FAIL("Unable to setup test"); - } - - test("Should connect ok: "); - for (i=0; i<10; i++) - { - s = natsConnection_Connect(&nc, opts); - if (s == NATS_OK) - break; - nats_Sleep(100); - } - testCond(s == NATS_OK); - - test("Should have been closed: "); - s = _waitForConnClosed(&arg); - testCond(s == NATS_OK); - - // First we would have gotten the async authentication expired error, - // then 2 consecutive auth violations that causes the lib to stop - // trying to reconnect. - test("Should have posted 3 errors: "); - if (s == NATS_OK) - { - natsMutex_Lock(arg.m); - s = (((arg.results[0] == 3) && arg.done) ? NATS_OK : NATS_ERR); - natsMutex_Unlock(arg.m); - } - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - nc = NULL; - - natsMutex_Lock(arg.m); - arg.disconnected = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - natsThread_Join(t); - natsThread_Destroy(t); - - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&arg); -} - -static void -_startServerSendErr2Thread(void *closure) -{ - natsStatus s = NATS_OK; - natsSock sock = NATS_SOCK_INVALID; - struct threadArg *arg = (struct threadArg*) closure; - natsSockCtx ctx; - char buffer[1024]; - int connect = 0; - bool done = false; - - memset(&ctx, 0, sizeof(natsSockCtx)); - - _startMockupServer(&sock, "localhost", "4222"); - - for (connect=1; !done; connect++) - { - if (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID) - || (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK)) - { - s = NATS_SYS_ERROR; - } - if (s == NATS_OK) - { - const char info[] = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n"; - - natsMutex_Lock(arg->m); - arg->control++; - natsMutex_Unlock(arg->m); - - // Send INFO. - s = natsSock_WriteFully(&ctx, info, (int) strlen(info)); - if (s == NATS_OK) - { - memset(buffer, 0, sizeof(buffer)); - - // Read connect and ping commands sent from the client - s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer)); - IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer))); - } - if (s == NATS_OK) - { - // Send PONG - s = natsSock_WriteFully(&ctx, _PONG_PROTO_, _PONG_PROTO_LEN_); - } - } - if ((s == NATS_OK) && connect == 1) - { - nats_Sleep(500); - snprintf(buffer, sizeof(buffer), "-ERR '%s'\r\n", AUTHENTICATION_EXPIRED_ERR); - s = natsSock_WriteFully(&ctx, buffer, (int)strlen(buffer)); - nats_Sleep(200); - } - else if (s == NATS_OK) - { - natsMutex_Lock(arg->m); - while ((s != NATS_TIMEOUT) && !arg->disconnected) - s = natsCondition_TimedWait(arg->c, arg->m, 5000); - natsMutex_Unlock(arg->m); - done = true; - } - natsSock_Close(ctx.fd); - } - natsSock_Close(sock); -} - -static void -test_AuthenticationExpiredReconnect(void) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - natsOptions *opts= NULL; - natsThread *t = NULL; - struct threadArg arg; - natsSockCtx ctx; - int i; - const char *lastErr = NULL; - - memset(&ctx, 0, sizeof(natsSockCtx)); - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetMaxReconnect(opts, 2)); - IFOK(s, natsOptions_SetReconnectWait(opts, 25)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); - IFOK(s, natsOptions_SetErrorHandler(opts, _authExpiredHandler, &arg)); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &arg)); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, &arg)); - if (s != NATS_OK) - FAIL("@@ Unable to setup test!"); - - s = natsThread_Create(&t, _startServerSendErr2Thread, (void*) &arg); - if (s != NATS_OK) - { - _destroyDefaultThreadArgs(&arg); - FAIL("Unable to setup test"); - } - - test("Should connect ok: "); - for (i=0; i<10; i++) - { - s = natsConnection_Connect(&nc, opts); - if (s == NATS_OK) - break; - nats_Sleep(100); - } - testCond(s == NATS_OK); - - test("Should have posted 1 error: "); - if (s == NATS_OK) - { - // Waiting for the err handler to fire - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done && arg.results[0] != 1) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - natsMutex_Unlock(arg.m); - } - testCond(s == NATS_OK); - - test("Should have reconnected: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.reconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - // Wait a tiny bit and make sure connection is still connected - nats_Sleep(100); - test("Still connected: "); - testCond(!natsConnection_IsClosed(nc)); - - // Check last error is cleared - test("Check last error cleared: "); - s = natsConnection_GetLastError(nc, &lastErr); - testCond((s == NATS_OK) && (lastErr[0] == '\0')); - - test("Close: "); - natsConnection_Destroy(nc); - s = _waitForConnClosed(&arg); - testCond(s == NATS_OK); - nc = NULL; - - natsMutex_Lock(arg.m); - arg.disconnected = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - natsThread_Join(t); - natsThread_Destroy(t); - - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_ConnectedServer(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - char buffer[128]; - - buffer[0] = '\0'; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Verify ConnectedUrl is correct: ") - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer))); - testCond((s == NATS_OK) - && (buffer[0] != '\0') - && (strcmp(buffer, NATS_DEFAULT_URL) == 0)); - - buffer[0] = '\0'; - - test("Verify ConnectedServerId is not null: ") - s = natsConnection_GetConnectedServerId(nc, buffer, sizeof(buffer)); - testCond((s == NATS_OK) && (buffer[0] != '\0')); - - buffer[0] = '\0'; - - test("Verify ConnectedUrl is empty after disconnect: ") - natsConnection_Close(nc); - s = natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer)); - testCond((s == NATS_OK) && (buffer[0] == '\0')); - - buffer[0] = '\0'; - - test("Verify ConnectedServerId is empty after disconnect: ") - s = natsConnection_GetConnectedServerId(nc, buffer, sizeof(buffer)); - testCond((s == NATS_OK) && (buffer[0] == '\0')); - - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_MultipleClose(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsThread *threads[10]; - natsPid serverPid = NATS_INVALID_PID; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Test that multiple Close are fine: ") - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - for (int i=0; (s == NATS_OK) && (i<10); i++) - s = natsThread_Create(&(threads[i]), _closeConn, (void*) nc); - - for (int i=0; (s == NATS_OK) && (i<10); i++) - { - natsThread_Join(threads[i]); - natsThread_Destroy(threads[i]); - } - testCond((s == NATS_OK) - && (nc->status == NATS_CONN_STATUS_CLOSED) - && (nc->refs == 1)); - - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_SimplePublish(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Test simple publish: ") - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_PublishString(nc, "foo", "Hello world!")); - IFOK(s, natsConnection_Publish(nc, "foo", (const void*) "Hello world!", - (int) strlen("Hello world!"))); - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_SimplePublishNoData(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Test simple publish with no data: ") - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_PublishString(nc, "foo", NULL)); - IFOK(s, natsConnection_PublishString(nc, "foo", "")); - IFOK(s, natsConnection_Publish(nc, "foo", NULL, 0)); - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_PublishMsg(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - arg.string = "hello!"; - arg.status = NATS_OK; - } - if ( s != NATS_OK) - FAIL("Unable to setup test!"); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Test simple publishMsg: ") - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, &arg)); - IFOK(s, natsConnection_Flush(nc)); - if (s == NATS_OK) - { - const char data[] = {104, 101, 108, 108, 111, 33}; - natsMsg *msg = NULL; - - s = natsMsg_Create(&msg, "foo", NULL, data, sizeof(data)); - IFOK(s, natsConnection_PublishMsg(nc, msg)); - - natsMsg_Destroy(msg); - } - IFOK(s, natsConnection_Flush(nc)); - - if (s == NATS_OK) - { - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.msgReceived) - s = natsCondition_TimedWait(arg.c, arg.m, 1500); - natsMutex_Unlock(arg.m); - } - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _stopServer(serverPid); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_InvalidSubsArgs(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsPid serverPid = NATS_INVALID_PID; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - // ASYNC Subscription - - test("Test async subscriber, invalid connection: ") - s = natsConnection_Subscribe(&sub, NULL, "foo", _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async subscriber, invalid subject: ") - s = natsConnection_Subscribe(&sub, nc, NULL, _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async subscriber, invalid subject (empty string): ") - s = natsConnection_Subscribe(&sub, nc, "", _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async subscriber, invalid cb: ") - s = natsConnection_Subscribe(&sub, nc, "foo", NULL, NULL); - testCond(s != NATS_OK); - - test("Test async queue subscriber, invalid connection: ") - s = natsConnection_QueueSubscribe(&sub, NULL, "foo", "group", _recvTestString, NULL); - testCond(s != NATS_OK); - - // Async Subscription Timeout - - test("Test async subscriber timeout, invalid connection: ") - s = natsConnection_SubscribeTimeout(&sub, NULL, "foo", 100, _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async subscriber timeout, invalid subject: ") - s = natsConnection_SubscribeTimeout(&sub, nc, NULL, 100, _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async subscriber timeout, invalid subject (empty string): ") - s = natsConnection_SubscribeTimeout(&sub, nc, "", 100, _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async subscriber timeout, invalid cb: ") - s = natsConnection_SubscribeTimeout(&sub, nc, "foo", 100, NULL, NULL); - testCond(s != NATS_OK); - - test("Test async subscriber timeout, invalid timeout (<0): ") - s = natsConnection_SubscribeTimeout(&sub, nc, "foo", -100, _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async subscriber timeout, invalid timeout (0): ") - s = natsConnection_SubscribeTimeout(&sub, nc, "foo", 0, _recvTestString, NULL); - testCond(s != NATS_OK); - - // ASYNC Queue Subscription - - test("Test async queue subscriber timeout, invalid connection: ") - s = natsConnection_QueueSubscribe(&sub, NULL, "foo", "group", _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async queue subscriber, invalid subject: ") - s = natsConnection_QueueSubscribe(&sub, nc, NULL, "group", _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async queue subscriber, invalid subject (empty string): ") - s = natsConnection_QueueSubscribe(&sub, nc, "", "group", _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async queue subscriber, invalid group name: ") - s = natsConnection_QueueSubscribe(&sub, nc, "foo", NULL, _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async queue subscriber, invalid group name (empty): ") - s = natsConnection_QueueSubscribe(&sub, nc, "foo", "", _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async queue subscriber, invalid cb: ") - s = natsConnection_QueueSubscribe(&sub, nc, "foo", "group", NULL, NULL); - testCond(s != NATS_OK); - - // ASYNC Queue Subscription Timeout - - test("Test async queue subscriber timeout, invalid connection: ") - s = natsConnection_QueueSubscribeTimeout(&sub, NULL, "foo", "group", 100, _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async queue subscriber timeout, invalid subject: ") - s = natsConnection_QueueSubscribeTimeout(&sub, nc, NULL, "group", 100, _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async queue subscriber timeout, invalid subject (empty string): ") - s = natsConnection_QueueSubscribeTimeout(&sub, nc, "", "group", 100, _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async queue subscriber timeout, invalid group name: ") - s = natsConnection_QueueSubscribeTimeout(&sub, nc, "foo", NULL, 100, _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async queue subscriber timeout, invalid group name (empty): ") - s = natsConnection_QueueSubscribeTimeout(&sub, nc, "foo", "", 100, _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async queue subscriber timeout, invalid cb: ") - s = natsConnection_QueueSubscribeTimeout(&sub, nc, "foo", "group", 100, NULL, NULL); - testCond(s != NATS_OK); - - test("Test async queue subscriber timeout, invalid timeout (<0): ") - s = natsConnection_QueueSubscribeTimeout(&sub, nc, "foo", "group", -100, _recvTestString, NULL); - testCond(s != NATS_OK); - - test("Test async queue subscriber timeout, invalid timeout (0): ") - s = natsConnection_QueueSubscribeTimeout(&sub, nc, "foo", "group", 0, _recvTestString, NULL); - testCond(s != NATS_OK); - - // SYNC Subscription - - test("Test sync subscriber, invalid connection: ") - s = natsConnection_SubscribeSync(&sub, NULL, "foo"); - testCond(s != NATS_OK); - - test("Test sync subscriber, invalid subject: ") - s = natsConnection_SubscribeSync(&sub, nc, NULL); - testCond(s != NATS_OK); - - test("Test sync subscriber, invalid subject (empty string): ") - s = natsConnection_SubscribeSync(&sub, nc, ""); - testCond(s != NATS_OK); - - // SYNC Queue Subscription - - test("Test sync queue subscriber, invalid connection: ") - s = natsConnection_QueueSubscribeSync(&sub, NULL, "foo", "group"); - testCond(s != NATS_OK); - - test("Test sync queue subscriber, invalid subject: ") - s = natsConnection_QueueSubscribeSync(&sub, nc, NULL, "group"); - testCond(s != NATS_OK); - - test("Test sync queue subscriber, invalid subject (empty string): ") - s = natsConnection_QueueSubscribeSync(&sub, nc, "", "group"); - testCond(s != NATS_OK); - - test("Test sync queue subscriber, invalid group name: ") - s = natsConnection_QueueSubscribeSync(&sub, nc, "foo", NULL); - testCond(s != NATS_OK); - - test("Test sync queue subscriber, invalid group name (empty): ") - s = natsConnection_QueueSubscribeSync(&sub, nc, "foo", ""); - testCond(s != NATS_OK); - - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_AsyncSubscribe(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if ( s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.string = "Hello World"; - arg.status = NATS_OK; - arg.control= 1; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Test async subscriber: ") - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, - (void*) &arg)); - IFOK(s, natsConnection_PublishString(nc, "foo", arg.string)); - - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.msgReceived) - s = natsCondition_TimedWait(arg.c, arg.m, 1500); - natsMutex_Unlock(arg.m); - - IFOK(s, arg.status); - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -typedef struct __asyncTimeoutInfo -{ - struct threadArg *arg; - int64_t timeout; - int64_t timeAfterFirstMsg; - int64_t timeSecondMsg; - int64_t timeFirstTimeout; - int64_t timeSecondTimeout; - -} _asyncTimeoutInfo; - -static void -_asyncTimeoutCb(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - _asyncTimeoutInfo *ai = (_asyncTimeoutInfo*) closure; - - natsMutex_Lock(ai->arg->m); - if (msg != NULL) - { - ai->arg->sum++; - switch (ai->arg->sum) - { - case 1: - { - // Release lock for sleep... - natsMutex_Unlock(ai->arg->m); - - // Sleep for 1.5x the timeout value - nats_Sleep(ai->timeout+ai->timeout/2); - - natsMutex_Lock(ai->arg->m); - ai->timeAfterFirstMsg = nats_Now(); - break; - } - case 2: ai->timeSecondMsg = nats_Now(); break; - case 3: - { - ai->arg->done = true; - natsSubscription_Destroy(sub); - natsCondition_Signal(ai->arg->c); - break; - } - default: - { - ai->arg->status = NATS_ERR; - break; - } - } - natsMsg_Destroy(msg); - } - else - { - ai->arg->timerFired++; - switch (ai->arg->timerFired) - { - case 1: - { - ai->timeFirstTimeout = nats_Now(); - // Notify the main thread to send the second message - // after waiting 1/2 of the timeout period. - natsCondition_Signal(ai->arg->c); - break; - } - case 2: - { - ai->timeSecondTimeout = nats_Now(); - // Signal that we timed-out for the 2nd time. - ai->arg->timerStopped = 1; - natsCondition_Signal(ai->arg->c); - break; - } - default: - { - ai->arg->status = NATS_ERR; - break; - } - } - } - natsMutex_Unlock(ai->arg->m); -} - -static void -test_AsyncSubscribeTimeout(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsPid serverPid = NATS_INVALID_PID; - natsOptions *opts = NULL; - struct threadArg arg; - bool useLibDlv = false; - int i; - char testText[128]; - int64_t timeout = 100; - _asyncTimeoutInfo ai; - - for (i=0; i<4; i++) - { - memset(&ai, 0, sizeof(_asyncTimeoutInfo)); - memset(&arg, 0, sizeof(struct threadArg)); - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_UseGlobalMessageDelivery(opts, useLibDlv)); - IFOK(s, _createDefaultThreadArgsForCbTests(&arg)); - if ( s != NATS_OK) - FAIL("Unable to setup test!"); - - ai.arg = &arg; - ai.timeout = timeout; - arg.status = NATS_OK; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - snprintf(testText, sizeof(testText), "Test async %ssubscriber timeout%s: ", - ((i == 1 || i == 3) ? "queue " : ""), - (i > 1 ? " (lib msg delivery)" : "")); - test(testText); - s = natsConnection_Connect(&nc, opts); - if (s == NATS_OK) - { - if (i == 0 || i == 2) - s = natsConnection_SubscribeTimeout(&sub, nc, "foo", timeout, - _asyncTimeoutCb, (void*) &ai); - else - s = natsConnection_QueueSubscribeTimeout(&sub, nc, "foo", "group", - timeout, _asyncTimeoutCb, (void*) &ai); - } - IFOK(s, natsConnection_PublishString(nc, "foo", "msg1")); - - // Wait to be notified that sub timed-out 2 times - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.timerFired != 1)) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - natsMutex_Unlock(arg.m); - - // Wait half the timeout - nats_Sleep(timeout/2); - - // Send the second message. This should reset the timeout timer. - IFOK(s, natsConnection_PublishString(nc, "foo", "msg2")); - IFOK(s, natsConnection_Flush(nc)); - - // Wait for 2nd timeout - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.timerStopped == 0)) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - natsMutex_Unlock(arg.m); - - // Send two more messages, only one should be received since the - // subscription will be unsubscribed/closed when receiving the - // first. - IFOK(s, natsConnection_PublishString(nc, "foo", "msg3")); - IFOK(s, natsConnection_PublishString(nc, "foo", "msg4")); - IFOK(s, natsConnection_Flush(nc)); - - // Wait for end of test - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - natsMutex_Unlock(arg.m); - - // Wait more than the timeout time to see if extra timeout callbacks - // incorrectly fire - nats_Sleep(timeout+timeout/2); - - // Check for success - natsMutex_Lock(arg.m); - testCond((s == NATS_OK) && (arg.status == NATS_OK) - && (arg.sum == 3) && (arg.timerFired == 2) - && (ai.timeFirstTimeout >= ai.timeAfterFirstMsg + timeout - 50) - && (ai.timeFirstTimeout <= ai.timeAfterFirstMsg + timeout + 50) - && (ai.timeSecondTimeout >= ai.timeSecondMsg + timeout - 50) - && (ai.timeSecondTimeout <= ai.timeSecondMsg + timeout + 50)) - natsMutex_Unlock(arg.m); - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); - - if (i >= 1) - useLibDlv = true; - } -} - -static void -test_SyncSubscribe(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsPid serverPid = NATS_INVALID_PID; - const char *string = "Hello World"; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Test sync subscriber: ") - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo")); - IFOK(s, natsConnection_PublishString(nc, "foo", string)); - IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000)); - testCond((s == NATS_OK) - && (msg != NULL) - && (strncmp(string, natsMsg_GetData(msg), natsMsg_GetDataLength(msg)) == 0)); - - natsMsg_Destroy(msg); - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_PubSubWithReply(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsPid serverPid = NATS_INVALID_PID; - const char *string = "Hello World"; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Test PubSub with reply: ") - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo")); - IFOK(s, natsConnection_PublishRequestString(nc, "foo", "bar", string)); - IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000)); - testCond((s == NATS_OK) - && (msg != NULL) - && (strncmp(string, natsMsg_GetData(msg), natsMsg_GetDataLength(msg)) == 0) - && (strncmp(natsMsg_GetReply(msg), "bar", 3) == 0)); - - natsMsg_Destroy(msg); - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_NoResponders(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - const char *string = "Hello World"; - struct threadArg arg; - - if (!serverVersionAtLeast(2,2,0)) - { - char txt[200]; - - snprintf(txt, sizeof(txt), "Skipping since requires server version of at least 2.2.0, got %s: ", serverVersion); - test(txt); - testCond(true); - return; - } - - s = _createDefaultThreadArgsForCbTests(&arg); - if ( s != NATS_OK) - FAIL("Unable to setup test!"); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("No responders on NextMsg: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo")); - IFOK(s, natsConnection_PublishRequestString(nc, "bar", "foo", string)); - IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000)); - testCond(NATS_NO_RESPONDERS); - - natsMsg_Destroy(msg); - natsSubscription_Destroy(sub); - sub = NULL; - - arg.status = NATS_ERR; - arg.control = 10; - - test("No responders in callback: "); - s = natsConnection_Subscribe(&sub, nc, "bar", _recvTestString, (void*)&arg); - IFOK(s, natsConnection_PublishRequestString(nc, "foo", "bar", string)); - if (s == NATS_OK) - { - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.msgReceived) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - if (s == NATS_OK) - s = arg.status; - natsMutex_Unlock(arg.m); - } - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - sub = NULL; - natsConnection_Destroy(nc); - nc = NULL; - - test("Disable no responders: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_DisableNoResponders(opts, true)); - IFOK(s, natsConnection_Connect(&nc, opts)); - IFOK(s, natsConnection_RequestString(&msg, nc, "foo", string, 500)); - testCond((s == NATS_TIMEOUT) && (msg == NULL)); - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -struct flushArg -{ - natsConnection *nc; - natsStatus s; - int count; - int64_t timeout; - int64_t initialSleep; - int64_t loopSleep; -}; - -static void -_doFlush(void *arg) -{ - struct flushArg *p = (struct flushArg*) arg; - int i; - - nats_Sleep(p->initialSleep); - - for (i = 0; (p->s == NATS_OK) && (i < p->count); i++) - { - p->s = natsConnection_FlushTimeout(p->nc, p->timeout); - if ((p->s == NATS_OK) && (p->loopSleep > 0)) - nats_Sleep(p->loopSleep); - } -} - -static void -test_Flush(void) -{ - natsStatus s; - natsOptions *opts = NULL; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - const char *string = "Hello World"; - natsThread *threads[3] = { NULL, NULL, NULL }; - struct flushArg args[3]; - int64_t start = 0; - int64_t elapsed = 0; - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222")); - IFOK(s, natsOptions_SetReconnectWait(opts, 100)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); - IFOK(s, natsOptions_SetPingInterval(opts, 100)); - - if (s != NATS_OK) - FAIL("Unable to setup test"); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Test Flush empties buffer: ") - s = natsConnection_Connect(&nc, opts); - for (int i=0; (s == NATS_OK) && (i < 1000); i++) - s = natsConnection_PublishString(nc, "flush", string); - IFOK(s, natsConnection_Flush(nc)); - testCond((s == NATS_OK) - && natsConnection_Buffered(nc) == 0); - - test("Check parallel Flush: ") - for (int i=0; (s == NATS_OK) && (i < 3); i++) - { - args[i].nc = nc; - args[i].s = NATS_OK; - args[i].timeout = 5000; -#ifdef _WIN32 - args[i].count = 100; -#else - args[i].count = 1000; -#endif - args[i].initialSleep = 500; - args[i].loopSleep = 1; - s = natsThread_Create(&(threads[i]), _doFlush, (void*) &(args[i])); - } - - for (int i=0; (s == NATS_OK) && (i < 10000); i++) - s = natsConnection_PublishString(nc, "flush", "Hello world"); - - for (int i=0; (i < 3); i++) - { - if (threads[i] == NULL) - continue; - - natsThread_Join(threads[i]); - natsThread_Destroy(threads[i]); - - if (args[i].s != NATS_OK) - s = args[i].s; - } - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - nc = NULL; - - test("Check Flush while in doReconnect: ") - s = natsOptions_SetReconnectWait(opts, 3000); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); - IFOK(s, natsConnection_Connect(&nc, opts)); - if (s == NATS_OK) - { - // Capture the moment we connected - start = nats_Now(); - - // Stop the server - _stopServer(serverPid); - serverPid = NATS_INVALID_PID; - - // We can restart right away, since the client library will wait 3 sec - // before reconnecting. - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - // Attempt to Flush. This should wait for the reconnect to occur, then - // proceed. - for (int i=0; (s == NATS_OK) && (i < 3); i++) - { - args[i].nc = nc; - args[i].s = NATS_OK; - args[i].timeout = 5000; - args[i].count = 1; - args[i].initialSleep = 1000; - args[i].loopSleep = 0; - s = natsThread_Create(&(threads[i]), _doFlush, (void*) &(args[i])); - } - } - for (int i=0; (i < 3); i++) - { - if (threads[i] == NULL) - continue; - - natsThread_Join(threads[i]); - natsThread_Destroy(threads[i]); - - if ((s == NATS_OK) && (args[i].s != NATS_OK)) - { - s = args[i].s; - printf("t=%d s=%u\n", i, s); - } - } - if (s == NATS_OK) - elapsed = (nats_Now() - start); - - testCond((s == NATS_OK) && (elapsed >= 2500) && (elapsed <= 3200)); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_ConnCloseDoesFlush(void) -{ - natsStatus s = NATS_OK; - natsPid pid = NATS_INVALID_PID; - natsConnection *nc1 = NULL; - natsConnection *nc2 = NULL; - natsSubscription *sub = NULL; - int tc = 100000; - int i, iter; - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - if (valgrind) - tc = 1000; - - test("Connection close flushes: "); - for (iter=0; (s == NATS_OK) && (iter<10); iter++) - { - s = natsConnection_ConnectTo(&nc1, NATS_DEFAULT_URL); - IFOK(s, natsConnection_SubscribeSync(&sub, nc1, "foo")); - IFOK(s, natsSubscription_SetPendingLimits(sub, -1, -1)); - IFOK(s, natsConnection_Flush(nc1)); - - IFOK(s, natsConnection_ConnectTo(&nc2, NATS_DEFAULT_URL)); - - for (i=0; (s == NATS_OK) && (imsgList.msgs == 0); - natsSub_Unlock(sub); - - natsSubscription_Destroy(sub); - sub = NULL; - natsConnection_Destroy(nc); - nc = NULL; - - // Repeat with global message delivery option. - test("Set global delivery option: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_UseGlobalMessageDelivery(opts, true)); - testCond(s == NATS_OK); - - test("Connect and create sub: "); - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _dummyMsgHandler, NULL)); - testCond(s == NATS_OK); - - natsSub_Lock(sub); - natsMutex_Lock(sub->libDlvWorker->lock); - test("Send message: "); - s = natsConnection_PublishString(nc, "foo", "hello"); - testCond(s == NATS_OK); - - test("Close sub: "); - natsMutex_Unlock(sub->libDlvWorker->lock); - natsSub_Unlock(sub); - natsSub_close(sub, false); - testCond(s == NATS_OK); - - test("Check msg not given: "); - natsSub_Lock(sub); - natsMutex_Lock(sub->libDlvWorker->lock); - testCond(sub->msgList.msgs == 0); - natsMutex_Unlock(sub->libDlvWorker->lock); - natsSub_Unlock(sub); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _stopServer(serverPid); -} - -static void -test_RequestTimeout(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsMsg *msg = NULL; - natsPid serverPid = NATS_INVALID_PID; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Test Request should timeout: ") - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_RequestString(&msg, nc, "foo", "bar", 500)); - testCond(serverVersionAtLeast(2, 2, 0) ? (s == NATS_NO_RESPONDERS) : (s == NATS_TIMEOUT)); - - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_Request(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsMsg *req = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - int i; - - s = _createDefaultThreadArgsForCbTests(&arg); - if ( s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.string = "I will help you"; - arg.status = NATS_OK; - arg.control= 4; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect and subscribe: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg)); - testCond(s == NATS_OK); - - test("Test Request: ") - s = natsConnection_RequestString(&msg, nc, "foo", "help", 500); - - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.msgReceived) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - IFOK(s, arg.status); - natsMutex_Unlock(arg.m); - - testCond((s == NATS_OK) - && (msg != NULL) - && (strncmp(arg.string, - natsMsg_GetData(msg), - natsMsg_GetDataLength(msg)) == 0)); - - natsMsg_Destroy(msg); - msg = NULL; - - test("Create req message: "); - s = natsMsg_Create(&req, "foo", NULL, "help", 4); - testCond(s == NATS_OK); - - test("Test RequestMsg: "); - s = natsConnection_RequestMsg(&msg, nc, req, 500); - - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.msgReceived) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - IFOK(s, arg.status); - natsMutex_Unlock(arg.m); - - testCond((s == NATS_OK) - && (msg != NULL) - && (strncmp(arg.string, - natsMsg_GetData(msg), - natsMsg_GetDataLength(msg)) == 0)); - - natsMsg_Destroy(msg); - msg = NULL; - natsMsg_Destroy(req); - req = NULL; - - natsMutex_Lock(arg.m); - arg.control = 11; - natsMutex_Unlock(arg.m); - - test("Race on timeout: "); - for (i=0; (s == NATS_OK) && (i<100); i++) - { - s = natsConnection_Request(&msg, nc, "foo", "help!", 5, 1); - // Make sure that we get either OK with msg != NULL - // or TIMEOUT but with msg == NULL - if (s == NATS_OK) - { - if (msg == NULL) - s = NATS_ERR; - else - { - natsMsg_Destroy(msg); - msg = NULL; - } - } - else if ((s == NATS_TIMEOUT) && (msg == NULL)) - { - s = NATS_OK; - nats_clearLastError(); - } - // else if timeout and msg != NULL, that is a bug! - } - testCond(s == NATS_OK); - - // Ensure the last callback returns to avoid accessing data that has been freed. - natsMutex_Lock(arg.m); - s = NATS_OK; - while ((s != NATS_TIMEOUT) && (arg.sum != 100)) - natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_RequestNoBody(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if ( s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.string = "I will help you"; - arg.status = NATS_OK; - arg.control= 4; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect and subscribe: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg)); - testCond(s == NATS_OK); - - test("Test Request with no body content: ") - s = natsConnection_RequestString(&msg, nc, "foo", NULL, 500); - - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) - && !arg.msgReceived) - { - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - } - natsMutex_Unlock(arg.m); - - IFOK(s, arg.status); - - testCond((s == NATS_OK) - && (msg != NULL) - && (strncmp(arg.string, - natsMsg_GetData(msg), - natsMsg_GetDataLength(msg)) == 0)); - - natsMsg_Destroy(msg); - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -_serverForMuxWithMappedSubject(void *closure) -{ - natsStatus s = NATS_OK; - natsSock sock = NATS_SOCK_INVALID; - struct threadArg *arg = (struct threadArg*) closure; - natsSockCtx ctx; - - memset(&ctx, 0, sizeof(natsSockCtx)); - - s = _startMockupServer(&sock, "127.0.0.1", "4222"); - natsMutex_Lock(arg->m); - arg->status = s; - natsCondition_Signal(arg->c); - natsMutex_Unlock(arg->m); - - if (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID) - || (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK)) - { - s = NATS_SYS_ERROR; - } - if (s == NATS_OK) - { - char info[1024]; - - snprintf(info, sizeof(info), "%s", "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n"); - - // Send INFO. - s = natsSock_WriteFully(&ctx, info, (int) strlen(info)); - } - if (s == NATS_OK) - { - char buffer[1024]; - - memset(buffer, 0, sizeof(buffer)); - - // Read connect and ping commands sent from the client - s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer)); - IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer))); - - // Send PONG - IFOK(s, natsSock_WriteFully(&ctx, _PONG_PROTO_, _PONG_PROTO_LEN_)); - - // Now wait for the SUB proto and the Request - IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer))); - IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer))); - - // Send the reply on a different subject - IFOK(s, natsSock_WriteFully(&ctx, "MSG bar 1 2\r\nok\r\n", 17)); - if (s == NATS_OK) - { - // Wait for client to tell us it is done - natsMutex_Lock(arg->m); - while ((s != NATS_TIMEOUT) && !(arg->done)) - s = natsCondition_TimedWait(arg->c, arg->m, 10000); - natsMutex_Unlock(arg->m); - } - natsSock_Close(ctx.fd); - } - natsSock_Close(sock); -} - -static void -test_RequestMuxWithMappedSubject(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsMsg *msg = NULL; - natsThread *t = NULL; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if ( s != NATS_OK) - FAIL("Unable to setup test!"); - - test("Start server: "); - arg.status = NATS_ERR; - s = natsThread_Create(&t, _serverForMuxWithMappedSubject, (void*) &arg); - if (s == NATS_OK) - { - // Wait for server to be ready - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - s = arg.status; - natsMutex_Unlock(arg.m); - } - testCond(s == NATS_OK); - - test("Connect: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - testCond(s == NATS_OK); - - test("Request: "); - s = natsConnection_RequestString(&msg, nc, "foo", "help", 1000); - testCond(s == NATS_OK); - - natsMsg_Destroy(msg); - natsConnection_Destroy(nc); - - // Notify mock server we are done - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - natsThread_Join(t); - natsThread_Destroy(t); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_OldRequest(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if ( s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.string = "I will help you"; - arg.status = NATS_OK; - arg.control= 4; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Setup: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_UseOldRequestStyle(opts, true)); - IFOK(s, natsConnection_Connect(&nc, opts)); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg)); - testCond(s == NATS_OK); - - test("Test Old Request Style: ") - s = natsConnection_RequestString(&msg, nc, "foo", "help", 500); - - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) - && !arg.msgReceived) - { - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - } - natsMutex_Unlock(arg.m); - - IFOK(s, arg.status); - - testCond((s == NATS_OK) - && (msg != NULL) - && (strncmp(arg.string, - natsMsg_GetData(msg), - natsMsg_GetDataLength(msg)) == 0)); - - natsMsg_Destroy(msg); - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -_sendRequest(void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - natsStatus s; - natsMsg *msg = NULL; - - nats_Sleep(250); - - s = natsConnection_RequestString(&msg, arg->nc, "foo", "Help!", 2000); - natsMutex_Lock(arg->m); - if ((s == NATS_OK) - && (msg != NULL) - && strncmp(arg->string, - natsMsg_GetData(msg), - natsMsg_GetDataLength(msg)) == 0) - { - arg->sum++; - } - else - { - arg->status = NATS_ERR; - } - natsMutex_Unlock(arg->m); - natsMsg_Destroy(msg); -} - -static void -test_SimultaneousRequest(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsThread *threads[10]; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - int i; - - s = _createDefaultThreadArgsForCbTests(&arg); - if ( s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.string = "ok"; - arg.status = NATS_OK; - arg.control= 4; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - if (s == NATS_OK) - { - arg.nc = nc; - s = natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg); - } - - for (i=0; i<10; i++) - threads[i] = NULL; - - test("Test simultaneous requests: ") - for (i=0; (s == NATS_OK) && (i<10); i++) - s = natsThread_Create(&(threads[i]), _sendRequest, (void*) &arg); - - for (i=0; i<10; i++) - { - if (threads[i] != NULL) - { - natsThread_Join(threads[i]); - natsThread_Destroy(threads[i]); - } - } - - natsMutex_Lock(arg.m); - if ((s != NATS_OK) - || ((s = arg.status) != NATS_OK) - || (arg.sum != 10)) - { - s = NATS_ERR; - } - natsMutex_Unlock(arg.m); - - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_RequestClose(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsThread *t = NULL; - natsPid serverPid = NATS_INVALID_PID; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - // Because of no responders, we would get an immediate timeout. - // So we need to create a sync subscriber that is simply not - // going to send a reply back. - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - test("Test Request is kicked out with a connection close: ") - IFOK(s, natsThread_Create(&t, _closeConnWithDelay, (void*) nc)); - IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo")); - IFOK(s, natsConnection_RequestString(&msg, nc, "foo", "help", 2000)); - - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - testCond((s == NATS_CONNECTION_CLOSED) && (msg == NULL)); - - natsMsg_Destroy(msg); - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - - -static void -test_CustomInbox(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsSubscription *sub1 = NULL; - natsSubscription *sub2 = NULL; - natsMsg *msg = NULL; - natsPid serverPid = NATS_INVALID_PID; - const char *badPfx[] = {"bad prefix", "bad\tprefix", "bad..prefix", "bad.*.prefix", - "bad.>.prefix", "bad.prefix.*", "bad.prefix.>", - "bad.prefix.", "bad.prefix.."}; - struct threadArg arg; - int i; - int mode; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Create options: "); - s = natsOptions_Create(&opts); - testCond(s == NATS_OK); - - for (i = 0; i<(int)(sizeof(badPfx)/sizeof(char*)); i++) - { - char tmp[128]; - - snprintf(tmp, sizeof(tmp), "Bad prefix '%s': ", badPfx[i]); - test(tmp); - s = natsOptions_SetCustomInboxPrefix(opts, badPfx[i]); - testCond((s == NATS_INVALID_ARG) && (opts->inboxPfx == NULL)); - nats_clearLastError(); - } - - test("Good prefix: "); - s = natsOptions_SetCustomInboxPrefix(opts, "my.prefix"); - testCond((s == NATS_OK) && (opts->inboxPfx != NULL) - && (strcmp(opts->inboxPfx, "my.prefix.") == 0)); - - arg.string = "I will help you"; - arg.control= 4; - - for (mode=0; mode<2; mode++) - { - test("Set old request style: "); - s = natsOptions_UseOldRequestStyle(opts, true); - testCond(s == NATS_OK); - - test("Connect and setup sub: "); - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_SubscribeSync(&sub1, nc, "my.prefix.>")); - IFOK(s, natsConnection_Subscribe(&sub2, nc, "foo", _recvTestString, (void*) &arg)); - testCond(s == NATS_OK); - - test("Send request: "); - s = natsConnection_RequestString(&msg, nc, "foo", "help", 1000); - testCond((s == NATS_OK) && (msg != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Check custom inbox: "); - s = natsSubscription_NextMsg(&msg, sub1, 500); - testCond((s == NATS_OK) && (msg != NULL) - && (strstr(natsMsg_GetSubject(msg), "my.prefix.") == natsMsg_GetSubject(msg))); - natsMsg_Destroy(msg); - msg = NULL; - - natsSubscription_Destroy(sub1); - sub1 = NULL; - natsSubscription_Destroy(sub2); - sub2 = NULL; - natsConnection_Destroy(nc); - nc = NULL; - } - - for (mode = 0; mode < 2; mode++) - { - test("Set option: "); - s = natsOptions_SetCustomInboxPrefix(opts, (mode == 0 ? NULL : "my.prefix")); - testCond(s == NATS_OK); - - test("Connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - test("Inbox init: "); - { - char inboxBuf[NATS_DEFAULT_INBOX_PRE_LEN+NUID_BUFFER_LEN+1]; - char *inbox = NULL; - bool allocated = false; - - s = natsConn_initInbox(nc, inboxBuf, sizeof(inboxBuf), &inbox, &allocated); - if (s == NATS_OK) - { - if (mode == 0) - { - if (allocated || (inbox != inboxBuf) || - (strstr(inbox, NATS_DEFAULT_INBOX_PRE) != inbox)) - { - s = NATS_ERR; - } - } - else - { - // Since the custom prefix "my.prefix." is more than "_INBOX.", - // init should have allocated memory - if (!allocated || (inbox == inboxBuf) || - (strstr(inbox, "my.prefix.") != inbox)) - { - s = NATS_ERR; - } - else if (allocated) - free(inbox); - } - } - } - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - nc = NULL; - } - - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - _stopServer(serverPid); -} - -static void -test_MessageBufferPadding(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsPid serverPid = NATS_INVALID_PID; - const char *string = "Hello World"; - const char *servers[] = { "nats://127.0.0.1:4222" }; - int serversCount = 1; - int paddingSize = 32; - bool paddingIsZeros = true; - - serverPid = _startServer(servers[0], NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Create options: "); - s = natsOptions_Create(&opts); - testCond(s == NATS_OK); - - test("Setting message buffer padding: "); - s = natsOptions_SetMessageBufferPadding(opts, paddingSize); - testCond(s == NATS_OK); - - test("Setting servers: "); - s = natsOptions_SetServers(opts, servers, serversCount); - testCond(s == NATS_OK); - - test("Test generating message for subscriber: ") - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo")); - IFOK(s, natsConnection_PublishString(nc, "foo", string)); - IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000)); - testCond((s == NATS_OK) - && (msg != NULL) - && (strncmp(string, natsMsg_GetData(msg), natsMsg_GetDataLength(msg)) == 0)); - - test("Test access to memory in message buffer beyond data length: "); - // This test can pass even if padding doesn't work as excepted. - // But valgrind will show access to unallocated memory - for (int i=natsMsg_GetDataLength(msg); i", "foo.bar.>"}; - const char *badWCs[] = {">.foo", "foo.>.bar", ">.>"}; - const char *badQueues[]= {"queue name", "queue.name ", " queue.name", - "queue\tname", "\tqueue.name", "\t.queue.name", "queue.name\t", "queue.name.\t", - "queue\rname", "\rqueue.name", "\r.queue.name", "queue.name\r", "queue.name.\r", - "queue\nname", "\nqueue.name", "\n.queue.name", "queue.name\n", "queue.name.\n"}; - char buf[256]; - int i; - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Connect ok: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - testCond(s == NATS_OK); - - for (i=0; i<(int) (sizeof(badSubjs)/sizeof(char*)); i++) - { - snprintf(buf, sizeof(buf), "test subject '%s': ", badSubjs[i]); - test(buf) - s = (nats_IsSubjectValid(badSubjs[i], true) ? NATS_ERR : NATS_OK); - testCond(s == NATS_OK); - } - - for (i=0; i<(int) (sizeof(goodSubjs)/sizeof(char*)); i++) - { - snprintf(buf, sizeof(buf), "test subject '%s': ", goodSubjs[i]); - test(buf) - s = (nats_IsSubjectValid(goodSubjs[i], true) ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - } - - for (i=0; i<(int) (sizeof(badQueues)/sizeof(char*)); i++) - { - snprintf(buf, sizeof(buf), "test queue '%s': ", badQueues[i]); - test(buf) - s = natsConnection_QueueSubscribeSync(&sub, nc, "foo", badQueues[i]); - testCond((s == NATS_INVALID_QUEUE_NAME) && (sub == NULL)); - nats_clearLastError(); - } - - for (i=0; i<(int) (sizeof(wcSubjs)/sizeof(char*)); i++) - { - snprintf(buf, sizeof(buf), "test wildcard ok '%s': ", wcSubjs[i]); - test(buf) - s = (nats_IsSubjectValid(wcSubjs[i], true) ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - } - - for (i=0; i<(int) (sizeof(wcSubjs)/sizeof(char*)); i++) - { - snprintf(buf, sizeof(buf), "test no wildcard allowed '%s': ", wcSubjs[i]); - test(buf) - s = (nats_IsSubjectValid(wcSubjs[i], false) ? NATS_ERR : NATS_OK); - testCond(s == NATS_OK); - } - - for (i=0; i<(int) (sizeof(badWCs)/sizeof(char*)); i++) - { - snprintf(buf, sizeof(buf), "bad wildcard '%s': ", badWCs[i]); - test(buf) - s = (nats_IsSubjectValid(badWCs[i], true) ? NATS_ERR : NATS_OK); - testCond(s == NATS_OK); - } - - natsConnection_Destroy(nc); - - _stopServer(pid); -} - -static void -_subComplete(void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - arg->done = true; - natsCondition_Signal(arg->c); - natsMutex_Unlock(arg->m); -} - -static void -test_ClientAsyncAutoUnsub(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - int checks; - int64_t nd = 0; - - s = _createDefaultThreadArgsForCbTests(&arg); - if ( s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.status = NATS_OK; - arg.control= 9; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg)); - IFOK(s, natsSubscription_AutoUnsubscribe(sub, 10)); - - for (int i=0; (s == NATS_OK) && (i<100); i++) - s = natsConnection_PublishString(nc, "foo", "hello"); - - IFOK(s, natsConnection_Flush(nc)); - - // Wait for the subscription to become invalid - checks = 0; - while (natsSubscription_IsValid(sub) - && (checks++ < 10)) - { - nats_Sleep(100); - } - test("IsValid should be false: "); - testCond((sub != NULL) && !natsSubscription_IsValid(sub)); - - test("Received no more than max: "); - testCond(arg.sum == 10); - - natsSubscription_Destroy(sub); - sub = NULL; - - test("Subscribe and publish 100 msgs: "); - s = natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg); - IFOK(s, natsSubscription_SetOnCompleteCB(sub, _subComplete, (void*) &arg)); - for (int i=0; (s == NATS_OK) && (i<100); i++) - s = natsConnection_PublishString(nc, "foo", "hello"); - IFOK(s, natsConnection_Flush(nc)); - checks = 0; - while ((natsSubscription_GetDelivered(sub, &nd) == NATS_OK) - && (checks++ < 10)) - { - nats_Sleep(100); - } - testCond((s == NATS_OK) && (nd == 100)); - - test("Auto-unsub with 10, should close the sub right away: "); - s = natsSubscription_AutoUnsubscribe(sub, 10); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 1000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_ClientSyncAutoUnsub(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsPid serverPid = NATS_INVALID_PID; - int received = 0; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo")); - IFOK(s, natsSubscription_AutoUnsubscribe(sub, 10)); - - for (int i=0; (s == NATS_OK) && (i<100); i++) - s = natsConnection_PublishString(nc, "foo", "hello"); - - IFOK(s, natsConnection_Flush(nc)); - - test("Get correct error: "); - for (int i=0; (s == NATS_OK) && (i<100); i++) - { - s = natsSubscription_NextMsg(&msg, sub, 10); - if (s == NATS_OK) - { - received++; - natsMsg_Destroy(msg); - } - } - testCond(s == NATS_MAX_DELIVERED_MSGS); - - test("Received no more than max: "); - testCond(received == 10); - - test("IsValid should be false: "); - testCond((sub != NULL) && !natsSubscription_IsValid(sub)); - - natsSubscription_Destroy(sub); - sub = NULL; - - test("Subscribe and publish 100 msgs: "); - s = natsConnection_SubscribeSync(&sub, nc, "foo"); - for (int i=0; (s == NATS_OK) && (i<100); i++) - s = natsConnection_PublishString(nc, "foo", "hello"); - IFOK(s, natsConnection_Flush(nc)); - for (int i=0; (s == NATS_OK) && (i<100); i++) - { - s = natsSubscription_NextMsg(&msg, sub, 1000); - received++; - natsMsg_Destroy(msg); - } - testCond(s == NATS_OK); - - test("Auto-unsub with 10, should close the sub right away: "); - s = natsSubscription_AutoUnsubscribe(sub, 10); - testCond(!natsSubscription_IsValid(sub)); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_ClientAutoUnsubAndReconnect(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - opts = _createReconnectOptions(); - if ((opts == NULL) - || ((s = _createDefaultThreadArgsForCbTests(&arg)) != NATS_OK)) - { - FAIL("Unable to setup test!"); - } - - arg.status = NATS_OK; - arg.control= 9; - - s = natsOptions_SetReconnectedCB(opts, _reconnectedCb, &arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg)); - IFOK(s, natsSubscription_AutoUnsubscribe(sub, 10)); - - // Send less than the max - for (int i=0; (s == NATS_OK) && (i<5); i++) - s = natsConnection_PublishString(nc, "foo", "hello"); - IFOK(s, natsConnection_Flush(nc)); - - // Restart the server - _stopServer(serverPid); - serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true); - CHECK_SERVER_STARTED(serverPid); - - // Wait for reconnect - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.reconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - natsMutex_Unlock(arg.m); - - // Now send more than the max - for (int i=0; (s == NATS_OK) && (i<50); i++) - s = natsConnection_PublishString(nc, "foo", "hello"); - IFOK(s, natsConnection_Flush(nc)); - - nats_Sleep(10); - - test("Received no more than max: "); - testCond((s == NATS_OK) && (arg.sum == 10)); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -_autoUnsub(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - - natsMsg_Destroy(msg); - natsSubscription_Destroy(sub); - natsMutex_Lock(args->m); - args->done = true; - natsCondition_Signal(args->c); - natsMutex_Unlock(args->m); -} - - -static void -test_AutoUnsubNoUnsubOnDestroy(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsPid serverPid = NATS_INVALID_PID; - natsBuffer *buf = NULL; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if ( s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.status = NATS_OK; - arg.control= 9; - - serverPid = _startServer("nats://127.0.0.1:4222", "-DV", true); - CHECK_SERVER_STARTED(serverPid); - - test("Auto-unsub: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _autoUnsub, (void*) &arg)); - IFOK(s, natsSubscription_AutoUnsubscribe(sub, 1)); - IFOK(s, natsConnection_PublishString(nc, "foo", "hello")); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); - - test("Read logfile: "); - s = nats_ReadFile(&buf, 1024, LOGFILE_NAME); - testCond(s == NATS_OK); - - test("Check UNSUB not sent: "); - s = (strstr(natsBuf_Data(buf), "[UNSUB 1 ]") == NULL ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - - natsBuf_Destroy(buf); -} - -static void -test_NextMsgOnClosedSub(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsPid serverPid = NATS_INVALID_PID; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Setup: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo")); - IFOK(s, natsSubscription_Unsubscribe(sub)); - testCond(s == NATS_OK); - - test("Get correct error: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond(s == NATS_INVALID_SUBSCRIPTION); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -_nextMsgKickedOut(void *closure) -{ - natsSubscription *sub = (natsSubscription*) closure; - natsMsg *msg = NULL; - - (void) natsSubscription_NextMsg(&msg, sub, 10000); -} - -static void -test_CloseSubRelease(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsThread *t = NULL; - natsThread *subs[3]; - natsPid serverPid = NATS_INVALID_PID; - int64_t start, end; - int i; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo")); - - for (i=0; i<3; i++) - s = natsThread_Create(&(subs[i]), _nextMsgKickedOut, (void*) sub); - - start = nats_Now(); - - IFOK(s, natsThread_Create(&t, _closeConnWithDelay, (void*) nc)); - - for (i=0; i<3; i++) - { - if (subs[i] != NULL) - { - natsThread_Join(subs[i]); - natsThread_Destroy(subs[i]); - } - } - - end = nats_Now(); - - test("Test that NexMsg was kicked out properly: "); - testCond((s != NATS_TIMEOUT) - && ((end - start) <= 1000)); - - natsThread_Join(t); - natsThread_Destroy(t); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_IsValidSubscriber(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsPid serverPid = NATS_INVALID_PID; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo")); - - test("Sub is valid: "); - testCond((s == NATS_OK) && natsSubscription_IsValid(sub)); - - test("Publish some msgs: "); - for (int i=0; (s == NATS_OK) && (i<10); i++) - s = natsConnection_PublishString(nc, "foo", "hello"); - - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Received msg ok: ") - s = natsSubscription_NextMsg(&msg, sub, 200); - testCond((s == NATS_OK) && (msg != NULL)); - - natsMsg_Destroy(msg); - - test("Unsub: "); - s = natsSubscription_Unsubscribe(sub); - testCond(s == NATS_OK); - - test("Received msg should fail after unsubscribe: ") - s = natsSubscription_NextMsg(&msg, sub, 200); - testCond(s != NATS_OK); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_SlowSubscriber(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsOptions *opts = NULL; - natsMsg *msg = NULL; - natsPid serverPid = NATS_INVALID_PID; - int total = 100; - int64_t start, end; - - s = natsOptions_Create(&opts); - if (s == NATS_OK) - s = natsOptions_SetMaxPendingMsgs(opts, total); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Setup: "); - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo")); - - for (int i=0; - (s == NATS_OK) && (i < total + 100); i++) - { - s = natsConnection_PublishString(nc, "foo", "hello"); - } - testCond(s == NATS_OK); - - test("Check flush returns before timeout: "); - start = nats_Now(); - - (void) natsConnection_FlushTimeout(nc, 5000); - - end = nats_Now(); - - testCond((end - start) < 5000); - - test("NextMsg should report error: "); - // Make sure NextMsg returns an error to indicate slow consumer - s = natsSubscription_NextMsg(&msg, sub, 200); - testCond(s != NATS_OK); - - natsMsg_Destroy(msg); - natsSubscription_Destroy(sub); - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_SlowAsyncSubscriber(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsOptions *opts = NULL; - const char *lastErr = NULL; - natsPid serverPid = NATS_INVALID_PID; - int total = 100; - int64_t start, end; - struct threadArg arg; - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetMaxPendingMsgs(opts, total)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - s = _createDefaultThreadArgsForCbTests(&arg); - if ( s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.status = NATS_OK; - arg.control= 7; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg)); - - for (int i=0; - (s == NATS_OK) && (i < (total + 100)); i++) - { - s = natsConnection_PublishString(nc, "foo", "hello"); - } - - test("Check Publish does not fail due to SlowConsumer: "); - testCond(s == NATS_OK); - - test("Check flush returns before timeout: "); - start = nats_Now(); - - s = natsConnection_FlushTimeout(nc, 5000); - - end = nats_Now(); - - testCond((end - start) < 5000); - - test("Flush should not report an error: "); - testCond(s == NATS_OK); - - // Make sure the callback blocks before checking for slow consumer - natsMutex_Lock(arg.m); - - while ((s != NATS_TIMEOUT) && !(arg.msgReceived)) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - - natsMutex_Unlock(arg.m); - - test("Last Error should be SlowConsumer: "); - testCond((s == NATS_OK) - && natsConnection_GetLastError(nc, &lastErr) == NATS_SLOW_CONSUMER); - - // Release the sub - natsMutex_Lock(arg.m); - - // Unblock the wait - arg.closed = true; - - // And destroy the subscription here so that the next msg callback - // is not invoked. - natsSubscription_Destroy(sub); - - natsCondition_Signal(arg.c); - arg.msgReceived = false; - natsMutex_Unlock(arg.m); - - // Let the callback finish - natsMutex_Lock(arg.m); - while (!arg.msgReceived) - natsCondition_TimedWait(arg.c, arg.m, 5000); - natsMutex_Unlock(arg.m); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - if (valgrind) - nats_Sleep(900); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -_slowConsErrCB(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - if (err == NATS_SLOW_CONSUMER) - { - arg->sum++; - natsCondition_Signal(arg->c); - } - natsMutex_Unlock(arg->m); -} - -static void -test_SlowConsumerCB(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsOptions *opts = NULL; - natsPid pid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetMaxPendingMsgs(opts, 1)); - IFOK(s, natsOptions_SetErrorHandler(opts, _slowConsErrCB, (void*) &arg)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - test("Create sub: ") - IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo")) - testCond(s == NATS_OK); - - test("Publish 2 messages: "); - IFOK(s, natsConnection_PublishString(nc, "foo", "msg1")); - IFOK(s, natsConnection_PublishString(nc, "foo", "msg2")); - testCond(s == NATS_OK); - - test("Error handler invoked: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.sum != 1)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Produce 1 message: "); - IFOK(s, natsConnection_PublishString(nc, "foo", "msg3")); - testCond(s == NATS_OK); - - test("Check handler is not invoked: "); - nats_Sleep(50); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.sum != 1)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - natsSubscription_Destroy(sub); - natsOptions_Destroy(opts); - _stopServer(pid); - _destroyDefaultThreadArgs(&arg); -} - -static void -test_PendingLimitsDeliveredAndDropped(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - const char *lastErr = NULL; - natsPid serverPid = NATS_INVALID_PID; - int total = 100; - int sent = total + 20; - int msgsLimit = 0; - int bytesLimit= 0; - int msgs = 0; - int bytes = 0; - int64_t dropped = 0; - int64_t delivered = 0; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.status = NATS_OK; - arg.control= 7; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg)); - - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - test("Settings, invalid args, NULL sub: "); - s = natsSubscription_SetPendingLimits(NULL, 1, 1); - testCond(s != NATS_OK); - - test("Settings, invalid args, zero msgs: "); - s = natsSubscription_SetPendingLimits(sub, 0, 1); - testCond(s != NATS_OK); - - test("Settings, invalid args, zero bytes: "); - s = natsSubscription_SetPendingLimits(sub, 1, 0); - testCond(s != NATS_OK); - - test("Check pending limits, NULL sub: "); - s = natsSubscription_GetPendingLimits(NULL, &msgsLimit, &bytesLimit); - testCond(s != NATS_OK); - - test("Check pending limits, other params NULL are OK: "); - s = natsSubscription_GetPendingLimits(sub, NULL, NULL); - testCond(s == NATS_OK); - - test("Check pending limits, msgsLimit NULL is OK: "); - s = natsSubscription_GetPendingLimits(sub, NULL, &bytesLimit); - testCond((s == NATS_OK) && (bytesLimit == NATS_OPTS_DEFAULT_MAX_PENDING_MSGS * 1024)); - - test("Check pending limits, msgsLibytesLimitmit NULL is OK: "); - s = natsSubscription_GetPendingLimits(sub, &msgsLimit, NULL); - testCond((s == NATS_OK) && (msgsLimit == NATS_OPTS_DEFAULT_MAX_PENDING_MSGS)); - - test("Set negative value for msgs OK: "); - s = natsSubscription_SetPendingLimits(sub, -1, 100); - testCond(s == NATS_OK); - - test("Set negative value for bytes OK: "); - s = natsSubscription_SetPendingLimits(sub, 100, -1); - testCond(s == NATS_OK); - - test("Set negative values OK: "); - s = natsSubscription_SetPendingLimits(sub, -10, -10); - testCond(s == NATS_OK); - - test("Get pending with negative values returned OK: "); - s = natsSubscription_GetPendingLimits(sub, &msgsLimit, &bytesLimit); - testCond((s == NATS_OK) && (msgsLimit == -10) && (bytesLimit == -10)); - - msgsLimit = 0; - bytesLimit = 0; - - test("Set valid values: "); - s = natsSubscription_SetPendingLimits(sub, total, total * 1024); - testCond(s == NATS_OK); - - test("Check pending limits: "); - s = natsSubscription_GetPendingLimits(sub, &msgsLimit, &bytesLimit); - testCond((s == NATS_OK) && (msgsLimit == total) && (bytesLimit == total * 1024)); - - for (int i=0; - (s == NATS_OK) && (i < sent); i++) - { - s = natsConnection_PublishString(nc, "foo", "hello"); - } - IFOK(s, natsConnection_Flush(nc)); - - // Make sure the callback blocks before checking for slow consumer - natsMutex_Lock(arg.m); - - while ((s != NATS_TIMEOUT) && !arg.msgReceived) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - - natsMutex_Unlock(arg.m); - - test("Last Error should be SlowConsumer: "); - testCond((s == NATS_OK) - && natsConnection_GetLastError(nc, &lastErr) == NATS_SLOW_CONSUMER); - - // Check the pending values - test("Check pending values, NULL sub: "); - s = natsSubscription_GetPending(NULL, &msgs, &bytes); - testCond(s != NATS_OK); - - test("Check pending values, NULL msgs: "); - s = natsSubscription_GetPending(sub, NULL, &bytes); - testCond(s == NATS_OK); - - test("Check pending values, NULL bytes: "); - s = natsSubscription_GetPending(sub, &msgs, NULL); - testCond(s == NATS_OK); - - msgs = 0; - bytes = 0; - - test("Check pending values: "); - s = natsSubscription_GetPending(sub, &msgs, &bytes); - testCond((s == NATS_OK) - && ((msgs == total) || (msgs == total - 1)) - && ((bytes == total * 5) || (bytes == (total - 1) * 5))); - - test("Check dropped: NULL sub: "); - s = natsSubscription_GetDropped(NULL, &dropped); - testCond(s != NATS_OK); - - test("Check dropped, NULL msgs: "); - s = natsSubscription_GetDropped(sub, NULL); - testCond(s != NATS_OK); - - msgs = 0; - test("Check dropped: "); - s = natsSubscription_GetDropped(sub, &dropped); - testCond((s == NATS_OK) - && ((dropped == (int64_t)(sent - total)) - || (dropped == (int64_t)(sent - total - 1)))); - - test("Check delivered: NULL sub: "); - s = natsSubscription_GetDelivered(NULL, &delivered); - testCond(s != NATS_OK); - - test("Check delivered: NULL msgs: "); - s = natsSubscription_GetDelivered(sub, NULL); - testCond(s != NATS_OK); - - msgs = 0; - test("Check delivered: "); - s = natsSubscription_GetDelivered(sub, &delivered); - testCond((s == NATS_OK) && (delivered == 1)); - - test("Check get stats pending: "); - s = natsSubscription_GetStats(sub, &msgs, &bytes, NULL, NULL, NULL, NULL); - testCond((s == NATS_OK) - && ((msgs == total) || (msgs == total - 1)) - && ((bytes == total * 5) || (bytes == (total - 1) * 5))); - - test("Check get stats max pending: "); - s = natsSubscription_GetStats(sub, NULL, NULL, &msgs, &bytes, NULL, NULL); - testCond((s == NATS_OK) - && (msgs >= total - 1) && (msgs <= total) - && (bytes >= (total - 1) * 5) && (bytes <= total * 5)); - - test("Check get stats delivered: "); - s = natsSubscription_GetStats(sub, NULL, NULL, NULL, NULL, &delivered, NULL); - testCond((s == NATS_OK) && (delivered == 1)); - - test("Check get stats dropped: "); - s = natsSubscription_GetStats(sub, NULL, NULL, NULL, NULL, NULL, &dropped); - testCond((s == NATS_OK) - && ((dropped == (int64_t) (sent - total)) - || (dropped == (int64_t) (sent - total - 1)))); - - test("Check get stats all NULL: "); - s = natsSubscription_GetStats(sub, NULL, NULL, NULL, NULL, NULL, NULL); - testCond(s == NATS_OK); - - // Release the sub - natsMutex_Lock(arg.m); - - // Unblock the wait - arg.closed = true; - - // And close the subscription here so that the next msg callback - // is not invoked. - natsSubscription_Unsubscribe(sub); - - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - // All these calls should fail with a closed subscription - test("SetPendingLimit on closed sub: "); - s = natsSubscription_SetPendingLimits(sub, 1, 1); - testCond(s != NATS_OK); - - test("GetPendingLimit on closed sub: "); - s = natsSubscription_GetPendingLimits(sub, NULL, NULL); - testCond(s != NATS_OK); - - test("GetPending on closed sub: "); - s = natsSubscription_GetPending(sub, &msgs, &bytes); - testCond(s != NATS_OK); - - test("GetDelivered on closed sub: "); - s = natsSubscription_GetDelivered(sub, &delivered); - testCond(s != NATS_OK); - - test("GetDropped on closed sub: "); - s = natsSubscription_GetDropped(sub, &dropped); - testCond(s != NATS_OK); - - test("Check get stats on closed sub: "); - s = natsSubscription_GetStats(sub, NULL, NULL, NULL, NULL, NULL, NULL); - testCond(s != NATS_OK); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_PendingLimitsWithSyncSub(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsPid serverPid = NATS_INVALID_PID; - int msgsLimit = 0; - int bytesLimit= 0; - int msgs = 0; - int bytes = 0; - int64_t dropped = 0; - int64_t delivered = 0; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo")); - IFOK(s, natsSubscription_SetPendingLimits(sub, 10000, 10)); - - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - test("Check pending limits: "); - s = natsSubscription_GetPendingLimits(sub, &msgsLimit, &bytesLimit); - testCond((s == NATS_OK) && (msgsLimit == 10000) && (bytesLimit == 10)); - - test("Can publish: "); - s = natsConnection_PublishString(nc, "foo", "abcde"); - IFOK(s, natsConnection_PublishString(nc, "foo", "abcdefghijklmnopqrstuvwxyz")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - // Check the pending values - test("Check pending values: "); - s = natsSubscription_GetPending(sub, &msgs, &bytes); - testCond((s == NATS_OK) && (msgs == 1) && (bytes == 5)); - - msgs = 0; - test("Check dropped: "); - s = natsSubscription_GetDropped(sub, &dropped); - testCond((s == NATS_OK) && (dropped == 1)); - - test("Can publish small: "); - s = natsConnection_PublishString(nc, "foo", "abc"); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Receive first msg: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) - && (msg != NULL) - && (strcmp(natsMsg_GetData(msg), "abcde") == 0)); - - msgs = 0; - test("Check delivered: "); - s = natsSubscription_GetDelivered(sub, &delivered); - testCond((s == NATS_OK) && (delivered == 1)); - - natsMsg_Destroy(msg); - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_AsyncSubscriptionPending(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsPid serverPid = NATS_INVALID_PID; - int total = 100; - int msgs = 0; - int bytes = 0; - int mlen = 10; - int totalSize = total * mlen; - uint64_t queuedMsgs= 0; - int i; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.status = NATS_OK; - arg.control= 7; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg)); - - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - // Check with invalid args - test("Call MaxPending with invalid args: NULL sub: "); - s = natsSubscription_GetMaxPending(NULL, &msgs, &bytes); - testCond(s != NATS_OK); - - test("Call MaxPending with invalid args: other NULL params OK: "); - s = natsSubscription_GetMaxPending(sub, NULL, &bytes); - IFOK(s, natsSubscription_GetMaxPending(sub, &msgs, NULL)); - IFOK(s, natsSubscription_GetMaxPending(sub, NULL, NULL)); - testCond(s == NATS_OK); - - for (i = 0; (s == NATS_OK) && (i < total); i++) - s = natsConnection_PublishString(nc, "foo", "0123456789"); - - IFOK(s, natsConnection_Flush(nc)); - - // Wait that a message is received, so checks are safe - natsMutex_Lock(arg.m); - - while ((s != NATS_TIMEOUT) && !arg.msgReceived) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - - natsMutex_Unlock(arg.m); - - // Test old way - test("Test queued msgs old way: "); - s = natsSubscription_QueuedMsgs(sub, &queuedMsgs); - testCond((s == NATS_OK) - && (((int)queuedMsgs == total) || ((int)queuedMsgs == total - 1))); - - // New way, make sure the same and check bytes. - test("Test new way: "); - s = natsSubscription_GetPending(sub, &msgs, &bytes); - testCond((s == NATS_OK) - && ((msgs == total) || (msgs == total - 1)) - && ((bytes == totalSize) || (bytes == totalSize - mlen))); - - - // Make sure max has been set. Since we block after the first message is - // received, MaxPending should be >= total - 1 and <= total - test("Check max pending: "); - s = natsSubscription_GetMaxPending(sub, &msgs, &bytes); - testCond((s == NATS_OK) - && ((msgs <= total) && (msgs >= total-1)) - && ((bytes <= totalSize) && (bytes >= totalSize-mlen))); - - test("Check ClearMaxPending: "); - s = natsSubscription_ClearMaxPending(sub); - if (s == NATS_OK) - s = natsSubscription_GetMaxPending(sub, &msgs, &bytes); - testCond((s == NATS_OK) && (msgs == 0) && (bytes == 0)); - - natsMutex_Lock(arg.m); - arg.closed = true; - natsSubscription_Unsubscribe(sub); - natsCondition_Signal(arg.c); - arg.msgReceived = false; - natsMutex_Unlock(arg.m); - - // These calls should fail once the subscription is closed. - test("Check MaxPending on closed sub: "); - s = natsSubscription_GetMaxPending(sub, &msgs, &bytes); - testCond(s != NATS_OK); - - test("Check ClearMaxPending on closed sub: "); - s = natsSubscription_ClearMaxPending(sub); - testCond(s != NATS_OK); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - natsMutex_Lock(arg.m); - while (!arg.msgReceived) - natsCondition_TimedWait(arg.c, arg.m, 5000); - natsMutex_Unlock(arg.m); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_AsyncSubscriptionPendingDrain(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsPid serverPid = NATS_INVALID_PID; - int total = 100; - int msgs = 0; - int bytes = 0; - int64_t delivered = 0; - int i; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.status = NATS_OK; - arg.string = "0123456789"; - arg.control= 1; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg)); - - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - for (i = 0; (s == NATS_OK) && (i < total); i++) - s = natsConnection_PublishString(nc, "foo", arg.string); - - IFOK(s, natsConnection_Flush(nc)); - - test("Wait for all delivered: "); - msgs = 0; - for (i=0; (s == NATS_OK) && (i<500); i++) - { - s = natsSubscription_GetDelivered(sub, &delivered); - if ((s == NATS_OK) && (delivered == (int64_t) total)) - break; - - nats_Sleep(10); - } - testCond((s == NATS_OK) && (delivered == (int64_t) total)); - - test("Check pending: "); - s = natsSubscription_GetPending(sub, &msgs, &bytes); - testCond((s == NATS_OK) && (msgs == 0) && (bytes == 0)); - - natsSubscription_Unsubscribe(sub); - - test("Check Delivered on closed sub: "); - s = natsSubscription_GetDelivered(sub, &delivered); - testCond(s != NATS_OK); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_SyncSubscriptionPending(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsMsg *msg = NULL; - natsSubscription *sub = NULL; - natsPid serverPid = NATS_INVALID_PID; - int total = 100; - int msgs = 0; - int bytes = 0; - int mlen = 10; - int totalSize = total * mlen; - uint64_t queuedMsgs= 0; - int i; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo")); - - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - // Check with invalid args - test("Call MaxPending with invalid args: NULL sub: "); - s = natsSubscription_GetMaxPending(NULL, &msgs, &bytes); - testCond(s != NATS_OK); - - test("Call MaxPending with invalid args: other NULL params OK: "); - s = natsSubscription_GetMaxPending(sub, NULL, &bytes); - IFOK(s, natsSubscription_GetMaxPending(sub, &msgs, NULL)); - IFOK(s, natsSubscription_GetMaxPending(sub, NULL, NULL)); - testCond(s == NATS_OK); - - for (i = 0; (s == NATS_OK) && (i < total); i++) - s = natsConnection_PublishString(nc, "foo", "0123456789"); - - IFOK(s, natsConnection_Flush(nc)); - - // Test old way - test("Test queued msgs old way: "); - s = natsSubscription_QueuedMsgs(sub, &queuedMsgs); - testCond((s == NATS_OK) - && (((int)queuedMsgs == total) || ((int)queuedMsgs == total - 1))); - - // New way, make sure the same and check bytes. - test("Test new way: "); - s = natsSubscription_GetPending(sub, &msgs, &bytes); - testCond((s == NATS_OK) - && ((msgs == total) || (msgs == total - 1)) - && ((bytes == totalSize) || (bytes == totalSize - mlen))); - - - // Make sure max has been set. Since we block after the first message is - // received, MaxPending should be >= total - 1 and <= total - test("Check max pending: "); - s = natsSubscription_GetMaxPending(sub, &msgs, &bytes); - testCond((s == NATS_OK) - && ((msgs <= total) && (msgs >= total-1)) - && ((bytes <= totalSize) && (bytes >= totalSize-mlen))); - - test("Check ClearMaxPending: "); - s = natsSubscription_ClearMaxPending(sub); - IFOK(s, natsSubscription_GetMaxPending(sub, &msgs, &bytes)); - testCond((s == NATS_OK) && (msgs == 0) && (bytes == 0)); - - // Drain all but one - for (i=0; (s == NATS_OK) && (im); - - if (arg->sum == 1) - { - natsMutex_Unlock(arg->m); - return; - } - - arg->sum = 1; - - if (sub != arg->sub) - arg->status = NATS_ERR; - - if ((arg->status == NATS_OK) && (err != NATS_SLOW_CONSUMER)) - arg->status = NATS_ERR; - - arg->closed = true; - arg->done = true; - natsCondition_Broadcast(arg->c); - - natsMutex_Unlock(arg->m); -} - -static void -test_AsyncErrHandler_MaxPendingMsgs(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.status = NATS_OK; - arg.control= 7; - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL)); - IFOK(s, natsOptions_SetMaxPendingMsgs(opts, 10)); - IFOK(s, natsOptions_SetErrorHandler(opts, _asyncErrCb, (void*) &arg)); - - if (s != NATS_OK) - FAIL("Unable to create options for test AsyncErrHandler"); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_Subscribe(&sub, nc, "async_test", _recvTestString, (void*) &arg)); - - natsMutex_Lock(arg.m); - arg.sub = sub; - natsMutex_Unlock(arg.m); - - for (int i=0; - (s == NATS_OK) && (i < (opts->maxPendingMsgs + 100)); i++) - { - s = natsConnection_PublishString(nc, "async_test", "hello"); - } - IFOK(s, natsConnection_Flush(nc)); - - // Wait for async err callback - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - - test("Aync fired properly, and all checks are good: "); - testCond((s == NATS_OK) - && arg.done - && arg.closed - && (arg.status == NATS_OK)); - - natsOptions_Destroy(opts); - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_AsyncErrHandler_MaxPendingBytes(void) -{ - natsStatus s; - natsConnection* nc = NULL; - natsOptions* opts = NULL; - natsSubscription* sub = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - int data_len = 10; - const char msg[] = { 0,1,2,3,4,5,6,7,8,9 }; //10 bytes long message - int64_t pendingBytesLimit = 100; - int i = 0; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.status = NATS_OK; - arg.control = 7; - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL)); - IFOK(s, natsOptions_SetMaxPendingBytes(opts, pendingBytesLimit)); - IFOK(s, natsOptions_SetErrorHandler(opts, _asyncErrCb, (void*)&arg)); - - if (s != NATS_OK) - FAIL("Unable to create options for test AsyncErrHandler"); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_Subscribe(&sub, nc, "async_test", _recvTestString, (void*)&arg)); - - natsMutex_Lock(arg.m); - arg.sub = sub; - natsMutex_Unlock(arg.m); - for (i=0; - (s == NATS_OK) && (i < (pendingBytesLimit + 100)); i+=data_len) //increment by 10 (message size) each iteration - { - s = natsConnection_Publish(nc, "async_test", msg, data_len); - } - IFOK(s, natsConnection_Flush(nc)); - - // Wait for async err callback - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - - test("Aync fired properly, and all checks are good: "); - testCond((s == NATS_OK) - && arg.done - && arg.closed - && (arg.status == NATS_OK)); - - natsOptions_Destroy(opts); - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -_asyncErrBlockingCb(natsConnection *nc, natsSubscription *sub, natsStatus err, void* closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - - arg->sum++; - - while ((arg->sum == 1) && !arg->closed) - natsCondition_Wait(arg->c, arg->m); - - if (sub != arg->sub) - arg->status = NATS_ERR; - - if ((arg->status == NATS_OK) && (err != NATS_SLOW_CONSUMER)) - arg->status = NATS_ERR; - - if (arg->status == NATS_OK) - { - // Call some subscription API to make sure that the pointer has not been freed. - arg->current = natsSubscription_IsValid(sub); - } - - arg->done = true; - natsCondition_Signal(arg->c); - - natsMutex_Unlock(arg->m); -} - -static void -test_AsyncErrHandlerSubDestroyed(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsPid serverPid = NATS_INVALID_PID; - natsMsg *msg = NULL; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL)); - IFOK(s, natsOptions_SetMaxPendingMsgs(opts, 1)); - IFOK(s, natsOptions_SetErrorHandler(opts, _asyncErrBlockingCb, (void*) &arg)); - - if (s != NATS_OK) - FAIL("Unable to create options for test AsyncErrHandler"); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - test("Create sync sub: "); - s = natsConnection_SubscribeSync(&sub, nc, "foo"); - testCond(s == NATS_OK); - - natsMutex_Lock(arg.m); - arg.sub = sub; - arg.current = true; - natsMutex_Unlock(arg.m); - - test("Cause error: "); - s = natsConnection_PublishString(nc, "foo", "msg1"); - IFOK(s, natsConnection_PublishString(nc, "foo", "msg2")); - testCond(s == NATS_OK); - - // Wait a bit to make sure that we have a slow consumer. - nats_Sleep(250); - - test("Next should be error: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_SLOW_CONSUMER) && (msg == NULL)); - nats_clearLastError(); - s = NATS_OK; - - test("Consume 1: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - msg = NULL; - - test("Cause error again: "); - s = natsConnection_PublishString(nc, "foo", "msg3"); - IFOK(s, natsConnection_PublishString(nc, "foo", "msg4")); - testCond(s == NATS_OK); - - test("Wait a bit for async error to be posted: "); - nats_Sleep(200); - testCond(true); - - test("Destroy subscription: "); - natsSubscription_Destroy(sub); - testCond(true); - - test("Wait for async error callback to return: "); - natsMutex_Lock(arg.m); - // First unblock the first instance - arg.closed = true; - natsCondition_Signal(arg.c); - // Now wait for the callback to have processed both and be done. - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - // If ok, arg.current should be false since the subscription should - // not be valid (closed) at the second callback iteration. - if ((s == NATS_OK) && arg.current) - s = NATS_ERR; - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -_responseCb(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - - arg->closed = true; - arg->done = true; - natsCondition_Signal(arg->c); - - natsMutex_Unlock(arg->m); - - natsMsg_Destroy(msg); -} - -static void -_startCb(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - natsInbox *response = NULL; - natsStatus s; - - natsMutex_Lock(arg->m); - - s = natsInbox_Create(&response); - IFOK(s, natsConnection_Subscribe(&(arg->sub), nc, response, _responseCb, (void*) arg)); - IFOK(s, natsConnection_PublishRequestString(nc, "helper", response, "Help Me!")); - - if (s != NATS_OK) - arg->status = s; - - // We need to destroy the inbox. It has been copied by the - // natsConnection_Subscribe() call. - natsInbox_Destroy(response); - - natsMutex_Unlock(arg->m); - - natsMsg_Destroy(msg); -} - -static void -test_AsyncSubscriberStarvation(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsSubscription *sub2 = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.status = NATS_OK; - arg.control= 4; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_Subscribe(&sub, nc, "helper", - _recvTestString, (void*) &arg)); - IFOK(s, natsConnection_Subscribe(&sub2, nc, "start", - _startCb, (void*) &arg)); - - IFOK(s, natsConnection_PublishString(nc, "start", "Begin")); - IFOK(s, natsConnection_Flush(nc)); - - // Wait for end of test - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - - test("Test not stalled in cb waiting for other cb: "); - testCond((s == NATS_OK) - && arg.done - && (arg.status == NATS_OK)); - - natsSubscription_Destroy(arg.sub); - natsSubscription_Destroy(sub); - natsSubscription_Destroy(sub2); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_AsyncSubscriberOnClose(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsSubscription *sub2 = NULL; - natsPid serverPid = NATS_INVALID_PID; - int seen = 0; - int checks = 0; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.status = NATS_OK; - arg.control= 8; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", - _recvTestString, (void*) &arg)); - - for (int i=0; (s == NATS_OK) && (i < 10); i++) - s = natsConnection_PublishString(nc, "foo", "Hello World"); - - IFOK(s, natsConnection_Flush(nc)); - - // Wait to receive the first message - test("Wait for first message: "); - natsMutex_Lock(arg.m); - while ((s == NATS_OK) && (arg.sum != 1)) - { - natsMutex_Unlock(arg.m); - nats_Sleep(100); - natsMutex_Lock(arg.m); - if (checks++ > 10) - s = NATS_ILLEGAL_STATE; - } - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - natsConnection_Close(nc); - - // Release callbacks - natsMutex_Lock(arg.m); - arg.closed = true; - natsCondition_Broadcast(arg.c); - natsMutex_Unlock(arg.m); - - // Wait for some time - nats_Sleep(100); - - natsMutex_Lock(arg.m); - seen = arg.sum; - natsMutex_Unlock(arg.m); - - test("Make sure only one callback fired: "); - testCond(seen == 1); - - natsSubscription_Destroy(sub); - natsSubscription_Destroy(sub2); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_NextMsgCallOnAsyncSub(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsPid serverPid = NATS_INVALID_PID; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Setup: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, NULL)) - testCond(s == NATS_OK); - - test("NextMsg should fail for async sub: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s != NATS_OK) && (msg == NULL)); - - natsSubscription_Destroy(sub); - - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -testSubOnComplete(void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - arg->status = (arg->control == 2 ? NATS_OK : NATS_ERR); - arg->done = true; - natsCondition_Signal(arg->c); - natsMutex_Unlock(arg->m); -} - -static void -testOnCompleteMsgHandler(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - arg->control = 1; - natsCondition_Signal(arg->c); - natsMutex_Unlock(arg->m); - - // Sleep here so that main thread invokes Unsubscribe() and we make sure that the - // onComplete is not invoked until this function returns. - nats_Sleep(500); - - natsMutex_Lock(arg->m); - arg->control = 2; - natsMutex_Unlock(arg->m); - - natsMsg_Destroy(msg); -} - -static void -test_SubOnComplete(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Invalid arg: "); - s = natsSubscription_SetOnCompleteCB(NULL, testSubOnComplete, NULL); - testCond(s == NATS_INVALID_ARG); - - test("Connect + sub: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo")); - testCond(s == NATS_OK); - - test("Invalid sub (NULL): "); - s= natsSubscription_SetOnCompleteCB(sub, testSubOnComplete, NULL); - testCond(s == NATS_INVALID_SUBSCRIPTION); - - natsSubscription_Unsubscribe(sub); - test("Invalid sub (sync): "); - s = natsSubscription_SetOnCompleteCB(sub, testSubOnComplete, NULL); - testCond(s == NATS_INVALID_SUBSCRIPTION); - - natsSubscription_Destroy(sub); - sub = NULL; - arg.status = NATS_ERR; - test("SetOnCompleteCB ok: "); - s = natsConnection_Subscribe(&sub, nc, "foo", testOnCompleteMsgHandler, &arg); - IFOK(s, natsSubscription_SetOnCompleteCB(sub, testSubOnComplete, &arg)); - testCond(s == NATS_OK); - test("SetOnCompleteCB to NULL ok: "); - s = natsSubscription_SetOnCompleteCB(sub, NULL, NULL); - if (s == NATS_OK) - { - natsSub_Lock(sub); - s = (((sub->onCompleteCB == NULL) && (sub->onCompleteCBClosure == NULL)) ? NATS_OK : NATS_ERR); - natsSub_Unlock(sub); - } - testCond(s == NATS_OK); - test("OnComplete invoked after last message: "); - s = natsSubscription_SetOnCompleteCB(sub, testSubOnComplete, &arg); - IFOK(s, natsConnection_PublishString(nc, "foo", "hello")); - IFOK(s, natsConnection_Flush(nc)); - if (s == NATS_OK) - { - // Wait for message handler to be invoked - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.control != 1)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - } - IFOK(s, natsSubscription_Unsubscribe(sub)); - if (s == NATS_OK) - { - // Now wait for the onComplete to return. - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - - // This will be OK if complete callback was invoked after msg handler returned, - // otherwise will be NATS_ERR; - s = arg.status; - natsMutex_Unlock(arg.m); - } - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - -static void -test_ServersOption(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - char buffer[128]; - int serversCount; - - serversCount = sizeof(testServers) / sizeof(char *); - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetNoRandomize(opts, true)); -#if _WIN32 - IFOK(s, natsOptions_SetTimeout(opts, 250)); -#endif - - if (s != NATS_OK) - FAIL("Unable to create options for test ServerOptions"); - - test("Connect should fail with NATS_NO_SERVER: "); - s = natsConnection_Connect(&nc, opts); - testCond((nc == NULL) && (s == NATS_NO_SERVER)); - - test("Connect with list of servers should fail with NATS_NO_SERVER: "); - s = natsOptions_SetServers(opts, testServers, serversCount); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond((nc == NULL) && (s == NATS_NO_SERVER)); - - serverPid = _startServer("nats://127.0.0.1:1222", "-p 1222", true); - CHECK_SERVER_STARTED(serverPid); - - buffer[0] = '\0'; - test("Can connect to first: ") - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer))); - testCond((s == NATS_OK) - && (buffer[0] != '\0') - && (strcmp(buffer, testServers[0]) == 0)); - - natsConnection_Destroy(nc); - nc = NULL; - - _stopServer(serverPid); - serverPid = NATS_INVALID_PID; - - // Make sure we can connect to a non first server if running - serverPid = _startServer("nats://127.0.0.1:1223", "-p 1223", true); - CHECK_SERVER_STARTED(serverPid); - - buffer[0] = '\0'; - test("Can connect to second: ") - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer))); - testCond((s == NATS_OK) - && (buffer[0] != '\0') - && (strcmp(buffer, testServers[1]) == 0)); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _stopServer(serverPid); -} - -static void -test_AuthServers(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid1= NATS_INVALID_PID; - natsPid serverPid2= NATS_INVALID_PID; - char buffer[128]; - const char *plainServers[] = {"nats://127.0.0.1:1222", - "nats://127.0.0.1:1224"}; - const char *authServers[] = {"nats://127.0.0.1:1222", - "nats://ivan:foo@127.0.0.1:1224"}; - int serversCount = 2; - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetNoRandomize(opts, true)); - IFOK(s, natsOptions_SetServers(opts, plainServers, serversCount)); - - if (s != NATS_OK) - FAIL("Unable to create options for test ServerOptions"); - - serverPid1 = _startServer("nats://127.0.0.1:1222", "-p 1222 --user ivan --pass foo", false); - CHECK_SERVER_STARTED(serverPid1); - - serverPid2 = _startServer("nats://127.0.0.1:1224", "-p 1224 --user ivan --pass foo", false); - if (serverPid2 == NATS_INVALID_PID) - _stopServer(serverPid1); - CHECK_SERVER_STARTED(serverPid2); - - nats_Sleep(500); - - test("Connect fails due to auth error: "); - s = natsConnection_Connect(&nc, opts); - testCond((s == NATS_CONNECTION_AUTH_FAILED) && (nc == NULL)); - - buffer[0] = '\0'; - test("Connect succeeds with correct servers list: ") - s = natsOptions_SetServers(opts, authServers, serversCount); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond((s == NATS_OK) - && (nc != NULL) - && (natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer)) == NATS_OK) - // Even though we are using the url nats://127.0.0.1:1222, the library - // will use the user/pwd info found in the second url. So we should have - // connected OK to the first (no randomization option set at beginning of test). - && (strcmp(buffer, authServers[0]) == 0)); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _stopServer(serverPid1); - _stopServer(serverPid2); -} - -static void -test_AuthFailToReconnect(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid1= NATS_INVALID_PID; - natsPid serverPid2= NATS_INVALID_PID; - natsPid serverPid3= NATS_INVALID_PID; - char buffer[64]; - const char *servers[] = {"nats://127.0.0.1:22222", - "nats://127.0.0.1:22223", - "nats://127.0.0.1:22224"}; - struct threadArg args; - - int serversCount = 3; - - s = _createDefaultThreadArgsForCbTests(&args); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetNoRandomize(opts, true)); - IFOK(s, natsOptions_SetServers(opts, servers, serversCount)); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &args)); - IFOK(s, natsOptions_SetMaxReconnect(opts, 10)); - IFOK(s, natsOptions_SetReconnectWait(opts, 100)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); - - if (s != NATS_OK) - FAIL("Unable to setup test"); - - serverPid1 = _startServer("nats://127.0.0.1:22222", "-p 22222", false); - CHECK_SERVER_STARTED(serverPid1); - - serverPid2 = _startServer("nats://127.0.0.1:22223", "-p 22223 --user ivan --pass foo", false); - if (serverPid2 == NATS_INVALID_PID) - _stopServer(serverPid1); - CHECK_SERVER_STARTED(serverPid2); - - serverPid3 = _startServer("nats://127.0.0.1:22224", "-p 22224", false); - if (serverPid3 == NATS_INVALID_PID) - { - _stopServer(serverPid1); - _stopServer(serverPid2); - } - CHECK_SERVER_STARTED(serverPid3); - - nats_Sleep(1000); - - test("Connect should succeed: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - // Stop the server which will trigger the reconnect - _stopServer(serverPid1); - serverPid1 = NATS_INVALID_PID; - - - // The client will try to connect to the second server, and that - // should fail. It should then try to connect to the third and succeed. - - // Wait for the reconnect CB. - test("Reconnect callback should be triggered: ") - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) - && !(args.reconnected)) - { - s = natsCondition_TimedWait(args.c, args.m, 5000); - } - natsMutex_Unlock(args.m); - testCond((s == NATS_OK) && args.reconnected); - - test("Connection should not be closed: "); - testCond(natsConnection_IsClosed(nc) == false); - - buffer[0] = '\0'; - test("Should have connected to third server: "); - s = natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer)); - testCond((s == NATS_OK) && (buffer[0] != '\0') - && (strcmp(buffer, servers[2]) == 0)); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&args); - - _stopServer(serverPid2); - _stopServer(serverPid3); -} - -static void -test_BasicClusterReconnect(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid1= NATS_INVALID_PID; - natsPid serverPid2= NATS_INVALID_PID; - char buffer[128]; - int serversCount; - int64_t reconnectTimeStart, reconnectTime; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - serversCount = sizeof(testServers) / sizeof(char*); - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_IPResolutionOrder(opts, 4)); - IFOK(s, natsOptions_SetTimeout(opts, 500)); - IFOK(s, natsOptions_SetNoRandomize(opts, true)); - IFOK(s, natsOptions_SetServers(opts, testServers, serversCount)); - IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg)); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg)); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)); - IFOK(s, natsOptions_SetReconnectWait(opts, 100)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); - - if (s != NATS_OK) - FAIL("Unable to create options for test ServerOptions"); - - serverPid1 = _startServer("nats://127.0.0.1:1222", "-p 1222", true); - CHECK_SERVER_STARTED(serverPid1); - - serverPid2 = _startServer("nats://127.0.0.1:1224", "-p 1224", true); - if (serverPid2 == NATS_INVALID_PID) - _stopServer(serverPid1); - CHECK_SERVER_STARTED(serverPid2); - - test("Check connected to the right server: "); - s = natsConnection_Connect(&nc, opts); - - _stopServer(serverPid1); - - // wait for disconnect - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.disconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - - reconnectTimeStart = nats_Now(); - - // wait for reconnect - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.reconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 3000); - natsMutex_Unlock(arg.m); - - testCond((s == NATS_OK) - && (natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer)) == NATS_OK) - && (strcmp(buffer, testServers[2]) == 0)); - - // Make sure we did not wait on reconnect for default time. - // Reconnect should be fast since it will be a switch to the - // second server and not be dependent on server restart time. - reconnectTime = nats_Now() - reconnectTimeStart; - - test("Check reconnect time did not take too long: "); -#if _WIN32 - testCond(reconnectTime <= 1300); -#else - testCond(reconnectTime <= 100); -#endif - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _waitForConnClosed(&arg); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid2); -} - -static const char* -_reconnectTokenHandler(void* closure) -{ - const char *token; - struct threadArg *args = (struct threadArg*) closure; - - natsMutex_Lock(args->m); - token = args->tokens[args->tokenCallCount % (sizeof(args->tokens)/sizeof(char*))]; - args->tokenCallCount++; - if (args->nc != NULL) - { - char buffer[256] = {'\0'}; - natsStatus s; - - s = natsConnection_GetConnectedUrl(args->nc, buffer, sizeof(buffer)); - if (s == NATS_OK) - { - if (((args->tokenCallCount == 2) && (strcmp(buffer, "nats://127.0.0.1:22223") == 0)) - || ((args->tokenCallCount == 3) && (strcmp(buffer, "nats://127.0.0.1:22224") == 0))) - { - args->results[0]++; - buffer[0] = '\0'; - s = natsConnection_GetConnectedServerId(args->nc, buffer, sizeof(buffer)); - if ((s == NATS_OK) && (strlen(buffer) > 0)) - args->results[0]++; - } - } - } - natsMutex_Unlock(args->m); - - return token; -} - -static void -test_ReconnectWithTokenHandler(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid1= NATS_INVALID_PID; - natsPid serverPid2= NATS_INVALID_PID; - natsPid serverPid3= NATS_INVALID_PID; - char buffer[64]; - const char *servers[] = {"nats://127.0.0.1:22222", - "nats://127.0.0.1:22223", - "nats://127.0.0.1:22224"}; - struct threadArg args; - - int serversCount = 3; - - s = _createDefaultThreadArgsForCbTests(&args); - args.tokenCallCount = 0; - args.tokens[0] = "token1"; - args.tokens[1] = "badtoken"; - args.tokens[2] = "token3"; - - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetNoRandomize(opts, true)); - IFOK(s, natsOptions_SetServers(opts, servers, serversCount)); - IFOK(s, natsOptions_SetTokenHandler(opts, _reconnectTokenHandler, (void*) &args)); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &args)); - IFOK(s, natsOptions_SetMaxReconnect(opts, 10)); - IFOK(s, natsOptions_SetReconnectWait(opts, 100)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); - - if (s != NATS_OK) - FAIL("Unable to setup test"); - - serverPid1 = _startServer("nats://token1@127.0.0.1:22222", "-p 22222 --auth token1", true); - CHECK_SERVER_STARTED(serverPid1); - - serverPid2 = _startServer("nats://user:foo@127.0.0.1:22223", "-p 22223 --user ivan --pass foo", true); - if (serverPid2 == NATS_INVALID_PID) - _stopServer(serverPid1); - CHECK_SERVER_STARTED(serverPid2); - - serverPid3 = _startServer("nats://token3@127.0.0.1:22224", "-p 22224 --auth token3", true); - if (serverPid3 == NATS_INVALID_PID) - { - _stopServer(serverPid1); - _stopServer(serverPid2); - } - CHECK_SERVER_STARTED(serverPid3); - - test("Connect should succeed: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - natsMutex_Lock(args.m); - args.nc = nc; - natsMutex_Unlock(args.m); - - // Stop the server which will trigger the reconnect - _stopServer(serverPid1); - serverPid1 = NATS_INVALID_PID; - - - // The client will try to connect to the second server, and that - // should fail. It should then try to connect to the third and succeed. - - // Wait for the reconnect CB. - test("Reconnect callback should be triggered: ") - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) - && !(args.reconnected)) - { - s = natsCondition_TimedWait(args.c, args.m, 5000); - } - natsMutex_Unlock(args.m); - testCond((s == NATS_OK) && args.reconnected); - - test("Connection should not be closed: "); - testCond(natsConnection_IsClosed(nc) == false); - - buffer[0] = '\0'; - test("Should have connected to third server: "); - s = natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer)); - testCond((s == NATS_OK) && (buffer[0] != '\0') - && (strcmp(buffer, servers[2]) == 0)); - - test("ConnectedURL and ServerID OKs during reconnect process: "); - natsMutex_Lock(args.m); - s = ((args.results[0] == 4) ? NATS_OK : NATS_ERR); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&args); - - _stopServer(serverPid2); - _stopServer(serverPid3); -} - -#define NUM_CLIENTS (100) - -struct hashCount -{ - int count; - -}; - -static void -test_HotSpotReconnect(void) -{ - natsStatus s; - natsConnection *nc[NUM_CLIENTS]; - natsOptions *opts = NULL; - natsPid serverPid1= NATS_INVALID_PID; - natsPid serverPid2= NATS_INVALID_PID; - natsPid serverPid3= NATS_INVALID_PID; - char buffer[128]; - int serversCount; - natsStrHash *cs = NULL; - struct threadArg arg; - struct hashCount *count = NULL; - -#if _WIN32 - test("Skip when running on Windows: "); - testCond(true); - return; -#endif - - memset(nc, 0, sizeof(nc)); - - s = natsStrHash_Create(&cs, 4); - IFOK(s, _createDefaultThreadArgsForCbTests(&arg)); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - serversCount = sizeof(testServers) / sizeof(char*); - - serverPid1 = _startServer("nats://127.0.0.1:1222", "-p 1222", true); - CHECK_SERVER_STARTED(serverPid1); - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetServers(opts, testServers, serversCount)) - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg)); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - for (int i=0; (s == NATS_OK) && (icount++; - s = natsStrHash_Set(cs, buffer, true, (void*) count, NULL); - } - } - - natsConnection_Close(nc[i]); - } - - test("Check correct number of servers: "); - testCond((s == NATS_OK) && (natsStrHash_Count(cs) == 2)); - - if (s == NATS_OK) - { - // numClients = 100 - // numServers = 2 - // expected = numClients / numServers - // v = expected * 0.3 - - natsStrHashIter iter; - struct hashCount *val; - int total; - int delta; - int v = (int) (((float)NUM_CLIENTS / 2) * 0.30); - void *p = NULL; - - natsStrHashIter_Init(&iter, cs); - while (natsStrHashIter_Next(&iter, NULL, &p)) - { - val = (struct hashCount*) p; - total = val->count; - - delta = ((NUM_CLIENTS / 2) - total); - if (delta < 0) - delta *= -1; - - if (delta > v) - s = NATS_ERR; - - free(val); - } - - test("Check variance: "); - testCond(s == NATS_OK); - } - - for (int i=0; ireconnects == 2)); - - // Disconnect CB only from disconnect from server 1. - test("Disconnected should have been called once: "); - testCond((s == NATS_OK) && arg.disconnects == 1); - - test("Connection should be closed: ") - testCond((s == NATS_OK) - && natsConnection_IsClosed(nc)); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - natsStatistics_Destroy(stats); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid2); -} - -static void -test_TimeoutOnNoServer(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - int serversCount; - int64_t startWait, timedWait; - struct threadArg arg; - -#if _WIN32 - test("Skip when running on Windows: "); - testCond(true); - return; -#endif - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - serversCount = sizeof(testServers) / sizeof(char*); - - // 1000 milliseconds total time wait - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetNoRandomize(opts, true)); - IFOK(s, natsOptions_SetMaxReconnect(opts, 10)); - IFOK(s, natsOptions_SetReconnectWait(opts, 100)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); - IFOK(s, natsOptions_SetServers(opts, testServers, serversCount)); - IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg)); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)); - - if (s != NATS_OK) - FAIL("Unable to create options for test ServerOptions"); - - serverPid = _startServer("nats://127.0.0.1:1222", "-p 1222", true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - _stopServer(serverPid); - - // wait for disconnect - test("Wait for disconnected: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.disconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && arg.disconnected); - - startWait = nats_Now(); - - // wait for closed - test("Wait for closed: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 2000 + serversCount*50); - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && arg.closed); - - timedWait = nats_Now() - startWait; - - // The client will try to reconnect to serversCount servers. - // It will do that for MaxReconnects==10 times. - // For a server that has been already tried, it should sleep - // ReconnectWait==100ms. When a server is not running, the connect - // failure on UNIXes should be fast, still account for that. - test("Check wait time for closed cb: "); - testCond(timedWait <= ((opts->maxReconnect * opts->reconnectWait) + serversCount*opts->maxReconnect*50)); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_PingReconnect(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - int serversCount; - int64_t disconnectedAt, reconnectedAt, pingCycle; - struct threadArg arg; - -#if _WIN32 - test("Skip when running on Windows: "); - testCond(true); - return; -#endif - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.control = 9; - - serversCount = sizeof(testServers) / sizeof(char*); - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetNoRandomize(opts, true)); - IFOK(s, natsOptions_SetReconnectWait(opts, 200)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); - IFOK(s, natsOptions_SetPingInterval(opts, 50)); - IFOK(s, natsOptions_SetMaxPingsOut(opts, -1)); - IFOK(s, natsOptions_SetServers(opts, testServers, serversCount)); - IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg)); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg)); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)); - - if (s != NATS_OK) - FAIL("Unable to create options for test ServerOptions"); - - serverPid = _startServer("nats://127.0.0.1:1222", "-p 1222", true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - test("Pings cause reconnects: ") - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.reconnects != 4)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && (arg.reconnects == 4)); - - natsConnection_Destroy(nc); - - for (int i=0; i<(4-1); i++) - { - disconnectedAt = arg.disconnectedAt[i]; - reconnectedAt = arg.reconnectedAt[i]; - - pingCycle = reconnectedAt - disconnectedAt; - if (pingCycle > 2 * opts->pingInterval) - { - s = NATS_ERR; - break; - } - } - - test("Reconnect due to ping cycle correct: "); - testCond(s == NATS_OK); - - // Wait for connection closed before destroying arg. - natsMutex_Lock(arg.m); - while (!arg.closed) - natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); - -} - -static void -test_GetServers(void) -{ - natsStatus s; - natsConnection *conn = NULL; - natsPid s1Pid = NATS_INVALID_PID; - natsPid s2Pid = NATS_INVALID_PID; - natsPid s3Pid = NATS_INVALID_PID; - char **servers = NULL; - int count = 0; - - s1Pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222 -cluster nats://127.0.0.1:5222 -cluster_name abc", true); - CHECK_SERVER_STARTED(s1Pid); - - s2Pid = _startServer("nats://127.0.0.1:4223", "-a 127.0.0.1 -p 4223 -cluster nats://127.0.0.1:5223 -cluster_name abc -routes nats://127.0.0.1:5222", true); - if (s2Pid == NATS_INVALID_PID) - _stopServer(s1Pid); - CHECK_SERVER_STARTED(s2Pid); - - s3Pid = _startServer("nats://127.0.0.1:4224", "-a 127.0.0.1 -p 4224 -cluster nats://127.0.0.1:5224 -cluster_name abc -routes nats://127.0.0.1:5222", true); - if (s3Pid == NATS_INVALID_PID) - { - _stopServer(s1Pid); - _stopServer(s2Pid); - } - CHECK_SERVER_STARTED(s3Pid); - - test("Get Servers: "); - s = natsConnection_ConnectTo(&conn, "nats://127.0.0.1:4222"); - IFOK(s, natsConnection_GetServers(conn, &servers, &count)); - if (s == NATS_OK) - { - int i; - - // Be tolerant that if we were to connect to an older - // server, we would just get 1 url back. - if ((count != 1) && (count != 3)) - s = nats_setError(NATS_ERR, "Unexpected number of servers: %d instead of 1 or 3", count); - - for (i=0; (s == NATS_OK) && (i < count); i++) - { - if ((strcmp(servers[i], "nats://127.0.0.1:4222") != 0) - && (strcmp(servers[i], "nats://127.0.0.1:4223") != 0) - && (strcmp(servers[i], "nats://127.0.0.1:4224") != 0)) - { - s = nats_setError(NATS_ERR, "Unexpected server URL: %s", servers[i]); - } - } - - for (i=0; i 1) - s = nats_setError(NATS_ERR, "Unexpected number of servers: %d instead of 1 or 0", count); - - for (i=0; (s == NATS_OK) && (i < count); i++) - { - if (strcmp(servers[i], "nats://127.0.0.1:4223") != 0) - s = nats_setError(NATS_ERR, "Unexpected server URL: %s", servers[i]); - } - - for (i=0; im); - arg->sum++; - natsCondition_Signal(arg->c); - natsMutex_Unlock(arg->m); -} - -static void -test_DiscoveredServersCb(void) -{ - natsStatus s; - natsConnection *conn = NULL; - natsPid s1Pid = NATS_INVALID_PID; - natsPid s2Pid = NATS_INVALID_PID; - natsPid s3Pid = NATS_INVALID_PID; - natsOptions *opts = NULL; - struct threadArg arg; - int invoked = 0; - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222")); - IFOK(s, natsOptions_SetDiscoveredServersCB(opts, _discoveredServersCb, &arg)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - s1Pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222 -cluster nats-route://127.0.0.1:5222 -cluster_name abc", true); - CHECK_SERVER_STARTED(s1Pid); - - s2Pid = _startServer("nats://127.0.0.1:4223", "-a 127.0.0.1 -p 4223 -cluster nats-route://127.0.0.1:5223 -cluster_name abc -routes nats-route://127.0.0.1:5222", true); - if (s2Pid == NATS_INVALID_PID) - _stopServer(s1Pid); - CHECK_SERVER_STARTED(s2Pid); - - test("DiscoveredServersCb not triggered on initial connect: "); - s = natsConnection_Connect(&conn, opts); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.sum == 0)) - s = natsCondition_TimedWait(arg.c, arg.m, 500); - invoked = arg.sum; - natsMutex_Unlock(arg.m); - testCond((s == NATS_TIMEOUT) && (invoked == 0)); - s = NATS_OK; - - s3Pid = _startServer("nats://127.0.0.1:4224", "-a 127.0.0.1 -p 4224 -cluster nats-route://127.0.0.1:5224 -cluster_name abc -routes nats-route://127.0.0.1:5222", true); - if (s3Pid == NATS_INVALID_PID) - { - _stopServer(s1Pid); - _stopServer(s2Pid); - } - CHECK_SERVER_STARTED(s3Pid); - - test("DiscoveredServersCb triggered on new server joining the cluster: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.sum == 0)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - invoked = arg.sum; - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && (invoked == 1)); - - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - _stopServer(s3Pid); - _stopServer(s2Pid); - _stopServer(s1Pid); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_IgnoreDiscoveredServers(void) -{ - natsStatus s; - natsConnection *conn = NULL; - natsPid s1Pid = NATS_INVALID_PID; - natsPid s2Pid = NATS_INVALID_PID; - natsOptions *opts = NULL; - struct threadArg arg; - int invoked = 0; - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222")); - IFOK(s, natsOptions_SetIgnoreDiscoveredServers(opts, true)); - IFOK(s, natsOptions_SetDiscoveredServersCB(opts, _discoveredServersCb, &arg)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - s1Pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222 -cluster nats-route://127.0.0.1:5222 -cluster_name abc", true); - CHECK_SERVER_STARTED(s1Pid); - - test("Connect: "); - s = natsConnection_Connect(&conn, opts); - testCond(s == NATS_OK); - - test("Start new server: "); - s2Pid = _startServer("nats://127.0.0.1:4223", "-a 127.0.0.1 -p 4223 -cluster nats-route://127.0.0.1:5223 -cluster_name abc -routes nats-route://127.0.0.1:5222", true); - CHECK_SERVER_STARTED(s2Pid); - testCond(true); - - test("Check discovered ignored: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.sum == 0)) - s = natsCondition_TimedWait(arg.c, arg.m, 1000); - invoked = arg.sum; - natsMutex_Unlock(arg.m); - testCond((s == NATS_TIMEOUT) && (invoked == 0)); - nats_clearLastError(); - - test("Check server pool: "); - natsConn_Lock(conn); - s = (natsSrvPool_GetSize(conn->srvPool) == 1 ? NATS_OK : NATS_ERR); - natsConn_Unlock(conn); - testCond(s == NATS_OK); - - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - _stopServer(s2Pid); - _stopServer(s1Pid); - - _destroyDefaultThreadArgs(&arg); -} - -static void -_serverSendsINFOAfterPONG(void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - natsStatus s = NATS_OK; - natsSock sock = NATS_SOCK_INVALID; - natsSockCtx ctx; - - memset(&ctx, 0, sizeof(natsSockCtx)); - - // We will hand run a fake server that will send an INFO protocol - // right after sending the initial PONG. - - s = _startMockupServer(&sock, "127.0.0.1", "4222"); - - natsMutex_Lock(arg->m); - arg->status = s; - arg->done = true; - natsCondition_Signal(arg->c); - natsMutex_Unlock(arg->m); - - if ((s == NATS_OK) - && (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID) - || (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK))) - { - s = NATS_SYS_ERROR; - } - if (s == NATS_OK) - { - const char* info = "INFO {}\r\n"; - - s = natsSock_WriteFully(&ctx, info, (int) strlen(info)); - } - if (s == NATS_OK) - { - char buffer[1024]; - - memset(buffer, 0, sizeof(buffer)); - - // Read connect and ping commands sent from the client - s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer)); - IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer))); - } - // Send PONG + INFO - if (s == NATS_OK) - { - char buffer[1024]; - - snprintf(buffer, sizeof(buffer), "PONG\r\nINFO {\"connect_urls\":[\"127.0.0.1:4222\",\"me:1\"]}\r\n"); - - s = natsSock_WriteFully(&ctx, buffer, (int) strlen(buffer)); - } - - // Wait for a signal from the client thread. - natsMutex_Lock(arg->m); - while (!arg->closed) - natsCondition_Wait(arg->c, arg->m); - arg->status = s; - natsMutex_Unlock(arg->m); - - natsSock_Close(ctx.fd); - natsSock_Close(sock); -} - -static void -test_ReceiveINFORightAfterFirstPONG(void) -{ - natsStatus s = NATS_OK; - natsThread *t = NULL; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222")); - IFOK(s, natsOptions_SetAllowReconnect(opts, false)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - test("Verify that INFO right after PONG is ok: "); - - s = natsThread_Create(&t, _serverSendsINFOAfterPONG, (void*) &arg); - if (s == NATS_OK) - { - natsMutex_Lock(arg.m); - while (!arg.done) - natsCondition_Wait(arg.c, arg.m); - s = arg.status; - natsMutex_Unlock(arg.m); - } - IFOK(s, natsConnection_Connect(&nc, opts)); - if (s == NATS_OK) - { - int i, j; - char **servers = NULL; - int serversCount = 0; - bool ok = false; - - for (i = 0; i < 100; i++) - { - s = natsConnection_GetDiscoveredServers(nc, &servers, &serversCount); - if (s != NATS_OK) - break; - - ok = ((serversCount == 1) - && (strcmp(servers[0], "nats://me:1") == 0)); - - for (j = 0; j < serversCount; j++) - free(servers[j]); - free(servers); - - if (ok) - break; - - nats_Sleep(15); - s = NATS_ERR; - } - } - if (t != NULL) - { - natsMutex_Lock(arg.m); - arg.closed = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - natsThread_Join(t); - natsThread_Destroy(t); - - natsMutex_Lock(arg.m); - if ((s == NATS_OK) && (arg.status != NATS_OK)) - s = arg.status; - natsMutex_Unlock(arg.m); - } - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); -} - -static void -test_ServerPoolUpdatedOnClusterUpdate(void) -{ - natsStatus s; - natsConnection *conn = NULL; - natsPid s1Pid = NATS_INVALID_PID; - natsPid s2Pid = NATS_INVALID_PID; - natsPid s3Pid = NATS_INVALID_PID; - natsOptions *opts = NULL; - struct threadArg arg; - int invoked = 0; - bool restartS2 = false; - - if (!serverVersionAtLeast(1,0,7)) - { - char txt[200]; - - snprintf(txt, sizeof(txt), "Skipping since requires server version of at least 1.0.7, got %s: ", serverVersion); - test(txt); - testCond(true); - return; - } - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - opts = _createReconnectOptions(); - if ((opts == NULL) - || (natsOptions_SetURL(opts, "nats://127.0.0.1:4222") != NATS_OK) - || (natsOptions_SetDiscoveredServersCB(opts, _discoveredServersCb, &arg)) - || (natsOptions_SetReconnectedCB(opts, _reconnectedCb, &arg) != NATS_OK) - || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK)) - { - FAIL("Unable to create reconnect options!"); - } - - s1Pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222 -cluster nats://127.0.0.1:6222 -cluster_name abc -routes nats://127.0.0.1:6223,nats://127.0.0.1:6224", true); - CHECK_SERVER_STARTED(s1Pid); - - test("Connect ok: "); - s = natsConnection_Connect(&conn, opts); - testCond(s == NATS_OK); - - s2Pid = _startServer("nats://127.0.0.1:4223", "-a 127.0.0.1 -p 4223 -cluster nats://127.0.0.1:6223 -cluster_name abc -routes nats://127.0.0.1:6222,nats://127.0.0.1:6224", true); - if (s2Pid == NATS_INVALID_PID) - _stopServer(s1Pid); - CHECK_SERVER_STARTED(s2Pid); - - test("DiscoveredServersCb triggered: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.sum == 0)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - invoked = arg.sum; - arg.sum = 0; - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && (invoked == 1)); - - { - const char *urls[] = {"127.0.0.1:4222", "127.0.0.1:4223"}; - - test("Check pool: "); - s = _checkPool(conn, (char**)urls, (int)(sizeof(urls)/sizeof(char*))); - testCond(s == NATS_OK); - } - - s3Pid = _startServer("nats://127.0.0.1:4224", "-a 127.0.0.1 -p 4224 -cluster nats://127.0.0.1:6224 -cluster_name abc -routes nats://127.0.0.1:6222,nats://127.0.0.1:6223", true); - if (s3Pid == NATS_INVALID_PID) - { - _stopServer(s1Pid); - _stopServer(s2Pid); - } - CHECK_SERVER_STARTED(s3Pid); - - test("DiscoveredServersCb triggered: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.sum == 0)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - invoked = arg.sum; - arg.sum = 0; - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && (invoked == 1)); - - { - const char *urls[] = {"127.0.0.1:4222", "127.0.0.1:4223", "127.0.0.1:4224"}; - test("Check pool: "); - s = _checkPool(conn, (char**)urls, (int)(sizeof(urls)/sizeof(char*))); - testCond(s == NATS_OK); - } - - // Stop s1. Since this was passed to the Connect() call, this one should - // still be present. - _stopServer(s1Pid); - s1Pid = NATS_INVALID_PID; - - test("Wait for reconnect: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.reconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - arg.reconnected = false; - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - { - const char *urls[] = {"127.0.0.1:4222", "127.0.0.1:4223", "127.0.0.1:4224"}; - - test("Check pool: "); - s = _checkPool(conn, (char**)urls, (int)(sizeof(urls)/sizeof(char*))); - testCond(s == NATS_OK); - } - - { - const char *urls[] = {"127.0.0.1:4222", ""}; - int port = 0; - - // Check the server we reconnected to. - natsMutex_Lock(conn->mu); - port = conn->cur->url->port; - natsMutex_Unlock(conn->mu); - - if (port == 4223) - { - urls[1] = "127.0.0.1:4224"; - _stopServer(s2Pid); - s2Pid = NATS_INVALID_PID; - restartS2 = true; - } - else - { - urls[1] = "127.0.0.1:4223"; - _stopServer(s3Pid); - s3Pid = NATS_INVALID_PID; - } - - test("Wait for reconnect: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.reconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - arg.reconnected = false; - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - // The implicit server that we just shutdown should have been removed from the pool - if (s == NATS_OK) - { - test("Check pool: "); - s = _checkPool(conn, (char**)urls, (int)(sizeof(urls)/sizeof(char*))); - testCond(s == NATS_OK); - } - } - - { - const char *urls[] = {"127.0.0.1:4222", "127.0.0.1:4223", "127.0.0.1:4224"}; - - if (restartS2) - { - s2Pid = _startServer("nats://127.0.0.1:4223", "-a 127.0.0.1 -p 4223 -cluster nats://127.0.0.1:6223 -cluster_name abc -routes nats://127.0.0.1:6222,nats://127.0.0.1:6224", true); - if (s2Pid == NATS_INVALID_PID) - _stopServer(s3Pid); - CHECK_SERVER_STARTED(s2Pid); - } - else - { - s3Pid = _startServer("nats://127.0.0.1:4224", "-a 127.0.0.1 -p 4224 -cluster nats://127.0.0.1:6224 -cluster_name abc -routes nats://127.0.0.1:6222,nats://127.0.0.1:6223", true); - if (s3Pid == NATS_INVALID_PID) - _stopServer(s2Pid); - CHECK_SERVER_STARTED(s3Pid); - } - // Since this is not a "new" server, the DiscoveredServersCB won't be invoked. - - // Checking the pool may fail for a while. - test("Check pool: "); - s = _checkPool(conn, (char**)urls, (int)(sizeof(urls)/sizeof(char*))); - testCond(s == NATS_OK); - } - - natsConnection_Close(conn); - _waitForConnClosed(&arg); - natsConnection_Destroy(conn); - conn = NULL; - - // Restart s1 - s1Pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222 -cluster nats://127.0.0.1:6222 -cluster_name abc -routes nats://127.0.0.1:6223,nats://127.0.0.1:6224", true); - if (s1Pid == NATS_INVALID_PID) - { - _stopServer(s2Pid); - _stopServer(s3Pid); - } - CHECK_SERVER_STARTED(s1Pid); - - // We should have all 3 servers running now... - test("Connect ok: "); - s = natsConnection_Connect(&conn, opts); - testCond(s == NATS_OK); - - { - int i; - natsSrv *srvrs[3]; - - test("Server pool size should be 3: "); - natsMutex_Lock(conn->mu); - s = (conn->srvPool->size == 3 ? NATS_OK : NATS_ERR); - natsMutex_Unlock(conn->mu); - testCond(s == NATS_OK); - - // Save references to servers from pool - natsMutex_Lock(conn->mu); - for (i=0; i<3; i++) - srvrs[i] = conn->srvPool->srvrs[i]; - natsMutex_Unlock(conn->mu); - - for (i=0; (s == NATS_OK) && (i<9); i++) - { - natsMutex_Lock(conn->mu); - natsSock_Shutdown(conn->sockCtx.fd); - natsMutex_Unlock(conn->mu); - - test("Wait for reconnect: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.reconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - arg.reconnected = false; - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - } - - { - int j; - - natsMutex_Lock(conn->mu); - test("Server pool size should be 3: "); - s = (conn->srvPool->size == 3 ? NATS_OK : NATS_ERR); - natsMutex_Unlock(conn->mu); - testCond(s == NATS_OK); - - test("Servers in pool have not been replaced: "); - natsMutex_Lock(conn->mu); - for (i=0; (s == NATS_OK) && (i<3); i++) - { - natsSrv *srv = conn->srvPool->srvrs[i]; - - s = NATS_ERR; - for (j=0; j<3; j++) - { - if (srvrs[j] == srv) - { - s = NATS_OK; - break; - } - } - } - natsMutex_Unlock(conn->mu); - testCond(s == NATS_OK); - } - - natsConnection_Close(conn); - _waitForConnClosed(&arg); - } - - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - _stopServer(s3Pid); - _stopServer(s2Pid); - _stopServer(s1Pid); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_ReconnectJitter(void) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid pid = NATS_INVALID_PID; - int64_t start = 0; - int64_t dur = 0; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - test("Default jitter values: "); - natsOptions_lock(opts); - s = (((opts->reconnectJitter == NATS_OPTS_DEFAULT_RECONNECT_JITTER) - && (opts->reconnectJitterTLS == NATS_OPTS_DEFAULT_RECONNECT_JITTER_TLS)) ? NATS_OK : NATS_ERR); - natsOptions_unlock(opts); - testCond(s == NATS_OK); - - s = natsOptions_SetURL(opts, "nats://127.0.0.1:4222"); - IFOK(s, natsOptions_SetMaxReconnect(opts, -1)); - IFOK(s, natsOptions_SetReconnectWait(opts, 50)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 500, 0)); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg)); - - pid = _startServer("nats://127.0.0.1:4222", "-p 4222", true); - CHECK_SERVER_STARTED(pid); - - test("Connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - // Shutdown server - _stopServer(pid); - pid = NATS_INVALID_PID; - - // Wait for several iterations of failed attempts and make - // sure that overall wait time is a bit more than just - // the number of reconnect attempts. - start = nats_Now(); - nats_Sleep(400); - pid = _startServer("nats://127.0.0.1:4222", "-p 4222", true); - CHECK_SERVER_STARTED(pid); - - test("Check jitter: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.reconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - if (s == NATS_OK) - dur = nats_Now() - start; - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && (dur >=500)); - - natsConnection_Destroy(nc); - nc = NULL; - - // Use a long reconnect wait - s = natsOptions_SetReconnectWait(opts, 10*60*1000); // 10 minutes - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - test("Connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - // Cause a disconnect - _stopServer(pid); - pid = NATS_INVALID_PID; - // Wait a bit for the reconnect loop to go into wait mode. - nats_Sleep(50); - pid = _startServer("nats://127.0.0.1:4222", "-p 4222", true); - CHECK_SERVER_STARTED(pid); - // Now close and expect the reconnect thread to return.. - natsConnection_Close(nc); - - test("Wait for closed: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Check reconnect thread done: "); - natsConn_Lock(nc); - s = (nc->reconnectThread == NULL ? NATS_OK : NATS_ERR); - natsConn_Unlock(nc); - testCond(s == NATS_OK) - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - _stopServer(pid); - _destroyDefaultThreadArgs(&arg); -} - -static int64_t -_customReconnectDelayCB(natsConnection *nc, int attempts, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - int64_t delay = 0; - - natsMutex_Lock(arg->m); - if (attempts != arg->control) - { - arg->status = NATS_ERR; - natsCondition_Signal(arg->c); - } - else - { - arg->control++; - if (attempts <= 4) - delay = 100; - else - natsConnection_Close(nc); - } - natsMutex_Unlock(arg->m); - return delay; -} - -static void -test_CustomReconnectDelay(void) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid pid = NATS_INVALID_PID; - int64_t start = 0; - int64_t dur = 0; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222")); -#if _WIN32 - IFOK(s, natsOptions_SetTimeout(opts, 100)); -#endif - IFOK(s, natsOptions_SetMaxReconnect(opts, -1)); - IFOK(s, natsOptions_SetCustomReconnectDelay(opts, _customReconnectDelayCB, (void*) &arg)); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - arg.control = 1; - arg.status = NATS_OK; - - pid = _startServer("nats://127.0.0.1:4222", "-p 4222", true); - CHECK_SERVER_STARTED(pid); - - test("Connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - // Cause disconnect - _stopServer(pid); - pid = NATS_INVALID_PID; - - // We should be trying to reconnect 4 times - start = nats_Now(); - - // Wait on error or completion of test. - test("Check custom delay cb: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed && (arg.status == NATS_OK)) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - IFOK(s, arg.status); - if (s == NATS_OK) - dur = nats_Now()-start; - natsMutex_Unlock(arg.m); -#if _WIN32 - testCond((s == NATS_OK) && (dur <= 1000)); -#else - testCond((s == NATS_OK) && (dur <= 600)); -#endif - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); -} - -static void -_lameDuckDiscoveredCb(natsConnection *nc, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - natsStatus s; - char **servers = NULL; - int count = 0; - - natsMutex_Lock(arg->m); - s = natsConnection_GetDiscoveredServers(nc, &servers, &count); - if (s == NATS_OK) - { - int i; - - if ((count != 1) || (strcmp(servers[0], "nats://127.0.0.1:1234") != 0)) - arg->status = NATS_ERR; - - for (i=0; idone = true; - natsCondition_Signal(arg->c); - natsMutex_Unlock(arg->m); -} - -static void -_lameDuckCb(natsConnection *nc, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - // Use this flag to indicate that LDM cb was invoked. - arg->disconnected = true; - natsCondition_Signal(arg->c); - natsMutex_Unlock(arg->m); -} - -static void -_lameDuckMockupServerThread(void *closure) -{ - natsStatus s = NATS_OK; - natsSock sock = NATS_SOCK_INVALID; - struct threadArg *arg = (struct threadArg*) closure; - char buffer[1024]; - const char *ldm[] = {"INFO {\"ldm\":true}\r\n", "INFO {\"connect_urls\":[\"127.0.0.1:1234\"],\"ldm\":true}\r\n"}; - natsSockCtx ctx; - int i; - - memset(&ctx, 0, sizeof(natsSockCtx)); - - s = _startMockupServer(&sock, "127.0.0.1", "4222"); - natsMutex_Lock(arg->m); - arg->status = s; - natsCondition_Signal(arg->c); - natsMutex_Unlock(arg->m); - - for (i=0; (s == NATS_OK) && (i<2); i++) - { - if (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID) - || (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK)) - { - s = NATS_SYS_ERROR; - } - if (s == NATS_OK) - { - const char *info = "INFO {\"server_id\":\"foobar\"}\r\n"; - - // Send INFO. - s = natsSock_WriteFully(&ctx, info, (int) strlen(info)); - if (s == NATS_OK) - { - memset(buffer, 0, sizeof(buffer)); - - // Read connect and ping commands sent from the client - s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer)); - IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer))); - } - // Send PONG - IFOK(s, natsSock_WriteFully(&ctx,_PONG_PROTO_, _PONG_PROTO_LEN_)); - if (s == NATS_OK) - { - // Wait a bit and then send a INFO with LDM - nats_Sleep(100); - s = natsSock_WriteFully(&ctx, ldm[i], (int) strlen(ldm[i])); - // Wait for client to close - if (s == NATS_OK) - natsSock_ReadLine(&ctx, buffer, sizeof(buffer)); - } - natsSock_Close(ctx.fd); - } - } - - natsSock_Close(sock); -} - -static void -test_LameDuckMode(void) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsThread *t = NULL; - int i; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222")); - IFOK(s, natsOptions_SetMaxReconnect(opts, -1)); - IFOK(s, natsOptions_SetDiscoveredServersCB(opts, _lameDuckDiscoveredCb, (void*) &arg)); - IFOK(s, natsOptions_SetLameDuckModeCB(opts, _lameDuckCb, (void*) &arg)); - // IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - // Set this to error, the mock server should set it to OK - // if it can start successfully. - arg.status = NATS_ERR; - s = natsThread_Create(&t, _lameDuckMockupServerThread, (void*) &arg); - if (s == NATS_OK) - { - // Wait for server to be ready - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - } - - for (i=0; i<2; i++) - { - test("Connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - test("Lame duck callback invoked: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.disconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - if (i == 0) - { - test("Discovered not invoked: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 200); - if ((arg.status == NATS_OK) && (s == NATS_TIMEOUT)) - s = NATS_OK; - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - } - else - { - test("Discovered servers ok: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - IFOK(s, arg.status); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - } - - natsConnection_Destroy(nc); - nc = NULL; - } - - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); -} - -static void -test_Version(void) -{ - const char *str = NULL; - - test("Compatibility: "); - testCond(nats_CheckCompatibility() == true); - - test("Version string: "); - str = nats_GetVersion(); - testCond((str != NULL) - && (strcmp(str, LIB_NATS_VERSION_STRING) == 0)); - - test("Version number: "); - testCond(nats_GetVersionNumber() == LIB_NATS_VERSION_NUMBER); -} - -static void -test_VersionMatchesTag(void) -{ - natsStatus s = NATS_OK; - const char *tag; - - tag = getenv("TRAVIS_TAG"); - if ((tag == NULL) || (tag[0] == '\0')) - { - test("Skipping test since no tag detected: "); - testCond(true); - return; - } - test("Check tag and version match: "); - // We expect a tag of the form vX.Y.Z. If that's not the case, - // we need someone to have a look. So fail if first letter is not - // a `v` - if (tag[0] != 'v') - s = NATS_ERR; - else - { - // Strip the `v` from the tag for the version comparison. - s = (strcmp(nats_GetVersion(), tag+1) == 0 ? NATS_OK : NATS_ERR); - } - testCond(s == NATS_OK); -} - -static void -_openCloseAndWaitMsgCB(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - nats_Sleep(300); - natsMsg_Destroy(msg); - natsMutex_Lock(arg->m); - arg->results[0]++; - natsMutex_Unlock(arg->m); -} - -static void -_openCloseAndWaitConnClosedCB(natsConnection *nc, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - arg->sum++; - natsMutex_Unlock(arg->m); -} - -static void -_openCloseAndWaitCloseFromThread(void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - arg->status = nats_CloseAndWait(0); - arg->done = true; - natsCondition_Signal(arg->c); - natsMutex_Unlock(arg->m); -} - -static void -_openCloseAndWaitThread(void *closure) -{ - nats_Sleep(300); - natsLib_Release(); -} - -static void -test_OpenCloseAndWait(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts= NULL; - natsSubscription *sub = NULL; - natsPid pid = NATS_INVALID_PID; - int i; - natsThread *t = NULL; - struct threadArg arg; - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - if (_createDefaultThreadArgsForCbTests(&arg) != NATS_OK) - FAIL("Unable to setup test"); - - // First close the library since it is opened in main - test("Close to prepare for test: "); - s = nats_CloseAndWait(0); - testCond(s == NATS_OK); - - test("Open/Close in loop: "); - for (i=0;i<2;i++) - { - s = nats_Open(-1); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetClosedCB(opts, _openCloseAndWaitConnClosedCB, (void*)&arg)); - IFOK(s, natsConnection_Connect(&nc, opts)); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _openCloseAndWaitMsgCB, (void*)&arg)); - IFOK(s, natsConnection_PublishString(nc, "foo", "hello")); - IFOK(s, natsConnection_Flush(nc)); - if (s == NATS_OK) - { - for (;;) - { - natsMutex_Lock(arg.m); - if (arg.results[0] == (i+1)) - { - natsMutex_Unlock(arg.m); - break; - } - natsMutex_Unlock(arg.m); - nats_Sleep(100); - } - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - nats_CloseAndWait(0); - } - } - testCond(s == NATS_OK); - - test("Check async cb count: "); - natsMutex_Lock(arg.m); - testCond(arg.sum == 2); - natsMutex_Unlock(arg.m); - - test("Check msgs count: "); - natsMutex_Lock(arg.m); - testCond(arg.results[0] == 2); - natsMutex_Unlock(arg.m); - - test("Close while not opened returns error: "); - s = nats_CloseAndWait(0); - testCond(s == NATS_NOT_INITIALIZED); - - // Re-open for rest of test - nats_Open(-1); - - test("Check Close from thread returns error: "); - s = natsThread_Create(&t, _openCloseAndWaitCloseFromThread, (void*)&arg); - if (s == NATS_OK) - { - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - s = (arg.status == NATS_ILLEGAL_STATE ? NATS_OK : NATS_ERR); - natsMutex_Unlock(arg.m); - - natsThread_Join(t); - natsThread_Destroy(t); - t = NULL; - } - testCond(s == NATS_OK); - - test("No timeout: "); - natsLib_Retain(); - s = natsThread_Create(&t, _openCloseAndWaitThread, NULL); - IFOK(s, nats_CloseAndWait(0)); - testCond(s == NATS_OK); - - natsThread_Join(t); - natsThread_Destroy(t); - t = NULL; - nats_Open(-1); - - test("Timeout: "); - natsLib_Retain(); - s = natsThread_Create(&t, _openCloseAndWaitThread, NULL); - IFOK(s, nats_CloseAndWait(100)); - testCond(s == NATS_TIMEOUT); - - // Now wait for thread to exit - natsThread_Join(t); - natsThread_Destroy(t); - - _destroyDefaultThreadArgs(&arg); - _stopServer(pid); -} - -static void -_testGetLastErrInThread(void *arg) -{ - natsStatus getLastErrSts; - natsOptions *opts = NULL; - const char *getLastErr = NULL; - - test("Check that new thread has get last err clear: "); - getLastErr = nats_GetLastError(&getLastErrSts); - testCond((getLastErr == NULL) && (getLastErrSts == NATS_OK)); - - natsOptions_Destroy(opts); -} - -static void -test_GetLastError(void) -{ - natsStatus s, getLastErrSts; - natsOptions *opts = NULL; - const char *getLastErr = NULL; - natsThread *t = NULL; - char stackBuf[256]; - FILE *stackFile = NULL; - - stackBuf[0] = '\0'; - - test("Check GetLastError returns proper status: "); - s = natsOptions_SetAllowReconnect(NULL, false); - - getLastErr = nats_GetLastError(&getLastErrSts); - - testCond((s == getLastErrSts) - && (getLastErr != NULL) - && (strstr(getLastErr, "Invalid") != NULL)); - - test("Check GetLastErrorStack with invalid args: "); - s = nats_GetLastErrorStack(NULL, 10); - if (s != NATS_OK) - s = nats_GetLastErrorStack(stackBuf, 0); - testCond(s == NATS_INVALID_ARG); - - test("Check GetLastErrorStack returns proper insufficient buffer: "); - s = nats_GetLastErrorStack(stackBuf, 10); - testCond(s == NATS_INSUFFICIENT_BUFFER); - - test("Check GetLastErrorStack: "); - s = nats_GetLastErrorStack(stackBuf, sizeof(stackBuf)); - testCond((s == NATS_OK) - && (strlen(stackBuf) > 0) - && (strstr(stackBuf, "natsOptions_SetAllowReconnect") != NULL)); - - test("Check PrintStack: "); - stackBuf[0] = '\0'; - stackFile = fopen("stack.txt", "w"); - if (stackFile == NULL) - FAIL("Unable to create a file for print stack test"); - - s = NATS_OK; - nats_PrintLastErrorStack(stackFile); - fclose(stackFile); - stackFile = fopen("stack.txt", "r"); - s = (fgets(stackBuf, sizeof(stackBuf), stackFile) == NULL ? NATS_ERR : NATS_OK); - if ((s == NATS_OK) - && ((strlen(stackBuf) == 0) - || (strstr(stackBuf, "Invalid Argument") == NULL))) - { - s = NATS_ERR; - } - s = (fgets(stackBuf, sizeof(stackBuf), stackFile) == NULL ? NATS_ERR : NATS_OK); - if (s == NATS_OK) - s = (fgets(stackBuf, sizeof(stackBuf), stackFile) == NULL ? NATS_ERR : NATS_OK); - if ((s == NATS_OK) - && ((strlen(stackBuf) == 0) - || (strstr(stackBuf, "natsOptions_SetAllowReconnect") == NULL))) - { - s = NATS_ERR; - } - testCond(s == NATS_OK); - fclose(stackFile); - remove("stack.txt"); - - test("Check the error not cleared until next error occurs: "); - s = natsOptions_Create(&opts); - - getLastErr = nats_GetLastError(&getLastErrSts); - - testCond((s == NATS_OK) - && (getLastErrSts != NATS_OK) - && (getLastErr != NULL) - && (strstr(getLastErr, "Invalid") != NULL)); - - s = natsThread_Create(&t, _testGetLastErrInThread, NULL); - if (s == NATS_OK) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - - natsOptions_Destroy(opts); - - nats_clearLastError(); - stackBuf[0] = '\0'; - - test("Check stack not updated when asked: "); - nats_doNotUpdateErrStack(true); - s = natsConnection_Publish(NULL, NULL, NULL, 0); - nats_GetLastErrorStack(stackBuf, sizeof(stackBuf)); - testCond((s != NATS_OK) - && (stackBuf[0] == '\0')); - - test("Check call reentrant: "); - nats_doNotUpdateErrStack(true); - nats_doNotUpdateErrStack(false); - s = natsConnection_Publish(NULL, NULL, NULL, 0); - nats_GetLastErrorStack(stackBuf, sizeof(stackBuf)); - testCond((s != NATS_OK) - && (stackBuf[0] == '\0')); - - nats_doNotUpdateErrStack(false); - - test("Check stack updates again: "); - s = natsConnection_Publish(NULL, NULL, NULL, 0); - nats_GetLastErrorStack(stackBuf, sizeof(stackBuf)); - testCond((s != NATS_OK) - && (stackBuf[0] != '\0')); - - nats_clearLastError(); -} - -static void -test_StaleConnection(void) -{ - natsStatus s = NATS_OK; - natsSock sock = NATS_SOCK_INVALID; - natsThread *t = NULL; - struct threadArg arg; - natsSockCtx ctx; - int i; - const char *stale_conn_err = "-ERR 'Stale Connection'\r\n"; - - memset(&ctx, 0, sizeof(natsSockCtx)); - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&(arg.opts))); - IFOK(s, natsOptions_SetReconnectWait(arg.opts, 20)); - IFOK(s, natsOptions_SetReconnectJitter(arg.opts, 0, 0)); - IFOK(s, natsOptions_SetMaxReconnect(arg.opts, 100)); - IFOK(s, natsOptions_SetDisconnectedCB(arg.opts, _disconnectedCb, &arg)); - IFOK(s, natsOptions_SetReconnectedCB(arg.opts, _reconnectedCb, &arg)); - IFOK(s, natsOptions_SetClosedCB(arg.opts, _closedCb, &arg)); - if (s != NATS_OK) - FAIL("@@ Unable to setup test!"); - - arg.control = 5; - - test("Behavior of connection on Stale Connection: ") - - s = _startMockupServer(&sock, "localhost", "4222"); - - // Start the thread that will try to connect to our server... - IFOK(s, natsThread_Create(&t, _connectToMockupServer, (void*) &arg)); - - for (i = 0; (i < 2) && (s == NATS_OK); i++) - { - if (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID) - || (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK)) - { - s = NATS_SYS_ERROR; - } - if (s == NATS_OK) - { - char info[1024]; - - strncpy(info, - "INFO {\"server_id\":\"foobar\",\"version\":\"latest\",\"go\":\"latest\",\"host\":\"localhost\",\"port\":4222,\"auth_required\":false,\"tls_required\":false,\"max_payload\":1048576}\r\n", - sizeof(info)); - - // Send INFO. - s = natsSock_WriteFully(&ctx, info, (int) strlen(info)); - if (s == NATS_OK) - { - char buffer[1024]; - - memset(buffer, 0, sizeof(buffer)); - - // Read connect and ping commands sent from the client - s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer)); - IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer))); - } - // Send PONG - IFOK(s, natsSock_WriteFully(&ctx, _PONG_PROTO_, _PONG_PROTO_LEN_)); - - if ((s == NATS_OK) && (i == 0)) - { - // Wait a tiny, and simulate a Stale Connection - nats_Sleep(50); - s = natsSock_WriteFully(&ctx, stale_conn_err, - (int)strlen(stale_conn_err)); - - // The client should try to reconnect. When getting the - // disconnected callback, wait for the disconnect cb - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !(arg.disconnected)) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - natsMutex_Unlock(arg.m); - } - else if (s == NATS_OK) - { - natsMutex_Lock(arg.m); - // We should check on !arg.closed here, but unfortunately, - // on Windows, the thread calling natsConnection_Close() - // would be blocked waiting for the readLoop thread to - // exit, which it would not because fd shutdown is not - // causing a socket error if the server is not closing - // its side. - while ((s != NATS_TIMEOUT) && (arg.disconnects != 2)) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - natsMutex_Unlock(arg.m); - } - - natsSock_Close(ctx.fd); - } - } - natsSock_Close(sock); - - // Wait for the client to finish. - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - - natsMutex_Lock(arg.m); - IFOK(s, arg.status); - if (s == NATS_OK) - { - // Wait for closed CB - while ((s != NATS_TIMEOUT) && !(arg.closed)) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - IFOK(s, arg.status); - } - natsMutex_Unlock(arg.m); - - testCond((s == NATS_OK) - && (arg.disconnects == 2) - && (arg.reconnects == 1) - && (arg.closed)); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_ServerErrorClosesConnection(void) -{ - natsStatus s = NATS_OK; - natsSock sock = NATS_SOCK_INVALID; - natsThread *t = NULL; - struct threadArg arg; - natsSockCtx ctx; - - memset(&ctx, 0, sizeof(natsSockCtx)); - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&(arg.opts))); - IFOK(s, natsOptions_SetReconnectWait(arg.opts, 20)); - IFOK(s, natsOptions_SetReconnectJitter(arg.opts, 0, 0)); - IFOK(s, natsOptions_SetMaxReconnect(arg.opts, 100)); - IFOK(s, natsOptions_SetDisconnectedCB(arg.opts, _disconnectedCb, &arg)); - IFOK(s, natsOptions_SetReconnectedCB(arg.opts, _reconnectedCb, &arg)); - IFOK(s, natsOptions_SetClosedCB(arg.opts, _closedCb, &arg)); - if (s != NATS_OK) - FAIL("@@ Unable to setup test!"); - - arg.control = 6; - arg.string = "Any Error"; - - test("Behavior of connection on Server Error: ") - - s = _startMockupServer(&sock, "localhost", "4222"); - - // Start the thread that will try to connect to our server... - IFOK(s, natsThread_Create(&t, _connectToMockupServer, (void*) &arg)); - - if ((s == NATS_OK) - && (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID) - || (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK))) - { - s = NATS_SYS_ERROR; - } - if (s == NATS_OK) - { - char info[1024]; - - strncpy(info, - "INFO {\"server_id\":\"foobar\",\"version\":\"latest\",\"go\":\"latest\",\"host\":\"localhost\",\"port\":4222,\"auth_required\":false,\"tls_required\":false,\"max_payload\":1048576}\r\n", - sizeof(info)); - - // Send INFO. - s = natsSock_WriteFully(&ctx, info, (int) strlen(info)); - if (s == NATS_OK) - { - char buffer[1024]; - - memset(buffer, 0, sizeof(buffer)); - - // Read connect and ping commands sent from the client - s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer)); - IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer))); - } - // Send PONG - IFOK(s, natsSock_WriteFully(&ctx, _PONG_PROTO_, _PONG_PROTO_LEN_)); - - if (s == NATS_OK) - { - // Wait a tiny, and simulate an error sent by the server - nats_Sleep(50); - - snprintf(info, sizeof(info), "-ERR '%s'\r\n", arg.string); - s = natsSock_WriteFully(&ctx, info, (int)strlen(info)); - } - - // Wait for the client to be done. - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !(arg.closed)) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - natsMutex_Unlock(arg.m); - - natsSock_Close(ctx.fd); - } - natsSock_Close(sock); - - // Wait for the client to finish. - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - - natsMutex_Lock(arg.m); - if (s == NATS_OK) - { - // Wait for closed CB - while ((s != NATS_TIMEOUT) && !(arg.closed)) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - IFOK(s, arg.status); - } - natsMutex_Unlock(arg.m); - - testCond((s == NATS_ERR) - && (arg.disconnects == 1) - && (arg.reconnects == 0) - && (arg.closed)); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_NoEcho(void) -{ - natsStatus s; - natsOptions *opts = NULL; - natsConnection *conn = NULL; - natsSubscription *sub = NULL; - natsPid pid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222")); - IFOK(s, natsOptions_SetNoEcho(opts, true)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - arg.control = 0; - arg.string = "test"; - test("Setup: "); - s = natsConnection_Connect(&conn, opts); - IFOK(s, natsConnection_Subscribe(&sub, conn, "foo", _recvTestString, (void*)&arg)); - IFOK(s, natsConnection_PublishString(conn, "foo", arg.string)); - IFOK(s, natsConnection_Flush(conn)); - // repeat - IFOK(s, natsConnection_Flush(conn)); - testCond(s == NATS_OK); - - test("NoEcho: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.msgReceived) - s = natsCondition_TimedWait(arg.c, arg.m, 500); - natsMutex_Unlock(arg.m); - // Message should not be received. - testCond(s == NATS_TIMEOUT); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(pid); -} - -static void -_startMockupServerThread(void *closure) -{ - natsStatus s = NATS_OK; - natsSock sock = NATS_SOCK_INVALID; - struct threadArg *arg = (struct threadArg*) closure; - natsSockCtx ctx; - testCheckInfoCB checkInfoCB = NULL; - - memset(&ctx, 0, sizeof(natsSockCtx)); - - s = _startMockupServer(&sock, "localhost", "4222"); - natsMutex_Lock(arg->m); - arg->status = s; - natsCondition_Signal(arg->c); - checkInfoCB = arg->checkInfoCB; - natsMutex_Unlock(arg->m); - - if (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID) - || (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK)) - { - s = NATS_SYS_ERROR; - } - if (s == NATS_OK) - { - char info[1024]; - - snprintf(info, sizeof(info), "%s", arg->string); - - // Send INFO. - s = natsSock_WriteFully(&ctx, info, (int) strlen(info)); - if (s == NATS_OK) - { - char buffer[1024]; - - memset(buffer, 0, sizeof(buffer)); - - // Read connect and ping commands sent from the client - s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer)); - if ((s == NATS_OK) && (checkInfoCB != NULL)) - s = (*checkInfoCB)(buffer); - IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer))); - } - // Send PONG - IFOK(s, natsSock_WriteFully(&ctx, _PONG_PROTO_, _PONG_PROTO_LEN_)); - - if (s == NATS_OK) - { - // Wait for client to tell us it is done - natsMutex_Lock(arg->m); - while ((s != NATS_TIMEOUT) && !(arg->done)) - s = natsCondition_TimedWait(arg->c, arg->m, 10000); - natsMutex_Unlock(arg->m); - } - natsSock_Close(ctx.fd); - } - - natsSock_Close(sock); -} - -static void -test_NoEchoOldServer(void) -{ - natsStatus s; - natsConnection *conn = NULL; - natsOptions *opts = NULL; - natsThread *t = NULL; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetNoEcho(opts, true)); - if (s == NATS_OK) - { - // Set this to error, the mock server should set it to OK - // if it can start successfully. - arg.status = NATS_ERR; - arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n"; - s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg); - } - if (s == NATS_OK) - { - // Wait for server to be ready - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - } - if (s != NATS_OK) - { - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - FAIL("Unable to setup test"); - } - - test("NoEcho with old server: "); - s = natsConnection_Connect(&conn, opts); - testCond(s == NATS_NO_SERVER_SUPPORT); - - // Notify mock server we are done - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - natsOptions_Destroy(opts); - natsThread_Join(t); - natsThread_Destroy(t); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_DrainSub(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub= NULL; - natsSubscription *sub2 = NULL; - natsSubscription *sub3 = NULL; - natsOptions *opts = NULL; - natsPid pid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - arg.control = 8; - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Connect and create subscriptions: "); - s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4222"); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg)); - IFOK(s, natsConnection_SubscribeSync(&sub2, nc, "foo")); - IFOK(s, natsConnection_SubscribeSync(&sub3, nc, "foo")); - IFOK(s, natsSubscription_AutoUnsubscribe(sub3, 2)); - testCond(s == NATS_OK); - - test("WaitForDrainCompletion returns invalid arg: "); - s = natsSubscription_WaitForDrainCompletion(NULL, 2000); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("WaitForDrainCompletion returns illegal state: "); - s = natsSubscription_WaitForDrainCompletion(sub, 2000); - testCond(s == NATS_ILLEGAL_STATE); - nats_clearLastError(); - - test("Send 2 messages: "); - s = natsConnection_PublishString(nc, "foo", "msg"); - IFOK(s, natsConnection_PublishString(nc, "foo", "msg")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Call Drain on subscription: "); - // Pass 0 or negative value to represent "for ever" timeout. - s = natsSubscription_DrainTimeout(sub, -1); - testCond(s == NATS_OK); - - test("Call Drain a second time is ok: "); - s = natsSubscription_Drain(sub); - testCond(s == NATS_OK); - - test("Drain sync subs: "); - s = natsSubscription_Drain(sub2); - IFOK(s, natsSubscription_Drain(sub3)); - testCond(s == NATS_OK); - - test("Wait for Drain times out: "); - s = natsSubscription_WaitForDrainCompletion(sub, 10); - if (s == NATS_TIMEOUT) - s = natsSubscription_WaitForDrainCompletion(sub2, 10); - testCond(s == NATS_TIMEOUT); - nats_clearLastError(); - - test("Send 1 more message: "); - s = natsConnection_PublishString(nc, "foo", "msg"); - testCond(s == NATS_OK); - - // Unblock the callback. - natsMutex_Lock(arg.m); - arg.closed = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - test("Wait for Drain to complete: "); - s = natsSubscription_WaitForDrainCompletion(sub, -1); - testCond(s == NATS_OK); - - // Wait a bit and make sure that we did not receive the 3rd msg - test("Third message not received: "); - nats_Sleep(100); - natsMutex_Lock(arg.m); - if ((s == NATS_OK) && (arg.sum != 2)) - s = NATS_ERR; - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Drain on closed sub fails: "); - s = natsSubscription_Drain(sub); - testCond(s == NATS_INVALID_SUBSCRIPTION); - nats_clearLastError(); - - test("Consume sync messages: "); - { - natsMsg *msg = NULL; - int i; - - s = NATS_OK; - for (i=0; (s == NATS_OK) && (i<2); i++) - { - s = natsSubscription_NextMsg(&msg, sub2, 2000); - natsMsg_Destroy(msg); - msg = NULL; - } - for (i=0; (s == NATS_OK) && (i<2); i++) - { - s = natsSubscription_NextMsg(&msg, sub3, 2000); - natsMsg_Destroy(msg); - msg = NULL; - } - } - testCond(s == NATS_OK); - - test("Wait for drain to complete: "); - s = natsSubscription_WaitForDrainCompletion(sub2, 1000); - IFOK(s, natsSubscription_WaitForDrainCompletion(sub3, 1000)); - testCond(s == NATS_OK); - - // Repeat async test with auto-unsubscribe - natsSubscription_Destroy(sub); - sub = NULL; - natsMutex_Lock(arg.m); - arg.sum = 0; - arg.closed = false; - natsMutex_Unlock(arg.m); - - test("Async sub with auto-unsub: "); - s = natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*)&arg); - IFOK(s, natsSubscription_AutoUnsubscribe(sub, 2)); - testCond(s == NATS_OK); - - test("Send 2 messages: "); - s = natsConnection_PublishString(nc, "foo", "msg"); - IFOK(s, natsConnection_PublishString(nc, "foo", "msg")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Check drain status with invalid arg: "); - s = natsSubscription_DrainCompletionStatus(NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Check drain status fails: "); - s = natsSubscription_DrainCompletionStatus(sub); - testCond(s == NATS_ILLEGAL_STATE); - - test("Call Drain on subscription: "); - s = natsSubscription_Drain(sub); - testCond(s == NATS_OK); - - test("Send 1 more message: "); - s = natsConnection_PublishString(nc, "foo", "msg"); - testCond(s == NATS_OK); - - // Unblock the callback. - natsMutex_Lock(arg.m); - arg.closed = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - test("Wait for Drain to complete: "); - s = natsSubscription_WaitForDrainCompletion(sub, -1); - testCond(s == NATS_OK); - - test("Check drain status: "); - s = natsSubscription_DrainCompletionStatus(sub); - testCond(s == NATS_OK); - - // Wait a bit and make sure that we did not receive the 3rd msg - test("Third message not received: "); - nats_Sleep(100); - natsMutex_Lock(arg.m); - s = (arg.sum == 2 ? NATS_OK : NATS_ERR); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - // Close connection - natsConnection_Close(nc); - - test("Drain on closed conn fails: "); - s = natsSubscription_Drain(sub); - if (s == NATS_CONNECTION_CLOSED) - s = natsSubscription_Drain(sub2); - if (s == NATS_CONNECTION_CLOSED) - s = natsSubscription_Drain(sub3); - testCond(s == NATS_CONNECTION_CLOSED); - - natsSubscription_Destroy(sub); - sub = NULL; - natsSubscription_Destroy(sub2); - sub2 = NULL; - natsSubscription_Destroy(sub3); - sub3 = NULL; - natsConnection_Destroy(nc); - nc = NULL; - - natsMutex_Lock(arg.m); - arg.sum = 0; - arg.closed = false; - natsMutex_Unlock(arg.m); - - test("Connect and create sub: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg)); - IFOK(s, natsConnection_Connect(&nc, opts)); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg)); - testCond(s == NATS_OK); - - test("Send 2 messages: "); - s = natsConnection_PublishString(nc, "foo", "msg"); - IFOK(s, natsConnection_PublishString(nc, "foo", "msg")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Disconnect: "); - _stopServer(pid); - testCond(s == NATS_OK); - - test("Wait for disconnect: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.disconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Call Drain on subscriptions: "); - s = natsSubscription_DrainTimeout(sub, 500); - testCond(s == NATS_OK); - - // Unblock the callback. - natsMutex_Lock(arg.m); - arg.closed = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - test("Wait for Drain to complete: "); - s = natsSubscription_WaitForDrainCompletion(sub, -1); - testCond(s == NATS_OK); - - test("Check drain status: "); - s = natsSubscription_DrainCompletionStatus(sub); - // Since the flush has timed-out, this is what the status will be. - testCond(s == NATS_TIMEOUT); - s = NATS_OK; - - natsSubscription_Destroy(sub); - sub = NULL; - natsConnection_Destroy(nc); - nc = NULL; - natsOptions_Destroy(opts); - opts = NULL; - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - natsMutex_Lock(arg.m); - arg.sum = 0; - arg.closed = false; - natsMutex_Unlock(arg.m); - - test("Create options for global msg delivery: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_UseGlobalMessageDelivery(opts, true)); - testCond(s == NATS_OK); - - test("Connect and create sub: "); - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*)&arg)); - IFOK(s, natsConnection_Subscribe(&sub2, nc, "foo", _recvTestString, (void*)&arg)); - IFOK(s, natsSubscription_AutoUnsubscribe(sub, 2)); - testCond(s == NATS_OK); - - test("Send 2 messages: "); - s = natsConnection_PublishString(nc, "foo", "msg"); - IFOK(s, natsConnection_PublishString(nc, "foo", "msg")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Call Drain on subscriptions: "); - s = natsSubscription_Drain(sub); - IFOK(s, natsSubscription_Drain(sub2)); - testCond(s == NATS_OK); - - // Unblock the callback. - nats_Sleep(250); - natsMutex_Lock(arg.m); - arg.closed = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - test("Wait for Drain to complete: "); - s = natsSubscription_WaitForDrainCompletion(sub, -1); - testCond(s == NATS_OK); - - test("Check drain status: "); - s = natsSubscription_DrainCompletionStatus(sub); - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - natsSubscription_Destroy(sub2); - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(pid); -} - -static void -_msgCBForDrainSubTest(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMsg_Destroy(msg); - natsMutex_Lock(arg->m); - if (++arg->sum == 1) - { - // Signal that we got the message - natsCondition_Signal(arg->c); - - // Wait for main thread to switch to drain mode - while (!arg->done) - natsCondition_Wait(arg->c, arg->m); - - // Report unsubscribe status - arg->status = natsSubscription_Unsubscribe(sub); - } - natsMutex_Unlock(arg->m); -} - -static void -_drainSubCompleteCB(void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - if (arg->sum == 1) - { - arg->closed = true; - natsCondition_Signal(arg->c); - } - natsMutex_Unlock(arg->m); -} - -static void -test_DrainSubStops(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub= NULL; - natsPid pid = NATS_INVALID_PID; - struct threadArg arg; - int i; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Connect and create subscriptions: "); - s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4222"); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _msgCBForDrainSubTest, (void*) &arg)); - IFOK(s, natsSubscription_SetOnCompleteCB(sub, _drainSubCompleteCB, (void*) &arg)) - testCond(s == NATS_OK); - - test("Send 10 messages: "); - for (i=0; (s == NATS_OK) && (i<10); i++) - s = natsConnection_PublishString(nc, "foo", "msg"); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Wait for 1st message to be received: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.sum != 1)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Drain subscription: "); - s = natsSubscription_Drain(sub); - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Wait for drain completion: "); - s = natsSubscription_WaitForDrainCompletion(sub, 0); - testCond(s == NATS_OK); - - test("Check drain status: "); - s = natsSubscription_DrainCompletionStatus(sub); - testCond(s == NATS_INVALID_SUBSCRIPTION); - - test("Check that drain stopped on unsubscribe: "); - natsMutex_Lock(arg.m); - s = NATS_OK; - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - // Get the status from unsubscribe in the callback - IFOK(s, arg.status); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - sub = NULL; - - natsMutex_Lock(arg.m); - arg.done = false; - arg.closed = false; - arg.sum = 0; - natsMutex_Unlock(arg.m); - - test("Create subscription: "); - s = natsConnection_Subscribe(&sub, nc, "foo", _msgCBForDrainSubTest, (void*) &arg); - IFOK(s, natsSubscription_SetOnCompleteCB(sub, _drainSubCompleteCB, (void*) &arg)) - testCond(s == NATS_OK); - - test("Send 10 messages: "); - for (i=0; (s == NATS_OK) && (i<10); i++) - s = natsConnection_PublishString(nc, "foo", "msg"); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Wait for 1st message to be received: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.sum != 1)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Drain connection: "); - s = natsConnection_Drain(nc); - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Wait for Drain to complete: "); - s = natsSubscription_WaitForDrainCompletion(sub, 0); - testCond(s == NATS_OK); - - test("Check that drain stopped on unsubscribe: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - // Get the status from unsubscribe in the callback - IFOK(s, arg.status); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(pid); -} - -static void -test_DrainSubRaceOnAutoUnsub(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub= NULL; - natsPid pid = NATS_INVALID_PID; - int i; - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Connect: "); - s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4222"); - testCond(s == NATS_OK); - - testDrainAutoUnsubRace = true; - - test("Drain with auto-unsub race: "); - for (i=0; (s == NATS_OK) && (i<500); i++) - { - s = natsConnection_Subscribe(&sub, nc, "foo", _dummyMsgHandler, NULL); - IFOK(s, natsSubscription_AutoUnsubscribe(sub, 1)); - IFOK(s, natsConnection_PublishString(nc, "foo", "msg")); - nats_Sleep(1); - if (s == NATS_OK) - { - s = natsSubscription_Drain(sub); - // Here, it is possible that the subscription is already - // invalid. In which case, don't attempt to wait for completion. - if (s == NATS_INVALID_SUBSCRIPTION) - { - s = NATS_OK; - nats_clearLastError(); - } - else - { - IFOK(s, natsSubscription_WaitForDrainCompletion(sub, -1)); - IFOK(s, natsSubscription_DrainCompletionStatus(sub)); - } - } - natsSubscription_Destroy(sub); - sub = NULL; - } - testCond(s == NATS_OK); - - testDrainAutoUnsubRace = false; - - natsConnection_Destroy(nc); - _stopServer(pid); -} - -static void -test_DrainSubNotResentOnReconnect(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsOptions *opts = NULL; - natsPid pid = NATS_INVALID_PID; - natsStatistics stats; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg)); - IFOK(s, natsOptions_SetMaxReconnect(opts, -1)); - IFOK(s, natsOptions_SetReconnectWait(opts, 10)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - natsMutex_Lock(arg.m); - arg.control = 8; - natsMutex_Unlock(arg.m); - - test("Connect and create subscription: "); - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg)); - testCond(s == NATS_OK); - - test("Send 1 message: "); - s = natsConnection_PublishString(nc, "foo", "msg"); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Wait for message to be received: "); - nats_Sleep(150); - testCond(s == NATS_OK); - - test("Drain subscription: "); - s = natsSubscription_Drain(sub); - testCond(s == NATS_OK); - - test("Disconnect: "); - nats_Sleep(250); - _stopServer(pid); - testCond(s == NATS_OK); - - test("Restart server: "); - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - testCond(s == NATS_OK); - - test("Wait for reconnect: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.reconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Release cb: "); - natsMutex_Lock(arg.m); - arg.closed = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Wait for drain completion: "); - s = natsSubscription_WaitForDrainCompletion(sub, 0); - testCond(s == NATS_OK); - - test("Check drain status: "); - s = natsSubscription_DrainCompletionStatus(sub); - testCond(s == NATS_OK); - - test("Send new message: "); - s = natsConnection_PublishString(nc, "foo", "msg"); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Msg not received by connection: "); - s = natsConnection_GetStats(nc, &stats); - IFOK(s, (stats.inMsgs == 1 ? NATS_OK : NATS_ERR)); - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - _stopServer(pid); -} - -static void -_drainConnBarSub(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - - natsMutex_Lock(args->m); - args->results[1]++; - if (args->results[1] == args->results[0]) - { - args->done = true; - natsCondition_Broadcast(args->c); - } - natsMutex_Unlock(args->m); - natsMsg_Destroy(msg); -} - -static void -_drainConnFooSub(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - - nats_Sleep(10); - natsMutex_Lock(args->m); - args->sum++; - if (args->status == NATS_OK) - args->status = natsConnection_PublishString(nc, natsMsg_GetReply(msg), "Stop bugging me"); - natsMutex_Unlock(args->m); - natsMsg_Destroy(msg); -} - -static void -_drainConnErrHandler(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - const char *lastError = NULL; - natsStatus s = NATS_OK; - - natsMutex_Lock(args->m); - // Since error handler is async, there is no guarantee that - // natsConnection_GetLastError() returns the error we expect. - // Only check if the error matches `err`. - if (err == NATS_TIMEOUT) - { - s = natsConnection_GetLastError(nc, &lastError); - if ((s != NATS_TIMEOUT) - || (strstr(lastError, args->string) != NULL)) - { - args->done = true; - natsCondition_Broadcast(args->c); - } - } - natsMutex_Unlock(args->m); -} - -static void -test_DrainConn(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsConnection *nc2 = NULL; - natsSubscription *sub2 = NULL; - natsSubscription *sub3 = NULL; - natsPid pid = NATS_INVALID_PID; - int expected= 50; - int64_t start = 0; - int i; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*)&arg)); - IFOK(s, natsOptions_SetErrorHandler(opts, _drainConnErrHandler, (void*) &arg)); - if (s != NATS_OK) - { - _destroyDefaultThreadArgs(&arg); - natsOptions_Destroy(opts); - FAIL("Unable to setup test"); - } - - arg.results[0] = expected; - arg.string = "Drain error"; - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Drain with invalid NULL: "); - s = natsConnection_Drain(NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - test("Drain with no sub/pub ok: "); - s = natsConnection_Drain(nc); - testCond(s == NATS_OK); - - test("Closed CB invoked: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - arg.closed = false; - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("No async error reported: "); - natsMutex_Lock(arg.m); - s = (arg.done == false ? NATS_OK : NATS_ERR); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - nc = NULL; - - test("Connect: "); - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_ConnectTo(&nc2, "nats://127.0.0.1:4222")); - testCond(s == NATS_OK); - - test("Create listener for responses on bar: "); - s = natsConnection_Subscribe(&sub2, nc2, "bar", _drainConnBarSub, (void*) &arg); - testCond(s == NATS_OK); - - test("Create slow consumer for responder: "); - s = natsConnection_Subscribe(&sub, nc, "foo", _drainConnFooSub, (void*) &arg); - testCond(s == NATS_OK); - - test("Send messages: "); - for (i=0; (s == NATS_OK) && (i= 10*expected ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - - test("Received all messages: "); - natsMutex_Lock(arg.m); - s = ((arg.sum == expected) ? NATS_OK : NATS_ERR); - if (s == NATS_OK) - s = arg.status; - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("All responses received: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - if ((s == NATS_OK) && (arg.results[1] != expected)) - s = NATS_ERR; - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Check sub drain status: "); - s = natsSubscription_DrainCompletionStatus(sub); - testCond(s == NATS_OK); - - test("Check IsDraining: "); - s = (natsConnection_IsDraining(nc) ? NATS_ERR : NATS_OK); - testCond(s == NATS_OK); - - test("Drain after closed should fail: "); - s = natsConnection_DrainTimeout(nc, 1); - testCond(s == NATS_CONNECTION_CLOSED); - nats_clearLastError(); - - natsSubscription_Destroy(sub); - sub = NULL; - natsConnection_Destroy(nc); - nc = NULL; - - natsMutex_Lock(arg.m); - arg.done = false; - arg.sum = 0; - arg.string = "timeout"; - natsMutex_Unlock(arg.m); - - test("Connect and subscribe: "); - s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _drainConnFooSub, (void*) &arg)); - testCond(s == NATS_OK); - - test("Publish: "); - for (i=0;(s==NATS_OK) && i<25;i++) - s = natsConnection_PublishString(nc, "foo", "hello"); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Drain timeout: "); - s = natsConnection_DrainTimeout(nc, 10); - if (s == NATS_OK) - { - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 1000); - natsMutex_Unlock(arg.m); - } - testCond(s == NATS_OK); - - test("Wait for subscription to drain: "); - s = natsSubscription_WaitForDrainCompletion(sub, -1); - testCond(s == NATS_OK); - - test("Check sub drain status: "); - s = natsSubscription_DrainCompletionStatus(sub); - // Since the connection drain timed-out, we should report as a timeout, - // not as the connection closed. - testCond(s == NATS_TIMEOUT); - - natsSubscription_Destroy(sub); - sub = NULL; - natsSubscription_Destroy(sub2); - natsSubscription_Destroy(sub3); - natsConnection_Destroy(nc); - nc = NULL; - natsConnection_Destroy(nc2); - nc2 = NULL; - natsOptions_Destroy(opts); - - natsMutex_Lock(arg.m); - arg.closed = false; - arg.sum = 0; - arg.control= 8; - natsMutex_Unlock(arg.m); - - test("Connect and create sub: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg)); - IFOK(s, natsConnection_ConnectTo(&nc2, NATS_DEFAULT_URL)); - testCond(s == NATS_OK); - - test("Send messages: "); - s = natsConnection_PublishString(nc, "foo", "msg1"); - IFOK(s, natsConnection_PublishString(nc, "foo", "msg2")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Drain: "); - s = natsConnection_DrainTimeout(nc, 10000); - testCond(s == NATS_OK); - - test("Drain sub directly should fail: "); - s = natsSubscription_Drain(sub); - testCond(s == NATS_DRAINING); - nats_clearLastError(); - s = NATS_OK; - - test("Disconnect: "); - _stopServer(pid); - testCond(s == NATS_OK); - - nats_Sleep(100); - - test("Drain while disconnected fails: "); - s = natsConnection_Drain(nc2); - testCond(s == NATS_ILLEGAL_STATE); - nats_clearLastError(); - s = NATS_OK; - - test("Release cb: "); - natsMutex_Lock(arg.m); - arg.closed = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Wait for completion: "); - s = natsSubscription_WaitForDrainCompletion(sub, 1000); - testCond(s == NATS_OK); - - test("Check drain status: "); - s = natsSubscription_DrainCompletionStatus(sub); - testCond(s == NATS_CONNECTION_CLOSED); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - natsConnection_Destroy(nc2); - - // Since the drain timed-out and closed the connection, - // the subscription will be closed but there is no guarantee - // that the callback is not in the middle of execution at that - // time. So to avoid valgrind reports, sleep a bit before - // destroying sub's closure. - nats_Sleep(100); - _destroyDefaultThreadArgs(&arg); -} - -static void -_noDoubleCloseCb(natsConnection *nc, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - arg->sum++; - arg->closed = true; - natsCondition_Signal(arg->c); - natsMutex_Unlock(arg->m); -} - -static void -_noDoubleCbSubCb(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - nats_Sleep(200); - natsMsg_Destroy(msg); -} - -static void -test_NoDoubleConnClosedOnDrain(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsPid pid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetClosedCB(opts, _noDoubleCloseCb, (void*)&arg)); - if (s != NATS_OK) - { - _destroyDefaultThreadArgs(&arg); - natsOptions_Destroy(opts); - FAIL("Unable to setup test"); - } - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - test("Create sub: "); - s = natsConnection_Subscribe(&sub, nc, "foo", _noDoubleCbSubCb, (void*)&arg); - testCond(s == NATS_OK); - - test("Publish msg: "); - s = natsConnection_PublishString(nc, "foo", "hello"); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Drain: "); - s = natsConnection_Drain(nc); - testCond(s == NATS_OK); - - nats_Sleep(200); - test("Closing: "); - natsConnection_Close(nc); - testCond(s == NATS_OK); - - test("Wait for close CB: "); - s = _waitForConnClosed(&arg); - testCond(s == NATS_OK); - - // Now wait for connection close and make sure it was invoked once. - test("Check closeCb invoked once: ") - nats_Sleep(300); - natsMutex_Lock(arg.m); - s = (arg.sum == 1 ? NATS_OK : NATS_ERR); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - _stopServer(pid); -} - -static void -test_GetClientID(void) -{ - natsStatus s; - natsPid pid1 = NATS_INVALID_PID; - natsPid pid2 = NATS_INVALID_PID; - natsConnection *nc1 = NULL; - natsConnection *nc2 = NULL; - natsOptions *opts = NULL; - uint64_t cid = 0; - uint64_t newcid = 0; - natsThread *t = NULL; - struct threadArg arg; - - if (!serverVersionAtLeast(1,2,0)) - { - char txt[200]; - - snprintf(txt, sizeof(txt), "Skipping since requires server version of at least 1.2.0, got %s: ", serverVersion); - test(txt); - testCond(true); - return; - } - pid1 = _startServer("nats://127.0.0.1:4222", "-cluster nats://127.0.0.1:6222 -cluster_name abc", true); - CHECK_SERVER_STARTED(pid1); - - test("Create nc1: "); - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222")); - IFOK(s, natsOptions_SetReconnectWait(opts, 50)); - IFOK(s, natsOptions_SetTimeout(opts, 250)); - IFOK(s, natsOptions_SetDiscoveredServersCB(opts, _discoveredServersCb, (void*)&arg)); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*)&arg)); - IFOK(s, natsConnection_Connect(&nc1, opts)); - testCond(s == NATS_OK); - - test("GetClientID for nc1: "); - s = natsConnection_GetClientID(nc1, &cid); - testCond((s == NATS_OK) && (cid != 0)); - - test("Wait for discovered callback: "); - pid2 = _startServer("nats://127.0.0.1:4223", "-p 4223 -cluster nats://127.0.0.1:6223 -cluster_name abc -routes nats://127.0.0.1:6222", true); - CHECK_SERVER_STARTED(pid2); - - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.sum != 1)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - s = (arg.sum == 1 ? NATS_OK: NATS_ERR); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Check CID same: "); - s = natsConnection_GetClientID(nc1, &newcid); - testCond((s == NATS_OK) && (newcid == cid)); - - test("Connect to server 2: "); - s = natsConnection_ConnectTo(&nc2, "nats://127.0.0.1:4223"); - testCond(s == NATS_OK); - - test("Stop server 1: "); - _stopServer(pid1); - testCond(s == NATS_OK); - - test("Wait for nc1 to reconnect: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.reconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 4000); - s = (arg.reconnected ? NATS_OK : NATS_ERR); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Check CID is different: "); - s = natsConnection_GetClientID(nc1, &newcid); - testCond((s == NATS_OK) && (newcid != cid)); - - // Stop clients and remaining server - natsConnection_Destroy(nc1); - natsConnection_Destroy(nc2); - natsOptions_Destroy(opts); - _stopServer(pid2); - - // Now have dummy server that returns no CID and check we get expected error. - nc1 = NULL; - arg.status = NATS_ERR; - arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n"; - s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg); - if (s == NATS_OK) - { - // Wait for server to be ready - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - s = arg.status; - natsMutex_Unlock(arg.m); - } - if (s != NATS_OK) - { - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - FAIL("Unable to setup test"); - } - - test("CID not supported: "); - s = natsConnection_ConnectTo(&nc1, NATS_DEFAULT_URL); - IFOK(s, natsConnection_GetClientID(nc1, &cid)); - testCond((s == NATS_NO_SERVER_SUPPORT) && (cid == 0)); - - // Notify mock server we are done - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - natsConnection_Destroy(nc1); - - natsThread_Join(t); - natsThread_Destroy(t); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_GetClientIP(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - char *ip = NULL; - natsThread *t = NULL; - struct threadArg arg; - - test("Check server version: "); - if (!serverVersionAtLeast(2,1,6)) - { - char txt[200]; - - snprintf(txt, sizeof(txt), "Skipping since requires server version of at least 2.1.6, got %s: ", serverVersion); - test(txt); - testCond(true); - return; - } - testCond(true); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect: "); - s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4222"); - testCond(s == NATS_OK); - - test("Get client IP - no conn: "); - s = natsConnection_GetClientIP(NULL, &ip); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Get client IP - no ip loc: "); - s = natsConnection_GetClientIP(nc, NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Get client IP: "); - s = natsConnection_GetClientIP(nc, &ip); - testCond((s == NATS_OK) && (strcmp(ip, "127.0.0.1")==0)); - free(ip); - ip = NULL; - - natsConnection_Close(nc); - test("Get client IP after conn closed: "); - s = natsConnection_GetClientIP(nc, &ip); - testCond((s == NATS_CONNECTION_CLOSED) && (ip == NULL)); - nats_clearLastError(); - - natsConnection_Destroy(nc); - nc = NULL; - - _stopServer(serverPid); - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - // Set this to error, the mock server should set it to OK - // if it can start successfully. - arg.status = NATS_ERR; - arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n"; - s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg); - } - if (s == NATS_OK) - { - // Wait for server to be ready - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - } - if (s != NATS_OK) - { - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - _destroyDefaultThreadArgs(&arg); - FAIL("Unable to setup test"); - } - - test("Connect: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - testCond(s == NATS_OK); - - test("Get client IP with old server: "); - s = natsConnection_GetClientIP(nc, &ip); - testCond ((s == NATS_NO_SERVER_SUPPORT) && (ip == NULL)); - nats_clearLastError(); - - // Notify mock server we are done - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - natsConnection_Close(nc); - natsConnection_Destroy(nc); - - natsThread_Join(t); - natsThread_Destroy(t); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_GetRTT(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - int64_t rtt = 0; - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetReconnectWait(opts, 10)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - - test("Get RTT - no conn: "); - s = natsConnection_GetRTT(NULL, &rtt); - testCond(s == NATS_INVALID_ARG); - - test("Get RTT - no rtt loc: "); - s = natsConnection_GetRTT(nc, NULL); - testCond(s == NATS_INVALID_ARG); - - test("Get RTT: "); - s = natsConnection_GetRTT(nc, &rtt); - // Check that it is below 500ms... - testCond((s == NATS_OK) && (rtt/1000000 <= 500)); - - _stopServer(serverPid); - - test("Get RTT while not connected: "); - s = natsConnection_GetRTT(nc, &rtt); - testCond(s == NATS_CONNECTION_DISCONNECTED); - - natsConnection_Close(nc); - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); -} - -static void -test_GetLocalIPAndPort(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid pid = NATS_INVALID_PID; - char *ip = NULL; - int port = 0; - struct threadArg arg; - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Connect: "); - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222")); - IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, &arg)); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - - test("Get Local IP and Port - no conn: "); - s = natsConnection_GetLocalIPAndPort(NULL, &ip, &port); - testCond(s == NATS_INVALID_ARG); - - test("Get Local IP and Port - no ip loc: "); - s = natsConnection_GetLocalIPAndPort(nc, NULL, &port); - testCond(s == NATS_INVALID_ARG); - - test("Get Local IP and Port - no port loc: "); - s = natsConnection_GetLocalIPAndPort(nc, &ip, NULL); - testCond(s == NATS_INVALID_ARG); - - nats_clearLastError(); - test("Get Local IP and Port: "); - s = natsConnection_GetLocalIPAndPort(nc, &ip, &port); - testCond((s == NATS_OK) - && ((ip != NULL) && (strcmp(ip, "127.0.0.1") == 0)) - && (port != 0)); - free(ip); - - test("Wait for disconnect: "); - s = NATS_OK; - _stopServer(pid); - natsMutex_Lock(arg.m); - while ((s == NATS_OK) && !arg.disconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Get Local IP and Port while disconnected: "); - s = natsConnection_GetLocalIPAndPort(nc, &ip, &port); - testCond(s == NATS_CONNECTION_DISCONNECTED); - nats_clearLastError(); - - // Close connection - natsConnection_Close(nc); - test("Get Local IP and Port with closed connection: "); - s = natsConnection_GetLocalIPAndPort(nc, &ip, &port); - testCond(s == NATS_CONNECTION_CLOSED); - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); -} - -static natsStatus -_userJWTCB(char **userJWT, char **customErrTxt, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - if (closure != NULL) - { - bool done = true; - - natsMutex_Lock(arg->m); - if (arg->string != NULL) - *customErrTxt = strdup(arg->string); - else if (arg->nc != NULL) - natsConnection_Destroy(arg->nc); - else - done = false; - natsMutex_Unlock(arg->m); - - if (done) - { - if (*customErrTxt != NULL) - return NATS_ERR; - return NATS_OK; - } - } - - *userJWT = strdup("some user jwt"); - - return NATS_OK; -} - -static natsStatus -_sigCB(char **customErrTxt, unsigned char **psig, int *sigLen, const char* nonce, void *closure) -{ - const unsigned char correctSign[] = { - 155, 157, 8, 183, 93, 154, 78, 7, - 219, 39, 11, 16, 134, 231, 46, 142, - 168, 87, 110, 202, 187, 180, 179, 62, - 49, 255, 225, 74, 48, 80, 176, 111, - 248, 162, 121, 188, 203, 101, 100, 195, - 162, 70, 213, 182, 220, 14, 71, 113, - 93, 239, 141, 131, 66, 190, 237, 127, - 104, 191, 138, 217, 227, 1, 92, 14, - }; - unsigned char *sig = NULL; - - if (closure != NULL) - { - struct threadArg *arg = (struct threadArg*) closure; - bool done = true; - - natsMutex_Lock(arg->m); - if (arg->string != NULL) - *customErrTxt = strdup(arg->string); - else if (arg->nc != NULL) - natsConnection_Destroy(arg->nc); - else - done = false; - natsMutex_Unlock(arg->m); - - if (done) - { - if (*customErrTxt != NULL) - return NATS_ERR; - - return NATS_OK; - } - } - - sig = malloc(NATS_CRYPTO_SIGN_BYTES); - memcpy(sig, correctSign, NATS_CRYPTO_SIGN_BYTES); - *psig = sig; - if (sigLen != NULL) - *sigLen = NATS_CRYPTO_SIGN_BYTES; - - return NATS_OK; -} - -static natsStatus -_checkJWTAndSigCB(char *buffer) -{ - // UserJWT callback should have returned this... - if (strstr(buffer, "some user jwt") == NULL) - return NATS_ERR; - - // The server is sending the nonce "nonce" and we - // use a seed that should have produced a signature - // that converted to base64 should be: - if (strstr(buffer, "m50It12aTgfbJwsQhucujqhXbsq7tLM-Mf_hSjBQsG_4onm8y2Vkw6JG1bbcDkdxXe-Ng0K-7X9ov4rZ4wFcDg") == NULL) - return NATS_ERR; - - return NATS_OK; -} - -static void -test_UserCredsCallbacks(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsOptions *opts2 = NULL; - natsPid pid = NATS_INVALID_PID; - natsThread *t = NULL; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - if (s != NATS_OK) - FAIL("Unable to create options for test UserCredsCallbacks"); - - test("Invalid arg 1: "); - s = natsOptions_SetUserCredentialsCallbacks(NULL, _dummyUserJWTCb, NULL, _dummySigCb, NULL); - testCond(s == NATS_INVALID_ARG); - - test("Invalid arg 2: "); - s = natsOptions_SetUserCredentialsCallbacks(opts, NULL, NULL, _dummySigCb, NULL); - testCond(s == NATS_INVALID_ARG); - - test("Clone: "); - s = natsOptions_SetUserCredentialsCallbacks(opts, _dummyUserJWTCb, (void*) 1, _dummySigCb, (void*) 2); - if (s == NATS_OK) - { - opts2 = natsOptions_clone(opts); - if (opts2 == NULL) - s = NATS_NO_MEMORY; - } - IFOK(s, natsOptions_SetUserCredentialsCallbacks(opts, NULL, NULL, NULL, NULL)); - testCond((s == NATS_OK) - && (opts2->userJWTHandler == _dummyUserJWTCb) - && (opts2->userJWTClosure == (void*) 1) - && (opts2->sigHandler == _dummySigCb) - && (opts2->sigClosure == (void*) 2)); - - natsOptions_Destroy(opts2); - - // We first check that we get error when callbacks do return error. - // For that part, we don't need that the server sends the nonce. - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - // Create a connection. The user JWT callback is going to return - // an error, ensure connection fails. - test("UserJWTCB returns error: "); - natsMutex_Lock(arg.m); - arg.string = "some jwt error"; - arg.nc = NULL; - natsMutex_Unlock(arg.m); - s = natsOptions_SetUserCredentialsCallbacks(opts, _userJWTCB, (void*) &arg, _sigCB, NULL); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond((s == NATS_ERR) - && (strstr(nats_GetLastError(NULL), "some jwt error") != NULL)); - - s = NATS_OK; - nats_clearLastError(); - test("SignatureCB returns error: "); - natsMutex_Lock(arg.m); - arg.string = "some sig error"; - arg.nc = NULL; - natsMutex_Unlock(arg.m); - s = natsOptions_SetUserCredentialsCallbacks(opts, _userJWTCB, NULL, _sigCB, (void*) &arg); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond((s == NATS_ERR) - && (strstr(nats_GetLastError(NULL), "some sig error") != NULL)); - - s = NATS_OK; - nats_clearLastError(); - test("UserJWTCB destroys connection: "); - natsMutex_Lock(arg.m); - arg.string = NULL; - arg.nc = NULL; - arg.closed = false; - natsMutex_Unlock(arg.m); - s = natsOptions_SetUserCredentialsCallbacks(opts, _userJWTCB, (void*) &arg, _sigCB, NULL); - IFOK(s, natsOptions_SetReconnectWait(opts, 100)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)); - IFOK(s, natsConnection_Connect(&nc, opts)); - if (s == NATS_OK) - { - natsMutex_Lock(arg.m); - arg.nc = nc; - natsMutex_Unlock(arg.m); - - _stopServer(pid); - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - } - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (!arg.closed)) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - nc = NULL; - - s = NATS_OK; - nats_clearLastError(); - test("SigCB destroys connection: "); - natsMutex_Lock(arg.m); - arg.string = NULL; - arg.nc = NULL; - arg.closed = false; - natsMutex_Unlock(arg.m); - s = natsOptions_SetUserCredentialsCallbacks(opts, _userJWTCB, NULL, _sigCB, (void*) &arg); - IFOK(s, natsOptions_SetReconnectWait(opts, 100)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)); - IFOK(s, natsConnection_Connect(&nc, opts)); - if (s == NATS_OK) - { - natsMutex_Lock(arg.m); - arg.nc = nc; - natsMutex_Unlock(arg.m); - - _stopServer(pid); - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - } - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (!arg.closed)) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - nc = NULL; - - _stopServer(pid); - - // Start fake server that will send predefined "nonce" so we can check - // that connection is sending appropriate jwt and signature. - - // Set this to error, the mock server should set it to OK - // if it can start successfully. - arg.status = NATS_ERR; - arg.checkInfoCB = _checkJWTAndSigCB; - arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576,\"nonce\":\"nonce\"}\r\n"; - s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg); - if (s == NATS_OK) - { - // Wait for server to be ready - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - } - if (s != NATS_OK) - { - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - FAIL("Unable to setup test"); - } - - test("Connect sends proper JWT and Signature: "); - natsOptions_Destroy(opts); - opts = NULL; - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetUserCredentialsCallbacks(opts, _userJWTCB, NULL, _sigCB, NULL)); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - - // Notify mock server we are done - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - natsThread_Join(t); - natsThread_Destroy(t); - _destroyDefaultThreadArgs(&arg); -} - -static void -test_UserCredsFromMemory(void) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsOptions *opts2 = NULL; - natsPid pid = NATS_INVALID_PID; - natsThread *t = NULL; - struct threadArg arg; - - const char *jwtAndSeed = "-----BEGIN NATS USER JWT----\nsome user jwt\n-----END NATS USER JWT-----\n\n-----BEGIN USER NKEY SEED-----\nSUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\n-----END USER NKEY SEED-----\n"; - const char *jwtWithoutSeed = "-----BEGIN NATS USER JWT----\nsome user jwt\n-----END NATS USER JWT-----\n"; - - s = natsOptions_Create(&opts); - if (s != NATS_OK) - FAIL("Unable to create options for test UserCredsFromFiles"); - - test("Clone: "); - s = natsOptions_SetUserCredentialsFromMemory(opts, jwtAndSeed); - if (s == NATS_OK) - { - opts2 = natsOptions_clone(opts); - if (opts2 == NULL) - s = NATS_NO_MEMORY; - } - IFOK(s, natsOptions_SetUserCredentialsFromMemory(opts, NULL)); - testCond((s == NATS_OK) - && (opts2->userCreds != NULL) - && (opts2->userJWTHandler == natsConn_userCreds) - && (opts2->userJWTClosure == (void*) opts2->userCreds) - && (opts2->sigHandler == natsConn_signatureHandler) - && (opts2->sigClosure == (void*) opts2->userCreds)); - natsOptions_Destroy(opts2); - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("invalidCreds provided: "); - s = natsOptions_SetUserCredentialsFromMemory(opts, "invalidCreds"); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_NOT_FOUND); - - // Use a file that contains no seed - test("jwtAndSeed string has no seed: "); - s = natsOptions_SetUserCredentialsFromMemory(opts, jwtWithoutSeed); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_NOT_FOUND); - - nc = NULL; - _stopServer(pid); - - // Start fake server that will send predefined "nonce" so we can check - // that connection is sending appropriate jwt and signature. - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - // Set this to error, the mock server should set it to OK - // if it can start successfully. - arg.done = false; - arg.status = NATS_ERR; - arg.checkInfoCB = _checkJWTAndSigCB; - arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576,\"nonce\":\"nonce\"}\r\n"; - s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg); - } - if (s == NATS_OK) - { - // Wait for server to be ready - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - } - if (s != NATS_OK) - { - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - FAIL("Unable to setup test"); - } - - s = NATS_OK; - test("Connect with jwtAndSeed string: "); - s = natsOptions_SetUserCredentialsFromMemory(opts, jwtAndSeed); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - - // Notify mock server we are done - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - natsConnection_Destroy(nc); - nc = NULL; - - natsThread_Join(t); - natsThread_Destroy(t); - t = NULL; - - _destroyDefaultThreadArgs(&arg); - - natsOptions_Destroy(opts); -} - -static void -test_UserCredsFromFiles(void) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsOptions *opts2 = NULL; - natsPid pid = NATS_INVALID_PID; - natsThread *t = NULL; - int i; - struct threadArg arg; - const char *ucfn = "user.creds"; - const char *sfn = "seed.txt"; - const char *snhfn = "seednh.txt"; - const char *nusfn = "nouors.txt"; - FILE *f = NULL; - - f = fopen(ucfn, "w"); - if (f == NULL) - s = NATS_ERR; - else - { - int res = fputs("-----BEGIN NATS USER JWT----\nsome user jwt\n-----END NATS USER JWT-----\n\n-----BEGIN USER NKEY SEED-----\nSUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\n-----END USER NKEY SEED-----\n", f); - if (res < 0) - s = NATS_ERR; - - if (fclose(f) != 0) - s = NATS_ERR; - f = NULL; - } - if (s == NATS_OK) - { - f = fopen(sfn, "w"); - if (f == NULL) - s = NATS_ERR; - else - { - int res = fputs("-----BEGIN USER NKEY SEED-----\nSUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\n-----END USER NKEY SEED-----\n", f); - if (res < 0) - s = NATS_ERR; - - if (fclose(f) != 0) - s = NATS_ERR; - f = NULL; - } - } - if (s == NATS_OK) - { - f = fopen(snhfn, "w"); - if (f == NULL) - s = NATS_ERR; - else - { - int res = fputs("This file does not have the proper header\nand also has spaces before the seed:\n \tSUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\nthis should work\n", f); - if (res < 0) - s = NATS_ERR; - - if (fclose(f) != 0) - s = NATS_ERR; - f = NULL; - } - } - if (s == NATS_OK) - { - f = fopen(nusfn, "w"); - if (f == NULL) - s = NATS_ERR; - else - { - int res = fputs("This file does not have a jwt\nnor a valid seed\n", f); - if (res < 0) - s = NATS_ERR; - - if (fclose(f) != 0) - s = NATS_ERR; - f = NULL; - } - } - if (s != NATS_OK) - FAIL("Unable to create creds test files"); - - s = natsOptions_Create(&opts); - if (s != NATS_OK) - FAIL("Unable to create options for test UserCredsFromFiles"); - - test("Invalid arg 1: "); - s = natsOptions_SetUserCredentialsFromFiles(NULL, "foo", "bar"); - testCond(s == NATS_INVALID_ARG); - - test("Invalid arg 2: "); - s = natsOptions_SetUserCredentialsFromFiles(opts, NULL, "bar"); - testCond(s == NATS_INVALID_ARG); - - test("Invalid arg 3: "); - s = natsOptions_SetUserCredentialsFromFiles(opts, "", "bar"); - testCond(s == NATS_INVALID_ARG); - - test("Clone: "); - s = natsOptions_SetUserCredentialsFromFiles(opts, "foo", "bar"); - if (s == NATS_OK) - { - opts2 = natsOptions_clone(opts); - if (opts2 == NULL) - s = NATS_NO_MEMORY; - } - IFOK(s, natsOptions_SetUserCredentialsFromFiles(opts, NULL, NULL)); - testCond((s == NATS_OK) - && (opts2->userCreds != NULL) - && (opts2->userJWTHandler == natsConn_userCreds) - && (opts2->userJWTClosure == (void*) opts2->userCreds) - && (opts2->sigHandler == natsConn_signatureHandler) - && (opts2->sigClosure == (void*) opts2->userCreds)); - natsOptions_Destroy(opts2); - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("UserOrChainedFile not found: "); - s = natsOptions_SetUserCredentialsFromFiles(opts, "userCredsNotFound", NULL); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond((s == NATS_ERR) - && (strstr(nats_GetLastError(NULL), "error opening file 'userCredsNotFound'") != NULL)); - - // Use a file that contains no userJWT.. - test("UserOrChainedFile has no JWT: "); - s = natsOptions_SetUserCredentialsFromFiles(opts, "list.txt", NULL); - IFOK(s, natsConnection_Connect(&nc, opts)); - // Since we return the whole content of the file when we don't find - // the key for the user, but we don't for seed, the error we'll get - // is about no user seed found. - testCond((s == NATS_ERR) - && (strstr(nats_GetLastError(NULL), "no nkey user seed found") != NULL)); - - test("SeedFile not found: "); - s = natsOptions_SetUserCredentialsFromFiles(opts, ucfn, "seedFileNotFound"); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond((s == NATS_ERR) - && (strstr(nats_GetLastError(NULL), "error opening file 'seedFileNotFound'") != NULL)); - - // Use a seed file that contains no seed.. - test("SeedFile has no seed: "); - s = natsOptions_SetUserCredentialsFromFiles(opts, ucfn, "list.txt"); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond((s == NATS_ERR) - && (strstr(nats_GetLastError(NULL), "no nkey user seed found") != NULL)); - - _stopServer(pid); - - for (i=0; i<3; i++) - { - // Start fake server that will send predefined "nonce" so we can check - // that connection is sending appropriate jwt and signature. - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - // Set this to error, the mock server should set it to OK - // if it can start successfully. - arg.done = false; - arg.status = NATS_ERR; - arg.checkInfoCB = _checkJWTAndSigCB; - arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576,\"nonce\":\"nonce\"}\r\n"; - s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg); - } - if (s == NATS_OK) - { - // Wait for server to be ready - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - } - if (s != NATS_OK) - { - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - FAIL("Unable to setup test"); - } - - s = NATS_OK; - if (i == 0) - { - test("Connect with chained file: "); - s = natsOptions_SetUserCredentialsFromFiles(opts, ucfn, NULL); - } - else if (i == 1) - { - test("Connect with user and seed files: "); - s = natsOptions_SetUserCredentialsFromFiles(opts, ucfn, sfn); - } - else if (i == 2) - { - test("Connect with user and seed files (seed does not contain header): "); - s = natsOptions_SetUserCredentialsFromFiles(opts, ucfn, snhfn); - } - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - - // Notify mock server we are done - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - natsConnection_Destroy(nc); - nc = NULL; - - natsThread_Join(t); - natsThread_Destroy(t); - t = NULL; - - _destroyDefaultThreadArgs(&arg); - } - natsOptions_Destroy(opts); - - remove(ucfn); - remove(sfn); - remove(snhfn); - remove(nusfn); -} - -static natsStatus -_checkNKeyAndSig(char *buffer) -{ - // NKey should have been included - if (strstr(buffer, "pubKey") == NULL) - return NATS_ERR; - - // The server is sending the nonce "nonce" and we - // use a seed that should have produced a signature - // that converted to base64 should be: - if (strstr(buffer, "m50It12aTgfbJwsQhucujqhXbsq7tLM-Mf_hSjBQsG_4onm8y2Vkw6JG1bbcDkdxXe-Ng0K-7X9ov4rZ4wFcDg") == NULL) - return NATS_ERR; - - return NATS_OK; -} - -static void -test_NKey(void) -{ - natsStatus s; - natsOptions *opts = NULL; - natsOptions *opts2 = NULL; - natsConnection *nc = NULL; - natsThread *t = NULL; - struct threadArg arg; - - s = natsOptions_Create(&opts); - if (s != NATS_OK) - FAIL("Failed to setup test"); - - test("Invalid arg 1: "); - s = natsOptions_SetNKey(NULL, "pubkey", _dummySigCb, (void*) 1); - testCond(s == NATS_INVALID_ARG); - - test("Invalid arg 2: "); - s = natsOptions_SetNKey(opts, "pubkey", NULL, NULL); - testCond(s == NATS_INVALID_ARG); - - test("Clone: "); - s = natsOptions_SetNKey(opts, "pubkey", _dummySigCb, (void*) 1); - if (s == NATS_OK) - { - opts2 = natsOptions_clone(opts); - if (opts2 == NULL) - s = NATS_NO_MEMORY; - } - IFOK(s, natsOptions_SetNKey(opts, NULL, NULL, NULL)); - testCond((s == NATS_OK) - && (opts2->nkey != NULL) - && (strcmp(opts2->nkey, "pubkey") == 0) - && (opts2->sigHandler == _dummySigCb) - && (opts2->sigClosure == (void*) 1)); - natsOptions_Destroy(opts2); - - test("NKey erase JWT: "); - s = natsOptions_SetUserCredentialsFromFiles(opts, "foo", "bar"); - IFOK(s, natsOptions_SetNKey(opts, "pubkey2", _dummySigCb, (void*) 2)); - testCond((s == NATS_OK) - && (opts->nkey != NULL) - && (strcmp(opts->nkey, "pubkey2") == 0) - && (opts->userJWTHandler == NULL) - && (opts->userJWTClosure == NULL) - && (opts->sigHandler == _dummySigCb) - && (opts->sigClosure == (void*) 2)); - - test("UserCreds erase NKey: "); - s = natsOptions_SetUserCredentialsFromFiles(opts, "foo", "bar"); - testCond((s == NATS_OK) - && (opts->nkey == NULL) - && (opts->userJWTHandler == natsConn_userCreds) - && (opts->userJWTClosure == (void*) opts->userCreds) - && (opts->sigHandler == natsConn_signatureHandler) - && (opts->sigClosure == (void*) opts->userCreds)); - - // Start fake server that will send predefined "nonce" so we can check - // that connection is sending appropriate jwt and signature. - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - // Set this to error, the mock server should set it to OK - // if it can start successfully. - arg.done = false; - arg.status = NATS_ERR; - arg.checkInfoCB = _checkNKeyAndSig; - arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576,\"nonce\":\"nonce\"}\r\n"; - s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg); - } - if (s == NATS_OK) - { - // Wait for server to be ready - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - } - if (s != NATS_OK) - { - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - FAIL("Unable to setup test"); - } - - test("NKey works ok: "); - s = natsOptions_SetNKey(opts, "pubKey", _sigCB, NULL); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - - // Notify mock server we are done - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - natsConnection_Destroy(nc); - nc = NULL; - - natsThread_Join(t); - natsThread_Destroy(t); - t = NULL; - - _destroyDefaultThreadArgs(&arg); - - natsOptions_Destroy(opts); -} - -static natsStatus -_checkNKeyFromSeed(char *buffer) -{ - // NKey should have been included - if (strstr(buffer, "UDXU4RCSJNZOIQHZNWXHXORDPRTGNJAHAHFRGZNEEJCPQTT2M7NLCNF4") == NULL) - return NATS_ERR; - - // The server is sending the nonce "nonce" and we - // use a seed that should have produced a signature - // that converted to base64 should be: - if (strstr(buffer, "AVfpO7Pw3rc56hoO1OJcFxXUCfBmO2qouchBchSlL45Fuur9zS15UzytEI1QC5wwVG7uiHIdqyfmOS6uPrwqCg") == NULL) - return NATS_ERR; - - return NATS_OK; -} - -static void -test_NKeyFromSeed(void) -{ - natsStatus s; - natsOptions *opts = NULL; - natsOptions *opts2 = NULL; - natsConnection *nc = NULL; - natsThread *t = NULL; - FILE *f = NULL; - struct threadArg arg; - - s = natsOptions_Create(&opts); - if (s != NATS_OK) - FAIL("Failed to setup test"); - - test("Invalid arg 1: "); - s = natsOptions_SetNKeyFromSeed(NULL, "pubkey", "seed.file"); - testCond(s == NATS_INVALID_ARG); - - test("Invalid arg 2: "); - s = natsOptions_SetNKeyFromSeed(opts, "pubkey", NULL); - testCond(s == NATS_INVALID_ARG); - - nats_clearLastError(); - - test("Clone: "); - s = natsOptions_SetNKeyFromSeed(opts, "pubkey", "seed.file"); - if (s == NATS_OK) - { - opts2 = natsOptions_clone(opts); - if (opts2 == NULL) - s = NATS_NO_MEMORY; - } - IFOK(s, natsOptions_SetNKeyFromSeed(opts, NULL, NULL)); - testCond((s == NATS_OK) - && (opts2->nkey != NULL) - && (strcmp(opts2->nkey, "pubkey") == 0) - && (opts2->sigHandler == natsConn_signatureHandler) - && (opts2->sigClosure == (void*) opts2->userCreds) - && (opts2->userCreds != NULL) - && (opts2->userCreds->seedFile != NULL) - && (strcmp(opts2->userCreds->seedFile, "seed.file") == 0)); - natsOptions_Destroy(opts2); - - test("NKeyFromSeed erase JWT: "); - s = natsOptions_SetUserCredentialsFromFiles(opts, "foo", "bar"); - IFOK(s, natsOptions_SetNKeyFromSeed(opts, "pubkey2", "seed.file")); - testCond((s == NATS_OK) - && (opts->nkey != NULL) - && (strcmp(opts->nkey, "pubkey2") == 0) - && (opts->userJWTHandler == NULL) - && (opts->userJWTClosure == NULL) - && (opts->sigHandler == natsConn_signatureHandler) - && (opts->sigClosure == (void*) opts->userCreds) - && (opts->userCreds != NULL) - && (opts->userCreds->seedFile != NULL) - && (strcmp(opts->userCreds->seedFile, "seed.file") == 0)); - - test("UserCreds erase NKeyFromSeed: "); - s = natsOptions_SetUserCredentialsFromFiles(opts, "foo", NULL); - testCond((s == NATS_OK) - && (opts->nkey == NULL) - && (opts->userJWTHandler == natsConn_userCreds) - && (opts->userJWTClosure == (void*) opts->userCreds) - && (opts->sigHandler == natsConn_signatureHandler) - && (opts->sigClosure == (void*) opts->userCreds) - && (opts->userCreds != NULL) - && (opts->userCreds->seedFile == NULL)); - - // Start fake server that will send predefined "nonce" so we can check - // that connection is sending appropriate jwt and signature. - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - // Set this to error, the mock server should set it to OK - // if it can start successfully. - arg.done = false; - arg.status = NATS_ERR; - arg.checkInfoCB = _checkNKeyFromSeed; - arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576,\"nonce\":\"nonce\"}\r\n"; - s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg); - } - if (s == NATS_OK) - { - // Wait for server to be ready - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - } - if (s != NATS_OK) - { - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - FAIL("Unable to setup test"); - } - - test("NKeyFromSeed works ok: "); - f = fopen("seed.file", "w"); - if (f == NULL) - s = NATS_ERR; - else - { - fputs("SUACSSL3UAHUDXKFSNVUZRF5UHPMWZ6BFDTJ7M6USDXIEDNPPQYYYCU3VY\n", f); - fclose(f); - f = NULL; - } - IFOK(s, natsOptions_SetNKeyFromSeed(opts, "UDXU4RCSJNZOIQHZNWXHXORDPRTGNJAHAHFRGZNEEJCPQTT2M7NLCNF4", "seed.file")); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - - // Notify mock server we are done - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - natsConnection_Destroy(nc); - nc = NULL; - - natsThread_Join(t); - natsThread_Destroy(t); - t = NULL; - - _destroyDefaultThreadArgs(&arg); - - natsOptions_Destroy(opts); - - remove("seed.file"); -} - -static void -test_ConnSign(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid pid = NATS_INVALID_PID; - const char *ucfn = "user.creds"; - FILE *f = NULL; - unsigned char sig[64]; - const unsigned char expected[] = { - 155, 157, 8, 183, 93, 154, 78, 7, - 219, 39, 11, 16, 134, 231, 46, 142, - 168, 87, 110, 202, 187, 180, 179, 62, - 49, 255, 225, 74, 48, 80, 176, 111, - 248, 162, 121, 188, 203, 101, 100, 195, - 162, 70, 213, 182, 220, 14, 71, 113, - 93, 239, 141, 131, 66, 190, 237, 127, - 104, 191, 138, 217, 227, 1, 92, 14, - }; - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Connect ok: "); - s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4222"); - testCond(s == NATS_OK); - - test("Can't sign without user creds: "); - s = natsConnection_Sign(nc, (const unsigned char*) "payload", 7, sig); - testCond((s == NATS_ERR) - && (nats_GetLastError(NULL) != NULL) - && (strstr(nats_GetLastError(NULL), "unable to sign") != NULL)); - - natsConnection_Destroy(nc); - nc = NULL; - - s = NATS_OK; - test("Set proper option: "); - f = fopen(ucfn, "w"); - if (f == NULL) - s = NATS_ERR; - else - { - int res = fputs("SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\n", f); - if (res < 0) - s = NATS_ERR; - - if (fclose(f) != 0) - s = NATS_ERR; - f = NULL; - } - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetUserCredentialsFromFiles(opts, ucfn, ucfn)); - testCond(s == NATS_OK); - - test("Connect ok: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - test("Sign with NULL message: "); - s = natsConnection_Sign(nc, NULL, 0, sig); - testCond(s == NATS_OK); - - test("Sign message: "); - s = natsConnection_Sign(nc, (const unsigned char*) "nonce", 5, sig); - testCond((s == NATS_OK) - && (sig != NULL) - && (memcmp((void*) sig, expected, sizeof(expected)) == 0)); - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _stopServer(pid); - - remove(ucfn); -} - -static void -test_WriteDeadline(void) -{ - natsStatus s; - natsOptions *opts = NULL; - natsConnection *nc = NULL; - natsThread *t = NULL; - char data[1024] = {0}; - struct threadArg arg; - - test("Create options: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetAllowReconnect(opts, false)); - testCond(s == NATS_OK); - - test("Set invalid write deadline: "); - s = natsOptions_SetWriteDeadline(opts, -1); - testCond(s == NATS_INVALID_ARG); - - test("Start mock server: "); - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - arg.status = NATS_ERR; - arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n"; - s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg); - } - if (s == NATS_OK) - { - // Wait for server to be ready - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - } - testCond(s == NATS_OK); - - test("Write deadline kicks publish out: "); - s = natsOptions_SetIOBufSize(opts, 100); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, &arg)); - IFOK(s, natsOptions_SetWriteDeadline(opts, 1)); - IFOK(s, natsConnection_Connect(&nc, opts)); - while (s == NATS_OK) - s = natsConnection_Publish(nc, "foo", data, sizeof(data)); - testCond(s == NATS_TIMEOUT); - - test("Caused a disconnect: "); - // Since we are not allowing for reconnect, we should - // get the closed CB. - natsMutex_Lock(arg.m); - s = NATS_OK; - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - natsThread_Join(t); - natsThread_Destroy(t); - - _destroyDefaultThreadArgs(&arg); -} - -static void -_publish(void *arg) -{ - natsConnection *nc = (natsConnection*) arg; - char data[1024] = {0}; - int i; - natsStatus s = NATS_OK; - - for (i=0; ((s == NATS_OK) && (i<1000)); i++) - s = natsConnection_Publish(nc, "foo", data, sizeof(data)); - -} - -static void -test_NoPartialOnReconnect(void) -{ - natsStatus s; - natsOptions *opts = NULL; - natsConnection *nc = NULL; - natsThread *t = NULL; - natsThread *t2 = NULL; - natsPid pid = NATS_INVALID_PID; - struct threadArg arg; - const char *servers[2] = {"nats://127.0.0.1:4222", "nats://127.0.0.1:4223"}; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("unable to setup test"); - - test("Create options: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetAllowReconnect(opts, true)); - IFOK(s, natsOptions_SetReconnectWait(opts, 10)); - IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); - IFOK(s, natsOptions_SetMaxReconnect(opts, 10000)); - IFOK(s, natsOptions_SetServers(opts, servers, 2)); - IFOK(s, natsOptions_SetNoRandomize(opts, true)); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &arg)); - testCond(s == NATS_OK); - - test("Start real backup server: "); - pid = _startServer("nats://127.0.0.1:4223", "-p 4223", true); - CHECK_SERVER_STARTED(pid); - testCond(true); - - test("Start mock server: "); - if (s == NATS_OK) - { - arg.status = NATS_ERR; - arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n"; - s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg); - } - if (s == NATS_OK) - { - // Wait for server to be ready - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - } - testCond(s == NATS_OK); - - test("Connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK) - - test("Start Publish: "); - s = natsThread_Create(&t2, _publish, (void*) nc); - testCond(s == NATS_OK); - - nats_Sleep(1000); - - test("Kill server: "); - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Wait for reconnect: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !(arg.reconnected)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - if (t2 != NULL) - { - natsThread_Join(t2); - natsThread_Destroy(t2); - } - - test("Check no proto error: "); - { - const char *le = NULL; - s = natsConnection_GetLastError(nc, &le); - } - testCond(s == NATS_OK); - - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(pid); -} - -static void -test_ForcedReconnect(void) -{ - natsStatus s; - struct threadArg arg; - natsOptions *opts = NULL; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsPid pid = NATS_INVALID_PID; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("unable to setup test"); - - test("Start server, connect, subscribe: "); - pid = _startServer("nats://127.0.0.1:4222", "-p 4222", true); - CHECK_SERVER_STARTED(pid); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &arg)); - IFOK(s, natsConnection_Connect(&nc, opts)); - IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo")); - testCond(s == NATS_OK); - - test("Send a message to foo: "); - IFOK(s, natsConnection_PublishString(nc, "foo", "bar")); - testCond(s == NATS_OK); - - test("Receive the message: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) && (msg != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Forced reconnect: "); - s = natsConnection_Reconnect(nc); - testCond(s == NATS_OK); - - test("Waiting for reconnect: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.reconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 5000); - arg.reconnected = false; - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Send a message to foo: "); - IFOK(s, natsConnection_PublishString(nc, "foo", "bar")); - testCond(s == NATS_OK); - - test("Receive the message: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) && (msg != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Reconnect again with allowReconnect false, the call succeeds: "); - natsMutex_Lock(nc->mu); - nc->opts->allowReconnect = false; - natsMutex_Unlock(nc->mu); - s = natsConnection_Reconnect(nc); - testCond(s == NATS_OK); - - // On MacOS this returns NATS_CONNECTION_CLOSED, on Ubuntu we get a - // NATS_TIMEOUT. - test("But the connection is closed: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond(((s == NATS_CONNECTION_CLOSED) || (s = NATS_TIMEOUT)) && (msg == NULL)); - - natsConnection_Close(nc); - test("Reconect on a close connection errors: "); - s = natsConnection_Reconnect(nc); - testCond(s == NATS_CONNECTION_CLOSED); - - test("Reconect on a NULL connection errors: "); - s = natsConnection_Reconnect(NULL); - testCond(s == NATS_INVALID_ARG); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); -} - -static void -_stopServerInThread(void *closure) -{ - natsPid pid = *((natsPid*) closure); - - nats_Sleep(150); - _stopServer(pid); -} - -static void -test_ReconnectFailsPendingRequest(void) -{ - natsStatus s; - natsOptions *opts = NULL; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsThread *t = NULL; - natsPid pid = NATS_INVALID_PID; - bool failr = false; - int iter; - - for (iter=1; iter<=2; iter++) - { - failr = (iter == 2 ? true : false); - - test("Create options: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetFailRequestsOnDisconnect(opts, failr)); - testCond(s == NATS_OK); - - test("Start server: "); - pid = _startServer("nats://127.0.0.1:4222", "-p 4222", true); - CHECK_SERVER_STARTED(pid); - testCond(true); - - test("Connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - test("Create service provider: "); - s = natsConnection_SubscribeSync(&sub, nc, "requests"); - testCond(s == NATS_OK); - - test("Start thread that will stop server: "); - s = natsThread_Create(&t, _stopServerInThread, (void*) &pid); - testCond(s == NATS_OK); - - test((failr ? "Fails due to disconnect: " : "Fails due to timeout: ")); - s = natsConnection_RequestString(&msg, nc, "requests", "help", 300); - testCond(s == (failr ? NATS_CONNECTION_DISCONNECTED : NATS_TIMEOUT)); - - natsThread_Join(t); - natsThread_Destroy(t); - t = NULL; - natsSubscription_Destroy(sub); - sub = NULL; - natsConnection_Destroy(nc); - nc = NULL; - natsOptions_Destroy(opts); - opts = NULL; - } -} - -static void -test_HeadersNotSupported(void) -{ - natsStatus s; - natsConnection *conn = NULL; - natsMsg *msg = NULL; - natsMsg *reply= NULL; - natsThread *t = NULL; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - // Set this to error, the mock server should set it to OK - // if it can start successfully. - arg.status = NATS_ERR; - arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n"; - s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg); - } - if (s == NATS_OK) - { - // Wait for server to be ready - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - } - if (s != NATS_OK) - { - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - _destroyDefaultThreadArgs(&arg); - FAIL("Unable to setup test"); - } - - test("Headers not supported with old server: "); - s = natsConnection_ConnectTo(&conn, NATS_DEFAULT_URL); - IFOK(s, natsConnection_HasHeaderSupport(conn)); - testCond(s == NATS_NO_SERVER_SUPPORT); - - test("Create msg with heades: "); - s = natsMsg_Create(&msg, "foo", NULL, "body", 4); - IFOK(s, natsMsgHeader_Set(msg, "Header", "Hello Headers!")); - testCond(s == NATS_OK); - - test("Publish fails: "); - s = natsConnection_PublishMsg(conn, msg); - testCond(s == NATS_NO_SERVER_SUPPORT); - - test("Request fails: "); - s = natsConnection_RequestMsg(&reply, conn, msg, 1000); - testCond((s == NATS_NO_SERVER_SUPPORT) && (reply == NULL)); - - natsConnection_Destroy(conn); - - // Notify mock server we are done - natsMutex_Lock(arg.m); - arg.done = true; - natsCondition_Signal(arg.c); - natsMutex_Unlock(arg.m); - - natsThread_Join(t); - natsThread_Destroy(t); - - natsMsg_Destroy(msg); - natsMsg_Destroy(reply); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_HeadersBasic(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsPid pid = NATS_INVALID_PID; - natsMsg *msg = NULL; - natsMsg *rmsg = NULL; - natsSubscription *sub = NULL; - const char *val = NULL; - - if (!serverVersionAtLeast(2, 2, 0)) - { - char txt[200]; - - snprintf(txt, sizeof(txt), "Skipping since requires server version of at least 2.2.0, got %s: ", serverVersion); - test(txt); - testCond(true); - return; - } - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Connect ok: "); - s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4222"); - testCond(s == NATS_OK); - - test("Headers supported: "); - s = natsConnection_HasHeaderSupport(nc); - testCond(s == NATS_OK); - - test("Create sub: "); - s = natsConnection_SubscribeSync(&sub, nc, "foo"); - testCond(s == NATS_OK); - - test("Create msg with headers: "); - s = natsMsg_Create(&msg, "foo", NULL, "body", 4); - IFOK(s, natsMsgHeader_Set(msg, "Headers", "Hello Headers!")) - testCond(s == NATS_OK); - - test("Publish with headers ok: "); - s = natsConnection_PublishMsg(nc, msg); - testCond(s == NATS_OK); - - test("Receive msg: ") - s = natsSubscription_NextMsg(&rmsg, sub, 1000); - testCond((s == NATS_OK) && (rmsg != NULL)); - - test("Resend msg without lift: "); - s = natsConnection_PublishMsg(nc, rmsg); - testCond(s == NATS_OK); - natsMsg_Destroy(rmsg); - rmsg = NULL; - - test("Receive msg: ") - s = natsSubscription_NextMsg(&rmsg, sub, 1000); - testCond((s == NATS_OK) && (rmsg != NULL)); - - test("Check headers: "); - s = natsMsgHeader_Get(rmsg, "Headers", &val); - testCond((s == NATS_OK) - && (val != NULL) && (strcmp(val, "Hello Headers!") == 0) - && (natsMsg_GetDataLength(rmsg) == 4) - && (strncmp(natsMsg_GetData(msg), "body", 4) == 0)); - - natsMsg_Destroy(rmsg); - rmsg = NULL; - test("Value with CRLFs replaced with spaces: "); - s = natsMsgHeader_Set(msg, "Headers", "value1\r\nvalue2\r\nvalue3"); - IFOK(s, natsConnection_PublishMsg(nc, msg)); - IFOK(s, natsSubscription_NextMsg(&rmsg, sub, 1000)); - IFOK(s, natsMsgHeader_Get(rmsg, "Headers", &val)); - testCond((s == NATS_OK) - && (val != NULL) && (strcmp(val, "value1 value2 value3") == 0) - && (natsMsg_GetDataLength(rmsg) == 4) - && (strncmp(natsMsg_GetData(msg), "body", 4) == 0)); - - natsMsg_Destroy(msg); - natsMsg_Destroy(rmsg); - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - _stopServer(pid); -} - -static void -_msgFilterNoOp(natsConnection *nc, natsMsg **msg, void *closure) { } - -static void -_msgFilterAlterMsg(natsConnection *nc, natsMsg **msg, void *closure) -{ - natsStatus s; - natsMsg *nm = NULL; - - s = natsMsg_Create(&nm, natsMsg_GetSubject(*msg), NULL, "replaced", 8); - if (s != NATS_OK) - nats_PrintLastErrorStack(stderr); - - natsMsg_Destroy(*msg); - *msg = nm; -} - -static void -_msgFilterDropMsg(natsConnection *nc, natsMsg **msg, void *closure) -{ - natsMsg_Destroy(*msg); - *msg = NULL; - natsConn_setFilter(nc, NULL); -} - -static void -test_natsMsgsFilter(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsPid pid = NATS_INVALID_PID; - natsMsg *msg = NULL; - natsSubscription *sub = NULL; - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Connect ok: "); - s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4222"); - testCond(s == NATS_OK); - - test("Create sub: "); - s = natsConnection_SubscribeSync(&sub, nc, "foo"); - testCond(s == NATS_OK); - - test("Add no-op filter: "); - natsConn_setFilter(nc, _msgFilterNoOp); - s = natsConnection_PublishString(nc, "foo", "original"); - IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000)); - testCond((s == NATS_OK) && (msg != NULL) - && (strcmp(natsMsg_GetData(msg), "original") == 0)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Add alter-msg filter: "); - natsConn_setFilter(nc, _msgFilterAlterMsg); - s = natsConnection_PublishString(nc, "foo", "original"); - IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000)); - testCond((s == NATS_OK) && (msg != NULL) - && (strcmp(natsMsg_GetData(msg), "replaced") == 0)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Add drop-msg filter: "); - natsConn_setFilter(nc, _msgFilterDropMsg); - s = natsConnection_PublishString(nc, "foo", "will be dropped"); - IFOK(s, natsSubscription_NextMsg(&msg, sub, 100)); - testCond((s == NATS_TIMEOUT) && (msg == NULL)); - nats_clearLastError(); - - test("Filter is removed from previous filter: "); - s = natsConnection_PublishString(nc, "foo", "got it"); - IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000)); - testCond((s == NATS_OK) && (msg != NULL) - && (strcmp(natsMsg_GetData(msg), "got it") == 0)); - natsMsg_Destroy(msg); - msg = NULL; - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - _stopServer(pid); -} - -static natsStatus -_evLoopAttach(void **userData, void *loop, natsConnection *nc, natsSock socket) -{ - struct threadArg *arg = (struct threadArg *) loop; - - natsMutex_Lock(arg->m); - *userData = arg; - arg->nc = nc; - arg->sock = socket; - arg->attached++; - arg->doRead = true; - natsCondition_Broadcast(arg->c); - natsMutex_Unlock(arg->m); - - return NATS_OK; -} - -static natsStatus -_evLoopRead(void *userData, bool add) -{ - struct threadArg *arg = (struct threadArg *) userData; - - natsMutex_Lock(arg->m); - arg->doRead = add; - natsCondition_Broadcast(arg->c); - natsMutex_Unlock(arg->m); - - return NATS_OK; -} - -static natsStatus -_evLoopWrite(void *userData, bool add) -{ - struct threadArg *arg = (struct threadArg *) userData; - - natsMutex_Lock(arg->m); - arg->doWrite = add; - natsCondition_Broadcast(arg->c); - natsMutex_Unlock(arg->m); - - return NATS_OK; -} - -static natsStatus -_evLoopDetach(void *userData) -{ - struct threadArg *arg = (struct threadArg *) userData; - - natsMutex_Lock(arg->m); - arg->detached++; - natsCondition_Broadcast(arg->c); - natsMutex_Unlock(arg->m); - - return NATS_OK; -} - -static void -_eventLoop(void *closure) -{ - struct threadArg *arg = (struct threadArg *) closure; - natsSock sock = NATS_SOCK_INVALID; - natsConnection *nc = NULL; - bool read = false; - bool write= false; - bool stop = false; - - while (!stop) - { - nats_Sleep(100); - natsMutex_Lock(arg->m); - while (!arg->evStop && ((sock = arg->sock) == NATS_SOCK_INVALID)) - natsCondition_Wait(arg->c, arg->m); - stop = arg->evStop; - nc = arg->nc; - read = arg->doRead; - write = arg->doWrite; - natsMutex_Unlock(arg->m); - - if (!stop) - { - if (read) - natsConnection_ProcessReadEvent(nc); - if (write) - natsConnection_ProcessWriteEvent(nc); - } - } -} - -static void -test_EventLoop(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsPid pid = NATS_INVALID_PID; - struct threadArg arg; - - test("Set options: "); - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetMaxReconnect(opts, 100)); - IFOK(s, natsOptions_SetReconnectWait(opts, 50)); - IFOK(s, natsOptions_SetEventLoop(opts, (void*) &arg, - _evLoopAttach, - _evLoopRead, - _evLoopWrite, - _evLoopDetach)); - IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg)); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg)); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)); - testCond(s == NATS_OK); - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Start event loop: "); - natsMutex_Lock(arg.m); - arg.sock = NATS_SOCK_INVALID; - natsMutex_Unlock(arg.m); - s = natsThread_Create(&arg.t, _eventLoop, (void*) &arg); - testCond(s == NATS_OK); - - test("Connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK) - - test("Create sub: "); - s = natsConnection_SubscribeSync(&sub, nc, "foo"); - testCond(s == NATS_OK); - - test("Stop server and wait for disconnect: "); - _stopServer(pid); - pid = NATS_INVALID_PID; - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.disconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Restart server: "); - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - testCond(s == NATS_OK); - - test("Wait for reconnect: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.reconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Publish: "); - s = natsConnection_PublishString(nc, "foo", "bar"); - testCond(s == NATS_OK); - - test("Check msg received: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - - test("Close and wait for close cb: "); - natsConnection_Close(nc); - s = _waitForConnClosed(&arg); - testCond(s == NATS_OK); - - natsMutex_Lock(arg.m); - arg.evStop = true; - natsCondition_Broadcast(arg.c); - natsMutex_Unlock(arg.m); - - natsThread_Join(arg.t); - natsThread_Destroy(arg.t); - - test("Check ev loop: "); - natsMutex_Lock(arg.m); - if (arg.attached != 2 || !arg.detached) - s = NATS_ERR; - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(pid); -} - -static void -test_EventLoopRetryOnFailedConnect(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsPid pid = NATS_INVALID_PID; - struct threadArg arg; - - test("Set options: "); - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetMaxReconnect(opts, 100)); - IFOK(s, natsOptions_SetReconnectWait(opts, 50)); - IFOK(s, natsOptions_SetRetryOnFailedConnect(opts, - true, - _connectedCb, - (void*) &arg)); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)); - IFOK(s, natsOptions_SetEventLoop(opts, (void*) &arg, - _evLoopAttach, - _evLoopRead, - _evLoopWrite, - _evLoopDetach)); - testCond(s == NATS_OK); - - test("Start event loop: "); - natsMutex_Lock(arg.m); - arg.sock = NATS_SOCK_INVALID; - natsMutex_Unlock(arg.m); - s = natsThread_Create(&arg.t, _eventLoop, (void*) &arg); - testCond(s == NATS_OK); - - test("Start connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_NOT_YET_CONNECTED); - if (s == NATS_NOT_YET_CONNECTED) - s = NATS_OK; - - test("Create sub: "); - s = natsConnection_SubscribeSync(&sub, nc, "foo"); - testCond(s == NATS_OK); - - test("Start server: "); - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - testCond(s == NATS_OK); - - test("Check connected: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.connected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Publish: "); - s = natsConnection_PublishString(nc, "foo", "bar"); - testCond(s == NATS_OK); - - test("Check msg received: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - - test("Close and wait for close cb: "); - natsConnection_Close(nc); - s = _waitForConnClosed(&arg); - testCond(s == NATS_OK); - - natsMutex_Lock(arg.m); - arg.evStop = true; - natsCondition_Broadcast(arg.c); - natsMutex_Unlock(arg.m); - - natsThread_Join(arg.t); - natsThread_Destroy(arg.t); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(pid); -} - -static void -test_EventLoopTLS(void) -{ -#if defined(NATS_HAS_TLS) - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid pid = NATS_INVALID_PID; - struct threadArg arg; - - test("Set options: "); - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&opts)); - IFOK(s, natsOptions_SetURL(opts, "nats://localhost:4443")); - IFOK(s, natsOptions_SkipServerVerification(opts, true)); - IFOK(s, natsOptions_SetSecure(opts, true)); - IFOK(s, natsOptions_SetMaxReconnect(opts, 100)); - IFOK(s, natsOptions_SetReconnectWait(opts, 50)); - IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg)); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg)); - IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)); - IFOK(s, natsOptions_SetEventLoop(opts, (void*) &arg, - _evLoopAttach, - _evLoopRead, - _evLoopWrite, - _evLoopDetach)); - testCond(s == NATS_OK); - - test("Start server: "); - pid = _startServer("nats://127.0.0.1:4443", "-config tls.conf -DV", true); - CHECK_SERVER_STARTED(pid); - testCond(s == NATS_OK); - - test("Start event loop: "); - natsMutex_Lock(arg.m); - arg.sock = NATS_SOCK_INVALID; - natsMutex_Unlock(arg.m); - s = natsThread_Create(&arg.t, _eventLoop, (void*) &arg); - testCond(s == NATS_OK); - - test("Connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - test("Disconnect: "); - _stopServer(pid); - pid = NATS_INVALID_PID; - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.disconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Restart server: "); - pid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true); - CHECK_SERVER_STARTED(pid); - testCond(s == NATS_OK); - - test("Check reconnected: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.reconnected) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Shutdown evLoop: "); - natsMutex_Lock(arg.m); - arg.evStop = true; - natsCondition_Broadcast(arg.c); - natsMutex_Unlock(arg.m); - natsThread_Join(arg.t); - natsThread_Destroy(arg.t); - testCond(s == NATS_OK); - - test("Close and wait for close cb: "); - natsConnection_Close(nc); - s = _waitForConnClosed(&arg); - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(pid); -#else - test("Skipped when built with no SSL support: "); - testCond(true); -#endif -} - -static void -test_SSLBasic(void) -{ -#if defined(NATS_HAS_TLS) - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg args; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s == NATS_OK) - opts = _createReconnectOptions(); - if (opts == NULL) - FAIL("Unable to setup test!"); - - serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true); - CHECK_SERVER_STARTED(serverPid); - - test("Check that connect switches to TLS automatically: "); - s = natsOptions_SetURL(opts, "nats://localhost:4443"); - // For this test skip server verification - IFOK(s, natsOptions_SkipServerVerification(opts, true)); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - - natsConnection_Destroy(nc); - nc = NULL; - - test("Check connects OK with SSL options: "); - s = natsOptions_SetSecure(opts, true); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args)); - - // For test purposes, we provide the CA trusted certs - IFOK(s, natsOptions_LoadCATrustedCertificates(opts, "certs/ca.pem")); - IFOK(s, natsConnection_Connect(&nc, opts)); - IFOK(s, natsConnection_PublishString(nc, "foo", "test")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK) - - test("Check reconnects OK: "); - _stopServer(serverPid); - - nats_Sleep(100); - - serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true); - CHECK_SERVER_STARTED(serverPid); - - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !(args.reconnected)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - - IFOK(s, natsConnection_PublishString(nc, "foo", "test")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK) - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&args); - - _stopServer(serverPid); -#else - test("Skipped when built with no SSL support: "); - testCond(true); -#endif -} - -static void -test_SSLVerify(void) -{ -#if defined(NATS_HAS_TLS) - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg args; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s == NATS_OK) - opts = _createReconnectOptions(); - if (opts == NULL) - FAIL("Unable to create reconnect options!"); - - serverPid = _startServer("nats://127.0.0.1:4443", "-config tlsverify.conf", true); - CHECK_SERVER_STARTED(serverPid); - - test("Check that connect fails if no SSL certs: "); - s = natsOptions_SetURL(opts, "nats://localhost:4443"); - IFOK(s, natsOptions_SetSecure(opts, true)); - // For test purposes, we provide the CA trusted certs - IFOK(s, natsOptions_LoadCATrustedCertificates(opts, "certs/ca.pem")); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args)); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s != NATS_OK); - - test("Check that connect succeeds with proper certs: "); - s = natsOptions_LoadCertificatesChain(opts, - "certs/client-cert.pem", - "certs/client-key.pem"); - IFOK(s, natsConnection_Connect(&nc, opts)); - IFOK(s, natsConnection_PublishString(nc, "foo", "test")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Check reconnects OK: "); - _stopServer(serverPid); - - nats_Sleep(100); - - serverPid = _startServer("nats://127.0.0.1:4443", "-config tlsverify.conf", true); - CHECK_SERVER_STARTED(serverPid); - - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !(args.reconnected)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - - IFOK(s, natsConnection_PublishString(nc, "foo", "test")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK) - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&args); - - _stopServer(serverPid); -#else - test("Skipped when built with no SSL support: "); - testCond(true); -#endif -} - -static void -test_SSLLoadCAFromMemory(void) -{ -#if defined(NATS_HAS_TLS) - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - natsBuffer *certBuf = NULL; - struct threadArg args; - - s = nats_ReadFile(&certBuf, 10000, "certs/ca.pem"); - IFOK(s, _createDefaultThreadArgsForCbTests(&args)); - if (s == NATS_OK) - opts = _createReconnectOptions(); - if (opts == NULL) - FAIL("Unable to create reconnect options!"); - - test("Check NULL certs: "); - s = natsOptions_SetCATrustedCertificates(opts, NULL); - testCond(s == NATS_INVALID_ARG); - - test("Check empty certs: "); - s = natsOptions_SetCATrustedCertificates(opts, ""); - testCond(s == NATS_INVALID_ARG); - - test("Check invalid cert: "); - s = natsOptions_SetCATrustedCertificates(opts, "invalid"); - testCond(s == NATS_SSL_ERROR); - - serverPid = _startServer("nats://127.0.0.1:4443", "-config tlsverify.conf", true); - CHECK_SERVER_STARTED(serverPid); - - test("Check that connect succeeds with proper certs: "); - s = natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args); - IFOK(s, natsOptions_SetURL(opts, "nats://localhost:4443")); - IFOK(s, natsOptions_SetSecure(opts, true)); - // For test purposes, we provide the CA trusted certs - if (s == NATS_OK) - { - s = natsOptions_SetCATrustedCertificates(opts, (const char*) natsBuf_Data(certBuf)); - // Demonstrate that we can free the memory after this call.. - natsBuf_Destroy(certBuf); - } - IFOK(s, natsOptions_LoadCertificatesChain(opts, - "certs/client-cert.pem", - "certs/client-key.pem")); - IFOK(s, natsConnection_Connect(&nc, opts)); - IFOK(s, natsConnection_PublishString(nc, "foo", "test")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Check reconnects OK: "); - _stopServer(serverPid); - - nats_Sleep(100); - - serverPid = _startServer("nats://127.0.0.1:4443", "-config tlsverify.conf", true); - CHECK_SERVER_STARTED(serverPid); - - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !(args.reconnected)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - - IFOK(s, natsConnection_PublishString(nc, "foo", "test")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK) - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&args); - - _stopServer(serverPid); -#else - test("Skipped when built with no SSL support: "); - testCond(true); -#endif -} - -static void -test_SSLCertAndKeyFromMemory(void) -{ -#if defined(NATS_HAS_TLS) - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - natsBuffer *certBuf = NULL; - natsBuffer *keyBuf = NULL; - struct threadArg args; - - s = nats_ReadFile(&certBuf, 10000, "certs/client-cert.pem"); - IFOK(s, nats_ReadFile(&keyBuf, 10000, "certs/client-key.pem")); - IFOK(s, _createDefaultThreadArgsForCbTests(&args)); - if (s == NATS_OK) - opts = _createReconnectOptions(); - if (opts == NULL) - FAIL("Unable to create reconnect options!"); - - test("Check NULL cert: "); - s = natsOptions_SetCertificatesChain(opts, NULL, (const char*) natsBuf_Data(keyBuf)); - testCond(s == NATS_INVALID_ARG); - - test("Check empty cert: "); - s = natsOptions_SetCertificatesChain(opts, "", (const char*) natsBuf_Data(keyBuf)); - testCond(s == NATS_INVALID_ARG); - - test("Check NULL key: "); - s = natsOptions_SetCertificatesChain(opts, (const char*) natsBuf_Data(certBuf), NULL); - testCond(s == NATS_INVALID_ARG); - - test("Check empty key: "); - s = natsOptions_SetCertificatesChain(opts, (const char*) natsBuf_Data(certBuf), ""); - testCond(s == NATS_INVALID_ARG); - - test("Check invalid cert: "); - s = natsOptions_SetCertificatesChain(opts, "invalid", (const char*) natsBuf_Data(keyBuf)); - testCond(s == NATS_SSL_ERROR); - - test("Check invalid key: "); - s = natsOptions_SetCertificatesChain(opts, (const char*) natsBuf_Data(certBuf), "invalid"); - testCond(s == NATS_SSL_ERROR); - - serverPid = _startServer("nats://127.0.0.1:4443", "-config tlsverify.conf", true); - CHECK_SERVER_STARTED(serverPid); - - test("Check that connect succeeds with proper certs: "); - s = natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args); - IFOK(s, natsOptions_SetURL(opts, "nats://localhost:4443")); - IFOK(s, natsOptions_SetSecure(opts, true)); - // For test purposes, we provide the CA trusted certs - IFOK(s, natsOptions_LoadCATrustedCertificates(opts, "certs/ca.pem")); - IFOK(s, natsOptions_SetCertificatesChain(opts, - (const char*) natsBuf_Data(certBuf), - (const char*) natsBuf_Data(keyBuf))); - // Demonstrate that we can free the memory after this call.. - natsBuf_Destroy(certBuf); - natsBuf_Destroy(keyBuf); - - IFOK(s, natsConnection_Connect(&nc, opts)); - IFOK(s, natsConnection_PublishString(nc, "foo", "test")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Check reconnects OK: "); - _stopServer(serverPid); - - nats_Sleep(100); - - serverPid = _startServer("nats://127.0.0.1:4443", "-config tlsverify.conf", true); - CHECK_SERVER_STARTED(serverPid); - - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !(args.reconnected)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - - IFOK(s, natsConnection_PublishString(nc, "foo", "test")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK) - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&args); - - _stopServer(serverPid); -#else - test("Skipped when built with no SSL support: "); - testCond(true); -#endif -} - -static void -test_SSLVerifyHostname(void) -{ -#if defined(NATS_HAS_TLS) - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg args; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s == NATS_OK) - opts = _createReconnectOptions(); - if (opts == NULL) - FAIL("Unable to create reconnect options!"); - - serverPid = _startServer("nats://127.0.0.1:4443", "-config tls_noip.conf", true); - CHECK_SERVER_STARTED(serverPid); - - test("Check that connect fails if url is IP: "); - s = natsOptions_SetURL(opts, "nats://127.0.0.1:4443"); - IFOK(s, natsOptions_SetSecure(opts, true)); - // For test purposes, we provide the CA trusted certs - IFOK(s, natsOptions_LoadCATrustedCertificates(opts, "certs/ca.pem")); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args)); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_SSL_ERROR); - - test("Check that connect fails if wrong expected hostname: "); - s = natsOptions_SetURL(opts, "nats://localhost:4443"); - IFOK(s, natsOptions_SetExpectedHostname(opts, "foo")); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_SSL_ERROR); - - test("Check that connect succeeds if hostname ok and no expected hostname set: "); - s = natsOptions_SetURL(opts, "nats://localhost:4443"); - IFOK(s, natsOptions_SetExpectedHostname(opts, NULL)); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK) - natsConnection_Destroy(nc); - nc = NULL; - - test("Check that connect succeeds with proper expected hostname: "); - s = natsOptions_SetURL(opts, "nats://127.0.0.1:4443"); - IFOK(s, natsOptions_SetExpectedHostname(opts, "localhost")); - IFOK(s, natsConnection_Connect(&nc, opts)); - IFOK(s, natsConnection_PublishString(nc, "foo", "test")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - test("Check reconnects OK: "); - _stopServer(serverPid); - - nats_Sleep(100); - - serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true); - CHECK_SERVER_STARTED(serverPid); - - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !(args.reconnected)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - - IFOK(s, natsConnection_PublishString(nc, "foo", "test")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK) - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&args); - - _stopServer(serverPid); -#else - test("Skipped when built with no SSL support: "); - testCond(true); -#endif -} - -static void -test_SSLSkipServerVerification(void) -{ -#if defined(NATS_HAS_TLS) - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg args; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s == NATS_OK) - opts = _createReconnectOptions(); - if (opts == NULL) - FAIL("Unable to create reconnect options!"); - - serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true); - CHECK_SERVER_STARTED(serverPid); - - test("Check that connect fails due to server verification: "); - s = natsOptions_SetURL(opts, "nats://127.0.0.1:4443"); - IFOK(s, natsOptions_SetSecure(opts, true)); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_SSL_ERROR); - - test("Check that connect succeeds with server verification disabled: "); - s = natsOptions_SkipServerVerification(opts, true); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args)); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - - test("Check reconnects OK: "); - _stopServer(serverPid); - - nats_Sleep(100); - - serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true); - CHECK_SERVER_STARTED(serverPid); - - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !(args.reconnected)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - - IFOK(s, natsConnection_PublishString(nc, "foo", "test")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK) - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&args); - - _stopServer(serverPid); -#else - test("Skipped when built with no SSL support: "); - testCond(true); -#endif -} - -static void -test_SSLCiphers(void) -{ -#if defined(NATS_HAS_TLS) - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg args; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s == NATS_OK) - opts = _createReconnectOptions(); - if (opts == NULL) - FAIL("Unable to setup test!"); - - serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true); - CHECK_SERVER_STARTED(serverPid); - - test("SetCipherSuites requires OpenSSL 1.1: "); - s = natsOptions_SetCipherSuites(opts, "TLS_AES_128_GCM_SHA256"); -#if defined(NATS_USE_OPENSSL_1_1) - testCond(s == NATS_OK); -#else - testCond(s == NATS_ERR); -#endif - - test("Check that connect fails if improper ciphers: "); - s = natsOptions_SetURL(opts, "nats://localhost:4443"); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args)); - IFOK(s, natsOptions_SetSecure(opts, true)); - // For test purposes, we provide the CA trusted certs - IFOK(s, natsOptions_LoadCATrustedCertificates(opts, "certs/ca.pem")); - IFOK(s, natsOptions_SetCiphers(opts, "-ALL:RSA")); -#if defined(NATS_USE_OPENSSL_1_1) - IFOK(s, natsOptions_SetCipherSuites(opts, "")); -#endif - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s != NATS_OK); - - test("Check connects OK with proper ciphers: "); - s = natsOptions_SetCiphers(opts, "-ALL:HIGH"); -#if defined(NATS_USE_OPENSSL_1_1) - IFOK(s, natsOptions_SetCipherSuites(opts, "TLS_AES_128_GCM_SHA256")); -#endif - IFOK(s, natsConnection_Connect(&nc, opts)); - IFOK(s, natsConnection_PublishString(nc, "foo", "test")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK) - - test("Check reconnects OK: "); - _stopServer(serverPid); - - nats_Sleep(100); - - serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true); - CHECK_SERVER_STARTED(serverPid); - - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !(args.reconnected)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - - IFOK(s, natsConnection_PublishString(nc, "foo", "test")); - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK) - - natsConnection_Destroy(nc); - natsOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&args); - - _stopServer(serverPid); -#else - test("Skipped when built with no SSL support: "); - testCond(true); -#endif -} - -#if defined(NATS_HAS_TLS) -static void -_sslMT(void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - char subj[64]; - int i; - const char *msgPayload = "this is a test payload"; - int count = 50; - - natsMutex_Lock(args->m); - snprintf(subj, sizeof(subj), "foo.%d", ++(args->sum)); - while (!(args->current) && (s == NATS_OK)) - s = natsCondition_TimedWait(args->c, args->m, 2000); - natsMutex_Unlock(args->m); - - if (valgrind) - count = 10; - - for (i=0; (s == NATS_OK) && (i < count); i++) - { - s = natsConnection_Connect(&nc, args->opts); - IFOK(s, natsConnection_SubscribeSync(&sub, nc, subj)); - IFOK(s, natsConnection_PublishString(nc, subj, msgPayload)); - IFOK(s, natsSubscription_NextMsg(&msg, sub, 2000)); - if (s == NATS_OK) - { - if (strcmp(natsMsg_GetData(msg), msgPayload) != 0) - s = NATS_ERR; - } - - natsMsg_Destroy(msg); - msg = NULL; - natsSubscription_Destroy(sub); - sub = NULL; - natsConnection_Destroy(nc); - nc = NULL; - } - - if (s != NATS_OK) - { - natsMutex_Lock(args->m); - if (args->status == NATS_OK) - args->status = s; - natsMutex_Unlock(args->m); - } -} - -#define SSL_THREADS (3) -#endif - -static void -test_SSLMultithreads(void) -{ -#if defined(NATS_HAS_TLS) - natsStatus s; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - natsThread *t[SSL_THREADS]; - int i; - struct threadArg args; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s == NATS_OK) - s = natsOptions_Create(&opts); - if (opts == NULL) - FAIL("Unable to setup test!"); - - serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true); - CHECK_SERVER_STARTED(serverPid); - - test("Create options: "); - s = natsOptions_SetURL(opts, "nats://127.0.0.1:4443"); - IFOK(s, natsOptions_SetSecure(opts, true)); - // For test purposes, we provide the CA trusted certs - IFOK(s, natsOptions_LoadCATrustedCertificates(opts, "certs/ca.pem")); - IFOK(s, natsOptions_SetExpectedHostname(opts, "localhost")); - testCond(s == NATS_OK); - - args.opts = opts; - - for (i=0; (s == NATS_OK) && (id_name, ".") || !strcmp(entry->d_name, "..")) - continue; - - if (nats_asprintf(&fullPath, "%s/%s", path, entry->d_name) < 0) - abort(); - - memset(&statEntry, 0, sizeof(struct stat)); - stat(fullPath, &statEntry); - - if (S_ISDIR(statEntry.st_mode) != 0) - rmtree(fullPath); - else - unlink(fullPath); - - free(fullPath); - } - - closedir(dir); - rmdir(path); -#endif -} - -static void -_makeUniqueDir(char *buf, int bufLen, const char *path) -{ - int n; - - if ((int) strlen(path) + 1 + NUID_BUFFER_LEN + 1 > bufLen) - abort(); - - n = snprintf(buf, bufLen, "%s", path); - natsNUID_Next(buf+n, NUID_BUFFER_LEN+1); - buf[n+NUID_BUFFER_LEN+1] = '\0'; -} - -static void -_createConfFile(char *buf, int bufLen, const char *content) -{ - FILE *f = NULL; - - if (5 + NUID_BUFFER_LEN + 1 > bufLen) - abort(); - - memcpy(buf, "conf_", 5); - natsNUID_Next(buf+5, NUID_BUFFER_LEN+1); - buf[5+NUID_BUFFER_LEN+1] = '\0'; - - f = fopen(buf, "w"); - if (f == NULL) - abort(); - - fputs(content, f); - fclose(f); -} - -static void -test_ReconnectImplicitUserInfo(void) -{ - natsStatus s; - natsPid pid1 = NATS_INVALID_PID; - natsPid pid2 = NATS_INVALID_PID; - natsOptions *o1 = NULL; - natsConnection *nc1 = NULL; - natsSubscription *sub = NULL; - natsConnection *nc2 = NULL; - natsMsg *msg = NULL; - char conf[256]; - char cmdLine[1024]; - struct threadArg args; - - _createConfFile(conf, sizeof(conf), - "accounts { "\ - " A { "\ - " users: [{user: a, password: pwd}] "\ - " }\n"\ - " B { "\ - " users: [{user: b, password: pwd}] "\ - " }\n"\ - "}\n"\ - "no_auth_user: b\n"); - test("Start server1: "); - snprintf(cmdLine, sizeof(cmdLine), "-cluster_name \"local\" -cluster nats://127.0.0.1:6222 -c %s", conf); - pid1 = _startServer("nats://127.0.0.1:4222", cmdLine, true); - CHECK_SERVER_STARTED(pid1); - testCond(true); - - test("Connect1: "); - s = _createDefaultThreadArgsForCbTests(&args); - IFOK(s, natsOptions_Create(&o1)); - IFOK(s, natsOptions_SetDiscoveredServersCB(o1, _discoveredServersCb, (void*) &args)); - IFOK(s, natsOptions_SetURL(o1, "nats://a:pwd@127.0.0.1:4222")); - IFOK(s, natsOptions_SetReconnectWait(o1, 100)); - IFOK(s, natsOptions_SetReconnectedCB(o1, _reconnectedCb, (void*) &args)); - IFOK(s, natsConnection_Connect(&nc1, o1)); - testCond(s == NATS_OK); - - test("Start server2: "); - snprintf(cmdLine, sizeof(cmdLine), "-p 4223 -cluster_name \"local\" -cluster nats://127.0.0.1:6223 -routes nats://127.0.0.1:6222 -c %s", conf); - pid2 = _startServer("nats://127.0.0.1:4223", cmdLine, true); - CHECK_SERVER_STARTED(pid2); - testCond(true); - - test("Check s2 discovered: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum == 0)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - test("Stop srv1: "); - _stopServer(pid1); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.reconnected) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - test("Create sub: "); - s = natsConnection_SubscribeSync(&sub, nc1, "foo"); - testCond(s == NATS_OK); - - test("Flush: "); - s = natsConnection_Flush(nc1); - testCond(s == NATS_OK); - - test("Connect 2: "); - s = natsConnection_ConnectTo(&nc2, "nats://a:pwd@127.0.0.1:4223"); - testCond(s == NATS_OK); - - test("Publish: "); - s = natsConnection_PublishString(nc2, "foo", "msg"); - testCond(s == NATS_OK); - - test("Check received: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc1); - natsOptions_Destroy(o1); - natsConnection_Destroy(nc2); - _stopServer(pid2); - _destroyDefaultThreadArgs(&args); - remove(conf); -} - -static void -test_JetStreamUnmarshalAccountInfo(void) -{ - natsStatus s; - nats_JSON *json = NULL; - jsAccountInfo *ai = NULL; - jsTier *r = NULL; - const char *bad[] = { - "{\"memory\":\"abc\"}", - "{\"storage\":\"abc\"}", - "{\"streams\":\"abc\"}", - "{\"consumers\":\"abc\"}", - "{\"domain\":123}", - "{\"api\":123}", - "{\"api\":{\"total\":\"abc\"}}", - "{\"api\":{\"errors\":\"abc\"}}", - "{\"limits\":123}", - "{\"limits\":{\"max_memory\":\"abc\"}}", - "{\"limits\":{\"max_storage\":\"abc\"}}", - "{\"limits\":{\"max_streams\":\"abc\"}}", - "{\"limits\":{\"max_consumers\":\"abc\"}}", - "{\"limits\":{\"max_ack_pending\":\"abc\"}}", - "{\"limits\":{\"memory_max_stream_bytes\":\"abc\"}}", - "{\"limits\":{\"storage_max_stream_bytes\":\"abc\"}}", - "{\"limits\":{\"max_bytes_required\":\"abc\"}}", - "{\"tier\":123}", - "{\"tier\":{1, 2}}", - "{\"tier\":{\"R1\":123}}", - "{\"tier\":{\"R1\":{\"memory\":\"abc\"}}}", - }; - char tmp[2048]; - int i; - - for (i=0; i<(int)(sizeof(bad)/sizeof(char*)); i++) - { - test("Bad fields: "); - s = nats_JSONParse(&json, bad[i], (int) strlen(bad[i])); - IFOK(s, js_unmarshalAccountInfo(json, &ai)); - testCond((s != NATS_OK) && (ai == NULL)); - nats_JSONDestroy(json); - json = NULL; - nats_clearLastError(); - } - - test("Account info complete: "); - snprintf(tmp, sizeof(tmp), "{\"memory\":1000,\"storage\":2000,\"streams\":5,\"consumers\":7,"\ - "\"domain\":\"MyDomain\","\ - "\"api\":{\"total\":8,\"errors\":2},"\ - "\"limits\":{\"max_memory\":3000,\"max_storage\":4000,\"max_streams\":10,\"max_consumers\":20,"\ - "\"max_ack_pending\":100,\"memory_max_stream_bytes\":1000000,\"storage_max_stream_bytes\":2000000,\"max_bytes_required\":true},"\ - "\"tier\":{\"R1\":{\"memory\":1000,\"storage\":2000,\"streams\":5,\"consumers\":7,"\ - "\"limits\":{\"max_memory\":3000,\"max_storage\":4000,\"max_streams\":10,\"max_consumers\":20,"\ - "\"max_ack_pending\":100,\"memory_max_stream_bytes\":1000000,\"storage_max_stream_bytes\":2000000,\"max_bytes_required\":true}},"\ - "\"R2\":{\"memory\":2000,\"storage\":3000,\"streams\":8,\"consumers\":9,"\ - "\"limits\":{\"max_memory\":4000,\"max_storage\":5000,\"max_streams\":20,\"max_consumers\":30,"\ - "\"max_ack_pending\":200,\"memory_max_stream_bytes\":2000000,\"storage_max_stream_bytes\":3000000}}}}"); - s = nats_JSONParse(&json, tmp, (int) strlen(tmp)); - IFOK(s, js_unmarshalAccountInfo(json, &ai)); - testCond((s == NATS_OK) && (ai != NULL) - && (ai->Memory == 1000) - && (ai->Store == 2000) - && (ai->Streams == 5) - && (ai->Consumers == 7) - && (ai->Domain != NULL) - && (strcmp(ai->Domain, "MyDomain") == 0) - && (ai->API.Total == 8) - && (ai->API.Errors == 2) - && (ai->Limits.MaxMemory == 3000) - && (ai->Limits.MaxStore == 4000) - && (ai->Limits.MaxStreams == 10) - && (ai->Limits.MaxConsumers == 20) - && (ai->Limits.MaxAckPending == 100) - && (ai->Limits.MemoryMaxStreamBytes == 1000000) - && (ai->Limits.StoreMaxStreamBytes == 2000000) - && (ai->Limits.MaxBytesRequired) - && (ai->Tiers != NULL) - && (ai->TiersLen == 2)); - - test("Check tier R1: "); - if (strcmp(ai->Tiers[0]->Name, "R1") == 0) - r = ai->Tiers[0]; - else if (strcmp(ai->Tiers[1]->Name, "R1") == 0) - r = ai->Tiers[1]; - else - s = NATS_ERR; - testCond((s == NATS_OK) - && (r->Memory == 1000) - && (r->Store == 2000) - && (r->Streams == 5) - && (r->Consumers == 7) - && (r->Limits.MaxMemory == 3000) - && (r->Limits.MaxStore == 4000) - && (r->Limits.MaxStreams == 10) - && (r->Limits.MaxConsumers == 20) - && (r->Limits.MaxAckPending == 100) - && (r->Limits.MemoryMaxStreamBytes == 1000000) - && (r->Limits.StoreMaxStreamBytes == 2000000) - && r->Limits.MaxBytesRequired); - - test("Check tier R2: "); - if (strcmp(ai->Tiers[0]->Name, "R2") == 0) - r = ai->Tiers[0]; - else if (strcmp(ai->Tiers[1]->Name, "R2") == 0) - r = ai->Tiers[1]; - else - s = NATS_ERR; - testCond((s == NATS_OK) - && (r->Memory == 2000) - && (r->Store == 3000) - && (r->Streams == 8) - && (r->Consumers == 9) - && (r->Limits.MaxMemory == 4000) - && (r->Limits.MaxStore == 5000) - && (r->Limits.MaxStreams == 20) - && (r->Limits.MaxConsumers == 30) - && (r->Limits.MaxAckPending == 200) - && (r->Limits.MemoryMaxStreamBytes == 2000000) - && (r->Limits.StoreMaxStreamBytes == 3000000) - && (!r->Limits.MaxBytesRequired)); - - nats_JSONDestroy(json); - jsAccountInfo_Destroy(ai); -} - -static void -test_JetStreamUnmarshalStreamState(void) -{ - natsStatus s; - nats_JSON *json = NULL; - jsStreamState state; - const char *bad[] = { - "{\"state\":{\"messages\":\"abc\"}}", - "{\"state\":{\"bytes\":\"abc\"}}", - "{\"state\":{\"first_seq\":\"abc\"}}", - "{\"state\":{\"first_ts\":123}}", - "{\"state\":{\"last_seq\":\"abc\"}}", - "{\"state\":{\"last_ts\":123}}", - "{\"state\":{\"num_deleted\":\"abc\"}}", - "{\"state\":{\"deleted\":\"abc\"}}", - "{\"state\":{\"lost\":\"abc\"}}", - "{\"state\":{\"lost\":{\"msgs\":\"abc\"}}}", - "{\"state\":{\"lost\":{\"bytes\":\"abc\"}}}", - "{\"state\":{\"consumer_count\":\"abc\"}}", - "{\"state\":{\"num_subjects\":\"abc\"}}", - }; - int i; - - for (i=0; i<(int)(sizeof(bad)/sizeof(char*)); i++) - { - test("Bad fields: "); - memset(&state, 0, sizeof(jsStreamState)); - s = nats_JSONParse(&json, bad[i], (int) strlen(bad[i])); - IFOK(s, js_unmarshalStreamState(json, "state", &state)); - testCond(s != NATS_OK); - nats_JSONDestroy(json); - json = NULL; - nats_clearLastError(); - } - - test("Unmarshal: "); - memset(&state, 0, sizeof(jsStreamState)); - s = nats_JSONParse(&json, "{\"state\":{\"messages\":1,\"bytes\":2,"\ - "\"first_seq\":3,\"first_ts\":\"2021-06-23T18:22:00.123Z\","\ - "\"last_seq\":4,\"last_ts\":\"2021-06-23T18:22:00.123456789Z\","\ - "\"num_deleted\":5,\"deleted\":[6,7,8,9,10],"\ - "\"lost\":{\"msgs\":[11,12,13],\"bytes\":14},"\ - "\"consumer_count\":15,\"num_subjects\":3}}", -1); - IFOK(s, js_unmarshalStreamState(json, "state", &state)); - testCond((s == NATS_OK) && (json != NULL) - && (state.Msgs == 1) - && (state.Bytes == 2) - && (state.FirstSeq == 3) - && (state.FirstTime == 1624472520123000000) - && (state.LastSeq == 4) - && (state.LastTime == 1624472520123456789) - && (state.NumDeleted == 5) - && (state.Deleted != NULL) - && (state.DeletedLen == 5) - && (state.Deleted[0] == 6) - && (state.Deleted[1] == 7) - && (state.Deleted[2] == 8) - && (state.Deleted[3] == 9) - && (state.Deleted[4] == 10) - && (state.Lost != NULL) - && (state.Lost->MsgsLen == 3) - && (state.Lost->Msgs != NULL) - && (state.Lost->Msgs[0] == 11) - && (state.Lost->Msgs[1] == 12) - && (state.Lost->Msgs[2] == 13) - && (state.Lost->Bytes == 14) - && (state.Consumers == 15) - && (state.NumSubjects == 3)); - - test("Cleanup: "); - js_cleanStreamState(&state); - testCond(true); - // Check that this is fine - js_cleanStreamState(NULL); - - nats_JSONDestroy(json); -} - -static void -test_JetStreamUnmarshalStreamConfig(void) -{ - natsStatus s; - nats_JSON *json = NULL; - jsStreamConfig *sc = NULL; - const char *missing[] = { - "{\"name\":\"TEST\"}", - "{\"name\":\"TEST\",\"retention\":\"limits\"}", - "{\"name\":\"TEST\",\"retention\":\"limits\",\"max_consumers\":5}", - "{\"name\":\"TEST\",\"retention\":\"limits\",\"max_consumers\":5,\"max_msgs\":10}", - "{\"name\":\"TEST\",\"retention\":\"limits\",\"max_consumers\":5,\"max_msgs\":10,\"max_bytes\":1000}", - "{\"name\":\"TEST\",\"retention\":\"limits\",\"max_consumers\":5,\"max_msgs\":10,\"max_bytes\":1000,\"max_age\":20000000}", - "{\"name\":\"TEST\",\"retention\":\"limits\",\"max_consumers\":5,\"max_msgs\":10,\"max_bytes\":1000,\"max_age\":20000000,\"discard\":\"new\"}", - "{\"name\":\"TEST\",\"retention\":\"limits\",\"max_consumers\":5,\"max_msgs\":10,\"max_bytes\":1000,\"max_age\":20000000,\"discard\":\"new\",\"storage\":\"memory\"}", - }; - char tmp[2048]; - int i; - - for (i=0; i<(int)(sizeof(missing)/sizeof(char*)); i++) - { - test("Missing fields: "); - s = nats_JSONParse(&json, missing[i], (int) strlen(missing[i])); - IFOK(s, js_unmarshalStreamConfig(json, NULL, &sc)); - testCond((s == NATS_OK) && (sc != NULL)); - js_destroyStreamConfig(sc); - sc = NULL; - nats_JSONDestroy(json); - json = NULL; - } - - test("Stream config with all required: "); - snprintf(tmp, sizeof(tmp), "{\"name\":\"TEST\",\"retention\":\"workqueue\","\ - "\"max_consumers\":5,\"max_msgs\":10,\"max_bytes\":1000,\"max_msgs_per_subject\":1,\"max_age\":20000000,"\ - "\"discard\":\"new\",\"storage\":\"memory\",\"num_replicas\":3}"); - s = nats_JSONParse(&json, tmp, (int) strlen(tmp)); - IFOK(s, js_unmarshalStreamConfig(json, NULL, &sc)); - testCond((s == NATS_OK) && (sc != NULL) - && (strcmp(sc->Name, "TEST") == 0) - && (sc->Retention == js_WorkQueuePolicy) - && (sc->MaxConsumers == 5) - && (sc->MaxMsgs == 10) - && (sc->MaxBytes == 1000) - && (sc->MaxAge == 20000000) - && (sc->MaxMsgsPerSubject == 1) - && (sc->Discard == js_DiscardNew) - && (sc->Storage == js_MemoryStorage) - && (sc->Replicas == 3)); - js_destroyStreamConfig(sc); - sc = NULL; - nats_JSONDestroy(json); - json = NULL; - - test("Stream config with all: "); - if (snprintf(tmp, sizeof(tmp), "{" - "\"name\":\"TEST\"" - ",\"description\":\"this is my stream\"" - ",\"subjects\":[\"foo\",\"bar\"]" - ",\"retention\":\"workqueue\"" - ",\"max_consumers\":5" - ",\"max_msgs\":10" - ",\"max_bytes\":1000" - ",\"max_age\":20000000" - ",\"max_msgs_per_subject\":1" - ",\"max_msg_size\":1024" - ",\"discard\":\"new\"" - ",\"storage\":\"memory\"" - ",\"num_replicas\":3" - ",\"no_ack\":true" - ",\"template_owner\":\"owner\"" - ",\"duplicate_window\":100000000000" - ",\"placement\":{\"cluster\":\"cluster\",\"tags\":[\"tag1\",\"tag2\"]}" - ",\"mirror\":{\"name\":\"TEST2\",\"opt_start_seq\":10,\"filter_subject\":\"foo\",\"external\":{\"api\":\"my_prefix\",\"deliver\":\"deliver_prefix\"}}" - ",\"sources\":[{\"name\":\"TEST3\",\"opt_start_seq\":20,\"filter_subject\":\"bar\",\"external\":{\"api\":\"my_prefix2\",\"deliver\":\"deliver_prefix2\"}}]" - ",\"sealed\":true" - ",\"deny_delete\":true" - ",\"deny_purge\":true" - ",\"allow_rollup_hdrs\":true" - ",\"republish\":{\"src\":\"foo\",\"dest\":\"bar\"}" - ",\"allow_direct\":true" - ",\"mirror_direct\":true" - ",\"discard_new_per_subject\":true" - ",\"metadata\":{\"foo\":\"bar\"}" - ",\"compression\":\"s2\"" - ",\"first_seq\":9999" - ",\"subject_transform\":{\"src\":\"foo\",\"dest\":\"bar\"}" - ",\"consumer_limits\":{\"inactive_threshold\":1000,\"max_ack_pending\":99}" - "}") >= (int) sizeof(tmp)) - { - abort(); - } - s = nats_JSONParse(&json, tmp, (int) strlen(tmp)); - IFOK(s, js_unmarshalStreamConfig(json, NULL, &sc)); - testCond((s == NATS_OK) && (sc != NULL) - && (sc->Name != NULL) - && (strcmp(sc->Name, "TEST") == 0) - && (sc->Description != NULL) - && (strcmp(sc->Description, "this is my stream") == 0) - && (sc->Subjects != NULL) - && (sc->SubjectsLen == 2) - && (strcmp(sc->Subjects[0], "foo") == 0) - && (strcmp(sc->Subjects[1], "bar") == 0) - && (sc->MaxMsgsPerSubject == 1) - && (sc->MaxMsgSize == 1024) - && (sc->NoAck) - && (strcmp(sc->Template, "owner") == 0) - && (sc->Duplicates == 100000000000) - && (sc->Placement != NULL) - && (strcmp(sc->Placement->Cluster, "cluster") == 0) - && (sc->Placement->TagsLen == 2) - && (strcmp(sc->Placement->Tags[0], "tag1") == 0) - && (strcmp(sc->Placement->Tags[1], "tag2") == 0) - && (sc->Mirror != NULL) - && (strcmp(sc->Mirror->Name, "TEST2") == 0) - && (sc->Mirror->OptStartSeq == 10) - && (strcmp(sc->Mirror->FilterSubject, "foo") == 0) - && (sc->Mirror->External != NULL) - && (strcmp(sc->Mirror->External->APIPrefix, "my_prefix") == 0) - && (strcmp(sc->Mirror->External->DeliverPrefix, "deliver_prefix") == 0) - && (sc->SourcesLen == 1) - && (strcmp(sc->Sources[0]->Name, "TEST3") == 0) - && (sc->Sources[0]->OptStartSeq == 20) - && (strcmp(sc->Sources[0]->FilterSubject, "bar") == 0) - && (sc->Sources[0]->External != NULL) - && (strcmp(sc->Sources[0]->External->APIPrefix, "my_prefix2") == 0) - && (strcmp(sc->Sources[0]->External->DeliverPrefix, "deliver_prefix2") == 0) - && sc->Sealed && sc->DenyDelete && sc->DenyPurge && sc->AllowRollup - && ((sc->RePublish != NULL) - && (sc->RePublish->Source != NULL) - && (strcmp(sc->RePublish->Source, "foo") == 0) - && (sc->RePublish->Destination != NULL) - && (strcmp(sc->RePublish->Destination, "bar") == 0)) - && sc->AllowDirect && sc->MirrorDirect - && sc->DiscardNewPerSubject - && (sc->Metadata.Count == 1) - && (sc->Metadata.List != NULL) - && (strcmp(sc->Metadata.List[0], "foo") == 0) - && (strcmp(sc->Metadata.List[1], "bar") == 0) - && (sc->Compression == js_StorageCompressionS2) - && (sc->FirstSeq == 9999) - && (strcmp(sc->SubjectTransform.Source, "foo") == 0) - && (strcmp(sc->SubjectTransform.Destination, "bar") == 0) - && (sc->ConsumerLimits.InactiveThreshold == 1000) - && (sc->ConsumerLimits.MaxAckPending == 99) - ); - js_destroyStreamConfig(sc); - sc = NULL; - nats_JSONDestroy(json); - json = NULL; -} - -static void -test_JetStreamUnmarshalStreamInfo(void) -{ - natsStatus s; - nats_JSON *json = NULL; - jsStreamInfo *si = NULL; - const char *good[] = { - "{\"cluster\":{\"name\":\"S1\",\"leader\":\"S2\"}}", - "{\"cluster\":{\"name\":\"S1\",\"leader\":\"S2\",\"replicas\":[{\"name\":\"S1\",\"current\":true,\"offline\":false,\"active\":123,\"lag\":456},{\"name\":\"S1\",\"current\":false,\"offline\":true,\"active\":123,\"lag\":456}]}}", - "{\"mirror\":{\"name\":\"M\",\"lag\":123,\"active\":456}}", - "{\"mirror\":{\"name\":\"M\",\"external\":{\"api\":\"MyApi\",\"deliver\":\"deliver.prefix\"},\"lag\":123,\"active\":456}}", - "{\"sources\":[{\"name\":\"S1\",\"lag\":123,\"active\":456}]}", - "{\"sources\":[{\"name\":\"S1\",\"lag\":123,\"active\":456,\"filter_subject\":\"foo\",\"subject_transforms:\":[{\"src\":\"foo\",\"dest\":\"bar\"}]}]}", - "{\"sources\":[{\"name\":\"S1\",\"lag\":123,\"active\":456},{\"name\":\"S2\",\"lag\":123,\"active\":456}]}", - "{\"sources\":[{\"name\":\"S1\",\"external\":{\"api\":\"MyApi\",\"deliver\":\"deliver.prefix\"},\"lag\":123,\"active\":456},{\"name\":\"S2\",\"lag\":123,\"active\":456}]}", - "{\"alternates\":[{\"name\":\"S1\",\"domain\":\"domain\",\"cluster\":\"abc\"}]}", - "{\"alternates\":[{\"name\":\"S1\",\"domain\":\"domain\",\"cluster\":\"abc\"},{\"name\":\"S2\",\"domain\":\"domain\",\"cluster\":\"abc\"}]}", - }; - const char *bad[] = { - "{\"config\":123}", - "{\"config\":{\"retention\":\"bad_policy\"}}", - "{\"config\":{\"discard\":\"bad_policy\"}}", - "{\"config\":{\"storage\":\"bad_policy\"}}", - "{\"config\":{\"placement\":{\"cluster\":123}}}", - "{\"config\":{\"placement\":{\"tags\":123}}}", - "{\"config\":{\"mirror\":{\"name\":123}}}", - "{\"config\":{\"mirror\":{\"opt_start_seq\":\"abc\"}}}", - "{\"config\":{\"mirror\":{\"opt_start_time\":123}}}", - "{\"config\":{\"mirror\":{\"filter_subject\":123}}}", - "{\"config\":{\"mirror\":{\"external\":123}}}", - "{\"config\":{\"mirror\":{\"external\":{\"api\":123}}}}", - "{\"config\":{\"mirror\":{\"external\":{\"deliver\":123}}}}", - "{\"state\":123}", - "{\"state\":{\"messages\":\"abc\"}}", - "{\"state\":{\"bytes\":\"abc\"}}", - "{\"state\":{\"first_seq\":\"abc\"}}", - "{\"state\":{\"first_ts\":\"abc\"}}", - "{\"state\":{\"last_seq\":\"abc\"}}", - "{\"state\":{\"last_ts\":\"abc\"}}", - "{\"state\":{\"num_deleted\":\"abc\"}}", - "{\"state\":{\"deleted\":\"abc\"}}", - "{\"state\":{\"deleted\":[\"abc\",\"def\"]}}", - "{\"state\":{\"lost\":\"abc\"}}", - "{\"state\":{\"lost\":{\"msgs\":\"abc\"}}}", - "{\"state\":{\"lost\":{\"msgs\":[\"abc\",\"def\"]}}}", - "{\"state\":{\"lost\":{\"bytes\":\"abc\"}}}", - "{\"state\":{\"consumer_count\":\"abc\"}}", - "{\"state\":{\"num_subjects\":\"abc\"}}", - "{\"state\":{\"subjects\":\"abc\"}}", - "{\"state\":{\"subjects\":{\"abc\"}}}", - "{\"state\":{\"subjects\":{\"abc\":1,\"def\":\"ghi\"}}}", - "{\"cluster\":123}", - "{\"cluster\":{\"name\":123}}", - "{\"cluster\":{\"name\":\"S1\",\"leader\":123}}", - "{\"cluster\":{\"name\":\"S1\",\"leader\":\"S2\",\"replicas\":123}}", - "{\"cluster\":{\"name\":\"S1\",\"leader\":\"S2\",\"replicas\":[{\"name\":123}]}}", - "{\"mirror\":123}", - "{\"mirror\":{\"name\":123}}", - "{\"mirror\":{\"name\":\"S1\",\"external\":123}}", - "{\"mirror\":{\"name\":\"S1\",\"external\":{\"api\":123}}}", - "{\"sources\":123}", - "{\"sources\":[{\"name\":123}]}", - "{\"sources\":[{\"name\":\"S1\",\"external\":123}]}", - "{\"sources\":[{\"name\":\"S1\",\"external\":{\"deliver\":123}}]}", - "{\"alternates\":123}", - "{\"alternates\":[{\"name\":123}]}", - "{\"alternates\":[{\"name\":\"S1\",\"domain\":123}]}", - "{\"alternates\":[{\"name\":\"S1\",\"domain\":\"domain\",\"cluster\":123}]}", - }; - int i; - char tmp[64]; - - for (i=0; i<(int)(sizeof(good)/sizeof(char*)); i++) - { - snprintf(tmp, sizeof(tmp), "Positive case %d: ", i+1); - test(tmp); - s = nats_JSONParse(&json, good[i], (int) strlen(good[i])); - IFOK(s, js_unmarshalStreamInfo(json, &si)); - testCond((s == NATS_OK) && (si != NULL)); - jsStreamInfo_Destroy(si); - si = NULL; - nats_JSONDestroy(json); - json = NULL; - } - - for (i=0; i<(int)(sizeof(bad)/sizeof(char*)); i++) - { - snprintf(tmp, sizeof(tmp), "Negative case %d: ", i+1); - test(tmp); - s = nats_JSONParse(&json, bad[i], (int) strlen(bad[i])); - IFOK(s, js_unmarshalStreamInfo(json, &si)); - testCond((s != NATS_OK) && (si == NULL)); - nats_JSONDestroy(json); - json = NULL; - nats_clearLastError(); - } -} - -static void -test_JetStreamMarshalStreamConfig(void) -{ - natsStatus s; - jsStreamConfig sc; - jsPlacement p; - jsStreamSource m; - jsExternalStream esm; - jsStreamSource s1; - jsExternalStream esmS1; - jsStreamSource s2; - jsExternalStream esmS2; - natsBuffer *buf = NULL; - nats_JSON *json = NULL; - jsStreamConfig *rsc = NULL; - int64_t optStartTime = 1624583232123456000; - jsRePublish rp; - - test("init bad args: "); - s = jsStreamConfig_Init(NULL); - if (s == NATS_INVALID_ARG) - s = jsPlacement_Init(NULL); - if (s == NATS_INVALID_ARG) - s = jsStreamSource_Init(NULL); - if (s == NATS_INVALID_ARG) - s = jsExternalStream_Init(NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - jsStreamConfig_Init(&sc); - sc.Name = "MyStream"; - sc.Description = "this is my stream"; - sc.Subjects = (const char*[2]){"foo", "bar"}; - sc.SubjectsLen = 2; - sc.Retention = js_InterestPolicy; - sc.MaxConsumers = 1; - sc.MaxMsgs = 2; - sc.MaxBytes = 3; - sc.MaxAge = 4; - sc.MaxMsgSize = 5; - sc.Duplicates = 6; - sc.MaxMsgsPerSubject = 1; - sc.Discard = js_DiscardNew; - sc.Storage = js_MemoryStorage; - sc.Replicas = 3; - sc.NoAck = true; - sc.Template = "template"; - - jsPlacement_Init(&p); - p.Cluster = "MyCluster"; - p.Tags = (const char*[2]){"tag1", "tag2"}; - p.TagsLen = 2; - sc.Placement = &p; - - jsStreamSource_Init(&m); - m.Name = "AStream"; - m.OptStartSeq = 100; - m.OptStartTime = optStartTime; - m.FilterSubject = "foo"; - jsExternalStream_Init(&esm); - esm.APIPrefix = "mirror.prefix"; - esm.DeliverPrefix = "deliver.prefix"; - m.External = &esm; - sc.Mirror = &m; - - jsStreamSource_Init(&s1); - s1.Name = "StreamOne"; - s1.OptStartSeq = 10; - s1.FilterSubject = "stream.one"; - jsExternalStream_Init(&esmS1); - esmS1.APIPrefix = "source1.prefix"; - esmS1.DeliverPrefix = "source1.deliver.prefix"; - s1.External = &esmS1; - - jsStreamSource_Init(&s2); - s2.Name = "StreamTwo"; - s2.OptStartSeq = 20; - s2.FilterSubject = "stream.two"; - jsExternalStream_Init(&esmS2); - esmS2.APIPrefix = "source2.prefix"; - esmS2.DeliverPrefix = "source2.deliver.prefix"; - s2.External = &esmS2; - - sc.Sources = (jsStreamSource *[2]){&s1, &s2}; - sc.SourcesLen = 2; - - // Seal, deny purge, etc.. - sc.Sealed = true; - sc.DenyDelete = true; - sc.DenyPurge = true; - sc.AllowRollup = true; - sc.AllowDirect = true; - sc.MirrorDirect = true; - sc.DiscardNewPerSubject = true; - - test("RePublish init err: "); - s = jsRePublish_Init(NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - s = NATS_OK; - - // Republish - jsRePublish_Init(&rp); - rp.Source = ">"; - rp.Destination = "RP.>"; - rp.HeadersOnly = true; - sc.RePublish = &rp; - - // 2.10 options: Compression, Metadata, etc. - sc.Compression = js_StorageCompressionS2; - sc.Metadata.List = (const char *[]){"k1", "v1", "k2", "v2"}; - sc.Metadata.Count = 2; - sc.FirstSeq = 9999; - sc.SubjectTransform = (jsSubjectTransformConfig) { - .Source = "foo", - .Destination = "bar", - }; - sc.ConsumerLimits = (jsStreamConsumerLimits) { - .InactiveThreshold = 1000, - .MaxAckPending = 99, - }; - - test("Marshal stream config: "); - s = js_marshalStreamConfig(&buf, &sc); - testCond((s == NATS_OK) && (buf != NULL) && (natsBuf_Len(buf) > 0)); - - test("Verify with unmarshal: "); - s = nats_JSONParse(&json, natsBuf_Data(buf), natsBuf_Len(buf)); - IFOK(s, js_unmarshalStreamConfig(json, NULL, &rsc)); - testCond((s == NATS_OK) && (rsc != NULL) - && (rsc->Name != NULL) - && (strcmp(rsc->Name, "MyStream") == 0) - && (rsc->Description != NULL) - && (strcmp(rsc->Description, "this is my stream") == 0) - && (rsc->SubjectsLen == 2) - && (rsc->Subjects != NULL) - && (strcmp(rsc->Subjects[0], "foo") == 0) - && (strcmp(rsc->Subjects[1], "bar") == 0) - && (rsc->Retention == js_InterestPolicy) - && (rsc->MaxConsumers == 1) - && (rsc->MaxMsgs == 2) - && (rsc->MaxBytes == 3) - && (rsc->MaxAge == 4) - && (rsc->MaxMsgSize == 5) - && (rsc->MaxMsgsPerSubject == 1) - && (rsc->Duplicates == 6) - && (rsc->Discard == js_DiscardNew) - && (rsc->Storage == js_MemoryStorage) - && (rsc->Replicas == 3) - && rsc->NoAck - && (strcmp(rsc->Template, "template") == 0) - && (rsc->Placement != NULL) - && (strcmp(rsc->Placement->Cluster, "MyCluster") == 0) - && (rsc->Placement->TagsLen == 2) - && (rsc->Placement->Tags != NULL) - && (strcmp(rsc->Placement->Tags[0], "tag1") == 0) - && (strcmp(rsc->Placement->Tags[1], "tag2") == 0) - && (rsc->Mirror != NULL) - && (strcmp(rsc->Mirror->Name, "AStream") == 0) - && (rsc->Mirror->OptStartSeq == 100) - && (rsc->Mirror->OptStartTime == optStartTime) - && (strcmp(rsc->Mirror->FilterSubject, "foo") == 0) - && (rsc->Mirror->External != NULL) - && (strcmp(rsc->Mirror->External->APIPrefix, "mirror.prefix") == 0) - && (strcmp(rsc->Mirror->External->DeliverPrefix, "deliver.prefix") == 0) - && (rsc->SourcesLen == 2) - && (rsc->Sources != NULL) - && (strcmp(rsc->Sources[0]->Name, "StreamOne") == 0) - && (rsc->Sources[0]->OptStartSeq == 10) - && (strcmp(rsc->Sources[0]->FilterSubject, "stream.one") == 0) - && (rsc->Sources[0]->External != NULL) - && (strcmp(rsc->Sources[0]->External->APIPrefix, "source1.prefix") == 0) - && (strcmp(rsc->Sources[0]->External->DeliverPrefix, "source1.deliver.prefix") == 0) - && (strcmp(rsc->Sources[1]->Name, "StreamTwo") == 0) - && (rsc->Sources[1]->OptStartSeq == 20) - && (strcmp(rsc->Sources[1]->FilterSubject, "stream.two") == 0) - && (rsc->Sources[1]->External != NULL) - && (strcmp(rsc->Sources[1]->External->APIPrefix, "source2.prefix") == 0) - && (strcmp(rsc->Sources[1]->External->DeliverPrefix, "source2.deliver.prefix") == 0) - && rsc->Sealed - && rsc->DenyDelete - && rsc->DenyPurge - && rsc->AllowRollup - && (rsc->RePublish != NULL) - && (rsc->RePublish->Source != NULL) - && (strcmp(rsc->RePublish->Source, ">") == 0) - && (rsc->RePublish->Destination != NULL) - && (strcmp(rsc->RePublish->Destination, "RP.>") == 0) - && rsc->RePublish->HeadersOnly - && rsc->AllowDirect - && rsc->MirrorDirect - && rsc->DiscardNewPerSubject - && (rsc->Compression == js_StorageCompressionS2) - && (rsc->Metadata.Count == 2) - && (strcmp(rsc->Metadata.List[0], "k2") == 0) - && (strcmp(rsc->Metadata.List[1], "v2") == 0) - && (strcmp(rsc->Metadata.List[2], "k1") == 0) - && (strcmp(rsc->Metadata.List[3], "v1") == 0) - && (rsc->FirstSeq == 9999) - && (strcmp(rsc->SubjectTransform.Source, "foo") == 0) - && (strcmp(rsc->SubjectTransform.Destination, "bar") == 0) - && (rsc->ConsumerLimits.InactiveThreshold == 1000) - && (rsc->ConsumerLimits.MaxAckPending == 99) - ); - js_destroyStreamConfig(rsc); - rsc = NULL; - // Check that this does not crash - js_destroyStreamConfig(NULL); - natsBuf_Destroy(buf); - buf = NULL; - nats_JSONDestroy(json); - json = NULL; - - test("WorkQueue policy: "); - jsStreamConfig_Init(&sc); - sc.Name = "TEST"; - sc.Retention = js_WorkQueuePolicy; - s = js_marshalStreamConfig(&buf, &sc); - testCond((s == NATS_OK) && (buf != NULL) && (natsBuf_Len(buf) > 0)); - - test("Verify with unmarshal: "); - s = nats_JSONParse(&json, natsBuf_Data(buf), natsBuf_Len(buf)); - IFOK(s, js_unmarshalStreamConfig(json, NULL, &rsc)); - testCond((s == NATS_OK) && (rsc != NULL) && (rsc->Retention == js_WorkQueuePolicy)); - js_destroyStreamConfig(rsc); - rsc = NULL; - natsBuf_Destroy(buf); - buf = NULL; - nats_JSONDestroy(json); - json = NULL; - - test("Bad retention: "); - jsStreamConfig_Init(&sc); - sc.Name = "TEST"; - sc.Retention = (jsRetentionPolicy) 100; - s = js_marshalStreamConfig(&buf, &sc); - testCond((s == NATS_INVALID_ARG) && (buf == NULL)); - - test("Bad discard: "); - jsStreamConfig_Init(&sc); - sc.Name = "TEST"; - sc.Discard = (jsDiscardPolicy) 100; - s = js_marshalStreamConfig(&buf, &sc); - testCond((s == NATS_INVALID_ARG) && (buf == NULL)); - - test("Bad storage: "); - jsStreamConfig_Init(&sc); - sc.Name = "TEST"; - sc.Storage = (jsStorageType) 100; - s = js_marshalStreamConfig(&buf, &sc); - testCond((s == NATS_INVALID_ARG) && (buf == NULL)); -} - -static void -test_JetStreamUnmarshalConsumerInfo(void) -{ - natsStatus s; - jsConsumerInfo *ci = NULL; - nats_JSON *json = NULL; - const char *good[] = { - "{\"stream_name\":\"TEST\"}", - "{\"name\":\"abc\"}", - "{\"created\":\"2021-06-23T18:22:00.123456789Z\"}", - "{\"delivered\":{\"consumer_seq\":1,\"stream_seq\":1,\"last_active\":\"2021-08-21T11:49:00.123456789Z\"}}", - "{\"ack_floor\":{\"consumer_seq\":1,\"stream_seq\":1,\"last_active\":\"2021-08-21T11:49:00.123456789Z\"}}", - "{\"num_ack_pending\":1}", - "{\"num_redelivered\":1}", - "{\"num_waiting\":1}", - "{\"num_pending\":1}", - "{\"push_bound\":true}", - "{\"config\":{\"deliver_policy\":\"" jsDeliverAllStr "\"}}", - "{\"config\":{\"deliver_policy\":\"" jsDeliverLastStr "\"}}", - "{\"config\":{\"deliver_policy\":\"" jsDeliverNewStr "\"}}", - "{\"config\":{\"deliver_policy\":\"" jsDeliverBySeqStr "\"}}", - "{\"config\":{\"deliver_policy\":\"" jsDeliverByTimeStr "\"}}", - "{\"config\":{\"deliver_policy\":\"" jsDeliverLastPerSubjectStr "\"}}", - "{\"config\":{\"ack_policy\":\"" jsAckNoneStr "\"}}", - "{\"config\":{\"ack_policy\":\"" jsAckAllStr "\"}}", - "{\"config\":{\"ack_policy\":\"" jsAckExplictStr "\"}}", - "{\"config\":{\"replay_policy\":\"" jsReplayInstantStr "\"}}", - "{\"config\":{\"replay_policy\":\"" jsReplayOriginalStr "\"}}", - "{\"config\":{\"deliver_group\":\"queue_name\"}}", - "{\"config\":{\"headers_only\":true}}", - "{\"config\":{\"max_batch\":1}}", - "{\"config\":{\"max_expires\":123456789}}", - "{\"config\":{\"max_bytes\":1024}}", - "{\"config\":{\"inactive_threshold\":123456789}}", - "{\"config\":{\"backoff\":[50000000,250000000]}}", - "{\"config\":{\"num_replicas\":1}}", - "{\"config\":{\"mem_storage\":true}}", - "{\"config\":{\"name\":\"my_name\"}}", - "{\"config\":{\"name\":\"my_name\",\"metadata\":{\"k1\":\"v1\",\"k2\":\"v2\"}}}", - }; - const char *bad[] = { - "{\"stream_name\":123}", - "{\"name\":123}", - "{\"created\":123}", - "{\"config\":123}", - "{\"config\":{\"durable_name\":123}}", - "{\"config\":{\"description\":123}}", - "{\"config\":{\"deliver_subject\":123}}", - "{\"config\":{\"deliver_policy\":\"bad_policy\"}}", - "{\"config\":{\"opt_start_seq\":\"abc\"}}", - "{\"config\":{\"opt_start_time\":123}}", - "{\"config\":{\"ack_policy\":\"bad_policy\"}}", - "{\"config\":{\"ack_wait\":\"abc\"}}", - "{\"config\":{\"max_deliver\":\"abc\"}}", - "{\"config\":{\"filter_subject\":123}}", - "{\"config\":{\"replay_policy\":\"bad_policy\"}}", - "{\"config\":{\"rate_limit_bps\":\"abc\"}}", - "{\"config\":{\"sample_freq\":123}}", - "{\"config\":{\"max_waiting\":\"abc\"}}", - "{\"config\":{\"max_ack_pending\":\"abc\"}}", - "{\"config\":{\"flow_control\":\"abc\"}}", - "{\"config\":{\"idle_heartbeat\":\"abc\"}}", - "{\"config\":{\"deliver_group\":123}}", - "{\"config\":{\"headers_only\":123}}", - "{\"config\":{\"max_batch\":\"1\"}}", - "{\"config\":{\"max_expires\":\"123456789\"}}", - "{\"config\":{\"max_bytes\":\"123456789\"}}", - "{\"config\":{\"inactive_threshold\":\"123456789\"}}", - "{\"config\":{\"backoff\":true}}", - "{\"config\":{\"max_batch\":\"abc\"}}", - "{\"config\":{\"max_expires\":false}}", - "{\"config\":{\"max_bytes\":false}}", - "{\"config\":{\"mem_storage\":\"abc\"}}", - "{\"delivered\":123}", - "{\"delivered\":{\"consumer_seq\":\"abc\"}}", - "{\"delivered\":{\"stream_seq\":\"abc\"}}", - "{\"delivered\":{\"last_active\":123}}", - "{\"ack_floor\":123}", - "{\"ack_floor\":{\"consumer_seq\":\"abc\"}}", - "{\"ack_floor\":{\"stream_seq\":\"abc\"}}", - "{\"ack_floor\":{\"last_active\":123}}", - "{\"num_ack_pending\":\"abc\"}", - "{\"num_redelivered\":\"abc\"}", - "{\"num_waiting\":\"abc\"}", - "{\"num_pending\":\"abc\"}", - "{\"cluster\":123}", - "{\"cluster\":{\"name\":123}}", - "{\"push_bound\":123}", - }; - int i; - char tmp[64]; - - for (i=0; i<(int)(sizeof(good)/sizeof(char*)); i++) - { - snprintf(tmp, sizeof(tmp), "Positive case %d: ", i+1); - test(tmp); - s = nats_JSONParse(&json, good[i], (int) strlen(good[i])); - IFOK(s, js_unmarshalConsumerInfo(json, &ci)); - testCond((s == NATS_OK) && (ci != NULL)); - jsConsumerInfo_Destroy(ci); - ci = NULL; - nats_JSONDestroy(json); - json = NULL; - } - - for (i=0; i<(int)(sizeof(bad)/sizeof(char*)); i++) - { - snprintf(tmp, sizeof(tmp), "Negative case %d: ", i+1); - test(tmp); - s = nats_JSONParse(&json, bad[i], (int) strlen(bad[i])); - IFOK(s, js_unmarshalConsumerInfo(json, &ci)); - testCond((s != NATS_OK) && (ci == NULL)); - nats_JSONDestroy(json); - json = NULL; - nats_clearLastError(); - } -} - -static void -testRespParsing(void) -{ - natsStatus s; - nats_JSON *json = NULL; - natsMsg *msg = NULL; - const char *bad[] = { - "not a JSON", - "{\"error\":123}", - "{\"error\":{\"code\":\"abc\"}}", - "{\"error\":{\"err_code\":\"abc\"}}", - "{\"error\":{\"description\":123}}", - }; - const char *good[] = { - "{\"not_an_error\":123}", - "{\"error\":{\"code\":100}}", - "{\"error\":{\"code\":404,\"description\":\"not found\"}}", - "{\"error\":{\"code\":404,\"err_code\":10014,\"description\":\"not found\"}}", - }; - struct _good { - bool isErr; - int code; - uint16_t err_code; - const char *desc; - }; - const struct _good goodRes[] = { - { false, 0, 0, NULL }, - { true, 100, 0, NULL }, - { true, 404, 0, "not found" }, - { true, 404, JSConsumerNotFoundErr, "not found" }, - }; - char tmp[64]; - int i; - jsApiResponse ar; - - for (i=0; i<(int)(sizeof(bad)/sizeof(char*)); i++) - { - snprintf(tmp, sizeof(tmp), "Negative test %d: ", i+1); - test(tmp); - s = natsMsg_Create(&msg, "foo", NULL, bad[i], (int)strlen(bad[i])); - IFOK(s, js_unmarshalResponse(&ar, &json, msg)); - testCond((s != NATS_OK) && (json == NULL)); - natsMsg_Destroy(msg); - msg = NULL; - js_freeApiRespContent(&ar); - } - for (i=0; i<(int)(sizeof(good)/sizeof(char*)); i++) - { - snprintf(tmp, sizeof(tmp), "Positive test %d: ", i+1); - test(tmp); - s = natsMsg_Create(&msg, "foo", NULL, good[i], (int)strlen(good[i])); - IFOK(s, js_unmarshalResponse(&ar, &json, msg)); - if (s == NATS_OK) - { - if (!goodRes[i].isErr) - { - if (js_apiResponseIsErr(&ar) || (ar.Error.Code != 0) - || (ar.Error.ErrCode != 0) - || !nats_IsStringEmpty(ar.Error.Description)) - { - s = NATS_ERR; - } - } - else - { - if ((goodRes[i].code != ar.Error.Code) - || (goodRes[i].err_code != ar.Error.ErrCode) - || ((goodRes[i].desc == NULL) && !nats_IsStringEmpty(ar.Error.Description)) - || ((goodRes[i].desc != NULL) && nats_IsStringEmpty(ar.Error.Description)) - || ((goodRes[i].desc != NULL) && strcmp(goodRes[i].desc, ar.Error.Description) != 0)) - { - s = NATS_ERR; - } - } - } - testCond((s == NATS_OK) && (json != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - nats_JSONDestroy(json); - json = NULL; - js_freeApiRespContent(&ar); - } - // Check that this is ok - js_freeApiRespContent(NULL); -} - -#define ENSURE_JS_VERSION(major, minor, update) \ -if (!serverVersionAtLeast((major), (minor), (update))) \ -{ \ - char txt[200]; \ - snprintf(txt, sizeof(txt), "Skipping since requires server version of at least %d.%d.%d, got %s: ", \ - (major), (minor), (update), serverVersion); \ - test(txt); \ - testCond(true); \ - return; \ -} - -#define JS_SETUP(major, minor, update) \ -natsConnection *nc = NULL; \ -jsCtx *js = NULL; \ -natsPid pid = NATS_INVALID_PID; \ -char datastore[256] = {'\0'}; \ -char cmdLine[1024] = {'\0'}; \ -\ -ENSURE_JS_VERSION((major), (minor), (update)); \ -\ -_makeUniqueDir(datastore, sizeof(datastore), "datastore_"); \ -test("Start JS Server: "); \ -snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s", datastore);\ -pid = _startServer("nats://127.0.0.1:4222", cmdLine, true); \ -CHECK_SERVER_STARTED(pid); \ -testCond(true); \ -\ -test("Connect: "); \ -s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); \ -testCond(s == NATS_OK); \ -\ -test("Get context: "); \ -s = natsConnection_JetStream(&js, nc, NULL); \ -testCond(s == NATS_OK); - -#define JS_TEARDOWN \ -jsCtx_Destroy(js); \ -natsConnection_Destroy(nc); \ -_stopServer(pid); \ -rmtree(datastore); - -static void -test_JetStreamContext(void) -{ - natsStatus s; - natsConnection *nc = NULL; - jsCtx *js = NULL; - natsPid pid = NATS_INVALID_PID; - jsOptions o; - jsAccountInfo *ai = NULL; - jsErrCode jerr = 0; - char datastore[256] = {'\0'}; - char cmdLine[1024] = {'\0'}; - char confFile[256] = {'\0'}; - - ENSURE_JS_VERSION(2, 3, 3); - - test("Check response parsing:\n"); - testRespParsing(); - testCond(true); - - _makeUniqueDir(datastore, sizeof(datastore), "datastore_"); - - pid = _startServer("nats://127.0.0.1:4222", "", true); - CHECK_SERVER_STARTED(pid); - - test("Connect: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - testCond(s == NATS_OK); - - test("Check JS context invalid args: ") - s = natsConnection_JetStream(NULL, nc, NULL); - if (s == NATS_INVALID_ARG) - s = natsConnection_JetStream(&js, NULL, NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Init options (bad args): "); - s = jsOptions_Init(NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Check JS context with negative wait: ") - jsOptions_Init(&o); - o.Wait = -10; - s = natsConnection_JetStream(&js, nc, &o); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), "negative") != NULL)); - o.Wait = 0; - nats_clearLastError(); - - test("Get context: "); - s = natsConnection_JetStream(&js, nc, NULL); - testCond(s == NATS_OK); - - test("Get account info (bad args): "); - s = js_GetAccountInfo(NULL, js, NULL, NULL); - s = js_GetAccountInfo(&ai, NULL, NULL, NULL); - testCond((s == NATS_INVALID_ARG) && (ai == NULL)); - nats_clearLastError(); - - test("Get account fail: "); - s = js_GetAccountInfo(&ai, js, NULL, &jerr); - testCond((s == NATS_NO_RESPONDERS) && (ai == NULL) && (jerr == JSNotEnabledErr)); - nats_clearLastError(); - jerr = 0; - - jsCtx_Destroy(js); - js = NULL; - // Check this is ok - jsCtx_Destroy(NULL); - natsConnection_Destroy(nc); - nc = NULL; - - // Restart the server with JS enabled - _stopServer(pid); - pid = NATS_INVALID_PID; - test("Start JS server: "); - snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s", datastore); - pid = _startServer("nats://127.0.0.1:4222", cmdLine, true); - CHECK_SERVER_STARTED(pid); - testCond(true); - - test("Connect: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - testCond(s == NATS_OK); - - test("Get context set unknown prefix: "); - o.Prefix = "MyPrefix"; - s = natsConnection_JetStream(&js, nc, &o); - testCond(s == NATS_OK); - - test("Get account info with unknown prefix: "); - s = js_GetAccountInfo(&ai, js, NULL, &jerr); - testCond((s == NATS_NO_RESPONDERS) && (ai == NULL) && (jerr == JSNotEnabledErr)); - nats_clearLastError(); - jerr = 0; - - test("Get account info override prefix: "); - o.Prefix = jsDefaultAPIPrefix; - s = js_GetAccountInfo(&ai, js, &o, &jerr); - testCond((s == NATS_OK) - && (jerr == 0) - && (ai != NULL) - && (ai->Limits.MaxMemory == -1) - && (ai->Limits.MaxStore == -1)); - jsAccountInfo_Destroy(ai); - ai = NULL; - - test("Prefix with trailing '.' works: "); - o.Prefix = "$JS.API."; - s = js_GetAccountInfo(&ai, js, &o, &jerr); - testCond((s == NATS_OK) - && (jerr == 0) - && (ai != NULL) - && (ai->Limits.MaxMemory == -1) - && (ai->Limits.MaxStore == -1)); - jsAccountInfo_Destroy(ai); - ai = NULL; - - jsCtx_Destroy(js); - js = NULL; - - test("Create context with prefix with traling '.': "); - s = natsConnection_JetStream(&js, nc, &o); - IFOK(s, js_GetAccountInfo(&ai, js, NULL, &jerr)); - testCond((s == NATS_OK) - && (jerr == 0) - && (js != NULL) - // Verify we don't touch the provided prefix - && (strcmp(o.Prefix, "$JS.API.") == 0) - && (ai != NULL) - && (ai->Limits.MaxMemory == -1) - && (ai->Limits.MaxStore == -1)); - jsAccountInfo_Destroy(ai); - ai = NULL; - // Check this does not crash - jsAccountInfo_Destroy(NULL); - jsCtx_Destroy(js); - js = NULL; - natsConnection_Destroy(nc); - nc = NULL; - _stopServer(pid); - pid = NATS_INVALID_PID; - - _createConfFile(confFile, sizeof(confFile), - " accounts { "\ - " NOJS { "\ - " users: [ "\ - " {user: ivan, pass: pwd} "\ - " ] "\ - " } "\ - " }\n" - ); - test("Start JS server: "); - snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -c %s", datastore, confFile); - pid = _startServer("nats://127.0.0.1:4222", cmdLine, true); - CHECK_SERVER_STARTED(pid); - testCond(true); - - test("Connect: "); - s = natsConnection_ConnectTo(&nc, "nats://ivan:pwd@127.0.0.1:4222"); - testCond(s == NATS_OK); - - test("Get context: "); - s = natsConnection_JetStream(&js, nc, NULL); - testCond(s == NATS_OK); - - test("Get account info error: "); - s = js_GetAccountInfo(&ai, js, NULL, &jerr); - testCond((s == NATS_ERR) && (ai == NULL) && (jerr == JSNotEnabledForAccountErr) - && (strstr(nats_GetLastError(NULL), "not enabled") != NULL)); - nats_clearLastError(); - - JS_TEARDOWN; - remove(confFile); -} - -static void -test_JetStreamContextDomain(void) -{ - natsStatus s; - natsConnection *nc = NULL; - jsCtx *js = NULL; - natsPid pid = NATS_INVALID_PID; - jsOptions o; - jsAccountInfo *ai = NULL; - jsErrCode jerr = 0; - char datastore[256] = {'\0'}; - char cmdLine[1024] = {'\0'}; - char confFile[256] = {'\0'}; - - ENSURE_JS_VERSION(2, 3, 3); - - _makeUniqueDir(datastore, sizeof(datastore), "datastore_"); - _createConfFile(confFile, sizeof(confFile), - " jetstream: { domain: ABC }\n" - ); - - test("Start JS server: "); - snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -c %s", datastore, confFile); - pid = _startServer("nats://127.0.0.1:4222", cmdLine, true); - CHECK_SERVER_STARTED(pid); - testCond(true); - - test("Connect: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - testCond(s == NATS_OK); - - test("Create context with domain: "); - jsOptions_Init(&o); - o.Domain = "ABC"; - s = natsConnection_JetStream(&js, nc, &o); - if (s == NATS_OK) - { - js_lock(js); - // The option "Domain" should create the following prefix. - // The JS internal option should not have domain set. - if ((strcmp(js->opts.Prefix, "$JS.ABC.API") != 0) - || (js->opts.Domain != NULL)) - { - s = NATS_ERR; - } - js_unlock(js); - } - testCond((s == NATS_OK) && (js != NULL)); - - test("Get account: "); - s = js_GetAccountInfo(&ai, js, NULL, &jerr); - testCond((s == NATS_OK) - && (jerr == 0) - && (ai != NULL) - && (strcmp(ai->Domain, "ABC") == 0) - && (ai->Limits.MaxMemory == -1) - && (ai->Limits.MaxStore == -1)); - jsAccountInfo_Destroy(ai); - ai = NULL; - - test("Get account with wrong domain override: "); - o.Domain = "DEF"; - s = js_GetAccountInfo(&ai, js, &o, &jerr); - testCond((s == NATS_NO_RESPONDERS) && (ai == NULL) && (jerr == JSNotEnabledErr)); - nats_clearLastError(); - jerr = 0; - - jsCtx_Destroy(js); - js = NULL; - - test("Create context with mix prefix/domain: "); - jsOptions_Init(&o); - o.Prefix = "should.not.be.used"; - o.Domain = "ABC"; - s = natsConnection_JetStream(&js, nc, &o); - testCond((s == NATS_OK) && (js != NULL)); - - test("Get account: "); - s = js_GetAccountInfo(&ai, js, NULL, &jerr); - testCond((s == NATS_OK) - && (jerr == 0) - && (ai != NULL) - && (strcmp(ai->Domain, "ABC") == 0) - && (ai->Limits.MaxMemory == -1) - && (ai->Limits.MaxStore == -1)); - jsAccountInfo_Destroy(ai); - ai = NULL; - - jsCtx_Destroy(js); - js = NULL; - - test("Create context without domain: "); - jsOptions_Init(&o); - s = natsConnection_JetStream(&js, nc, &o); - testCond((s == NATS_OK) && (js != NULL)); - - test("Get account with domain override: "); - o.Domain = "ABC"; - s = js_GetAccountInfo(&ai, js, &o, &jerr); - testCond((s == NATS_OK) - && (jerr == 0) - && (ai != NULL) - && (strcmp(ai->Domain, "ABC") == 0) - && (ai->Limits.MaxMemory == -1) - && (ai->Limits.MaxStore == -1)); - jsAccountInfo_Destroy(ai); - ai = NULL; - - test("Get account with domain override (prefix option ignored): "); - o.Prefix = "should.not.be.used"; - o.Domain = "ABC"; - s = js_GetAccountInfo(&ai, js, &o, &jerr); - testCond((s == NATS_OK) - && (jerr == 0) - && (ai != NULL) - && (strcmp(ai->Domain, "ABC") == 0) - && (ai->Limits.MaxMemory == -1) - && (ai->Limits.MaxStore == -1)); - jsAccountInfo_Destroy(ai); - ai = NULL; - - JS_TEARDOWN; - remove(confFile); -} - -static void -_streamsInfoListReq(natsConnection *nc, natsMsg **msg, void *closure) -{ - int *count = (int*) closure; - const char *payload = NULL; - natsMsg *newMsg = NULL; - - if (strstr(natsMsg_GetData(*msg), "stream_list_response") == NULL) - return; - - (*count)++; - if (*count == 1) - { - // Pretend limit is 2 and send 2 simplified stream infos - payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_list_response\",\"total\":5,\"offset\":0,\"limit\":2,"\ - "\"streams\":[{\"config\":{\"name\":\"S1\"}},{\"config\":{\"name\":\"S2\"}}]}"; - } - else if (*count == 2) - { - // Pretend that there is a repeat of a stream name to check - // that we are properly replacing and not leaking memory. - payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_list_response\",\"total\":5,\"offset\":2,\"limit\":2,"\ - "\"streams\":[{\"config\":{\"name\":\"S2\"}},{\"config\":{\"name\":\"S3\"}}]}"; - } - else if (*count == 3) - { - // Pretend that our next page was over the limit (say streams were removed) - // and therefore the server returned no streams (but set offset to total) - payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_list_response\",\"total\":3,\"offset\":3,\"limit\":2,"\ - "\"streams\":[]}"; - } - else - { - // Use original message - return; - } - if (natsMsg_create(&newMsg, (*msg)->subject, (int) strlen((*msg)->subject), NULL, 0, - payload, (int) strlen(payload), 0) == NATS_OK) - { - natsMsg_Destroy(*msg); - *msg = newMsg; - } -} - -static void -_streamsNamesListReq(natsConnection *nc, natsMsg **msg, void *closure) -{ - int *count = (int*) closure; - const char *payload = NULL; - natsMsg *newMsg = NULL; - - if (strstr(natsMsg_GetData(*msg), "stream_names_response") == NULL) - return; - - (*count)++; - if (*count == 1) - { - // Pretend limit is 2 and send 2 stream names - payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_names_response\",\"total\":5,\"offset\":0,\"limit\":2,"\ - "\"streams\":[\"S1\",\"S2\"]}"; - } - else if (*count == 2) - { - // Pretend that there is a repeat of a stream name to check - // that we are properly replacing and not leaking memory. - payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_names_response\",\"total\":5,\"offset\":2,\"limit\":2,"\ - "\"streams\":[\"S2\",\"S3\"]}"; - } - else if (*count == 3) - { - // Pretend that our next page was over the limit (say streams were removed) - // and therefore the server returned no streams (but set offset to total) - payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_names_response\",\"total\":3,\"offset\":3,\"limit\":2,"\ - "\"streams\":null}"; - } - else - { - // Use original message - return; - } - if (natsMsg_create(&newMsg, (*msg)->subject, (int) strlen((*msg)->subject), NULL, 0, - payload, (int) strlen(payload), 0) == NATS_OK) - { - natsMsg_Destroy(*msg); - *msg = newMsg; - } -} - -static void -test_JetStreamMgtStreams(void) -{ - natsStatus s; - jsCtx *js2= NULL; - jsStreamInfo *si = NULL; - jsStreamConfig cfg; - jsErrCode jerr = 0; - natsMsg *resp = NULL; - natsMsg *msg = NULL; - natsSubscription *sub = NULL; - char *desc = NULL; - jsStreamInfoList *siList = NULL; - jsStreamNamesList *snList = NULL; - int count = 0; - jsStreamSource ss; - jsExternalStream se; - jsOptions o; - int i; - - JS_SETUP(2, 3, 3); - - test("Get context 2: "); - // Set some purge options that will be saved in the context. - // They can be overridden when invoking Purge API. - jsOptions_Init(&o); - o.Stream.Purge.Subject = "baz"; - o.Stream.Purge.Sequence = 10; - s = natsConnection_JetStream(&js2, nc, &o); - testCond(s == NATS_OK); - - test("Stream config init, bad args: "); - s = jsStreamConfig_Init(NULL); - testCond(s == NATS_INVALID_ARG); - - test("Stream config init: "); - s = jsStreamConfig_Init(&cfg); - testCond(s == NATS_OK); - - test("Add stream, bad args: "); - s = js_AddStream(&si, NULL, &cfg, NULL, NULL); - testCond((s == NATS_INVALID_ARG) && (si == NULL)); - nats_clearLastError(); - - test("Stream name required: "); - s = js_AddStream(&si, js, NULL, NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_AddStream(&si, js, &cfg, NULL, NULL); - testCond((s == NATS_INVALID_ARG) - && (si == NULL) - && (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL)); - nats_clearLastError(); - - test("Invalid stream name: "); - cfg.Name = "invalid.stream.name"; - s = js_AddStream(&si, js, &cfg, NULL, NULL); - testCond((s == NATS_INVALID_ARG) - && (si == NULL) - && (strstr(nats_GetLastError(NULL), "invalid stream name") != NULL)); - nats_clearLastError(); - - test("Create basic: "); - cfg.Name = "TEST"; - s = js_AddStream(&si, js, &cfg, NULL, &jerr); - testCond((s == NATS_OK) - && (si != NULL) - && (si->Config != NULL) - && (strcmp(si->Config->Name, "TEST") == 0) - && (jerr == 0)); - jsStreamInfo_Destroy(si); - si = NULL; - - test("Destroy null stream info ok: "); - jsStreamInfo_Destroy(NULL); - testCond(true); - - test("Create failed because already used: "); - cfg.Subjects = (const char*[2]){"foo", "bar"}; - cfg.SubjectsLen = 2; - s = js_AddStream(&si, js, &cfg, NULL, &jerr); - testCond((s == NATS_ERR) - && (si == NULL) - && (strstr(nats_GetLastError(NULL), "already in use") != NULL) - && ((jerr == 0) || (jerr == JSStreamNameExistErr))); - nats_clearLastError(); - - if (serverVersionAtLeast(2, 10, 0)) - { - test("Create stream with 2.10 server features: "); - cfg.Name = "TEST210"; - cfg.Subjects = (const char*[]){"foo210"}; - cfg.SubjectsLen = 1; - cfg.Metadata.List = (const char *[]){"k1", "v1", "k2", "v2"}; - cfg.Metadata.Count = 2; - cfg.Compression = js_StorageCompressionS2; - cfg.FirstSeq = 9999; - cfg.SubjectTransform = (jsSubjectTransformConfig) {.Source = "foo210", .Destination = "bar210"}; - cfg.ConsumerLimits = (jsStreamConsumerLimits) {.InactiveThreshold = 1000, .MaxAckPending = 99}; - - s = js_AddStream(&si, js, &cfg, NULL, &jerr); - - testCond((s == NATS_OK) - && (si != NULL) - && (si->Config != NULL) - && (strcmp(si->Config->Name, "TEST210") == 0) - && (si->Config->Metadata.Count == 2) - && (si->Config->Compression == js_StorageCompressionS2) - && (si->Config->FirstSeq == 9999) - && (strcmp(si->Config->SubjectTransform.Source, "foo210") == 0) - && (strcmp(si->Config->SubjectTransform.Destination, "bar210") == 0) - && (si->Config->ConsumerLimits.InactiveThreshold == 1000) - && (si->Config->ConsumerLimits.MaxAckPending == 99) - && (jerr == 0) - ); - jsStreamInfo_Destroy(si); - si = NULL; - } - - jerr = 0; - // Reset config - jsStreamConfig_Init(&cfg); - test("Add stream without getting stream info back: "); - cfg.Name = "TEST2"; - s = js_AddStream(NULL, js, &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (si == NULL)); - - test("Update stream config missing: "); - s = js_UpdateStream(&si, js, NULL, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), jsErrStreamConfigRequired) != NULL)); - nats_clearLastError(); - - test("Update stream stream name missing: "); - cfg.Name = ""; - s = js_UpdateStream(&si, js, &cfg, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL)); - nats_clearLastError(); - - test("Update stream stream name invalid: "); - cfg.Name = "bad.stream.name"; - s = js_UpdateStream(&si, js, &cfg, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), jsErrInvalidStreamName) != NULL)); - nats_clearLastError(); - cfg.Name = "TEST"; - - test("Update stream: "); - cfg.MaxMsgs = 1000; - s = js_UpdateStream(&si, js, &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) - && (si != NULL) - && (si->Config != NULL) - && (si->Config->MaxMsgs == 1000)); - jsStreamInfo_Destroy(si); - si = NULL; - - test("Update stream (not found): "); - cfg.Name = "NOT_FOUND"; - s = js_UpdateStream(&si, js, &cfg, NULL, &jerr); - testCond((s == NATS_NOT_FOUND) - && (jerr == JSStreamNotFoundErr) - && (si == NULL)); - nats_clearLastError(); - - test("Send 1 message: "); - s = natsConnection_Request(&resp, nc, "TEST2", "hello", 5, 1000); - testCond(s == NATS_OK); - natsMsg_Destroy(resp); - resp = NULL; - - test("Get stream info (bad args): "); - s = js_GetStreamInfo(NULL, NULL, "TEST2", NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_GetStreamInfo(NULL, js, "TEST2", NULL, NULL); - testCond(s == NATS_INVALID_ARG); - - test("Get stream info (stream name missing): "); - s = js_GetStreamInfo(&si, js, NULL, NULL, NULL); - testCond((s == NATS_INVALID_ARG) - && (si == NULL) - && (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL)); - nats_clearLastError(); - - test("Get stream info (invalid stream name): "); - s = js_GetStreamInfo(&si, js, "bad.stream.name", NULL, NULL); - testCond((s == NATS_INVALID_ARG) - && (si == NULL) - && (strstr(nats_GetLastError(NULL), jsErrInvalidStreamName) != NULL)); - nats_clearLastError(); - - test("Get stream info: "); - s = js_GetStreamInfo(&si, js, "TEST2", NULL, &jerr); - testCond((s == NATS_OK) - && (jerr == 0) - && (si != NULL) - && (si->Config != NULL) - && (strcmp(si->Config->Subjects[0], "TEST2") == 0) - && (si->State.Msgs == 1)); - jsStreamInfo_Destroy(si); - si = NULL; - - test("Get stream info (not found): "); - s = js_GetStreamInfo(&si, js, "NOT_FOUND", NULL, &jerr); - testCond((s == NATS_NOT_FOUND) - && (jerr == JSStreamNotFoundErr) - && (si == NULL) - && (nats_GetLastError(NULL) == NULL)); - - test("Purge stream (bad args): "); - s = js_PurgeStream(NULL, "TEST2", NULL, &jerr); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Purge stream (stream name missing): "); - s = js_PurgeStream(js, NULL, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_PurgeStream(js, "", NULL, &jerr); - testCond((s == NATS_INVALID_ARG) - && (jerr == 0) - && (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL)); - nats_clearLastError(); - - test("Purge stream (stream name invalid): "); - s = js_PurgeStream(js, "bad.stream.name", NULL, &jerr); - testCond((s == NATS_INVALID_ARG) - && (jerr == 0) - && (strstr(nats_GetLastError(NULL), jsErrInvalidStreamName) != NULL)); - nats_clearLastError(); - - test("Purge stream: "); - s = js_PurgeStream(js, "TEST2", NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Purge stream (not found): "); - s = js_PurgeStream(js, "NOT_FOUND", NULL, &jerr); - testCond((s == NATS_NOT_FOUND) - && (jerr == JSStreamNotFoundErr)); - nats_clearLastError(); - - test("Get stream info (verify purged): "); - s = js_GetStreamInfo(&si, js, "TEST2", NULL, &jerr); - testCond((s == NATS_OK) - && (jerr == 0) - && (si != NULL) - && (si->Config != NULL) - && (strcmp(si->Config->Subjects[0], "TEST2") == 0) - && (si->State.Msgs == 0)); - jsStreamInfo_Destroy(si); - si = NULL; - - test("Create sub to check requests: "); - s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.STREAM.MSG.DELETE.TEST_MSG_DELETE"); - testCond((s == NATS_OK) && (sub != NULL)); - - test("Create stream for delete msg: "); - jsStreamConfig_Init(&cfg); - cfg.Name = "TEST_MSG_DELETE"; - cfg.Subjects = (const char*[1]){"delete.msg"}; - cfg.SubjectsLen = 1; - cfg.Storage = js_MemoryStorage; - s = js_AddStream(NULL, js, &cfg, NULL, &jerr); - testCond(s == NATS_OK); - - test("Send 1 message: "); - s = natsConnection_Request(&resp, nc, "delete.msg", "hello", 5, 1000); - testCond(s == NATS_OK); - natsMsg_Destroy(resp); - resp = NULL; - - test("Delete msg (stream missing): "); - s = js_DeleteMsg(js, NULL, 2, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_DeleteMsg(js, "", 2, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) && (jerr == 0)); - nats_clearLastError(); - - test("Delete msg: "); - s = js_DeleteMsg(js, "TEST_MSG_DELETE", 1, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Check no_erase present: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - IFOK(s, (strstr(natsMsg_GetData(msg), "no_erase") != NULL ? NATS_OK : NATS_ERR)); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - msg = NULL; - - test("Delete msg (not found): "); - s = js_DeleteMsg(js, "TEST_MSG_DELETE", 2, NULL, &jerr); - testCond((s == NATS_ERR) && (jerr == JSSequenceNotFoundErr)); - nats_clearLastError(); - - test("Check no_erase present: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - IFOK(s, (strstr(natsMsg_GetData(msg), "no_erase") != NULL ? NATS_OK : NATS_ERR)); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - msg = NULL; - - test("Send 1 message: "); - s = natsConnection_Request(&resp, nc, "delete.msg", "hello", 5, 1000); - testCond(s == NATS_OK); - natsMsg_Destroy(resp); - resp = NULL; - - test("Erase msg: "); - s = js_EraseMsg(js, "TEST_MSG_DELETE", 2, &o, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Check no_erase absent: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - IFOK(s, (strstr(natsMsg_GetData(msg), "no_erase") != NULL ? NATS_ERR : NATS_OK)); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - msg = NULL; - - test("Erase msg (not found): "); - s = js_EraseMsg(js, "TEST_MSG_DELETE", 3, &o, &jerr); - testCond((s == NATS_ERR) && (jerr == JSSequenceNotFoundErr)); - nats_clearLastError(); - - test("Check no_erase absent: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - IFOK(s, (strstr(natsMsg_GetData(msg), "no_erase") != NULL ? NATS_ERR : NATS_OK)); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - msg = NULL; - - test("Get stream info (verify deleted): "); - s = js_GetStreamInfo(&si, js, "TEST_MSG_DELETE", NULL, &jerr); - testCond((s == NATS_OK) - && (jerr == 0) - && (si != NULL) - && (si->Config != NULL) - && (strcmp(si->Config->Subjects[0], "delete.msg") == 0) - && (si->State.Msgs == 0)); - jsStreamInfo_Destroy(si); - si = NULL; - - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create stream: "); - jsStreamConfig_Init(&cfg); - cfg.Name = "TEST3"; - cfg.Subjects = (const char*[2]){"foo", "bar"}; - cfg.SubjectsLen = 2; - s = js_AddStream(NULL, js, &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Create sub to check purge req: "); - s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.STREAM.PURGE.TEST3"); - testCond(s == NATS_OK); - - test("Purge with options (subj+seq): "); - jsOptions_Init(&o); - // Will purge only messages from "foo" - o.Stream.Purge.Subject = "foo"; - // Purge up-to but do not include this sequence. - o.Stream.Purge.Sequence = 4; - // We care only on the outbound request, not the result of the API call. - js_PurgeStream(js, "TEST3", &o, NULL); - nats_clearLastError(); - s = natsSubscription_NextMsg(&resp, sub, 1000); - testCond((s == NATS_OK) - && (resp != NULL) - && (strncmp("{\"filter\":\"foo\",\"seq\":4}", - natsMsg_GetData(resp), - natsMsg_GetDataLength(resp)) == 0)); - natsMsg_Destroy(resp); - resp = NULL; - - test("Purge without options and check context used: "); - js_PurgeStream(js2, "TEST3", NULL, NULL); - nats_clearLastError(); - s = natsSubscription_NextMsg(&resp, sub, 1000); - testCond((s == NATS_OK) - && (resp != NULL) - && (strncmp("{\"filter\":\"baz\",\"seq\":10}", - natsMsg_GetData(resp), - natsMsg_GetDataLength(resp)) == 0)); - natsMsg_Destroy(resp); - resp = NULL; - - test("Purge with options (seq and keep mutually exclusive): "); - jsOptions_Init(&o); - o.Stream.Purge.Subject = "bar"; - o.Stream.Purge.Sequence = 8; - o.Stream.Purge.Keep = 2; - s = js_PurgeStream(js, "TEST3", &o, &jerr); - testCond((s == NATS_INVALID_ARG) - && (jerr == 0) - && (strstr(nats_GetLastError(NULL), "exclusive") != NULL)); - nats_clearLastError(); - - test("Check no request was sent: "); - s = natsSubscription_NextMsg(&resp, sub, 500); - testCond((s == NATS_TIMEOUT) && (resp == NULL)); - nats_clearLastError(); - - test("Purge with options (subj+keep): "); - jsOptions_Init(&o); - o.Stream.Purge.Subject = "bar"; - // Keep 2 messages in the stream's bar subject space. - o.Stream.Purge.Keep = 2; - // We care only on the outbound request, not the result of the API call. - js_PurgeStream(js, "TEST3", &o, NULL); - nats_clearLastError(); - s = natsSubscription_NextMsg(&resp, sub, 1000); - testCond((s == NATS_OK) - && (resp != NULL) - && (strncmp("{\"filter\":\"bar\",\"keep\":2}", - natsMsg_GetData(resp), - natsMsg_GetDataLength(resp)) == 0)); - natsMsg_Destroy(resp); - resp = NULL; - - test("Delete stream (bad args): "); - s = js_DeleteStream(NULL, "TEST2", NULL, &jerr); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Delete stream (stream name missing): "); - s = js_DeleteStream(js, NULL, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_DeleteStream(js, "", NULL, &jerr); - testCond((s == NATS_INVALID_ARG) - && (jerr == 0) - && (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL)); - nats_clearLastError(); - - test("Delete stream (stream name invalid): "); - s = js_DeleteStream(js, "bad.stream.name", NULL, &jerr); - testCond((s == NATS_INVALID_ARG) - && (jerr == 0) - && (strstr(nats_GetLastError(NULL), jsErrInvalidStreamName) != NULL)); - nats_clearLastError(); - - test("Delete stream (not found): "); - s = js_DeleteStream(js, "NOT_FOUND", NULL, &jerr); - testCond((s == NATS_NOT_FOUND) - && (jerr == JSStreamNotFoundErr)); - nats_clearLastError(); - - test("Delete stream: "); - s = js_DeleteStream(js, "TEST2", NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Get stream info (not found): "); - s = js_GetStreamInfo(&si, js, "TEST2", NULL, &jerr); - testCond((s == NATS_NOT_FOUND) - && (jerr == JSStreamNotFoundErr) - && (si == NULL)); - nats_clearLastError(); - - natsSubscription_Destroy(sub); - sub = NULL; - test("Create sub to check stream info req: "); - s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.STREAM.INFO.TEST3"); - testCond(s == NATS_OK); - - test("StreamInfo with detailed delete: "); - o.Stream.Info.DeletedDetails = true; - IFOK(s, js_GetStreamInfo(&si, js, "TEST3", &o, &jerr)); - IFOK(s, natsSubscription_NextMsg(&resp, sub, 1000)); - testCond((s == NATS_OK) - && (resp != NULL) - && (natsMsg_GetDataLength(resp) > 0) - && (strncmp("{\"deleted_details\":true}", - natsMsg_GetData(resp), - natsMsg_GetDataLength(resp)) == 0)); - jsStreamInfo_Destroy(si); - si = NULL; - natsMsg_Destroy(resp); - resp = NULL; - - test("Create stream with description: "); - jsStreamConfig_Init(&cfg); - cfg.Name = "TEST_DESC"; - cfg.Description = "MyDescription"; - s = js_AddStream(&si, js, &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (si != NULL) && (jerr == 0) - && (si->Config != NULL) - && (si->Config->Description != NULL) - && (strcmp(si->Config->Description, "MyDescription") == 0)); - jsStreamInfo_Destroy(si); - si = NULL; - - test("Create stream with description too long: "); - jsStreamConfig_Init(&cfg); - cfg.Name = "TEST_DESC2"; - desc = malloc(4*1024+2); - for (i=0;i<4*1024+1;i++) - desc[i] = 'a'; - desc[i]='\0'; - cfg.Description = (const char*) desc; - s = js_AddStream(&si, js, &cfg, NULL, &jerr); - testCond((s == NATS_ERR) && (si == NULL) && (jerr == JSStreamInvalidConfig)); - nats_clearLastError(); - - free(desc); - jsCtx_Destroy(js2); - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create stream with wilcards: "); - jsStreamConfig_Init(&cfg); - cfg.Name = "STREAM_WITH_WC"; - cfg.Subjects = (const char*[2]){"foo.>", "bar.*"}; - cfg.SubjectsLen = 2; - s = js_AddStream(&si, js, &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) & (si != NULL) && (si->Config != NULL) - && (si->Config->SubjectsLen == 2) - && (strcmp(si->Config->Subjects[0], "foo.>") == 0) - && (strcmp(si->Config->Subjects[1], "bar.*") == 0)); - jsStreamInfo_Destroy(si); - si = NULL; - - test("List stream infos (bad args): "); - s = js_Streams(NULL, js, NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_Streams(&siList, NULL, NULL, NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Create sub for pagination check: "); - s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.STREAM.LIST"); - testCond(s == NATS_OK); - - natsConn_setFilterWithClosure(nc, _streamsInfoListReq, (void*) &count); - - test("List stream infos: "); - s = js_Streams(&siList, js, NULL, &jerr); - testCond((s == NATS_OK) && (siList != NULL) && (siList->List != NULL) && (siList->Count == 3)); - - natsConn_setFilter(nc, NULL); - - test("Check 1st request: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) - && (strstr(natsMsg_GetData(msg), "offset\":0") != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Check 2nd request: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) - && (strstr(natsMsg_GetData(msg), "offset\":2") != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Check 3rd request: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) - && (strstr(natsMsg_GetData(msg), "offset\":4") != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - natsSubscription_Destroy(sub); - sub = NULL; - - test("Destroy list: "); - // Will see with valgrind if this is doing the right thing - jsStreamInfoList_Destroy(siList); - siList = NULL; - // Check this does not crash - jsStreamInfoList_Destroy(siList); - testCond(true); - - test("List stream infos with filter: "); - jsOptions_Init(&o); - o.Stream.Info.SubjectsFilter = "TEST"; - s = js_Streams(&siList, js, &o, &jerr); - testCond((s == NATS_OK) && (siList != NULL) && (siList->List != NULL) && (siList->Count == 1) - && (strcmp(siList->List[0]->Config->Name, "TEST") == 0)); - jsStreamInfoList_Destroy(siList); - siList = NULL; - - test("List stream infos with filter no match: "); - jsOptions_Init(&o); - o.Stream.Info.SubjectsFilter = "no.match"; - s = js_Streams(&siList, js, &o, &jerr); - testCond((s == NATS_NOT_FOUND) && (siList == NULL)); - - // Do names now - - test("List stream names (bad args): "); - s = js_StreamNames(NULL, js, NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_StreamNames(&snList, NULL, NULL, NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Create sub for pagination check: "); - s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.STREAM.NAMES"); - testCond(s == NATS_OK); - - count = 0; - natsConn_setFilterWithClosure(nc, _streamsNamesListReq, (void*) &count); - - test("List stream names: "); - s = js_StreamNames(&snList, js, NULL, &jerr); - testCond((s == NATS_OK) && (snList != NULL) && (snList->List != NULL) && (snList->Count == 3)); - - natsConn_setFilter(nc, NULL); - - test("Check 1st request: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) - && (strstr(natsMsg_GetData(msg), "offset\":0") != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Check 2nd request: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) - && (strstr(natsMsg_GetData(msg), "offset\":2") != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Check 3rd request: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) - && (strstr(natsMsg_GetData(msg), "offset\":4") != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - natsSubscription_Destroy(sub); - sub = NULL; - - test("Destroy list: "); - // Will see with valgrind if this is doing the right thing - jsStreamNamesList_Destroy(snList); - snList = NULL; - // Check this does not crash - jsStreamNamesList_Destroy(snList); - testCond(true); - - test("List stream names with filter: "); - jsOptions_Init(&o); - o.Stream.Info.SubjectsFilter = "TEST"; - s = js_StreamNames(&snList, js, &o, &jerr); - testCond((s == NATS_OK) && (snList != NULL) && (snList->List != NULL) && (snList->Count == 1) - && (strcmp(snList->List[0], "TEST") == 0)); - jsStreamNamesList_Destroy(snList); - snList = NULL; - - test("List stream names with filter no match: "); - jsOptions_Init(&o); - o.Stream.Info.SubjectsFilter = "no.match"; - s = js_StreamNames(&snList, js, &o, &jerr); - testCond((s == NATS_NOT_FOUND) && (snList == NULL)); - - test("Mirror domain and external set error: "); - jsStreamConfig_Init(&cfg); - cfg.Name = "MDESET"; - jsStreamSource_Init(&ss); - ss.Domain = "Domain"; - jsExternalStream_Init(&se); - se.DeliverPrefix = "some.prefix"; - ss.External = &se; - cfg.Mirror = &ss; - s = js_AddStream(&si, js, &cfg, NULL, NULL); - testCond((s == NATS_INVALID_ARG) && (si == NULL) - && (strstr(nats_GetLastError(NULL), "domain and external are both set") != NULL)); - nats_clearLastError(); - - test("Source domain and external set error: "); - jsStreamConfig_Init(&cfg); - cfg.Name = "SDESET"; - jsStreamSource_Init(&ss); - ss.Domain = "Domain"; - jsExternalStream_Init(&se); - se.DeliverPrefix = "some.prefix"; - ss.External = &se; - cfg.Sources = (jsStreamSource*[1]){&ss}; - cfg.SourcesLen = 1; - s = js_AddStream(&si, js, &cfg, NULL, NULL); - testCond((s == NATS_INVALID_ARG) && (si == NULL) - && (strstr(nats_GetLastError(NULL), "domain and external are both set") != NULL)); - nats_clearLastError(); - - JS_TEARDOWN; -} - -static void -_consumersInfoListReq(natsConnection *nc, natsMsg **msg, void *closure) -{ - int *count = (int*) closure; - const char *payload = NULL; - natsMsg *newMsg = NULL; - - - if (strstr(natsMsg_GetData(*msg), "consumer_list_response") == NULL) - return; - - (*count)++; - if (*count == 1) - { - // Pretend limit is 2 and send 2 simplified consumer infos - payload = "{\"type\":\"io.nats.jetstream.api.v1.consumer_list_response\",\"total\":5,\"offset\":0,\"limit\":2,"\ - "\"consumers\":[{\"stream_name\":\"A\",\"name\":\"a\"},{\"stream_name\":\"A\",\"name\":\"b\"}]}"; - } - else if (*count == 2) - { - // Pretend that there is a repeat of a stream name to check - // that we are properly replacing and not leaking memory. - payload = "{\"type\":\"io.nats.jetstream.api.v1.consumer_list_response\",\"total\":5,\"offset\":2,\"limit\":2,"\ - "\"consumers\":[{\"stream_name\":\"A\",\"name\":\"b\"},{\"stream_name\":\"A\",\"name\":\"c\"}]}"; - } - else if (*count == 3) - { - // Pretend that our next page was over the limit (say streams were removed) - // and therefore the server returned no streams (but set offset to total) - payload = "{\"type\":\"io.nats.jetstream.api.v1.consumer_list_response\",\"total\":3,\"offset\":3,\"limit\":2,"\ - "\"consumers\":[]}"; - } - else - { - // Use original message - return; - } - if (natsMsg_create(&newMsg, (*msg)->subject, (int) strlen((*msg)->subject), NULL, 0, - payload, (int) strlen(payload), 0) == NATS_OK) - { - natsMsg_Destroy(*msg); - *msg = newMsg; - } -} - -static void -_consumerNamesListReq(natsConnection *nc, natsMsg **msg, void *closure) -{ - int *count = (int*) closure; - const char *payload = NULL; - natsMsg *newMsg = NULL; - - if (strstr(natsMsg_GetData(*msg), "consumer_names_response") == NULL) - return; - - (*count)++; - if (*count == 1) - { - // Pretend limit is 2 and send 2 stream names - payload = "{\"type\":\"io.nats.jetstream.api.v1.consumer_names_response\",\"total\":5,\"offset\":0,\"limit\":2,"\ - "\"consumers\":[\"a\",\"b\"]}"; - } - else if (*count == 2) - { - // Pretend that there is a repeat of a stream name to check - // that we are properly replacing and not leaking memory. - payload = "{\"type\":\"io.nats.jetstream.api.v1.consumer_names_response\",\"total\":5,\"offset\":0,\"limit\":2,"\ - "\"consumers\":[\"b\",\"c\"]}"; - } - else if (*count == 3) - { - // Pretend that our next page was over the limit (say streams were removed) - // and therefore the server returned no streams (but set offset to total) - payload = "{\"type\":\"io.nats.jetstream.api.v1.consumer_names_response\",\"total\":3,\"offset\":3,\"limit\":2,"\ - "\"consumers\":[]}"; - } - else - { - // Use original message - return; - } - if (natsMsg_create(&newMsg, (*msg)->subject, (int) strlen((*msg)->subject), NULL, 0, - payload, (int) strlen(payload), 0) == NATS_OK) - { - natsMsg_Destroy(*msg); - *msg = newMsg; - } -} - -static void -test_JetStreamMgtConsumers(void) -{ - natsStatus s; - jsConsumerInfo *ci = NULL; - jsConsumerConfig cfg; - jsErrCode jerr = 0; - natsMsg *resp = NULL; - natsSubscription *sub = NULL; - char *desc = NULL; - jsStreamConfig scfg; - int i; - const char *dlvPoliciesStr[] = { - "\"deliver_policy\":\"" jsDeliverAllStr "\"", - "\"deliver_policy\":\"" jsDeliverLastStr "\"", - "\"deliver_policy\":\"" jsDeliverNewStr "\"", - "\"deliver_policy\":\"" jsDeliverBySeqStr "\"", - "\"deliver_policy\":\"" jsDeliverByTimeStr "\"", - "\"deliver_policy\":\"" jsDeliverLastPerSubjectStr "\"", - }; - jsDeliverPolicy dlvPolicies[] = { - js_DeliverAll, js_DeliverLast, js_DeliverNew, js_DeliverByStartSequence, - js_DeliverByStartTime, js_DeliverLastPerSubject}; - const char *ackPoliciesStr[] = { - "\"ack_policy\":\"" jsAckNoneStr "\"", - "\"ack_policy\":\"" jsAckAllStr "\"", - "\"ack_policy\":\"" jsAckExplictStr "\"", - }; - jsAckPolicy ackPolicies[] = { - js_AckNone, js_AckAll, js_AckExplicit}; - const char *replayPoliciesStr[] = { - "\"replay_policy\":\"" jsReplayInstantStr "\"", - "\"replay_policy\":\"" jsReplayOriginalStr "\"", - }; - jsReplayPolicy replayPolicies[] = { - js_ReplayInstant, js_ReplayOriginal}; - const char *badConsNames[] = { - "foo.bar", - ".foobar", - "foobar.", - "foo bar", - " foobar", - "foobar ", - "foo*bar", - "*foobar", - "foobar*", - "foo>bar", - ">foobar", - "foobar>", - }; - jsConsumerInfoList *ciList = NULL; - jsConsumerNamesList *cnList = NULL; - int count = 0; - natsMsg *msg = NULL; - jsConsumerConfig *cloneCfg = NULL; - - JS_SETUP(2, 9, 0); - - test("Consumer config init, bad args: "); - s = jsConsumerConfig_Init(NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Consumer config init: "); - s = jsConsumerConfig_Init(&cfg); - testCond(s == NATS_OK); - - test("Add consumer, ctx missing: "); - s = js_AddConsumer(&ci, NULL, "MY_STREAM", &cfg, NULL, NULL); - testCond((s == NATS_INVALID_ARG) && (ci == NULL)); - nats_clearLastError(); - - test("Add consumer, config missing: "); - s = js_AddConsumer(&ci, js, "MY_STREAM", NULL, NULL, NULL); - testCond((s == NATS_INVALID_ARG) && (ci == NULL) - && (strstr(nats_GetLastError(NULL), jsErrConsumerConfigRequired) != NULL)); - nats_clearLastError(); - - test("Add consumer, stream name required: "); - s = js_AddConsumer(&ci, js, NULL, &cfg, NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_AddConsumer(&ci, js, "", &cfg, NULL, NULL); - testCond((s == NATS_INVALID_ARG) && (ci == NULL) - && (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL)); - nats_clearLastError(); - - test("Add consumer, stream name invalid: "); - s = js_AddConsumer(&ci, js, "bad.stream.name", &cfg, NULL, NULL); - testCond((s == NATS_INVALID_ARG) && (ci == NULL) - && (strstr(nats_GetLastError(NULL), jsErrInvalidStreamName) != NULL)); - nats_clearLastError(); - - test("Add consumer, invalid durable name: "); - cfg.Durable = "invalid.durable.name"; - s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, NULL); - testCond((s == NATS_INVALID_ARG) && (ci == NULL) - && (strstr(nats_GetLastError(NULL), jsErrInvalidDurableName) != NULL)); - nats_clearLastError(); - - s = NATS_OK; - test("Add consumer, invalid name: "); - jsConsumerConfig_Init(&cfg); - for (i=0; (s == NATS_OK) && (i<(int)(sizeof(badConsNames)/sizeof(char*))); i++) - { - cfg.Name = badConsNames[i]; - s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, NULL); - if ((s == NATS_INVALID_ARG) && (ci == NULL) - && (strstr(nats_GetLastError(NULL), jsErrInvalidConsumerName) != NULL)) - { - nats_clearLastError(); - s = NATS_OK; - } - } - testCond(s == NATS_OK); - - test("Create check sub: "); - s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.CONSUMER.CREATE.MY_STREAM"); - testCond(s == NATS_OK); - - for (i=0; i<6; i++) - { - test("Deliver policy: "); - jsConsumerConfig_Init(&cfg); - cfg.DeliverPolicy = dlvPolicies[i]; - s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_ERR) && (jerr == JSStreamNotFoundErr) && (ci == NULL)); - nats_clearLastError(); - - test("Verify config: "); - s = natsSubscription_NextMsg(&resp, sub, 1000); - testCond((s == NATS_OK) && (resp != NULL) - && (strstr(natsMsg_GetData(resp), dlvPoliciesStr[i]) != NULL)); - natsMsg_Destroy(resp); - resp = NULL; - } - - test("Deliver policy bad: "); - jsConsumerConfig_Init(&cfg); - cfg.DeliverPolicy = (jsDeliverPolicy) 100; - s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) && (jerr == 0) && (ci == NULL)); - nats_clearLastError(); - - for (i=0; i<3; i++) - { - test("Ack policy: "); - jsConsumerConfig_Init(&cfg); - cfg.AckPolicy = ackPolicies[i]; - s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_ERR) && (jerr == JSStreamNotFoundErr) && (ci == NULL)); - nats_clearLastError(); - - test("Verify config: "); - s = natsSubscription_NextMsg(&resp, sub, 1000); - testCond((s == NATS_OK) && (resp != NULL) - && (strstr(natsMsg_GetData(resp), ackPoliciesStr[i]) != NULL)); - natsMsg_Destroy(resp); - resp = NULL; - } - - test("Ack policy bad: "); - jsConsumerConfig_Init(&cfg); - cfg.AckPolicy = (jsAckPolicy) 100; - s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) && (jerr == 0) && (ci == NULL)); - nats_clearLastError(); - - for (i=0; i<2; i++) - { - test("Replay policy: "); - jsConsumerConfig_Init(&cfg); - cfg.ReplayPolicy = replayPolicies[i]; - s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_ERR) && (jerr == JSStreamNotFoundErr) && (ci == NULL)); - nats_clearLastError(); - - test("Verify config: "); - s = natsSubscription_NextMsg(&resp, sub, 1000); - testCond((s == NATS_OK) && (resp != NULL) - && (strstr(natsMsg_GetData(resp), replayPoliciesStr[i]) != NULL)); - natsMsg_Destroy(resp); - resp = NULL; - } - - test("Replay policy bad: "); - jsConsumerConfig_Init(&cfg); - cfg.ReplayPolicy = (jsReplayPolicy) 100; - s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) && (jerr == 0) && (ci == NULL)); - nats_clearLastError(); - - test("Add consumer (non durable): "); - cfg.Durable = NULL; - cfg.Description = "MyDescription"; - cfg.DeliverSubject = "foo"; - cfg.DeliverPolicy = js_DeliverLast; - cfg.OptStartSeq = 100; - cfg.OptStartTime = 1624472520123450000; - cfg.AckPolicy = js_AckExplicit; - cfg.AckWait = 200; - cfg.MaxDeliver = 300; - cfg.FilterSubject = "bar"; - cfg.ReplayPolicy = js_ReplayInstant; - cfg.RateLimit = 400; - cfg.SampleFrequency = "60%%"; - cfg.MaxWaiting = 500; - cfg.MaxAckPending = 600; - cfg.FlowControl = true; - cfg.Heartbeat = 700; - cfg.Replicas = 1; - cfg.MemoryStorage = true; - cfg.Metadata.List = (const char *[]){"key1", "val1", "key2", "val2"}; - cfg.Metadata.Count = 2; - - // We create a consumer with non existing stream, so we - // expect this to fail. We are just checking that the config - // is properly serialized. - s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_ERR) && (jerr == JSStreamNotFoundErr) && (ci == NULL)); - nats_clearLastError(); - - test("Verify config: "); - s = natsSubscription_NextMsg(&resp, sub, 1000); - testCond((s == NATS_OK) && (resp != NULL) - && (strncmp(natsMsg_GetData(resp), - "{\"stream_name\":\"MY_STREAM\","\ - "\"config\":{\"deliver_policy\":\"last\","\ - "\"description\":\"MyDescription\","\ - "\"deliver_subject\":\"foo\","\ - "\"opt_start_seq\":100,"\ - "\"opt_start_time\":\"2021-06-23T18:22:00.12345Z\",\"ack_policy\":\"explicit\","\ - "\"ack_wait\":200,\"max_deliver\":300,\"filter_subject\":\"bar\","\ - "\"metadata\":{\"key1\":\"val1\",\"key2\":\"val2\"},"\ - "\"replay_policy\":\"instant\",\"rate_limit_bps\":400,"\ - "\"sample_freq\":\"60%%\",\"max_waiting\":500,\"max_ack_pending\":600,"\ - "\"flow_control\":true,\"idle_heartbeat\":700,"\ - "\"num_replicas\":1,\"mem_storage\":true}}", - natsMsg_GetDataLength(resp)) == 0)); - natsMsg_Destroy(resp); - resp = NULL; - - if (serverVersionAtLeast(2, 10, 0)) - { - test("Add consumer (non durable, filter subjects): "); - cfg.FilterSubject = NULL; - cfg.FilterSubjects = (const char *[]){"bar1", "bar2"}; - cfg.FilterSubjectsLen = 2; - s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_ERR) && (jerr == JSStreamNotFoundErr) && (ci == NULL)); - nats_clearLastError(); - - test("Verify config: "); - s = natsSubscription_NextMsg(&resp, sub, 1000); - testCond((s == NATS_OK) && (resp != NULL) && (strncmp(natsMsg_GetData(resp), "{\"stream_name\":\"MY_STREAM\"," - "\"config\":{\"deliver_policy\":\"last\"," - "\"description\":\"MyDescription\"," - "\"deliver_subject\":\"foo\"," - "\"opt_start_seq\":100," - "\"opt_start_time\":\"2021-06-23T18:22:00.12345Z\",\"ack_policy\":\"explicit\"," - "\"ack_wait\":200,\"max_deliver\":300,\"filter_subjects\":[\"bar1\",\"bar2\"]," - "\"metadata\":{\"key1\":\"val1\",\"key2\":\"val2\"}," - "\"replay_policy\":\"instant\",\"rate_limit_bps\":400," - "\"sample_freq\":\"60%%\",\"max_waiting\":500,\"max_ack_pending\":600," - "\"flow_control\":true,\"idle_heartbeat\":700," - "\"num_replicas\":1,\"mem_storage\":true}}", - natsMsg_GetDataLength(resp)) == 0)); - natsMsg_Destroy(resp); - resp = NULL; - cfg.FilterSubjects = NULL; - cfg.FilterSubjectsLen = 0; - cfg.FilterSubject = "bar"; - } - - test("Create check sub: "); - natsSubscription_Destroy(sub); - sub = NULL; - s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.CONSUMER.DURABLE.CREATE.MY_STREAM.dur"); - testCond(s == NATS_OK); - - test("Add consumer (durable): "); - cfg.Durable = "dur"; - s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_ERR) && (jerr == JSStreamNotFoundErr) && (ci == NULL)); - nats_clearLastError(); - - test("Verify config: "); - s = natsSubscription_NextMsg(&resp, sub, 1000); - testCond((s == NATS_OK) && (resp != NULL) - && (strncmp(natsMsg_GetData(resp), - "{\"stream_name\":\"MY_STREAM\","\ - "\"config\":{\"deliver_policy\":\"last\","\ - "\"description\":\"MyDescription\","\ - "\"durable_name\":\"dur\",\"deliver_subject\":\"foo\","\ - "\"opt_start_seq\":100,"\ - "\"opt_start_time\":\"2021-06-23T18:22:00.12345Z\",\"ack_policy\":\"explicit\","\ - "\"ack_wait\":200,\"max_deliver\":300,\"filter_subject\":\"bar\","\ - "\"metadata\":{\"key1\":\"val1\",\"key2\":\"val2\"},"\ - "\"replay_policy\":\"instant\",\"rate_limit_bps\":400,"\ - "\"sample_freq\":\"60%%\",\"max_waiting\":500,\"max_ack_pending\":600,"\ - "\"flow_control\":true,\"idle_heartbeat\":700,"\ - "\"num_replicas\":1,\"mem_storage\":true}}", - natsMsg_GetDataLength(resp)) == 0)); - natsMsg_Destroy(resp); - resp = NULL; - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create check sub: "); - natsSubscription_Destroy(sub); - sub = NULL; - s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.CONSUMER.CREATE.MY_STREAM.>"); - testCond(s == NATS_OK); - - test("Add consumer (name): "); - cfg.Durable = NULL; - cfg.Name = "my_name"; - s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_ERR) && (jerr == JSStreamNotFoundErr) && (ci == NULL)); - nats_clearLastError(); - - test("Verify config: "); - s = natsSubscription_NextMsg(&resp, sub, 1000); - testCond((s == NATS_OK) && (resp != NULL) - && (strncmp(natsMsg_GetData(resp), - "{\"stream_name\":\"MY_STREAM\","\ - "\"config\":{\"deliver_policy\":\"last\","\ - "\"name\":\"my_name\","\ - "\"description\":\"MyDescription\","\ - "\"deliver_subject\":\"foo\","\ - "\"opt_start_seq\":100,"\ - "\"opt_start_time\":\"2021-06-23T18:22:00.12345Z\",\"ack_policy\":\"explicit\","\ - "\"ack_wait\":200,\"max_deliver\":300,\"filter_subject\":\"bar\","\ - "\"metadata\":{\"key1\":\"val1\",\"key2\":\"val2\"},"\ - "\"replay_policy\":\"instant\",\"rate_limit_bps\":400,"\ - "\"sample_freq\":\"60%%\",\"max_waiting\":500,\"max_ack_pending\":600,"\ - "\"flow_control\":true,\"idle_heartbeat\":700,"\ - "\"num_replicas\":1,\"mem_storage\":true}}", - natsMsg_GetDataLength(resp)) == 0)); - natsMsg_Destroy(resp); - resp = NULL; - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create stream: "); - jsStreamConfig_Init(&scfg); - scfg.Name = "MY_STREAM"; - scfg.Subjects = (const char*[1]){"bar.>"}; - scfg.SubjectsLen = 1; - s = js_AddStream(NULL, js, &scfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Add consumer (name): "); - jsConsumerConfig_Init(&cfg); - cfg.Name = "my_name"; - cfg.DeliverSubject = "mn.foo"; - cfg.FilterSubject = "bar.>"; -#define TIME_20350101 ((int64_t)2051251200L * (int64_t)1000000000L) - if (serverVersionAtLeast(2, 11, 0)) - { - cfg.PauseUntil = TIME_20350101; - } - s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (ci != NULL) - && (strcmp(ci->Stream, "MY_STREAM") == 0) - && (strcmp(ci->Name, "my_name") == 0) - && (strcmp(ci->Config->Name, "my_name") == 0) - && ((cfg.PauseUntil == 0) || (ci->Paused && ci->PauseRemaining > 0))); - jsConsumerInfo_Destroy(ci); - ci = NULL; - - if (serverVersionAtLeast(2, 11, 0)) - { - test("Pause consumer: "); - jsConsumerPauseResponse *cpr = NULL; - s = js_PauseConsumer(&cpr, js, "MY_STREAM", "my_name", TIME_20350101, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (cpr != NULL) - && cpr->Paused - && (cpr->PauseUntil == TIME_20350101) - && (cpr->PauseRemaining > 0)); - jsConsumerPauseResponse_Destroy(cpr); - cpr = NULL; - - test("Verify consumer paused with GetInfo: "); - s = js_GetConsumerInfo(&ci, js, "MY_STREAM", "my_name", NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (ci != NULL) - && ci->Paused - && (ci->PauseRemaining > 0)); - jsConsumerInfo_Destroy(ci); - ci = NULL; - - test("Unpause consumer: "); - s = js_PauseConsumer(&cpr, js, "MY_STREAM", "my_name", 0, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (cpr != NULL) - && !cpr->Paused - && (cpr->PauseUntil == 0) - && (cpr->PauseRemaining == 0)); - jsConsumerPauseResponse_Destroy(cpr); - cpr = NULL; - } - - test("Add consumer (durable): "); - jsConsumerConfig_Init(&cfg); - cfg.Durable = "dur"; - cfg.DeliverSubject = "foo"; - cfg.FilterSubject = "bar.>"; - s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (ci != NULL) - && (strcmp(ci->Stream, "MY_STREAM") == 0) - && (strcmp(ci->Name, "dur") == 0)); - jsConsumerInfo_Destroy(ci); - ci = NULL; - - test("Publish: "); - s = natsConnection_Publish(nc, "bar.baz", "hello", 5); - testCond(s == NATS_OK); - - test("Get consumer info (bad args): "); - s = js_GetConsumerInfo(NULL, js, "MY_STREAM", "dur", NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_GetConsumerInfo(&ci, NULL, "MY_STREAM", "dur", NULL, &jerr); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Get consumer info, stream name missing: "); - s = js_GetConsumerInfo(&ci, js, NULL, "dur", NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_GetConsumerInfo(&ci, js, "", "dur", NULL, &jerr); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL)); - nats_clearLastError(); - - test("Get consumer info, stream name invalid: "); - s = js_GetConsumerInfo(&ci, js, "bad.stream.name", "dur", NULL, &jerr); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), jsErrInvalidStreamName) != NULL)); - nats_clearLastError(); - - test("Get consumer info, consumer name missing: "); - s = js_GetConsumerInfo(&ci, js, "MY_STREAM", NULL, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_GetConsumerInfo(&ci, js, "MY_STREAM", "", NULL, &jerr); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), jsErrConsumerNameRequired) != NULL)); - nats_clearLastError(); - - test("Get consumer info, consumer name invalid: "); - s = js_GetConsumerInfo(&ci, js, "MY_STREAM", "bad.consumer.name", NULL, &jerr); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), jsErrInvalidConsumerName) != NULL)); - nats_clearLastError(); - - test("Get consumer info: "); - s = js_GetConsumerInfo(&ci, js, "MY_STREAM", "dur", NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (ci != NULL) - && (strcmp(ci->Stream, "MY_STREAM") == 0) - && (strcmp(ci->Name, "dur") == 0) - && (ci->Config != NULL) - && (ci->Config->FilterSubject != NULL) - && (strcmp(ci->Config->FilterSubject, "bar.>") == 0)); - jsConsumerInfo_Destroy(ci); - ci = NULL; - - test("Get consumer info (not found): "); - s = js_GetConsumerInfo(&ci, js, "MY_STREAM", "dur2", NULL, &jerr); - testCond((s == NATS_NOT_FOUND) - && (jerr == JSConsumerNotFoundErr) - && (ci == NULL) - && (nats_GetLastError(NULL) == NULL)); - - test("Delete consumer (bad args): "); - s = js_DeleteConsumer(NULL, "MY_STREAM", "dur", NULL, &jerr); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Delete consumer, stream name missing: "); - s = js_DeleteConsumer(js, NULL, "dur", NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_DeleteConsumer(js, "", "dur", NULL, &jerr); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL)); - nats_clearLastError(); - - test("Delete consumer, stream name invalid: "); - s = js_DeleteConsumer(js, "bad.stream.name", "dur", NULL, &jerr); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), jsErrInvalidStreamName) != NULL)); - nats_clearLastError(); - - test("Delete consumer, consumer name missing: "); - s = js_DeleteConsumer(js, "MY_STREAM", NULL, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_DeleteConsumer(js, "MY_STREAM", "", NULL, &jerr); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), jsErrConsumerNameRequired) != NULL)); - nats_clearLastError(); - - test("Get consumer info, consumer name invalid: "); - s = js_DeleteConsumer(js, "MY_STREAM", "bad.consumer.name", NULL, &jerr); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), jsErrInvalidConsumerName) != NULL)); - nats_clearLastError(); - - test("Delete consumer: "); - s = js_DeleteConsumer(js, "MY_STREAM", "dur", NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Delete consumer (not found): "); - s = js_DeleteConsumer(js, "MY_STREAM", "dur2", NULL, &jerr); - testCond((s == NATS_NOT_FOUND) - && (jerr == JSConsumerNotFoundErr)); - nats_clearLastError(); - - test("Create consumer with description: "); - jsConsumerConfig_Init(&cfg); - cfg.Description = "MyDescription"; - cfg.DeliverSubject = "desc1"; - s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (ci != NULL) && (jerr == 0) - && (ci->Config != NULL) - && (ci->Config->Description != NULL) - && (strcmp(ci->Config->Description, "MyDescription") == 0)); - jsConsumerInfo_Destroy(ci); - ci = NULL; - - test("Create consumer with description too long: "); - jsConsumerConfig_Init(&cfg); - desc = malloc(4*1024+2); - for (i=0;i<4*1024+1;i++) - desc[i] = 'a'; - desc[i]='\0'; - cfg.Description = (const char*) desc; - cfg.DeliverSubject = "desc2"; - s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_ERR) && (ci == NULL) && (jerr == JSConsumerDescriptionTooLongErr)); - nats_clearLastError(); - free(desc); - - test("Create consumer: "); - jsConsumerConfig_Init(&cfg); - cfg.Durable = "update_push_consumer"; - cfg.DeliverSubject = "bar"; - cfg.FilterSubject = "bar.baz"; - cfg.AckPolicy = js_AckExplicit; - s = js_AddConsumer(NULL, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - // We will update this config and pass it to UpdateConsumer - // and check that the result after UpdateConsumer matches... - // Currently, server supports these fields: - // description, ack_wait, max_deliver, sample_freq, max_ack_pending, max_waiting and headers_only - cfg.Description = "my description"; - cfg.AckWait = NATS_SECONDS_TO_NANOS(2); - cfg.MaxDeliver = 1; - cfg.SampleFrequency = "30"; - cfg.MaxAckPending = 10; - cfg.HeadersOnly = true; - - test("Update consumer, config missing: "); - s = js_UpdateConsumer(&ci, js, "MY_STREAM", NULL, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) && (ci == NULL) - && (strstr(nats_GetLastError(NULL), jsErrConsumerConfigRequired) != NULL)); - nats_clearLastError(); - - test("Update consumer, stream name missing: "); - s = js_UpdateConsumer(&ci, js, NULL, &cfg, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_UpdateConsumer(&ci, js, "", &cfg, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) && (ci == NULL) && (jerr == 0) - && (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL)); - nats_clearLastError(); - - test("Update consumer, stream name invalid: "); - s = js_UpdateConsumer(&ci, js, "bad.stream.name", &cfg, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) && (ci == NULL) && (jerr == 0) - && (strstr(nats_GetLastError(NULL), jsErrInvalidStreamName) != NULL)); - nats_clearLastError(); - - test("Update needs durable name: "); - cfg.Durable = NULL; - s = js_UpdateConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - if (s == NATS_INVALID_ARG) - { - cfg.Durable = ""; - s = js_UpdateConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - } - testCond((s == NATS_INVALID_ARG) && (ci == NULL) && (jerr == 0) - && (strstr(nats_GetLastError(NULL), jsErrDurRequired) != NULL)); - nats_clearLastError(); - cfg.Durable = "update_push_consumer"; - cfg.FilterSubject = "bar.bat"; - - test("Update works ok: "); - s = js_UpdateConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (ci != NULL) && (ci->Config != NULL) - && (strcmp(ci->Config->Description, "my description") == 0) - && (ci->Config->AckWait == NATS_SECONDS_TO_NANOS(2)) - && (ci->Config->MaxDeliver == 1) - && (strcmp(ci->Config->SampleFrequency, "30") == 0) - && (ci->Config->MaxAckPending == 10) - && (ci->Config->HeadersOnly) - && (ci->Config->FilterSubject != NULL) - && (strcmp(ci->Config->FilterSubject, "bar.bat") == 0)); - jsConsumerInfo_Destroy(ci); - ci = NULL; - - if (serverVersionAtLeast(2, 10, 0)) - { - test("Update (filter subjects) works ok: "); - cfg.FilterSubject = NULL; - cfg.FilterSubjects = (const char *[]){"bar1", "bar2"}; - cfg.FilterSubjectsLen = 2; - s = js_UpdateConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (ci != NULL) && (ci->Config != NULL) - && (strcmp(ci->Config->Description, "my description") == 0) - && (ci->Config->AckWait == NATS_SECONDS_TO_NANOS(2)) - && (ci->Config->MaxDeliver == 1) - && (strcmp(ci->Config->SampleFrequency, "30") == 0) - && (ci->Config->MaxAckPending == 10) - && (ci->Config->HeadersOnly) - && (ci->Config->FilterSubject == NULL) - && (ci->Config->FilterSubjectsLen == 2) - && (ci->Config->FilterSubjects != NULL) - && (strcmp(ci->Config->FilterSubjects[0], "bar1") == 0) - && (strcmp(ci->Config->FilterSubjects[1], "bar2") == 0)); - jsConsumerInfo_Destroy(ci); - ci = NULL; - cfg.FilterSubject = "bar.bat"; - cfg.FilterSubjects = NULL; - cfg.FilterSubjectsLen = 0; - } - - test("Add pull consumer: "); - jsConsumerConfig_Init(&cfg); - cfg.Durable = "update_pull_consumer"; - cfg.AckPolicy = js_AckExplicit; - cfg.MaxWaiting = 1; - s = js_AddConsumer(NULL, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - cfg.Description = "my description"; - cfg.AckWait = NATS_SECONDS_TO_NANOS(2); - cfg.MaxDeliver = 1; - cfg.SampleFrequency = "30"; - cfg.MaxAckPending = 10; - cfg.HeadersOnly = true; - cfg.MaxRequestBatch = 10; - cfg.MaxRequestExpires = NATS_SECONDS_TO_NANOS(2); - - test("Update works ok: "); - s = js_UpdateConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (ci != NULL) && (ci->Config != NULL) - && (strcmp(ci->Config->Description, "my description") == 0) - && (ci->Config->AckWait == NATS_SECONDS_TO_NANOS(2)) - && (ci->Config->MaxDeliver == 1) - && (strcmp(ci->Config->SampleFrequency, "30") == 0) - && (ci->Config->MaxAckPending == 10) - && (ci->Config->HeadersOnly) - && (ci->Config->MaxRequestBatch == 10) - && (ci->Config->MaxRequestExpires == NATS_SECONDS_TO_NANOS(2))); - jsConsumerInfo_Destroy(ci); - ci = NULL; - - test("Create stream: "); - jsStreamConfig_Init(&scfg); - scfg.Name = "A"; - scfg.Subjects = (const char*[2]){"foo", "bar"}; - scfg.SubjectsLen = 2; - s = js_AddStream(NULL, js, &scfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Create sub: "); - s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.CONSUMER.>"); - testCond(s == NATS_OK); - - test("Ephemeral with name: "); - jsConsumerConfig_Init(&cfg); - cfg.Name = "a"; - cfg.AckPolicy = js_AckExplicit; - cfg.InactiveThreshold = NATS_SECONDS_TO_NANOS(1); - s = js_AddConsumer(&ci, js, "A", &cfg, NULL, &jerr); - testCond((s == NATS_OK) - && (strcmp(ci->Name, "a") == 0) - && (ci->Config->Durable == NULL) - && (ci->Config->InactiveThreshold == NATS_SECONDS_TO_NANOS(1)) - && (jerr == 0)); - jsConsumerInfo_Destroy(ci); - ci = NULL; - - test("Check: "); - s = natsSubscription_NextMsg(&resp, sub, 1000); - testCond((s == NATS_OK) - && (resp != NULL) - && (strcmp(natsMsg_GetSubject(resp), "$JS.API.CONSUMER.CREATE.A.a") == 0) - && (strstr(natsMsg_GetData(resp), "\"name\":\"a\"") != NULL)); - natsMsg_Destroy(resp); - resp = NULL; - - test("Durable: "); - jsConsumerConfig_Init(&cfg); - cfg.Durable = "b"; - cfg.AckPolicy = js_AckExplicit; - s = js_AddConsumer(&ci, js, "A", &cfg, NULL, &jerr); - testCond((s == NATS_OK) - && (strcmp(ci->Name, "b") == 0) - && (strcmp(ci->Config->Durable, "b") == 0) - && (ci->Config->InactiveThreshold == 0) - && (jerr == 0)); - jsConsumerInfo_Destroy(ci); - ci = NULL; - - test("Check: "); - s = natsSubscription_NextMsg(&resp, sub, 1000); - testCond((s == NATS_OK) - && (resp != NULL) - && (strcmp(natsMsg_GetSubject(resp), "$JS.API.CONSUMER.DURABLE.CREATE.A.b") == 0) - && (strstr(natsMsg_GetData(resp), "\"name\":") == NULL)); - natsMsg_Destroy(resp); - resp = NULL; - - test("Durable and Name same: "); - jsConsumerConfig_Init(&cfg); - cfg.Name = "b"; - cfg.Durable = "b"; - cfg.AckPolicy = js_AckExplicit; - s = js_AddConsumer(&ci, js, "A", &cfg, NULL, &jerr); - testCond((s == NATS_OK) - && (strcmp(ci->Name, "b") == 0) - && (strcmp(ci->Config->Durable, "b") == 0) - && (ci->Config->InactiveThreshold == 0) - && (jerr == 0)); - jsConsumerInfo_Destroy(ci); - ci = NULL; - - test("Check subject: "); - s = natsSubscription_NextMsg(&resp, sub, 1000); - testCond((s == NATS_OK) - && (resp != NULL) - && (strcmp(natsMsg_GetSubject(resp), "$JS.API.CONSUMER.CREATE.A.b") == 0) - && (strstr(natsMsg_GetData(resp), "\"name\":\"b\"") != NULL)); - natsMsg_Destroy(resp); - resp = NULL; - - test("Durable and Name different: "); - jsConsumerConfig_Init(&cfg); - cfg.Name = "x"; - cfg.Durable = "y"; - cfg.AckPolicy = js_AckExplicit; - s = js_AddConsumer(&ci, js, "A", &cfg, NULL, &jerr); - testCond((s == NATS_ERR) - && (jerr == JSConsumerDurableNameNotMatchSubjectErr) - && (strstr(nats_GetLastError(NULL), "consumer name in subject does not match durable name in request") != NULL)); - - test("Check subject: "); - s = natsSubscription_NextMsg(&resp, sub, 1000); - testCond((s == NATS_OK) - && (resp != NULL) - && (strcmp(natsMsg_GetSubject(resp), "$JS.API.CONSUMER.CREATE.A.x") == 0) - && (strstr(natsMsg_GetData(resp), "\"name\":\"x\"") != NULL)); - natsMsg_Destroy(resp); - resp = NULL; - - test("Ephemeral with filter: "); - jsConsumerConfig_Init(&cfg); - cfg.Name = "c"; - cfg.AckPolicy = js_AckExplicit; - cfg.FilterSubject = "bar"; - s = js_AddConsumer(&ci, js, "A", &cfg, NULL, &jerr); - testCond((s == NATS_OK) - && (strcmp(ci->Name, "c") == 0) - && (ci->Config->Durable == NULL) - && (ci->Config->InactiveThreshold != 0) - && (jerr == 0)); - jsConsumerInfo_Destroy(ci); - ci = NULL; - - test("Check subject: "); - s = natsSubscription_NextMsg(&resp, sub, 1000); - testCond((s == NATS_OK) - && (resp != NULL) - && (strcmp(natsMsg_GetSubject(resp), "$JS.API.CONSUMER.CREATE.A.c.bar") == 0) - && (strstr(natsMsg_GetData(resp), "\"name\":\"c\"") != NULL)); - natsMsg_Destroy(resp); - resp = NULL; - - test("Legacy ephemeral: "); - jsConsumerConfig_Init(&cfg); - cfg.AckPolicy = js_AckExplicit; - cfg.FilterSubject = "bar"; - s = js_AddConsumer(&ci, js, "A", &cfg, NULL, &jerr); - testCond((s == NATS_OK) - && (ci->Name != NULL) - && (ci->Config->Durable == NULL) - && (ci->Config->InactiveThreshold != 0) - && (jerr == 0)); - jsConsumerInfo_Destroy(ci); - ci = NULL; - - test("Check subject: "); - s = natsSubscription_NextMsg(&resp, sub, 1000); - testCond((s == NATS_OK) - && (resp != NULL) - && (strcmp(natsMsg_GetSubject(resp), "$JS.API.CONSUMER.CREATE.A") == 0) - && (strstr(natsMsg_GetData(resp), "\"name\":") == NULL)); - natsMsg_Destroy(resp); - resp = NULL; - - natsSubscription_Destroy(sub); - sub = NULL; - - test("List consumer infos (bad args): "); - s = js_Consumers(NULL, js, "A", NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_Consumers(&ciList, NULL, "A", NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_Consumers(&ciList, js, NULL, NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_Consumers(&ciList, js, "", NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_Consumers(&ciList, js, "invalid.stream.name", NULL, NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("List consumer infos (unknown stream): "); - s = js_Consumers(&ciList, js, "unknown", NULL, &jerr); - testCond((s == NATS_NOT_FOUND) && (jerr == JSStreamNotFoundErr)); - nats_clearLastError(); - - test("Create sub for pagination check: "); - s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.CONSUMER.LIST.A"); - testCond(s == NATS_OK); - - natsConn_setFilterWithClosure(nc, _consumersInfoListReq, (void*) &count); - - test("List consumers infos: "); - s = js_Consumers(&ciList, js, "A", NULL, &jerr); - testCond((s == NATS_OK) && (ciList != NULL) && (ciList->List != NULL) && (ciList->Count == 3)); - - natsConn_setFilter(nc, NULL); - - test("Check 1st request: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) - && (strstr(natsMsg_GetData(msg), "offset\":0") != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Check 2nd request: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) - && (strstr(natsMsg_GetData(msg), "offset\":2") != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Check 3rd request: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) - && (strstr(natsMsg_GetData(msg), "offset\":4") != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - natsSubscription_Destroy(sub); - sub = NULL; - - test("Destroy list: "); - // Will see with valgrind if this is doing the right thing - jsConsumerInfoList_Destroy(ciList); - ciList = NULL; - // Check this does not crash - jsConsumerInfoList_Destroy(ciList); - testCond(true); - - // Do names now - - test("List consumer names (bad args): "); - s = js_ConsumerNames(NULL, js, "A", NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_ConsumerNames(&cnList, NULL, "A", NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_ConsumerNames(&cnList, js, NULL, NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_ConsumerNames(&cnList, js, "", NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_ConsumerNames(&cnList, js, "invalid.stream.name", NULL, NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("List consumer names (unknown stream): "); - s = js_ConsumerNames(&cnList, js, "unknown", NULL, &jerr); - testCond((s == NATS_NOT_FOUND) && (jerr == JSStreamNotFoundErr)); - nats_clearLastError(); - - test("Create sub for pagination check: "); - s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.CONSUMER.NAMES.A"); - testCond(s == NATS_OK); - - count = 0; - natsConn_setFilterWithClosure(nc, _consumerNamesListReq, (void*) &count); - - test("List consumer names: "); - s = js_ConsumerNames(&cnList, js, "A", NULL, &jerr); - testCond((s == NATS_OK) && (cnList != NULL) && (cnList->List != NULL) && (cnList->Count == 3)); - - natsConn_setFilter(nc, NULL); - - test("Check 1st request: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) - && (strstr(natsMsg_GetData(msg), "offset\":0") != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Check 2nd request: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) - && (strstr(natsMsg_GetData(msg), "offset\":2") != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Check 3rd request: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) - && (strstr(natsMsg_GetData(msg), "offset\":4") != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - natsSubscription_Destroy(sub); - sub = NULL; - - test("Destroy list: "); - // Will see with valgrind if this is doing the right thing - jsConsumerNamesList_Destroy(cnList); - cnList = NULL; - // Check this does not crash - jsConsumerNamesList_Destroy(cnList); - testCond(true); - - test("Check clone: "); - jsConsumerConfig_Init(&cfg); - cfg.Name = "A"; - cfg.Durable = "B"; - cfg.Description = "C"; - cfg.FilterSubject = "D"; - cfg.FilterSubjects = (const char*[]){"E", "F"}; - cfg.FilterSubjectsLen = 2; - cfg.SampleFrequency = "G"; - cfg.DeliverSubject = "H"; - cfg.DeliverGroup = "I"; - cfg.BackOff = (int64_t[]){NATS_MILLIS_TO_NANOS(50), NATS_MILLIS_TO_NANOS(250)}; - cfg.BackOffLen = 2; - s = js_cloneConsumerConfig(&cfg, &cloneCfg); - testCond((s == NATS_OK) && (cloneCfg != NULL) - && (cloneCfg->Name != NULL) && (strcmp(cloneCfg->Name, "A") == 0) - && (cloneCfg->Durable != NULL) && (strcmp(cloneCfg->Durable, "B") == 0) - && (cloneCfg->Description != NULL) && (strcmp(cloneCfg->Description, "C") == 0) - && (cloneCfg->FilterSubject != NULL) && (strcmp(cloneCfg->FilterSubject, "D") == 0) - && (cloneCfg->FilterSubject != NULL) && (strcmp(cloneCfg->FilterSubject, "D") == 0) - && (cloneCfg->FilterSubjectsLen == 2) - && (cloneCfg->FilterSubjects != NULL) - && (strcmp(cloneCfg->FilterSubjects[0], "E") == 0) - && (strcmp(cloneCfg->FilterSubjects[1], "F") == 0) - && (cloneCfg->SampleFrequency != NULL) && (strcmp(cloneCfg->SampleFrequency, "G") == 0) - && (cloneCfg->DeliverSubject != NULL) && (strcmp(cloneCfg->DeliverSubject, "H") == 0) - && (cloneCfg->DeliverGroup != NULL) && (strcmp(cloneCfg->DeliverGroup, "I") == 0) - && (cloneCfg->BackOffLen == 2) - && (cloneCfg->BackOff != NULL) - && (cloneCfg->BackOff[0] == NATS_MILLIS_TO_NANOS(50)) - && (cloneCfg->BackOff[1] == NATS_MILLIS_TO_NANOS(250))); - js_destroyConsumerConfig(cloneCfg); - - JS_TEARDOWN; -} - -static void -test_JetStreamPublish(void) -{ - natsStatus s; - natsConnection *nc = NULL; - jsCtx *js = NULL; - natsPid pid = NATS_INVALID_PID; - jsStreamConfig cfg; - jsPubOptions opts; - jsErrCode jerr = 0; - jsPubAck *pa = NULL; - natsMsg *msg = NULL; - char datastore[256] = {'\0'}; - char cmdLine[1024] = {'\0'}; - char confFile[256] = {'\0'}; - jsOptions o; - - ENSURE_JS_VERSION(2, 3, 5); - - _makeUniqueDir(datastore, sizeof(datastore), "datastore_"); - _createConfFile(confFile, sizeof(confFile), - " jetstream: { domain: ABC }\n" - ); - - test("Start JS server: "); - snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -c %s", datastore, confFile); - pid = _startServer("nats://127.0.0.1:4222", cmdLine, true); - CHECK_SERVER_STARTED(pid); - testCond(true); - - test("Connect: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - testCond(s == NATS_OK); - - test("Get context: "); - jsOptions_Init(&o); - o.Domain = "ABC"; - s = natsConnection_JetStream(&js, nc, &o); - testCond(s == NATS_OK); - - test("Stream config init: "); - s = jsStreamConfig_Init(&cfg); - testCond(s == NATS_OK); - - test("Add stream: "); - cfg.Name = "TEST"; - cfg.Subjects = (const char*[2]){"foo", "bar"}; - cfg.SubjectsLen = 2; - s = js_AddStream(NULL, js, &cfg, NULL, NULL); - testCond(s == NATS_OK); - - test("Publish bad args: "); - s = js_Publish(NULL, NULL, NULL, "hello", 5, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_Publish(NULL, js, NULL, "hello", 5, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_Publish(NULL, js, "", "hello", 5, NULL, &jerr); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Options init bad args: "); - s = jsPubOptions_Init(NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Publish bad ttl option: "); - s = jsPubOptions_Init(&opts); - if (s == NATS_OK) - { - opts.MaxWait = -10; - s = js_Publish(NULL, js, "foo", "hello", 5, &opts, &jerr); - } - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), "negative") != NULL)); - nats_clearLastError(); - - test("Publish data: "); - opts.MaxWait = 3000; - s = js_Publish(NULL, js, "foo", "hello", 5, &opts, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Publish data with pubAck: "); - jsPubOptions_Init(&opts); - opts.MsgId = "msg2"; - s = js_Publish(&pa, js, "foo", "hello", 5, &opts, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (pa != NULL) - && (strcmp(pa->Domain, "ABC") == 0) - && (strcmp(pa->Stream, "TEST") == 0) - && (pa->Sequence == 2) - && !pa->Duplicate); - jsPubAck_Destroy(pa); - pa = NULL; - // Check this is ok - jsPubAck_Destroy(NULL); - - test("Publish message with same msgID: "); - s = js_Publish(&pa, js, "foo", "hello", 5, &opts, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (pa != NULL) && pa->Duplicate); - jsPubAck_Destroy(pa); - pa = NULL; - - test("Publish with wrong expected stream: "); - jsPubOptions_Init(&opts); - opts.ExpectStream = "WRONG"; - s = js_Publish(&pa, js, "foo", "hello", 5, &opts, &jerr); - testCond((s == NATS_ERR) && ((jerr == 0) || (jerr == JSStreamNotMatchErr)) && (pa == NULL)); - jerr = 0; - - test("Publish with wrong expected sequence: "); - jsPubOptions_Init(&opts); - opts.ExpectLastSeq = 4; - s = js_Publish(&pa, js, "foo", "hello", 5, &opts, &jerr); - testCond((s == NATS_ERR) && ((jerr == 0) || (jerr == JSStreamWrongLastSequenceErr)) && (pa == NULL)); - jerr = 0; - - test("Publish with wrong expected message ID: "); - jsPubOptions_Init(&opts); - opts.ExpectLastMsgId = "WRONG"; - s = js_Publish(&pa, js, "foo", "hello", 5, &opts, &jerr); - testCond((s == NATS_ERR) && ((jerr == 0) || (jerr == JSStreamWrongLastMsgIDErr)) && (pa == NULL)); - jerr = 0; - - test("Publish 1 msg on bar: "); - jsPubOptions_Init(&opts); - s = js_Publish(&pa, js, "bar", "hello", 5, &opts, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (pa != NULL) - && (strcmp(pa->Stream, "TEST") == 0) - && (pa->Sequence == 3) - && !pa->Duplicate); - jsPubAck_Destroy(pa); - pa = NULL; - - test("Publish with wrong expected subj sequence: "); - jsPubOptions_Init(&opts); - // There should be 3 messages now, with "foo, 1", "foo, 2" and "bar, 3" - // We are going to send on "foo" and say that last expected msg seq on "foo" - // is 3, which is wrong, so should fail. - opts.ExpectLastSubjectSeq = 3; - s = js_Publish(&pa, js, "foo", "hello", 5, &opts, &jerr); - testCond((s == NATS_ERR) && ((jerr == 0) || (jerr == JSStreamWrongLastSequenceErr)) && (pa == NULL)); - jerr = 0; - nats_clearLastError(); - - test("Publish with correct expected subj sequence: "); - // Now set last expected for subject to 2, and it should be ok, and the sequence will be 4. - opts.ExpectLastSubjectSeq = 2; - s = js_Publish(&pa, js, "foo", "hello", 5, &opts, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (pa != NULL) - && (strcmp(pa->Stream, "TEST") == 0) - && (pa->Sequence == 4) - && !pa->Duplicate); - jsPubAck_Destroy(pa); - pa = NULL; - - // ---- Same than above but with PublishMsg variant - test("Recreate stream: "); - s = js_DeleteStream(js, "TEST", NULL, NULL); - IFOK(s, js_AddStream(NULL, js, &cfg, NULL, NULL)); - testCond(s == NATS_OK); - - test("Publish bad args: "); - s = js_PublishMsg(NULL, NULL, NULL, NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_PublishMsg(NULL, js, NULL, NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_PublishMsg(NULL, NULL, msg, NULL, NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Create msg: "); - s = natsMsg_Create(&msg, "foo", NULL, "hello", 5); - testCond(s == NATS_OK); - - test("Publish bad ttl option: "); - s = jsPubOptions_Init(&opts); - if (s == NATS_OK) - { - opts.MaxWait = -10; - s = js_PublishMsg(NULL, js, msg, &opts, &jerr); - } - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), "negative") != NULL)); - nats_clearLastError(); - - test("Publish data: "); - opts.MaxWait = 3000; - s = js_PublishMsg(NULL, js, msg, &opts, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Publish data with pubAck: "); - jsPubOptions_Init(&opts); - opts.MsgId = "msg2"; - s = js_PublishMsg(&pa, js, msg, &opts, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (pa != NULL) - && (strcmp(pa->Stream, "TEST") == 0) - && (pa->Sequence == 2) - && !pa->Duplicate); - jsPubAck_Destroy(pa); - pa = NULL; - natsMsg_Destroy(msg); - msg = NULL; - natsMsg_Create(&msg, "foo", NULL, "hello", 5); - - test("Publish message with same msgID: "); - s = js_PublishMsg(&pa, js, msg, &opts, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (pa != NULL) && pa->Duplicate); - jsPubAck_Destroy(pa); - pa = NULL; - natsMsg_Destroy(msg); - msg = NULL; - natsMsg_Create(&msg, "foo", NULL, "hello", 5); - - test("Publish with wrong expected stream: "); - jsPubOptions_Init(&opts); - opts.ExpectStream = "WRONG"; - s = js_PublishMsg(&pa, js, msg, &opts, &jerr); - testCond((s == NATS_ERR) && ((jerr == 0) || (jerr == JSStreamNotMatchErr)) && (pa == NULL)); - jerr = 0; - natsMsg_Destroy(msg); - msg = NULL; - natsMsg_Create(&msg, "foo", NULL, "hello", 5); - - test("Publish with wrong expected sequence: "); - jsPubOptions_Init(&opts); - opts.ExpectLastSeq = 4; - s = js_PublishMsg(&pa, js, msg, &opts, &jerr); - testCond((s == NATS_ERR) && ((jerr == 0) || (jerr == JSStreamWrongLastSequenceErr)) && (pa == NULL)); - jerr = 0; - natsMsg_Destroy(msg); - msg = NULL; - natsMsg_Create(&msg, "foo", NULL, "hello", 5); - - test("Publish with wrong expected message ID: "); - jsPubOptions_Init(&opts); - opts.ExpectLastMsgId = "WRONG"; - s = js_PublishMsg(&pa, js, msg, &opts, &jerr); - testCond((s == NATS_ERR) && ((jerr == 0) || (jerr == JSStreamWrongLastMsgIDErr)) && (pa == NULL)); - jerr = 0; - natsMsg_Destroy(msg); - msg = NULL; - - test("Publish 1 msg on bar: "); - jsPubOptions_Init(&opts); - natsMsg_Create(&msg, "bar", NULL, "hello", 5); - s = js_PublishMsg(&pa, js, msg, &opts, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (pa != NULL) - && (strcmp(pa->Stream, "TEST") == 0) - && (pa->Sequence == 3) - && !pa->Duplicate); - jsPubAck_Destroy(pa); - pa = NULL; - natsMsg_Destroy(msg); - msg = NULL; - - test("Publish with wrong expected subj sequence: "); - jsPubOptions_Init(&opts); - natsMsg_Create(&msg, "foo", NULL, "hello", 5); - // There should be 3 messages now, with "foo, 1", "foo, 2" and "bar, 3" - // We are going to send on "foo" and say that last expected msg seq on "foo" - // is 3, which is wrong, so should fail. - opts.ExpectLastSubjectSeq = 3; - s = js_PublishMsg(&pa, js, msg, &opts, &jerr); - testCond((s == NATS_ERR) && ((jerr == 0) || (jerr == JSStreamWrongLastSequenceErr)) && (pa == NULL)); - jerr = 0; - natsMsg_Destroy(msg); - msg = NULL; - - test("Publish with correct expected subj sequence: "); - natsMsg_Create(&msg, "foo", NULL, "hello", 5); - // Now set last expected for subject to 2, and it should be ok, and the sequence will be 4. - opts.ExpectLastSubjectSeq = 2; - s = js_PublishMsg(&pa, js, msg, &opts, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (pa != NULL) - && (strcmp(pa->Stream, "TEST") == 0) - && (pa->Sequence == 4) - && !pa->Duplicate); - jsPubAck_Destroy(pa); - pa = NULL; - natsMsg_Destroy(msg); - msg = NULL; - - JS_TEARDOWN; - remove(confFile); -} - -static void -_jsPubAckErrHandler(jsCtx *js, jsPubAckErr *pae, void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - - natsMutex_Lock(args->m); - args->sum++; - if (strcmp(natsMsg_GetData(pae->Msg), "fail1") == 0) - { - if ((pae->Err == NATS_ERR) - && ((pae->ErrCode == 0) || (pae->ErrCode == JSStreamWrongLastMsgIDErr)) - && (strstr(pae->ErrText, "wrong last") != NULL)) - { - args->status = NATS_ERR; - } - } - else if (strcmp(natsMsg_GetData(pae->Msg), "fail2") == 0) - { - // Resend only once - if (args->sum == 2) - js_PublishMsgAsync(js, &(pae->Msg), NULL); - } - else if (strcmp(natsMsg_GetData(pae->Msg), "fail3") == 0) - { - while (!args->done) - natsCondition_Wait(args->c, args->m); - - // Destroy context - jsCtx_Destroy(js); - // Notify that we are done - args->closed = true; - natsCondition_Broadcast(args->c); - } - else if (strcmp(natsMsg_GetData(pae->Msg), "block") == 0) - { - while (!args->done) - natsCondition_Wait(args->c, args->m); - nats_Sleep(500); - - args->closed = true; - natsCondition_Broadcast(args->c); - } - else if (strcmp(natsMsg_GetData(pae->Msg), "destroyed") == 0) - { - // Notify that we are in the callback. - args->msgReceived = true; - natsCondition_Broadcast(args->c); - - // Now wait to be notified that the context was destroyed. - while (!args->closed) - natsCondition_Wait(args->c, args->m); - - // Then access the message content again to make sure that message - // is still valid. - if (strcmp(natsMsg_GetData(pae->Msg), "destroyed") != 0) - args->status = NATS_ERR; - - // Notify that we are done. - args->done = true; - natsCondition_Broadcast(args->c); - } - else if ((pae->Err == NATS_NO_RESPONDERS) || (pae->Err == NATS_TIMEOUT)) - { - args->msgReceived = true; - natsCondition_Broadcast(args->c); - } - natsMutex_Unlock(args->m); -} - -static void -test_JetStreamPublishAsync(void) -{ - natsStatus s; - natsSubscription *sub= NULL; - jsOptions o; - jsStreamConfig cfg; - jsPubOptions opts; - natsMsg *msg = NULL; - natsMsg *cmsg = NULL; - natsMsg *msg1 = NULL; - natsMsg *msg2 = NULL; - const char *val = NULL; - const char **keys = NULL; - int keysCount = 0; - struct threadArg args; - int i; - bool ok1 = false; - bool ok2 = false; - natsMsgList pending; - - JS_SETUP(2, 3, 3); - jsCtx_Destroy(js); - js = NULL; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - test("Create control sub: "); - s = natsConnection_SubscribeSync(&sub, nc, "foo"); - testCond(s == NATS_OK); - - test("Prepare JS options: "); - s = jsOptions_Init(&o); - if (s == NATS_OK) - { - o.PublishAsync.ErrHandler = _jsPubAckErrHandler; - o.PublishAsync.ErrHandlerClosure = &args; - } - testCond(s == NATS_OK); - - test("Get context: "); - s = natsConnection_JetStream(&js, nc, &o); - testCond(s == NATS_OK); - - test("Stream config init: "); - s = jsStreamConfig_Init(&cfg); - testCond(s == NATS_OK); - - test("Add stream: "); - cfg.Name = "foo"; - s = js_AddStream(NULL, js, &cfg, NULL, NULL); - testCond(s == NATS_OK); - - test("Publish bad args: "); - s = js_PublishAsync(NULL, NULL, "hello", 5, NULL); - if (s == NATS_INVALID_ARG) - s = js_PublishAsync(js, NULL, "hello", 5, NULL); - if (s == NATS_INVALID_ARG) - s = js_PublishAsync(js, "", "hello", 5, NULL); - if (s == NATS_INVALID_ARG) - s = js_PublishAsync(NULL, "foo", "hello", 5, NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("PublishAsyncComplete bad args: "); - s = js_PublishAsyncComplete(NULL, NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("PublishAsyncComplete with no pending: "); - s = js_PublishAsyncComplete(js, NULL); - testCond(s == NATS_OK); - - test("Publish data: "); - s = js_PublishAsync(js, "foo", "ok1", 3, NULL); - IFOK(s, js_PublishAsyncComplete(js, NULL)); - testCond(s == NATS_OK); - - test("Check pub msg no header and reply set: "); - s = natsSubscription_NextMsg(&cmsg, sub, 1000); - testCond((s == NATS_OK) && (cmsg != NULL) - && !nats_IsStringEmpty(natsMsg_GetReply(cmsg)) - && (natsMsgHeader_Keys(cmsg, &keys, &keysCount) == NATS_NOT_FOUND)); - natsMsg_Destroy(cmsg); - - test("Publish msg (bad args): "); - s = js_PublishMsgAsync(NULL, NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_PublishMsgAsync(NULL, &msg, NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Publish msg: "); - s = jsPubOptions_Init(&opts); - if (s == NATS_OK) - opts.MsgId = "msgID"; - IFOK(s, natsMsg_Create(&msg, "foo", NULL, "ok2", 3)); - IFOK(s, js_PublishMsgAsync(js, &msg, &opts)); - // Check that library took ownership of message by checking - // that msg is now NULL. - testCond((s == NATS_OK) && (msg == NULL)); - - test("Check pub msg reply set: "); - s = natsSubscription_NextMsg(&cmsg, sub, 1000); - testCond((s == NATS_OK) && (cmsg != NULL) - && !nats_IsStringEmpty(natsMsg_GetReply(cmsg))); - - test("Check msg ID header set: "); - s = natsMsgHeader_Get(cmsg, jsMsgIdHdr, &val); - testCond((s == NATS_OK) && (strcmp(val, "msgID") == 0)); - natsMsg_Destroy(cmsg); - val = NULL; - - test("Wait for complete (bad args): "); - opts.MaxWait = -1000; - s = js_PublishAsyncComplete(js, &opts); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Wait for complete: "); - opts.MaxWait = 1000; - s = js_PublishAsyncComplete(js, &opts); - testCond(s == NATS_OK); - - test("Send fails due to wrong last ID: "); - args.status = NATS_OK; - opts.MsgId = NULL; - opts.ExpectLastMsgId = "wrong"; - s = natsMsg_Create(&msg, "foo", NULL, "fail1", 5); - IFOK(s, js_PublishMsgAsync(js, &msg, &opts)); - testCond((s == NATS_OK) && (msg == NULL)); - - test("Check pub msg reply set: "); - s = natsSubscription_NextMsg(&cmsg, sub, 1000); - testCond((s == NATS_OK) && (cmsg != NULL) - && !nats_IsStringEmpty(natsMsg_GetReply(cmsg))); - - test("Check msg ID header not set: "); - s = natsMsgHeader_Get(cmsg, jsMsgIdHdr, &val); - testCond((s == NATS_NOT_FOUND) && (val == NULL)); - - test("Check expect last msg ID header set: "); - s = natsMsgHeader_Get(cmsg, jsExpectedLastMsgIdHdr, &val); - testCond((s == NATS_OK) && (val != NULL) && (strcmp(val, "wrong") == 0)); - natsMsg_Destroy(cmsg); - val = NULL; - - test("Wait for complete: "); - s = js_PublishAsyncComplete(js, NULL); - testCond(s == NATS_OK); - - test("Check cb got proper failure: ") - natsMutex_Lock(args.m); - s = ((args.status == NATS_ERR) && (args.sum == 1) ? NATS_OK : NATS_ERR); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - test("Send new failed message, will be resent in cb: "); - s = js_PublishAsync(js, "foo", "fail2", 5, &opts); - testCond(s == NATS_OK); - - test("Wait complete: ") - s = js_PublishAsyncComplete(js, NULL); - if (s == NATS_OK) - { - natsMutex_Lock(args.m); - s = (args.sum == 3 ? NATS_OK : NATS_ERR); - natsMutex_Unlock(args.m); - } - testCond(s == NATS_OK); - - test("Send new failed messages which will block cb: "); - s = js_PublishAsync(js, "foo", "fail3", 5, &opts); - // Send another message, which should not be delivered to CB - // since we will destroy context from CB on releasing CB - // after fail3 msg is processed. - IFOK(s, js_PublishAsync(js, "foo", "fail4", 5, &opts)); - testCond(s == NATS_OK); - - test("Check complete timeout: "); - opts.MaxWait = 100; - s = js_PublishAsyncComplete(js, &opts); - testCond(s == NATS_TIMEOUT); - nats_clearLastError(); - - test("Release cb which will destroy context: "); - s = NATS_OK; - natsMutex_Lock(args.m); - args.done = true; - natsCondition_Broadcast(args.c); - while ((s != NATS_TIMEOUT) && !args.closed) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - test("Check that last msg was not delivered to CB: "); - natsMutex_Lock(args.m); - // cb has seen: fail1, fail2 twice, fail3, so sum == 4 - s = (args.sum == 4 ? NATS_OK: NATS_ERR); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - js = NULL; - jsOptions_Init(&o); - test("Stall wait bad args: "); - o.PublishAsync.StallWait = -10; - s = natsConnection_JetStream(&js, nc, &o); - testCond((s == NATS_INVALID_ARG) && (js == NULL)); - nats_clearLastError(); - - test("Recreate context: "); - o.PublishAsync.MaxPending = 1; - o.PublishAsync.StallWait = 100; - o.PublishAsync.ErrHandler = _jsPubAckErrHandler; - o.PublishAsync.ErrHandlerClosure = &args; - s = natsConnection_JetStream(&js, nc, &o); - testCond(s == NATS_OK); - - test("Block CB: "); - natsMutex_Lock(args.m); - args.done = false; - args.closed = false; - natsMutex_Unlock(args.m); - // Pass options so that we add an expected last msg ID which will - // cause failure. - jsPubOptions_Init(&opts); - opts.ExpectLastMsgId = "WRONG"; - s = js_PublishAsync(js, "foo", "block", 5, &opts); - testCond(s == NATS_OK); - - test("Send should fail due to stall: "); - s = js_PublishAsync(js, "foo", "stalled", 7, NULL); - testCond((s == NATS_TIMEOUT) - && (strstr(nats_GetLastError(NULL), "too many outstanding") != NULL)); - nats_clearLastError(); - - // Release CB, which will sleep a bit before returning, so that we - // have time to start a publish here that we will check gets unstalled. - // Artificially increase the stallWait so that we don't flap on Travis/etc.. - natsMutex_Lock(js->mu); - js->opts.PublishAsync.StallWait = 10000; - natsMutex_Unlock(js->mu); - // should unstall the publish that we are going to make here. - natsMutex_Lock(args.m); - args.done = true; - natsCondition_Broadcast(args.c); - natsMutex_Unlock(args.m); - - test("Pub will stall: "); - s = js_PublishAsync(js, "foo", "ok", 2, NULL); - testCond(s == NATS_OK); - - test("Wait complete: "); - s = js_PublishAsyncComplete(js, NULL); - testCond(s == NATS_OK); - - test("Wait for CB to return: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.closed) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - test("Msg needs to be destroyed on failure: "); - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - nc = NULL; - s = natsMsg_Create(&msg, "foo", NULL, "conclosed", 9); - IFOK(s, js_PublishMsgAsync(js, &msg, NULL)); - testCond((s == NATS_CONNECTION_CLOSED) && (msg != NULL)); - nats_clearLastError(); - - test("Msg destroy: "); - natsMsg_Destroy(msg); - testCond(true); - - jsCtx_Destroy(js); - js = NULL; - - test("Connect: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - testCond(s == NATS_OK); - - test("Create context: "); - s = jsOptions_Init(&o); - if (s == NATS_OK) - { - o.PublishAsync.ErrHandler = _jsPubAckErrHandler; - o.PublishAsync.ErrHandlerClosure = &args; - } - IFOK(s, natsConnection_JetStream(&js, nc, &o)); - testCond((s == NATS_OK) && (js != NULL)); - - test("Publish async no responders: "); - s = js_PublishAsync(js, "no.responder.check", "hello", 5, NULL); - if (s == NATS_OK) - { - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.msgReceived) - s = natsCondition_TimedWait(args.c, args.m, 2000); - args.msgReceived = false; - natsMutex_Unlock(args.m); - } - testCond(s == NATS_OK); - - test("Enqueue message with bad subject: "); - s = natsMsg_Create(&msg, "some.subject", NULL, "hello", 5); - if (s == NATS_OK) - { - natsSubscription *rsub; - - js_lock(js); - rsub = js->rsub; - js_unlock(js); - - _waitSubPending(rsub, 0); - - natsSub_Lock(rsub); - rsub->msgList.head = msg; - rsub->msgList.tail = msg; - rsub->msgList.msgs = 1; - rsub->msgList.bytes = natsMsg_dataAndHdrLen(msg); - natsCondition_Signal(rsub->cond); - natsSub_Unlock(rsub); - - // Message is owned by subscription, do not destroy it here. - } - testCond(s == NATS_OK); - - test("Publish async cb received non existent pid: "); - { - char subj[64]; - snprintf(subj, sizeof(subj), "%sabcdefgh", js->rpre); - s = natsConnection_Publish(nc, subj, NULL, 0); - } - testCond(s == NATS_OK); - - test("Produce failed message: "); - jsPubOptions_Init(&opts); - opts.ExpectStream = "WRONG"; - natsMutex_Lock(args.m); - args.done = false; - args.closed = false; - args.msgReceived = false; - args.status = NATS_OK; - natsMutex_Unlock(args.m); - // Pass options with wrong expected stream, so pub will fail. - s = js_PublishAsync(js, "foo", "destroyed", 9, &opts); - testCond(s == NATS_OK); - - test("Wait for msg in CB: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.msgReceived) - s = natsCondition_TimedWait(args.c, args.m, 2000); - args.msgReceived = false; - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - test("Destroy context, notify CB: "); - jsCtx_Destroy(js); - js = NULL; - natsMutex_Lock(args.m); - args.closed = true; - natsCondition_Broadcast(args.c); - natsMutex_Unlock(args.m); - testCond(true); - - test("Wait for CB to return: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.done) - s = natsCondition_TimedWait(args.c, args.m, 2000); - if (s == NATS_OK) - s = args.status; - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - test("Reply subject can be set: "); - s = natsConnection_JetStream(&js, nc, NULL); - IFOK(s, natsMsg_Create(&msg, "bar", "baz", "bat", 3)); - IFOK(s, js_PublishMsgAsync(js, &msg, NULL)); - testCond((s == NATS_OK) && (msg == NULL)); - - test("Wait complete: "); - s = js_PublishAsyncComplete(js, NULL); - testCond(s == NATS_OK); - - test("Publish async: "); - _stopServer(pid); - pid = NATS_INVALID_PID; - s = natsMsg_Create(&msg1, "foo", NULL, "hello1", 6); - IFOK(s, natsMsg_Create(&msg2, "foo", NULL, "hello2", 6)); - IFOK(s, js_PublishMsgAsync(js, &msg1, NULL)); - IFOK(s, js_PublishMsgAsync(js, &msg2, NULL)); - testCond((s == NATS_OK) && (msg1 == NULL) && (msg2 == NULL)); - - test("Get pending (bad args): "); - s = js_PublishAsyncGetPendingList(NULL, NULL); - if (s == NATS_INVALID_ARG) - s = js_PublishAsyncGetPendingList(&pending, NULL); - if (s == NATS_INVALID_ARG) - s = js_PublishAsyncGetPendingList(NULL, js); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Get pending: "); - s = js_PublishAsyncGetPendingList(&pending, js); - testCond((s == NATS_OK) - && (pending.Msgs != NULL) - && (pending.Count == 2)); - - test("Verify pending list: "); - for (i=0; im); - args->sum++; - args->status = NATS_ERR; - switch (args->sum) { - case 1: - if ((pae == NULL) && (pa != NULL) - && ((pa->Stream != NULL) && (strcmp(pa->Stream, "TEST") == 0)) - && (pa->Sequence == 1)) - { - // Test resending message from callback... - args->status = js_PublishMsgAsync(js, &msg, NULL); - } - break; - case 2: - if ((pae == NULL) && (pa != NULL) - && ((pa->Stream != NULL) && (strcmp(pa->Stream, "TEST") == 0)) - && (pa->Sequence == 1) && (pa->Duplicate)) - { - args->status = NATS_OK; - } - break; - case 3: - if ((pa == NULL) && (pae != NULL) - && (pae->Err == NATS_ERR) && (pae->ErrCode == JSStreamStoreFailedErr) - && (strstr(pae->ErrText, "maximum messages exceeded") != NULL)) - { - args->status = NATS_OK; - } - break; - case 4: - if ((msg != NULL) && (natsMsg_GetData(msg)[0] == '1') - && (pa == NULL) && (pae != NULL) - && (pae->Err == NATS_TIMEOUT) && (pae->ErrCode == 0)) - { - args->status = NATS_OK; - } - break; - case 5: - if ((msg != NULL) && (natsMsg_GetData(msg)[0] == '2') - && (pa == NULL) && (pae != NULL) - && (pae->Err == NATS_TIMEOUT) && (pae->ErrCode == 0)) - { - args->status = NATS_OK; - } - break; - case 6: - if ((msg != NULL) && (natsMsg_GetData(msg)[0] == '3') - && (pa == NULL) && (pae != NULL) - && (pae->Err == NATS_TIMEOUT) && (pae->ErrCode == 0)) - { - args->status = NATS_OK; - } - break; - } - natsMsg_Destroy(msg); - args->done = true; - natsCondition_Broadcast(args->c); - natsMutex_Unlock(args->m); -} - -static natsStatus -_checkPubAckResult(natsStatus s, struct threadArg *args) -{ - if (s != NATS_OK) - return s; - - natsMutex_Lock(args->m); - while ((s != NATS_TIMEOUT) && !args->done) - s = natsCondition_TimedWait(args->c, args->m, 1000); - IFOK(s, args->status); - args->done = false; - args->status = NATS_OK; - natsMutex_Unlock(args->m); - - return s; -} - -static void -test_JetStreamPublishAckHandler(void) -{ - natsStatus s; - jsOptions o; - jsStreamConfig cfg; - jsPubOptions opts; - // natsMsg *msg = NULL; - struct threadArg args; - - - JS_SETUP(2, 3, 3); - jsCtx_Destroy(js); - js = NULL; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - test("Prepare JS options: "); - s = jsOptions_Init(&o); - if (s == NATS_OK) - { - o.PublishAsync.AckHandler = _jsPubAckHandler; - o.PublishAsync.AckHandlerClosure = &args; - } - testCond(s == NATS_OK); - - test("Get context: "); - s = natsConnection_JetStream(&js, nc, &o); - testCond(s == NATS_OK); - - test("Add stream: "); - jsStreamConfig_Init(&cfg); - cfg.Name = "TEST"; - cfg.Subjects = (const char*[1]){"foo"}; - cfg.SubjectsLen = 1; - cfg.MaxMsgs = 1; - cfg.Discard = js_DiscardNew; - s = js_AddStream(NULL, js, &cfg, NULL, NULL); - testCond(s == NATS_OK); - - test("Publish async ok: "); - jsPubOptions_Init(&opts); - opts.MsgId = "msg1"; - s = js_PublishAsync(js, "foo", (const void*) "ok", 2, &opts); - s = _checkPubAckResult(s, &args); - testCond(s == NATS_OK); - - test("Publish async (duplicate): "); - // Message above is resent from the ack handler callback. - s = _checkPubAckResult(s, &args); - testCond(s == NATS_OK); - - test("Publish async (max msgs): "); - s = js_PublishAsync(js, "foo", (const void*) "ok", 2, NULL); - s = _checkPubAckResult(s, &args); - testCond(s == NATS_OK); - - _stopServer(pid); - pid = NATS_INVALID_PID; - - test("Publish async with timeouts: "); - opts.MsgId = NULL; - opts.MaxWait = 250; - s = js_PublishAsync(js, "foo", (const void*) "2", 1, &opts); - opts.MaxWait = 500; - IFOK(s, js_PublishAsync(js, "foo", (const void*) "3", 1, &opts)); - opts.MaxWait = 100; - IFOK(s, js_PublishAsync(js, "foo", (const void*) "1", 1, &opts)); - testCond(s == NATS_OK); - - test("Publish async timeout (1): "); - s = _checkPubAckResult(s, &args); - testCond(s == NATS_OK); - - test("Publish async timeout (2): "); - s = _checkPubAckResult(s, &args); - testCond(s == NATS_OK); - - test("Publish async timeout (3): "); - s = _checkPubAckResult(s, &args); - testCond(s == NATS_OK); - - test("Ctx destroy releases timer: "); - opts.MaxWait = 250; - s = js_PublishAsync(js, "foo", (const void*) "4", 1, &opts); - IFOK(s, js_PublishAsync(js, "foo", (const void*) "5", 1, &opts)); - if (s == NATS_OK) - { - js_lock(js); - js->refs++; - nats_Sleep(300); - jsCtx_Destroy(js); - js_unlock(js); - } - testCond(s == NATS_OK); - - test("Check refs: "); - nats_Sleep(100); - js_lock(js); - s = (js->refs == 1 ? NATS_OK : NATS_ERR); - js_unlock(js); - testCond(s == NATS_OK); - - js_release(js); - nats_Sleep(100); - js = NULL; - - JS_TEARDOWN; - _destroyDefaultThreadArgs(&args); -} - -static void -_jsMsgHandler(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - - natsMutex_Lock(args->m); - args->sum++; - if (args->control == 1) - natsMsg_Ack(msg, NULL); - else if (args->control == 3) - { - if (args->sum == 1) - natsMsg_Nak(msg, NULL); - else - { - while (!args->done) - natsCondition_Wait(args->c, args->m); - args->msgReceived = true; - } - } - if ((args->control != 2) || (args->sum == args->results[0])) - natsCondition_Broadcast(args->c); - natsMutex_Unlock(args->m); - - natsMsg_Destroy(msg); -} - -static void -_jsSubComplete(void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - - natsMutex_Lock(args->m); - args->done = true; - natsCondition_Broadcast(args->c); - natsMutex_Unlock(args->m); -} - -static void -_jsCreateSubThread(void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - natsStatus s; - jsCtx *js; - jsSubOptions so; - natsSubscription *sub = NULL; - - natsMutex_Lock(args->m); - js = args->js; - natsMutex_Unlock(args->m); - - nats_Sleep(100); - - jsSubOptions_Init(&so); - so.Stream = "TEST_CONCURRENT"; - so.Config.Durable = "my_durable"; - s = js_Subscribe(&sub, js, "concurrent", _jsMsgHandler, (void*) args, NULL, &so, NULL); - if (s == NATS_OK) - { - natsMutex_Lock(args->m); - args->attached++; - args->sub = sub; - natsMutex_Unlock(args->m); - } - else - { - natsMutex_Lock(args->m); - args->detached++; - natsMutex_Unlock(args->m); - } -} - -static void -_jsDrainErrCb(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - - natsMutex_Lock(args->m); - if ((err == NATS_NOT_FOUND) - && (strstr(nats_GetLastError(NULL), "delete consumer") != NULL)) - { - args->msgReceived = true; - natsCondition_Broadcast(args->c); - } - natsMutex_Unlock(args->m); -} - -static void -test_JetStreamSubscribe(void) -{ - natsStatus s; - natsOptions *ncOpts = NULL; - natsConnection *nc = NULL; - natsSubscription *sub= NULL; - jsCtx *js = NULL; - natsPid pid = NATS_INVALID_PID; - jsErrCode jerr= 0; - char datastore[256] = {'\0'}; - char cmdLine[1024] = {'\0'}; - natsSubscription *ackSub = NULL; - natsMsg *ack = NULL; - natsSubscription *sub2= NULL; - jsStreamConfig sc; - jsConsumerConfig cc; - jsSubOptions so; - struct threadArg args; - int i; - jsConsumerInfo *ci = NULL; -#ifndef _WIN32 - char longsn[256]; -#endif - natsThread *threads[10] = {NULL}; - const char *subjectsStack[] = {"foo", "bar"}; - const char **subjects = subjectsStack; - const int numSubjects = sizeof(subjectsStack)/sizeof(char*); - - ENSURE_JS_VERSION(2, 3, 5); - - s = _createDefaultThreadArgsForCbTests(&args); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - _makeUniqueDir(datastore, sizeof(datastore), "datastore_"); - - test("Start JS server: "); - snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s", datastore); - pid = _startServer("nats://127.0.0.1:4222", cmdLine, true); - CHECK_SERVER_STARTED(pid); - testCond(true); - - test("Create options: "); - s = natsOptions_Create(&ncOpts); - IFOK(s, natsOptions_SetErrorHandler(ncOpts, _jsDrainErrCb, (void*) &args)); - testCond(s == NATS_OK); - - test("Connect: "); - s = natsConnection_Connect(&nc, ncOpts); - testCond(s == NATS_OK); - - test("Get context: "); - s = natsConnection_JetStream(&js, nc, NULL); - testCond(s == NATS_OK); - - test("Sub options init (bad args): "); - s = jsSubOptions_Init(NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Create async sub (invalid args): "); - s = js_Subscribe(NULL, js, "foo", NULL, NULL, NULL, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_Subscribe(&sub, NULL, "foo", NULL, NULL, NULL, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_Subscribe(&sub, js, NULL, NULL, NULL, NULL, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_Subscribe(&sub, js, "", NULL, NULL, NULL, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_Subscribe(&sub, js, "foo", NULL, NULL, NULL, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0)); - nats_clearLastError(); - - test("Create, no stream exists: "); - s = js_Subscribe(&sub, js, "foo", _dummyMsgHandler, NULL, NULL, NULL, &jerr); - testCond((s != NATS_OK) && (sub == NULL) - && (strstr(nats_GetLastError(NULL), jsErrNoStreamMatchesSubject) != NULL)); - nats_clearLastError(); - - test("Create stream: "); - jsStreamConfig_Init(&sc); - sc.Name = "TEST"; - sc.Subjects = subjects; - sc.SubjectsLen = numSubjects; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Populate: "); - s = js_Publish(NULL, js, "foo", "msg1", 4, NULL, &jerr); - IFOK(s, js_Publish(NULL, js, "foo", "msg2", 4, NULL, &jerr)); - IFOK(s, js_Publish(NULL, js, "foo", "msg3", 4, NULL, &jerr)); - IFOK(s, js_Publish(NULL, js, "bar", "msg1", 4, NULL, &jerr)); - IFOK(s, js_Publish(NULL, js, "bar", "msg2", 4, NULL, &jerr)); - testCond(s == NATS_OK); - - test("Create sub to check lib sends ACKs: "); - s = natsConnection_SubscribeSync(&ackSub, nc, "$JS.ACK.TEST.>"); - testCond(s == NATS_OK); - - test("Subscribe, no options: "); - s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, &args, NULL, NULL, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Check msgs received: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum != 3)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - test("Check acks sent: "); - // Sub should have its own autoAck CB, so usrCb should be != NULL - natsMutex_Lock(sub->mu); - s = (sub->jsi->usrCb != NULL ? NATS_OK : NATS_ERR); - natsMutex_Unlock(sub->mu); - for (i=0; (s == NATS_OK) && (i < 3); i++) - { - s = natsSubscription_NextMsg(&ack, ackSub, 1000); - if (s == NATS_OK) - { - // Make sure this was not a sync call... - s = (natsMsg_GetReply(ack) == NULL ? NATS_OK : NATS_ERR); - } - natsMsg_Destroy(ack); - ack = NULL; - } - testCond(s == NATS_OK); - - test("Create sub with manual ack: "); - natsSubscription_Destroy(sub); - sub = NULL; - natsMutex_Lock(args.m); - args.control = 1; - args.sum = 0; - natsMutex_Unlock(args.m); - jsSubOptions_Init(&so); - so.ManualAck = true; - s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Check msgs received: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum != 3)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - test("Check acks sent: "); - // Sub should have NOT have its own autoAck CB, so usrCb should be NULL - natsMutex_Lock(sub->mu); - s = (sub->jsi->usrCb == NULL ? NATS_OK : NATS_ERR); - natsMutex_Unlock(sub->mu); - for (i=0; (s == NATS_OK) && (i < 3); i++) - { - s = natsSubscription_NextMsg(&ack, ackSub, 1000); - natsMsg_Destroy(ack); - ack = NULL; - } - testCond(s == NATS_OK); - - test("Check no auto-ack behavior: "); - s = natsSubscription_NextMsg(&ack, ackSub, 150); - testCond((s == NATS_TIMEOUT) && (ack == NULL)); - nats_clearLastError(); - - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create sub with auto-ack: "); - natsMutex_Lock(args.m); - args.control = 3; - args.sum = 0; - natsMutex_Unlock(args.m); - jsSubOptions_Init(&so); - so.Stream = "TEST"; - so.Config.DeliverPolicy = js_DeliverLast; - s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*)&args, NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Check msgs received: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum < 1)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - test("Check NAck sent: "); - s = natsSubscription_NextMsg(&ack, ackSub, 1000); - testCond(s == NATS_OK); - natsMsg_Destroy(ack); - ack = NULL; - - test("Check no auto-ack: "); - s = natsSubscription_NextMsg(&ack, ackSub, 100); - testCond((s == NATS_TIMEOUT) && (ack == NULL)); - nats_clearLastError(); - s = NATS_OK; - - natsMutex_Lock(args.m); - args.done = true; - natsCondition_Broadcast(args.c); - while ((s != NATS_TIMEOUT) && !args.msgReceived) - s = natsCondition_TimedWait(args.c, args.m, 1000); - args.done = false; - args.msgReceived = false; - natsMutex_Unlock(args.m); - - test("Check auto-ack sent: "); - s = natsSubscription_NextMsg(&ack, ackSub, 1000); - testCond(s == NATS_OK); - natsMsg_Destroy(ack); - ack = NULL; - - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create queue sub: "); - natsMutex_Lock(args.m); - args.control = 0; - args.sum = 0; - natsMutex_Unlock(args.m); - jsSubOptions_Init(&so); - so.Stream = "TEST"; - so.Queue = "queue"; - so.Config.Durable = "qdurable"; - s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*)&args, NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Check msgs received: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum != 3)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - test("Check acks sent: "); - for (i=0; (s == NATS_OK) && (i < 3); i++) - { - s = natsSubscription_NextMsg(&ack, ackSub, 1000); - natsMsg_Destroy(ack); - ack = NULL; - } - testCond(s == NATS_OK); - - test("Add second member with binding: "); - jsSubOptions_Init(&so); - so.Stream = "TEST"; - so.Queue = "queue"; - so.Consumer = "qdurable"; - s = js_Subscribe(&sub2, js, "foo", _jsMsgHandler, (void*)&args, NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub2 != NULL) && (jerr == 0)); - - // Wait a bit and make sure no new message is delivered - test("No new message: "); - natsMutex_Lock(args.m); - natsCondition_TimedWait(args.c, args.m, 250); - s = (args.sum == 3 ? NATS_OK : NATS_ERR); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - test("Attached consumer not destroyed on unsub: "); - s = natsSubscription_Unsubscribe(sub2); - IFOK(s, js_GetConsumerInfo(&ci, js, "TEST", "qdurable", NULL, &jerr)); - testCond((s == NATS_OK) && (ci != NULL) && (jerr == 0)); - jsConsumerInfo_Destroy(ci); - ci = NULL; - - natsSubscription_Destroy(sub); - sub = NULL; - natsSubscription_Destroy(sub2); - sub2 = NULL; - - test("No auto-ack for AckNone: "); - natsMutex_Lock(args.m); - args.control = 0; - args.sum = 0; - args.done = false; - natsMutex_Unlock(args.m); - jsSubOptions_Init(&so); - so.Config.DeliverPolicy = js_DeliverAll; - so.Config.AckPolicy = js_AckNone; - s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Check msgs received: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum != 3)) - s = natsCondition_TimedWait(args.c, args.m, 1000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - test("Check no ack was sent: "); - natsMutex_Lock(sub->mu); - // We should not have set an internal autoAck CB, so usrCb should be NULL. - s = (sub->jsi->usrCb == NULL ? NATS_OK : NATS_ERR); - natsMutex_Unlock(sub->mu); - IFOK(s, natsSubscription_NextMsg(&ack, ackSub, 300)); - testCond((s == NATS_TIMEOUT) && (ack == NULL)); - nats_clearLastError(); - - test("Check user setting onComplete is ok: "); - s = natsSubscription_SetOnCompleteCB(sub, _jsSubComplete, (void*)&args); - testCond(s == NATS_OK); - - test("Wait for complete: "); - natsSubscription_Destroy(sub); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.done) - s = natsCondition_TimedWait(args.c, args.m, 1000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - -#ifndef _WIN32 - test("Create stream with long subject: "); - for (i=0; i<((int)sizeof(longsn))-1; i++) - longsn[i] = 'a'; - longsn[i]='\0'; - jsStreamConfig_Init(&sc); - sc.Name = longsn; - sc.Subjects = (const char*[1]){"baz"}; - sc.SubjectsLen = 1; - s = js_AddStream(NULL, js, &sc, NULL, NULL); - testCond(s == NATS_OK); - - test("Create sub to check lib sends ACKs: "); - natsSubscription_Destroy(ackSub); - ackSub = NULL; - { - char tmp[512]; - - snprintf(tmp, sizeof(tmp), "$JS.ACK.%s.>", longsn); - s = natsConnection_SubscribeSync(&ackSub, nc, tmp); - } - testCond(s == NATS_OK); - - test("Create sub with auto-ack: "); - sub = NULL; - natsMutex_Lock(args.m); - args.sum = 0; - args.control = 0; - natsMutex_Unlock(args.m); - s = js_Subscribe(&sub, js, "baz", _jsMsgHandler, &args, NULL, NULL, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Send 1 msg: "); - s = js_Publish(NULL, js, "baz", "hello", 5, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Check msg received: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum != 1)) - s = natsCondition_TimedWait(args.c, args.m, 1000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - test("Check ack sent: "); - s = natsSubscription_NextMsg(&ack, ackSub, 1000); - natsMsg_Destroy(ack); - ack = NULL; - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); -#endif - sub = NULL; - - test("Create stream with several subjects: "); - jsStreamConfig_Init(&sc); - sc.Name = "MULTIPLE_SUBJS"; - sc.Subjects = (const char*[2]){"sub.1", "sub.2"}; - sc.SubjectsLen = 2; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Create consumer with filter: "); - jsConsumerConfig_Init(&cc); - cc.Durable = "dur"; - cc.DeliverSubject = "push.dur.sub.2"; - cc.FilterSubject = "sub.2"; - s = js_AddConsumer(NULL, js, "MULTIPLE_SUBJS", &cc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Subscribe subj != filter: "); - jsSubOptions_Init(&so); - so.Stream = "MULTIPLE_SUBJS"; - so.Consumer = "dur"; - s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, &args, NULL, &so, &jerr); - testCond((s == NATS_ERR) && (sub == NULL) - && (strstr(nats_GetLastError(NULL), "filter subject") != NULL)); - nats_clearLastError(); - - if (serverVersionAtLeast(2, 10, 0)) - { - test("Create consumer with multiple filters: "); - jsConsumerConfig_Init(&cc); - cc.Durable = "dur-multi-filter"; - cc.DeliverSubject = "push.dur.sub.2"; - cc.FilterSubjectsLen = 2; - cc.FilterSubjects = (const char *[2]){"sub.1", "sub.2"}; - s = js_AddConsumer(NULL, js, "MULTIPLE_SUBJS", &cc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Subscribe subj != filters: "); - so.Consumer = "dur-multi-filter"; - s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, &args, NULL, &so, &jerr); - testCond((s == NATS_ERR) && (sub == NULL) - && (strstr(nats_GetLastError(NULL), "filter subject") != NULL)); - nats_clearLastError(); - cc.FilterSubject = "sub.2"; - cc.FilterSubjects = NULL; - cc.FilterSubjectsLen = 0; - so.Consumer = "dur"; - } - - test("Subject not required when binding to stream/consumer: "); - s = js_Subscribe(&sub, js, NULL, _jsMsgHandler, &args, NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create consumer for pull: "); - jsConsumerConfig_Init(&cc); - cc.Durable = "dur2"; - s = js_AddConsumer(NULL, js, "MULTIPLE_SUBJS", &cc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Subscribe subj not a pull: "); - jsSubOptions_Init(&so); - so.Stream = "MULTIPLE_SUBJS"; - so.Consumer = "dur2"; - s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, &args, NULL, &so, &jerr); - testCond((s == NATS_ERR) && (sub == NULL) - && (strstr(nats_GetLastError(NULL), jsErrPullSubscribeRequired) != NULL)); - nats_clearLastError(); - - test("Create stream for concurrent test: "); - jsStreamConfig_Init(&sc); - sc.Name = "TEST_CONCURRENT"; - sc.Subjects = (const char*[1]){"concurrent"}; - sc.SubjectsLen = 1; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Start concurrent creation of subs: "); - natsMutex_Lock(args.m); - args.attached = 0; - args.detached = 0; - args.control = 0; - args.js = js; - natsMutex_Unlock(args.m); - s = NATS_OK; - for (i=0; ((s == NATS_OK) && (i<10)); i++) - s = natsThread_Create(&threads[i], _jsCreateSubThread, (void*) &args); - testCond(s == NATS_OK); - - test("Wait for threads to return: "); - for (i=0; i<10; i++) - { - natsThread_Join(threads[i]); - natsThread_Destroy(threads[i]); - } - testCond(true); - - test("Only 1 should be started, 9 should have failed: "); - natsMutex_Lock(args.m); - s = ((args.attached == 1) && (args.detached == 9)) ? NATS_OK : NATS_ERR; - sub = args.sub; - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - sub = NULL; - natsSubscription_Destroy(ackSub); - ackSub = NULL; - - test("Create consumer: "); - jsSubOptions_Init(&so); - so.Config.Durable = "delcons1"; - s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Unsub deletes consumer: "); - s = natsSubscription_Unsubscribe(sub); - IFOK(s, js_GetConsumerInfo(&ci, js, "TEST", "delcons1", NULL, &jerr)); - testCond((s == NATS_NOT_FOUND) && (ci == NULL) && (jerr == JSConsumerNotFoundErr) - && (nats_GetLastError(NULL) == NULL)); - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create consumer: "); - jsSubOptions_Init(&so); - so.Config.Durable = "delcons2"; - s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Drain deletes consumer: "); - s = natsSubscription_Drain(sub); - IFOK(s, natsSubscription_WaitForDrainCompletion(sub, 1000)); - IFOK(s, js_GetConsumerInfo(&ci, js, "TEST", "delcons2", NULL, &jerr)); - testCond((s == NATS_NOT_FOUND) && (ci == NULL) && (jerr == JSConsumerNotFoundErr) - && (nats_GetLastError(NULL) == NULL)); - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create consumer: "); - jsSubOptions_Init(&so); - so.Config.Durable = "delcons2sync"; - s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Drain deletes consumer: "); - s = natsSubscription_Drain(sub); - for (i=0; i<3; i++) - { - natsMsg *msg = NULL; - IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000)); - IFOK(s, natsMsg_Ack(msg, NULL)); - natsMsg_Destroy(msg); - msg = NULL; - } - IFOK(s, natsSubscription_WaitForDrainCompletion(sub, 1000)); - IFOK(s, js_GetConsumerInfo(&ci, js, "TEST", "delcons2sync", NULL, &jerr)); - testCond((s == NATS_NOT_FOUND) && (ci == NULL) && (jerr == JSConsumerNotFoundErr) - && (nats_GetLastError(NULL) == NULL)); - natsSubscription_Destroy(sub); - sub = NULL; - - if (serverVersionAtLeast(2, 10, 14)) - { - test("Create consumer (multiple subjects): "); - jsSubOptions_Init(&so); - so.Config.Durable = "delcons3sync"; - s = js_SubscribeSyncMulti(&sub, js, subjects, numSubjects, NULL, &so, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Drain deletes consumer: "); - s = natsSubscription_Drain(sub); - for (i = 0; i < 5; i++) - { - natsMsg *msg = NULL; - IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000)); - IFOK(s, natsMsg_Ack(msg, NULL)); - natsMsg_Destroy(msg); - msg = NULL; - } - IFOK(s, natsSubscription_WaitForDrainCompletion(sub, 1000)); - IFOK(s, js_GetConsumerInfo(&ci, js, "TEST", "delcons3sync", NULL, &jerr)); - testCond((s == NATS_NOT_FOUND) && (ci == NULL) && (jerr == JSConsumerNotFoundErr) && (nats_GetLastError(NULL) == NULL)); - natsSubscription_Destroy(sub); - sub = NULL; - } - - test("Create consumer: "); - jsSubOptions_Init(&so); - so.Config.Durable = "delcons3"; - s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Delete consumer: "); - s = js_DeleteConsumer(js, "TEST", "delcons3", NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Unsub report error: "); - s = natsSubscription_Unsubscribe(sub); - testCond((s != NATS_OK) - && (strstr(nats_GetLastError(NULL), "not found") != NULL)); - nats_clearLastError(); - natsSubscription_Destroy(sub); - sub = NULL; - - natsMutex_Lock(args.m); - args.msgReceived = false; - natsMutex_Unlock(args.m); - - test("Create consumer: "); - jsSubOptions_Init(&so); - so.Config.Durable = "delcons4"; - s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Delete consumer: "); - s = js_DeleteConsumer(js, "TEST", "delcons4", NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Drain report error: "); - s = natsSubscription_Drain(sub); - if (s == NATS_OK) - { - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.msgReceived) - s = natsCondition_TimedWait(args.c, args.m, 1000); - natsMutex_Unlock(args.m); - } - testCond(s == NATS_OK); - natsSubscription_Destroy(sub); - sub = NULL; - - test("Destroy sub does not delete consumer: "); - jsSubOptions_Init(&so); - so.Config.Durable = "delcons5"; - s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr); - if (s == NATS_OK) - { - natsSubscription_Destroy(sub); - sub = NULL; - s = js_GetConsumerInfo(&ci, js, "TEST", "delcons5", NULL, &jerr); - } - testCond((s == NATS_OK) && (ci != NULL) && (jerr == 0)); - jsConsumerInfo_Destroy(ci); - ci = NULL; - - test("Queue and HB is invalid: "); - jsSubOptions_Init(&so); - so.Stream = "TEST"; - so.Queue = "queue"; - so.Config.Heartbeat = NATS_SECONDS_TO_NANOS(2); - s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr); - testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0) - && (strstr(nats_GetLastError(NULL), jsErrNoHeartbeatForQueueSub) != NULL)); - nats_clearLastError(); - - test("Queue and FlowControl is invalid: "); - jsSubOptions_Init(&so); - so.Stream = "TEST"; - so.Queue = "queue"; - so.Config.FlowControl = true; - s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr); - testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0) - && (strstr(nats_GetLastError(NULL), jsErrNoFlowControlForQueueSub) != NULL)); - nats_clearLastError(); - - test("Queue name can't contain dots: "); - jsSubOptions_Init(&so); - so.Stream = "TEST"; - so.Queue = "queue.name"; - s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr); - testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0) - && (strstr(nats_GetLastError(NULL), "cannot contain '.'") != NULL)); - nats_clearLastError(); - - test("Queue group serves as durable: "); - jsSubOptions_Init(&so); - so.Stream = "TEST"; - so.Queue = "qgroup"; - s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr); - IFOK(s, js_GetConsumerInfo(&ci, js, "TEST", "qgroup", NULL, &jerr)); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0) - && (ci != NULL) && (ci->Config != NULL) - && (ci->Config->Durable != NULL) - && (strcmp(ci->Config->Durable, "qgroup") == 0)); - jsConsumerInfo_Destroy(ci); - ci = NULL; - natsSubscription_Destroy(sub); - sub = NULL; - - test("Durable name invalid (push): "); - jsSubOptions_Init(&so); - so.Config.DeliverSubject = "bar"; - so.Config.Durable = "dur.invalid"; - s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr); - testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0) - && (strstr(nats_GetLastError(NULL), "cannot contain '.'") != NULL)); - nats_clearLastError(); - - test("Durable name invalid (pull): "); - s = js_PullSubscribe(&sub, js, "foo", "dur.invalid", NULL, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0) - && (strstr(nats_GetLastError(NULL), "cannot contain '.'") != NULL)); - nats_clearLastError(); - - JS_TEARDOWN; - natsOptions_Destroy(ncOpts); - _destroyDefaultThreadArgs(&args); -} - -static void -test_JetStreamSubscribeSync(void) -{ - natsStatus s; - natsSubscription *sub= NULL; - jsErrCode jerr= 0; - natsSubscription *ackSub = NULL; - natsMsg *ack = NULL; - jsStreamConfig sc; - int i; - natsMsg *msgs[4]; - natsMsg *msg = NULL; - const char *consName; - jsConsumerInfo *ci = NULL; - jsConsumerInfo *ci2 = NULL; - jsMsgMetaData *meta = NULL; - jsSubOptions so; - - JS_SETUP(2, 7, 0); - - test("Create sync sub (invalid args): "); - s = js_SubscribeSync(NULL, js, "foo", NULL, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_SubscribeSync(&sub, NULL, "foo", NULL, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_SubscribeSync(&sub, js, NULL, NULL, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_SubscribeSync(&sub, js, "", NULL, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0)); - nats_clearLastError(); - - test("Create, no stream exists: "); - s = js_SubscribeSync(&sub, js, "foo", NULL, NULL, &jerr); - testCond((s != NATS_OK) && (sub == NULL) - && (strstr(nats_GetLastError(NULL), jsErrNoStreamMatchesSubject) != NULL)); - nats_clearLastError(); - - test("Create stream: "); - jsStreamConfig_Init(&sc); - sc.Name = "TEST"; - sc.Subjects = (const char*[1]){"foo"}; - sc.SubjectsLen = 1; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Populate: "); - s = js_Publish(NULL, js, "foo", "msg1", 4, NULL, &jerr); - IFOK(s, js_Publish(NULL, js, "foo", "msg2", 4, NULL, &jerr)); - IFOK(s, js_Publish(NULL, js, "foo", "msg3", 4, NULL, &jerr)); - IFOK(s, js_Publish(NULL, js, "foo", "msg4", 4, NULL, &jerr)); - testCond(s == NATS_OK); - - test("Create sub to check ACKs: "); - s = natsConnection_SubscribeSync(&ackSub, nc, "$JS.ACK.TEST.>"); - testCond(s == NATS_OK); - - test("Subscribe, no options: "); - s = js_SubscribeSync(&sub, js, "foo", NULL, NULL, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Check msgs received: "); - for (i=0; (s == NATS_OK) && (i<4); i++) - s = natsSubscription_NextMsg(&msgs[i], sub, 1000); - testCond(s == NATS_OK); - - test("Check get meta data (bad args): "); - s = natsMsg_GetMetaData(NULL, NULL); - if (s == NATS_INVALID_ARG) - s = natsMsg_GetMetaData(NULL, msgs[0]); - if (s == NATS_INVALID_ARG) - s = natsMsg_GetMetaData(&meta, NULL); - testCond((s == NATS_INVALID_ARG) && (meta == NULL)); - nats_clearLastError(); - - test("Check get meta: "); - s = natsMsg_GetMetaData(&meta, msgs[0]); - testCond((s == NATS_OK) && (meta != NULL) - && (strcmp(meta->Stream, "TEST") == 0) - && (strlen(meta->Consumer) > 0) - && (meta->NumPending == 3) - && (meta->NumDelivered == 1) - && (meta->Sequence.Consumer == 1) - && (meta->Sequence.Stream == 1) - && (meta->Timestamp > 0)); - jsMsgMetaData_Destroy(meta); - meta = NULL; - // Check this is ok - jsMsgMetaData_Destroy(NULL); - - test("Ack: "); - s = natsMsg_Ack(msgs[0], NULL); - IFOK(s, natsSubscription_NextMsg(&ack, ackSub, 1000)); - if (s == NATS_OK) - { - s = (strncmp( - natsMsg_GetData(ack), - jsAckAck, - natsMsg_GetDataLength(ack)) == 0 ? NATS_OK : NATS_ERR); - - natsMsg_Destroy(ack); - ack = NULL; - } - testCond(s == NATS_OK); - - test("Second ack does nothing: "); - s = natsMsg_Ack(msgs[0], NULL); - IFOK(s, natsSubscription_NextMsg(&ack, ackSub, 300)); - testCond(s == NATS_TIMEOUT); - natsMsg_Destroy(msgs[0]); - - test("AckSync: "); - s = natsMsg_AckSync(msgs[1], NULL, &jerr); - IFOK(s, natsSubscription_NextMsg(&ack, ackSub, 1000)); - if (s == NATS_OK) - { - s = (strncmp( - natsMsg_GetData(ack), - jsAckAck, - natsMsg_GetDataLength(ack)) == 0 ? NATS_OK : NATS_ERR); - - natsMsg_Destroy(ack); - ack = NULL; - } - natsMsg_Destroy(msgs[1]); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Nack: "); - s = natsMsg_Nak(msgs[2], NULL); - IFOK(s, natsSubscription_NextMsg(&ack, ackSub, 1000)); - if (s == NATS_OK) - { - s = (strncmp( - natsMsg_GetData(ack), - jsAckNak, - natsMsg_GetDataLength(ack)) == 0 ? NATS_OK : NATS_ERR); - - natsMsg_Destroy(ack); - ack = NULL; - } - natsMsg_Destroy(msgs[2]); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Nack msg resent: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) && (msg != NULL)); - - // Should be able to send InProgress more than once. - for (i=0; i<2; i++) - { - test("InProgress: "); - s = natsMsg_InProgress(msg, NULL); - IFOK(s, natsSubscription_NextMsg(&ack, ackSub, 1000)); - if (s == NATS_OK) - { - s = (strncmp( - natsMsg_GetData(ack), - jsAckInProgress, - natsMsg_GetDataLength(ack)) == 0 ? NATS_OK : NATS_ERR); - - natsMsg_Destroy(ack); - ack = NULL; - } - testCond(s == NATS_OK); - } - natsMsg_Destroy(msg); - msg = NULL; - - test("Term: "); - s = natsMsg_Term(msgs[3], NULL); - IFOK(s, natsSubscription_NextMsg(&ack, ackSub, 1000)); - if (s == NATS_OK) - { - s = (strncmp( - natsMsg_GetData(ack), - jsAckTerm, - natsMsg_GetDataLength(ack)) == 0 ? NATS_OK : NATS_ERR); - - natsMsg_Destroy(ack); - ack = NULL; - } - natsMsg_Destroy(msgs[3]); - testCond(s == NATS_OK); - - test("Ack failure (bad args): "); - s = natsMsg_Ack(NULL, NULL); - if (s == NATS_INVALID_ARG) - s = natsMsg_AckSync(NULL, NULL, NULL); - if (s == NATS_INVALID_ARG) - s = natsMsg_Nak(NULL, NULL); - if (s == NATS_INVALID_ARG) - s = natsMsg_InProgress(NULL, NULL); - if (s == NATS_INVALID_ARG) - s = natsMsg_Term(NULL, NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Ack failure (not bound): "); - natsMsg_Create(&msg, "foo", NULL, "test", 4); - s = natsMsg_Ack(msg, NULL); - if (s == NATS_ILLEGAL_STATE) - s = natsMsg_AckSync(msg, NULL, NULL); - if (s == NATS_ILLEGAL_STATE) - s = natsMsg_Nak(msg, NULL); - if (s == NATS_ILLEGAL_STATE) - s = natsMsg_InProgress(msg, NULL); - if (s == NATS_ILLEGAL_STATE) - s = natsMsg_Term(msg, NULL); - testCond((s == NATS_ILLEGAL_STATE) - && (strstr(nats_GetLastError(NULL), jsErrMsgNotBound) != NULL)); - nats_clearLastError(); - - test("Get Meta failure (not bound): ") - s = natsMsg_GetMetaData(&meta, msg); - testCond((s == NATS_ILLEGAL_STATE) && (meta == NULL) - && (strstr(nats_GetLastError(NULL), jsErrMsgNotBound) != NULL)); - nats_clearLastError(); - - test("Ack failure (no reply): "); - // artificially bind to existing sub - msg->sub = sub; - s = natsMsg_Ack(msg, NULL); - if (s == NATS_ILLEGAL_STATE) - s = natsMsg_AckSync(msg, NULL, NULL); - if (s == NATS_ILLEGAL_STATE) - s = natsMsg_Nak(msg, NULL); - if (s == NATS_ILLEGAL_STATE) - s = natsMsg_InProgress(msg, NULL); - if (s == NATS_ILLEGAL_STATE) - s = natsMsg_Term(msg, NULL); - testCond((s == NATS_ILLEGAL_STATE) - && (strstr(nats_GetLastError(NULL), jsErrMsgNotJS) != NULL)); - nats_clearLastError(); - - test("Get Meta failure (no reply): "); - s = natsMsg_GetMetaData(&meta, msg); - testCond((s == NATS_ILLEGAL_STATE) && (meta == NULL) - && (strstr(nats_GetLastError(NULL), jsErrMsgNotJS) != NULL)); - nats_clearLastError(); - msg->sub = NULL; - natsMsg_Destroy(msg); - msg = NULL; - - test("Get Meta failure (not JS Ack): "); - s = natsMsg_Create(&msg, "foo", "not.a.js.ack", NULL, 0); - if (s == NATS_OK) - msg->sub = sub; - IFOK(s, natsMsg_GetMetaData(&meta, msg)); - testCond((s == NATS_ERR) && (meta == NULL) - && (strstr(nats_GetLastError(NULL), "invalid meta") != NULL)); - nats_clearLastError(); - msg->sub = NULL; - natsMsg_Destroy(msg); - msg = NULL; - - test("Get Meta failure (terminal dot): "); - s = natsMsg_Create(&msg, "foo", "$JS.ACK.TEST.CONSUMER.1.1.1.1629415486698860000.3.", NULL, 0); - if (s == NATS_OK) - msg->sub = sub; - IFOK(s, natsMsg_GetMetaData(&meta, msg)); - testCond((s == NATS_ERR) && (meta == NULL) - && (strstr(nats_GetLastError(NULL), "invalid meta") != NULL)); - nats_clearLastError(); - msg->sub = NULL; - natsMsg_Destroy(msg); - msg = NULL; - - test("Get Meta failure (too small): "); - s = natsMsg_Create(&msg, "foo", "$JS.ACK.TEST.CONSUMER.1.1.1.1629415486698860000", NULL, 0); - if (s == NATS_OK) - msg->sub = sub; - IFOK(s, natsMsg_GetMetaData(&meta, msg)); - testCond((s == NATS_ERR) && (meta == NULL) - && (strstr(nats_GetLastError(NULL), "invalid meta") != NULL)); - nats_clearLastError(); - msg->sub = NULL; - natsMsg_Destroy(msg); - msg = NULL; - - test("Get Meta failure (too small v2): "); - s = natsMsg_Create(&msg, "foo", "$JS.ACK.HUB.accHash.TEST.CONSUMER.1.1.1.1629415486698860000", NULL, 0); - if (s == NATS_OK) - msg->sub = sub; - IFOK(s, natsMsg_GetMetaData(&meta, msg)); - testCond((s == NATS_ERR) && (meta == NULL) - && (strstr(nats_GetLastError(NULL), "invalid meta") != NULL)); - nats_clearLastError(); - msg->sub = NULL; - natsMsg_Destroy(msg); - msg = NULL; - - test("Get Meta failure (invalid content): "); - s = natsMsg_Create(&msg, "foo", "$JS.ACK.TEST.CONSUMER.and.some.bad.other.things", NULL, 0); - if (s == NATS_OK) - msg->sub = sub; - IFOK(s, natsMsg_GetMetaData(&meta, msg)); - testCond((s == NATS_ERR) && (meta == NULL) - && (strstr(nats_GetLastError(NULL), "invalid meta") != NULL)); - nats_clearLastError(); - msg->sub = NULL; - natsMsg_Destroy(msg); - msg = NULL; - - test("Get Meta failure (invalid content v2): "); - s = natsMsg_Create(&msg, "foo", "$JS.ACK.HUB.accHash.TEST.CONSUMER.and.some.bad.other.things", NULL, 0); - if (s == NATS_OK) - msg->sub = sub; - IFOK(s, natsMsg_GetMetaData(&meta, msg)); - testCond((s == NATS_ERR) && (meta == NULL) - && (strstr(nats_GetLastError(NULL), "invalid meta") != NULL)); - nats_clearLastError(); - msg->sub = NULL; - natsMsg_Destroy(msg); - msg = NULL; - - test("Get Meta v2: "); - s = natsMsg_Create(&msg, "foo", "$JS.ACK.HUB.accHash.TEST.CONSUMER.1.2.3.1629415486698860000.4.random", NULL, 0); - if (s == NATS_OK) - msg->sub = sub; - IFOK(s, natsMsg_GetMetaData(&meta, msg)); - testCond((s == NATS_OK) && (meta != NULL) - && (strcmp(meta->Domain, "HUB") == 0) - && (strcmp(meta->Stream, "TEST") == 0) - && (strcmp(meta->Consumer, "CONSUMER") == 0) - && (meta->NumDelivered == 1) - && (meta->Sequence.Stream == 2) - && (meta->Sequence.Consumer == 3) - && (meta->Timestamp == 1629415486698860000) - && (meta->NumPending == 4)); - jsMsgMetaData_Destroy(meta); - meta = NULL; - msg->sub = NULL; - natsMsg_Destroy(msg); - msg = NULL; - - test("Get Meta v2 (empty domain): "); - s = natsMsg_Create(&msg, "foo", "$JS.ACK._.accHash.TEST.CONSUMER.1.2.3.1629415486698860000.4.random", NULL, 0); - if (s == NATS_OK) - msg->sub = sub; - IFOK(s, natsMsg_GetMetaData(&meta, msg)); - testCond((s == NATS_OK) && (meta != NULL) - && (meta->Domain == NULL) - && (strcmp(meta->Stream, "TEST") == 0) - && (strcmp(meta->Consumer, "CONSUMER") == 0) - && (meta->NumDelivered == 1) - && (meta->Sequence.Stream == 2) - && (meta->Sequence.Consumer == 3) - && (meta->Timestamp == 1629415486698860000) - && (meta->NumPending == 4)); - jsMsgMetaData_Destroy(meta); - meta = NULL; - msg->sub = NULL; - natsMsg_Destroy(msg); - msg = NULL; - - test("Get Meta v2 (no failure with appended tokens): "); - s = natsMsg_Create(&msg, "foo", "$JS.ACK._.accHash.TEST.CONSUMER.1.2.3.1629415486698860000.4.random.new_one", NULL, 0); - if (s == NATS_OK) - msg->sub = sub; - IFOK(s, natsMsg_GetMetaData(&meta, msg)); - testCond((s == NATS_OK) && (meta != NULL) - && (meta->Domain == NULL) - && (strcmp(meta->Stream, "TEST") == 0) - && (strcmp(meta->Consumer, "CONSUMER") == 0) - && (meta->NumDelivered == 1) - && (meta->Sequence.Stream == 2) - && (meta->Sequence.Consumer == 3) - && (meta->Timestamp == 1629415486698860000) - && (meta->NumPending == 4)); - jsMsgMetaData_Destroy(meta); - meta = NULL; - msg->sub = NULL; - natsMsg_Destroy(msg); - msg = NULL; - - test("Get Meta v2 (no failure with appended tokens, malformed ok): "); - s = natsMsg_Create(&msg, "foo", "$JS.ACK._.accHash.TEST.CONSUMER.1.2.3.1629415486698860000.4.", NULL, 0); - if (s == NATS_OK) - msg->sub = sub; - IFOK(s, natsMsg_GetMetaData(&meta, msg)); - testCond((s == NATS_OK) && (meta != NULL) - && (meta->Domain == NULL) - && (strcmp(meta->Stream, "TEST") == 0) - && (strcmp(meta->Consumer, "CONSUMER") == 0) - && (meta->NumDelivered == 1) - && (meta->Sequence.Stream == 2) - && (meta->Sequence.Consumer == 3) - && (meta->Timestamp == 1629415486698860000) - && (meta->NumPending == 4)); - jsMsgMetaData_Destroy(meta); - meta = NULL; - msg->sub = NULL; - natsMsg_Destroy(msg); - msg = NULL; - - test("Send msg: "); - s = js_Publish(NULL, js, "foo", "block", 5, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Drain does not delete consumer: "); - consName = sub->jsi->consumer; - s = natsSubscription_Drain(sub); - nats_Sleep(500); - IFOK(s, js_GetConsumerInfo(&ci, js, "TEST", consName, NULL, &jerr)); - testCond((s == NATS_OK) && (ci != NULL) && (jerr == 0)); - jsConsumerInfo_Destroy(ci); - ci = NULL; - - test("Wait for drain to complete: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - natsMsg_Destroy(msg); - IFOK(s, natsSubscription_WaitForDrainCompletion(sub, 1000)); - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create sub with ack-none: "); - jsSubOptions_Init(&so); - so.Config.AckPolicy = js_AckNone; - so.Config.DeliverPolicy = js_DeliverLast; - s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Check msg received: "); - msg = NULL; - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond(s == NATS_OK); - - test("Check ack: "); - s = natsMsg_InProgress(msg, NULL); - IFOK(s, natsMsg_Nak(msg, NULL)); - IFOK(s, natsMsg_Ack(msg, NULL)); - IFOK(s, natsMsg_Term(msg, NULL)); - testCond(s == NATS_OK); - - test("Check no ack sent: "); - natsMsg_Destroy(msg); - msg = NULL; - s = natsSubscription_NextMsg(&msg, ackSub, 100); - testCond((s == NATS_TIMEOUT) && (msg == NULL)); - - natsSubscription_Destroy(sub); - sub = NULL; - natsSubscription_Destroy(ackSub); - ackSub = NULL; - - test("Test InactiveThreshold (bad value): "); - jsSubOptions_Init(&so); - so.Config.InactiveThreshold = NATS_MILLIS_TO_NANOS(-100); - s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr); - testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0) - && (strstr(nats_GetLastError(NULL), "invalid InactiveThreshold") != NULL)); - nats_clearLastError(); - - test("Create normal sub: "); - s = natsConnection_SubscribeSync(&sub, nc, "test"); - testCond((s == NATS_OK) && (sub != NULL)); - - test("sub get info (invalid args): "); - s = natsSubscription_GetConsumerInfo(NULL, sub, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = natsSubscription_GetConsumerInfo(&ci, NULL, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) && (jerr == 0)); - nats_clearLastError(); - - test("sub get info (invalid sub): "); - s = natsSubscription_GetConsumerInfo(&ci, sub, NULL, &jerr); - testCond((s == NATS_INVALID_SUBSCRIPTION) && (ci == NULL) && (jerr == 0)); - nats_clearLastError(); - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create sub: "); - jsSubOptions_Init(&so); - so.Config.InactiveThreshold = NATS_MILLIS_TO_NANOS(50); - s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Get consumer info: "); - s = natsSubscription_GetConsumerInfo(&ci, sub, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (ci != NULL) && (ci->Config != NULL) - && (ci->Config->InactiveThreshold == NATS_MILLIS_TO_NANOS(50))); - - test("Close conn: "); - jsCtx_Destroy(js); - js = NULL; - natsConnection_Destroy(nc); - nc = NULL; - testCond(true); - - nats_Sleep(150); - - test("Check consumer gone: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_JetStream(&js, nc, NULL)); - IFOK(s, js_GetConsumerInfo(&ci2, js, "TEST", ci->Name, NULL, &jerr)); - testCond((s == NATS_NOT_FOUND) && (jerr == JSConsumerNotFoundErr) && (ci2 == NULL)); - jsConsumerInfo_Destroy(ci); - natsSubscription_Destroy(sub); - JS_TEARDOWN; -} - -static void -test_JetStreamSubscribeConfigCheck(void) -{ - natsStatus s; - natsSubscription *sub= NULL; - const char *name = NULL; - jsErrCode jerr= 0; - char durName[64]; - char testName[64]; - jsStreamConfig sc; - jsConsumerConfig cc; - int i; - int64_t backOffListOf3[3] = {1, 2, 3}; - int64_t backOffListOf2[2] = {1, 2}; - - JS_SETUP(2, 9, 0); - - test("Create stream: "); - jsStreamConfig_Init(&sc); - sc.Name = "TEST"; - sc.Subjects = (const char*[1]){"foo"}; - sc.SubjectsLen = 1; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - for (i=0; i<17; i++) - { - jsSubOptions so1; - jsSubOptions so2; - - natsNUID_Next(durName, sizeof(durName)); - - jsSubOptions_Init(&so1); - jsSubOptions_Init(&so2); - switch (i) - { - case 0: - { - name = "description"; - so1.Config.Description = "a"; - so2.Config.Description = "b"; - break; - } - case 1: - { - name = "deliver policy"; - so1.Config.DeliverPolicy = js_DeliverAll; - so2.Config.DeliverPolicy = js_DeliverLast; - break; - } - case 2: - { - name = "optional start sequence"; - so1.Config.OptStartSeq = 1; - so2.Config.OptStartSeq = 10; - break; - } - case 3: - { - name = "optional start time"; - so1.Config.OptStartTime = 1000000000; - so2.Config.OptStartTime = 2000000000; - break; - } - case 4: - { - name = "ack wait"; - so1.Config.AckWait = NATS_SECONDS_TO_NANOS(10); - so2.Config.AckWait = NATS_SECONDS_TO_NANOS(15); - break; - } - case 5: - { - name = "max deliver"; - so1.Config.MaxDeliver = 3; - so2.Config.MaxDeliver = 5; - break; - } - case 6: - { - name = "replay policy"; - so1.Config.ReplayPolicy = js_ReplayOriginal; - so2.Config.ReplayPolicy = js_ReplayInstant; - break; - } - case 7: - { - name = "max waiting"; - so1.Config.MaxWaiting = 10; - so2.Config.MaxWaiting = 20; - break; - } - case 8: - { - name = "max ack pending"; - so1.Config.MaxAckPending = 10; - so2.Config.MaxAckPending = 20; - break; - } - case 9: - { - name = "sample frequency"; - so1.Config.SampleFrequency = "100%"; - so2.Config.SampleFrequency = "50%"; - break; - } - case 10: - { - name = "backoff"; - so1.Config.BackOff = backOffListOf3; - so1.Config.BackOffLen = 3; - so1.Config.MaxDeliver = 5; - so2.Config.BackOff = backOffListOf2; - so2.Config.BackOffLen = 2; - so2.Config.MaxDeliver = 5; - break; - } - case 11: - { - name = "headers only"; - so1.Config.HeadersOnly = true; - // Not setting it for the 2nd subscribe call should fail. - break; - } - case 12: - { - name = "max request batch"; - so1.Config.MaxRequestBatch = 100; - so2.Config.MaxRequestBatch = 200; - break; - } - case 13: - { - name = "max request expires"; - so1.Config.MaxRequestExpires = NATS_SECONDS_TO_NANOS(1); - so2.Config.MaxRequestExpires = NATS_SECONDS_TO_NANOS(2); - break; - } - case 14: - { - name = "inactive threshold"; - so1.Config.InactiveThreshold = NATS_SECONDS_TO_NANOS(1); - so2.Config.InactiveThreshold = NATS_SECONDS_TO_NANOS(2); - break; - } - case 15: - { - name = "replicas"; - so1.Config.Replicas = 1; - so2.Config.Replicas = 3; - break; - } - case 16: - { - name = "memory storage"; - so1.Config.MemoryStorage = true; - // Not setting it for the 2nd subscribe call should fail. - break; - } - } - snprintf(testName, sizeof(testName), "Check %s: ", name); - test(testName); - s = js_PullSubscribe(&sub, js, "foo", durName, NULL, &so1, &jerr); - if ((s == NATS_OK) && (jerr == 0)) - { - natsSubscription *nsub = NULL; - - s = js_PullSubscribe(&nsub, js, "foo", durName, NULL, &so2, &jerr); - if ((s != NATS_OK) && (nsub == NULL) && (strstr(nats_GetLastError(NULL), name) != NULL)) - { - s = NATS_OK; - nats_clearLastError(); - } - else - s = NATS_ERR; - } - testCond(s == NATS_OK); - natsSubscription_Unsubscribe(sub); - natsSubscription_Destroy(sub); - sub = NULL; - } - - for (i=0; i<4; i++) - { - jsConsumerConfig cc; - natsInbox *inbox = NULL; - - natsInbox_Create(&inbox); - natsNUID_Next(durName, sizeof(durName)); - jsConsumerConfig_Init(&cc); - switch (i) - { - case 0: cc.AckPolicy = js_AckAll; break; - case 1: cc.RateLimit = 10; break; - case 2: cc.FlowControl = false; break; - case 3: cc.Heartbeat = NATS_SECONDS_TO_NANOS(10); break; - } - cc.Durable = durName; - cc.DeliverSubject = inbox; - s = js_AddConsumer(NULL, js, "TEST", &cc, NULL, &jerr); - if ((s == NATS_OK) && (jerr == 0)) - { - jsSubOptions so; - - jsSubOptions_Init(&so); - so.Config.Durable = durName; - switch (i) - { - case 0: name="ack policy"; so.Config.AckPolicy = js_AckNone; break; - case 1: name="rate limit"; so.Config.RateLimit = 100; break; - case 2: name="flow control"; so.Config.FlowControl = true; break; - case 3: name="heartbeat"; so.Config.Heartbeat = NATS_SECONDS_TO_NANOS(20); break; - } - snprintf(testName, sizeof(testName), "Check %s: ", name); - test(testName); - s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr); - if ((s != NATS_OK) && (sub == NULL) && (strstr(nats_GetLastError(NULL), name) != NULL)) - { - s = NATS_OK; - nats_clearLastError(); - } - else - s = NATS_ERR; - testCond(s == NATS_OK); - } - natsInbox_Destroy(inbox); - inbox = NULL; - } - - // Verify that we don't fail if user did not set it. - for (i=0; i<14; i++) - { - natsSubscription *nsub = NULL; - jsSubOptions so; - - jsSubOptions_Init(&so); - switch (i) - { - case 0: name = "description"; so.Config.Description = "a"; break; - case 1: name = "deliver policy"; so.Config.DeliverPolicy = js_DeliverLast; break; - case 2: name = "optional start sequence"; so.Config.OptStartSeq = 10; break; - case 3: name = "optional start time"; so.Config.OptStartTime = 1000000000; break; - case 4: name = "ack wait"; so.Config.AckWait = NATS_SECONDS_TO_NANOS(10); break; - case 5: name = "max deliver"; so.Config.MaxDeliver = 3; break; - case 6: name = "replay policy"; so.Config.ReplayPolicy = js_ReplayInstant; break; - case 7: name = "max waiting"; so.Config.MaxWaiting = 10; break; - case 8: name = "max ack pending"; so.Config.MaxAckPending = 10; break; - case 9: - { - name = "backoff"; - so.Config.BackOff = backOffListOf3; - so.Config.BackOffLen = 3; - so.Config.MaxDeliver = 4; - break; - } - case 10: name = "max request batch"; so.Config.MaxRequestBatch = 100; break; - case 11: name = "max request expires"; so.Config.MaxRequestExpires = NATS_SECONDS_TO_NANOS(2); break; - case 12: name = "inactive threshold"; so.Config.InactiveThreshold = NATS_SECONDS_TO_NANOS(2); break; - case 13: name = "replicas"; so.Config.Replicas = 1; break; - } - - natsNUID_Next(durName, sizeof(durName)); - - snprintf(testName, sizeof(testName), "Check %s: ", name); - test(testName); - s = js_PullSubscribe(&sub, js, "foo", durName, NULL, &so, &jerr); - if (s == NATS_OK) - { - // If not explicitly asked by the user, we are ok - s = js_PullSubscribe(&nsub, js, "foo", durName, NULL, NULL, &jerr); - - natsSubscription_Unsubscribe(nsub); - natsSubscription_Destroy(nsub); - nsub = NULL; - } - testCond(s == NATS_OK); - natsSubscription_Unsubscribe(sub); - natsSubscription_Destroy(sub); - sub = NULL; - } - - // If the option is the same as the server default, it is not an error either. - for (i=0; i<5; i++) - { - natsSubscription *nsub = NULL; - jsSubOptions so; - - jsSubOptions_Init(&so); - switch (i) - { - case 1: name = "default deliver policy"; so.Config.DeliverPolicy = js_DeliverAll; break; - case 4: name = "default ack wait"; so.Config.AckWait = NATS_SECONDS_TO_NANOS(30); break; - case 6: name = "default replay policy"; so.Config.ReplayPolicy = js_ReplayInstant; break; - case 7: name = "default max waiting"; so.Config.MaxWaiting = 512; break; - case 8: name = "default max ack pending"; so.Config.MaxAckPending = 65536; break; - } - - natsNUID_Next(durName, sizeof(durName)); - - snprintf(testName, sizeof(testName), "Check %s: ", name); - test(testName); - s = js_PullSubscribe(&sub, js, "foo", durName, NULL, NULL, &jerr); - if (s == NATS_OK) - { - s = js_PullSubscribe(&nsub, js, "foo", durName, NULL, &so, &jerr); - natsSubscription_Unsubscribe(nsub); - natsSubscription_Destroy(nsub); - nsub = NULL; - } - testCond(s == NATS_OK); - natsSubscription_Unsubscribe(sub); - natsSubscription_Destroy(sub); - sub = NULL; - } - - for (i=0; i<5; i++) - { - jsSubOptions so; - - jsSubOptions_Init(&so); - switch (i) - { - case 0: name = "deliver policy"; so.Config.DeliverPolicy = js_DeliverNew; break; - case 1: name = "ack wait"; so.Config.AckWait = NATS_SECONDS_TO_NANOS(31); break; - case 2: name = "replay policy"; so.Config.ReplayPolicy = js_ReplayOriginal; break; - case 3: name = "max waiting"; so.Config.MaxWaiting = 513; break; - case 4: name = "max ack pending"; so.Config.MaxAckPending = 2; break; - } - - natsNUID_Next(durName, sizeof(durName)); - - snprintf(testName, sizeof(testName), "Check %s: ", name); - test(testName); - s = js_PullSubscribe(&sub, js, "foo", durName, NULL, NULL, &jerr); - if (s == NATS_OK) - { - natsSubscription *nsub = NULL; - - // First time it was created with defaults and the - // second time a change is attempted, so it is an error. - s = js_PullSubscribe(&nsub, js, "foo", durName, NULL, &so, &jerr); - if ((s != NATS_OK) && (nsub == NULL) && (strstr(nats_GetLastError(NULL), name) != NULL)) - { - s = NATS_OK; - nats_clearLastError(); - } - else - s = NATS_ERR; - } - testCond(s == NATS_OK); - natsSubscription_Unsubscribe(sub); - natsSubscription_Destroy(sub); - sub = NULL; - } - - // Check that binding to a durable (without specifying durable option) works - test("Create durable: "); - jsConsumerConfig_Init(&cc); - cc.Durable = "BindDurable"; - cc.DeliverSubject = "bar"; - s = js_AddConsumer(NULL, js, "TEST", &cc, NULL, &jerr); - if (s == NATS_OK) - { - jsSubOptions so; - - jsSubOptions_Init(&so); - so.Stream = "TEST"; - so.Consumer = "BindDurable"; - s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr); - natsSubscription_Destroy(sub); - } - testCond(s == NATS_OK); - - JS_TEARDOWN; -} - -static void -_jsSeqMismatch(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - - natsMutex_Lock(args->m); - if (err == NATS_MISMATCH) - args->done = true; - else if (err == NATS_MISSED_HEARTBEAT) - args->results[args->control]++; - natsCondition_Broadcast(args->c); - natsMutex_Unlock(args->m); -} - -static void -_setMsgReply(natsConnection *nc, natsMsg **msg, void* closure) -{ - natsMsg *m = NULL; - - natsMsg_Create(&m, (*msg)->subject, (const char*) closure, (*msg)->data, (*msg)->dataLen); - natsMsg_Destroy(*msg); - *msg = m; - natsConn_setFilter(nc, NULL); -} - -static void -test_JetStreamSubscribeIdleHearbeat(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsSubscription *sub= NULL; - jsCtx *js = NULL; - natsPid pid = NATS_INVALID_PID; - jsErrCode jerr= 0; - char datastore[256] = {'\0'}; - char cmdLine[1024] = {'\0'}; - jsStreamConfig sc; - jsSubOptions so; - natsMsg *msg; - struct threadArg args; - natsOptions *opts = NULL; - natsSubscription *nsub = NULL; - char *inbox = NULL; - int i; - jsConsumerSequenceMismatch csm; - jsConsumerConfig cc; - - ENSURE_JS_VERSION(2, 3, 5); - - s = natsOptions_Create(&opts); - IFOK(s, _createDefaultThreadArgsForCbTests(&args)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - _makeUniqueDir(datastore, sizeof(datastore), "datastore_"); - - test("Start JS server: "); - snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s", datastore); - pid = _startServer("nats://127.0.0.1:4222", cmdLine, true); - CHECK_SERVER_STARTED(pid); - testCond(true); - - test("Connect: "); - s = natsOptions_SetErrorHandler(opts, _jsSeqMismatch, (void*) &args); - IFOK(s, natsOptions_SetReconnectWait(opts, 50)); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - - test("Get context: "); - s = natsConnection_JetStream(&js, nc, NULL); - testCond(s == NATS_OK); - - test("Create stream: "); - jsStreamConfig_Init(&sc); - sc.Name = "TEST"; - sc.Subjects = (const char*[1]){"foo"}; - sc.SubjectsLen = 1; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Populate: "); - s = js_Publish(NULL, js, "foo", "msg1", 4, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Subscribe async: "); - jsSubOptions_Init(&so); - so.Config.Durable = "dur1"; - so.Config.Heartbeat = 150*1000000; - s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Check msg received: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum != 1)) - s = natsCondition_TimedWait(args.c, args.m, 1000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - test("Check HB received: "); - nats_Sleep(300); - natsSubAndLdw_Lock(sub); - s = (sub->jsi->mismatch.dseq == 1 ? NATS_OK : NATS_ERR); - natsSubAndLdw_Unlock(sub); - testCond(s == NATS_OK); - - test("Check HB is not given to app: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum == 1)) - s = natsCondition_TimedWait(args.c, args.m, 300); - natsMutex_Unlock(args.m); - testCond(s == NATS_TIMEOUT); - - test("Check get mismatch (bad args): "); - // Create regular sub to check invalid args. - s = natsConnection_SubscribeSync(&nsub, nc, "normal_sub"); - IFOK(s, natsSubscription_GetSequenceMismatch(NULL, NULL)); - if (s == NATS_INVALID_ARG) - s = natsSubscription_GetSequenceMismatch(NULL, sub); - if (s == NATS_INVALID_ARG) - s = natsSubscription_GetSequenceMismatch(&csm, NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Not a JS sub: ") - s = natsSubscription_GetSequenceMismatch(&csm, nsub); - testCond((s == NATS_INVALID_SUBSCRIPTION) - && (strstr(nats_GetLastError(NULL), jsErrNotAJetStreamSubscription) != NULL)); - nats_clearLastError(); - - test("Check get mismatch returns not found: "); - s = natsSubscription_GetSequenceMismatch(&csm, sub); - testCond((s == NATS_NOT_FOUND) && (nats_GetLastError(NULL) == NULL)); - - test("Send new message: "); - s = js_Publish(NULL, js, "foo", "msg2", 4, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - // Cheat by pretending that the server sends message seq 3, while client - // received only seq 1. Disable auto-ack for this message, or we mess up the - // server state. -#define PUBLISH_FAKE_JS_MSG_WITH_SEQ(_reply, _msg) \ - { \ - natsSub_Lock(sub); \ - inbox = sub->subject; \ - sub->jsi->ackNone = true; \ - natsSub_Unlock(sub); \ - \ - natsConn_setFilterWithClosure(nc, _setMsgReply, (void *)(_reply)); \ - s = natsConnection_PublishString(nc, inbox, (_msg)); \ - } - -#define PUBLISH_FAKE_RESET() \ - { \ - natsSub_Lock(sub); \ - sub->jsi->ackNone = false; \ - natsSub_Unlock(sub); \ - } - - test("Check seq mismatch: "); - PUBLISH_FAKE_JS_MSG_WITH_SEQ("$JS.ACK.TEST.dur1.1.3.3.1624472520000000000.0", "msg3 fake"); - // Wait for past the next HB and we should get an async error - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.done) - s = natsCondition_TimedWait(args.c, args.m, 300); - natsMutex_Unlock(args.m); - PUBLISH_FAKE_RESET(); - testCond(s == NATS_OK); - - test("Check that notification is sent only once: "); - natsMutex_Lock(args.m); - args.done = false; - natsMutex_Unlock(args.m); - // Wait for more than 1 HB, the callback should not - // be invoked because we have notified once and - // the mismatch still exist. - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.done) - s = natsCondition_TimedWait(args.c, args.m, 300); - natsMutex_Unlock(args.m); - testCond(s == NATS_TIMEOUT); - - test("Check get mismatch: "); - // Server will say that the consumer seq is at 2 - s = natsSubscription_GetSequenceMismatch(&csm, sub); - testCond((s == NATS_OK) - && (csm.Stream == 3) - && (csm.ConsumerClient == 3) - && (csm.ConsumerServer == 2)); - - test("Check mismatch suppression cleared: "); - // Send real message so that all clears up - s = js_Publish(NULL, js, "foo", "msg3", 4, NULL, &jerr); - nats_Sleep(300); - natsSubAndLdw_Lock(sub); - s = (sub->jsi->ssmn == false ? NATS_OK : NATS_ERR); - natsSubAndLdw_Unlock(sub); - testCond(s == NATS_OK); - - test("Skip again: "); - PUBLISH_FAKE_JS_MSG_WITH_SEQ("$JS.ACK.TEST.dur1.1.4.4.1624482520000000000.0", "msg4 fake"); - testCond(s == NATS_OK); - - test("Check async cb invoked: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.done) - s = natsCondition_TimedWait(args.c, args.m, 1000); - natsMutex_Unlock(args.m); - PUBLISH_FAKE_RESET(); - testCond(s == NATS_OK); - - test("Check HB timer reports missed HB: "); - _stopServer(pid); - // The HB interval is 150ms, but we check *2, so be a bit more - // than 300ms to avoid flapping. - nats_Sleep(500); - pid = _startServer("nats://127.0.0.1:4222", cmdLine, true); - CHECK_SERVER_STARTED(pid); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.results[0] == 0)) - s = natsCondition_TimedWait(args.c, args.m, 1000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - sub = NULL; - - natsMutex_Lock(args.m); - args.sum = 0; - args.done = false; - args.control = 1; - natsMutex_Unlock(args.m); - - test("Subscribe sync: "); - jsSubOptions_Init(&so); - so.Config.Durable = "dur2"; - so.Config.Heartbeat = 150*1000000; - s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Check msgs received: "); - for (i=0; (s == NATS_OK) && (i<3); i++) - { - s = natsSubscription_NextMsg(&msg, sub, 1000); - IFOK(s, natsMsg_Ack(msg, NULL)); - natsMsg_Destroy(msg); - msg = NULL; - } - testCond(s == NATS_OK); - - test("Check HB received: "); - nats_Sleep(300); - natsMutex_Lock(sub->mu); - s = (sub->jsi->mismatch.dseq == 3 ? NATS_OK : NATS_ERR); - natsMutex_Unlock(sub->mu); - testCond(s == NATS_OK); - - test("Check HB is not given to app: "); - s = natsSubscription_NextMsg(&msg, sub, 250); - testCond((s == NATS_TIMEOUT) && (msg == NULL)); - nats_clearLastError(); - - test("Skip: "); - PUBLISH_FAKE_JS_MSG_WITH_SEQ("$JS.ACK.TEST.dur2.1.4.4.1624482520000000000.0", "msg4 fake"); - testCond(s == NATS_OK); - - // For sync subs, we should not get async error - test("No async error: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.done) - s = natsCondition_TimedWait(args.c, args.m, 300); - natsMutex_Unlock(args.m); - testCond(s == NATS_TIMEOUT); - nats_clearLastError(); - PUBLISH_FAKE_RESET(); - - test("NextMsg reports error: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_MISMATCH) && (msg == NULL)); - nats_clearLastError(); - - test("NextMsg again is ok: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) && (msg != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Check get mismatch: "); - s = natsSubscription_GetSequenceMismatch(&csm, sub); - testCond((s == NATS_OK) - && (csm.Stream == 4) - && (csm.ConsumerClient == 4) - && (csm.ConsumerServer == 3)); - - test("Check mismatch suppression cleared: "); - // Send real message so that all clears up - s = js_Publish(NULL, js, "foo", "msg4", 4, NULL, &jerr); - nats_Sleep(300); - natsSubAndLdw_Lock(sub); - s = (sub->jsi->ssmn == false && sub->jsi->sm == false ? NATS_OK : NATS_ERR); - natsSubAndLdw_Unlock(sub); - testCond(s == NATS_OK); - - test("Skip again: "); - PUBLISH_FAKE_JS_MSG_WITH_SEQ("$JS.ACK.TEST.dur1.1.5.5.1624492520000000000.0", "msg5 fake"); - testCond(s == NATS_OK); - - test("NextMsg reports error: "); - nats_Sleep(300); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_MISMATCH) && (msg == NULL)); - nats_clearLastError(); - PUBLISH_FAKE_RESET(); - - test("Check HB timer reports missed HB: "); - s = NATS_OK; - _stopServer(pid); - nats_Sleep(500); - pid = _startServer("nats://127.0.0.1:4222", cmdLine, true); - CHECK_SERVER_STARTED(pid); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.results[1] == 0)) - s = natsCondition_TimedWait(args.c, args.m, 1000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create consumer with HB: "); - jsConsumerConfig_Init(&cc); - cc.Durable = "qdur"; - cc.DeliverSubject = "bar"; - cc.DeliverGroup = "queue"; - cc.Heartbeat = 150*1000000; - s = js_AddConsumer(NULL, js, "TEST", &cc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Queue sub against this consumer: "); - jsSubOptions_Init(&so); - so.Queue = "queue"; - so.Stream = "TEST"; - so.Consumer = "qdur"; - so.Config.DeliverSubject = "bar"; - s = js_Subscribe(&sub, js, "bar", _jsMsgHandler, (void*)&args, NULL, &so, &jerr); - if (s == NATS_ERR) - s = js_SubscribeSync(&sub, js, "bar", NULL, &so, &jerr); - testCond((s == NATS_ERR) && (sub == NULL) && (jerr == 0) - && (strstr(nats_GetLastError(NULL), jsErrNoHeartbeatForQueueSub) != NULL)); - nats_clearLastError(); - - natsSubscription_Destroy(nsub); - JS_TEARDOWN; - _destroyDefaultThreadArgs(&args); - natsOptions_Destroy(opts); -} - -static void -test_JetStreamSubscribeFlowControl(void) -{ - natsStatus s; - natsSubscription *sub= NULL; - jsErrCode jerr= 0; - jsStreamConfig sc; - jsSubOptions so; - natsMsg *msg = NULL; - struct threadArg args; - natsSubscription *nsub = NULL; - int i; - int total = 20000; - char *data = NULL; - char *subj = NULL; - natsBuffer *buf = NULL; - - JS_SETUP(2, 3, 3); - - data = malloc(100*1024); - if (data == NULL) - FAIL("Unable to allocate data"); - - if (valgrind) - total = 2000; - - if (data == NULL) - FAIL("Unable to allocate data"); - for (i=0; i<1024; i++) - data[i] = 'A'; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s != NATS_OK) - FAIL("Unable to setup test"); - args.control = 2; - args.results[0] = total; - - test("Create stream: "); - jsStreamConfig_Init(&sc); - sc.Name = "TEST"; - sc.Subjects = (const char*[2]){"foo", "bar"}; - sc.SubjectsLen = 2; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Populate: "); - for (i=0; (s == NATS_OK) && (i"); - testCond((s == NATS_OK) && (nsub != NULL)); - - test("FC requires HB: "); - jsSubOptions_Init(&so); - so.Config.FlowControl = true; - s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr); - testCond((s == NATS_ERR) && (sub == NULL) && (jerr == JSConsumerWithFlowControlNeedsHeartbeatsErr)); - nats_clearLastError(); - - test("Subscribe async: "); - jsSubOptions_Init(&so); - so.Config.FlowControl = true; - so.Config.Heartbeat = NATS_SECONDS_TO_NANOS(1); - s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Check msg received: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum != total)) - s = natsCondition_TimedWait(args.c, args.m, 10000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - test("Check FC responses were sent: "); - s = natsSubscription_NextMsg(&msg, nsub, 2000); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - msg = NULL; - - natsSubscription_Destroy(sub); - sub = NULL; - natsSubscription_Destroy(nsub); - nsub = NULL; - - test("Create sub to check for FC: "); - s = natsConnection_SubscribeSync(&nsub, nc, "$JS.FC.TEST.>"); - testCond((s == NATS_OK) && (nsub != NULL)); - - test("Subscribe sync: "); - jsSubOptions_Init(&so); - so.Config.FlowControl = true; - so.Config.Heartbeat = NATS_SECONDS_TO_NANOS(1); - s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Check msg received: "); - for (i=0; (s == NATS_OK) && (i"); - testCond((s == NATS_OK) && (nsub != NULL)); - - test("Subscribe: "); - jsSubOptions_Init(&so); - so.Config.FlowControl = true; - so.Config.Heartbeat = NATS_SECONDS_TO_NANOS(1); - s = js_Subscribe(&sub, js, "bar", _jsMsgHandler, (void*) &args, NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Send hb header: "); - natsSub_Lock(sub); - subj = sub->subject; - natsSub_Unlock(sub); - s = natsBuf_Create(&buf, 256); - IFOK(s, natsBuf_Append(buf, "HPUB ", -1)); - IFOK(s, natsBuf_Append(buf, subj, -1)); - IFOK(s, natsBuf_Append(buf, " 76 76\r\n", -1)); - IFOK(s, natsBuf_Append(buf, "NATS/1.0 100 Idle Heartbeat\r\n", -1)); - IFOK(s, natsBuf_Append(buf, "Nats-Consumer-Stalled: $JS.FC.TEST.fc.reply\r\n\r\n\r\n", -1)); - natsConn_Lock(nc); - IFOK(s, natsSock_WriteFully(&nc->sockCtx, natsBuf_Data(buf), natsBuf_Len(buf))); - natsConn_Unlock(nc); - testCond(s == NATS_OK); - - test("Check FC reply due to HB header: "); - IFOK(s, natsSubscription_NextMsg(&msg, nsub, 1000)); - testCond(s == NATS_OK); - - natsBuf_Destroy(buf); - natsMsg_Destroy(msg); - natsSubscription_Destroy(sub); - natsSubscription_Destroy(nsub); - _destroyDefaultThreadArgs(&args); - JS_TEARDOWN; -} - -static void -_jsPubThread(void *closure) -{ - jsCtx *js = (jsCtx*) closure; - int i; - - nats_Sleep(300); - for (i=0; i<5; i++) - js_Publish(NULL, js, "foo", "hello", 5, NULL, NULL); -} - -static void -_sendToPullSub(void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - natsMsg *msg = NULL; - char *subj = NULL; - natsSubscription *sub = NULL; - uint64_t id = 0; - natsStatus s; - - nats_Sleep(250); - natsMutex_Lock(args->m); - sub = args->sub; - natsSub_Lock(sub); - id = sub->jsi->fetchID; - if (args->sum != 0) - id = (uint64_t) args->sum; - if (nats_asprintf(&subj, "%.*s%" PRIu64, (int) strlen(sub->subject)-1, sub->subject, id) < 0) - s = NATS_NO_MEMORY; - else - s = natsMsg_create(&msg, subj, (int) strlen(subj), - NULL, 0, args->string, (int) strlen(args->string), (int) strlen(args->string)); - natsSub_Unlock(sub); - IFOK(s, natsConnection_PublishMsg(args->nc, msg)); - natsMutex_Unlock(args->m); - natsMsg_Destroy(msg); - free(subj); -} - -static void -_fetchRequest(void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - natsSubscription *sub = NULL; - jsFetchRequest fr; - natsStatus s; - natsMsgList list; - int64_t start; - - natsMutex_Lock(args->m); - sub = args->sub; - natsMutex_Unlock(args->m); - - jsFetchRequest_Init(&fr); - // With current messages, for a MaxBytes of 150, we should get 2 messages, - // for a total size of 142. - fr.Batch = 10; - fr.MaxBytes = 150; - fr.Expires = NATS_SECONDS_TO_NANOS(2); - start = nats_Now(); - s = natsSubscription_FetchRequest(&list, sub, &fr); - if (s == NATS_OK) - { - int i; - int total = 0; - - for (i=0; iwsz; - natsMsg_AckSync(list.Msgs[i], NULL, NULL); - } - - if ((total > 150) || (list.Count != 2) || ((nats_Now() - start) >= 1900)) - s = NATS_ERR; - - natsMsgList_Destroy(&list); - } - - natsMutex_Lock(args->m); - args->status = s; - natsMutex_Unlock(args->m); -} - -static void -_dropIdleHBs(natsConnection *nc, natsMsg **msg, void* closure) -{ - int ct = 0; - - if (natsMsg_GetDataLength(*msg) > 0) - return; - - if (!natsMsg_isJSCtrl(*msg, &ct)) - return; - - if (ct != jsCtrlHeartbeat) - return; - - natsMsg_Destroy(*msg); - *msg = NULL; -} - -static void -_dropTimeoutProto(natsConnection *nc, natsMsg **msg, void* closure) -{ - const char *val = NULL; - - if (natsMsgHeader_Get(*msg, STATUS_HDR, &val) != NATS_OK) - return; - - natsMsg_Destroy(*msg); - *msg = NULL; -} - -static void -test_JetStreamSubscribePull(void) -{ - natsStatus s; - natsSubscription *sub= NULL; - natsMsg *msg = NULL; - jsErrCode jerr= 0; - natsMsgList list; - jsStreamConfig sc; - jsSubOptions so; - struct threadArg args; - int64_t start, dur; - int i; - natsThread *t = NULL; - jsConsumerConfig cc; - jsFetchRequest fr; - natsSubscription *sub2 = NULL; - natsSubscription *sub3 = NULL; - - JS_SETUP(2, 9, 2); - - s = _createDefaultThreadArgsForCbTests(&args); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - test("Create pull sub (invalid args): "); - s = js_PullSubscribe(NULL, js, "foo", "dur", NULL, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_PullSubscribe(&sub, NULL, "foo", "dur", NULL, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_PullSubscribe(&sub, js, NULL, "dur", NULL, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_PullSubscribe(&sub, js, "", "dur", NULL, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0)); - nats_clearLastError(); - - test("Create reg sync sub: "); - s = natsConnection_SubscribeSync(&sub, nc, "foo"); - testCond((s == NATS_OK) && (sub != NULL)); - - test("Fetch bad args: "); - s = natsSubscription_Fetch(NULL, sub, 1, 1000, NULL); - if (s == NATS_INVALID_ARG) - s = natsSubscription_Fetch(&list, NULL, 1, 1000, NULL); - if (s == NATS_INVALID_ARG) - s = natsSubscription_Fetch(&list, sub, 0, 1000, NULL); - if (s == NATS_INVALID_ARG) - s = natsSubscription_Fetch(&list, sub, -1, 1000, NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Fetch bad timeout: "); - s = natsSubscription_Fetch(&list, sub, 1, 0, NULL); - if (s == NATS_INVALID_TIMEOUT) - s = natsSubscription_Fetch(&list, sub, 1, -1, NULL); - testCond(s == NATS_INVALID_TIMEOUT); - nats_clearLastError(); - - test("Fetch bad sub: "); - s = natsSubscription_Fetch(&list, sub, 1, 1000, NULL); - testCond((s == NATS_INVALID_SUBSCRIPTION) - && (strstr(nats_GetLastError(NULL), jsErrNotAPullSubscription) != NULL)); - nats_clearLastError(); - - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create reg async sub: "); - s = natsConnection_Subscribe(&sub, nc, "foo", _dummyMsgHandler, NULL); - testCond((s == NATS_OK) && (sub != NULL)); - - test("Fetch bad sub: "); - s = natsSubscription_Fetch(&list, sub, 1, 1000, NULL); - testCond((s == NATS_INVALID_SUBSCRIPTION) - && (strstr(nats_GetLastError(NULL), jsErrNotAPullSubscription) != NULL)); - nats_clearLastError(); - - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create stream: "); - jsStreamConfig_Init(&sc); - sc.Name = "TEST"; - sc.Subjects = (const char*[1]){"foo"}; - sc.SubjectsLen = 1; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("AckNone ok: "); - jsSubOptions_Init(&so); - so.Config.AckPolicy = js_AckNone; - s = js_PullSubscribe(&sub, js, "foo", "ackNone", NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - natsSubscription_Unsubscribe(sub); - natsSubscription_Destroy(sub); - sub = NULL; - - test("AckAll ok: "); - jsSubOptions_Init(&so); - so.Config.AckPolicy = js_AckAll; - s = js_PullSubscribe(&sub, js, "foo", "ackAll", NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - natsSubscription_Unsubscribe(sub); - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create push consumer: "); - jsConsumerConfig_Init(&cc); - cc.Durable = "push_dur"; - cc.DeliverSubject = "push.deliver"; - s = js_AddConsumer(NULL, js, "TEST", &cc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Try create pull sub from push consumer: "); - s = js_PullSubscribe(&sub, js, "foo", "push_dur", NULL, NULL, &jerr); - testCond((s == NATS_ERR) && (sub == NULL) && (jerr == 0) - && (strstr(nats_GetLastError(NULL), jsErrPullSubscribeToPushConsumer) != NULL)); - nats_clearLastError(); - - test("Create pull bound failure: "); - jsSubOptions_Init(&so); - so.Stream = "TEST"; - so.Consumer = "bar"; - s = js_PullSubscribe(&sub, js, "foo", "bar", NULL, &so, &jerr); - testCond((s == NATS_NOT_FOUND) && (sub == NULL) && (jerr == JSConsumerNotFoundErr)); - nats_clearLastError(); - - test("Create pull sub: "); - jsSubOptions_Init(&so); - so.Config.MaxAckPending = 10; - so.Config.AckWait = NATS_MILLIS_TO_NANOS(300); - s = js_PullSubscribe(&sub, js, "foo", "dur", NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Can't call NextMsg: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_INVALID_SUBSCRIPTION) && (msg == NULL) - && (strstr(nats_GetLastError(NULL), jsErrNotApplicableToPullSub) != NULL)); - nats_clearLastError(); - - test("Fetch, no msg avail, timeout: "); - start = nats_Now(); - s = natsSubscription_Fetch(&list, sub, 1, 500, &jerr); - dur = nats_Now() - start; - testCond((s == NATS_TIMEOUT) && (list.Msgs == NULL) && (list.Count == 0) && (jerr == 0) - && (dur >= 450) && (dur <= 600)); - nats_clearLastError(); - - test("Send a message: "); - s = js_Publish(NULL, js, "foo", "hello", 5, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Fetch batch 1: "); - start = nats_Now(); - s = natsSubscription_Fetch(&list, sub, 1, 1000, &jerr); - dur = nats_Now() - start; - testCond((s == NATS_OK) && (list.Msgs != NULL) && (list.Count == 1) && (jerr == 0) - && (dur <= 500)); - - test("Ack: "); - for (i=0; (s == NATS_OK) && (isubject, (int) strlen(sub->subject), NULL, 0, - "NATS/1.0\r\nk:v\r\n\r\n", 17, 17); - IFOK(s, natsConnection_PublishMsg(nc, msg)); - IFOK(s, natsSubscription_Fetch(&list, sub, 1, 1000, &jerr)); - testCond((s == NATS_OK) && (list.Msgs != NULL) && (list.Count == 1) && (jerr == 0)); - natsMsg_Destroy(msg); - msg = NULL; - natsMsgList_Destroy(&list); - - // Make sure there is no more pending. - _waitSubPending(sub, 0); - - test("Msg with 404 status present before fetch: "); - s = natsMsg_create(&msg, sub->subject, (int) strlen(sub->subject), NULL, 0, - "NATS/1.0 404 No Messages\r\n\r\n", 28, 28); - IFOK(s, natsConnection_PublishMsg(nc, msg)); - if (s == NATS_OK) - _waitSubPending(sub, 1); - IFOK(s, natsSubscription_Fetch(&list, sub, 1, 100, &jerr)); - testCond((s == NATS_TIMEOUT) && (list.Msgs == NULL) && (list.Count == 0) && (jerr == 0)); - nats_clearLastError(); - natsMsg_Destroy(msg); - msg = NULL; - natsMsgList_Destroy(&list); - - // Since we faked the 404, the server is going to send a 408 when the request - // expires, so wait for it to be sent. - nats_Sleep(200); - - test("Fetch returns on 408: "); - natsMutex_Lock(args.m); - args.nc = nc; - args.sub = sub; - args.string = "NATS/1.0 408 Request Timeout\r\n\r\n"; - natsMutex_Unlock(args.m); - start = nats_Now(); - s = natsThread_Create(&t, _sendToPullSub, (void*) &args); - IFOK(s, natsSubscription_Fetch(&list, sub, 1, 1000, &jerr)); - dur = nats_Now() - start; - // Since we wait 250ms to publish, it will take aound 250ms - testCond((s == NATS_TIMEOUT) && (list.Msgs == NULL) && (list.Count == 0) && (jerr == 0) - && (dur < 500)); - nats_clearLastError(); - natsMsgList_Destroy(&list); - - natsThread_Join(t); - natsThread_Destroy(t); - t = NULL; - - test("Fetch gets 503: "); - natsMutex_Lock(args.m); - args.string = "NATS/1.0 503\r\n\r\n"; - natsMutex_Unlock(args.m); - s = natsThread_Create(&t, _sendToPullSub, (void*) &args); - IFOK(s, natsSubscription_Fetch(&list, sub, 1, 10000, &jerr)); - testCond((s == NATS_NO_RESPONDERS) && (list.Msgs == NULL) && (list.Count == 0) && (jerr == 0)); - nats_clearLastError(); - natsMsg_Destroy(msg); - msg = NULL; - natsMsgList_Destroy(&list); - - natsThread_Join(t); - natsThread_Destroy(t); - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create stream: "); - jsStreamConfig_Init(&sc); - sc.Name = "TEST2"; - sc.Subjects = (const char*[4]){"bar", "baz", "bat", "box"}; - sc.SubjectsLen = 4; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Pull with max waiting: "); - jsSubOptions_Init(&so); - so.Config.MaxWaiting = 2; - s = js_PullSubscribe(&sub, js, "bar", "pullmaxwaiting", NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Fill requests: "); - // Send requests manually to use the max requests - s = natsConnection_SubscribeSync(&sub2, nc, "my.pull.cons.inbox1"); - IFOK(s, natsConnection_SubscribeSync(&sub3, nc, "my.pull.cons.inbox2")); - IFOK(s, natsConnection_PublishRequestString(nc, "$JS.API.CONSUMER.MSG.NEXT.TEST2.pullmaxwaiting", "my.pull.cons.inbox1", "{\"batch\":1,\"expires\":1000000000}")); - IFOK(s, natsConnection_PublishRequestString(nc, "$JS.API.CONSUMER.MSG.NEXT.TEST2.pullmaxwaiting", "my.pull.cons.inbox1", "{\"batch\":1,\"expires\":1000000000}")); - testCond(s == NATS_OK); - - test("Max waiting error: "); - s = natsSubscription_Fetch(&list, sub, 1, 1000, &jerr); - testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "Exceeded") != NULL)); - nats_clearLastError(); - - natsSubscription_Destroy(sub2); - natsSubscription_Destroy(sub3); - natsSubscription_Destroy(sub); - sub = NULL; - - test("Max request batch: "); - jsSubOptions_Init(&so); - so.Config.MaxRequestBatch = 2; - s = js_PullSubscribe(&sub, js, "baz", "max-request-batch", NULL, &so, &jerr); - IFOK(s, natsSubscription_Fetch(&list, sub, 10, 1000, &jerr)); - testCond((s != NATS_OK) && (list.Count == 0) && (list.Msgs == NULL) && (jerr == 0) - && (strstr(nats_GetLastError(NULL), "MaxRequestBatch of 2") != NULL)); - nats_clearLastError(); - natsSubscription_Destroy(sub); - sub = NULL; - - test("Max request expires: "); - jsSubOptions_Init(&so); - so.Config.MaxRequestExpires = NATS_MILLIS_TO_NANOS(50); - s = js_PullSubscribe(&sub, js, "baz", "max-request-expires", NULL, &so, &jerr); - IFOK(s, natsSubscription_Fetch(&list, sub, 10, 1000, &jerr)); - testCond((s != NATS_OK) && (list.Count == 0) && (list.Msgs == NULL) && (jerr == 0) - && (strstr(nats_GetLastError(NULL), "MaxRequestExpires of 50ms") != NULL)); - nats_clearLastError(); - - natsSubscription_Destroy(sub); - sub = NULL; - - test("Ephemeral pull allowed (NULL): "); - s = js_PullSubscribe(&sub, js, "bar", NULL, NULL, NULL, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Send a message: "); - s = js_Publish(NULL, js, "bar", "hello", 5, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Msgs received: "); - s = natsSubscription_Fetch(&list, sub, 1, 1000, &jerr); - testCond((s == NATS_OK) && (list.Msgs != NULL) && (list.Count == 1) && (jerr == 0)); - - natsMsgList_Destroy(&list); - natsSubscription_Destroy(sub); - sub = NULL; - - test("Ephemeral pull allowed (empty): "); - s = js_PullSubscribe(&sub, js, "bar", "", NULL, NULL, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Msgs received: "); - s = natsSubscription_Fetch(&list, sub, 1, 1000, &jerr); - testCond((s == NATS_OK) && (list.Msgs != NULL) && (list.Count == 1) && (jerr == 0)); - - natsMsgList_Destroy(&list); - - test("Fetch returns before 408: "); - natsConn_setFilter(nc, _dropTimeoutProto); - start = nats_Now(); - s = natsSubscription_Fetch(&list, sub, 1, 1000, NULL); - dur = nats_Now() - start; - testCond((s == NATS_TIMEOUT) && (list.Count == 0) && (dur >= 600)); - nats_clearLastError(); - - test("Next Fetch waits for proper timeout: "); - nats_Sleep(100); - natsConn_setFilter(nc, NULL); - natsMutex_Lock(args.m); - args.nc = nc; - args.sub = sub; - args.string = "NATS/1.0 408 Request Timeout\r\n\r\n"; - // Will make the 408 sent to a subject ID with 1 while we are likely at 2 or above. - // So this will be considered a "late" 408 timeout and should be ignored. - args.sum = 1; - natsMutex_Unlock(args.m); - s = natsThread_Create(&t, _sendToPullSub, (void*) &args); - start = nats_Now(); - IFOK(s, natsSubscription_Fetch(&list, sub, 1, 1000, NULL)); - dur = nats_Now() - start; - testCond((s == NATS_TIMEOUT) && (list.Count == 0) && (dur >= 600)); - nats_clearLastError(); - - natsThread_Join(t); - natsThread_Destroy(t); - t = NULL; - - natsSubscription_Destroy(sub); - sub = NULL; - - test("jsFetchRequest init bad args: "); - s = jsFetchRequest_Init(NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Create pull consumer with MaxRequestBytes: "); - jsSubOptions_Init(&so); - so.Config.MaxRequestMaxBytes = 1024; - s = js_PullSubscribe(&sub, js, "bat", "max-request-bytes", NULL, &so, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - jsFetchRequest_Init(&fr); - - test("FetchRequest bad args: "); - s = natsSubscription_FetchRequest(NULL, sub, &fr); - if (s == NATS_INVALID_ARG) - s = natsSubscription_FetchRequest(&list, NULL, &fr); - if (s == NATS_INVALID_ARG) - s = natsSubscription_FetchRequest(&list, sub, NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("FetchRequest no expiration err: "); - jsFetchRequest_Init(&fr); - // If NoWait is false, then Expires must be set. - fr.Batch = 1; - s = natsSubscription_FetchRequest(&list, sub, &fr); - testCond((s == NATS_INVALID_TIMEOUT && (list.Count == 0) && (list.Msgs == NULL))); - nats_clearLastError(); - - test("Batch must be set: "); - jsFetchRequest_Init(&fr); - fr.MaxBytes = 100; - fr.Expires = NATS_SECONDS_TO_NANOS(1); - s = natsSubscription_FetchRequest(&list, sub, &fr); - testCond((s == NATS_INVALID_ARG) && (list.Count == 0) && (list.Msgs == NULL)); - nats_clearLastError(); - - test("MaxBytes must be > 0: "); - jsFetchRequest_Init(&fr); - fr.Batch = 1; - fr.MaxBytes = -100; - fr.Expires = NATS_SECONDS_TO_NANOS(1); - s = natsSubscription_FetchRequest(&list, sub, &fr); - testCond((s == NATS_INVALID_ARG) && (list.Count == 0) && (list.Msgs == NULL)); - nats_clearLastError(); - - test("Requesting more than allowed max bytes: "); - jsFetchRequest_Init(&fr); - fr.Batch = 1; - fr.MaxBytes = 2048; - fr.Expires = NATS_SECONDS_TO_NANOS(1); - s = natsSubscription_FetchRequest(&list, sub, &fr); - testCond((s == NATS_ERR) && (list.Count == 0) && (list.Msgs == NULL) - && (strstr(nats_GetLastError(NULL), "Exceeded MaxRequestMaxBytes") != NULL)); - nats_clearLastError(); - - test("No concurrent call: "); - natsMutex_Lock(args.m); - args.sub = sub; - args.status = NATS_OK; - natsMutex_Unlock(args.m); - s = natsThread_Create(&t, _fetchRequest, (void*) &args); - if (s == NATS_OK) - { - nats_Sleep(250); - jsFetchRequest_Init(&fr); - fr.Batch = 1; - fr.Expires = NATS_SECONDS_TO_NANOS(1); - s = natsSubscription_FetchRequest(&list, sub, &fr); - } - testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), jsErrConcurrentFetchNotAllowed) != NULL)); - nats_clearLastError(); - s = NATS_OK; - - test("Populate: "); - for (i=0; (s == NATS_OK) && (i<10); i++) - s = js_PublishAsync(js, "bat", (const void*) "abcdefghij", 10, NULL); - testCond(s == NATS_OK); - - test("Received ok: "); - natsThread_Join(t); - natsMutex_Lock(args.m); - s = args.status; - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - natsThread_Destroy(t); - natsSubscription_Destroy(sub); - sub = NULL; - - test("Create pull consumer: "); - s = js_PullSubscribe(&sub, js, "box", "feth-request", NULL, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Populate: "); - s = js_PublishAsync(js, "box", (const void*) "abcdefghij", 10, NULL); - testCond(s == NATS_OK); - - test("Check expiration: "); - // Unlike with the simple fetch, asking for more than is avail will - // wait until expiration to return. - jsFetchRequest_Init(&fr); - fr.Batch = 10; - fr.Expires = NATS_MILLIS_TO_NANOS(500); - fr.Heartbeat = NATS_MILLIS_TO_NANOS(50); - start = nats_Now(); - s = natsSubscription_FetchRequest(&list, sub, &fr); - dur = nats_Now() - start; - testCond((s == NATS_OK) && (list.Count == 1) && (list.Msgs != NULL) - && (dur > 400) && (dur < 600)); - natsMsgList_Destroy(&list); - -#if _WIN32 - nats_Sleep(1000); -#endif - - test("Check invalid hb: "); - jsFetchRequest_Init(&fr); - fr.Batch = 10; - fr.Expires = NATS_SECONDS_TO_NANOS(1); - fr.Heartbeat = NATS_SECONDS_TO_NANOS(10); - s = natsSubscription_FetchRequest(&list, sub, &fr); - testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "too large") != NULL)); - nats_clearLastError(); - - test("Check idle hearbeat: "); - jsFetchRequest_Init(&fr); - fr.Batch = 10; - // Let's make it wait for 2 seconds - fr.Expires = NATS_SECONDS_TO_NANOS(2); - // And have HBs every 50ms - fr.Heartbeat = NATS_MILLIS_TO_NANOS(50); - // Set a message filter that will drop HB messages - natsConn_setFilter(nc, _dropIdleHBs); - start = nats_Now(); - // We should be kicked out of the fetch request with an error indicating - // that we missed hearbeats. - s = natsSubscription_FetchRequest(&list, sub, &fr); - dur = nats_Now() - start; - testCond((s == NATS_MISSED_HEARTBEAT) && (dur < 500)); - - natsSubscription_Destroy(sub); - JS_TEARDOWN; - _destroyDefaultThreadArgs(&args); -} - -static void -test_JetStreamSubscribeHeadersOnly(void) -{ - natsStatus s; - natsSubscription *sub= NULL; - natsMsg *msg = NULL; - jsErrCode jerr= 0; - jsStreamConfig sc; - jsSubOptions so; - int i; - - JS_SETUP(2, 6, 2); - - test("Create stream: "); - jsStreamConfig_Init(&sc); - sc.Name = "S"; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Populate: "); - for (i=0; (s == NATS_OK) && (i<10); i++) - { - s = natsMsg_Create(&msg, "S", NULL, "hello", 5); - IFOK(s, natsMsgHeader_Set(msg, "User-Header", "MyValue")); - IFOK(s, js_PublishMsg(NULL, js, msg, NULL, NULL)); - natsMsg_Destroy(msg); - msg = NULL; - } - testCond(s == NATS_OK); - - test("Create consumer with headers only: "); - jsSubOptions_Init(&so); - so.Config.HeadersOnly = true; - s = js_SubscribeSync(&sub, js, "S", NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Verify only headers: "); - for (i=0; (s == NATS_OK) && (i<10); i++) - { - const char *val = NULL; - s = natsSubscription_NextMsg(&msg, sub, 1000); - IFOK(s, (natsMsg_GetDataLength(msg) == 0 ? NATS_OK : NATS_ERR)); - IFOK(s, natsMsgHeader_Get(msg, "User-Header", &val)); - IFOK(s, (strcmp(val, "MyValue") == 0 ? NATS_OK : NATS_ERR)); - IFOK(s, natsMsgHeader_Get(msg, JSMsgSize, &val)); - IFOK(s, (strcmp(val, "5") == 0 ? NATS_OK : NATS_ERR)); - natsMsg_Destroy(msg); - msg = NULL; - } - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - jsCtx_Destroy(js); - natsConnection_Destroy(nc); - _stopServer(pid); - rmtree(datastore); -} - -static void -_orderedConsCB(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - - natsMutex_Lock(args->m); - if (natsMsg_GetDataLength(msg) == 0) - { - args->done = true; - natsCondition_Signal(args->c); - } - else - { - natsBuf_Append(args->buf, natsMsg_GetData(msg), natsMsg_GetDataLength(msg)); - args->sum++; - } - natsMutex_Unlock(args->m); - natsMsg_Destroy(msg); -} - -static natsStatus -_testOrderedCons(jsCtx *js, jsStreamInfo *si, char *asset, int assetLen, struct threadArg *args, bool async) -{ - natsStatus s = NATS_OK; - natsSubscription *sub = NULL; - int received = 0; - jsSubOptions so; - - jsSubOptions_Init(&so); - so.Ordered = true; - so.Config.Heartbeat = NATS_MILLIS_TO_NANOS(250); - - s = natsBuf_Create(&args->buf, assetLen); - if ((s == NATS_OK) && async) - { - s = js_Subscribe(&sub, js, "a", _orderedConsCB, (void*) args, NULL, &so, NULL); - if (s == NATS_OK) - { - natsMutex_Lock(args->m); - while ((s != NATS_TIMEOUT) && !args->done) - s = natsCondition_TimedWait(args->c, args->m, 5000); - received = args->sum; - natsMutex_Unlock(args->m); - } - } - else if (s == NATS_OK) - { - s = js_SubscribeSync(&sub, js, "a", NULL, &so, NULL); - if (s == NATS_OK) - { - bool done = false; - int64_t start = 0; - natsMsg *msg = NULL; - - start = nats_Now(); - while ((s == NATS_OK) && !done) - { - s = natsSubscription_NextMsg(&msg, sub, 5000); - if (s == NATS_OK) - { - done = (natsMsg_GetDataLength(msg) == 0 ? true : false); - if (!done) - { - s = natsBuf_Append(args->buf, natsMsg_GetData(msg), natsMsg_GetDataLength(msg)); - received++; - } - natsMsg_Destroy(msg); - msg = NULL; - } - if ((s == NATS_OK) && (nats_Now() - start > 5500)) - s = NATS_TIMEOUT; - } - } - } - if ((s == NATS_OK) && (natsBuf_Len(args->buf) != assetLen)) - { - fprintf(stderr, "\nAsset length (%d) does not match received data length (%d)\n", - assetLen, natsBuf_Len(args->buf)); - s = NATS_MISMATCH; - } - else if (s == NATS_OK) - { - int i; - char *data = natsBuf_Data(args->buf); - - for (i=0; iState.Msgs-1)); - - fflush(stderr); - natsSubscription_Destroy(sub); - natsMutex_Lock(args->m); - natsBuf_Destroy(args->buf); - args->buf = NULL; - args->sum = 0; - args->done = false; - natsMutex_Unlock(args->m); - - return s; -} - -static void -_singleLoss(natsConnection *nc, natsMsg **msg, void* closure) -{ - const char *val = NULL; - int res = rand() % 100; - - if ((res <= 10) && (natsMsgHeader_Get(*msg, "data", &val) == NATS_OK)) - { - natsMsg_Destroy(*msg); - *msg = NULL; - natsConn_setFilter(nc, NULL); - } -} - -static void -_multiLoss(natsConnection *nc, natsMsg **msg, void* closure) -{ - const char *val = NULL; - int res = rand() % 100; - - if ((res <= 10) && (natsMsgHeader_Get(*msg, "data", &val) == NATS_OK)) - { - natsMsg_Destroy(*msg); - *msg = NULL; - } -} - -static void -_firstOnlyLoss(natsConnection *nc, natsMsg **msg, void* closure) -{ - jsMsgMetaData *meta = NULL; - - if (natsMsg_GetMetaData(&meta, *msg) == NATS_OK) - { - if (meta->Sequence.Consumer == 1) - { - natsMsg_Destroy(*msg); - *msg = NULL; - natsConn_setFilter(nc, NULL); - } - jsMsgMetaData_Destroy(meta); - } -} - -static void -_lastOnlyLoss(natsConnection *nc, natsMsg **msg, void* closure) -{ - jsMsgMetaData *meta = NULL; - jsStreamInfo *si = (jsStreamInfo*) closure; - - if (natsMsg_GetMetaData(&meta, *msg) == NATS_OK) - { - if (meta->Sequence.Stream == si->State.LastSeq-1) - { - natsMsg_Destroy(*msg); - *msg = NULL; - natsConn_setFilter(nc, NULL); - } - jsMsgMetaData_Destroy(meta); - } -} - -static void -test_JetStreamOrderedConsumer(void) -{ - natsStatus s; - natsSubscription *sub= NULL; - jsErrCode jerr= 0; - jsStreamConfig sc; - jsSubOptions so; - struct threadArg args; - int i; - char *asset = NULL; - int assetLen = 1024*1024; - const int chunkSize = 1024; - jsStreamInfo *si = NULL; - jsConsumerInfo *ci1 = NULL; - jsConsumerInfo *ci2 = NULL; - - JS_SETUP(2, 3, 3); - - s = _createDefaultThreadArgsForCbTests(&args); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - test("Create stream: "); - jsStreamConfig_Init(&sc); - sc.Name = "OBJECT"; - sc.Subjects = (const char*[1]){"a"}; - sc.SubjectsLen = 1; - sc.Storage = js_MemoryStorage; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - // Create a sample asset. - asset = malloc(assetLen); - for (i=0; iName, ci2->Name) == 0)); - jsConsumerInfo_Destroy(ci1); - jsConsumerInfo_Destroy(ci2); - - free(asset); - jsStreamInfo_Destroy(si); - natsSubscription_Destroy(sub); - _destroyDefaultThreadArgs(&args); - JS_TEARDOWN; -} - -static void -_jsOrderedErrHandler(natsConnection *nc, natsSubscription *subscription, natsStatus err, void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - - natsMutex_Lock(args->m); - args->status = err; - natsCondition_Signal(args->c); - natsMutex_Unlock(args->m); -} - -static void -test_JetStreamOrderedConsumerWithErrors(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsSubscription *sub= NULL; - jsCtx *js = NULL; - natsPid pid = NATS_INVALID_PID; - jsErrCode jerr= 0; - char datastore[256] = {'\0'}; - char cmdLine[1024] = {'\0'}; - jsStreamConfig sc; - jsSubOptions so; - int i, iter; - char *asset = NULL; - int assetLen = 128*1024; - const int chunkSize = 256; - struct threadArg args; - - ENSURE_JS_VERSION(2, 3, 3); - - s = _createDefaultThreadArgsForCbTests(&args); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - _makeUniqueDir(datastore, sizeof(datastore), "datastore_"); - - test("Start JS server: "); - snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s", datastore); - pid = _startServer("nats://127.0.0.1:4222", cmdLine, true); - CHECK_SERVER_STARTED(pid); - testCond(true); - - test("Connect: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetErrorHandler(opts, _jsOrderedErrHandler, (void*) &args)); - IFOK(s, natsConnection_Connect(&nc, opts)); - testCond(s == NATS_OK); - - test("Get context: "); - s = natsConnection_JetStream(&js, nc, NULL); - testCond(s == NATS_OK); - - // Create a sample asset. - asset = malloc(assetLen); - for (i=0; ijsi->consumer; - natsSub_Unlock(sub); - s = js_DeleteConsumer(js, "OBJECT", cons, NULL, &jerr); - } - testCond((s == NATS_OK) && (jerr == 0)); - - test("Check we get error: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.status != NATS_MISSED_HEARTBEAT)) - s = natsCondition_TimedWait(args.c, args.m, 1000); - args.status = NATS_OK; - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - sub = NULL; - } - - free(asset); - JS_TEARDOWN; - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&args); -} - -static void -_dropMsgFive(natsConnection *nc, natsMsg **msg, void* closure) -{ - const char *val = NULL; - struct threadArg *args = (struct threadArg*) closure; - - if (natsMsgHeader_Get(*msg, "data", &val) == NATS_OK) - { - if (++(args->results[0]) == 5) - { - natsMsg_Destroy(*msg); - *msg = NULL; - natsConn_setFilter(nc, NULL); - } - } -} - -static void -test_JetStreamOrderedConsumerWithAutoUnsub(void) -{ - natsStatus s; - natsConnection *nc2= NULL; - natsSubscription *sub= NULL; - jsCtx *js2= NULL; - jsErrCode jerr= 0; - jsStreamConfig sc; - jsSubOptions so; - struct threadArg args; - int i; - natsStatistics stats; - uint64_t in1 = 0; - uint64_t in2 = 0; - - JS_SETUP(2, 3, 3); - - s = _createDefaultThreadArgsForCbTests(&args); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - test("Create stream: "); - jsStreamConfig_Init(&sc); - sc.Name = "OBJECT"; - sc.Subjects = (const char*[1]){"a"}; - sc.SubjectsLen = 1; - sc.Storage = js_MemoryStorage; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Subscribe: "); - jsSubOptions_Init(&so); - so.Ordered = true; - so.Config.Heartbeat = NATS_MILLIS_TO_NANOS(250); - s = js_Subscribe(&sub, js, "a", _jsMsgHandler, (void*)&args, NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Auto-unsub: "); - s = natsSubscription_AutoUnsubscribe(sub, 10); - testCond(s == NATS_OK); - - // Set a message filter that will drop 1 message - natsConn_setFilterWithClosure(nc, _dropMsgFive, (void*)&args); - - // Now produce 20 messages - test("Produce: "); - for (i=0; (s == NATS_OK) && (i < 20); i++) - { - natsMsg *msg = NULL; - - s = natsMsg_Create(&msg, "a", NULL, "hello", 5); - IFOK(s, natsMsgHeader_Set(msg, "data", "true")); - IFOK(s, js_PublishMsgAsync(js, &msg, NULL)); - natsMsg_Destroy(msg); - msg = NULL; - } - testCond(s == NATS_OK); - - test("Wait for all pubs done: "); - s = js_PublishAsyncComplete(js, NULL); - testCond(s == NATS_OK); - - test("Wait for subscription to be invalid: "); - s = NATS_ERR; - for (i=0; i<10; i++) - { - if (!natsSubscription_IsValid(sub)) - { - s = NATS_OK; - break; - } - nats_Sleep(100); - } - testCond(s == NATS_OK); - - // Wait a bit to make sure we are not receiving more than expected, - // and give a chance for the server to process the auto-unsub - // protocol. - nats_Sleep(500); - - test("Check count: "); - natsMutex_Lock(args.m); - s = (args.sum == 10 ? NATS_OK : NATS_ERR); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - // Now capture the in msgs count for the connection - test("Get stats: "); - s = natsConnection_GetStats(nc, &stats); - IFOK(s, natsStatistics_GetCounts(&stats, &in1, NULL, NULL, NULL, NULL)); - testCond(s == NATS_OK); - - // Send one more message and this count should not increase if the - // server had properly processed the auto-unsub after the - // reset of the ordered consumer. Use a different connection - // to send. - test("Send one msg: "); - s = natsConnection_ConnectTo(&nc2, NATS_DEFAULT_URL); - IFOK(s, natsConnection_JetStream(&js2, nc2, NULL)); - IFOK(s, js_Publish(NULL, js2, "a", "bad", 3, NULL, NULL)); - testCond(s == NATS_OK); - - test("Get stats 2: "); - s = natsConnection_GetStats(nc, &stats); - IFOK(s, natsStatistics_GetCounts(&stats, &in2, NULL, NULL, NULL, NULL)); - testCond(s == NATS_OK); - - test("Msg not received: "); - s = (in1 == in2 ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - - natsSubscription_Destroy(sub); - jsCtx_Destroy(js2); - natsConnection_Destroy(nc2); - _destroyDefaultThreadArgs(&args); - JS_TEARDOWN; -} - -static void -test_JetStreamOrderedConsSrvRestart(void) -{ - natsStatus s; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - natsOptions *opts = NULL; - jsConsumerInfo *ci = NULL; - jsErrCode jerr= 0; - jsStreamConfig sc; - jsSubOptions so; - struct threadArg args; - int i; - - JS_SETUP(2, 9, 2); - - s = _createDefaultThreadArgsForCbTests(&args); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - // JS_SETUP creates a basic connection, we want to know when - // we reconnected, so create a new one. - test("Connect: "); - natsConnection_Destroy(nc); - nc = NULL; - jsCtx_Destroy(js); - js = NULL; - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &args)); - IFOK(s, natsOptions_SetReconnectWait(opts, 100)); - IFOK(s, natsConnection_Connect(&nc, opts)); - IFOK(s, natsConnection_JetStream(&js, nc, NULL)); - testCond(s == NATS_OK); - - test("Create stream: "); - jsStreamConfig_Init(&sc); - sc.Name = "OCRESTART"; - sc.Subjects = (const char*[1]){"foo"}; - sc.SubjectsLen = 1; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Subscribe: "); - jsSubOptions_Init(&so); - so.Ordered = true; - so.Config.Heartbeat = NATS_MILLIS_TO_NANOS(250); - so.Config.HeadersOnly = true; - s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Send 1 message: "); - s = js_Publish(NULL, js, "foo", "hello", 5, NULL, NULL); - testCond(s == NATS_OK) - - test("Consume: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - msg = NULL; - - test("Stopping server: "); - _stopServer(pid); - testCond(true); - // Wait more than the HB failure detection so that we check - // that resetting the ordered consumer works even while the - // server is still down. - test("Waiting before restarting: "); - nats_Sleep(1500); - testCond(true); - // Restart - test("Restarting server: "); - pid = _startServer("nats://127.0.0.1:4222", cmdLine, true); - CHECK_SERVER_STARTED(pid); - testCond(true); - - test("Wait for reconnect: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.reconnected) - s = natsCondition_TimedWait(args.c, args.m, 2000); - testCond(s == NATS_OK); - - test("Send 1 message: "); - s = js_Publish(NULL, js, "foo", "hello", 5, NULL, NULL); - testCond(s == NATS_OK) - - test("Consume: "); - s = natsSubscription_NextMsg(&msg, sub, 5000); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - msg = NULL; - - test("Check configuration is similar: "); - for (i=0; (s==NATS_OK) && (i<10); i++) - { - s = natsSubscription_GetConsumerInfo(&ci, sub, NULL, NULL); - if (s == NATS_OK) - break; - else if (s == NATS_NOT_FOUND) - { - s = NATS_OK; - nats_Sleep(100); - } - } - testCond((s == NATS_OK) - && (ci != NULL) && (ci->Config != NULL) - && (ci->Config->MemoryStorage) - && (ci->Config->Replicas == 1) - && (ci->Config->HeadersOnly)); - jsConsumerInfo_Destroy(ci); - - natsSubscription_Destroy(sub); - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&args); - JS_TEARDOWN; -} - -static void -test_JetStreamSubscribeWithFWC(void) -{ - natsStatus s; - natsSubscription *sub= NULL; - jsErrCode jerr= 0; - jsStreamConfig sc; - jsConsumerConfig cc; - jsSubOptions so; - - JS_SETUP(2, 3, 5); - - test("Create stream: "); - jsStreamConfig_Init(&sc); - sc.Name = "TEST_WC"; - sc.Subjects = (const char*[1]){"fwc.>"}; - sc.SubjectsLen = 1; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Create consumer: "); - jsConsumerConfig_Init(&cc); - cc.Durable = "dur"; - cc.DeliverSubject = "bar"; - cc.FilterSubject = "fwc.>"; - s = js_AddConsumer(NULL, js, "TEST_WC", &cc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Subscribe: "); - jsSubOptions_Init(&so); - so.Config.Durable = "dur"; - s = js_SubscribeSync(&sub, js, "fwc.>", NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL)); - natsSubscription_Destroy(sub); - - JS_TEARDOWN; -} - -static void -test_JetStreamStreamsSealAndRollup(void) -{ - natsStatus s; - jsStreamInfo *si = NULL; - jsStreamConfig cfg; - jsErrCode jerr = 0; - natsMsg *msg = NULL; - natsSubscription *sub = NULL; - int i; - - JS_SETUP(2, 6, 2); - - test("Create sealed stream fails: "); - jsStreamConfig_Init(&cfg); - cfg.Name = "SEAL_FAIL"; - cfg.Sealed = true; - s = js_AddStream(&si, js, &cfg, NULL, &jerr); - testCond((s == NATS_ERR) && (si == NULL) && (jerr == JSStreamInvalidConfig)); - nats_clearLastError(); - - test("Create stream: "); - jsStreamConfig_Init(&cfg); - cfg.Name = "SEAL"; - s = js_AddStream(NULL, js, &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Seal stream: "); - jsStreamConfig_Init(&cfg); - cfg.Name = "SEAL"; - cfg.Sealed = true; - s = js_UpdateStream(&si, js, &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (si != NULL) - && (si->Config != NULL) && si->Config->Sealed); - jsStreamInfo_Destroy(si); - si = NULL; - - test("Can't send: "); - s = js_Publish(NULL, js, "SEAL", "a", 1, NULL, &jerr); - testCond((s == NATS_ERR) && (jerr == JSStreamSealedErr)); - nats_clearLastError(); - - test("Create stream with deny purge/delete: "); - jsStreamConfig_Init(&cfg); - cfg.Name = "AUDIT"; - cfg.Storage = js_MemoryStorage; - cfg.DenyPurge = true; - cfg.DenyDelete = true; - s = js_AddStream(&si, js, &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (si != NULL) - && (si->Config != NULL) && si->Config->DenyDelete - && si->Config->DenyPurge); - jsStreamInfo_Destroy(si); - si = NULL; - - test("Publish: "); - for (i=0; (s == NATS_OK) && (i < 10); i++) - s = js_Publish(NULL, js, "AUDIT", "ok", 2, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Can't delete: "); - s = js_DeleteMsg(js, "AUDIT", 1, NULL, &jerr); - testCond((s == NATS_ERR) && (jerr == JSStreamMsgDeleteFailed) - && (strstr(nats_GetLastError(NULL), "message delete not permitted") != NULL)); - nats_clearLastError(); - - test("Can't purge: "); - s = js_PurgeStream(js, "AUDIT", NULL, &jerr); - testCond((s == NATS_ERR) && (jerr == JSStreamPurgeFailedErr) - && (strstr(nats_GetLastError(NULL), "stream purge not permitted") != NULL)); - nats_clearLastError(); - - test("Try to remove deny clauses: "); - cfg.DenyPurge = false; - cfg.DenyDelete = false; - s = js_UpdateStream(NULL, js, &cfg, NULL, &jerr); - testCond((s == NATS_ERR) && (jerr == JSStreamInvalidConfig)); - nats_clearLastError(); - - test("Create stream for rollup: "); - jsStreamConfig_Init(&cfg); - cfg.Name = "ROLLUP"; - cfg.Subjects = (const char*[1]){"rollup.*"}; - cfg.SubjectsLen = 1; - cfg.AllowRollup = true; - s = js_AddStream(&si, js, &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (si != NULL) - && (si->Config != NULL) && si->Config->AllowRollup); - jsStreamInfo_Destroy(si); - si = NULL; - - test("Populate: "); - for (i=0; (s == NATS_OK) && (i<10); i++) - s = js_Publish(NULL, js, "rollup.a", "a", 1, NULL, &jerr); - for (i=0; (s == NATS_OK) && (i<10); i++) - s = js_Publish(NULL, js, "rollup.b", "b", 1, NULL, &jerr); - for (i=0; (s == NATS_OK) && (i<10); i++) - s = js_Publish(NULL, js, "rollup.c", "c", 1, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Check stream: "); - s = js_GetStreamInfo(&si, js, "ROLLUP", NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (si != NULL) - && (si->State.Msgs == 30)); - jsStreamInfo_Destroy(si); - si = NULL; - - test("Rollup per subject: "); - s = natsMsg_Create(&msg, "rollup.b", NULL, "Rollup", 6); - IFOK(s, natsMsgHeader_Set(msg, JSMsgRollup, JSMsgRollupSubject)); - IFOK(s, js_PublishMsg(NULL, js, msg, NULL, &jerr)); - testCond((s == NATS_OK) && (jerr == 0)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Create consumer: "); - s = js_SubscribeSync(&sub, js, "rollup.b", NULL, NULL, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Check content: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - IFOK(s, (strcmp(natsMsg_GetData(msg), "Rollup") == 0 ? NATS_OK : NATS_ERR)); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - msg = NULL; - - test("Make sure single msg: "); - s = natsSubscription_NextMsg(&msg, sub, 250); - testCond((s == NATS_TIMEOUT) && (msg == NULL)); - nats_clearLastError(); - - natsSubscription_Destroy(sub); - sub = NULL; - - test("Rollup for all: "); - s = natsMsg_Create(&msg, "rollup.c", NULL, "RollupAll", 9); - IFOK(s, natsMsgHeader_Set(msg, JSMsgRollup, JSMsgRollupAll)); - IFOK(s, js_PublishMsg(NULL, js, msg, NULL, &jerr)); - testCond((s == NATS_OK) && (jerr == 0)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Create consumer: "); - s = js_SubscribeSync(&sub, js, "rollup.c", NULL, NULL, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Check content: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - IFOK(s, (strcmp(natsMsg_GetData(msg), "RollupAll") == 0 ? NATS_OK : NATS_ERR)); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - msg = NULL; - - test("Make sure single msg: "); - s = natsSubscription_NextMsg(&msg, sub, 250); - testCond((s == NATS_TIMEOUT) && (msg == NULL)); - nats_clearLastError(); - - test("Check stream: "); - s = js_GetStreamInfo(&si, js, "ROLLUP", NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (si != NULL) - && (si->State.Msgs == 1)); - jsStreamInfo_Destroy(si); - si = NULL; - - natsSubscription_Destroy(sub); - JS_TEARDOWN; -} - -static void -test_JetStreamGetMsgAndLastMsg(void) -{ - natsStatus s; - natsMsg *msg = NULL; - jsStreamConfig cfg; - jsErrCode jerr = 0; - const char *val = NULL; - - JS_SETUP(2, 3, 1); - - test("Create stream: "); - jsStreamConfig_Init(&cfg); - cfg.Name = "GET_MSG"; - cfg.Subjects = (const char*[1]){"foo.*"}; - cfg.SubjectsLen = 1; - cfg.Storage = js_MemoryStorage; - s = js_AddStream(NULL, js, &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Populate: "); - s = js_Publish(NULL, js, "foo.bar", "msg1", 4, NULL, NULL); - IFOK(s, js_Publish(NULL, js, "foo.bar", "msg2", 4, NULL, NULL)); - IFOK(s, js_Publish(NULL, js, "foo.baz", "msg3", 4, NULL, NULL)); - IFOK(s, natsMsg_Create(&msg, "foo.baz", NULL, "msg4", 4)); - IFOK(s, natsMsgHeader_Set(msg, "Some-Header", "Some-Value")); - IFOK(s, js_PublishMsg(NULL, js, msg, NULL, NULL)); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - msg = NULL; - - test("GetMsg bad args: "); - s = js_GetMsg(NULL, js, "GET_MSG", 1, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_GetMsg(&msg, NULL, "GET_MSG", 1, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_GetMsg(&msg, js, "GET_MSG", 0, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) && (msg == NULL) && (jerr == 0)); - nats_clearLastError(); - - test("GetMsg stream name required: "); - s = js_GetMsg(&msg, js, NULL, 1, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_GetMsg(&msg, js, "", 1, NULL, &jerr); - testCond((s == NATS_INVALID_ARG) && (msg == NULL) && (jerr == 0) - && (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL)); - nats_clearLastError(); - - test("GetMsg stream not found: "); - s = js_GetMsg(&msg, js, "NOT_FOUND", 1, NULL, &jerr); - testCond((s == NATS_ERR) && (msg == NULL) && (jerr == JSStreamNotFoundErr) - && (strstr(nats_GetLastError(NULL), "stream not found") != NULL)); - nats_clearLastError(); - - test("GetMsg message not found: "); - s = js_GetMsg(&msg, js, "GET_MSG", 100, NULL, &jerr); - testCond((s == NATS_NOT_FOUND) && (msg == NULL) && (jerr == JSNoMessageFoundErr)); - nats_clearLastError(); - - test("GetMsg message ok: "); - s = js_GetMsg(&msg, js, "GET_MSG", 1, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (msg != NULL) - && (strcmp(natsMsg_GetSubject(msg), "foo.bar") == 0) - && (strcmp(natsMsg_GetData(msg), "msg1") == 0) - && (natsMsg_GetSequence(msg) == 1) - && (natsMsg_GetTime(msg) != 0)); - - natsMsg_Destroy(msg); - msg = NULL; - - // GetLasMsg tests now.... - - test("GetLastMsg bad args: "); - s = js_GetLastMsg(NULL, js, "GET_MSG", "foo.bar", NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_GetLastMsg(&msg, NULL, "GET_MSG", "foo.bar", NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_GetLastMsg(&msg, js, "GET_MSG", NULL, NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_GetLastMsg(&msg, js, "GET_MSG", "", NULL, &jerr); - testCond((s == NATS_INVALID_ARG) && (msg == NULL) && (jerr == 0)); - nats_clearLastError(); - - test("GetLastMsg stream name required: "); - s = js_GetLastMsg(&msg, js, NULL, "foo.bar", NULL, &jerr); - if (s == NATS_INVALID_ARG) - s = js_GetLastMsg(&msg, js, "", "foo.bar", NULL, &jerr); - testCond((s == NATS_INVALID_ARG) && (msg == NULL) && (jerr == 0) - && (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL)); - nats_clearLastError(); - - test("GetLastMsg stream not found: "); - s = js_GetLastMsg(&msg, js, "NOT_FOUND", "foo.bar", NULL, &jerr); - testCond((s == NATS_ERR) && (msg == NULL) && (jerr == JSStreamNotFoundErr) - && (strstr(nats_GetLastError(NULL), "stream not found") != NULL)); - nats_clearLastError(); - - test("GetLastMsg message not found: "); - s = js_GetLastMsg(&msg, js, "GET_MSG", "not.found", NULL, &jerr); - testCond((s == NATS_NOT_FOUND) && (msg == NULL) && (jerr == JSNoMessageFoundErr)); - nats_clearLastError(); - - test("GetLastMsg message ok: "); - s = js_GetLastMsg(&msg, js, "GET_MSG", "foo.baz", NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (msg != NULL) && (msg != NULL) - && (strcmp(natsMsg_GetSubject(msg), "foo.baz") == 0) - && (strcmp(natsMsg_GetData(msg), "msg4") == 0) - && (natsMsgHeader_Get(msg, "Some-Header", &val) == NATS_OK) - && (strcmp(val, "Some-Value") == 0) - && (natsMsg_GetSequence(msg) == 4) - && (natsMsg_GetTime(msg) != 0)); - natsMsg_Destroy(msg); - JS_TEARDOWN; -} - -static void -test_JetStreamConvertDirectMsg(void) -{ - natsStatus s; - natsMsg *msg = NULL; - const char *val = NULL; - - test("Bad request: "); - s = natsMsg_Create(&msg, "inbox", NULL, NULL, 0); - IFOK(s, natsMsgHeader_Set(msg, STATUS_HDR, REQ_TIMEOUT)); - IFOK(s, natsMsgHeader_Set(msg, DESCRIPTION_HDR, "Bad Request")); - IFOK(s, js_directGetMsgToJSMsg("test", msg)); - testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "Bad Request") != NULL)); - nats_clearLastError(); - - test("Not found: "); - s = natsMsgHeader_Set(msg, STATUS_HDR, NOT_FOUND_STATUS); - IFOK(s, natsMsgHeader_Set(msg, DESCRIPTION_HDR, "Message Not Found")); - IFOK(s, js_directGetMsgToJSMsg("test", msg)); - testCond((s == NATS_NOT_FOUND) && (strstr(nats_GetLastError(NULL), natsStatus_GetText(NATS_NOT_FOUND)) != NULL)); - nats_clearLastError(); - natsMsg_Destroy(msg); - msg = NULL; - - test("Msg has no header: "); - s = natsMsg_Create(&msg, "inbox", NULL, "1", 1); - IFOK(s, js_directGetMsgToJSMsg("test", msg)); - testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "should have headers") != NULL)); - nats_clearLastError(); - - test("Missing stream: "); - s = natsMsgHeader_Set(msg, "some", "header"); - IFOK(s, js_directGetMsgToJSMsg("test", msg)); - testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "missing stream") != NULL)); - nats_clearLastError(); - - test("Missing sequence: "); - s = natsMsgHeader_Set(msg, JSStream, "test"); - IFOK(s, js_directGetMsgToJSMsg("test", msg)); - testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "invalid sequence") != NULL)); - nats_clearLastError(); - - test("Invalid sequence: "); - s = natsMsgHeader_Set(msg, JSSequence, "abc"); - IFOK(s, js_directGetMsgToJSMsg("test", msg)); - testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "invalid sequence 'abc'") != NULL)); - nats_clearLastError(); - - test("Missing timestamp: "); - s = natsMsgHeader_Set(msg, JSSequence, "1"); - IFOK(s, js_directGetMsgToJSMsg("test", msg)); - testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "missing or invalid timestamp") != NULL)); - nats_clearLastError(); - - test("Invalid timestamp: "); - s = natsMsgHeader_Set(msg, JSTimeStamp, "aaaaaaaaa bbbbbbbbbbbb cccccccccc ddddddddddd eeeeeeeeee ffffff"); - IFOK(s, js_directGetMsgToJSMsg("test", msg)); - testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "missing or invalid timestamp 'aaaaaaaaa bbbbbbbbbbbb cccccccccc ddddddddddd eeeeeeeeee ffffff'") != NULL)); - nats_clearLastError(); - - test("Missing subject: "); - s = natsMsgHeader_Set(msg, JSTimeStamp, "2006-01-02T15:04:05Z"); - IFOK(s, js_directGetMsgToJSMsg("test", msg)); - testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "missing or invalid subject") != NULL)); - nats_clearLastError(); - - test("Invalid subject: "); - s = natsMsgHeader_Set(msg, JSSubject, ""); - IFOK(s, js_directGetMsgToJSMsg("test", msg)); - testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "missing or invalid subject ''") != NULL)); - nats_clearLastError(); - - test("Valid msg: "); - s = natsMsgHeader_Set(msg, JSSubject, "foo"); - IFOK(s, js_directGetMsgToJSMsg("test", msg)); - testCond((s == NATS_OK) - && (strcmp(natsMsg_GetSubject(msg), "foo") == 0) - && (natsMsg_GetSequence(msg) == 1) - && (natsMsg_GetTime(msg) == 1136214245000000000L) - && (natsMsgHeader_Get(msg, "some", &val) == NATS_OK) - && (strcmp(val, "header") == 0)); - - natsMsg_Destroy(msg); -} - -static natsStatus -_checkDirectGet(jsCtx *js, uint64_t seq, const char *nextBySubj, const char *lastBySubj, - const char *expectedSubj, uint64_t expectedSeq) -{ - natsStatus s = NATS_OK; - natsMsg *msg = NULL; - jsDirectGetMsgOptions o; - - jsDirectGetMsgOptions_Init(&o); - o.Sequence = seq; - o.NextBySubject = nextBySubj; - o.LastBySubject = lastBySubj; - - s = js_DirectGetMsg(&msg, js, "DGM", NULL, &o); - if ((s != NATS_OK) - || (msg == NULL) - || (strcmp(natsMsg_GetSubject(msg), expectedSubj) != 0) - || (natsMsg_GetSequence(msg) != expectedSeq) - || (natsMsg_GetTime(msg) == 0)) - { - s = NATS_ERR; - } - - natsMsg_Destroy(msg); - return s; -} - -static void -test_JetStreamDirectGetMsg(void) -{ - natsStatus s; - natsMsg *msg = NULL; - jsStreamConfig cfg; - jsErrCode jerr = 0; - const char *val = NULL; - jsDirectGetMsgOptions dgo; - - JS_SETUP(2, 9, 0); - - test("Create stream: "); - jsStreamConfig_Init(&cfg); - cfg.Name = "DGM"; - cfg.Subjects = (const char*[2]){"foo", "bar"}; - cfg.SubjectsLen = 2; - cfg.Storage = js_MemoryStorage; - cfg.AllowDirect = true; - s = js_AddStream(NULL, js, &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Populate: "); - s = js_Publish(NULL, js, "foo", "a", 1, NULL, NULL); - IFOK(s, js_Publish(NULL, js, "foo", "b", 1, NULL, NULL)); - IFOK(s, js_Publish(NULL, js, "foo", "c", 1, NULL, NULL)); - IFOK(s, js_Publish(NULL, js, "bar", "d", 1, NULL, NULL)); - IFOK(s, js_Publish(NULL, js, "foo", "e", 1, NULL, NULL)); - testCond(s == NATS_OK); - - test("DirecGetMsg bad args: "); - s = js_DirectGetMsg(NULL, js, "DGM", NULL, &dgo); - if (s == NATS_INVALID_ARG) - s = js_DirectGetMsg(&msg, NULL, "DGM", NULL, &dgo); - if (s == NATS_INVALID_ARG) - s = js_DirectGetMsg(&msg, js, "DGM", NULL, NULL); - testCond((s == NATS_INVALID_ARG) && (msg == NULL)); - nats_clearLastError(); - - test("GetMsg stream name required: "); - s = js_DirectGetMsg(&msg, js, NULL, NULL, &dgo); - if (s == NATS_INVALID_ARG) - s = js_DirectGetMsg(&msg, js, "", NULL, &dgo); - testCond((s == NATS_INVALID_ARG) && (msg == NULL) - && (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL)); - nats_clearLastError(); - - test("DirectGetMsg options init bad args: "); - s = jsDirectGetMsgOptions_Init(NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Seq==0 next_by_subj(bar): "); - testCond(_checkDirectGet(js, 0, "bar", NULL, "bar", 4) == NATS_OK); - - test("Seq==0 last_by_subj: "); - testCond(_checkDirectGet(js, 0, NULL, "foo", "foo", 5) == NATS_OK); - - test("Seq==0 next_by_subj(foo): "); - testCond(_checkDirectGet(js, 0, "foo", NULL, "foo", 1) == NATS_OK); - - test("Seq==4 next_by_subj: "); - testCond(_checkDirectGet(js, 4, "foo", NULL, "foo", 5) == NATS_OK); - - test("Seq==2 next_by_subj: "); - testCond(_checkDirectGet(js, 2, "foo", NULL, "foo", 2) == NATS_OK); - - test("Publish msg with header: "); - s = natsMsg_Create(&msg, "foo", NULL, "f", 1); - IFOK(s, natsMsgHeader_Add(msg, "MyHeader", "MyValue")); - IFOK(s, js_PublishMsg(NULL, js, msg, NULL, NULL)); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - msg = NULL; - - test("Check headers preserved: "); - jsDirectGetMsgOptions_Init(&dgo); - dgo.Sequence = 6; - s = js_DirectGetMsg(&msg, js, "DGM", NULL, &dgo); - testCond((s == NATS_OK) - && (natsMsgHeader_Get(msg, "MyHeader", &val) == NATS_OK) - && (val != NULL) - && (strcmp(val, "MyValue") == 0)); - - test("Change subject header: "); - s = natsMsgHeader_Set(msg, JSSubject, "xxx"); - testCond(s == NATS_OK); - - test("Msg subject not affected: "); - testCond(strcmp(natsMsg_GetSubject(msg), "foo") == 0); - - natsMsg_Destroy(msg); - - JS_TEARDOWN; -} - -static void -test_JetStreamNakWithDelay(void) -{ - natsStatus s; - natsSubscription *sub= NULL; - jsErrCode jerr= 0; - jsStreamConfig sc; - natsMsg *msg = NULL; - int64_t start=0; - int64_t dur = 0; - jsOptions o; - - JS_SETUP(2, 7, 2); - - test("Create stream: "); - jsStreamConfig_Init(&sc); - sc.Name = "TEST"; - sc.Subjects = (const char*[1]){"foo"}; - sc.SubjectsLen = 1; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Publish: "); - s = js_Publish(NULL, js, "foo", "ok", 2, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Create sub: "); - s = js_SubscribeSync(&sub, js, "foo", NULL, NULL, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Get message: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) && (msg != NULL)); - - test("Nak with 500ms: "); - s = natsMsg_NakWithDelay(msg, 500, NULL); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - msg = NULL; - - test("Should not be redelivered yet: "); - s = natsSubscription_NextMsg(&msg, sub, 250); - testCond((s == NATS_TIMEOUT) && (msg == NULL)); - nats_clearLastError(); - - test("Wait for redelivery: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) && (msg != NULL)); - - test("NakWithDelay with 0: "); - s = natsMsg_NakWithDelay(msg, 0, NULL); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - msg = NULL; - - test("Wait for redelivery: "); - s = natsSubscription_NextMsg(&msg, sub, 250); - testCond((s == NATS_OK) && (msg != NULL)); - - natsSub_retain(sub); - natsSubscription_Destroy(sub); - - // Stop the server... - _stopServer(pid); - pid = NATS_INVALID_PID; - - test("Check ack sync with options: "); - jsOptions_Init(&o); - o.Wait = 500; - start = nats_Now(); - s = natsMsg_Ack(msg, &o); - dur = nats_Now()-start; - testCond((s == NATS_TIMEOUT) && (dur > 250) && (dur < 750)); - natsMsg_Destroy(msg); - natsSub_release(sub); - JS_TEARDOWN; -} - -static void -test_JetStreamBackOffRedeliveries(void) -{ - natsStatus s; - natsSubscription *sub= NULL; - jsErrCode jerr= 0; - jsStreamConfig sc; - natsMsg *msg = NULL; - int64_t start =0; - int64_t dur = 0; - natsInbox *inbox = NULL; - jsConsumerInfo *ci = NULL; - jsSubOptions so; - int i; - - JS_SETUP(2, 7, 2); - - test("Create stream: "); - jsStreamConfig_Init(&sc); - sc.Name = "TEST"; - sc.Subjects = (const char*[1]){"foo"}; - sc.SubjectsLen = 1; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Create inbox: "); - s = natsInbox_Create(&inbox); - testCond(s == NATS_OK); - - test("Wrong MaxDeliver: "); - jsSubOptions_Init(&so); - so.Stream = "TEST"; - so.Config.Durable = "backoff"; - so.Config.AckPolicy = js_AckExplicit; - so.Config.DeliverPolicy = js_DeliverAll; - so.Config.DeliverSubject = inbox; - so.Config.MaxDeliver = 2; - so.Config.BackOff = (int64_t[]){NATS_MILLIS_TO_NANOS(50), NATS_MILLIS_TO_NANOS(250)}; - so.Config.BackOffLen = 2; - s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr); - testCond((s != NATS_OK) && (sub == NULL) && (jerr == JSConsumerMaxDeliverBackoffErr) - && (strstr(nats_GetLastError(NULL), "max deliver is required") != NULL)); - nats_clearLastError(); - - test("Create ok: "); - so.Config.MaxDeliver = 4; - s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr); - testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)); - - test("Send: "); - s = js_Publish(NULL, js, "foo", "ok", 2, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Consume msg: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond(s == NATS_OK); - natsMsg_Destroy(msg); - msg = NULL; - - // We should get a redelivery at around 50ms - test("Redelivered at 50ms: "); - start = nats_Now(); - s = natsSubscription_NextMsg(&msg, sub, 1000); - dur = (nats_Now()-start); - testCond((s == NATS_OK) && (dur > 25) && (dur < 100)); - natsMsg_Destroy(msg); - msg = NULL; - - // Now it should be every 250ms or so - for (i=0; i<2; i++) - { - test("Redelivered at 250ms: "); - start = nats_Now(); - s = natsSubscription_NextMsg(&msg, sub, 1000); - dur = (nats_Now()-start); - testCond((s == NATS_OK) && (dur > 200) && (dur < 300)); - natsMsg_Destroy(msg); - msg = NULL; - } - - // At this point, we should have go reach MaxDeliver - test("No more: "); - s = natsSubscription_NextMsg(&msg, sub, 300); - testCond(s == NATS_TIMEOUT); - - test("Check consumer info: "); - s = js_GetConsumerInfo(&ci, js, "TEST", "backoff", NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (ci != NULL) && (ci->Config != NULL) - && (ci->Config->BackOffLen == 2) - && (ci->Config->BackOff[0] == NATS_MILLIS_TO_NANOS(50)) - && (ci->Config->BackOff[1] == NATS_MILLIS_TO_NANOS(250))); - jsConsumerInfo_Destroy(ci); - natsInbox_Destroy(inbox); - natsSubscription_Destroy(sub); - JS_TEARDOWN; -} - -static void -_subjectsInfoReq(natsConnection *nc, natsMsg **msg, void *closure) -{ - natsMsg *newMsg = NULL; - int *count = (int*) closure; - const char *payload = NULL; - - if (strstr((*msg)->data, "stream_info_response") == NULL) - return; - - (*count)++; - if (*count == 1) - { - // Pretend that we have 5 subjects and a limit of 2 - payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_info_response\",\"total\":5,\"offset\":0,\"limit\":2,"\ - "\"config\":{\"name\":\"TEST\",\"subjects\":[\"foo.>\"]},"\ - "\"state\":{\"num_subjects\":5,\"subjects\":{\"foo.bar\":1,\"foo.baz\":2}}}"; - } - else if (*count == 2) - { - // Continue with the pagination - payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_info_response\",\"total\":5,\"offset\":2,\"limit\":2,"\ - "\"config\":{\"name\":\"TEST\",\"subjects\":[\"foo.>\"]},"\ - "\"state\":{\"num_subjects\":5,\"subjects\":{\"foo.bat\":3,\"foo.box\":4}}}"; - } - else if (*count == 3) - { - // Pretend that the number went down in between page requests and the - // last request got nothing in return. - payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_info_response\",\"total\":3,\"offset\":3,\"limit\":2,"\ - "\"config\":{\"name\":\"TEST\",\"subjects\":[\"foo.>\"]},"\ - "\"state\":{\"num_subjects\":3}}"; - } - else - { - // Keep the original message - return; - } - if (natsMsg_create(&newMsg, (*msg)->subject, (int) strlen((*msg)->subject), - NULL, 0, payload, (int) strlen(payload), 0) == NATS_OK) - { - natsMsg_Destroy(*msg); - *msg = newMsg; - } -} - -static void -test_JetStreamInfoWithSubjects(void) -{ - natsStatus s; - jsStreamInfo *si = NULL; - jsStreamConfig cfg; - jsErrCode jerr = 0; - jsOptions o; - int count = 0; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - - JS_SETUP(2, 9, 0); - - test("Create stream: "); - jsStreamConfig_Init(&cfg); - cfg.Name = "TEST"; - cfg.Subjects = (const char*[1]){"foo.>"}; - cfg.SubjectsLen = 1; - s = js_AddStream(NULL, js, &cfg, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Send to different subjects: "); - s = js_Publish(NULL, js, "foo.bar", "m1", 2, NULL, &jerr); - IFOK(s, js_Publish(NULL, js, "foo.baz", "m1", 2, NULL, &jerr)); - IFOK(s, js_Publish(NULL, js, "foo.baz", "m2", 2, NULL, &jerr)); - IFOK(s, js_Publish(NULL, js, "foo.baz.bat", "m1", 2, NULL, &jerr)); - IFOK(s, js_Publish(NULL, js, "foo.baz.bat", "m2", 2, NULL, &jerr)); - IFOK(s, js_Publish(NULL, js, "foo.baz.bat", "m3", 2, NULL, &jerr)); - testCond(s == NATS_OK); - - test("Check number subjects: "); - s = js_GetStreamInfo(&si, js, "TEST", NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (si != NULL) && (si->State.NumSubjects == 3)); - jsStreamInfo_Destroy(si); - si = NULL; - - test("Get subjects list (no match): "); - jsOptions_Init(&o); - o.Stream.Info.SubjectsFilter = "none"; - s = js_GetStreamInfo(&si, js, "TEST", &o, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (si != NULL) && (si->State.NumSubjects == 3) - && (si->State.Subjects == NULL)); - jsStreamInfo_Destroy(si); - si = NULL; - - test("Get subjects list (1 match): "); - jsOptions_Init(&o); - o.Stream.Info.SubjectsFilter = "foo.bar"; - s = js_GetStreamInfo(&si, js, "TEST", &o, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (si != NULL) && (si->State.NumSubjects == 3) - && (si->State.Subjects != NULL) - && (si->State.Subjects->Count == 1) - && (strcmp(si->State.Subjects->List[0].Subject, "foo.bar") == 0) - && (si->State.Subjects->List[0].Msgs == 1)); - jsStreamInfo_Destroy(si); - si = NULL; - - test("Get subjects list (all matches): "); - jsOptions_Init(&o); - o.Stream.Info.SubjectsFilter = "foo.>"; - s = js_GetStreamInfo(&si, js, "TEST", &o, &jerr); - if ((s == NATS_OK) && ((si == NULL) || (si->State.Subjects == NULL) || (si->State.Subjects->Count != 3))) - s = NATS_ERR; - else - { - int i; - bool got1, got2, got3; - - for (i=0; iState.Subjects->Count; i++) - { - jsStreamStateSubject *subj = &(si->State.Subjects->List[i]); - - if ((strcmp(subj->Subject, "foo.bar") == 0) && (subj->Msgs == 1)) - got1 = true; - else if ((strcmp(subj->Subject, "foo.baz") == 0) && (subj->Msgs == 2)) - got2 = true; - else if ((strcmp(subj->Subject, "foo.baz.bat") == 0) && (subj->Msgs == 3)) - got3 = true; - } - s = (got1 && got2 && got3 ? NATS_OK : NATS_ERR); - } - testCond((s == NATS_OK) && (jerr == 0) && (si->State.NumSubjects == 3)); - jsStreamInfo_Destroy(si); - si = NULL; - - test("Create sub for pagination check: "); - s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.STREAM.INFO.TEST"); - testCond(s == NATS_OK); - - natsConn_setFilterWithClosure(nc, _subjectsInfoReq, (void*) &count); - - test("Get all subjects with pagination: "); - jsOptions_Init(&o); - o.Stream.Info.SubjectsFilter = ">"; - s = js_GetStreamInfo(&si, js, "TEST", &o, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (si != NULL) && (si->State.NumSubjects == 3) - && (si->State.Subjects != NULL) - && (si->State.Subjects->List != NULL) - && (si->State.Subjects->Count == 4)); - jsStreamInfo_Destroy(si); - si = NULL; - - natsConn_setFilter(nc, NULL); - - test("Check 1st request: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) - && (strstr(natsMsg_GetData(msg), "offset") == NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Check 2nd request: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) - && (strstr(natsMsg_GetData(msg), "offset\":2") != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - test("Check 3rd request: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond((s == NATS_OK) - && (strstr(natsMsg_GetData(msg), "offset\":4") != NULL)); - natsMsg_Destroy(msg); - msg = NULL; - - natsSubscription_Destroy(sub); - - JS_TEARDOWN; -} - -static natsStatus -_checkJSClusterReady(const char *url) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - jsCtx *js = NULL; - jsErrCode jerr= 0; - int i; - jsOptions jo; - - jsOptions_Init(&jo); - jo.Wait = 1000; - - s = natsConnection_ConnectTo(&nc, url); - IFOK(s, natsConnection_JetStream(&js, nc, &jo)); - for (i=0; (s == NATS_OK) && (i<10); i++) - { - jsStreamInfo *si = NULL; - - s = js_GetStreamInfo(&si, js, "CHECK_CLUSTER", &jo, &jerr); - if (jerr == JSStreamNotFoundErr) - { - nats_clearLastError(); - s = NATS_OK; - break; - } - if ((s != NATS_OK) && (i < 9)) - { - s = NATS_OK; - nats_Sleep(500); - } - } - jsCtx_Destroy(js); - natsConnection_Destroy(nc); - return s; -} - -static void -test_JetStreamInfoAlternates(void) -{ - char datastore1[256] = {'\0'}; - char datastore2[256] = {'\0'}; - char datastore3[256] = {'\0'}; - char cmdLine[1024] = {'\0'}; - natsPid pid1 = NATS_INVALID_PID; - natsPid pid2 = NATS_INVALID_PID; - natsPid pid3 = NATS_INVALID_PID; - natsConnection *nc = NULL; - jsCtx *js = NULL; - jsStreamInfo *si = NULL; - jsStreamConfig sc; - jsStreamSource ss; - natsStatus s; - - ENSURE_JS_VERSION(2, 9, 0); - - test("Start cluster: "); - _makeUniqueDir(datastore1, sizeof(datastore1), "datastore_"); - snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -cluster_name abc -server_name A -cluster nats://127.0.0.1:6222 -routes nats://127.0.0.1:6222,nats://127.0.0.1:6223,nats://127.0.0.1:6224 -p 4222", datastore1); - pid1 = _startServer("nats://127.0.0.1:4222", cmdLine, true); - CHECK_SERVER_STARTED(pid1); - - _makeUniqueDir(datastore2, sizeof(datastore2), "datastore_"); - snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -cluster_name abc -server_name B -cluster nats://127.0.0.1:6223 -routes nats://127.0.0.1:6222,nats://127.0.0.1:6223,nats://127.0.0.1:6224 -p 4223", datastore2); - pid2 = _startServer("nats://127.0.0.1:4223", cmdLine, true); - CHECK_SERVER_STARTED(pid1); - - _makeUniqueDir(datastore3, sizeof(datastore3), "datastore_"); - snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -cluster_name abc -server_name C -cluster nats://127.0.0.1:6224 -routes nats://127.0.0.1:6222,nats://127.0.0.1:6223,nats://127.0.0.1:6224 -p 4224", datastore3); - pid3 = _startServer("nats://127.0.0.1:4224", cmdLine, true); - CHECK_SERVER_STARTED(pid1); - testCond(true); - - test("Check cluster: "); - s = _checkJSClusterReady("nats://127.0.0.1:4224"); - testCond(s == NATS_OK); - - test("Connect: "); - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - testCond(s == NATS_OK); - - test("Get context: "); - s = natsConnection_JetStream(&js, nc, NULL); - testCond(s == NATS_OK); - - test("Create stream: "); - jsStreamConfig_Init(&sc); - sc.Name = "TEST"; - sc.Subjects = (const char*[1]){"foo"}; - sc.SubjectsLen = 1; - s = js_AddStream(NULL, js, &sc, NULL, NULL); - testCond(s == NATS_OK); - - test("Create mirror: "); - jsStreamConfig_Init(&sc); - sc.Name = "MIRROR"; - jsStreamSource_Init(&ss); - ss.Name = "TEST"; - sc.Mirror = &ss; - s = js_AddStream(NULL, js, &sc, NULL, NULL); - testCond(s == NATS_OK); - - test("Check for alternate: "); - s = js_GetStreamInfo(&si, js, "TEST", NULL, NULL); - testCond((s == NATS_OK) && (si != NULL) && (si->AlternatesLen == 2)); - - test("Check alternate content: "); - if ((strcmp(si->Alternates[0]->Cluster, "abc") != 0) - || (strcmp(si->Alternates[1]->Cluster, "abc") != 0)) - { - s = NATS_ERR; - } - else if (((strcmp(si->Alternates[0]->Name, "TEST") == 0) && (strcmp(si->Alternates[1]->Name, "MIRROR") != 0)) - || ((strcmp(si->Alternates[0]->Name, "MIRROR") == 0) && (strcmp(si->Alternates[1]->Name, "TEST") != 0))) - { - s = NATS_ERR; - } - testCond(s == NATS_OK); - jsStreamInfo_Destroy(si); - - jsCtx_Destroy(js); - natsConnection_Destroy(nc); - - _stopServer(pid3); - _stopServer(pid2); - _stopServer(pid1); - rmtree(datastore1); - rmtree(datastore2); - rmtree(datastore3); -} - -static void -test_KeyValueManager(void) -{ - natsStatus s; - kvStore *kv = NULL; - kvConfig kvc; - jsStreamConfig sc; - jsErrCode jerr = 0; - jsStreamInfo *si = NULL; - - JS_SETUP(2, 7, 2); - - test("kvConfig Init (bad args): "); - s = kvConfig_Init(NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Create KV - bad args: "); - kvConfig_Init(&kvc); - kvc.Bucket = "TEST"; - s = js_CreateKeyValue(NULL, js, &kvc); - if (s == NATS_INVALID_ARG) - s = js_CreateKeyValue(&kv, NULL, &kvc); - if (s == NATS_INVALID_ARG) - s = js_CreateKeyValue(&kv, js, NULL); - testCond((s == NATS_INVALID_ARG) && (kv == NULL)); - nats_clearLastError(); - - test("Create KV - bad bucket name: "); - kvConfig_Init(&kvc); - kvc.Bucket = "This.is.not.a.valid.name!"; - s = js_CreateKeyValue(&kv, js, &kvc); - testCond((s == NATS_INVALID_ARG) && (kv == NULL) - && (strstr(nats_GetLastError(NULL), kvErrInvalidBucketName) != NULL)); - nats_clearLastError(); - - test("Create KV - history too big: "); - kvConfig_Init(&kvc); - kvc.Bucket = "TEST"; - kvc.History = kvMaxHistory + 10; - s = js_CreateKeyValue(&kv, js, &kvc); - testCond((s == NATS_INVALID_ARG) && (kv == NULL) - && (strstr(nats_GetLastError(NULL), kvErrHistoryTooLarge) != NULL)); - nats_clearLastError(); - - test("Create KV: "); - kvConfig_Init(&kvc); - kvc.Bucket = "TEST"; - kvc.History = 3; - s = js_CreateKeyValue(&kv, js, &kvc); - testCond((s == NATS_OK) && (kv != NULL)); - - test("Check discard policy: "); - s = js_GetStreamInfo(&si, js, "KV_TEST", NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0) && (si != NULL) && (si->Config != NULL) - && (si->Config->Discard == js_DiscardNew)); - jsStreamInfo_Destroy(si); - si = NULL; - - test("Destroy kv store: ") - kvStore_Destroy(kv); - kv = NULL; - // Check that this is ok - kvStore_Destroy(NULL); - testCond(true); - - test("Bind (bad args): "); - s = js_KeyValue(NULL, js, "TEST"); - if (s == NATS_INVALID_ARG) - s = js_KeyValue(&kv, NULL, "TEST"); - if (s == NATS_INVALID_ARG) - s = js_KeyValue(&kv, js, NULL); - if (s == NATS_INVALID_ARG) - s = js_KeyValue(&kv, NULL, ""); - if (s == NATS_INVALID_ARG) - s = js_KeyValue(&kv, NULL, "bad.bucket.name"); - testCond((s == NATS_INVALID_ARG) && (kv == NULL)); - nats_clearLastError(); - - test("Bind (not found): "); - s = js_KeyValue(&kv, js, "NOT_FOUND"); - testCond((s == NATS_NOT_FOUND) && (kv == NULL) - && (nats_GetLastError(NULL) == NULL)); - - test("Bind to existing: "); - s = js_KeyValue(&kv, js, "TEST"); - testCond((s == NATS_OK) && (kv != NULL)); - - test("Destroy kv store: ") - kvStore_Destroy(kv); - kv = NULL; - testCond(true); - - test("Create non-kv stream: "); - jsStreamConfig_Init(&sc); - // Stream name has to start with "KV_" since this is how we - // form the stream name: KV_ + bucket name. - sc.Name = "KV_NON_KV_STREAM"; - sc.Subjects = (const char*[1]){"foo"}; - sc.SubjectsLen = 1; - s = js_AddStream(NULL, js, &sc, NULL, &jerr); - testCond((s == NATS_OK) && (jerr == 0)); - - test("Bind to non-kv stream: "); - s = js_KeyValue(&kv, js, "NON_KV_STREAM"); - testCond((s == NATS_INVALID_ARG) && (kv == NULL) - && (strstr(nats_GetLastError(NULL), kvErrBadBucket) != NULL)); - nats_clearLastError(); - - test("Delete kv store (bad args): "); - s = js_DeleteKeyValue(NULL, "TEST"); - if (s == NATS_INVALID_ARG) - s = js_DeleteKeyValue(js, NULL); - if (s == NATS_INVALID_ARG) - s = js_DeleteKeyValue(js, ""); - if (s == NATS_INVALID_ARG) - s = js_DeleteKeyValue(js, "bad.bucket.name"); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Delete kv store: "); - s = js_DeleteKeyValue(js, "TEST"); - testCond(s == NATS_OK); - - test("Check it is gone (bind should fail): "); - s = js_KeyValue(&kv, js, "TEST"); - testCond((s == NATS_NOT_FOUND) && (kv == NULL)); - - JS_TEARDOWN; -} - -static void -test_KeyValueBasics(void) -{ - natsStatus s; - kvStore *kv = NULL; - kvEntry *e = NULL; - kvStatus *sts= NULL; - uint64_t rev = 0; - kvConfig kvc; - int iterMax = 1; - int i; - char bucketName[10]; - - JS_SETUP(2, 6, 2); - - if (serverVersionAtLeast(2, 9, 0)) { - iterMax = 2; - } - - for (i=0; imu); - kv->useDirect = false; - natsMutex_Unlock(kv->mu); - } - - test("Check bucket: "); - s = (strcmp(kvStore_Bucket(kv), bucketName) == 0 ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - - test("Check bucket (returns NULL): "); - s = (kvStore_Bucket(NULL) == NULL ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - - test("Check bytes is 0: "); - s = kvStore_Status(&sts, kv); - IFOK(s, (kvStatus_Bytes(sts) == 0 ? NATS_OK : NATS_ERR)); - testCond(s == NATS_OK); - kvStatus_Destroy(sts); - sts = NULL; - - test("Simple put (bad args): "); - rev = 1234; - s = kvStore_Put(&rev, NULL, "key", (const void*) "value", 5); - testCond((s == NATS_INVALID_ARG) && (rev == 0)); - nats_clearLastError(); - - test("Simple put (bad key): "); - rev = 1234; - s = kvStore_Put(&rev, kv, NULL, (const void*) "value", 5); - if (s == NATS_INVALID_ARG) - s = kvStore_Put(&rev, kv, "", (const void*) "value", 5); - if (s == NATS_INVALID_ARG) - s = kvStore_Put(&rev, kv, ".bad.key", (const void*) "value", 5); - if (s == NATS_INVALID_ARG) - s = kvStore_Put(&rev, kv, "bad.key.", (const void*) "value", 5); - if (s == NATS_INVALID_ARG) - s = kvStore_Put(&rev, kv, "this is a bad key", (const void*) "value", 5); - testCond((s == NATS_INVALID_ARG) && (rev == 0) - && (strstr(nats_GetLastError(NULL), kvErrInvalidKey) != NULL)); - nats_clearLastError(); - - test("Simple put: "); - s = kvStore_Put(&rev, kv, "key", (const void*) "value", 5); - testCond((s == NATS_OK) && (rev == 1)); - - test("Get (bad args): "); - s = kvStore_Get(NULL, kv, "key"); - if (s == NATS_INVALID_ARG) - s = kvStore_Get(&e, NULL, "key"); - testCond((s == NATS_INVALID_ARG) && (e == NULL)); - nats_clearLastError(); - - test("Get (bad key): "); - s = kvStore_Get(&e, kv, NULL); - if (s == NATS_INVALID_ARG) - s = kvStore_Get(&e, kv, ""); - if (s == NATS_INVALID_ARG) - s = kvStore_Get(&e, kv, ".bad.key"); - if (s == NATS_INVALID_ARG) - s = kvStore_Get(&e, kv, "bad.key."); - if (s == NATS_INVALID_ARG) - s = kvStore_Get(&e, kv, "this is a bad key"); - testCond((s == NATS_INVALID_ARG) && (e == NULL) - && (strstr(nats_GetLastError(NULL), kvErrInvalidKey) != NULL)); - nats_clearLastError(); - - test("Simple get: "); - s = kvStore_Get(&e, kv, "key"); - testCond((s == NATS_OK) && (e != NULL) - && (kvEntry_ValueLen(e) == 5) - && (memcmp(kvEntry_Value(e), "value", 5) == 0) - && (kvEntry_Revision(e) == 1)); - - test("Destroy entry: "); - kvEntry_Destroy(e); - e = NULL; - // Check that this is ok - kvEntry_Destroy(NULL); - testCond(true); - - test("Get not found: "); - s = kvStore_Get(&e, kv, "not.found"); - testCond((s == NATS_NOT_FOUND) && (e == NULL) - && (nats_GetLastError(NULL) == NULL)); - - test("Simple put string: "); - s = kvStore_PutString(&rev, kv, "key", "value2"); - testCond((s == NATS_OK) && (rev == 2)); - - test("Simple get string: "); - s = kvStore_Get(&e, kv, "key"); - testCond((s == NATS_OK) && (e != NULL) - && (strcmp(kvEntry_ValueString(e), "value2") == 0) - && (kvEntry_Revision(e) == 2)); - - test("Destroy entry: "); - kvEntry_Destroy(e); - e = NULL; - testCond(true); - - test("Get revision (bad args): "); - s = kvStore_GetRevision(&e, kv, "key", 0); - testCond((s == NATS_INVALID_ARG) && (e == NULL) - && (strstr(nats_GetLastError(NULL), kvErrInvalidRevision) != NULL)); - nats_clearLastError(); - - test("Get revision: "); - s = kvStore_GetRevision(&e, kv, "key", 1); - testCond((s == NATS_OK) && (e != NULL) - && (strcmp(kvEntry_ValueString(e), "value") == 0) - && (kvEntry_Revision(e) == 1)); - - test("Destroy entry: "); - kvEntry_Destroy(e); - e = NULL; - testCond(true); - - test("Delete key (bad args): "); - s = kvStore_Delete(NULL, "key"); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Delete key (bad key): "); - s = kvStore_Delete(kv, NULL); - if (s == NATS_INVALID_ARG) - s = kvStore_Delete(kv, ""); - if (s == NATS_INVALID_ARG) - s = kvStore_Delete(kv, ".bad.key"); - if (s == NATS_INVALID_ARG) - s = kvStore_Delete(kv, "bad.key."); - if (s == NATS_INVALID_ARG) - s = kvStore_Delete(kv, "this is a bad key"); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), kvErrInvalidKey) != NULL)); - nats_clearLastError(); - - test("Delete key: "); - s = kvStore_Delete(kv, "key"); - testCond(s == NATS_OK); - - test("Check key gone: "); - s = kvStore_Get(&e, kv, "key"); - testCond((s == NATS_NOT_FOUND) && (e == NULL) - && (nats_GetLastError(NULL) == NULL)); - - test("Create (bad args): "); - s = kvStore_Create(&rev, NULL, "key", (const void*) "value3", 6); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Create (bad key): "); - s = kvStore_Create(&rev, kv, NULL, (const void*) "value3", 6); - if (s == NATS_INVALID_ARG) - s = kvStore_Create(&rev, kv, "", (const void*) "value3", 6); - if (s == NATS_INVALID_ARG) - s = kvStore_Create(&rev, kv, ".bad.key", (const void*) "value3", 6); - if (s == NATS_INVALID_ARG) - s = kvStore_Create(&rev, kv, "bad.key.", (const void*) "value3", 6); - if (s == NATS_INVALID_ARG) - s = kvStore_Create(&rev, kv, "this..is a bad key", (const void*) "value3", 6); - testCond((s == NATS_INVALID_ARG) - && (strstr(nats_GetLastError(NULL), kvErrInvalidKey) != NULL)); - nats_clearLastError(); - - test("Create: "); - s = kvStore_Create(&rev, kv, "key", (const void*) "value3", 6); - testCond((s == NATS_OK) && (rev == 4)); - - test("Create fail, since already exists: "); - s = kvStore_Create(&rev, kv, "key", (const void*) "value4", 6); - testCond((s == NATS_ERR) && (rev == 0)); - nats_clearLastError(); - - test("Update (bad args): "); - s = kvStore_Update(&rev, NULL, "key", (const void*) "value4", 6, 4); - testCond((s == NATS_INVALID_ARG) && (rev == 0)); - nats_clearLastError(); - - test("Update (bad key): "); - s = kvStore_Update(&rev, kv, NULL, (const void*) "value4", 6, 4); - if (s == NATS_INVALID_ARG) - s = kvStore_Update(&rev, kv, "", (const void*) "value4", 6, 4); - if (s == NATS_INVALID_ARG) - s = kvStore_Update(&rev, kv, ".bad.key", (const void*) "value4", 6, 4); - if (s == NATS_INVALID_ARG) - s = kvStore_Update(&rev, kv, "bad.key.", (const void*) "value4", 6, 4); - if (s == NATS_INVALID_ARG) - s = kvStore_Update(&rev, kv, "bad&key", (const void*) "value4", 6, 4); - testCond((s == NATS_INVALID_ARG) && (rev == 0) - && (strstr(nats_GetLastError(NULL), kvErrInvalidKey) != NULL)); - nats_clearLastError(); - - test("Update: "); - s = kvStore_Update(&rev, kv, "key", (const void*) "value4", 6, 4); - testCond((s == NATS_OK) && (rev == 5)); - - test("Update fail because wrong rev: "); - s = kvStore_Update(NULL, kv, "key", (const void*) "value5", 6, 4); - testCond(s == NATS_ERR); - nats_clearLastError(); - - test("Update ok: "); - s = kvStore_Update(&rev, kv, "key", (const void*) "value5", 6, rev); - testCond((s == NATS_OK) && (rev == 6)); - nats_clearLastError(); - - test("Create (string): "); - s = kvStore_CreateString(&rev, kv, "key2", "value1"); - testCond((s == NATS_OK) && (rev == 7)); - - test("Update ok (string): "); - s = kvStore_UpdateString(&rev, kv, "key2", "value2", rev); - testCond((s == NATS_OK) && (rev == 8)); - - test("Status (bad args): "); - s = kvStore_Status(NULL, kv); - if (s == NATS_INVALID_ARG) - s = kvStore_Status(&sts, NULL); - testCond((s == NATS_INVALID_ARG) && (sts == NULL)); - nats_clearLastError(); - - test("Status: "); - s = kvStore_Status(&sts, kv); - testCond((s == NATS_OK) && (sts != NULL)); - - test("Check history: "); - s = (kvStatus_History(sts) == 5 ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - - test("Check bucket: "); - s = (strcmp(kvStatus_Bucket(sts), bucketName) == 0 ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - - test("Check TTL: "); - s = (kvStatus_TTL(sts) == NATS_SECONDS_TO_NANOS(3600) ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - - test("Check values: "); - s = (kvStatus_Values(sts) == 7 ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - - test("Check replicas: "); - s = (kvStatus_Replicas(sts) == 1 ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - - test("Check bytes: "); - { - jsStreamInfo *si = NULL; - - if (i == 0) - s = js_GetStreamInfo(&si, js, "KV_TEST0", NULL, NULL); - else - s = js_GetStreamInfo(&si, js, "KV_TEST1", NULL, NULL); - IFOK(s, (kvStatus_Bytes(sts) == si->State.Bytes ? NATS_OK : NATS_ERR)); - - jsStreamInfo_Destroy(si); - } - testCond(s == NATS_OK); - - test("Check status with NULL: "); - if ((kvStatus_History(NULL) != 0) || (kvStatus_Bucket(NULL) != NULL) - || (kvStatus_TTL(NULL) != 0) || (kvStatus_Values(NULL) != 0) - || (kvStatus_Bytes(NULL) != 0)) - { - s = NATS_ERR; - } - testCond(s == NATS_OK); - - test("Destroy status: "); - kvStatus_Destroy(sts); - sts = NULL; - // Check that this is ok - kvStatus_Destroy(NULL); - testCond(true); - - test("Put for revision check: "); - s = kvStore_PutString(&rev, kv, "test.rev.one", "val1"); - IFOK(s, kvStore_PutString(NULL, kv, "test.rev.two", "val2")); - testCond(s == NATS_OK); - - test("Get revision (bad args): "); - s = kvStore_GetRevision(&e, kv, "test.rev.one", 0); - testCond((s == NATS_INVALID_ARG) && (e == NULL) - && (strstr(nats_GetLastError(NULL), kvErrInvalidRevision) != NULL)); - nats_clearLastError(); - - test("Get revision: "); - s = kvStore_GetRevision(&e, kv, "test.rev.one", rev); - testCond((s == NATS_OK) && (e != NULL) - && (strcmp(kvEntry_ValueString(e), "val1") == 0) - && (kvEntry_Revision(e) == rev)); - kvEntry_Destroy(e); - e = NULL; - - test("Get wrong revision for the key: "); - s = kvStore_GetRevision(&e, kv, "test.rev.two", rev); - testCond((s == NATS_NOT_FOUND) && (e == NULL)); - - test("Destroy kv store: "); - kvStore_Destroy(kv); - testCond(true); - kv = NULL; - } - - JS_TEARDOWN; -} - -static bool -_expectInitDone(kvWatcher *w) -{ - natsStatus s; - kvEntry *e = NULL; - - test("Check init done: "); - s = kvWatcher_Next(&e, w, 1000); - return ((s == NATS_OK) && (e == NULL)); -} - -static bool -_expectUpdate(kvWatcher *w, const char *key, const char *val, uint64_t rev) -{ - natsStatus s; - kvEntry *e = NULL; - - test("Check update: "); - s = kvWatcher_Next(&e, w, 1000); - if ((s != NATS_OK) || (e == NULL)) - return false; - - if ((strcmp(kvEntry_Bucket(e), "WATCH") != 0) - || (strcmp(kvEntry_Key(e), key) != 0) - || (strcmp(kvEntry_ValueString(e), val) != 0) - || (kvEntry_Revision(e) != rev) - || (kvEntry_Created(e) == 0)) - { - return false; - } - kvEntry_Destroy(e); - return true; -} - -static bool -_expectDelete(kvWatcher *w, const char *key, uint64_t rev) -{ - natsStatus s; - kvEntry *e = NULL; - - test("Check update: "); - s = kvWatcher_Next(&e, w, 1000); - if ((s != NATS_OK) || (e == NULL)) - return false; - - if ((kvEntry_Operation(e) != kvOp_Delete) - || (kvEntry_Revision(e) != rev)) - { - return false; - } - kvEntry_Destroy(e); - return true; -} - -static void -_stopWatcher(void *closure) -{ - kvWatcher *w = (kvWatcher*) closure; - - nats_Sleep(100); - kvWatcher_Stop(w); -} - -static void -test_KeyValueWatch(void) -{ - natsStatus s; - kvStore *kv = NULL; - kvWatcher *w = NULL; - kvEntry *e = NULL; - natsThread *t = NULL; - int plc = 0; - int plb = 0; - kvConfig kvc; - int64_t start; - - JS_SETUP(2, 6, 2); - - test("Create KV: "); - kvConfig_Init(&kvc); - kvc.Bucket = "WATCH"; - s = js_CreateKeyValue(&kv, js, &kvc); - testCond(s == NATS_OK); - - test("Create watcher (bad args): "); - s = kvStore_Watch(NULL, kv, "foo", NULL); - if (s == NATS_INVALID_ARG) - s = kvStore_Watch(&w, NULL, "foo", NULL); - if (s == NATS_INVALID_ARG) - s = kvStore_Watch(&w, kv, NULL, NULL); - if (s == NATS_INVALID_ARG) - s = kvStore_Watch(&w, kv, "", NULL); - testCond((s == NATS_INVALID_ARG) && (w == NULL)); - nats_clearLastError(); - - test("Create watcher: "); - s = kvStore_WatchAll(&w, kv, NULL); - testCond((s == NATS_OK) && (w != NULL)); - - testCond(_expectInitDone(w)); - - test("Create: "); - s = kvStore_CreateString(NULL, kv, "name", "derek"); - testCond(s == NATS_OK); - testCond(_expectUpdate(w, "name", "derek", 1)); - - test("Put: "); - s = kvStore_PutString(NULL, kv, "name", "rip"); - testCond(s == NATS_OK); - testCond(_expectUpdate(w, "name", "rip", 2)); - - test("Put: "); - s = kvStore_PutString(NULL, kv, "name", "ik"); - testCond(s == NATS_OK); - testCond(_expectUpdate(w, "name", "ik", 3)); - - test("Put: "); - s = kvStore_PutString(NULL, kv, "age", "22"); - testCond(s == NATS_OK); - testCond(_expectUpdate(w, "age", "22", 4)); - - test("Put: "); - s = kvStore_PutString(NULL, kv, "age", "33"); - testCond(s == NATS_OK); - testCond(_expectUpdate(w, "age", "33", 5)); - - test("Delete: "); - s = kvStore_Delete(kv, "age"); - testCond(s == NATS_OK); - testCond(_expectDelete(w, "age", 6)); - - test("Next (bad args): "); - s = kvWatcher_Next(NULL, w, 1000); - if (s == NATS_INVALID_ARG) - s = kvWatcher_Next(&e, NULL, 1000); - if (s == NATS_INVALID_ARG) - s = kvWatcher_Next(&e, w, 0); - if (s == NATS_INVALID_ARG) - s = kvWatcher_Next(&e, w, -1000); - testCond((s == NATS_INVALID_ARG) && (e == NULL)); - nats_clearLastError(); - - test("Next (timeout): "); - s = kvWatcher_Next(&e, w, 1); - testCond((s == NATS_TIMEOUT) && (e == NULL)); - nats_clearLastError(); - - test("Stop (bad args): "); - s = kvWatcher_Stop(NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Stop: "); - s = kvWatcher_Stop(w); - testCond(s == NATS_OK); - - test("Stop again: "); - s = kvWatcher_Stop(w); - testCond(s == NATS_OK); - - test("Next fails: "); - s = kvWatcher_Next(&e, w, 1000); - testCond((s == NATS_ILLEGAL_STATE) && (e == NULL)) - nats_clearLastError(); - - test("Destroy watcher: "); - kvWatcher_Destroy(w); - w = NULL; - // Check that this is ok - kvWatcher_Destroy(NULL); - testCond(true); - - // Now try wildcard matching and make sure we only get last value when starting. - test("Put values in different keys: "); - s = kvStore_PutString(NULL, kv, "t.name", "derek"); - IFOK(s, kvStore_PutString(NULL, kv, "t.name", "ik")); - IFOK(s, kvStore_PutString(NULL, kv, "t.age", "22")); - IFOK(s, kvStore_PutString(NULL, kv, "t.age", "49")); - testCond(s == NATS_OK); - - test("Create watcher: "); - s = kvStore_Watch(&w, kv, "t.*", NULL); - testCond(s == NATS_OK); - - test("Check pending limits: "); - natsMutex_Lock(w->mu); - s = natsSubscription_GetPendingLimits(w->sub, &plc, &plb); - natsMutex_Unlock(w->mu); - testCond((s == NATS_OK) && (plc == -1) && (plb == -1)); - - testCond(_expectUpdate(w, "t.name", "ik", 8)); - testCond(_expectUpdate(w, "t.age", "49", 10)); - testCond(_expectInitDone(w)); - - test("Block: "); - start = nats_Now(); - s = natsThread_Create(&t, _stopWatcher, (void*) w); - IFOK(s, kvWatcher_Next(&e, w, 10000)); - testCond((s == NATS_ILLEGAL_STATE) && (e == NULL) - && ((nats_Now() - start) <= 1000)); - nats_clearLastError(); - - natsThread_Join(t); - natsThread_Destroy(t); - kvWatcher_Destroy(w); - kvStore_Destroy(kv); - - JS_TEARDOWN; -} - -static void -test_KeyValueWatchMulti(void) -{ - natsStatus s; - kvStore *kv = NULL; - kvWatcher *w = NULL; - kvConfig kvc; - - JS_SETUP(2, 10, 14); - - test("Create KV: "); - kvConfig_Init(&kvc); - kvc.Bucket = "WATCH"; - s = js_CreateKeyValue(&kv, js, &kvc); - testCond(s == NATS_OK); - - // Now try wildcard matching and make sure we only get last value when starting. - test("Put values in different keys: "); - s = kvStore_PutString(NULL, kv, "a.name", "A"); - IFOK(s, kvStore_PutString(NULL, kv, "b.name", "B")); - IFOK(s, kvStore_PutString(NULL, kv, "a.name", "AA")); - IFOK(s, kvStore_PutString(NULL, kv, "b.name", "BB")); - IFOK(s, kvStore_PutString(NULL, kv, "a.age", "22")); - IFOK(s, kvStore_PutString(NULL, kv, "a.age", "99")); - testCond(s == NATS_OK); - - test("Create watcher: "); - const char **subjects = (const char *[]){"a.*", "b.*", "c.*"}; - s = kvStore_WatchMulti(&w, kv, subjects, 3, NULL); - testCond(s == NATS_OK); - - testCond(_expectUpdate(w, "a.name", "AA", 3)); - testCond(_expectUpdate(w, "b.name", "BB", 4)); - testCond(_expectUpdate(w, "a.age", "99", 6)); - testCond(_expectInitDone(w)); - - IFOK(s, kvStore_PutString(NULL, kv, "c.occupation", "gardener")); - IFOK(s, kvStore_PutString(NULL, kv, "XXX.occupation", "no, plumber")); - IFOK(s, kvStore_PutString(NULL, kv, "c.occupation", "a digital plumber for a digital garden")); - testCond(_expectUpdate(w, "c.occupation", "gardener", 7)); - testCond(_expectUpdate(w, "c.occupation", "a digital plumber for a digital garden", 9)); - - kvWatcher_Destroy(w); - kvStore_Destroy(kv); - - JS_TEARDOWN; -} - -static void -test_KeyValueHistory(void) -{ - natsStatus s; - kvStore *kv = NULL; - kvEntry *e = NULL; - kvEntryList l; - kvWatchOptions o; - kvConfig kvc; - int i; - - JS_SETUP(2, 6, 2); - - test("Create KV: "); - kvConfig_Init(&kvc); - kvc.Bucket = "WATCH"; - kvc.History = 10; - s = js_CreateKeyValue(&kv, js, &kvc); - testCond(s == NATS_OK); - - test("Populate: "); - for (i=0; (s == NATS_OK) && (i<50); i++) - { - char tmp[16]; - - snprintf(tmp, sizeof(tmp), "%d", i+22); - s = kvStore_PutString(NULL, kv, "age", tmp); - } - testCond(s == NATS_OK); - - test("Get history (bad args): "); - s = kvStore_History(NULL, kv, "age", NULL); - if (s == NATS_INVALID_ARG) - s = kvStore_History(&l, NULL, "age", NULL); - if (s == NATS_INVALID_ARG) - s = kvStore_History(&l, kv, NULL, NULL); - if (s == NATS_INVALID_ARG) - s = kvStore_History(&l, kv, "", NULL); - testCond((s == NATS_INVALID_ARG) && (l.Entries == NULL) && (l.Count == 0)); - nats_clearLastError(); - - test("Get history (timeout): "); - kvWatchOptions_Init(&o); - o.Timeout = 1; - s = kvStore_History(&l, kv, "age", &o); - testCond(((s == NATS_OK) && (l.Entries != NULL) && (l.Count == 10)) - || ((s == NATS_TIMEOUT) && (l.Entries == NULL) && (l.Count == 0))); - nats_clearLastError(); - kvEntryList_Destroy(&l); - - test("Get History: "); - s = kvStore_History(&l, kv, "age", NULL); - testCond((s == NATS_OK) && (l.Entries != NULL) && (l.Count == 10)); - - test("Check values: "); - for (i=0; (s == NATS_OK) && (i < 10); i++) - { - e = l.Entries[i]; - if (e == NULL) - s = NATS_ERR; - if (strcmp(kvEntry_Key(e), "age") != 0) - s = NATS_ERR; - else if (kvEntry_Revision(e) != (uint64_t)(i+41)) - s = NATS_ERR; - else - { - int val = (int) nats_ParseInt64(kvEntry_Value(e), kvEntry_ValueLen(e)); - if (val != i+62) - s = NATS_ERR; - } - } - testCond(s == NATS_OK); - - test("Destroy list: ") - kvEntryList_Destroy(&l); - testCond((l.Entries == NULL) && (l.Count == 0)); - - // Check that this is ok - kvEntryList_Destroy(NULL); - - kvStore_Destroy(kv); - - JS_TEARDOWN; -} - -static void -test_KeyValueKeys(void) -{ - natsStatus s; - kvStore *kv = NULL; - kvKeysList l; - bool nameOK = false; - bool ageOK = false; - bool countryOK = false; - char *k = NULL; - kvConfig kvc; - kvWatchOptions o; - int i; - - JS_SETUP(2, 6, 2); - - test("Create KV: "); - kvConfig_Init(&kvc); - kvc.Bucket = "KVS"; - kvc.History = 2; - s = js_CreateKeyValue(&kv, js, &kvc); - testCond(s == NATS_OK); - - test("Populate: "); - s = kvStore_PutString(NULL, kv, "name", "derek"); - IFOK(s, kvStore_PutString(NULL, kv, "age", "22")); - IFOK(s, kvStore_PutString(NULL, kv, "country", "US")); - IFOK(s, kvStore_PutString(NULL, kv, "name", "ivan")); - IFOK(s, kvStore_PutString(NULL, kv, "age", "33")); - IFOK(s, kvStore_PutString(NULL, kv, "country", "US")); - IFOK(s, kvStore_PutString(NULL, kv, "name", "rip")); - IFOK(s, kvStore_PutString(NULL, kv, "age", "44")); - IFOK(s, kvStore_PutString(NULL, kv, "country", "MT")); - testCond(s == NATS_OK); - - test("Get keys (bad args): "); - s = kvStore_Keys(NULL, kv, NULL); - if (s == NATS_INVALID_ARG) - s = kvStore_Keys(&l, NULL, NULL); - testCond((s == NATS_INVALID_ARG) && (l.Keys == NULL) && (l.Count == 0)); - nats_clearLastError(); - - test("Get keys (timeout): "); - kvWatchOptions_Init(&o); - o.Timeout = 1; - s = kvStore_Keys(&l, kv, &o); - testCond((((s == NATS_OK) && (l.Keys != NULL) && (l.Count == 3))) - || ((s == NATS_TIMEOUT) && (l.Keys == NULL) && (l.Count == 0))); - nats_clearLastError(); - kvKeysList_Destroy(&l); - - test("Get keys: "); - s = kvStore_Keys(&l, kv, NULL); - testCond((s == NATS_OK) && (l.Keys != NULL) && (l.Count == 3)); - - test("Check keys: "); - for (i=0; (s == NATS_OK) && (i<3); i++) - { - char *k = l.Keys[i]; - if (k == NULL) - s = NATS_ERR; - else - { - if (strcmp(k, "name") == 0) - nameOK = true; - else if (strcmp(k, "age") == 0) - ageOK = true; - else if (strcmp(k, "country") == 0) - countryOK = true; - } - } - testCond((s == NATS_OK) && nameOK && ageOK && countryOK); - - test("Destroy list: "); - kvKeysList_Destroy(&l); - testCond((l.Keys == NULL) && (l.Count == 0)); - // Check this is ok - kvKeysList_Destroy(NULL); - - // Make sure delete and purge do the right thing and not return the keys. - test("Delete name: "); - s = kvStore_Delete(kv, "name"); - testCond(s == NATS_OK); - - test("Purge country: "); - s = kvStore_Purge(kv, "country", NULL); - testCond(s == NATS_OK); - - test("Get keys: "); - s = kvStore_Keys(&l, kv, NULL); - testCond((s == NATS_OK) && (l.Keys != NULL) && (l.Count == 1)); - - test("Check key: "); - k = l.Keys[0]; - s = ((k != NULL) && (strcmp(k, "age") == 0) ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - kvKeysList_Destroy(&l); - - kvStore_Destroy(kv); - - JS_TEARDOWN; -} - -static void -test_KeyValueDeleteVsPurge(void) -{ - natsStatus s; - kvStore *kv = NULL; - kvEntry *e = NULL; - kvEntryList l; - kvConfig kvc; - int i; - - JS_SETUP(2, 6, 2); - - test("Create KV: "); - kvConfig_Init(&kvc); - kvc.Bucket = "KVS"; - kvc.History = 10; - s = js_CreateKeyValue(&kv, js, &kvc); - testCond(s == NATS_OK); - - test("Populate: "); - s = kvStore_PutString(NULL, kv, "name", "derek"); - IFOK(s, kvStore_PutString(NULL, kv, "age", "22")); - IFOK(s, kvStore_PutString(NULL, kv, "name", "ivan")); - IFOK(s, kvStore_PutString(NULL, kv, "age", "33")); - IFOK(s, kvStore_PutString(NULL, kv, "name", "rip")); - IFOK(s, kvStore_PutString(NULL, kv, "age", "44")); - testCond(s == NATS_OK); - - test("Delete age: "); - s = kvStore_Delete(kv, "age"); - testCond(s == NATS_OK); - - test("Get age history: "); - s = kvStore_History(&l, kv, "age", NULL); - testCond((s == NATS_OK) && (l.Entries != NULL) && (l.Count == 4)); - - test("Check: "); - for (i=0;(s == NATS_OK) && (i<4); i++) - { - e = l.Entries[i]; - if (e == NULL) - s = NATS_ERR; - else if ((int) kvEntry_Delta(e) != (3-i)) - s = NATS_ERR; - } - testCond(s == NATS_OK); - kvEntryList_Destroy(&l); - - test("Purge name: "); - s = kvStore_Purge(kv, "name", NULL); - testCond(s == NATS_OK); - - test("Check marker: "); - s = kvStore_Get(&e, kv, "age"); - testCond((s == NATS_NOT_FOUND) && (e == NULL)); - - test("Get history: "); - s = kvStore_History(&l, kv, "name", NULL); - testCond((s == NATS_OK) && (l.Entries != NULL) && (l.Count == 1)); - - test("Check: "); - e = l.Entries[0]; - s = ((e != NULL) && (kvEntry_Operation(e) == kvOp_Purge) ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - kvEntryList_Destroy(&l); - - kvStore_Destroy(kv); - - JS_TEARDOWN; -} - -static void -test_KeyValueDeleteTombstones(void) -{ - natsStatus s; - kvStore *kv = NULL; - char *v = NULL; - jsStreamInfo *si = NULL; - kvConfig kvc; - kvPurgeOptions po; - int i; - - JS_SETUP(2, 6, 2); - - test("Create KV: "); - kvConfig_Init(&kvc); - kvc.Bucket = "KVS"; - kvc.History = 10; - s = js_CreateKeyValue(&kv, js, &kvc); - testCond(s == NATS_OK); - - test("Create asset: "); - v = (char*) malloc(100); - if (v != NULL) - { - for (i=0; i<100; i++) - v[i] = 'A' + (char)(i % 26); - v[99] = '\0'; - } - testCond(v != NULL); - - test("Populate: "); - for (i=0; (s == NATS_OK) && (i<100); i++) - { - char tmp[64]; - snprintf(tmp, sizeof(tmp), "key-%d", i); - s = kvStore_PutString(NULL, kv, tmp, v); - } - testCond(s == NATS_OK); - free(v); - - test("Delete: "); - for (i=0; (s == NATS_OK) && (i<100); i++) - { - char tmp[64]; - snprintf(tmp, sizeof(tmp), "key-%d", i); - s = kvStore_Delete(kv, tmp); - } - testCond(s == NATS_OK); - - test("Purge deletes (bad args): "); - s = kvStore_PurgeDeletes(NULL, NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Purge deletes: "); - kvPurgeOptions_Init(&po); - po.DeleteMarkersOlderThan = -1; - s = kvStore_PurgeDeletes(kv, &po); - testCond(s == NATS_OK); - - test("Check stream: "); - s = js_GetStreamInfo(&si, js, "KV_KVS", NULL, NULL); - testCond((s == NATS_OK) && (si != NULL) && (si->State.Msgs == 0)); - jsStreamInfo_Destroy(si); - - kvStore_Destroy(kv); - - JS_TEARDOWN; -} - -static void -test_KeyValuePurgeDeletesMarkerThreshold(void) -{ - natsStatus s; - kvStore *kv = NULL; - kvConfig kvc; - kvPurgeOptions po; - kvEntryList list; - - JS_SETUP(2, 7, 2); - - test("Create KV: "); - kvConfig_Init(&kvc); - kvc.Bucket = "KVS"; - kvc.History = 10; - s = js_CreateKeyValue(&kv, js, &kvc); - testCond(s == NATS_OK); - - test("Put keys: ") - s = kvStore_PutString(NULL, kv, "foo", "foo1"); - IFOK(s, kvStore_PutString(NULL, kv, "bar", "bar1")); - IFOK(s, kvStore_PutString(NULL, kv, "foo", "foo2")); - testCond(s == NATS_OK); - - test("Delete foo: "); - s = kvStore_Delete(kv, "foo"); - testCond(s == NATS_OK); - - nats_Sleep(500); - - test("Delete bar: "); - s = kvStore_Delete(kv, "bar"); - testCond(s == NATS_OK); - - test("PurgeOptions init bad args: "); - s = kvPurgeOptions_Init(NULL); - testCond(s == NATS_INVALID_ARG); - nats_clearLastError(); - - test("Purge deletes: "); - kvPurgeOptions_Init(&po); - po.DeleteMarkersOlderThan = NATS_MILLIS_TO_NANOS(100); - s = kvStore_PurgeDeletes(kv, &po); - testCond(s == NATS_OK); - - // The key foo should have been completely cleared of the data - // and the delete marker. - test("Check foo history: ") - s = kvStore_History(&list, kv, "foo", NULL); - testCond((s == NATS_NOT_FOUND) && (list.Entries == NULL) && (list.Count == 0)); - - test("Check bar history: "); - s = kvStore_History(&list, kv, "bar", NULL); - testCond((s == NATS_OK) && (list.Count == 1) && (list.Entries != NULL) - && (kvEntry_Operation(list.Entries[0]) == kvOp_Delete)); - kvEntryList_Destroy(&list); - kvStore_Destroy(kv); - - JS_TEARDOWN; -} - -static void -test_KeyValueCrossAccount(void) -{ - natsStatus s; - natsOptions *opts= NULL; - natsConnection *nc1 = NULL; - natsConnection *nc2 = NULL; - jsCtx *js1 = NULL; - jsCtx *js2 = NULL; - natsPid pid = NATS_INVALID_PID; - kvStore *kv1 = NULL; - kvWatcher *w1 = NULL; - kvStore *kv2 = NULL; - kvWatcher *w2 = NULL; - kvEntry *e = NULL; - jsStreamInfo *si = NULL; - uint64_t rev = 0; - kvConfig kvc; - jsOptions o; - char datastore[256] = {'\0'}; - char cmdLine[1024] = {'\0'}; - char confFile[256] = {'\0'}; - kvPurgeOptions po; - - ENSURE_JS_VERSION(2, 6, 2); - - _makeUniqueDir(datastore, sizeof(datastore), "datastore_"); - _createConfFile(confFile, sizeof(confFile), - "jetstream: enabled\n"\ - "accounts: {\n"\ - " A: {\n"\ - " users: [ {user: a, password: a} ]\n"\ - " jetstream: enabled\n"\ - " exports: [\n"\ - " {service: '$JS.API.>' }\n"\ - " {service: '$KV.>'}\n"\ - " {stream: 'accI.>'}\n"\ - " ]\n"\ - " },\n"\ - " I: {\n"\ - " users: [ {user: i, password: i} ]\n"\ - " imports: [\n"\ - " {service: {account: A, subject: '$JS.API.>'}, to: 'fromA.>' }\n"\ - " {service: {account: A, subject: '$KV.>'}, to: 'fromA.$KV.>' }\n"\ - " {stream: {subject: 'accI.>', account: A}}\n"\ - " ]\n"\ - " }\n"\ - "}"); - - test("Start JS server: "); - snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -c %s", datastore, confFile); - pid = _startServer("nats://127.0.0.1:4222", cmdLine, true); - CHECK_SERVER_STARTED(pid); - testCond(true); - - test("Create conn1: "); - s = natsConnection_ConnectTo(&nc1, "nats://a:a@127.0.0.1:4222"); - testCond(s == NATS_OK); - - test("Get context1: "); - s = natsConnection_JetStream(&js1, nc1, NULL); - testCond(s == NATS_OK); - - test("Create KV1: "); - kvConfig_Init(&kvc); - kvc.Bucket = "Map"; - s = js_CreateKeyValue(&kv1, js1, &kvc); - testCond(s == NATS_OK); - - test("Create Watcher1: "); - s = kvStore_Watch(&w1, kv1, "map", NULL); - IFOK(s, kvWatcher_Next(&e, w1, 1000)); - testCond((s == NATS_OK) && (e == NULL)); - - test("Create conn2: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetURL(opts, "nats://i:i@127.0.0.1:4222")); - IFOK(s, natsOptions_SetCustomInboxPrefix(opts, "accI")); - IFOK(s, natsConnection_Connect(&nc2, opts)); - testCond(s == NATS_OK); - - test("Get context2: "); - jsOptions_Init(&o); - o.Prefix = "fromA"; - s = natsConnection_JetStream(&js2, nc2, &o); - testCond(s == NATS_OK); - - test("Create KV2: "); - kvConfig_Init(&kvc); - kvc.Bucket = "Map"; - s = js_CreateKeyValue(&kv2, js2, &kvc); - testCond(s == NATS_OK); - - test("Create Watcher2: "); - s = kvStore_Watch(&w2, kv2, "map", NULL); - IFOK(s, kvWatcher_Next(&e, w2, 1000)); - testCond((s == NATS_OK) && (e == NULL)); - - test("Put: "); - s = kvStore_PutString(&rev, kv2, "map", "value"); - testCond(s == NATS_OK); - - test("Get from kv1: "); - s = kvStore_Get(&e, kv1, "map"); - testCond((s == NATS_OK) && (e != NULL) - && (strcmp(kvEntry_Key(e), "map") == 0) - && (strcmp(kvEntry_ValueString(e), "value") == 0)); - kvEntry_Destroy(e); - e = NULL; - - test("Get from kv2: "); - s = kvStore_Get(&e, kv2, "map"); - testCond((s == NATS_OK) && (e != NULL) - && (strcmp(kvEntry_Key(e), "map") == 0) - && (strcmp(kvEntry_ValueString(e), "value") == 0)); - kvEntry_Destroy(e); - e = NULL; - - test("Watcher1 Next: "); - s = kvWatcher_Next(&e, w1, 1000); - testCond((s == NATS_OK) && (e != NULL) - && (strcmp(kvEntry_Key(e), "map") == 0) - && (strcmp(kvEntry_ValueString(e), "value") == 0)); - kvEntry_Destroy(e); - e = NULL; - - test("Watcher2 Next: "); - s = kvWatcher_Next(&e, w2, 1000); - testCond((s == NATS_OK) && (e != NULL) - && (strcmp(kvEntry_Key(e), "map") == 0) - && (strcmp(kvEntry_ValueString(e), "value") == 0)); - kvEntry_Destroy(e); - e = NULL; - - test("Update from kv2: "); - s = kvStore_UpdateString(NULL, kv2, "map", "updated", rev); - testCond(s == NATS_OK); - - test("Get from kv1: "); - s = kvStore_Get(&e, kv1, "map"); - testCond((s == NATS_OK) && (e != NULL) - && (strcmp(kvEntry_Key(e), "map") == 0) - && (strcmp(kvEntry_ValueString(e), "updated") == 0)); - kvEntry_Destroy(e); - e = NULL; - - test("Get from kv2: "); - s = kvStore_Get(&e, kv2, "map"); - testCond((s == NATS_OK) && (e != NULL) - && (strcmp(kvEntry_Key(e), "map") == 0) - && (strcmp(kvEntry_ValueString(e), "updated") == 0)); - kvEntry_Destroy(e); - e = NULL; - - test("Watcher1 Next: "); - s = kvWatcher_Next(&e, w1, 1000); - testCond((s == NATS_OK) && (e != NULL) - && (strcmp(kvEntry_Key(e), "map") == 0) - && (strcmp(kvEntry_ValueString(e), "updated") == 0)); - kvEntry_Destroy(e); - e = NULL; - - test("Watcher2 Next: "); - s = kvWatcher_Next(&e, w2, 1000); - testCond((s == NATS_OK) && (e != NULL) - && (strcmp(kvEntry_Key(e), "map") == 0) - && (strcmp(kvEntry_ValueString(e), "updated") == 0)); - kvEntry_Destroy(e); - e = NULL; - - test("Purge key from kv2: "); - s = kvStore_Purge(kv2, "map", NULL); - testCond(s == NATS_OK); - - test("Check purge ok from w1: "); - s = kvWatcher_Next(&e, w1, 1000); - testCond((s == NATS_OK) && (e != NULL) && (kvEntry_Operation(e) == kvOp_Purge)); - kvEntry_Destroy(e); - e = NULL; - - test("Check purge ok from w2: "); - s = kvWatcher_Next(&e, w2, 1000); - testCond((s == NATS_OK) && (e != NULL) && (kvEntry_Operation(e) == kvOp_Purge)); - kvEntry_Destroy(e); - e = NULL; - - test("Delete purge records: "); - kvPurgeOptions_Init(&po); - po.DeleteMarkersOlderThan = -1; - s = kvStore_PurgeDeletes(kv2, &po); - testCond(s == NATS_OK); - - test("All gone: "); - s = js_GetStreamInfo(&si, js1, "KV_Map", NULL, NULL); - testCond((s == NATS_OK) && (si != NULL) && (si->State.Msgs == 0)); - jsStreamInfo_Destroy(si); - si = NULL; - - test("Delete key from kv2: "); - s = kvStore_Delete(kv2, "map"); - testCond(s == NATS_OK); - - test("Check key gone: "); - s = kvStore_Get(&e, kv1, "map"); - testCond((s == NATS_NOT_FOUND) && (e == NULL)); - - kvWatcher_Destroy(w2); - w2 = NULL; - kvStore_Destroy(kv2); - kv2 = NULL; - jsCtx_Destroy(js2); - js2 = NULL; - - test("Get context2 (with trailing dot for prefix): "); - jsOptions_Init(&o); - o.Prefix = "fromA"; - s = natsConnection_JetStream(&js2, nc2, &o); - testCond(s == NATS_OK); - - test("Create KV2: "); - kvConfig_Init(&kvc); - kvc.Bucket = "Map"; - s = js_CreateKeyValue(&kv2, js2, &kvc); - testCond(s == NATS_OK); - - test("Put: "); - s = kvStore_PutString(NULL, kv2, "map", "value2"); - testCond(s == NATS_OK); - - test("Get from kv1: "); - s = kvStore_Get(&e, kv1, "map"); - testCond((s == NATS_OK) && (e != NULL) - && (strcmp(kvEntry_Key(e), "map") == 0) - && (strcmp(kvEntry_ValueString(e), "value2") == 0)); - kvEntry_Destroy(e); - e = NULL; - - kvWatcher_Destroy(w1); - kvStore_Destroy(kv1); - jsCtx_Destroy(js1); - kvWatcher_Destroy(w2); - kvStore_Destroy(kv2); - jsCtx_Destroy(js2); - natsOptions_Destroy(opts); - natsConnection_Destroy(nc1); - natsConnection_Destroy(nc2); - _stopServer(pid); - rmtree(datastore); - remove(confFile); -} - -static natsStatus -_checkDiscard(jsCtx *js, jsDiscardPolicy expected, kvStore **newKV) -{ - kvStore *kv = NULL; - jsStreamInfo *si = NULL; - kvConfig kvc; - natsStatus s; - - kvConfig_Init(&kvc); - kvc.Bucket = "TEST"; - s = js_CreateKeyValue(&kv, js, &kvc); - IFOK(s, js_GetStreamInfo(&si, js, "KV_TEST", NULL, NULL)); - IFOK(s, (si->Config->Discard == expected ? NATS_OK : NATS_ERR)); - - jsStreamInfo_Destroy(si); - - *newKV = kv; - - return s; -} - -static void -test_KeyValueDiscardOldToNew(void) -{ - kvStore *kv = NULL; - kvConfig kvc; - natsStatus s; - - JS_SETUP(2, 7, 2); - - // Change the server version in the connection to - // create as-if we were connecting to a v2.7.1 server. - natsConn_Lock(nc); - nc->srvVersion.ma = 2; - nc->srvVersion.mi = 7; - nc->srvVersion.up = 1; - natsConn_Unlock(nc); - - test("Check discard (old): "); - s = _checkDiscard(js, js_DiscardOld, &kv); - testCond(s == NATS_OK); - kvStore_Destroy(kv); - kv = NULL; - - // Now change version to 2.7.2 - natsConn_Lock(nc); - nc->srvVersion.ma = 2; - nc->srvVersion.mi = 7; - nc->srvVersion.up = 2; - natsConn_Unlock(nc); - - test("Check discard (old, no auto-update): "); - s = _checkDiscard(js, js_DiscardOld, &kv); - testCond((s == NATS_ERR) && (kv == NULL) - && (strstr(nats_GetLastError(NULL), "different configuration") != NULL)); - nats_clearLastError(); - - // Now delete the kv store and create against 2.7.2+ - test("Delete KV: "); - s = js_DeleteStream(js, "KV_TEST", NULL, NULL); - testCond(s == NATS_OK); - - test("Check discard (new): "); - s = _checkDiscard(js, js_DiscardNew, &kv); - testCond(s == NATS_OK); - kvStore_Destroy(kv); - kv = NULL; - - test("Check that other changes are rejected: "); - kvConfig_Init(&kvc); - kvc.Bucket = "TEST"; - kvc.MaxBytes = 1024*1024; - s = js_CreateKeyValue(&kv, js, &kvc); - testCond((s == NATS_ERR) - && (strstr(nats_GetLastError(NULL), "different configuration") != NULL)); - kvStore_Destroy(kv); - - JS_TEARDOWN; -} - -static void -test_KeyValueRePublish(void) -{ - kvStore *kv = NULL; - jsStreamInfo *si = NULL; - natsSubscription *sub = NULL; - natsMsg *msg = NULL; - kvConfig kvc; - jsRePublish rp; - natsStatus s; - - JS_SETUP(2, 9, 0); - - test("Create KV: "); - kvConfig_Init(&kvc); - kvc.Bucket = "TEST_UPDATE"; - s = js_CreateKeyValue(&kv, js, &kvc); - testCond(s == NATS_OK); - - kvStore_Destroy(kv); - kv = NULL; - - test("Set RePublish should fail: "); - jsRePublish_Init(&rp); - rp.Source = ">"; - rp.Destination = "bar.>"; - kvc.RePublish =&rp; - s = js_CreateKeyValue(&kv, js, &kvc); - testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "different configuration") != NULL)); - nats_clearLastError(); - - test("Create with repub: "); - kvc.Bucket = "TEST"; - s = js_CreateKeyValue(&kv, js, &kvc); - testCond(s == NATS_OK); - - test("Check set: "); - s = js_GetStreamInfo(&si, js, "KV_TEST", NULL, NULL); - testCond((s == NATS_OK) && (si->Config != NULL) && (si->Config->RePublish != NULL)); - jsStreamInfo_Destroy(si); - - test("Sub: "); - s = natsConnection_SubscribeSync(&sub, nc, "bar.>"); - testCond(s == NATS_OK); - - test("Put: "); - s = kvStore_PutString(NULL, kv, "foo", "value"); - testCond(s == NATS_OK); - - test("Get msg: "); - s = natsSubscription_NextMsg(&msg, sub, 1000); - testCond(s == NATS_OK); - - test("Check msg: "); - s = (strcmp(natsMsg_GetData(msg), "value") == 0 ? NATS_OK : NATS_ERR); - if (s == NATS_OK) - { - const char *subj = NULL; - - s = natsMsgHeader_Get(msg, JSSubject, &subj); - if (s == NATS_OK) - s = (strcmp(subj, "$KV.TEST.foo") == 0 ? NATS_OK : NATS_ERR); - } - testCond(s == NATS_OK); - - natsMsg_Destroy(msg); - natsSubscription_Destroy(sub); - kvStore_Destroy(kv); - - JS_TEARDOWN; -} - -static void -test_KeyValueMirrorDirectGet(void) -{ - kvStore *kv = NULL; - kvConfig kvc; - jsStreamConfig sc; - jsStreamSource ss; - natsStatus s; - int i; - - JS_SETUP(2, 9, 0); - - test("Create KV: "); - kvConfig_Init(&kvc); - kvc.Bucket = "DIRECT_GET"; - s = js_CreateKeyValue(&kv, js, &kvc); - testCond(s == NATS_OK); - - test("Add mirror: "); - jsStreamConfig_Init(&sc); - sc.Name = "MIRROR"; - jsStreamSource_Init(&ss); - ss.Name = "KV_DIRECT_GET"; - sc.Mirror = &ss; - sc.MirrorDirect = true; - s = js_AddStream(NULL, js, &sc, NULL, NULL); - testCond(s == NATS_OK); - - test("Populate: "); - for (i=0; (s==NATS_OK) && (i<100); i++) - { - char key[64]; - - snprintf(key, sizeof(key), "KEY.%d", i); - s = kvStore_PutString(NULL, kv, key, key); - } - testCond(s == NATS_OK); - - test("Check get: "); - for (i=0; (s==NATS_OK) && (i<100); i++) - { - kvEntry *e = NULL; - s = kvStore_Get(&e, kv, "KEY.22"); - if (s == NATS_OK) - { - s = (strcmp(kvEntry_ValueString(e), "KEY.22") == 0 ? NATS_OK : NATS_ERR); - kvEntry_Destroy(e); - } - } - testCond(s == NATS_OK); - - kvStore_Destroy(kv); - - JS_TEARDOWN; -} - -static natsStatus -_connectToHubAndCheckLeaf(natsConnection **hub, natsConnection *lnc) -{ - natsStatus s = NATS_OK; - natsConnection *nc = NULL; - natsSubscription *sub = NULL; - int i; - - s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); - IFOK(s, natsConnection_SubscribeSync(&sub, nc, "check")); - IFOK(s, natsConnection_Flush(nc)); - if (s == NATS_OK) - { - for (i=0; i<10; i++) - { - s = natsConnection_PublishString(lnc, "check", "hello"); - if (s == NATS_OK) - { - natsMsg *msg = NULL; - s = natsSubscription_NextMsg(&msg, sub, 500); - natsMsg_Destroy(msg); - if (s == NATS_OK) - break; - } - } - } - natsSubscription_Destroy(sub); - if (s == NATS_OK) - *hub = nc; - else - natsConnection_Destroy(nc); - return s; -} - -static void -test_KeyValueMirrorCrossDomains(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsConnection *lnc= NULL; - jsCtx *js = NULL; - jsCtx *ljs= NULL; - jsCtx *rjs= NULL; - natsPid pid = NATS_INVALID_PID; - natsPid pid2= NATS_INVALID_PID; - jsOptions o; - jsErrCode jerr = 0; - char datastore[256] = {'\0'}; - char datastore2[256] = {'\0'}; - char cmdLine[1024] = {'\0'}; - char confFile[256] = {'\0'}; - char lconfFile[256] = {'\0'}; - kvStore *kv = NULL; - kvStore *lkv = NULL; - kvStore *mkv = NULL; - kvStore *rkv = NULL; - kvEntry *e = NULL; - jsStreamInfo *si = NULL; - kvWatcher *w = NULL; - int ok = 0; - kvPurgeOptions po; - kvConfig kvc; - jsStreamSource src; - int i; - - ENSURE_JS_VERSION(2, 9, 0); - - _makeUniqueDir(datastore, sizeof(datastore), "datastore_"); - _createConfFile(confFile, sizeof(confFile), - "server_name: HUB\n"\ - "listen: 127.0.0.1:4222\n"\ - "jetstream: { domain: HUB }\n"\ - "leafnodes { listen: 127.0.0.1:7422 }\n"); - - test("Start hub: "); - snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -c %s", datastore, confFile); - pid = _startServer("nats://127.0.0.1:4222", cmdLine, true); - CHECK_SERVER_STARTED(pid); - testCond(true); - - _makeUniqueDir(datastore2, sizeof(datastore2), "datastore_"); - _createConfFile(lconfFile, sizeof(lconfFile), - "server_name: LEAF\n"\ - "listen: 127.0.0.1:4223\n"\ - "jetstream: { domain: LEAF }\n"\ - "leafnodes {\n"\ - " remotes = [ { url: leaf://127.0.0.1:7422 } ]\n"\ - "}\n"); - - test("Start leaf: "); - snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -c %s", datastore, lconfFile); - pid2 = _startServer("nats://127.0.0.1:4223", cmdLine, true); - CHECK_SERVER_STARTED(pid2); - testCond(true); - - test("Connect to leaf: "); - s = natsConnection_ConnectTo(&lnc, "nats://127.0.0.1:4223"); - testCond(s == NATS_OK); - - test("Get context: "); - s = natsConnection_JetStream(&ljs, lnc, NULL); - testCond(s == NATS_OK); - - test("Connect to hub and check connectivity through leaf: "); - s = _connectToHubAndCheckLeaf(&nc, lnc); - testCond(s == NATS_OK); - - test("Get context: "); - s = natsConnection_JetStream(&js, nc, NULL); - testCond(s == NATS_OK); - - test("Create KV value: "); - kvConfig_Init(&kvc); - kvc.Bucket = "TEST"; - s = js_CreateKeyValue(&kv, js, &kvc); - testCond(s == NATS_OK); - - test("Put keys: "); - s = kvStore_PutString(NULL, kv, "name", "derek"); - IFOK(s, kvStore_PutString(NULL, kv, "age", "22")); - testCond(s == NATS_OK); - - test("Create KV: "); - kvConfig_Init(&kvc); - kvc.Bucket = "MIRROR"; - jsStreamSource_Init(&src); - src.Name = "TEST"; - src.Domain = "HUB"; - kvc.Mirror = &src; - s = js_CreateKeyValue(&lkv, ljs, &kvc); - testCond(s == NATS_OK); - - test("Check config not changed: "); - testCond((strcmp(kvc.Bucket, "MIRROR") == 0) - && (kvc.Mirror != NULL) - && (strcmp(kvc.Mirror->Name, "TEST") == 0) - && (strcmp(kvc.Mirror->Domain, "HUB") == 0) - && (kvc.Mirror->External == NULL)); - - test("Get stream info: "); - s = js_GetStreamInfo(&si, ljs, "KV_MIRROR", NULL, &jerr); - testCond((s == NATS_OK) && (si != NULL) && (jerr == 0)); - - test("Check mirror direct: "); - testCond(si->Config->MirrorDirect); - jsStreamInfo_Destroy(si); - si = NULL; - - test("Check mirror syncs: "); - for (i=0; i<10; i++) - { - s = js_GetStreamInfo(&si, ljs, "KV_MIRROR", NULL, NULL); - if (s != NATS_OK) - break; - - if (si->State.Msgs != 2) - s = NATS_ERR; - - jsStreamInfo_Destroy(si); - si = NULL; - if (s == NATS_OK) - break; - nats_Sleep(250); - } - testCond(s == NATS_OK); - - // Bind locally from leafnode and make sure both get and put work. - test("Leaf KV: "); - s = js_KeyValue(&mkv, ljs, "MIRROR"); - testCond(s == NATS_OK); - - test("Put key: "); - s = kvStore_PutString(NULL, mkv, "name", "rip"); - testCond(s == NATS_OK); - - test("Get key: "); - s = kvStore_Get(&e, mkv, "name"); - // levb: we seem to get the old value at times here, so try one more time if - // there's a mismatch - for (i = 0; i < 3; i++) - if ((s == NATS_OK) && (e != NULL) && (strcmp(kvEntry_ValueString(e), "rip") != 0)) - { - kvEntry_Destroy(e); - e = NULL; - s = kvStore_Get(&e, mkv, "name"); - } - if ((s == NATS_OK) && (e != NULL)) - s = (strcmp(kvEntry_ValueString(e), "rip") == 0 ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - kvEntry_Destroy(e); - e = NULL; - - test("Get context for HUB: "); - jsOptions_Init(&o); - o.Domain = "HUB"; - s = natsConnection_JetStream(&rjs, lnc, &o); - testCond(s == NATS_OK); - - test("Get KV: "); - s = js_KeyValue(&rkv, rjs, "TEST"); - testCond(s == NATS_OK); - - test("Put key: "); - s = kvStore_PutString(NULL, rkv, "name", "ivan"); - testCond(s == NATS_OK); - - test("Get key: "); - s = kvStore_Get(&e, rkv, "name"); - if ((s == NATS_OK) && (e != NULL)) - s = (strcmp(kvEntry_ValueString(e), "ivan") == 0 ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - kvEntry_Destroy(e); - e = NULL; - - test("Shutdown hub: "); - jsCtx_Destroy(js); - kvStore_Destroy(kv); - natsConnection_Destroy(nc); - nc = NULL; - _stopServer(pid); - pid = NATS_INVALID_PID; - testCond(true); - nats_Sleep(500); - - test("Get key: "); - // Use mkv here, not rkv. - s = kvStore_Get(&e, mkv, "name"); - if ((s == NATS_OK) && (e != NULL)) - s = (strcmp(kvEntry_ValueString(e), "ivan") == 0 ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - kvEntry_Destroy(e); - e = NULL; - - test("Create watcher (name): "); - s = kvStore_Watch(&w, mkv, "name", NULL); - testCond(s == NATS_OK); - - test("Check watcher: "); - s = kvWatcher_Next(&e, w, 1000); - if (s == NATS_OK) - { - if ((strcmp(kvEntry_Key(e), "name") != 0) || (strcmp(kvEntry_ValueString(e), "ivan") != 0)) - s = NATS_ERR; - kvEntry_Destroy(e); - e = NULL; - } - IFOK(s, kvWatcher_Next(&e, w, 1000)); - if (s == NATS_OK) - { - if ((kvEntry_Key(e) != NULL) || (kvEntry_ValueString(e) != NULL)) - s = NATS_ERR; - kvEntry_Destroy(e); - e = NULL; - } - testCond(s == NATS_OK); - - test("No more: "); - s = kvWatcher_Next(&e, w, 250); - testCond((s == NATS_TIMEOUT) && (e == NULL)); - nats_clearLastError(); - - kvWatcher_Destroy(w); - w = NULL; - - test("Create watcher (all): ") - s = kvStore_WatchAll(&w, mkv, NULL); - testCond((s == NATS_OK) && (w != NULL)); - - test("Check watcher: "); - s = kvWatcher_Next(&e, w, 1000); - if (s == NATS_OK) - { - if ((strcmp(kvEntry_Key(e), "age") != 0) || (strcmp(kvEntry_ValueString(e), "22") != 0)) - s = NATS_ERR; - kvEntry_Destroy(e); - e = NULL; - } - IFOK(s, kvWatcher_Next(&e, w, 1000)); - if (s == NATS_OK) - { - if ((strcmp(kvEntry_Key(e), "name") != 0) || (strcmp(kvEntry_ValueString(e), "ivan") != 0)) - s = NATS_ERR; - kvEntry_Destroy(e); - e = NULL; - } - IFOK(s, kvWatcher_Next(&e, w, 1000)); - if (s == NATS_OK) - { - if ((kvEntry_Key(e) != NULL) || (kvEntry_ValueString(e) != NULL)) - s = NATS_ERR; - kvEntry_Destroy(e); - e = NULL; - } - testCond(s == NATS_OK); - - test("No more: "); - s = kvWatcher_Next(&e, w, 250); - testCond((s == NATS_TIMEOUT) && (e == NULL)); - nats_clearLastError(); - - test("Restart hub: "); - snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -c %s", datastore, confFile); - pid = _startServer("nats://127.0.0.1:4222", cmdLine, true); - CHECK_SERVER_STARTED(pid); - testCond(true); - - test("Connect to hub and check connectivity through leaf: "); - s = _connectToHubAndCheckLeaf(&nc, lnc); - testCond(s == NATS_OK); - - test("Delete keys: "); - s = kvStore_Delete(mkv, "age"); - IFOK(s, kvStore_Delete(mkv, "name")); - testCond(s == NATS_OK); - - test("Check mirror syncs: "); - for (i=0; (ok != 2) && (i < 10); i++) - { - if (kvWatcher_Next(&e, w, 10000) == NATS_OK) - { - if (((strcmp(kvEntry_Key(e), "age") == 0) || (strcmp(kvEntry_Key(e), "name") == 0)) - && (kvEntry_Operation(e) == kvOp_Delete)) - { - ok++; - } - kvEntry_Destroy(e); - e = NULL; - } - } - testCond((s == NATS_OK) && (ok == 2)); - - test("Purge deletes: "); - kvPurgeOptions_Init(&po); - po.DeleteMarkersOlderThan = -1; - s = kvStore_PurgeDeletes(mkv, &po); - testCond(s == NATS_OK); - - nats_clearLastError(); - test("Check stream: "); - s = js_GetStreamInfo(&si, ljs, "KV_MIRROR", NULL, NULL); - testCond((s == NATS_OK) && (si != NULL) && (si->State.Msgs == 0)); - jsStreamInfo_Destroy(si); - - kvWatcher_Destroy(w); - natsConnection_Destroy(nc); - kvStore_Destroy(rkv); - kvStore_Destroy(mkv); - kvStore_Destroy(lkv); - jsCtx_Destroy(rjs); - jsCtx_Destroy(ljs); - natsConnection_Destroy(lnc); - _stopServer(pid2); - rmtree(datastore2); - _stopServer(pid); - rmtree(datastore); - remove(confFile); - remove(lconfFile); -} - -static void -test_MicroMatchEndpointSubject(void) -{ - // endpoint, actual, match - const char *test_cases[] = { - "foo", "foo", "true", - "foo.bar.meh", "foo.bar.meh", "true", - "foo.bar.meh", "foo.bar", NULL, - "foo.bar", "foo.bar.meh", NULL, - "foo.*.meh", "foo.bar.meh", "true", - "*.bar.meh", "foo.bar.meh", "true", - "foo.bar.*", "foo.bar.meh", "true", - "foo.bar.>", "foo.bar.meh", "true", - "foo.>", "foo.bar.meh", "true", - "foo.b*ar.meh", "foo.bar.meh", NULL, - }; - char buf[1024]; - int i; - const char *ep_subject; - const char *actual_subject; - bool expected_match; - - for (i = 0; i < (int)(sizeof(test_cases) / sizeof(const char *)); i = i + 3) - { - ep_subject = test_cases[i]; - actual_subject = test_cases[i + 1]; - expected_match = (test_cases[i + 2] != NULL); - - snprintf(buf, sizeof(buf), "endpoint subject '%s', actual subject: '%s': ", ep_subject, actual_subject); - test(buf); - testCond(micro_match_endpoint_subject(ep_subject, actual_subject) == expected_match); - } -} - -static microError * -_microHandleRequest42(microRequest *req) -{ - return microRequest_Respond(req, "42", 2); -} - -static microError * -_microHandleRequestNoisy42(microRequest *req) -{ - if ((rand() % 10) == 0) - return micro_Errorf("Unexpected error!"); - - // Happy Path. - // Random delay between 5-10ms - nats_Sleep(5 + (rand() % 5)); - - return microRequest_Respond(req, "42", 2); -} - -static void -_microServiceDoneHandler(microService *m) -{ - struct threadArg *arg = (struct threadArg*) microService_GetState(m); - - natsMutex_Lock(arg->m); - arg->microRunningServiceCount--; - if (arg->microRunningServiceCount == 0) - { - arg->microAllDone = true; - natsCondition_Broadcast(arg->c); - } - natsMutex_Unlock(arg->m); -} - -static microError * -_startMicroservice(microService** new_m, natsConnection *nc, microServiceConfig *cfg, microEndpointConfig **eps, int num_eps, struct threadArg *arg) -{ - microError *err = NULL; - bool prev_done; - int i; - - cfg->DoneHandler = _microServiceDoneHandler; - cfg->State = arg; - - natsMutex_Lock(arg->m); - - arg->microRunningServiceCount++; - prev_done = arg->microAllDone; - arg->microAllDone = false; - - err = micro_AddService(new_m, nc, cfg); - if (err != NULL) - { - arg->microRunningServiceCount--; - arg->microAllDone = prev_done; - } - - natsMutex_Unlock(arg->m); - - if (err != NULL) - return err; - - for (i=0; i < num_eps; i++) - { - err = microService_AddEndpoint(*new_m, eps[i]); - if (err != NULL) - { - microService_Destroy(*new_m); - *new_m = NULL; - return err; - } - } - - return NULL; -} - -static void -_startMicroserviceOK(microService** new_m, natsConnection *nc, microServiceConfig *cfg, microEndpointConfig **eps, int num_eps, struct threadArg *arg) -{ - char buf[64]; - - snprintf(buf, sizeof(buf), "Start microservice %s: ", cfg->Name); - test(buf); - - testCond (NULL == _startMicroservice(new_m, nc, cfg, eps, num_eps, arg)); -} - -static void -_startManyMicroservices(microService** svcs, int n, natsConnection *nc, microServiceConfig *cfg, microEndpointConfig **eps, int num_eps, struct threadArg *arg) -{ - int i; - - for (i = 0; i < n; i++) - { - _startMicroserviceOK(&(svcs[i]), nc, cfg, eps, num_eps, arg); - } - - testCond(true); -} - -static void -_waitForMicroservicesAllDone(struct threadArg *arg) -{ - natsStatus s = NATS_OK; - - test("Wait for all microservices to stop: "); - natsMutex_Lock(arg->m); - while ((s != NATS_TIMEOUT) && !arg->microAllDone) - s = natsCondition_TimedWait(arg->c, arg->m, 1000); - natsMutex_Unlock(arg->m); - testCond((NATS_OK == s) && arg->microAllDone); - - // `Done` may be immediately followed by freeing the service, so wait a bit - // to make sure it happens before the test exits. - nats_Sleep(20); -} - -static void -_destroyMicroservicesAndWaitForAllDone(microService** svcs, int n, struct threadArg *arg) -{ - char buf[64]; - - snprintf(buf, sizeof(buf), "Wait for all %d microservices to stop: ", n); - test(buf); - - for (int i = 0; i < n; i++) - { - if (NULL != microService_Destroy(svcs[i])) - FAIL("Unable to destroy microservice!"); - } - - _waitForMicroservicesAllDone(arg); -} - -typedef struct -{ - const char *name; - microServiceConfig *cfg; - microEndpointConfig **endpoints; - int num_endpoints; - bool null_nc; - bool null_receiver; - const char *expected_err; - int expected_num_subjects; -} add_service_test_case_t; - -static void -test_MicroAddService(void) -{ - natsStatus s = NATS_OK; - microError *err = NULL; - struct threadArg arg; - natsOptions *opts = NULL; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - microService *m = NULL; - microServiceInfo *info = NULL; - natsMsg *reply = NULL; - int i, j, n; - char buf[1024]; - char *subject = NULL; - nats_JSON *js = NULL; - nats_JSON **array = NULL; - int array_len; - const char *str; - - microEndpointConfig default_ep_cfg = { - .Name = "default", - .Handler = _microHandleRequestNoisy42, - }; - microEndpointConfig ep1_cfg = { - .Name = "ep1", - .Handler = _microHandleRequestNoisy42, - }; - microEndpointConfig ep2_cfg = { - .Name = "ep2", - .Subject = "different-from-name", - .Handler = _microHandleRequestNoisy42, - }; - microEndpointConfig ep3_cfg = { - .Name = "ep3", - .Handler = _microHandleRequestNoisy42, - }; - microEndpointConfig *all_ep_cfgs[] = {&ep1_cfg, &ep2_cfg, &ep3_cfg}; - - microServiceConfig minimal_cfg = { - .Version = "1.0.0", - .Name = "minimal", - - }; - microServiceConfig full_cfg = { - .Version = "1.0.0", - .Name = "full", - .Endpoint = &default_ep_cfg, - .Description = "fully declared microservice", - }; - microServiceConfig err_no_name_cfg = { - .Version = "1.0.0", - }; - microServiceConfig err_no_version_cfg = { - .Name = "no-version", - }; - microServiceConfig err_invalid_version_cfg = { - .Version = "BLAH42", - .Name = "invalid-version", - }; - - add_service_test_case_t tcs[] = { - { - .name = "Minimal", - .cfg = &minimal_cfg, - }, - { - .name = "Full", - .cfg = &full_cfg, - .expected_num_subjects = 1, - }, - { - .name = "Full-with-endpoints", - .cfg = &full_cfg, - .endpoints = all_ep_cfgs, - .num_endpoints = sizeof(all_ep_cfgs) / sizeof(all_ep_cfgs[0]), - .expected_num_subjects = 4, - }, - { - .name = "Err-null-connection", - .cfg = &minimal_cfg, - .null_nc = true, - .expected_err = "status 16: invalid function argument", - }, - { - .name = "Err-null-receiver", - .cfg = &minimal_cfg, - .null_receiver = true, - .expected_err = "status 16: invalid function argument", - }, - { - .name = "Err-no-name", - .cfg = &err_no_name_cfg, - .expected_err = "status 16: invalid function argument", - }, - { - .name = "Err-no-version", - .cfg = &err_no_version_cfg, - .expected_err = "status 16: invalid function argument", - }, - { - .name = "Err-invalid-version", - .cfg = &err_invalid_version_cfg, - // TODO: validate the version format. - }, - }; - add_service_test_case_t tc; - - srand((unsigned int)nats_NowInNanoSeconds()); - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - opts = _createReconnectOptions(); - if ((opts == NULL) - || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK) - || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) - { - FAIL("Unable to setup test for MicroConnectionEvents!"); - } - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect to server: "); - testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - - for (n = 0; n < (int)(sizeof(tcs) / sizeof(tcs[0])); n++) - { - tc = tcs[n]; - - snprintf(buf, sizeof(buf), "%s: AddService: ", tc.name); - test(buf); - m = NULL; - err = _startMicroservice( - tc.null_receiver ? NULL : &m, - tc.null_nc ? NULL : nc, - tc.cfg, NULL, 0, &arg); - if (nats_IsStringEmpty(tc.expected_err)) - { - testCond(err == NULL); - } - else - { - if (strcmp(tc.expected_err, microError_String(err, buf, sizeof(buf))) != 0) - { - char buf2[2*1024]; - snprintf(buf2, sizeof(buf2), "Expected error '%s', got '%s'", tc.expected_err, buf); - FAIL(buf2); - } - testCond(true); - microError_Destroy(err); - continue; - } - - for (i = 0; i < tc.num_endpoints; i++) - { - snprintf(buf, sizeof(buf), "%s: AddEndpoint '%s': ", tc.name, tc.endpoints[i]->Name); - test(buf); - testCond(NULL == microService_AddEndpoint(m, tc.endpoints[i])); - } - - err = microService_GetInfo(&info, m); - if (err != NULL) - FAIL("failed to get service info!") - - // run through PING and INFO subject variations: 0: all, 1: .name, 2: .name.id - for (j = 0; j < 3; j++) - { - err = micro_new_control_subject(&subject, MICRO_PING_VERB, - (j > 0 ? tc.cfg->Name : NULL), - (j > 1 ? info->Id : NULL)); - if (err != NULL) - FAIL("failed to generate PING subject!"); - - snprintf(buf, sizeof(buf), "%s: Verify PING subject %s: ", tc.name, subject); - test(buf); - s = natsConnection_Request(&reply, nc, subject, NULL, 0, 1000); - IFOK(s, nats_JSONParse(&js, natsMsg_GetData(reply), natsMsg_GetDataLength(reply))); - IFOK(s, nats_JSONGetStrPtr(js, "id", &str)); - IFOK(s, (strcmp(str, info->Id) == 0) ? NATS_OK : NATS_ERR); - IFOK(s, nats_JSONGetStrPtr(js, "name", &str)); - IFOK(s, (strcmp(str, tc.cfg->Name) == 0) ? NATS_OK : NATS_ERR); - IFOK(s, nats_JSONGetStrPtr(js, "version", &str)); - IFOK(s, (strcmp(str, tc.cfg->Version) == 0) ? NATS_OK : NATS_ERR); - IFOK(s, nats_JSONGetStrPtr(js, "type", &str)); - IFOK(s, (strcmp(str, MICRO_PING_RESPONSE_TYPE) == 0) ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - nats_JSONDestroy(js); - natsMsg_Destroy(reply); - NATS_FREE(subject); - - err = micro_new_control_subject(&subject, MICRO_INFO_VERB, - (j > 0 ? tc.cfg->Name : NULL), - (j > 1 ? info->Id : NULL)); - if (err != NULL) - FAIL("failed to generate INFO subject!"); - - snprintf(buf, sizeof(buf), "%s: Verify INFO subject %s: ", tc.name, subject); - test(buf); - s = natsConnection_Request(&reply, nc, subject, NULL, 0, 1000); - array = NULL; - array_len = 0; - testCond((NATS_OK == s) - && (NATS_OK == nats_JSONParse(&js, natsMsg_GetData(reply), natsMsg_GetDataLength(reply))) - && (NATS_OK == nats_JSONGetStrPtr(js, "id", &str)) && (strcmp(str, info->Id) == 0) - && (NATS_OK == nats_JSONGetStrPtr(js, "name", &str)) && (strcmp(str, tc.cfg->Name) == 0) - && (NATS_OK == nats_JSONGetStrPtr(js, "type", &str)) && (strcmp(str, MICRO_INFO_RESPONSE_TYPE) == 0) - && (NATS_OK == nats_JSONGetArrayObject(js, "endpoints", &array, &array_len)) && (array_len == tc.expected_num_subjects) - ); - - NATS_FREE(array); - nats_JSONDestroy(js); - natsMsg_Destroy(reply); - NATS_FREE(subject); - } - - microServiceInfo_Destroy(info); - - if (m != NULL) - { - snprintf(buf, sizeof(buf), "%s: Destroy service: %d", m->cfg->Name, m->refs); - test(buf); - testCond(NULL == microService_Destroy(m)); - _waitForMicroservicesAllDone(&arg); - } - } - - test("Destroy the test connection: "); - natsConnection_Destroy(nc); - testCond(NATS_OK == _waitForConnClosed(&arg)); - - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - _stopServer(serverPid); -} - -static void -test_MicroGroups(void) -{ - natsStatus s = NATS_OK; - microError *err = NULL; - struct threadArg arg; - natsOptions *opts = NULL; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - microService *m = NULL; - microGroup *g1 = NULL; - microGroup *g2 = NULL; - microServiceInfo *info = NULL; - int i; - - microEndpointConfig ep1_cfg = { - .Name = "ep1", - .Handler = _microHandleRequest42, - }; - microEndpointConfig ep2_cfg = { - .Name = "ep2", - .Handler = _microHandleRequest42, - }; - microServiceConfig cfg = { - .Version = "1.0.0", - .Name = "with-groups", - }; - - const char* expected_subjects[] = { - "ep1", - "g1.ep1", - "g1.g2.ep1", - "g1.g2.ep2", - "g1.ep2", - }; - int expected_num_endpoints = sizeof(expected_subjects) / sizeof(expected_subjects[0]); - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - opts = _createReconnectOptions(); - if ((opts == NULL) - || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK) - || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) - { - FAIL("Unable to setup test for MicroConnectionEvents!"); - } - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect to server: "); - testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - - _startMicroservice(&m, nc, &cfg, NULL, 0, &arg); - - test("AddEndpoint 1 to service: "); - testCond(NULL == microService_AddEndpoint(m, &ep1_cfg)); - - test("AddGroup g1: "); - testCond(NULL == microService_AddGroup(&g1, m, "g1")); - - test("AddEndpoint 1 to g1: "); - testCond(NULL == microGroup_AddEndpoint(g1, &ep1_cfg)); - - test("Add sub-Group g2: "); - testCond(NULL == microGroup_AddGroup(&g2, g1, "g2")); - - test("AddEndpoint 1 to g2: "); - testCond(NULL == microGroup_AddEndpoint(g2, &ep1_cfg)); - - test("AddEndpoint 2 to g2: "); - testCond(NULL == microGroup_AddEndpoint(g2, &ep2_cfg)); - - test("AddEndpoint 2 to g1: "); - testCond(NULL == microGroup_AddEndpoint(g1, &ep2_cfg)); - - err = microService_GetInfo(&info, m); - if (err != NULL) - FAIL("failed to get service info!") - - test("Verify number of endpoints: "); - testCond(info->EndpointsLen == expected_num_endpoints); - - test("Verify endpoint subjects: "); - for (i = 0; i < info->EndpointsLen; i++) - { - if (strcmp(info->Endpoints[i].Subject, expected_subjects[i]) != 0) { - char buf[1024]; - snprintf(buf, sizeof(buf), "expected %s, got %s", expected_subjects[i], info->Endpoints[i].Subject); - FAIL(buf); - } - } - testCond(true); - - microServiceInfo_Destroy(info); - - microService_Destroy(m); - _waitForMicroservicesAllDone(&arg); - - test("Destroy the test connection: "); - natsConnection_Destroy(nc); - testCond(NATS_OK == _waitForConnClosed(&arg)); - - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - _stopServer(serverPid); -} - -#define NUM_MICRO_SERVICES 5 - -static void -test_MicroBasics(void) -{ - natsStatus s = NATS_OK; - microError *err = NULL; - struct threadArg arg; - natsOptions *opts = NULL; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - microService *svcs[NUM_MICRO_SERVICES]; - microEndpointConfig ep1_cfg = { - .Name = "do", - .Subject = "svc.do", - .Handler = _microHandleRequestNoisy42, - .State = NULL, - }; - microEndpointConfig ep2_cfg = { - .Name = "unused", - .Subject = "svc.unused", - .Handler = _microHandleRequestNoisy42, - .Metadata = (natsMetadata){ - .List = (const char *[]){"key1", "value1", "key2", "value2", "key3", "value3"}, - .Count = 3, - }, - .State = NULL, - }; - microEndpointConfig *eps[] = { - &ep1_cfg, - &ep2_cfg, - }; - microServiceConfig cfg = { - .Version = "1.0.0", - .Name = "CoolService", - .Description = "returns 42", - .Metadata = (natsMetadata){ - .List = (const char *[]){"skey1", "svalue1", "skey2", "svalue2"}, - .Count = 2, - }, - .Endpoint = NULL, - .State = NULL, - }; - natsMsg *reply = NULL; - microServiceInfo *info = NULL; - int i; - char buf[1024]; - char *subject = NULL; - natsInbox *inbox = NULL; - natsSubscription *sub = NULL; - nats_JSON *js = NULL; - nats_JSON *md = NULL; - int num_requests = 0; - int num_errors = 0; - int n; - nats_JSON **array; - int array_len; - const char *str; - - srand((unsigned int)nats_NowInNanoSeconds()); - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - opts = _createReconnectOptions(); - if ((opts == NULL) - || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK) - || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) - { - FAIL("Unable to setup test for MicroConnectionEvents!"); - } - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect to server: "); - testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - - _startManyMicroservices(svcs, NUM_MICRO_SERVICES, nc, &cfg, eps, sizeof(eps)/sizeof(eps[0]), &arg); - - // Now send 50 requests. - test("Send 50 requests (no matter response): "); - for (i = 0; i < 50; i++) - { - s = natsConnection_Request(&reply, nc, "svc.do", NULL, 0, 1000); - if (NATS_OK != s) - FAIL("Unable to send request"); - natsMsg_Destroy(reply); - } - testCond(NATS_OK == s); - - // Make sure we can request valid info with local API. - for (i = 0; i < NUM_MICRO_SERVICES; i++) - { - snprintf(buf, sizeof(buf), "Check local info #%d: ", i); - test(buf); - err = microService_GetInfo(&info, svcs[i]); - testCond((err == NULL) && - (strcmp(info->Name, "CoolService") == 0) && - (strlen(info->Id) > 0) && - (strcmp(info->Description, "returns 42") == 0) && - (strcmp(info->Version, "1.0.0") == 0) && - (info->Metadata.Count == 2)); - microServiceInfo_Destroy(info); - } - - // Make sure we can request valid info with $SRV.INFO request. - test("Create INFO inbox: "); - testCond(NATS_OK == natsInbox_Create(&inbox)); - micro_new_control_subject(&subject, MICRO_INFO_VERB, "CoolService", NULL); - test("Subscribe to INFO inbox: "); - testCond(NATS_OK == natsConnection_SubscribeSync(&sub, nc, inbox)); - test("Publish INFO request: "); - testCond(NATS_OK == natsConnection_PublishRequest(nc, subject, inbox, NULL, 0)); - for (i = 0;; i++) - { - snprintf(buf, sizeof(buf), "Receive INFO response #%d: ", i); - test(buf); - reply = NULL; - s = natsSubscription_NextMsg(&reply, sub, 250); - if (s == NATS_TIMEOUT) - { - testCond(i == NUM_MICRO_SERVICES); - break; - } - testCond(NATS_OK == s); - - snprintf(buf, sizeof(buf), "Parse INFO response#%d: ", i); - test(buf); - js = NULL; - testCond(NATS_OK == nats_JSONParse(&js, reply->data, reply->dataLen)) ; - - snprintf(buf, sizeof(buf), "Validate INFO response strings#%d: ", i); - test(buf); - testCond( - (NATS_OK == nats_JSONGetStrPtr(js, "name", &str)) && (strcmp(str, "CoolService") == 0) - && (NATS_OK == nats_JSONGetStrPtr(js, "description", &str)) && (strcmp(str, "returns 42") == 0) - && (NATS_OK == nats_JSONGetStrPtr(js, "version", &str)) && (strcmp(str, "1.0.0") == 0) - && (NATS_OK == nats_JSONGetStrPtr(js, "id", &str)) && (strlen(str) > 0) - ); - - snprintf(buf, sizeof(buf), "Validate INFO service metadata#%d: ", i); - test(buf); - md = NULL; - testCond( - (NATS_OK == nats_JSONGetObject(js, "metadata", &md)) - && (NATS_OK == nats_JSONGetStrPtr(md, "skey1", &str)) && (strcmp(str, "svalue1") == 0) - && (NATS_OK == nats_JSONGetStrPtr(md, "skey2", &str)) && (strcmp(str, "svalue2") == 0) - ); - test("Validate INFO has 2 endpoints: "); - array = NULL; - array_len = 0; - s = nats_JSONGetArrayObject(js, "endpoints", &array, &array_len); - testCond((NATS_OK == s) && (array != NULL) && (array_len == 2)); - - test("Validate INFO svc.do endpoint: "); - md = NULL; - testCond( - (NATS_OK == nats_JSONGetStrPtr(array[0], "name", &str)) && (strcmp(str, "do") == 0) - && (NATS_OK == nats_JSONGetStrPtr(array[0], "subject", &str)) && (strcmp(str, "svc.do") == 0) - && (NATS_OK == nats_JSONGetObject(array[0], "metadata", &md)) && (md == NULL) - ); - - test("Validate INFO unused endpoint with metadata: "); - md = NULL; - testCond( - (NATS_OK == nats_JSONGetStrPtr(array[1], "name", &str)) && (strcmp(str, "unused") == 0) - && (NATS_OK == nats_JSONGetStrPtr(array[1], "subject", &str)) && (strcmp(str, "svc.unused") == 0) - && (NATS_OK == nats_JSONGetObject(array[1], "metadata", &md)) - && (NATS_OK == nats_JSONGetStrPtr(md, "key1", &str)) && (strcmp(str, "value1") == 0) - && (NATS_OK == nats_JSONGetStrPtr(md, "key2", &str)) && (strcmp(str, "value2") == 0) - && (NATS_OK == nats_JSONGetStrPtr(md, "key3", &str)) && (strcmp(str, "value3") == 0) - ); - - nats_JSONDestroy(js); - natsMsg_Destroy(reply); - NATS_FREE(array); - } - natsSubscription_Destroy(sub); - natsInbox_Destroy(inbox); - NATS_FREE(subject); - - // Make sure we can request SRV.PING. - test("Create PING inbox: "); - testCond(NATS_OK == natsInbox_Create(&inbox)); - micro_new_control_subject(&subject, MICRO_PING_VERB, "CoolService", NULL); - test("Subscribe to PING inbox: "); - testCond(NATS_OK == natsConnection_SubscribeSync(&sub, nc, inbox)); - test("Publish PING request: "); - testCond(NATS_OK == natsConnection_PublishRequest(nc, subject, inbox, NULL, 0)); - for (i = 0;; i++) - { - snprintf(buf, sizeof(buf), "Receive PING response #%d: ", i); - test(buf); - reply = NULL; - s = natsSubscription_NextMsg(&reply, sub, 250); - if (s == NATS_TIMEOUT) - { - testCond(i == NUM_MICRO_SERVICES); - break; - } - testCond(NATS_OK == s); - snprintf(buf, sizeof(buf), "Validate PING response #%d: ", i); - test(buf); - js = NULL; - testCond((NATS_OK == nats_JSONParse(&js, reply->data, reply->dataLen)) && - (NATS_OK == nats_JSONGetStrPtr(js, "name", &str)) && - (strcmp(str, "CoolService") == 0)); - nats_JSONDestroy(js); - natsMsg_Destroy(reply); - } - natsSubscription_Destroy(sub); - natsInbox_Destroy(inbox); - NATS_FREE(subject); - - // Get and validate $SRV.STATS from all service instances. - test("Create STATS inbox: "); - testCond(NATS_OK == natsInbox_Create(&inbox)); - micro_new_control_subject(&subject, MICRO_STATS_VERB, "CoolService", NULL); - test("Subscribe to STATS inbox: "); - testCond(NATS_OK == natsConnection_SubscribeSync(&sub, nc, inbox)); - test("Publish STATS request: "); - testCond(NATS_OK == natsConnection_PublishRequest(nc, subject, inbox, NULL, 0)); - - for (i = 0;; i++) - { - snprintf(buf, sizeof(buf), "Receive STATS response #%d: ", i); - test(buf); - reply = NULL; - s = natsSubscription_NextMsg(&reply, sub, 250); - if (s == NATS_TIMEOUT) - { - testCond(i == NUM_MICRO_SERVICES); - break; - } - testCond(NATS_OK == s); - - test("Parse STATS response: "); - js = NULL; - s = nats_JSONParse(&js, reply->data, reply->dataLen); - testCond((NATS_OK == s) && (js != NULL)); - - test("Ensure STATS has 2 endpoints: "); - array = NULL; - array_len = 0; - s = nats_JSONGetArrayObject(js, "endpoints", &array, &array_len); - testCond((NATS_OK == s) && (array != NULL) && (array_len == 2)) - - test("Ensure endpoint 0 has num_requests: "); - n = 0; - s = nats_JSONGetInt(array[0], "num_requests", &n); - testCond(NATS_OK == s); - num_requests += n; - - test("Ensure endpoint 0 has num_errors: "); - n = 0; - s = nats_JSONGetInt(array[0], "num_errors", &n); - testCond(NATS_OK == s); - num_errors += n; - - NATS_FREE(array); - nats_JSONDestroy(js); - natsMsg_Destroy(reply); - } - test("Check that STATS total request counts add up (50): "); - testCond(num_requests == 50); - test("Check that STATS total error count is positive, depends on how many instances: "); - testCond(num_errors > 0); - - natsSubscription_Destroy(sub); - natsInbox_Destroy(inbox); - NATS_FREE(subject); - - _destroyMicroservicesAndWaitForAllDone(svcs, NUM_MICRO_SERVICES, &arg); - - test("Destroy the test connection: "); - natsConnection_Destroy(nc); - testCond(NATS_OK == _waitForConnClosed(&arg)); - - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - _stopServer(serverPid); -} - -static void -test_MicroStartStop(void) -{ - natsStatus s = NATS_OK; - struct threadArg arg; - natsOptions *opts = NULL; - natsConnection *nc = NULL; - natsPid serverPid = NATS_INVALID_PID; - microService *svcs[NUM_MICRO_SERVICES]; - microEndpointConfig ep_cfg = { - .Name = "do", - .Subject = "svc.do", - .Handler = _microHandleRequest42, - }; - microServiceConfig cfg = { - .Version = "1.0.0", - .Name = "CoolService", - .Description = "returns 42", - .Endpoint = &ep_cfg, - }; - natsMsg *reply = NULL; - int i; - - srand((unsigned int)nats_NowInNanoSeconds()); - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - opts = _createReconnectOptions(); - if ((opts == NULL) - || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) - || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK) - || (natsOptions_SetAllowReconnect(opts, false) != NATS_OK)) - { - FAIL("Unable to setup test for MicroConnectionEvents!"); - } - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect to server: "); - testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - - _startManyMicroservices(svcs, NUM_MICRO_SERVICES, nc, &cfg, NULL, 0, &arg); - - // Now send some requests. - test("Send requests: "); - for (i = 0; i < 20; i++) - { - reply = NULL; - s = natsConnection_Request(&reply, nc, "svc.do", NULL, 0, 2000); - if (NATS_OK != s) - FAIL("Unable to send request"); - if (reply == NULL || reply->dataLen != 2 || memcmp(reply->data, "42", 2) != 0) - FAIL("Unexpected reply"); - natsMsg_Destroy(reply); - } - testCond(NATS_OK == s); - - _destroyMicroservicesAndWaitForAllDone(svcs, NUM_MICRO_SERVICES, &arg); - - test("Destroy the test connection: "); - natsConnection_Destroy(nc); - testCond(NATS_OK == _waitForConnClosed(&arg)); - - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - _stopServer(serverPid); -} - -static void -test_MicroServiceStopsOnClosedConn(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - microService *m = NULL; - microServiceConfig cfg = { - .Name = "test", - .Version = "1.0.0", - }; - natsMsg *reply = NULL; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - opts = _createReconnectOptions(); - - if ((opts == NULL) || - (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) || - (natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)) || - (natsOptions_SetAllowReconnect(opts, false) != NATS_OK)) - { - FAIL("Unable to setup test for MicroConnectionEvents!"); - } - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect for microservice: "); - testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - - _startMicroservice(&m, nc, &cfg, NULL, 0, &arg); - - test("Test microservice is running: "); - testCond(!microService_IsStopped(m)) - - test("Test microservice is responding to PING: "); - testCond(NATS_OK == natsConnection_RequestString(&reply, nc, "$SRV.PING.test", "", 500)); - natsMsg_Destroy(reply); - reply = NULL; - - test("Close the connection: "); - testCond(NATS_OK == natsConnection_Drain(nc)); - natsConnection_Close(nc); - - test("Wait for it: "); - testCond(_waitForConnClosed(&arg) == NATS_OK); - - test("Ensure the connection has closed: "); - testCond(natsConnection_IsClosed(nc)); - - test("Wait for the service to stop: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.microAllDone) - s = natsCondition_TimedWait(arg.c, arg.m, 1000); - natsMutex_Unlock(arg.m); - testCond(arg.microAllDone); - - test("Test microservice is stopped: "); - testCond(microService_IsStopped(m)); - - test("Destroy microservice (final): "); - testCond(NULL == microService_Destroy(m)) - _waitForMicroservicesAllDone(&arg); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - _destroyDefaultThreadArgs(&arg); - _stopServer(serverPid); -} - -static void -test_MicroServiceStopsWhenServerStops(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - microService *m = NULL; - microServiceConfig cfg = { - .Name = "test", - .Version = "1.0.0", - }; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - opts = _createReconnectOptions(); - - if ((opts == NULL) - || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) - || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK) - || (natsOptions_SetAllowReconnect(opts, false) != NATS_OK)) - { - FAIL("Unable to setup test for MicroConnectionEvents!"); - } - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect for microservice: "); - testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - - _startMicroservice(&m, nc, &cfg, NULL, 0, &arg); - - test("Test microservice is running: "); - testCond(!microService_IsStopped(m)) - - test("Stop the server: "); - testCond((_stopServer(serverPid), true)); - - test("Wait for the service to stop: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.microAllDone) - s = natsCondition_TimedWait(arg.c, arg.m, 1000); - natsMutex_Unlock(arg.m); - testCond(arg.microAllDone); - - test("Test microservice is not running: "); - testCond(microService_IsStopped(m)) - - microService_Destroy(m); - _waitForMicroservicesAllDone(&arg); - - test("Destroy the test connection: "); - natsConnection_Destroy(nc); - testCond(NATS_OK == _waitForConnClosed(&arg)); - - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); -} - -void _microAsyncErrorHandler(microService *m, microEndpoint *ep, natsStatus s) -{ - struct threadArg *arg = (struct threadArg*) microService_GetState(m); - - natsMutex_Lock(arg->m); - // release the pending test request that caused the error - arg->closed = true; - - // set the data to verify - arg->status = s; - natsCondition_Broadcast(arg->c); - natsMutex_Unlock(arg->m); -} - -microError * -_microAsyncErrorRequestHandler(microRequest *req) -{ - struct threadArg *arg = microRequest_GetServiceState(req); - - natsMutex_Lock(arg->m); - - arg->msgReceived = true; - natsCondition_Signal(arg->c); - - while (!arg->closed) - natsCondition_Wait(arg->c, arg->m); - - natsMutex_Unlock(arg->m); - - return NULL; -} - -static void -test_MicroAsyncErrorHandler_MaxPendingMsgs(void) -{ - natsStatus s; - struct threadArg arg; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - microService *m = NULL; - microEndpoint *ep = NULL; - microEndpointConfig ep_cfg = { - .Name = "do", - .Subject = "async_test", - .Handler = _microAsyncErrorRequestHandler, - }; - microServiceConfig cfg = { - .Name = "test", - .Version = "1.0.0", - .ErrHandler = _microAsyncErrorHandler, - .State = &arg, - .DoneHandler = _microServiceDoneHandler, - }; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL)); - IFOK(s, natsOptions_SetMaxPendingMsgs(opts, 10)); - if (s != NATS_OK) - FAIL("Unable to create options for test AsyncErrHandler"); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect to NATS: "); - testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - - _startMicroservice(&m, nc, &cfg, NULL, 0, &arg); - - test("Test microservice is running: "); - testCond(!microService_IsStopped(m)) - - test("Add test endpoint: "); - testCond(NULL == micro_add_endpoint(&ep, m, NULL, &ep_cfg, true)); - - natsMutex_Lock(arg.m); - arg.status = NATS_OK; - natsMutex_Unlock(arg.m); - - test("Cause an error by sending too many messages: "); - for (int i=0; - (s == NATS_OK) && (i < (opts->maxPendingMsgs + 100)); i++) - { - s = natsConnection_PublishString(nc, "async_test", "hello"); - } - testCond((NATS_OK == s) - && (NATS_OK == natsConnection_Flush(nc))); - - test("Wait for async err callback: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 1000); - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && arg.closed && (arg.status == NATS_SLOW_CONSUMER)); - - microService_Destroy(m); - _waitForMicroservicesAllDone(&arg); - - test("Destroy the test connection: "); - natsConnection_Destroy(nc); - testCond(NATS_OK == _waitForConnClosed(&arg)); - - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - _stopServer(serverPid); -} - -static void -test_MicroAsyncErrorHandler_MaxPendingBytes(void) -{ - natsStatus s; - struct threadArg arg; - natsConnection* nc = NULL; - natsOptions* opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - microService* m = NULL; - microEndpoint* ep = NULL; - microEndpointConfig ep_cfg = { - .Name = "do", - .Subject = "async_test", - .Handler = _microAsyncErrorRequestHandler, - }; - microServiceConfig cfg = { - .Name = "test", - .Version = "1.0.0", - .ErrHandler = _microAsyncErrorHandler, - .State = &arg, - .DoneHandler = _microServiceDoneHandler, - }; - int data_len = 10; - const char msg[] = { 0,1,2,3,4,5,6,7,8,9 }; //10 bytes long message - int64_t pendingBytesLimit = 100; - int i = 0; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL)); - IFOK(s, natsOptions_SetMaxPendingBytes(opts, pendingBytesLimit)); - if (s != NATS_OK) - FAIL("Unable to create options for test AsyncErrHandler"); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect to NATS: "); - testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - - _startMicroservice(&m, nc, &cfg, NULL, 0, &arg); - - test("Test microservice is running: "); - testCond(!microService_IsStopped(m)) - - test("Add test endpoint: "); - testCond(NULL == micro_add_endpoint(&ep, m, NULL, &ep_cfg, true)); - - natsMutex_Lock(arg.m); - arg.status = NATS_OK; - natsMutex_Unlock(arg.m); - - test("Cause an error by sending too many messages: "); - for (i=0; - (s == NATS_OK) && (i < (pendingBytesLimit + 100)); i+=data_len) //increment by 10 (message size) each iteration - { - s = natsConnection_Publish(nc, "async_test", msg, data_len); - } - testCond((NATS_OK == s) - && (NATS_OK == natsConnection_Flush(nc))); - - test("Wait for async err callback: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 1000); - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && arg.closed && (arg.status == NATS_SLOW_CONSUMER)); - - microService_Destroy(m); - _waitForMicroservicesAllDone(&arg); - - test("Destroy the test connection: "); - natsConnection_Destroy(nc); - testCond(NATS_OK == _waitForConnClosed(&arg)); - - natsOptions_Destroy(opts); - _destroyDefaultThreadArgs(&arg); - _stopServer(serverPid); -} - -#if defined(NATS_HAS_STREAMING) - -static int -_roundUp(int val) -{ - return ((val + (MEMALIGN-1))/MEMALIGN)*MEMALIGN; -} - -static void -test_StanPBufAllocator(void) -{ - natsPBufAllocator *a = NULL; - natsStatus s; - char *ptr1; - char *ptr2; - char *ptr3; - char *ptr4; - char *oldBuf; - int oldCap; - int expectedProtoSize; - int expectedOverhead; - int expectedUsed; - int expectedRemaining; - int expectedCap; - int prevUsed; - - test("Create: "); - s = natsPBufAllocator_Create(&a, 10, 2); - expectedProtoSize = MEMALIGN + _roundUp(10); - expectedOverhead = (MEMALIGN * 2) + 2 + (2 * (MEMALIGN-1)); - testCond((s == NATS_OK) - && (a->protoSize == expectedProtoSize) - && (a->overhead == expectedOverhead) - && (a->base.alloc != NULL) - && (a->base.free != NULL) - && (a->base.allocator_data == a)); - - test("Prepare: "); - natsPBufAllocator_Prepare(a, 20); - expectedCap = expectedProtoSize + expectedOverhead + 20; - testCond((a->buf != NULL) - && (a->cap == expectedCap) - && (a->remaining == a->cap) - && (a->used == 0)); - - test("Alloc 1: "); - ptr1 = (char*) a->base.alloc((void*)a, 10); - expectedUsed = MEMALIGN + _roundUp(10); - expectedRemaining = expectedCap - expectedUsed; - testCond((ptr1 != NULL) - && ((ptr1-MEMALIGN) == a->buf) - && ((ptr1-MEMALIGN)[0] == '0') - && (a->used == expectedUsed) - && (a->remaining == expectedRemaining)); - - test("Alloc 2: "); - ptr2 = (char*) a->base.alloc((void*)a, 5); - prevUsed = expectedUsed; - expectedUsed += MEMALIGN + _roundUp(5); - expectedRemaining = expectedCap - expectedUsed; - testCond((ptr2 != ptr1) - && ((ptr2-MEMALIGN) == (a->buf + prevUsed)) - && ((ptr2-MEMALIGN)[0] == '0') - && (a->used == expectedUsed) - && (a->remaining == expectedRemaining)); - - test("Alloc 3: "); - ptr3 = (char*) a->base.alloc((void*)a, 3); - prevUsed = expectedUsed; - expectedUsed += MEMALIGN + _roundUp(3); - expectedRemaining = expectedCap - expectedUsed; - testCond((ptr3 != ptr2) - && ((ptr3-MEMALIGN) == (a->buf + prevUsed)) - && ((ptr3-MEMALIGN)[0] == '0') - && (a->used == expectedUsed) - && (a->remaining == expectedRemaining)); - - test("Alloc 4: "); - ptr4 = (char*) a->base.alloc((void*)a, 50); - testCond((ptr4 != ptr3) - && (((ptr4-MEMALIGN) < a->buf) || ((ptr4-MEMALIGN) > (a->buf+a->cap))) - && ((ptr4-MEMALIGN)[0] == '1') - && (a->used == expectedUsed) - && (a->remaining == expectedRemaining)); - - // Free out of order, just make sure it does not crash - // and valgrind will make sure that we freed ptr4. - test("Free 2: "); - a->base.free((void*) a, (void*) ptr2); - testCond(1); - - test("Free 1: "); - a->base.free((void*) a, (void*) ptr1); - testCond(1); - - test("Free 4: "); - a->base.free((void*) a, (void*) ptr4); - testCond(1); - - test("Free 3: "); - a->base.free((void*) a, (void*) ptr3); - testCond(1); - - // Call prepare again with smaller buffer, buf should - // remain same, but used/remaining should be updated. - oldBuf = a->buf; - oldCap = a->cap; - test("Prepare with smaller buffer: "); - natsPBufAllocator_Prepare(a, 5); - testCond((a->buf == oldBuf) - && (a->cap == oldCap) - && (a->remaining == a->cap) - && (a->used == 0)); - - test("Prepare requires expand: "); - natsPBufAllocator_Prepare(a, 100); - // Realloc may or may not make a->buf be different... - expectedCap = expectedProtoSize + expectedOverhead + 100; - testCond((a->buf != NULL) - && (a->cap == expectedCap) - && (a->remaining == a->cap) - && (a->used == 0)); - - test("Destroy: "); - natsPBufAllocator_Destroy(a); - testCond(1); -} - -static void -_stanConnLostCB(stanConnection *sc, const char *errorTxt, void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - arg->closed = true; - arg->status = NATS_OK; - if ((arg->string != NULL) && (strcmp(errorTxt, arg->string) != 0)) - arg->status = NATS_ERR; - natsCondition_Signal(arg->c); - natsMutex_Unlock(arg->m); -} - -static void -test_StanConnOptions(void) -{ - natsStatus s; - stanConnOptions *opts = NULL; - stanConnOptions *clone= NULL; - natsOptions *no = NULL; - - test("Create option: "); - s = stanConnOptions_Create(&opts); - testCond(s == NATS_OK); - - test("Has default values: "); - testCond( - (opts->connTimeout == STAN_CONN_OPTS_DEFAULT_CONN_TIMEOUT) && - (opts->connectionLostCB == stanConn_defaultConnLostHandler) && - (opts->connectionLostCBClosure == NULL) && - (strcmp(opts->discoveryPrefix, STAN_CONN_OPTS_DEFAULT_DISCOVERY_PREFIX) == 0) && - (opts->maxPubAcksInFlightPercentage == STAN_CONN_OPTS_DEFAULT_MAX_PUB_ACKS_INFLIGHT_PERCENTAGE) && - (opts->maxPubAcksInflight == STAN_CONN_OPTS_DEFAULT_MAX_PUB_ACKS_INFLIGHT) && - (opts->ncOpts == NULL) && - (opts->pingInterval == STAN_CONN_OPTS_DEFAULT_PING_INTERVAL) && - (opts->pingMaxOut == STAN_CONN_OPTS_DEFAULT_PING_MAX_OUT) && - (opts->pubAckTimeout == STAN_CONN_OPTS_DEFAULT_PUB_ACK_TIMEOUT) && - (opts->url == NULL)); - - test("Check invalid connection wait: "); - s = stanConnOptions_SetConnectionWait(opts, -10); - if (s != NATS_OK) - s = stanConnOptions_SetConnectionWait(opts, 0); - testCond(s != NATS_OK); - nats_clearLastError(); - - test("Check invalid discovery prefix: "); - s = stanConnOptions_SetDiscoveryPrefix(opts, NULL); - if (s != NATS_OK) - s = stanConnOptions_SetDiscoveryPrefix(opts, ""); - testCond(s != NATS_OK); - nats_clearLastError(); - - test("Check invalid max pub acks: "); - s = stanConnOptions_SetMaxPubAcksInflight(opts, -1, 1); - if (s != NATS_OK) - s = stanConnOptions_SetMaxPubAcksInflight(opts, 0, 1); - if (s != NATS_OK) - s = stanConnOptions_SetMaxPubAcksInflight(opts, 10, -1); - if (s != NATS_OK) - s = stanConnOptions_SetMaxPubAcksInflight(opts, 10, 0); - if (s != NATS_OK) - s = stanConnOptions_SetMaxPubAcksInflight(opts, 10, 2); - testCond(s != NATS_OK); - nats_clearLastError(); - - test("Check invalid pings: "); - s = stanConnOptions_SetPings(opts, -1, 10); - if (s != NATS_OK) - s = stanConnOptions_SetPings(opts, 0, 10); - if (s != NATS_OK) - s = stanConnOptions_SetPings(opts, 1, -1); - if (s != NATS_OK) - s = stanConnOptions_SetPings(opts, 1, 0); - if (s != NATS_OK) - s = stanConnOptions_SetPings(opts, 1, 1); - testCond(s != NATS_OK); - nats_clearLastError(); - - test("Check invalid pub ack wait: "); - s = stanConnOptions_SetPubAckWait(opts, -1); - if (s != NATS_OK) - s = stanConnOptions_SetPubAckWait(opts, 0); - testCond(s != NATS_OK); - nats_clearLastError(); - - test("Set values: "); - s = stanConnOptions_SetConnectionWait(opts, 10000); - IFOK(s, stanConnOptions_SetDiscoveryPrefix(opts, "myPrefix")); - IFOK(s, stanConnOptions_SetMaxPubAcksInflight(opts, 10, (float) 0.8)); - IFOK(s, stanConnOptions_SetPings(opts, 1, 10)); - IFOK(s, stanConnOptions_SetPubAckWait(opts, 2000)); - IFOK(s, stanConnOptions_SetURL(opts, "nats://me:1")); - IFOK(s, stanConnOptions_SetConnectionLostHandler(opts, _stanConnLostCB, (void*) 1)); - testCond((s == NATS_OK) && - (opts->connTimeout == 10000) && - (strcmp(opts->discoveryPrefix, "myPrefix") == 0) && - (opts->maxPubAcksInFlightPercentage == (float) 0.8) && - (opts->maxPubAcksInflight == 10) && - (opts->pingInterval == 1) && - (opts->pingMaxOut == 10) && - (opts->pubAckTimeout == 2000) && - (strcmp(opts->url, "nats://me:1") == 0) && - (opts->connectionLostCB == _stanConnLostCB) && - (opts->connectionLostCBClosure == (void*) 1) - ); - - test("Set NATS options: "); - s = natsOptions_Create(&no); - IFOK(s, natsOptions_SetMaxPendingMsgs(no, 1000)); - IFOK(s, stanConnOptions_SetNATSOptions(opts, no)); - // change value from no after setting to stan opts - // check options were cloned. - IFOK(s, natsOptions_SetMaxPendingMsgs(no, 2000)); - testCond((s == NATS_OK) && - (opts->ncOpts != NULL) && // set - (opts->ncOpts != no) && // not a reference - (opts->ncOpts->maxPendingMsgs == 1000) // original value - ); - - test("Check clone: "); - s = stanConnOptions_clone(&clone, opts); - // Change values from original, check that clone - // keeps original values. - IFOK(s, stanConnOptions_SetConnectionWait(opts, 3000)); - IFOK(s, stanConnOptions_SetDiscoveryPrefix(opts, "xxxxx")); - IFOK(s, stanConnOptions_SetMaxPubAcksInflight(opts, 100, (float) 0.2)); - IFOK(s, stanConnOptions_SetPings(opts, 10, 20)); - IFOK(s, stanConnOptions_SetPubAckWait(opts, 3000)); - IFOK(s, stanConnOptions_SetURL(opts, "nats://metoo:1")); - IFOK(s, stanConnOptions_SetConnectionLostHandler(opts, NULL, NULL)); - IFOK(s, stanConnOptions_SetNATSOptions(opts, NULL)); - testCond((s == NATS_OK) && - (clone != opts) && - (clone->connTimeout == 10000) && - (strcmp(clone->discoveryPrefix, "myPrefix") == 0) && - (clone->maxPubAcksInFlightPercentage == (float) 0.8) && - (clone->maxPubAcksInflight == 10) && - (clone->pingInterval == 1) && - (clone->pingMaxOut == 10) && - (clone->pubAckTimeout == 2000) && - (strcmp(clone->url, "nats://me:1") == 0) && - (clone->connectionLostCB == _stanConnLostCB) && - (clone->connectionLostCBClosure == (void*) 1) && - (clone->ncOpts != NULL) && - (clone->ncOpts != no) && - (clone->ncOpts->maxPendingMsgs == 1000) - ); - - test("Check cb and NATS options can be set to NULL: "); - testCond( - (opts->ncOpts == NULL) && - (opts->connectionLostCB == NULL) && - (opts->connectionLostCBClosure == NULL)); - - test("Check URL can be set to NULL: "); - s = stanConnOptions_SetURL(opts, NULL); - testCond(s == NATS_OK); - - test("Check clone ok after destroy original: "); - stanConnOptions_Destroy(opts); - testCond((s == NATS_OK) && - (clone->connTimeout == 10000) && - (strcmp(clone->discoveryPrefix, "myPrefix") == 0) && - (clone->maxPubAcksInFlightPercentage == (float) 0.8) && - (clone->maxPubAcksInflight == 10) && - (clone->pingInterval == 1) && - (clone->pingMaxOut == 10) && - (clone->pubAckTimeout == 2000) && - (strcmp(clone->url, "nats://me:1") == 0) && - (clone->connectionLostCB == _stanConnLostCB) && - (clone->connectionLostCBClosure == (void*) 1) && - (clone->ncOpts != NULL) && - (clone->ncOpts != no) && - (clone->ncOpts->maxPendingMsgs == 1000) - ); - - natsOptions_Destroy(no); - stanConnOptions_Destroy(clone); -} - -static void -test_StanSubOptions(void) -{ - natsStatus s; - stanSubOptions *opts = NULL; - stanSubOptions *clone= NULL; - int64_t now = 0; - - test("Create Options: "); - s = stanSubOptions_Create(&opts); - testCond(s == NATS_OK); - - test("Default values: "); - testCond( - (opts->ackWait == STAN_SUB_OPTS_DEFAULT_ACK_WAIT) && - (opts->durableName == NULL) && - (opts->manualAcks == false) && - (opts->maxInflight == STAN_SUB_OPTS_DEFAULT_MAX_INFLIGHT) && - (opts->startAt == PB__START_POSITION__NewOnly) && - (opts->startSequence == 0) && - (opts->startTime == 0) - ); - - test("Check invalid ackwait: "); - s = stanSubOptions_SetAckWait(opts, -1); - if (s != NATS_OK) - s = stanSubOptions_SetAckWait(opts, 0); - testCond(s != NATS_OK); - nats_clearLastError(); - - test("Check invalid maxinflight: "); - s = stanSubOptions_SetMaxInflight(opts, -1); - if (s != NATS_OK) - s = stanSubOptions_SetMaxInflight(opts, 0); - testCond(s != NATS_OK); - nats_clearLastError(); - - test("Check invalid start seq: "); - s = stanSubOptions_StartAtSequence(opts, 0); - testCond(s != NATS_OK); - nats_clearLastError(); - - test("Check invalid start time: "); - s = stanSubOptions_StartAtTime(opts, -1); - testCond(s != NATS_OK); - nats_clearLastError(); - - test("Check invalid start time: "); - s = stanSubOptions_StartAtTimeDelta(opts, -1); - testCond(s != NATS_OK); - nats_clearLastError(); - - test("Check set values: "); - s = stanSubOptions_SetAckWait(opts, 1000); - IFOK(s, stanSubOptions_SetDurableName(opts, "myDurable")); - IFOK(s, stanSubOptions_SetManualAckMode(opts, true)); - IFOK(s, stanSubOptions_SetMaxInflight(opts, 200)); - testCond((s == NATS_OK) && - (opts->ackWait == 1000) && - (strcmp(opts->durableName, "myDurable") == 0) && - (opts->manualAcks == true) && - (opts->maxInflight == 200) - ); - - now = nats_Now(); - test("Check start at time delta: "); - s = stanSubOptions_StartAtTimeDelta(opts, 20000); - testCond((s == NATS_OK) && - (opts->startAt == PB__START_POSITION__TimeDeltaStart) && - ((opts->startTime >= now-20200) && - (opts->startTime <= now-19800)) - ); - - test("Check start at time: "); - s = stanSubOptions_StartAtTime(opts, 1234567890); - testCond((s == NATS_OK) && - (opts->startAt == PB__START_POSITION__TimeDeltaStart) && - (opts->startTime == 1234567890) - ); - - test("Check start at seq: "); - s = stanSubOptions_StartAtSequence(opts, 100); - testCond((s == NATS_OK) && - (opts->startAt == PB__START_POSITION__SequenceStart) && - (opts->startSequence == 100) - ); - - test("Check deliver all avail: "); - s = stanSubOptions_DeliverAllAvailable(opts); - testCond((s == NATS_OK) && (opts->startAt == PB__START_POSITION__First)); - - test("Check clone: "); - s = stanSubOptions_clone(&clone, opts); - // Change values of opts to show that this does not affect - // the clone - IFOK(s, stanSubOptions_SetAckWait(opts, 20000)); - IFOK(s, stanSubOptions_SetDurableName(opts, NULL)); - IFOK(s, stanSubOptions_SetManualAckMode(opts, false)); - IFOK(s, stanSubOptions_SetMaxInflight(opts, 4000)); - IFOK(s, stanSubOptions_StartAtSequence(opts, 100)); - testCond((s == NATS_OK) && - (clone != opts) && - (clone->ackWait == 1000) && - (strcmp(clone->durableName, "myDurable") == 0) && - (clone->manualAcks == true) && - (clone->maxInflight == 200) && - (clone->startAt == PB__START_POSITION__First) - ); - - test("Check clone ok after destroy original: "); - stanSubOptions_Destroy(opts); - testCond((s == NATS_OK) && - (clone != opts) && - (clone->ackWait == 1000) && - (strcmp(clone->durableName, "myDurable") == 0) && - (clone->manualAcks == true) && - (clone->maxInflight == 200) && - (clone->startAt == PB__START_POSITION__First) - ); - - stanSubOptions_Destroy(clone); -} - -static void -test_StanMsg(void) -{ - test("GetSequence with NULL msg: "); - testCond(stanMsg_GetSequence(NULL) == 0); - - test("GetData with NULL msg: "); - testCond(stanMsg_GetData(NULL) == NULL); - - test("GetDataLength with NULL msg: "); - testCond(stanMsg_GetDataLength(NULL) == 0); - - test("GetTimestamp with NULL msg: "); - testCond(stanMsg_GetTimestamp(NULL) == 0); - - test("IsRedelivered with NULL msg: "); - testCond(stanMsg_IsRedelivered(NULL) == false); - - stanMsg_Destroy(NULL); -} - -static void -test_StanServerNotReachable(void) -{ - natsStatus s; - stanConnection *sc = NULL; - stanConnOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - int64_t now = 0; - int64_t elapsed = 0; - - s = stanConnOptions_Create(&opts); - IFOK(s, stanConnOptions_SetURL(opts, "nats://127.0.0.1:4222")); - IFOK(s, stanConnOptions_SetConnectionWait(opts, 250)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("Connect fails if no streaming server running: "); - now = nats_Now(); - s = stanConnection_Connect(&sc, clusterName, clientName, opts); - elapsed = nats_Now()-now; - if (serverVersionAtLeast(2, 2, 0)) - { - testCond((s == NATS_NO_RESPONDERS) && - (strstr(nats_GetLastError(NULL), STAN_ERR_CONNECT_REQUEST_NO_RESP) != NULL)); - } - else - { - testCond((s == NATS_TIMEOUT) && - (strstr(nats_GetLastError(NULL), STAN_ERR_CONNECT_REQUEST_TIMEOUT) != NULL) && - (elapsed < 2000)); - } - - stanConnOptions_Destroy(opts); - - _stopServer(serverPid); -} - -static void -test_StanBasicConnect(void) -{ - natsStatus s; - stanConnection *sc = NULL; - natsPid pid = NATS_INVALID_PID; - stanConnOptions *opts = NULL; - natsOptions *nopts = NULL; - - pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Basic connect: "); - s = stanConnection_Connect(&sc, clusterName, clientName, NULL); - testCond(s == NATS_OK); - - test("Connection close: "); - s = stanConnection_Close(sc); - testCond(s == NATS_OK); - - test("Connection double close: "); - s = stanConnection_Close(sc); - testCond(s == NATS_OK); - - stanConnection_Destroy(sc); - sc = NULL; - - _stopServer(pid); - pid = NATS_INVALID_PID; - - pid = _startStreamingServer("nats://127.0.0.1:4223", "-p 4223", true); - CHECK_SERVER_STARTED(pid); - - test("Connect with non default stan URL: "); - s = stanConnOptions_Create(&opts); - IFOK(s, stanConnOptions_SetURL(opts, "nats://127.0.0.1:4223")); - IFOK(s, stanConnection_Connect(&sc, clusterName, clientName, opts)); - testCond(s == NATS_OK); - - stanConnection_Destroy(sc); - sc = NULL; - - test("stan URL takes precedence: "); - s = natsOptions_Create(&nopts); - IFOK(s, natsOptions_SetURL(nopts, "nats://127.0.0.1:4224")); // wrong URL - IFOK(s, stanConnOptions_SetNATSOptions(opts, nopts)); - IFOK(s, stanConnection_Connect(&sc, clusterName, clientName, opts)); - // Should connect because it should use the one from stanConnOptions_SetURL. - testCond(s == NATS_OK); - - stanConnection_Destroy(sc); - sc = NULL; - - test("If no stan URL set, uses NATS URL: "); - s = stanConnOptions_SetURL(opts, NULL); - IFOK(s, natsOptions_SetURL(nopts, "nats://127.0.0.1:4223")); - IFOK(s, stanConnOptions_SetNATSOptions(opts, nopts)); - IFOK(s, stanConnection_Connect(&sc, clusterName, clientName, opts)); - // Should connect because it should use the one from StanURL. - testCond(s == NATS_OK); - - stanConnection_Destroy(sc); - stanConnOptions_Destroy(opts); - natsOptions_Destroy(nopts); - - _stopServer(pid); -} - -static void -test_StanConnectError(void) -{ - natsStatus s; - stanConnection *sc = NULL; - stanConnection *sc2 = NULL; - natsPid nPid = NATS_INVALID_PID; - natsPid sPid = NATS_INVALID_PID; - stanConnOptions *opts = NULL; - - nPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(nPid); - - sPid = _startStreamingServer("nats://127.0.0.1:4222", "-ns nats://127.0.0.1:4222", true); - CHECK_SERVER_STARTED(sPid); - - test("Check connect response error: "); - s = stanConnection_Connect(&sc, clusterName, clientName, NULL); - IFOK(s, stanConnection_Connect(&sc2, clusterName, clientName, NULL)); - testCond((s == NATS_ERR) && - (strstr(nats_GetLastError(NULL), "clientID already registered") != NULL)); - - test("Check wrong discovery prefix: "); - s = stanConnOptions_Create(&opts); - IFOK(s, stanConnOptions_SetDiscoveryPrefix(opts, "wrongprefix")); - IFOK(s, stanConnOptions_SetConnectionWait(opts, 500)); - IFOK(s, stanConnection_Connect(&sc2, clusterName, "newClient", opts)); - testCond(serverVersionAtLeast(2, 2, 0) ? (s == NATS_NO_RESPONDERS) : (s == NATS_TIMEOUT)); - - stanConnection_Destroy(sc); - stanConnOptions_Destroy(opts); - - _stopServer(sPid); - _stopServer(nPid); -} - - -static void -test_StanBasicPublish(void) -{ - natsStatus s; - stanConnection *sc = NULL; - natsPid pid = NATS_INVALID_PID; - - pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Basic publish: "); - s = stanConnection_Connect(&sc, clusterName, clientName, NULL); - IFOK(s, stanConnection_Publish(sc, "foo", (const void*) "hello", 5)); - testCond(s == NATS_OK); - - stanConnection_Destroy(sc); - - if (valgrind) - nats_Sleep(900); - - _stopServer(pid); -} - -static void -_stanPubAckHandler(const char *guid, const char *errTxt, void* closure) -{ - struct threadArg *args= (struct threadArg*) closure; - - natsMutex_Lock(args->m); - args->status = NATS_OK; - if (errTxt != NULL) - { - if ((args->string == NULL) || (strstr(errTxt, args->string) == NULL)) - args->status = NATS_ERR; - } - else if (args->string != NULL) - { - args->status = NATS_ERR; - } - args->msgReceived = true; - natsCondition_Signal(args->c); - natsMutex_Unlock(args->m); -} - -static void -test_StanBasicPublishAsync(void) -{ - natsStatus s; - stanConnection *sc = NULL; - natsPid pid = NATS_INVALID_PID; - struct threadArg args; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Basic publish async: "); - s = stanConnection_Connect(&sc, clusterName, clientName, NULL); - IFOK(s, stanConnection_PublishAsync(sc, "foo", (const void*) "hello", 5, - _stanPubAckHandler, (void*) &args)); - testCond(s == NATS_OK); - - test("PubAck callback report no error: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.msgReceived) - s = natsCondition_TimedWait(args.c, args.m, 2000); - IFOK(s, args.status); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - stanConnection_Destroy(sc); - - _destroyDefaultThreadArgs(&args); - - if (valgrind) - nats_Sleep(900); - - _stopServer(pid); -} - -static void -test_StanPublishTimeout(void) -{ - natsStatus s; - stanConnection *sc = NULL; - struct threadArg args; - natsPid nPid = NATS_INVALID_PID; - natsPid sPid = NATS_INVALID_PID; - stanConnOptions *opts = NULL; - - s = _createDefaultThreadArgsForCbTests(&args); - IFOK(s, stanConnOptions_Create(&opts)); - IFOK(s, stanConnOptions_SetPubAckWait(opts, 50)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - nPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(nPid); - - sPid = _startStreamingServer("nats://127.0.0.1:4222", "-ns nats://127.0.0.1:4222", true); - CHECK_SERVER_STARTED(sPid); - - // First connect, then once that's done, shutdown streaming server - s = stanConnection_Connect(&sc, clusterName, clientName, opts); - - _stopServer(sPid); - - if (s != NATS_OK) - { - _stopServer(nPid); - FAIL("Not able to create connection for this test"); - } - - args.string = STAN_ERR_PUB_ACK_TIMEOUT; - - test("Check publish async timeout"); - s = stanConnection_PublishAsync(sc, "foo", (const void*) "hello", 5, - _stanPubAckHandler, (void*) &args); - testCond(s == NATS_OK); - - test("PubAck callback report pub ack timeout error: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.msgReceived) - s = natsCondition_TimedWait(args.c, args.m, 2000); - IFOK(s, args.status); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - // Speed up test by closing stan's nc connection to avoid timing out on conn close - stanConnClose(sc, false); - - stanConnOptions_Destroy(opts); - stanConnection_Destroy(sc); - - _destroyDefaultThreadArgs(&args); - - _stopServer(nPid); -} - -static void -_stanPublishAsyncThread(void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - int i; - - for (i = 0; i < 10; i++) - stanConnection_PublishAsync(args->sc, "foo", (const void*)"hello", 5, NULL, NULL); -} - -static void -_stanPublishSyncThread(void *closure) -{ - stanConnection *sc = (stanConnection*) closure; - - stanConnection_Publish(sc, "foo", (const void*)"hello", 5); -} - -static void -test_StanPublishMaxAcksInflight(void) -{ - natsStatus s; - stanConnection *sc1 = NULL; - stanConnection *sc2 = NULL; - struct threadArg args; - natsPid nPid = NATS_INVALID_PID; - natsPid sPid = NATS_INVALID_PID; - stanConnOptions *opts = NULL; - natsThread *t = NULL; - natsThread *pts[10]; - int i; - natsConnection *nc = NULL; - - for (i=0;i<10;i++) - pts[i] = NULL; - - s = _createDefaultThreadArgsForCbTests(&args); - IFOK(s, stanConnOptions_Create(&opts)); - IFOK(s, stanConnOptions_SetMaxPubAcksInflight(opts, 5, 1.0)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - nPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(nPid); - - sPid = _startStreamingServer("nats://127.0.0.1:4222", "-ns nats://127.0.0.1:4222", true); - CHECK_SERVER_STARTED(sPid); - - // First connect, then once that's done, shutdown streaming server - s = stanConnection_Connect(&sc1, clusterName, clientName, opts); - IFOK(s, stanConnection_Connect(&sc2, clusterName, "otherClient", opts)); - if (s != NATS_OK) - { - stanConnection_Destroy(sc1); - stanConnection_Destroy(sc2); - _stopServer(sPid); - _stopServer(nPid); - FAIL("Not able to create connection for this test"); - } - - _stopServer(sPid); - - // grap nc first - natsMutex_Lock(sc1->mu); - nc = sc1->nc; - natsMutex_Unlock(sc1->mu); - - test("Check max inflight: "); - args.sc = sc1; - // Retain the connection - stanConn_retain(sc1); - s = natsThread_Create(&t, _stanPublishAsyncThread, (void*) &args); - if (s == NATS_OK) - { - int i = 0; - // Check that pubAckMap size is never greater than 5. - for (i=0; (s == NATS_OK) && (i<10); i++) - { - nats_Sleep(100); - natsMutex_Lock(sc1->pubAckMu); - s = (natsStrHash_Count(sc1->pubAckMap) <= 5 ? NATS_OK : NATS_ERR); - natsMutex_Unlock(sc1->pubAckMu); - } - } - testCond(s == NATS_OK); - - test("Close unblock: "); - natsConn_close(nc); - nc = NULL; - stanConnection_Destroy(sc1); - natsThread_Join(t); - natsThread_Destroy(t); - stanConn_release(sc1); - testCond(s == NATS_OK); - - // Repeat test with sync publishers - - // grap nc first - natsMutex_Lock(sc2->mu); - nc = sc2->nc; - natsMutex_Unlock(sc2->mu); - - test("Check max inflight: "); - // Retain the connection - stanConn_retain(sc2); - for (i=0; (s == NATS_OK) && (i<10); i++) - s = natsThread_Create(&(pts[i]), _stanPublishSyncThread, (void*) sc2); - if (s == NATS_OK) - { - int i = 0; - // Check that pubAckMap size is never greater than 5. - for (i=0; (s == NATS_OK) && (i<10); i++) - { - nats_Sleep(100); - natsMutex_Lock(sc2->pubAckMu); - s = (natsStrHash_Count(sc2->pubAckMap) <= 5 ? NATS_OK : NATS_ERR); - natsMutex_Unlock(sc2->pubAckMu); - } - } - testCond(s == NATS_OK); - - test("Close unblock: "); - natsConn_close(nc); - nc = NULL; - stanConnection_Destroy(sc2); - for (i = 0; i<10; i++) - { - if (pts[i] != NULL) - { - natsThread_Join(pts[i]); - natsThread_Destroy(pts[i]); - } - } - stanConn_release(sc2); - testCond(s == NATS_OK); - - stanConnOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&args); - - _stopServer(nPid); -} - -static void -_dummyStanMsgHandler(stanConnection *sc, stanSubscription *sub, const char *channel, - stanMsg *msg, void* closure) -{ - stanMsg_Destroy(msg); -} - -static void -_stanMsgHandlerBumpSum(stanConnection *sc, stanSubscription *sub, const char *channel, - stanMsg *msg, void* closure) -{ - struct threadArg *args = (struct threadArg*) closure; - - natsMutex_Lock(args->m); - if (!stanMsg_IsRedelivered(msg)) - args->sum++; - else - args->redelivered++; - natsCondition_Broadcast(args->c); - natsMutex_Unlock(args->m); - - stanMsg_Destroy(msg); -} - -static void -test_StanBasicSubscription(void) -{ - natsStatus s; - stanConnection *sc = NULL; - stanSubscription *sub = NULL; - stanSubscription *subf = NULL; - natsPid pid = NATS_INVALID_PID; - - pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - s = stanConnection_Connect(&sc, clusterName, clientName, NULL); - if (s != NATS_OK) - { - _stopServer(pid); - FAIL("Unable to create connection for this test"); - } - - test("Basic subscibe: "); - s = stanConnection_Subscribe(&sub, sc, "foo", _dummyStanMsgHandler, NULL, NULL); - testCond(s == NATS_OK); - - test("Close connection: ") - s = stanConnection_Close(sc); - testCond(s == NATS_OK); - - test("Subscribe should fail after conn closed: "); - s = stanConnection_Subscribe(&subf, sc, "foo", _dummyStanMsgHandler, NULL, NULL); - testCond(s == NATS_CONNECTION_CLOSED); - - test("Subscribe should fail after conn closed: "); - s = stanConnection_QueueSubscribe(&subf, sc, "foo", "bar", _dummyStanMsgHandler, NULL, NULL); - testCond(s == NATS_CONNECTION_CLOSED); - - stanSubscription_Destroy(sub); - stanConnection_Destroy(sc); - - if (valgrind) - nats_Sleep(900); - - _stopServer(pid); -} - -static void -test_StanSubscriptionCloseAndUnsubscribe(void) -{ - natsStatus s; - stanConnection *sc = NULL; - stanSubscription *sub = NULL; - stanSubscription *sub2 = NULL; - natsPid pid = NATS_INVALID_PID; - natsPid spid = NATS_INVALID_PID; - char *cs = NULL; - stanConnOptions *opts = NULL; - - s = stanConnOptions_Create(&opts); - IFOK(s, stanConnOptions_SetConnectionWait(opts, 250)); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - pid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - spid = _startStreamingServer("nats://127.0.0.1:4222", "-ns nats://127.0.0.1:4222", true); - CHECK_SERVER_STARTED(spid); - - s = stanConnection_Connect(&sc, clusterName, clientName, opts); - if (s != NATS_OK) - { - _stopServer(spid); - _stopServer(pid); - FAIL("Unable to create connection for this test"); - } - - test("Unsubscribe: "); - s = stanConnection_Subscribe(&sub, sc, "foo", _dummyStanMsgHandler, NULL, NULL); - IFOK(s, stanSubscription_Unsubscribe(sub)); - testCond(s == NATS_OK); - - stanSubscription_Destroy(sub); - sub = NULL; - - test("Close: "); - s = stanConnection_Subscribe(&sub, sc, "foo", _dummyStanMsgHandler, NULL, NULL); - IFOK(s, stanSubscription_Close(sub)); - testCond(s == NATS_OK); - - stanSubscription_Destroy(sub); - sub = NULL; - - test("Close not supported: "); - // Simulate that we connected to an older server - natsMutex_Lock(sc->mu); - cs = sc->subCloseRequests; - sc->subCloseRequests = NULL; - natsMutex_Unlock(sc->mu); - s = stanConnection_Subscribe(&sub, sc, "foo", _dummyStanMsgHandler, NULL, NULL); - IFOK(s, stanSubscription_Close(sub)); - testCond((s == NATS_NO_SERVER_SUPPORT) && - (strstr(nats_GetLastError(NULL), STAN_ERR_SUB_CLOSE_NOT_SUPPORTED) != NULL)); - - stanSubscription_Destroy(sub); - sub = NULL; - - natsMutex_Lock(sc->mu); - sc->subCloseRequests = cs; - natsMutex_Unlock(sc->mu); - - test("Close/Unsub timeout: "); - s = stanConnection_Subscribe(&sub, sc, "foo", _dummyStanMsgHandler, NULL, NULL); - IFOK(s, stanConnection_Subscribe(&sub2, sc, "foo", _dummyStanMsgHandler, NULL, NULL)); - - // Stop the serer - _stopServer(spid); - - if (s == NATS_OK) - { - s = stanSubscription_Close(sub); - if (s != NATS_OK) - s = stanSubscription_Unsubscribe(sub2); - } - if (serverVersionAtLeast(2, 2, 0)) - { - testCond((s == NATS_NO_RESPONDERS) && - (strstr(nats_GetLastError(NULL), "no streaming server was listening") != NULL)); - } - else - { - testCond((s == NATS_TIMEOUT) && - (strstr(nats_GetLastError(NULL), "request timeout") != NULL)); - } - stanSubscription_Destroy(sub); - stanSubscription_Destroy(sub2); - - stanConnClose(sc, false); - stanConnection_Destroy(sc); - stanConnOptions_Destroy(opts); - - if (valgrind) - nats_Sleep(900); - - _stopServer(pid); -} - -static void -test_StanDurableSubscription(void) -{ - natsStatus s; - stanConnection *sc = NULL; - stanSubscription *dur = NULL; - stanSubOptions *opts = NULL; - natsPid pid = NATS_INVALID_PID; - struct threadArg args; - int i; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s != NATS_OK) - FAIL("Error setting up test"); - - pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - s = stanConnection_Connect(&sc, clusterName, clientName, NULL); - if (s != NATS_OK) - { - _stopServer(pid); - FAIL("Unable to create connection for this test"); - } - - test("Send some messages: "); - for (i=0; (s == NATS_OK) && (i<3); i++) - s = stanConnection_Publish(sc, "foo", (const void*) "hello", 5); - testCond(s == NATS_OK); - - test("Basic durable subscibe: "); - s = stanSubOptions_Create(&opts); - IFOK(s, stanSubOptions_SetDurableName(opts, "dur")); - IFOK(s, stanSubOptions_DeliverAllAvailable(opts)); - IFOK(s, stanConnection_Subscribe(&dur, sc, "foo", _stanMsgHandlerBumpSum, &args, opts)); - testCond(s == NATS_OK); - - test("Check 3 messages received: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum != 3)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - // Wait a bit to give a chance for the server to process acks. - nats_Sleep(500); - - test("Close connection: "); - s = stanConnection_Close(sc); - testCond(s == NATS_OK); - - stanSubscription_Destroy(dur); - dur = NULL; - stanConnection_Destroy(sc); - sc = NULL; - - test("Connect again: "); - s = stanConnection_Connect(&sc, clusterName, clientName, NULL); - testCond(s == NATS_OK); - - test("Send 2 more messages: "); - for (i=0; (s == NATS_OK) && (i<2); i++) - s = stanConnection_Publish(sc, "foo", (const void*) "hello", 5); - testCond(s == NATS_OK); - - test("Recreate durable with start seq 1: "); - s = stanSubOptions_StartAtSequence(opts, 1); - IFOK(s, stanConnection_Subscribe(&dur, sc, "foo", _stanMsgHandlerBumpSum, &args, opts)); - testCond(s == NATS_OK); - - test("Check 5 messages total are received: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum != 5)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - testCond(s == NATS_OK); - test("Check no redelivered: "); - testCond((s == NATS_OK) && (args.redelivered == 0)); - natsMutex_Unlock(args.m); - - stanSubscription_Destroy(dur); - stanSubOptions_Destroy(opts); - stanConnection_Destroy(sc); - - _destroyDefaultThreadArgs(&args); - - _stopServer(pid); -} - -static void -test_StanBasicQueueSubscription(void) -{ - natsStatus s; - stanConnection *sc = NULL; - stanSubscription *qsub1 = NULL; - stanSubscription *qsub2 = NULL; - stanSubscription *qsub3 = NULL; - stanSubOptions *opts = NULL; - natsPid pid = NATS_INVALID_PID; - struct threadArg args; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s != NATS_OK) - FAIL("Error setting up test"); - - pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - s = stanConnection_Connect(&sc, clusterName, clientName, NULL); - if (s != NATS_OK) - { - _stopServer(pid); - FAIL("Unable to create connection for this test"); - } - - test("Basic queue subscibe: "); - s = stanConnection_QueueSubscribe(&qsub1, sc, "foo", "bar", _stanMsgHandlerBumpSum, &args, NULL); - IFOK(s, stanConnection_QueueSubscribe(&qsub2, sc, "foo", "bar", _stanMsgHandlerBumpSum, &args, NULL)); - testCond(s == NATS_OK); - - // Test that durable and non durable queue subscribers with - // same name can coexist and they both receive the same message. - test("New durable queue sub with same queue name: "); - s = stanSubOptions_Create(&opts); - IFOK(s, stanSubOptions_SetDurableName(opts, "durable-queue-sub")); - IFOK(s, stanConnection_QueueSubscribe(&qsub3, sc, "foo", "bar", _stanMsgHandlerBumpSum, &args, opts)); - testCond(s == NATS_OK); - - test("Check published message ok: "); - s = stanConnection_Publish(sc, "foo", (const void*)"hello", 5); - testCond(s == NATS_OK); - - test("Check 1 message published is received once per group: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum != 2)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - stanSubscription_Destroy(qsub1); - stanSubscription_Destroy(qsub2); - stanSubscription_Destroy(qsub3); - stanSubOptions_Destroy(opts); - stanConnection_Destroy(sc); - - _destroyDefaultThreadArgs(&args); - - if (valgrind) - nats_Sleep(900); - - _stopServer(pid); -} - -static void -test_StanDurableQueueSubscription(void) -{ - natsStatus s; - stanConnection *sc = NULL; - stanSubscription *dur = NULL; - stanSubOptions *opts = NULL; - natsPid pid = NATS_INVALID_PID; - struct threadArg args; - int i; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s != NATS_OK) - FAIL("Error setting up test"); - - pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - s = stanConnection_Connect(&sc, clusterName, clientName, NULL); - if (s != NATS_OK) - { - _stopServer(pid); - FAIL("Unable to create connection for this test"); - } - - test("Send some messages: "); - for (i=0; (s == NATS_OK) && (i<3); i++) - s = stanConnection_Publish(sc, "foo", (const void*) "hello", 5); - testCond(s == NATS_OK); - - test("Basic durable subscibe: "); - s = stanSubOptions_Create(&opts); - IFOK(s, stanSubOptions_SetDurableName(opts, "dur")); - IFOK(s, stanSubOptions_DeliverAllAvailable(opts)); - IFOK(s, stanConnection_QueueSubscribe(&dur, sc, "foo", "bar", _stanMsgHandlerBumpSum, &args, opts)); - testCond(s == NATS_OK); - - test("Check 3 messages received: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum != 3)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - // Give a chance for the server to process those acks - nats_Sleep(500); - - test("Close connection: "); - s = stanConnection_Close(sc); - testCond(s == NATS_OK); - - stanSubscription_Destroy(dur); - dur = NULL; - stanConnection_Destroy(sc); - sc = NULL; - - test("Connect again: "); - s = stanConnection_Connect(&sc, clusterName, clientName, NULL); - testCond(s == NATS_OK); - - test("Send 2 more messages: "); - for (i=0; (s == NATS_OK) && (i<2); i++) - s = stanConnection_Publish(sc, "foo", (const void*) "hello", 5); - testCond(s == NATS_OK); - - test("Recreate durable with start seq 1: "); - s = stanSubOptions_StartAtSequence(opts, 1); - IFOK(s, stanConnection_QueueSubscribe(&dur, sc, "foo", "bar", _stanMsgHandlerBumpSum, &args, opts)); - testCond(s == NATS_OK); - - test("Check 5 messages total are received: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum != 5)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - testCond(s == NATS_OK); - test("Check no redelivered: "); - testCond((s == NATS_OK) && (args.redelivered == 0)); - natsMutex_Unlock(args.m); - - stanSubscription_Destroy(dur); - stanSubOptions_Destroy(opts); - stanConnection_Destroy(sc); - - _destroyDefaultThreadArgs(&args); - - _stopServer(pid); -} - -static void -_stanCheckRecvStanMsg(stanConnection *sc, stanSubscription *sub, const char *channel, - stanMsg *msg, void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - - natsMutex_Lock(args->m); - if (strcmp(channel, args->channel) != 0) - args->status = NATS_ERR; - if ((args->status == NATS_OK) && (strncmp(args->string, (char*) stanMsg_GetData(msg), strlen(args->string)) != 0)) - args->status = NATS_ERR; - if ((args->status == NATS_OK) && (stanMsg_GetDataLength(msg) != 5)) - args->status = NATS_ERR; - if ((args->status == NATS_OK) && (stanMsg_GetSequence(msg) == 0)) - args->status = NATS_ERR; - if ((args->status == NATS_OK) && (stanMsg_GetTimestamp(msg) == 0)) - args->status = NATS_ERR; - stanMsg_Destroy(msg); - args->done = true; - natsCondition_Signal(args->c); - natsMutex_Unlock(args->m); -} - -static void -test_StanCheckReceivedvMsg(void) -{ - natsStatus s; - stanConnection *sc = NULL; - stanSubscription *sub = NULL; - natsPid pid = NATS_INVALID_PID; - struct threadArg args; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s != NATS_OK) - FAIL("Error setting up test"); - args.channel = "foo"; - args.string = "hello"; - - pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - s = stanConnection_Connect(&sc, clusterName, clientName, NULL); - if (s != NATS_OK) - { - _stopServer(pid); - FAIL("Unable to create connection for this test"); - } - - test("Create sub: "); - s = stanConnection_Subscribe(&sub, sc, "foo", _stanCheckRecvStanMsg, (void*) &args, NULL); - testCond(s == NATS_OK); - - test("Send a message: "); - s = stanConnection_Publish(sc, "foo", (const void*) "hello", 5); - testCond(s == NATS_OK); - - test("Check message received: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.done) - s = natsCondition_TimedWait(args.c, args.m, 2000); - s = args.status; - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - stanSubscription_Destroy(sub); - stanConnection_Destroy(sc); - - _destroyDefaultThreadArgs(&args); - - _stopServer(pid); -} - -static void -_stanManualAck(stanConnection *sc, stanSubscription *sub, const char *channel, - stanMsg *msg, void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - natsStatus s; - - natsMutex_Lock(args->m); - // control 1 means auto-ack, so expect ack to fail - s = stanSubscription_AckMsg(sub, msg); - args->status = NATS_OK; - if ((args->control == 1) && - (s != NATS_ERR) && - (strstr(nats_GetLastError(NULL), STAN_ERR_MANUAL_ACK) == NULL)) - { - args->status = NATS_ERR; - } - else if ((args->control == 2) && (s != NATS_OK)) - { - args->status = NATS_ERR; - } - args->sum++; - natsCondition_Signal(args->c); - stanMsg_Destroy(msg); - natsMutex_Unlock(args->m); -} - -static void -_stanGetMsg(stanConnection *sc, stanSubscription *sub, const char *channel, - stanMsg *msg, void *closure) -{ - struct threadArg *args = (struct threadArg*) closure; - - natsMutex_Lock(args->m); - args->sMsg = msg; - args->msgReceived = true; - natsCondition_Signal(args->c); - natsMutex_Unlock(args->m); -} - -static void -test_StanSubscriptionAckMsg(void) -{ - natsStatus s; - stanConnection *sc = NULL; - stanSubscription *sub = NULL; - stanSubscription *sub2 = NULL; - natsPid pid = NATS_INVALID_PID; - stanSubOptions *opts = NULL; - struct threadArg args; - - s = _createDefaultThreadArgsForCbTests(&args); - IFOK(s, stanSubOptions_Create(&opts)); - IFOK(s, stanSubOptions_SetManualAckMode(opts, true)); - if (s != NATS_OK) - FAIL("Error setting up test"); - - pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - s = stanConnection_Connect(&sc, clusterName, clientName, NULL); - if (s != NATS_OK) - { - _stopServer(pid); - FAIL("Unable to create connection for this test"); - } - - test("Create sub with auto-ack: "); - args.control = 1; - s = stanConnection_Subscribe(&sub, sc, "foo", _stanManualAck, (void*) &args, NULL); - testCond(s == NATS_OK); - - test("Publish message: "); - s = stanConnection_Publish(sc, "foo", (const void*) "hello", 5); - testCond(s == NATS_OK); - - test("Check manual ack fails: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum != 1)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - if (s == NATS_OK) - s = args.status; - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - stanSubscription_Destroy(sub); - sub = NULL; - - natsMutex_Lock(args.m); - args.control = 2; - args.sum = 0; - natsMutex_Unlock(args.m); - - test("Create sub with manual ack: "); - s = stanConnection_Subscribe(&sub, sc, "foo", _stanManualAck, (void*) &args, opts); - testCond(s == NATS_OK); - - test("Publish message: "); - s = stanConnection_Publish(sc, "foo", (const void*) "hello", 5); - testCond(s == NATS_OK); - - test("Check manual ack ok: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum != 1)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - if (s == NATS_OK) - s = args.status; - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - stanSubscription_Destroy(sub); - sub = NULL; - - test("Create sub and get message: "); - s = stanConnection_Subscribe(&sub, sc, "foo", _stanGetMsg, (void*) &args, NULL); - IFOK(s, stanConnection_Publish(sc, "foo", (const void*) "hello", 5)); - if (s == NATS_OK) - { - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && !args.msgReceived) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - } - testCond(s == NATS_OK) - - test("Create sub with manual ack: "); - s = stanConnection_Subscribe(&sub2, sc, "foo", _dummyStanMsgHandler, NULL, opts); - testCond(s == NATS_OK); - - test("NULL Sub should fail: "); - s = stanSubscription_AckMsg(NULL, args.sMsg); - testCond(s == NATS_INVALID_ARG); - - test("NULL Msg should fail: "); - s = stanSubscription_AckMsg(sub2, NULL); - testCond(s == NATS_INVALID_ARG); - - test("Sub acking not own message fails: "); - s = stanSubscription_AckMsg(sub2, args.sMsg); - testCond((s == NATS_ILLEGAL_STATE) - && (nats_GetLastError(NULL) != NULL) - && (strstr(nats_GetLastError(NULL), STAN_ERR_SUB_NOT_OWNER) != NULL)); - - natsMutex_Lock(args.m); - if (args.sMsg != NULL) - stanMsg_Destroy(args.sMsg); - natsMutex_Unlock(args.m); - - stanSubscription_Destroy(sub); - stanSubscription_Destroy(sub2); - stanConnection_Destroy(sc); - stanSubOptions_Destroy(opts); - - _destroyDefaultThreadArgs(&args); - - if (valgrind) - nats_Sleep(900); - - _stopServer(pid); -} - -static void -test_StanPings(void) -{ - natsStatus s; - natsPid pid = NATS_INVALID_PID; - stanConnection *sc = NULL; - stanConnOptions *opts = NULL; - natsConnection *nc = NULL; - natsSubscription *psub = NULL; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - testAllowMillisecInPings = true; - s = stanConnOptions_Create(&opts); - } - IFOK(s, stanConnOptions_SetPings(opts, -50, 5)); - if (s == NATS_OK) - { - arg.string = STAN_ERR_MAX_PINGS; - s = stanConnOptions_SetConnectionLostHandler(opts, _stanConnLostCB, (void*) &arg); - } - if (s != NATS_OK) - { - _destroyDefaultThreadArgs(&arg); - stanConnOptions_Destroy(opts); - testAllowMillisecInPings = false; - FAIL("Unable to setup test"); - } - - pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true); - if (pid == NATS_INVALID_PID) - { - testAllowMillisecInPings = false; - FAIL("Unable to start server"); - } - - // Create NATS Subscription on pings subject and count the - // outgoing pings. - test("Pings are sent: "); - s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4222"); - // Use _recvTestString with control == 3 to increment sum up to 10 - if (s == NATS_OK) - { - char psubject[256]; - - snprintf(psubject, sizeof(psubject), "%s.%s.pings", STAN_CONN_OPTS_DEFAULT_DISCOVERY_PREFIX, clusterName); - arg.control = 3; - s = natsConnection_Subscribe(&psub, nc, psubject, _recvTestString, (void*) &arg); - } - - // Connect to Stan - IFOK(s, stanConnection_Connect(&sc, clusterName, clientName, opts)); - - // We should start receiving PINGs - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.sum < 10)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - if ((s == NATS_OK) && (arg.sum < 10)) - s = NATS_ERR; - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - natsSubscription_Destroy(psub); - natsConnection_Destroy(nc); - - test("Connection lost handler invoked: "); - _stopServer(pid); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - IFOK(s, arg.status); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Connection closed: "); - natsMutex_Lock(sc->mu); - s = (sc->closed ? NATS_OK : NATS_ERR); - natsMutex_Unlock(sc->mu); - testCond(s == NATS_OK); - - stanConnClose(sc, false); - stanConnection_Destroy(sc); - stanConnOptions_Destroy(opts); - testAllowMillisecInPings = false; - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_StanPingsNoResponder(void) -{ - natsStatus s; - natsPid nPid = NATS_INVALID_PID; - natsPid sPid = NATS_INVALID_PID; - stanConnection *sc = NULL; - stanConnOptions *opts = NULL; - struct threadArg arg; - - nPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(nPid); - - sPid = _startStreamingServer("nats://127.0.0.1:4222", "-ns nats://127.0.0.1:4222", true); - CHECK_SERVER_STARTED(sPid); - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - testAllowMillisecInPings = true; - s = stanConnOptions_Create(&opts); - } - IFOK(s, stanConnOptions_SetPings(opts, -50, 5)); - IFOK(s, stanConnOptions_SetConnectionLostHandler(opts, _stanConnLostCB, (void*) &arg)); - if (s != NATS_OK) - { - _destroyDefaultThreadArgs(&arg); - stanConnOptions_Destroy(opts); - testAllowMillisecInPings = false; - FAIL("Unable to setup test"); - } - - // Connect to Stan - test("Connects ok: ") - s = stanConnection_Connect(&sc, clusterName, clientName, opts); - testCond(s == NATS_OK); - - // Shutdown the streaming server, but not NATS Server - _stopServer(sPid); - - // We should get the connection lost callback invoked. - test("Connection lost: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - stanConnection_Destroy(sc); - stanConnOptions_Destroy(opts); - testAllowMillisecInPings = false; - - _stopServer(nPid); - _destroyDefaultThreadArgs(&arg); -} - -static void -test_StanConnectionLostHandlerNotSet(void) -{ - natsStatus s; - natsPid pid = NATS_INVALID_PID; - stanConnection *sc = NULL; - stanConnOptions *opts = NULL; - natsOptions *nOpts = NULL; - struct threadArg arg; - - testAllowMillisecInPings = true; - - s = _createDefaultThreadArgsForCbTests(&arg); - IFOK(s, natsOptions_Create(&nOpts)); - IFOK(s, natsOptions_SetClosedCB(nOpts, _closedCb, (void*)&arg)); - IFOK(s, stanConnOptions_Create(&opts)); - IFOK(s, stanConnOptions_SetNATSOptions(opts, nOpts)); - IFOK(s, stanConnOptions_SetPings(opts, -50, 5)); - if (s != NATS_OK) - { - _destroyDefaultThreadArgs(&arg); - stanConnOptions_Destroy(opts); - testAllowMillisecInPings = false; - FAIL("Unable to setup test"); - } - - pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true); - if (pid == NATS_INVALID_PID) - { - testAllowMillisecInPings = false; - FAIL("Unable to start server"); - } - - test("Connect: "); - s = stanConnection_Connect(&sc, clusterName, clientName, opts); - testCond(s == NATS_OK); - - // Stop server and wait for closed notification - _stopServer(pid); - - test("Connection closed: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Connection closed: "); - natsMutex_Lock(sc->mu); - s = (sc->closed ? NATS_OK : NATS_ERR); - natsMutex_Unlock(sc->mu); - testCond(s == NATS_OK); - - stanConnClose(sc, false); - stanConnection_Destroy(sc); - stanConnOptions_Destroy(opts); - natsOptions_Destroy(nOpts); - testAllowMillisecInPings = false; - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_StanPingsUnblockPubCalls(void) -{ - natsStatus s; - natsPid pid = NATS_INVALID_PID; - stanConnection *sc = NULL; - stanConnOptions *opts = NULL; - natsThread *t = NULL; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - { - testAllowMillisecInPings = true; - s = stanConnOptions_Create(&opts); - } - IFOK(s, stanConnOptions_SetMaxPubAcksInflight(opts, 1, 1.0)); - IFOK(s, stanConnOptions_SetPings(opts, -100, 5)); - if (s == NATS_OK) - { - arg.string = STAN_ERR_MAX_PINGS; - s = stanConnOptions_SetConnectionLostHandler(opts, _stanConnLostCB, (void*) &arg); - } - if (s != NATS_OK) - { - _destroyDefaultThreadArgs(&arg); - stanConnOptions_Destroy(opts); - testAllowMillisecInPings = false; - FAIL("Unable to setup test"); - } - - pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true); - if (pid == NATS_INVALID_PID) - { - testAllowMillisecInPings = false; - FAIL("Unable to start server"); - } - - test("Connect: "); - s = stanConnection_Connect(&sc, clusterName, clientName, opts); - testCond(s == NATS_OK); - - _stopServer(pid); - - natsThread_Create(&t, _stanPublishAsyncThread, (void*) &arg); - - test("Sync publish released: "); - s = stanConnection_Publish(sc, "foo", (const void*)"hello", 5); - testCond(s != NATS_OK); - nats_clearLastError(); - s = NATS_OK; - - test("Async publish released: "); - if (t != NULL) - { - natsThread_Join(t); - natsThread_Destroy(t); - } - testCond(s == NATS_OK); - - test("Connection lost handler invoked: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - IFOK(s, arg.status); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Connection closed: "); - natsMutex_Lock(sc->mu); - s = (sc->closed ? NATS_OK : NATS_ERR); - natsMutex_Unlock(sc->mu); - testCond(s == NATS_OK); - - stanConnClose(sc, false); - stanConnection_Destroy(sc); - - stanConnOptions_Destroy(opts); - testAllowMillisecInPings = false; - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_StanGetNATSConnection(void) -{ - natsStatus s; - natsPid pid = NATS_INVALID_PID; - stanConnection *sc = NULL; - stanSubscription *ssub = NULL; - natsConnection *nc = NULL; - natsConnection *nc2 = NULL; - natsSubscription *nsub = NULL; - struct threadArg args; - - s = _createDefaultThreadArgsForCbTests(&args); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Connect: "); - s = stanConnection_Connect(&sc, clusterName, clientName, NULL); - testCond(s == NATS_OK); - - test("Create sub: "); - s = stanConnection_Subscribe(&ssub, sc, "foo", _stanMsgHandlerBumpSum, (void*) &args, NULL); - testCond(s == NATS_OK); - - test("Get NATS Connection: "); - s = stanConnection_GetNATSConnection(sc, &nc); - testCond((s == NATS_OK) && (nc != NULL)); - - test("Create sub & pub with NATS OK: "); - s = natsConnection_Subscribe(&nsub, nc, "foo", _dummyMsgHandler, NULL); - IFOK(s, natsConnection_PublishString(nc, "foo", "hello")); - testCond(s == NATS_OK); - - test("Invalid to drain: "); - s = natsConnection_Drain(nc); - if (s == NATS_ILLEGAL_STATE) - s = natsConnection_DrainTimeout(nc, 1000); - if ((s == NATS_ILLEGAL_STATE) - && (nats_GetLastError(NULL) != NULL) - && (strstr(nats_GetLastError(NULL), "owned") != NULL)) - { - s = NATS_OK; - nats_clearLastError(); - } - testCond(s == NATS_OK); - - test("Closing NATS Conn has no effect: "); - natsConnection_Close(nc); - s = stanConnection_Publish(sc, "foo", (const void*)"hello", 5); - testCond(s == NATS_OK); - - test("Destroying NATS Conn has no effect: "); - natsConnection_Destroy(nc); - s = stanConnection_Publish(sc, "foo", (const void*)"hello", 5); - testCond(s == NATS_OK); - - test("Calling release more than get does not crash: "); - stanConnection_ReleaseNATSConnection(sc); - stanConnection_ReleaseNATSConnection(sc); - s = stanConnection_Publish(sc, "foo", (const void*)"hello", 5); - testCond(s == NATS_OK); - - test("Should have received 3 messages: "); - natsMutex_Lock(args.m); - while ((s != NATS_TIMEOUT) && (args.sum != 3)) - s = natsCondition_TimedWait(args.c, args.m, 2000); - natsMutex_Unlock(args.m); - testCond(s == NATS_OK); - - nc2 = NULL; - test("Invalid arg (sc == NULL): "); - s = stanConnection_GetNATSConnection(NULL, &nc2); - testCond((s == NATS_INVALID_ARG) && (nc2 == NULL)); - nats_clearLastError(); - - test("Invalid arg (nc == NULL): "); - s = stanConnection_GetNATSConnection(sc, NULL); - testCond((s == NATS_INVALID_ARG) && (nc2 == NULL)); - nats_clearLastError(); - - test("GetNATSConnection on closed connection fails: "); - s = stanConnection_Close(sc); - IFOK(s, stanConnection_GetNATSConnection(sc, &nc2)); - testCond(s == NATS_CONNECTION_CLOSED); - nats_clearLastError(); - - natsSubscription_Destroy(nsub); - stanSubscription_Destroy(ssub); - // Valgrind will tell us if the NATS connection/STAN connection - // were properly released. - stanConnection_Destroy(sc); - - _destroyDefaultThreadArgs(&args); - - if (valgrind) - nats_Sleep(900); - - _stopServer(pid); -} - -static void -test_StanNoRetryOnFailedConnect(void) -{ - natsStatus s; - natsOptions *opts = NULL; - stanConnOptions *sopts = NULL; - stanConnection *sc = NULL; - - test("RetryOnFailedConnect not supported: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetRetryOnFailedConnect(opts, true, _dummyConnHandler, (void*)1)); - IFOK(s, stanConnOptions_Create(&sopts)); - IFOK(s, stanConnOptions_SetNATSOptions(sopts, opts)); - IFOK(s, stanConnection_Connect(&sc, clusterName, clientName, sopts)); - testCond(s == NATS_NO_SERVER); - - natsOptions_Destroy(opts); - stanConnOptions_Destroy(sopts); -} - -static bool -_subDlvThreadPooled(natsSubscription *sub) -{ - bool pooled; - natsSub_Lock(sub); - pooled = (sub->libDlvWorker != NULL); - natsSub_Unlock(sub); - return pooled; -} - -static void -test_StanInternalSubsNotPooled(void) -{ - natsStatus s; - natsPid pid = NATS_INVALID_PID; - natsOptions *opts = NULL; - stanConnOptions *sopts = NULL; - stanSubscription *sub = NULL; - stanConnection *sc = NULL; - natsSubscription *hbSub = NULL; - natsSubscription *ackSub = NULL; - natsSubscription *pingSub = NULL; - natsSubscription *inboxSub = NULL; - - pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Can connet: "); - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_UseGlobalMessageDelivery(opts, true)); - IFOK(s, stanConnOptions_Create(&sopts)); - IFOK(s, stanConnOptions_SetNATSOptions(sopts, opts)); - IFOK(s, stanConnection_Connect(&sc, clusterName, clientName, sopts)); - testCond(s == NATS_OK); - - test("Create sub: "); - s = stanConnection_Subscribe(&sub, sc, "foo", _dummyStanMsgHandler, NULL, NULL); - testCond(s == NATS_OK); - - stanConn_Lock(sc); - hbSub = sc->hbSubscription; - ackSub = sc->ackSubscription; - pingSub = sc->pingSub; - stanConn_Unlock(sc); - - stanSub_Lock(sub); - inboxSub = sub->inboxSub; - stanSub_Unlock(sub); - - test("HBSub not pooled: "); - testCond((s == NATS_OK) && !_subDlvThreadPooled(hbSub)); - - test("AckSub not pooled: "); - testCond((s == NATS_OK) && !_subDlvThreadPooled(ackSub)); - - test("PingSub not pooled: "); - testCond((s == NATS_OK) && !_subDlvThreadPooled(pingSub)); - - test("InboxSub pooled: "); - testCond((s == NATS_OK) && _subDlvThreadPooled(inboxSub)); - - natsOptions_Destroy(opts); - stanConnOptions_Destroy(sopts); - stanSubscription_Destroy(sub); - stanConnection_Destroy(sc); - - if (valgrind) - nats_Sleep(900); - - _stopServer(pid); -} - -static void -_stanSubOnComplete(void *closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - if (arg->control == 1) - { - arg->done = true; - natsCondition_Signal(arg->c); - } - natsMutex_Unlock(arg->m); -} - -static void -_stanSubOnCompleteMsgCB(stanConnection *sc, stanSubscription *sub, const char *channel, - stanMsg *msg, void* closure) -{ - struct threadArg *arg = (struct threadArg*) closure; - - natsMutex_Lock(arg->m); - arg->msgReceived = true; - natsCondition_Signal(arg->c); - natsMutex_Unlock(arg->m); - // Sleep to simulate callbac doing some processing and let the - // main thread close the subscription. - nats_Sleep(500); - // Update a field that _stanSubOnComplete will ensure is set to - // prove that the onComplete is invoked after the msg callback - // has returned. - natsMutex_Lock(arg->m); - arg->control = 1; - natsMutex_Unlock(arg->m); - - stanMsg_Destroy(msg); -} - -static void -test_StanSubOnComplete(void) -{ - natsStatus s; - natsPid pid = NATS_INVALID_PID; - stanConnection *sc = NULL; - stanSubscription *sub = NULL; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test"); - - pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Can connet: "); - s = stanConnection_Connect(&sc, clusterName, clientName, NULL); - testCond(s == NATS_OK); - - test("SetOnComplete error: "); - s = stanSubscription_SetOnCompleteCB(NULL, _stanSubOnComplete, NULL); - testCond(s == NATS_INVALID_ARG); - - test("Create sub: "); - s = stanConnection_Subscribe(&sub, sc, "foo", _stanSubOnCompleteMsgCB, &arg, NULL); - testCond(s == NATS_OK); - - test("SetOnComplete ok: "); - s = stanSubscription_SetOnCompleteCB(sub, _stanSubOnComplete, &arg); - testCond(s == NATS_OK); - - test("Remove onComplete: "); - s = stanSubscription_SetOnCompleteCB(sub, NULL, NULL); - if (s == NATS_OK) - { - stanSub_Lock(sub); - if ((sub->onCompleteCB != NULL) || (sub->onCompleteCBClosure != NULL)) - s = NATS_ERR; - stanSub_Unlock(sub); - } - testCond(s == NATS_OK); - - test("SetOnComplete ok: "); - s = stanSubscription_SetOnCompleteCB(sub, _stanSubOnComplete, &arg); - testCond(s == NATS_OK); - - test("Publish msg: "); - s = stanConnection_Publish(sc, "foo", (const void*) "hello", 5); - testCond(s == NATS_OK); - - test("Wait for message to be received: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.msgReceived) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("Close subscription: ") - s = stanSubscription_Close(sub); - testCond(s == NATS_OK); - - test("Ensure onComplete invoked after cb returned: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - testCond(s == NATS_OK); - - test("SetOnComplete after close: "); - s = stanSubscription_SetOnCompleteCB(sub, _stanSubOnComplete, &arg); - testCond(s == NATS_INVALID_SUBSCRIPTION); - - stanSubscription_Destroy(sub); - stanConnection_Destroy(sc); - _stopServer(pid); - - _destroyDefaultThreadArgs(&arg); -} - -static void -test_StanSubTimeout(void) -{ - natsStatus s; - natsPid pid = NATS_INVALID_PID; - stanConnection *sc = NULL; - stanSubscription *sub = NULL; - natsConnection *nc = NULL; - natsSubscription *ncSub = NULL; - const char *closeSubj = NULL; - natsMsg *resp = NULL; - int i = 0; - Pb__SubscriptionRequest *r = NULL; - - pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(pid); - - test("Can connect: "); - s = stanConnection_Connect(&sc, clusterName, clientName, NULL); - testCond(s == NATS_OK); - - test("Get NATS subscription: "); - s = stanConnection_GetNATSConnection(sc, &nc); - testCond(s == NATS_OK); - - test("Setup NATS sub: "); - natsMutex_Lock(sc->mu); - closeSubj = (const char*) sc->subCloseRequests; - natsMutex_Unlock(sc->mu); - s = natsConnection_SubscribeSync(&ncSub, nc, closeSubj); - testCond(s == NATS_OK); - - // Artificially lower the timeout to make sure it fails. - natsMutex_Lock(sc->mu); - sc->opts->connTimeout=1; - natsMutex_Unlock(sc->mu); - - test("Subscribe should timeout: "); - // Try to get the timeout... - for (i=0;i<50;i++) - { - s = stanConnection_Subscribe(&sub, sc, "foo", _dummyStanMsgHandler, NULL, NULL); - if (s == NATS_TIMEOUT) - break; - stanSubscription_Destroy(sub); - sub = NULL; - } - // We don't want to fail the thest if we did not get a timeout. - testCond((s == NATS_OK) || (s == NATS_TIMEOUT)); - - // However, proceed with the rest of the test only if it was a timeout. - if (s == NATS_TIMEOUT) - { - test("Check sub close request sent: "); - s = natsSubscription_NextMsg(&resp, ncSub, 1000); - testCond((s == NATS_OK) && (resp != NULL)); - - test("Check request: "); - r = pb__subscription_request__unpack(NULL, - (size_t) natsMsg_GetDataLength(resp), - (const uint8_t*) natsMsg_GetData(resp)); - testCond((r != NULL) - && (strcmp(r->clientid, clientName) == 0) - && (strcmp(r->subject, "foo") == 0) - && (r->inbox != NULL)); - if (r != NULL) - pb__subscription_request__free_unpacked(r, NULL); - - natsMsg_Destroy(resp); - } - - natsSubscription_Destroy(ncSub); - stanSubscription_Destroy(sub); - stanConnection_ReleaseNATSConnection(sc); - stanConnection_Destroy(sc); - - if (valgrind) - nats_Sleep(900); - - _stopServer(pid); -} - -#endif - -typedef void (*testFunc)(void); - -typedef struct __testInfo -{ - const char *name; - testFunc func; - -} testInfo; - -static testInfo allTests[] = -{ - // Building blocks - {"Version", test_Version}, - {"VersionMatchesTag", test_VersionMatchesTag}, - {"OpenCloseAndWait", test_OpenCloseAndWait}, - {"natsNowAndSleep", test_natsNowAndSleep}, - {"natsAllocSprintf", test_natsAllocSprintf}, - {"natsStrCaseStr", test_natsStrCaseStr}, - {"natsSnprintf", test_natsSnprintf}, - {"natsBuffer", test_natsBuffer}, - {"natsParseInt64", test_natsParseInt64}, - {"natsParseControl", test_natsParseControl}, - {"natsNormalizeErr", test_natsNormalizeErr}, - {"natsMutex", test_natsMutex}, - {"natsThread", test_natsThread}, - {"natsCondition", test_natsCondition}, - {"natsTimer", test_natsTimer}, - {"natsUrl", test_natsUrl}, - {"natsCreateStringFromBuffer", test_natsCreateStringFromBuffer}, - {"natsHash", test_natsHash}, - {"natsHashing", test_natsHashing}, - {"natsStrHash", test_natsStrHash}, - {"natsInbox", test_natsInbox}, - {"natsOptions", test_natsOptions}, - {"natsSock_ConnectTcp", test_natsSock_ConnectTcp}, - {"natsSock_ShuffleIPs", test_natsSock_ShuffleIPs}, - {"natsSock_IPOrder", test_natsSock_IPOrder}, - {"natsSock_ReadLine", test_natsSock_ReadLine}, - {"natsJSON", test_natsJSON}, - {"natsEncodeTimeUTC", test_natsEncodeTimeUTC}, - {"natsErrWithLongText", test_natsErrWithLongText}, - {"natsErrStackMoreThanMaxFrames", test_natsErrStackMoreThanMaxFrames}, - {"natsMsg", test_natsMsg}, - {"natsBase32", test_natsBase32Decode}, - {"natsBase64", test_natsBase64Encode}, - {"natsCRC16", test_natsCRC16}, - {"natsKeys", test_natsKeys}, - {"natsReadFile", test_natsReadFile}, - {"natsGetJWTOrSeed", test_natsGetJWTOrSeed}, - {"natsHostIsIP", test_natsHostIsIP}, - {"natsWaitReady", test_natsWaitReady}, - {"natsSign", test_natsSign}, - {"HeadersLift", test_natsMsgHeadersLift}, - {"HeadersAPIs", test_natsMsgHeaderAPIs}, - {"MsgIsJSControl", test_natsMsgIsJSCtrl}, - {"SrvVersionAtLeast", test_natsSrvVersionAtLeast}, - {"FormatStringArray", test_natsFormatStringArray}, - - // Package Level Tests - - {"ReconnectServerStats", test_ReconnectServerStats}, - {"ParseStateReconnectFunctionality",test_ParseStateReconnectFunctionality}, - {"ServersRandomize", test_ServersRandomize}, - {"SelectNextServer", test_SelectNextServer}, - {"ParserPing", test_ParserPing}, - {"ParserErr", test_ParserErr}, - {"ParserOK", test_ParserOK}, - {"ParseINFO", test_ParseINFO}, - {"ParserShouldFail", test_ParserShouldFail}, - {"ParserSplitMsg", test_ParserSplitMsg}, - {"ProcessMsgArgs", test_ProcessMsgArgs}, - {"LibMsgDelivery", test_LibMsgDelivery}, - {"AsyncINFO", test_AsyncINFO}, - {"RequestPool", test_RequestPool}, - {"NoFlusherIfSendAsapOption", test_NoFlusherIfSendAsap}, - {"HeadersAndSubPendingBytes", test_HeadersAndSubPendingBytes}, - - // Public API Tests - - {"DefaultConnection", test_DefaultConnection}, - {"SimplifiedURLs", test_SimplifiedURLs}, - {"IPResolutionOrder", test_IPResolutionOrder}, - {"UseDefaultURLIfNoServerSpecified",test_UseDefaultURLIfNoServerSpecified}, - {"ConnectToWithMultipleURLs", test_ConnectToWithMultipleURLs}, - {"ConnectionWithNULLOptions", test_ConnectionWithNullOptions}, - {"ConnectionToWithNullURLs", test_ConnectionToWithNullURLs}, - {"ConnectionStatus", test_ConnectionStatus}, - {"ConnClosedCB", test_ConnClosedCB}, - {"CloseDisconnectedCB", test_CloseDisconnectedCB}, - {"ServerStopDisconnectedCB", test_ServerStopDisconnectedCB}, - {"ClosedConnections", test_ClosedConnections}, - {"ConnectVerboseOption", test_ConnectVerboseOption}, - {"ReconnectThreadLeak", test_ReconnectThreadLeak}, - {"ReconnectTotalTime", test_ReconnectTotalTime}, - {"ReconnectDisallowedFlags", test_ReconnectDisallowedFlags}, - {"ReconnectAllowedFlags", test_ReconnectAllowedFlags}, - {"ConnCloseBreaksReconnectLoop", test_ConnCloseBreaksReconnectLoop}, - {"BasicReconnectFunctionality", test_BasicReconnectFunctionality}, - {"ExtendedReconnectFunctionality", test_ExtendedReconnectFunctionality}, - {"QueueSubsOnReconnect", test_QueueSubsOnReconnect}, - {"IsClosed", test_IsClosed}, - {"IsReconnectingAndStatus", test_IsReconnectingAndStatus}, - {"ReconnectBufSize", test_ReconnectBufSize}, - {"RetryOnFailedConnect", test_RetryOnFailedConnect}, - {"NoPartialOnReconnect", test_NoPartialOnReconnect}, - {"ReconnectFailsPendingRequests", test_ReconnectFailsPendingRequest}, - {"ForcedReconnect", test_ForcedReconnect}, - - {"ErrOnConnectAndDeadlock", test_ErrOnConnectAndDeadlock}, - {"ErrOnMaxPayloadLimit", test_ErrOnMaxPayloadLimit}, - - {"Auth", test_Auth}, - {"AuthFailNoDisconnectCB", test_AuthFailNoDisconnectCB}, - {"AuthToken", test_AuthToken}, - {"AuthTokenHandler", test_AuthTokenHandler}, - {"PermViolation", test_PermViolation}, - {"AuthViolation", test_AuthViolation}, - {"AuthenticationExpired", test_AuthenticationExpired}, - {"AuthenticationExpiredReconnect", test_AuthenticationExpiredReconnect}, - {"ConnectedServer", test_ConnectedServer}, - {"MultipleClose", test_MultipleClose}, - {"SimplePublish", test_SimplePublish}, - {"SimplePublishNoData", test_SimplePublishNoData}, - {"PublishMsg", test_PublishMsg}, - {"InvalidSubsArgs", test_InvalidSubsArgs}, - {"AsyncSubscribe", test_AsyncSubscribe}, - {"AsyncSubscribeTimeout", test_AsyncSubscribeTimeout}, - {"SyncSubscribe", test_SyncSubscribe}, - {"PubSubWithReply", test_PubSubWithReply}, - {"NoResponders", test_NoResponders}, - {"Flush", test_Flush}, - {"ConnCloseDoesFlush", test_ConnCloseDoesFlush}, - {"QueueSubscriber", test_QueueSubscriber}, - {"ReplyArg", test_ReplyArg}, - {"SyncReplyArg", test_SyncReplyArg}, - {"Unsubscribe", test_Unsubscribe}, - {"DoubleUnsubscribe", test_DoubleUnsubscribe}, - {"SubRemovedWhileProcessingMsg", test_SubRemovedWhileProcessingMsg}, - {"RequestTimeout", test_RequestTimeout}, - {"Request", test_Request}, - {"RequestNoBody", test_RequestNoBody}, - {"RequestMuxWithMappedSubject", test_RequestMuxWithMappedSubject}, - {"OldRequest", test_OldRequest}, - {"SimultaneousRequests", test_SimultaneousRequest}, - {"RequestClose", test_RequestClose}, - {"CustomInbox", test_CustomInbox}, - {"MessagePadding", test_MessageBufferPadding}, - {"FlushInCb", test_FlushInCb}, - {"ReleaseFlush", test_ReleaseFlush}, - {"FlushErrOnDisconnect", test_FlushErrOnDisconnect}, - {"Inbox", test_Inbox}, - {"Stats", test_Stats}, - {"BadSubject", test_BadSubject}, - {"SubBadSubjectAndQueueNames", test_SubBadSubjectAndQueueName}, - {"ClientAsyncAutoUnsub", test_ClientAsyncAutoUnsub}, - {"ClientSyncAutoUnsub", test_ClientSyncAutoUnsub}, - {"ClientAutoUnsubAndReconnect", test_ClientAutoUnsubAndReconnect}, - {"AutoUnsubNoUnsubOnDestroy", test_AutoUnsubNoUnsubOnDestroy}, - {"NextMsgOnClosedSub", test_NextMsgOnClosedSub}, - {"CloseSubRelease", test_CloseSubRelease}, - {"IsValidSubscriber", test_IsValidSubscriber}, - {"SlowSubscriber", test_SlowSubscriber}, - {"SlowAsyncSubscriber", test_SlowAsyncSubscriber}, - {"SlowConsumerCb", test_SlowConsumerCB}, - {"PendingLimitsDeliveredAndDropped",test_PendingLimitsDeliveredAndDropped}, - {"PendingLimitsWithSyncSub", test_PendingLimitsWithSyncSub}, - {"AsyncSubscriptionPending", test_AsyncSubscriptionPending}, - {"AsyncSubscriptionPendingDrain", test_AsyncSubscriptionPendingDrain}, - {"SyncSubscriptionPending", test_SyncSubscriptionPending}, - {"SyncSubscriptionPendingDrain", test_SyncSubscriptionPendingDrain}, - {"AsyncErrHandlerMaxPendingMsgs", test_AsyncErrHandler_MaxPendingMsgs}, - {"AsyncErrHandlerMaxPendingBytes", test_AsyncErrHandler_MaxPendingBytes }, - {"AsyncErrHandlerSubDestroyed", test_AsyncErrHandlerSubDestroyed}, - {"AsyncSubscriberStarvation", test_AsyncSubscriberStarvation}, - {"AsyncSubscriberOnClose", test_AsyncSubscriberOnClose}, - {"NextMsgCallOnAsyncSub", test_NextMsgCallOnAsyncSub}, - {"SubOnComplete", test_SubOnComplete}, - {"GetLastError", test_GetLastError}, - {"StaleConnection", test_StaleConnection}, - {"ServerErrorClosesConnection", test_ServerErrorClosesConnection}, - {"NoEcho", test_NoEcho}, - {"NoEchoOldServer", test_NoEchoOldServer}, - {"DrainSub", test_DrainSub}, - {"DrainSubStops", test_DrainSubStops}, - {"DrainSubRaceOnAutoUnsub", test_DrainSubRaceOnAutoUnsub}, - {"DrainSubNotResentOnReconnect", test_DrainSubNotResentOnReconnect}, - {"DrainConn", test_DrainConn}, - {"NoDoubleCloseCbOnDrain", test_NoDoubleConnClosedOnDrain}, - {"GetClientID", test_GetClientID}, - {"GetClientIP", test_GetClientIP}, - {"GetRTT", test_GetRTT}, - {"GetLocalIPAndPort", test_GetLocalIPAndPort}, - {"UserCredsCallbacks", test_UserCredsCallbacks}, - {"UserCredsFromFiles", test_UserCredsFromFiles}, - {"UserCredsFromMemory", test_UserCredsFromMemory}, - {"NKey", test_NKey}, - {"NKeyFromSeed", test_NKeyFromSeed}, - {"ConnSign", test_ConnSign}, - {"WriteDeadline", test_WriteDeadline}, - {"HeadersNotSupported", test_HeadersNotSupported}, - {"HeadersBasic", test_HeadersBasic}, - {"MsgsFilter", test_natsMsgsFilter}, - {"EventLoop", test_EventLoop}, - {"EventLoopRetryOnFailedConnect", test_EventLoopRetryOnFailedConnect}, - {"EventLoopTLS", test_EventLoopTLS}, - {"SSLBasic", test_SSLBasic}, - {"SSLVerify", test_SSLVerify}, - {"SSLCAFromMemory", test_SSLLoadCAFromMemory}, - {"SSLCertAndKeyFromMemory", test_SSLCertAndKeyFromMemory}, - {"SSLVerifyHostname", test_SSLVerifyHostname}, - {"SSLSkipServerVerification", test_SSLSkipServerVerification}, - {"SSLCiphers", test_SSLCiphers}, - {"SSLMultithreads", test_SSLMultithreads}, - {"SSLConnectVerboseOption", test_SSLConnectVerboseOption}, - {"SSLSocketLeakEventLoop", test_SSLSocketLeakWithEventLoop}, - {"SSLReconnectWithAuthError", test_SSLReconnectWithAuthError}, - {"SSLAvailable", test_SSLAvailable}, - - // Clusters Tests - - {"ServersOption", test_ServersOption}, - {"AuthServers", test_AuthServers}, - {"AuthFailToReconnect", test_AuthFailToReconnect}, - {"ReconnectWithTokenHandler", test_ReconnectWithTokenHandler}, - {"BasicClusterReconnect", test_BasicClusterReconnect}, - {"HotSpotReconnect", test_HotSpotReconnect}, - {"ProperReconnectDelay", test_ProperReconnectDelay}, - {"ProperFalloutAfterMaxAttempts", test_ProperFalloutAfterMaxAttempts}, - {"StopReconnectAfterTwoAuthErr", test_StopReconnectAfterTwoAuthErr}, - {"TimeoutOnNoServer", test_TimeoutOnNoServer}, - {"PingReconnect", test_PingReconnect}, - {"GetServers", test_GetServers}, - {"GetDiscoveredServers", test_GetDiscoveredServers}, - {"DiscoveredServersCb", test_DiscoveredServersCb}, - {"IgnoreDiscoveredServers", test_IgnoreDiscoveredServers}, - {"INFOAfterFirstPONGisProcessedOK", test_ReceiveINFORightAfterFirstPONG}, - {"ServerPoolUpdatedOnClusterUpdate",test_ServerPoolUpdatedOnClusterUpdate}, - {"ReconnectJitter", test_ReconnectJitter}, - {"CustomReconnectDelay", test_CustomReconnectDelay}, - {"LameDuckMode", test_LameDuckMode}, - {"ReconnectImplicitUserInfo", test_ReconnectImplicitUserInfo}, - - {"JetStreamUnmarshalAccInfo", test_JetStreamUnmarshalAccountInfo}, - {"JetStreamUnmarshalStreamState", test_JetStreamUnmarshalStreamState}, - {"JetStreamUnmarshalStreamCfg", test_JetStreamUnmarshalStreamConfig}, - {"JetStreamUnmarshalStreamInfo", test_JetStreamUnmarshalStreamInfo}, - {"JetStreamMarshalStreamCfg", test_JetStreamMarshalStreamConfig}, - {"JetStreamUnmarshalConsumerInfo", test_JetStreamUnmarshalConsumerInfo}, - {"JetStreamContext", test_JetStreamContext}, - {"JetStreamDomain", test_JetStreamContextDomain}, - {"JetStreamMgtStreams", test_JetStreamMgtStreams}, - {"JetStreamMgtConsumers", test_JetStreamMgtConsumers}, - {"JetStreamPublish", test_JetStreamPublish}, - {"JetStreamPublishAsync", test_JetStreamPublishAsync}, - {"JetStreamPublishAckHandler", test_JetStreamPublishAckHandler}, - {"JetStreamSubscribe", test_JetStreamSubscribe}, - {"JetStreamSubscribeSync", test_JetStreamSubscribeSync}, - {"JetStreamSubscribeConfigCheck", test_JetStreamSubscribeConfigCheck}, - {"JetStreamSubscribeIdleHeartbeat", test_JetStreamSubscribeIdleHearbeat}, - {"JetStreamSubscribeFlowControl", test_JetStreamSubscribeFlowControl}, - {"JetStreamSubscribePull", test_JetStreamSubscribePull}, - {"JetStreamSubscribeHeadersOnly", test_JetStreamSubscribeHeadersOnly}, - {"JetStreamOrderedCons", test_JetStreamOrderedConsumer}, - {"JetStreamOrderedConsWithErrors", test_JetStreamOrderedConsumerWithErrors}, - {"JetStreamOrderedConsAutoUnsub", test_JetStreamOrderedConsumerWithAutoUnsub}, - {"JetStreamOrderedConsSrvRestart", test_JetStreamOrderedConsSrvRestart}, - {"JetStreamSubscribeWithFWC", test_JetStreamSubscribeWithFWC}, - {"JetStreamStreamsSealAndRollup", test_JetStreamStreamsSealAndRollup}, - {"JetStreamGetMsgAndLastMsg", test_JetStreamGetMsgAndLastMsg}, - {"JetStreamConvertDirectMsg", test_JetStreamConvertDirectMsg}, - {"JetStreamDirectGetMsg", test_JetStreamDirectGetMsg}, - {"JetStreamNakWithDelay", test_JetStreamNakWithDelay}, - {"JetStreamBackOffRedeliveries", test_JetStreamBackOffRedeliveries}, - {"JetStreamInfoWithSubjects", test_JetStreamInfoWithSubjects}, - {"JetStreamInfoAlternates", test_JetStreamInfoAlternates}, - - {"KeyValueManager", test_KeyValueManager}, - {"KeyValueBasics", test_KeyValueBasics}, - {"KeyValueWatch", test_KeyValueWatch}, - {"KeyValueWatchMulti", test_KeyValueWatchMulti}, - {"KeyValueHistory", test_KeyValueHistory}, - {"KeyValueKeys", test_KeyValueKeys}, - {"KeyValueDeleteVsPurge", test_KeyValueDeleteVsPurge}, - {"KeyValueDeleteTombstones", test_KeyValueDeleteTombstones}, - {"KeyValueDeleteMarkerThreshold", test_KeyValuePurgeDeletesMarkerThreshold}, - {"KeyValueCrossAccount", test_KeyValueCrossAccount}, - {"KeyValueDiscardOldToNew", test_KeyValueDiscardOldToNew}, - {"KeyValueRePublish", test_KeyValueRePublish}, - {"KeyValueMirrorDirectGet", test_KeyValueMirrorDirectGet}, - {"KeyValueMirrorCrossDomains", test_KeyValueMirrorCrossDomains}, - - {"MicroMatchEndpointSubject", test_MicroMatchEndpointSubject}, - {"MicroAddService", test_MicroAddService}, - {"MicroGroups", test_MicroGroups}, - {"MicroBasics", test_MicroBasics}, - {"MicroStartStop", test_MicroStartStop}, - {"MicroServiceStopsOnClosedConn", test_MicroServiceStopsOnClosedConn}, - {"MicroServiceStopsWhenServerStops", test_MicroServiceStopsWhenServerStops}, - {"MicroAsyncErrorHandlerMaxPendingMsgs", test_MicroAsyncErrorHandler_MaxPendingMsgs}, - {"MicroAsyncErrorHandlerMaxPendingBytes", test_MicroAsyncErrorHandler_MaxPendingBytes }, - -#if defined(NATS_HAS_STREAMING) - {"StanPBufAllocator", test_StanPBufAllocator}, - {"StanConnOptions", test_StanConnOptions}, - {"StanSubOptions", test_StanSubOptions}, - {"StanMsg", test_StanMsg}, - {"StanServerNotReachable", test_StanServerNotReachable}, - {"StanBasicConnect", test_StanBasicConnect}, - {"StanConnectError", test_StanConnectError}, - {"StanBasicPublish", test_StanBasicPublish}, - {"StanBasicPublishAsync", test_StanBasicPublishAsync}, - {"StanPublishTimeout", test_StanPublishTimeout}, - {"StanPublishMaxAcksInflight", test_StanPublishMaxAcksInflight}, - {"StanBasicSubscription", test_StanBasicSubscription}, - {"StanSubscriptionCloseAndUnsub", test_StanSubscriptionCloseAndUnsubscribe}, - {"StanDurableSubscription", test_StanDurableSubscription}, - {"StanBasicQueueSubscription", test_StanBasicQueueSubscription}, - {"StanDurableQueueSubscription", test_StanDurableQueueSubscription}, - {"StanCheckReceivedMsg", test_StanCheckReceivedvMsg}, - {"StanSubscriptionAckMsg", test_StanSubscriptionAckMsg}, - {"StanPings", test_StanPings}, - {"StanPingsNoResponder", test_StanPingsNoResponder}, - {"StanConnectionLostHandlerNotSet", test_StanConnectionLostHandlerNotSet}, - {"StanPingsUnblockPublishCalls", test_StanPingsUnblockPubCalls}, - {"StanGetNATSConnection", test_StanGetNATSConnection}, - {"StanNoRetryOnFailedConnect", test_StanNoRetryOnFailedConnect}, - {"StanInternalSubsNotPooled", test_StanInternalSubsNotPooled}, - {"StanSubOnComplete", test_StanSubOnComplete}, - {"StanSubTimeout", test_StanSubTimeout}, - -#endif - -}; - -static int maxTests = (int) (sizeof(allTests)/sizeof(testInfo)); - -static void -generateList(void) -{ - FILE *list = fopen("list.txt", "w"); - int i; - - if (list == NULL) - { - printf("@@ Unable to create file 'list.txt': %d\n", errno); - return; - } - - printf("Number of tests: %d\n", maxTests); - - for (i=0; i= maxTests) - || (testEnd < 0) || (testEnd >= maxTests) - || (testStart > testEnd)) - { - printf("@@ Usage: %s [start] [end] (0 .. %d)\n", argv[0], (maxTests - 1)); - return 1; - } - -#ifndef _WIN32 - signal(SIGSEGV, _sigsegv_handler); -#endif // _WIN32 - - envStr = getenv("NATS_TEST_TRAVIS"); - if ((envStr != NULL) && (envStr[0] != '\0')) - runOnTravis = true; - - envStr = getenv("NATS_TEST_VALGRIND"); - if ((envStr != NULL) && (envStr[0] != '\0')) - { - valgrind = true; - printf("Test running in VALGRIND mode.\n"); - } - - envStr = getenv("NATS_TEST_KEEP_SERVER_OUTPUT"); - if ((envStr != NULL) && (envStr[0] != '\0')) - { - keepServerOutput = true; - printf("Test prints server's output.\n"); - } - - envStr = getenv("NATS_TEST_SERVER_EXE"); - if ((envStr != NULL) && (envStr[0] != '\0')) - { - natsServerExe = envStr; - printf("Test using server executable: %s\n", natsServerExe); - } - - envStr = getenv("NATS_TEST_STREAMING_SERVER_EXE"); - if ((envStr != NULL) && (envStr[0] != '\0')) - { - natsStreamingServerExe = envStr; - printf("Test using server executable: %s\n", natsStreamingServerExe); - } - - envStr = getenv("NATS_TEST_SERVER_VERSION"); - if ((envStr != NULL) && (envStr[0] != '\0')) - { - serverVersion = envStr; - printf("Test server version: %s\n", serverVersion); - } - - if (nats_Open(-1) != NATS_OK) - { - printf("@@ Unable to run tests: unable to initialize the library!\n"); - return 1; - } - - if (natsMutex_Create(&slMu) != NATS_OK) - { - printf("@@ Unable to create lock for servers map\n"); - return 1; - } - if (natsHash_Create(&slMap, 4) != NATS_OK) - { - natsMutex_Destroy(slMu); - printf("@@ Unable to create lock for servers map\n"); - return 1; - } - - // Execute tests - for (i=testStart; (i<=testEnd) && !failed; i++) - { -#ifdef _WIN32 - printf("\n== %s ==\n", allTests[i].name); -#else - printf("\033[0;34m\n== %s ==\n\033[0;0m", allTests[i].name); -#endif - (*(allTests[i].func))(); - } - -#ifdef _WIN32 - if (logHandle != NULL) - { - CloseHandle(logHandle); - DeleteFile(LOGFILE_NAME); - } -#else - remove(LOGFILE_NAME); -#endif - - // Shutdown servers that are still running likely due to failed test - { - natsHash *pids = NULL; - natsHashIter iter; - int64_t key; - - if (natsHash_Create(&pids, 16) == NATS_OK) - { - natsMutex_Lock(slMu); - natsHashIter_Init(&iter, slMap); - while (natsHashIter_Next(&iter, &key, NULL)) - { - natsHash_Set(pids, key, NULL, NULL); - natsHashIter_RemoveCurrent(&iter); - } - natsHashIter_Done(&iter); - natsHash_Destroy(slMap); - slMap = NULL; - natsMutex_Unlock(slMu); - - natsHashIter_Init(&iter, pids); - while (natsHashIter_Next(&iter, &key, NULL)) - _stopServer((natsPid) key); - - natsHash_Destroy(pids); - } - else - { - natsHash_Destroy(slMap); - } - natsMutex_Destroy(slMu); - } - - // Makes valgrind happy - nats_CloseAndWait((failed ? 1 : 2000)); - - if (failed) - { - printf("*** TEST FAILED ***\n"); - return 1; - } - - printf("ALL PASSED\n"); - return 0; -} diff --git a/test/test.h b/test/test.h new file mode 100644 index 000000000..3d79f71e8 --- /dev/null +++ b/test/test.h @@ -0,0 +1,83 @@ +#ifndef _NATSTEST_H +#define _NATSTEST_H + +extern int __tests; +extern bool __failed; +extern char __namebuf[1024]; + +#define test(s) \ + { \ + printf("\n#%02d ", ++__tests); \ + printf("%s\n", (s)); \ + fflush(stdout); \ + } + +#ifdef _WIN32 +#define NATS_INVALID_PID (NULL) +#define testCond(c) \ + if (c) \ + { \ + printf("PASSED\n"); \ + fflush(stdout); \ + } \ + else \ + { \ + printf("FAILED\n"); \ + nats_PrintLastErrorStack(stdout); \ + fflush(stdout); \ + __failed = true; \ + return; \ + } +#define testCondNoReturn(c) \ + if (c) \ + { \ + printf("PASSED\n"); \ + fflush(stdout); \ + } \ + else \ + { \ + printf("FAILED\n"); \ + nats_PrintLastErrorStack(stdout); \ + fflush(stdout); \ + __failed = true; \ + } +#define LOGFILE_NAME "wserver.log" +#else +#define NATS_INVALID_PID (-1) +#define testCond(c) \ + if (c) \ + { \ + printf("\033[0;32mPASSED\033[0;0m\n"); \ + fflush(stdout); \ + } \ + else \ + { \ + printf("\033[0;31mFAILED\033[0;0m\n"); \ + nats_PrintLastErrorStack(stdout); \ + fflush(stdout); \ + __failed = true; \ + return; \ + } +#define testCondNoReturn(c) \ + if (c) \ + { \ + printf("\033[0;32mPASSED\033[0;0m\n"); \ + fflush(stdout); \ + } \ + else \ + { \ + printf("\033[0;31mFAILED\033[0;0m\n"); \ + nats_PrintLastErrorStack(stdout); \ + fflush(stdout); \ + __failed = true; \ + } +#define LOGFILE_NAME "server.log" +#endif +#define FAIL(m) \ + { \ + printf("@@ %s @@\n", (m)); \ + __failed = true; \ + return; \ + } + +#endif // _TEST_H diff --git a/test/tls.conf b/test/tls.conf deleted file mode 100644 index 7017966fe..000000000 --- a/test/tls.conf +++ /dev/null @@ -1,15 +0,0 @@ - -# Simple TLS config file - -port: 4443 -net: "0.0.0.0" - -tls { - # Server cert - cert_file: "certs/server-cert.pem" - # Server private key - key_file: "certs/server-key.pem" - # Increase timeout for valgrind tests - timeout: 2 -} - diff --git a/test/tls_default_port.conf b/test/tls_default_port.conf deleted file mode 100644 index 5ace4fbe6..000000000 --- a/test/tls_default_port.conf +++ /dev/null @@ -1,15 +0,0 @@ - -# Simple TLS config file - -port: 4222 -net: "0.0.0.0" - -tls { - # Server cert - cert_file: "certs/server-cert.pem" - # Server private key - key_file: "certs/server-key.pem" - # Increase timeout for valgrind tests - timeout: 2 -} - diff --git a/test/tls_noip.conf b/test/tls_noip.conf deleted file mode 100644 index 76245c22b..000000000 --- a/test/tls_noip.conf +++ /dev/null @@ -1,15 +0,0 @@ - -# Simple TLS config file - -port: 4443 -net: "0.0.0.0" - -tls { - # Server cert - cert_file: "certs/server-noip.pem" - # Server private key - key_file: "certs/server-key-noip.pem" - # Increase timeout for valgrind tests - timeout: 2 -} - diff --git a/test/tlsverify.conf b/test/tlsverify.conf deleted file mode 100644 index 1f839db9f..000000000 --- a/test/tlsverify.conf +++ /dev/null @@ -1,19 +0,0 @@ - -# Simple TLS config file - -port: 4443 -net: "0.0.0.0" - -tls { - # Server cert - cert_file: "certs/server-cert.pem" - # Server private key - key_file: "certs/server-key.pem" - # Verify client certs - verify: true - # Server verifies client certificate, so need a CA - ca_file: "certs/ca.pem" - # Increase timeout for valgrind tests - timeout: 2 -} -