3666 lines
141 KiB
C
3666 lines
141 KiB
C
/**
|
|
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
* SPDX-License-Identifier: Apache-2.0.
|
|
*/
|
|
|
|
#include "mqtt5_client.h"
|
|
#include "http_connection.h"
|
|
#include "http_message.h"
|
|
#include "io.h"
|
|
|
|
#include <aws/http/proxy.h>
|
|
#include <aws/io/socket.h>
|
|
#include <aws/io/tls_channel_handler.h>
|
|
#include <aws/mqtt/v5/mqtt5_client.h>
|
|
#include <aws/mqtt/v5/mqtt5_packet_storage.h>
|
|
#include <aws/mqtt/v5/mqtt5_types.h>
|
|
|
|
/* object key names for referencing mqtt5-related properties on napi objects */
|
|
static const char *AWS_NAPI_KEY_NAME = "name";
|
|
static const char *AWS_NAPI_KEY_VALUE = "value";
|
|
static const char *AWS_NAPI_KEY_USER_PROPERTIES = "userProperties";
|
|
static const char *AWS_NAPI_KEY_SESSION_PRESENT = "sessionPresent";
|
|
static const char *AWS_NAPI_KEY_REASON_CODE = "reasonCode";
|
|
static const char *AWS_NAPI_KEY_REASON_CODES = "reasonCodes";
|
|
static const char *AWS_NAPI_KEY_SESSION_EXPIRY_INTERVAL = "sessionExpiryInterval";
|
|
static const char *AWS_NAPI_KEY_RECEIVE_MAXIMUM = "receiveMaximum";
|
|
static const char *AWS_NAPI_KEY_MAXIMUM_QOS = "maximumQos";
|
|
static const char *AWS_NAPI_KEY_RETAIN_AVAILABLE = "retainAvailable";
|
|
static const char *AWS_NAPI_KEY_MAXIMUM_PACKET_SIZE = "maximumPacketSize";
|
|
static const char *AWS_NAPI_KEY_ASSIGNED_CLIENT_IDENTIFIER = "assignedClientIdentifier";
|
|
static const char *AWS_NAPI_KEY_TOPIC_ALIAS_MAXIMUM = "topicAliasMaximum";
|
|
static const char *AWS_NAPI_KEY_REASON_STRING = "reasonString";
|
|
static const char *AWS_NAPI_KEY_WILDCARD_SUBSCRIPTIONS_AVAILABLE = "wildcardSubscriptionsAvailable";
|
|
static const char *AWS_NAPI_KEY_SUBSCRIPTION_IDENTIFIERS_AVAILABLE = "subscriptionIdentifiersAvailable";
|
|
static const char *AWS_NAPI_KEY_SHARED_SUBSCRIPTIONS_AVAILABLE = "sharedSubscriptionsAvailable";
|
|
static const char *AWS_NAPI_KEY_SERVER_KEEP_ALIVE = "serverKeepAlive";
|
|
static const char *AWS_NAPI_KEY_RESPONSE_INFORMATION = "responseInformation";
|
|
static const char *AWS_NAPI_KEY_SERVER_REFERENCE = "serverReference";
|
|
static const char *AWS_NAPI_KEY_RECEIVE_MAXIMUM_FROM_SERVER = "receiveMaximumFromServer";
|
|
static const char *AWS_NAPI_KEY_MAXIMUM_PACKET_SIZE_TO_SERVER = "maximumPacketSizeToServer";
|
|
static const char *AWS_NAPI_KEY_TOPIC_ALIAS_MAXIMUM_TO_SERVER = "topicAliasMaximumToServer";
|
|
static const char *AWS_NAPI_KEY_TOPIC_ALIAS_MAXIMUM_TO_CLIENT = "topicAliasMaximumToClient";
|
|
static const char *AWS_NAPI_KEY_REJOINED_SESSION = "rejoinedSession";
|
|
static const char *AWS_NAPI_KEY_CLIENT_ID = "clientId";
|
|
static const char *AWS_NAPI_KEY_SESSION_EXPIRY_INTERVAL_SECONDS = "sessionExpiryIntervalSeconds";
|
|
static const char *AWS_NAPI_KEY_TOPIC_NAME = "topicName";
|
|
static const char *AWS_NAPI_KEY_PAYLOAD = "payload";
|
|
static const char *AWS_NAPI_KEY_QOS = "qos";
|
|
static const char *AWS_NAPI_KEY_RETAIN = "retain";
|
|
static const char *AWS_NAPI_KEY_PAYLOAD_FORMAT = "payloadFormat";
|
|
static const char *AWS_NAPI_KEY_MESSAGE_EXPIRY_INTERVAL_SECONDS = "messageExpiryIntervalSeconds";
|
|
static const char *AWS_NAPI_KEY_TOPIC_ALIAS = "topicAlias";
|
|
static const char *AWS_NAPI_KEY_RESPONSE_TOPIC = "responseTopic";
|
|
static const char *AWS_NAPI_KEY_CORRELATION_DATA = "correlationData";
|
|
static const char *AWS_NAPI_KEY_CONTENT_TYPE = "contentType";
|
|
static const char *AWS_NAPI_KEY_KEEP_ALIVE_INTERVAL_SECONDS = "keepAliveIntervalSeconds";
|
|
static const char *AWS_NAPI_KEY_USERNAME = "username";
|
|
static const char *AWS_NAPI_KEY_PASSWORD = "password";
|
|
static const char *AWS_NAPI_KEY_REQUEST_RESPONSE_INFORMATION = "requestResponseInformation";
|
|
static const char *AWS_NAPI_KEY_REQUEST_PROBLEM_INFORMATION = "requestProblemInformation";
|
|
static const char *AWS_NAPI_KEY_MAXIMUM_PACKET_SIZE_BYTES = "maximumPacketSizeBytes";
|
|
static const char *AWS_NAPI_KEY_WILL_DELAY_INTERVAL_SECONDS = "willDelayIntervalSeconds";
|
|
static const char *AWS_NAPI_KEY_WILL = "will";
|
|
static const char *AWS_NAPI_KEY_HOST_NAME = "hostName";
|
|
static const char *AWS_NAPI_KEY_PORT = "port";
|
|
static const char *AWS_NAPI_KEY_SESSION_BEHAVIOR = "sessionBehavior";
|
|
static const char *AWS_NAPI_KEY_EXTENDED_VALIDATION_AND_FLOW_CONTROL_OPTIONS =
|
|
"extendedValidationAndFlowControlOptions";
|
|
static const char *AWS_NAPI_KEY_OFFLINE_QUEUE_BEHAVIOR = "offlineQueueBehavior";
|
|
static const char *AWS_NAPI_KEY_RETRY_JITTER_MODE = "retryJitterMode";
|
|
static const char *AWS_NAPI_KEY_MIN_RECONNECT_DELAY_MS = "minReconnectDelayMs";
|
|
static const char *AWS_NAPI_KEY_MAX_RECONNECT_DELAY_MS = "maxReconnectDelayMs";
|
|
static const char *AWS_NAPI_KEY_MIN_CONNECTED_TIME_TO_RESET_RECONNECT_DELAY_MS =
|
|
"minConnectedTimeToResetReconnectDelayMs";
|
|
static const char *AWS_NAPI_KEY_PING_TIMEOUT_MS = "pingTimeoutMs";
|
|
static const char *AWS_NAPI_KEY_CONNACK_TIMEOUT_MS = "connackTimeoutMs";
|
|
static const char *AWS_NAPI_KEY_ACK_TIMEOUT_SECONDS = "ackTimeoutSeconds";
|
|
static const char *AWS_NAPI_KEY_CONNECT_PROPERTIES = "connectProperties";
|
|
static const char *AWS_NAPI_KEY_WEBSOCKET_HANDSHAKE_TRANSFORM = "websocketHandshakeTransform";
|
|
static const char *AWS_NAPI_KEY_SUBSCRIPTIONS = "subscriptions";
|
|
static const char *AWS_NAPI_KEY_TOPIC_FILTER = "topicFilter";
|
|
static const char *AWS_NAPI_KEY_TOPIC_FILTERS = "topicFilters";
|
|
static const char *AWS_NAPI_KEY_NO_LOCAL = "noLocal";
|
|
static const char *AWS_NAPI_KEY_RETAIN_AS_PUBLISHED = "retainAsPublished";
|
|
static const char *AWS_NAPI_KEY_RETAIN_HANDLING_TYPE = "retainHandlingType";
|
|
static const char *AWS_NAPI_KEY_SUBSCRIPTION_IDENTIFIER = "subscriptionIdentifier";
|
|
static const char *AWS_NAPI_KEY_SUSBCRIPTION_IDENTIFIERS = "subscriptionIdentifiers";
|
|
static const char *AWS_NAPI_KEY_INCOMPLETE_OPERATION_COUNT = "incompleteOperationCount";
|
|
static const char *AWS_NAPI_KEY_INCOMPLETE_OPERATION_SIZE = "incompleteOperationSize";
|
|
static const char *AWS_NAPI_KEY_UNACKED_OPERATION_COUNT = "unackedOperationCount";
|
|
static const char *AWS_NAPI_KEY_UNACKED_OPERATION_SIZE = "unackedOperationSize";
|
|
static const char *AWS_NAPI_KEY_TYPE = "type";
|
|
static const char *AWS_NAPI_KEY_TOPIC_ALIASING_OPTIONS = "topicAliasingOptions";
|
|
static const char *AWS_NAPI_KEY_OUTBOUND_BEHAVIOR = "outboundBehavior";
|
|
static const char *AWS_NAPI_KEY_OUTBOUND_CACHE_MAX_SIZE = "outboundCacheMaxSize";
|
|
static const char *AWS_NAPI_KEY_INBOUND_BEHAVIOR = "inboundBehavior";
|
|
static const char *AWS_NAPI_KEY_INBOUND_CACHE_MAX_SIZE = "inboundCacheMaxSize";
|
|
|
|
/*
|
|
* Binding object that outlives the associated napi wrapper object. When that object finalizes, then it's a signal
|
|
* to this object to destroy the client (and itself, afterwards).
|
|
*/
|
|
struct aws_mqtt5_client_binding {
|
|
struct aws_allocator *allocator;
|
|
|
|
/*
|
|
* We ref count the binding itself because there are anomalous situations where the binding must outlive even
|
|
* the native client. In particular, if we have a native client being destroyed it may emit lifecycle events
|
|
* or completion callbacks for submitted operations as it does so. Those events get marshalled across to the
|
|
* node/libuv thread and in the time it takes to do so, the native client may have completed destruction. But
|
|
* we still need the binding when we're processing those events/callbacks in the libuv thread so the binding
|
|
* must not simply destroy itself as soon as the native client has destroyed itself.
|
|
*
|
|
* We handle this by having all operations/events inc/dec this ref count as well as the base of one from
|
|
* creating the client. In this way, the binding will only destroy itself when the native client is completely
|
|
* gone and all callbacks and events have been successfully emitted to node.
|
|
*/
|
|
struct aws_ref_count ref_count;
|
|
|
|
struct aws_mqtt5_client *client;
|
|
|
|
struct aws_tls_connection_options tls_connection_options;
|
|
|
|
/*
|
|
* Single count ref to the JS mqtt 5 client object.
|
|
*/
|
|
napi_ref node_mqtt5_client_ref;
|
|
|
|
/*
|
|
* Single count ref to the node external managed by the client.
|
|
*/
|
|
napi_ref node_client_external_ref;
|
|
|
|
napi_threadsafe_function on_stopped;
|
|
napi_threadsafe_function on_attempting_connect;
|
|
napi_threadsafe_function on_connection_success;
|
|
napi_threadsafe_function on_connection_failure;
|
|
napi_threadsafe_function on_disconnection;
|
|
napi_threadsafe_function on_message_received;
|
|
|
|
napi_threadsafe_function transform_websocket;
|
|
};
|
|
|
|
struct aws_mqtt5_client *aws_napi_get_mqtt5_client_from_binding(struct aws_mqtt5_client_binding *binding) {
|
|
if (binding == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return binding->client;
|
|
}
|
|
|
|
static void s_aws_mqtt5_client_binding_destroy(struct aws_mqtt5_client_binding *binding) {
|
|
if (binding == NULL) {
|
|
return;
|
|
}
|
|
|
|
aws_tls_connection_options_clean_up(&binding->tls_connection_options);
|
|
|
|
AWS_CLEAN_THREADSAFE_FUNCTION(binding, on_stopped);
|
|
AWS_CLEAN_THREADSAFE_FUNCTION(binding, on_attempting_connect);
|
|
AWS_CLEAN_THREADSAFE_FUNCTION(binding, on_connection_success);
|
|
AWS_CLEAN_THREADSAFE_FUNCTION(binding, on_connection_failure);
|
|
AWS_CLEAN_THREADSAFE_FUNCTION(binding, on_disconnection);
|
|
AWS_CLEAN_THREADSAFE_FUNCTION(binding, on_message_received);
|
|
AWS_CLEAN_THREADSAFE_FUNCTION(binding, transform_websocket);
|
|
|
|
aws_mem_release(binding->allocator, binding);
|
|
}
|
|
|
|
static void s_aws_mqtt5_client_binding_on_zero(void *object) {
|
|
s_aws_mqtt5_client_binding_destroy(object);
|
|
}
|
|
|
|
static struct aws_mqtt5_client_binding *s_aws_mqtt5_client_binding_acquire(struct aws_mqtt5_client_binding *binding) {
|
|
if (binding == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
aws_ref_count_acquire(&binding->ref_count);
|
|
return binding;
|
|
}
|
|
|
|
static struct aws_mqtt5_client_binding *s_aws_mqtt5_client_binding_release(struct aws_mqtt5_client_binding *binding) {
|
|
if (binding != NULL) {
|
|
aws_ref_count_release(&binding->ref_count);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void s_aws_mqtt5_client_binding_on_client_terminate(void *user_data) {
|
|
struct aws_mqtt5_client_binding *binding = user_data;
|
|
|
|
s_aws_mqtt5_client_binding_release(binding);
|
|
}
|
|
|
|
/*
|
|
* Invoked when the node mqtt5 client is garbage collected or if fails construction partway through
|
|
*/
|
|
static void s_aws_mqtt5_client_extern_finalize(napi_env env, void *finalize_data, void *finalize_hint) {
|
|
(void)finalize_hint;
|
|
(void)env;
|
|
|
|
struct aws_mqtt5_client_binding *binding = finalize_data;
|
|
|
|
AWS_LOGF_INFO(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_aws_mqtt5_client_extern_finalize - mqtt5_client node wrapper is being finalized",
|
|
(void *)binding->client);
|
|
|
|
if (binding->client != NULL) {
|
|
/* if client is not null, then this is a successfully constructed client which should shutdown normally */
|
|
aws_mqtt5_client_release(binding->client);
|
|
binding->client = NULL;
|
|
} else {
|
|
/*
|
|
* no client, this must be a creation attempt that failed partway through and we should directly clean up the
|
|
* binding
|
|
*/
|
|
s_aws_mqtt5_client_binding_on_client_terminate(binding);
|
|
}
|
|
}
|
|
|
|
struct on_message_received_user_data {
|
|
struct aws_allocator *allocator;
|
|
struct aws_mqtt5_client_binding *binding;
|
|
struct aws_mqtt5_packet_publish_storage publish_storage;
|
|
|
|
struct aws_byte_buf *payload;
|
|
struct aws_byte_buf *correlation_data;
|
|
};
|
|
|
|
static void s_on_message_received_user_data_destroy(struct on_message_received_user_data *user_data) {
|
|
if (user_data == NULL) {
|
|
return;
|
|
}
|
|
|
|
user_data->binding = s_aws_mqtt5_client_binding_release(user_data->binding);
|
|
aws_mqtt5_packet_publish_storage_clean_up(&user_data->publish_storage);
|
|
|
|
if (user_data->payload != NULL) {
|
|
aws_byte_buf_clean_up(user_data->payload);
|
|
aws_mem_release(user_data->allocator, user_data->payload);
|
|
}
|
|
|
|
if (user_data->correlation_data != NULL) {
|
|
aws_byte_buf_clean_up(user_data->correlation_data);
|
|
aws_mem_release(user_data->allocator, user_data->correlation_data);
|
|
}
|
|
|
|
aws_mem_release(user_data->allocator, user_data);
|
|
}
|
|
|
|
static struct on_message_received_user_data *s_on_message_received_user_data_new(
|
|
struct aws_mqtt5_client_binding *binding,
|
|
const struct aws_mqtt5_packet_publish_view *publish_packet) {
|
|
|
|
struct on_message_received_user_data *user_data =
|
|
aws_mem_calloc(binding->allocator, 1, sizeof(struct on_message_received_user_data));
|
|
user_data->allocator = binding->allocator;
|
|
|
|
/*
|
|
* Binary data needs to be separately pinned and tracked so that it can be individually finalized. In order to not
|
|
* make even more redundant copies of it, we do some hacky nonsense to "split" it out of the storage into separate
|
|
* buffers. We can then "take" the buffer pointers when we're calling into node and manage them separately.
|
|
*/
|
|
struct aws_mqtt5_packet_publish_view publish_copy = *publish_packet;
|
|
|
|
user_data->payload = aws_mem_calloc(binding->allocator, 1, sizeof(struct aws_byte_buf));
|
|
if (aws_byte_buf_init_copy_from_cursor(user_data->payload, binding->allocator, publish_copy.payload)) {
|
|
goto error;
|
|
}
|
|
AWS_ZERO_STRUCT(publish_copy.payload);
|
|
|
|
if (publish_copy.correlation_data != NULL) {
|
|
user_data->correlation_data = aws_mem_calloc(binding->allocator, 1, sizeof(struct aws_byte_buf));
|
|
if (aws_byte_buf_init_copy_from_cursor(
|
|
user_data->correlation_data, binding->allocator, *publish_copy.correlation_data)) {
|
|
goto error;
|
|
}
|
|
publish_copy.correlation_data = NULL;
|
|
}
|
|
|
|
/*
|
|
* We've saved off correlation data and payload separately and erased them from the packet copy. Now we can make
|
|
* a persistent copy of the packet without copying the binary data twice.
|
|
*/
|
|
if (aws_mqtt5_packet_publish_storage_init(&user_data->publish_storage, user_data->allocator, &publish_copy)) {
|
|
goto error;
|
|
}
|
|
user_data->binding = s_aws_mqtt5_client_binding_acquire(binding);
|
|
|
|
return user_data;
|
|
|
|
error:
|
|
|
|
s_on_message_received_user_data_destroy(user_data);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void s_on_publish_received(const struct aws_mqtt5_packet_publish_view *publish_packet, void *user_data) {
|
|
struct aws_mqtt5_client_binding *binding = user_data;
|
|
|
|
if (!binding->on_message_received) {
|
|
return;
|
|
}
|
|
|
|
struct on_message_received_user_data *message_received_ud =
|
|
s_on_message_received_user_data_new(binding, publish_packet);
|
|
if (message_received_ud == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* queue a callback in node's libuv thread */
|
|
AWS_NAPI_ENSURE(NULL, aws_napi_queue_threadsafe_function(binding->on_message_received, message_received_ud));
|
|
}
|
|
|
|
struct on_simple_event_user_data {
|
|
struct aws_allocator *allocator;
|
|
struct aws_mqtt5_client_binding *binding;
|
|
};
|
|
|
|
static void s_on_simple_event_user_data_destroy(struct on_simple_event_user_data *user_data) {
|
|
if (user_data == NULL) {
|
|
return;
|
|
}
|
|
|
|
user_data->binding = s_aws_mqtt5_client_binding_release(user_data->binding);
|
|
|
|
aws_mem_release(user_data->allocator, user_data);
|
|
}
|
|
|
|
static struct on_simple_event_user_data *s_on_simple_event_user_data_new(struct aws_mqtt5_client_binding *binding) {
|
|
|
|
struct on_simple_event_user_data *user_data =
|
|
aws_mem_calloc(binding->allocator, 1, sizeof(struct on_simple_event_user_data));
|
|
user_data->allocator = binding->allocator;
|
|
user_data->binding = s_aws_mqtt5_client_binding_acquire(binding);
|
|
|
|
return user_data;
|
|
}
|
|
|
|
static void s_on_stopped(struct aws_mqtt5_client_binding *binding) {
|
|
if (!binding->on_stopped) {
|
|
return;
|
|
}
|
|
|
|
/* queue a callback in node's libuv thread */
|
|
AWS_NAPI_ENSURE(
|
|
NULL, aws_napi_queue_threadsafe_function(binding->on_stopped, s_on_simple_event_user_data_new(binding)));
|
|
}
|
|
|
|
static void s_on_attempting_connect(struct aws_mqtt5_client_binding *binding) {
|
|
if (!binding->on_attempting_connect) {
|
|
return;
|
|
}
|
|
|
|
/* queue a callback in node's libuv thread */
|
|
AWS_NAPI_ENSURE(
|
|
NULL,
|
|
aws_napi_queue_threadsafe_function(binding->on_attempting_connect, s_on_simple_event_user_data_new(binding)));
|
|
}
|
|
|
|
/* unions callback data needed for connection succes and failure as a convenience */
|
|
struct on_connection_result_user_data {
|
|
struct aws_allocator *allocator;
|
|
struct aws_mqtt5_client_binding *binding;
|
|
struct aws_mqtt5_packet_connack_storage connack_storage;
|
|
bool is_connack_valid;
|
|
int error_code;
|
|
struct aws_mqtt5_negotiated_settings settings;
|
|
};
|
|
|
|
static void s_on_connection_result_user_data_destroy(struct on_connection_result_user_data *connection_result_ud) {
|
|
if (connection_result_ud == NULL) {
|
|
return;
|
|
}
|
|
|
|
connection_result_ud->binding = s_aws_mqtt5_client_binding_release(connection_result_ud->binding);
|
|
|
|
aws_mqtt5_packet_connack_storage_clean_up(&connection_result_ud->connack_storage);
|
|
aws_mqtt5_negotiated_settings_clean_up(&connection_result_ud->settings);
|
|
|
|
aws_mem_release(connection_result_ud->allocator, connection_result_ud);
|
|
}
|
|
|
|
static struct on_connection_result_user_data *s_on_connection_result_user_data_new(
|
|
struct aws_allocator *allocator,
|
|
struct aws_mqtt5_client_binding *binding,
|
|
const struct aws_mqtt5_packet_connack_view *connack,
|
|
const struct aws_mqtt5_negotiated_settings *settings,
|
|
int error_code) {
|
|
|
|
struct on_connection_result_user_data *connection_result_ud =
|
|
aws_mem_calloc(allocator, 1, sizeof(struct on_connection_result_user_data));
|
|
|
|
connection_result_ud->allocator = allocator;
|
|
connection_result_ud->error_code = error_code;
|
|
connection_result_ud->binding = s_aws_mqtt5_client_binding_acquire(binding);
|
|
|
|
if (connack != NULL) {
|
|
if (aws_mqtt5_packet_connack_storage_init(&connection_result_ud->connack_storage, allocator, connack)) {
|
|
goto error;
|
|
}
|
|
connection_result_ud->is_connack_valid = true;
|
|
}
|
|
|
|
if (settings != NULL) {
|
|
if (aws_mqtt5_negotiated_settings_copy(settings, &connection_result_ud->settings)) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
return connection_result_ud;
|
|
|
|
error:
|
|
|
|
s_on_connection_result_user_data_destroy(connection_result_ud);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void s_on_connection_success(
|
|
struct aws_mqtt5_client_binding *binding,
|
|
const struct aws_mqtt5_packet_connack_view *connack,
|
|
const struct aws_mqtt5_negotiated_settings *settings) {
|
|
|
|
if (!binding->on_connection_success) {
|
|
return;
|
|
}
|
|
|
|
struct on_connection_result_user_data *connection_result_ud =
|
|
s_on_connection_result_user_data_new(binding->allocator, binding, connack, settings, AWS_ERROR_SUCCESS);
|
|
if (connection_result_ud == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* queue a callback in node's libuv thread */
|
|
AWS_NAPI_ENSURE(NULL, aws_napi_queue_threadsafe_function(binding->on_connection_success, connection_result_ud));
|
|
}
|
|
|
|
static void s_on_connection_failure(
|
|
struct aws_mqtt5_client_binding *binding,
|
|
const struct aws_mqtt5_packet_connack_view *connack,
|
|
int error_code) {
|
|
if (!binding->on_connection_failure) {
|
|
return;
|
|
}
|
|
|
|
struct on_connection_result_user_data *connection_result_ud =
|
|
s_on_connection_result_user_data_new(binding->allocator, binding, connack, NULL, error_code);
|
|
if (connection_result_ud == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* queue a callback in node's libuv thread */
|
|
AWS_NAPI_ENSURE(NULL, aws_napi_queue_threadsafe_function(binding->on_connection_failure, connection_result_ud));
|
|
}
|
|
|
|
struct on_disconnection_user_data {
|
|
struct aws_allocator *allocator;
|
|
struct aws_mqtt5_client_binding *binding;
|
|
struct aws_mqtt5_packet_disconnect_storage disconnect_storage;
|
|
bool is_disconnect_valid;
|
|
int error_code;
|
|
};
|
|
|
|
static void s_on_disconnection_user_data_destroy(struct on_disconnection_user_data *disconnection_ud) {
|
|
if (disconnection_ud == NULL) {
|
|
return;
|
|
}
|
|
|
|
disconnection_ud->binding = s_aws_mqtt5_client_binding_release(disconnection_ud->binding);
|
|
|
|
aws_mqtt5_packet_disconnect_storage_clean_up(&disconnection_ud->disconnect_storage);
|
|
|
|
aws_mem_release(disconnection_ud->allocator, disconnection_ud);
|
|
}
|
|
|
|
static struct on_disconnection_user_data *s_on_disconnection_user_data_new(
|
|
struct aws_allocator *allocator,
|
|
struct aws_mqtt5_client_binding *binding,
|
|
const struct aws_mqtt5_packet_disconnect_view *disconnect,
|
|
int error_code) {
|
|
struct on_disconnection_user_data *disconnection_ud =
|
|
aws_mem_calloc(allocator, 1, sizeof(struct on_disconnection_user_data));
|
|
|
|
disconnection_ud->allocator = allocator;
|
|
disconnection_ud->error_code = error_code;
|
|
disconnection_ud->binding = s_aws_mqtt5_client_binding_acquire(binding);
|
|
|
|
if (disconnect != NULL) {
|
|
if (aws_mqtt5_packet_disconnect_storage_init(&disconnection_ud->disconnect_storage, allocator, disconnect)) {
|
|
goto error;
|
|
}
|
|
disconnection_ud->is_disconnect_valid = true;
|
|
}
|
|
|
|
return disconnection_ud;
|
|
|
|
error:
|
|
|
|
s_on_disconnection_user_data_destroy(disconnection_ud);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void s_on_disconnection(
|
|
struct aws_mqtt5_client_binding *binding,
|
|
const struct aws_mqtt5_packet_disconnect_view *disconnect,
|
|
int error_code) {
|
|
if (!binding->on_disconnection) {
|
|
return;
|
|
}
|
|
|
|
struct on_disconnection_user_data *disconnection_ud =
|
|
s_on_disconnection_user_data_new(binding->allocator, binding, disconnect, error_code);
|
|
if (disconnection_ud == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* queue a callback in node's libuv thread */
|
|
AWS_NAPI_ENSURE(NULL, aws_napi_queue_threadsafe_function(binding->on_disconnection, disconnection_ud));
|
|
}
|
|
|
|
static void s_lifecycle_event_callback(const struct aws_mqtt5_client_lifecycle_event *event) {
|
|
struct aws_mqtt5_client_binding *binding = event->user_data;
|
|
|
|
switch (event->event_type) {
|
|
case AWS_MQTT5_CLET_STOPPED:
|
|
s_on_stopped(binding);
|
|
break;
|
|
|
|
case AWS_MQTT5_CLET_ATTEMPTING_CONNECT:
|
|
s_on_attempting_connect(binding);
|
|
break;
|
|
|
|
case AWS_MQTT5_CLET_CONNECTION_SUCCESS:
|
|
s_on_connection_success(binding, event->connack_data, event->settings);
|
|
break;
|
|
|
|
case AWS_MQTT5_CLET_CONNECTION_FAILURE:
|
|
s_on_connection_failure(binding, event->connack_data, event->error_code);
|
|
break;
|
|
|
|
case AWS_MQTT5_CLET_DISCONNECTION:
|
|
s_on_disconnection(binding, event->disconnect_data, event->error_code);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
typedef void(napi_threadsafe_function_type)(napi_env env, napi_value function, void *context, void *user_data);
|
|
|
|
/* in-node/libuv-thread function to trigger the emission of a STOPPED client lifecycle event */
|
|
static void s_napi_on_stopped(napi_env env, napi_value function, void *context, void *user_data) {
|
|
(void)context;
|
|
|
|
struct on_simple_event_user_data *simple_ud = user_data;
|
|
struct aws_mqtt5_client_binding *binding = simple_ud->binding;
|
|
|
|
if (env) {
|
|
napi_value params[1];
|
|
const size_t num_params = AWS_ARRAY_SIZE(params);
|
|
|
|
/*
|
|
* If we can't resolve the weak ref to the mqtt5 client, then it's been garbage collected and we should not
|
|
* do anything.
|
|
*/
|
|
params[0] = NULL;
|
|
if (napi_get_reference_value(env, binding->node_mqtt5_client_ref, ¶ms[0]) != napi_ok || params[0] == NULL) {
|
|
AWS_LOGF_INFO(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_on_stopped_call - mqtt5_client node wrapper no longer resolvable",
|
|
(void *)binding->client);
|
|
goto done;
|
|
}
|
|
|
|
AWS_NAPI_ENSURE(
|
|
env, aws_napi_dispatch_threadsafe_function(env, binding->on_stopped, NULL, function, num_params, params));
|
|
}
|
|
|
|
done:
|
|
|
|
s_on_simple_event_user_data_destroy(simple_ud);
|
|
}
|
|
|
|
/* in-node/libuv-thread function to trigger the emission of an ATTEMPTING_CONNECT client lifecycle event */
|
|
static void s_napi_on_attempting_connect(napi_env env, napi_value function, void *context, void *user_data) {
|
|
(void)context;
|
|
|
|
struct on_simple_event_user_data *simple_ud = user_data;
|
|
struct aws_mqtt5_client_binding *binding = simple_ud->binding;
|
|
|
|
if (env) {
|
|
napi_value params[1];
|
|
const size_t num_params = AWS_ARRAY_SIZE(params);
|
|
|
|
/*
|
|
* If we can't resolve the weak ref to the mqtt5 client, then it's been garbage collected and we should not
|
|
* do anything.
|
|
*/
|
|
params[0] = NULL;
|
|
if (napi_get_reference_value(env, binding->node_mqtt5_client_ref, ¶ms[0]) != napi_ok || params[0] == NULL) {
|
|
AWS_LOGF_INFO(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_on_attempting_connect_call - mqtt5_client node wrapper no longer resolvable",
|
|
(void *)binding->client);
|
|
goto done;
|
|
}
|
|
|
|
AWS_NAPI_ENSURE(
|
|
env,
|
|
aws_napi_dispatch_threadsafe_function(
|
|
env, binding->on_attempting_connect, NULL, function, num_params, params));
|
|
}
|
|
|
|
done:
|
|
|
|
s_on_simple_event_user_data_destroy(simple_ud);
|
|
}
|
|
|
|
/* utility function to attach native-specified user properties to a napi object as an array of user property objects */
|
|
static int s_attach_object_property_user_properties(
|
|
napi_value napi_packet,
|
|
napi_env env,
|
|
size_t user_property_count,
|
|
const struct aws_mqtt5_user_property *user_properties) {
|
|
|
|
if (env == NULL) {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_THREADSAFE_FUNCTION_NULL_NAPI_ENV);
|
|
}
|
|
|
|
napi_value user_property_array = NULL;
|
|
AWS_NAPI_CALL(env, napi_create_array_with_length(env, user_property_count, &user_property_array), {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE);
|
|
});
|
|
|
|
for (size_t i = 0; i < user_property_count; ++i) {
|
|
const struct aws_mqtt5_user_property *property = &user_properties[i];
|
|
|
|
napi_value user_property_value = NULL;
|
|
AWS_NAPI_CALL(env, napi_create_object(env, &user_property_value), {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE);
|
|
});
|
|
|
|
if (aws_napi_attach_object_property_string(user_property_value, env, AWS_NAPI_KEY_NAME, property->name) ||
|
|
aws_napi_attach_object_property_string(user_property_value, env, AWS_NAPI_KEY_VALUE, property->value)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
AWS_NAPI_CALL(env, napi_set_element(env, user_property_array, (uint32_t)i, user_property_value), {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE);
|
|
});
|
|
}
|
|
|
|
AWS_NAPI_CALL(env, napi_set_named_property(env, napi_packet, AWS_NAPI_KEY_USER_PROPERTIES, user_property_array), {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE);
|
|
});
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
/* Builds a napi object that represents a CONNACK packet, matching the AwsMqtt5PacketConnack interface */
|
|
static int s_create_napi_connack_packet(
|
|
napi_env env,
|
|
const struct on_connection_result_user_data *connection_result_ud,
|
|
napi_value *packet_out) {
|
|
|
|
if (env == NULL) {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_THREADSAFE_FUNCTION_NULL_NAPI_ENV);
|
|
}
|
|
|
|
if (!connection_result_ud->is_connack_valid) {
|
|
AWS_NAPI_CALL(
|
|
env, napi_get_null(env, packet_out), { return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE); });
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
napi_value packet = NULL;
|
|
AWS_NAPI_CALL(
|
|
env, napi_create_object(env, &packet), { return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE); });
|
|
|
|
const struct aws_mqtt5_packet_connack_view *connack_view = &connection_result_ud->connack_storage.storage_view;
|
|
|
|
if (aws_napi_attach_object_property_u32(packet, env, AWS_NAPI_KEY_TYPE, (uint32_t)AWS_MQTT5_PT_CONNACK)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_boolean(
|
|
packet, env, AWS_NAPI_KEY_SESSION_PRESENT, connack_view->session_present)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_u32(
|
|
packet, env, AWS_NAPI_KEY_REASON_CODE, (uint32_t)connack_view->reason_code)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_u32(
|
|
packet, env, AWS_NAPI_KEY_SESSION_EXPIRY_INTERVAL, connack_view->session_expiry_interval)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_u16(
|
|
packet, env, AWS_NAPI_KEY_RECEIVE_MAXIMUM, connack_view->receive_maximum)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (connack_view->maximum_qos != NULL) {
|
|
uint32_t maximum_qos = *connack_view->maximum_qos;
|
|
if (aws_napi_attach_object_property_u32(packet, env, AWS_NAPI_KEY_MAXIMUM_QOS, maximum_qos)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_boolean(
|
|
packet, env, AWS_NAPI_KEY_RETAIN_AVAILABLE, connack_view->retain_available)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_u32(
|
|
packet, env, AWS_NAPI_KEY_MAXIMUM_PACKET_SIZE, connack_view->maximum_packet_size)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_string(
|
|
packet, env, AWS_NAPI_KEY_ASSIGNED_CLIENT_IDENTIFIER, connack_view->assigned_client_identifier)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_u16(
|
|
packet, env, AWS_NAPI_KEY_TOPIC_ALIAS_MAXIMUM, connack_view->topic_alias_maximum)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_string(
|
|
packet, env, AWS_NAPI_KEY_REASON_STRING, connack_view->reason_string)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (s_attach_object_property_user_properties(
|
|
packet, env, connack_view->user_property_count, connack_view->user_properties)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_boolean(
|
|
packet,
|
|
env,
|
|
AWS_NAPI_KEY_WILDCARD_SUBSCRIPTIONS_AVAILABLE,
|
|
connack_view->wildcard_subscriptions_available)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_boolean(
|
|
packet,
|
|
env,
|
|
AWS_NAPI_KEY_SUBSCRIPTION_IDENTIFIERS_AVAILABLE,
|
|
connack_view->subscription_identifiers_available)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_boolean(
|
|
packet, env, AWS_NAPI_KEY_SHARED_SUBSCRIPTIONS_AVAILABLE, connack_view->shared_subscriptions_available)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_u16(
|
|
packet, env, AWS_NAPI_KEY_SERVER_KEEP_ALIVE, connack_view->server_keep_alive)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_string(
|
|
packet, env, AWS_NAPI_KEY_RESPONSE_INFORMATION, connack_view->response_information)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_string(
|
|
packet, env, AWS_NAPI_KEY_SERVER_REFERENCE, connack_view->server_reference)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
*packet_out = packet;
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
/* Builds a napi object that represents connection negotiated settings, using the Mqtt5NegotiatedSettings interface */
|
|
static int s_create_napi_negotiated_settings(
|
|
napi_env env,
|
|
const struct aws_mqtt5_negotiated_settings *settings,
|
|
napi_value *value_out) {
|
|
|
|
if (env == NULL) {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_THREADSAFE_FUNCTION_NULL_NAPI_ENV);
|
|
}
|
|
|
|
napi_value napi_settings = NULL;
|
|
AWS_NAPI_CALL(
|
|
env, napi_create_object(env, &napi_settings), { return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE); });
|
|
|
|
uint32_t maximum_qos = settings->maximum_qos;
|
|
if (aws_napi_attach_object_property_u32(napi_settings, env, AWS_NAPI_KEY_MAXIMUM_QOS, maximum_qos)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_u32(
|
|
napi_settings, env, AWS_NAPI_KEY_SESSION_EXPIRY_INTERVAL, settings->session_expiry_interval)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_u32(
|
|
napi_settings,
|
|
env,
|
|
AWS_NAPI_KEY_RECEIVE_MAXIMUM_FROM_SERVER,
|
|
(uint32_t)settings->receive_maximum_from_server)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_u32(
|
|
napi_settings, env, AWS_NAPI_KEY_MAXIMUM_PACKET_SIZE_TO_SERVER, settings->maximum_packet_size_to_server)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_u16(
|
|
napi_settings, env, AWS_NAPI_KEY_TOPIC_ALIAS_MAXIMUM_TO_SERVER, settings->topic_alias_maximum_to_server)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_u16(
|
|
napi_settings, env, AWS_NAPI_KEY_TOPIC_ALIAS_MAXIMUM_TO_CLIENT, settings->topic_alias_maximum_to_client)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_u32(
|
|
napi_settings, env, AWS_NAPI_KEY_SERVER_KEEP_ALIVE, (uint32_t)settings->server_keep_alive)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_boolean(
|
|
napi_settings, env, AWS_NAPI_KEY_RETAIN_AVAILABLE, settings->retain_available)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_boolean(
|
|
napi_settings,
|
|
env,
|
|
AWS_NAPI_KEY_WILDCARD_SUBSCRIPTIONS_AVAILABLE,
|
|
settings->wildcard_subscriptions_available)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_boolean(
|
|
napi_settings,
|
|
env,
|
|
AWS_NAPI_KEY_SUBSCRIPTION_IDENTIFIERS_AVAILABLE,
|
|
settings->subscription_identifiers_available)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_boolean(
|
|
napi_settings,
|
|
env,
|
|
AWS_NAPI_KEY_SHARED_SUBSCRIPTIONS_AVAILABLE,
|
|
settings->shared_subscriptions_available)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_boolean(
|
|
napi_settings, env, AWS_NAPI_KEY_REJOINED_SESSION, settings->rejoined_session)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_string(
|
|
napi_settings, env, AWS_NAPI_KEY_CLIENT_ID, aws_byte_cursor_from_buf(&settings->client_id_storage))) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
*value_out = napi_settings;
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
/* in-node/libuv-thread function to trigger the emission of a CONNECTION_SUCCESS client lifecycle event */
|
|
static void s_napi_on_connection_success(napi_env env, napi_value function, void *context, void *user_data) {
|
|
(void)context;
|
|
|
|
struct on_connection_result_user_data *connection_result_ud = user_data;
|
|
struct aws_mqtt5_client_binding *binding = connection_result_ud->binding;
|
|
|
|
if (env) {
|
|
napi_value params[3];
|
|
const size_t num_params = AWS_ARRAY_SIZE(params);
|
|
|
|
/*
|
|
* If we can't resolve the weak ref to the mqtt5 client, then it's been garbage collected and we should not
|
|
* do anything.
|
|
*/
|
|
params[0] = NULL;
|
|
if (napi_get_reference_value(env, binding->node_mqtt5_client_ref, ¶ms[0]) != napi_ok || params[0] == NULL) {
|
|
AWS_LOGF_INFO(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_on_connection_success_call - mqtt5_client node wrapper no longer resolvable",
|
|
(void *)binding->client);
|
|
goto done;
|
|
}
|
|
|
|
if (s_create_napi_connack_packet(env, connection_result_ud, ¶ms[1])) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_on_connection_success_call - failed to create connack object",
|
|
(void *)binding->client);
|
|
goto done;
|
|
}
|
|
|
|
if (s_create_napi_negotiated_settings(env, &connection_result_ud->settings, ¶ms[2])) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_on_connection_success_call - failed to create negotiated settings object",
|
|
(void *)binding->client);
|
|
goto done;
|
|
}
|
|
|
|
AWS_NAPI_ENSURE(
|
|
env,
|
|
aws_napi_dispatch_threadsafe_function(
|
|
env, binding->on_connection_success, NULL, function, num_params, params));
|
|
}
|
|
|
|
done:
|
|
|
|
s_on_connection_result_user_data_destroy(connection_result_ud);
|
|
}
|
|
|
|
/* in-node/libuv-thread function to trigger the emission of a CONNECTION_FAILURE client lifecycle event */
|
|
static void s_napi_on_connection_failure(napi_env env, napi_value function, void *context, void *user_data) {
|
|
(void)context;
|
|
|
|
struct on_connection_result_user_data *connection_result_ud = user_data;
|
|
struct aws_mqtt5_client_binding *binding = connection_result_ud->binding;
|
|
|
|
if (env) {
|
|
napi_value params[3];
|
|
const size_t num_params = AWS_ARRAY_SIZE(params);
|
|
|
|
/*
|
|
* If we can't resolve the weak ref to the mqtt5 client, then it's been garbage collected and we should not
|
|
* do anything.
|
|
*/
|
|
params[0] = NULL;
|
|
if (napi_get_reference_value(env, binding->node_mqtt5_client_ref, ¶ms[0]) != napi_ok || params[0] == NULL) {
|
|
AWS_LOGF_INFO(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_on_connection_failure_call - mqtt5_client node wrapper no longer resolvable",
|
|
(void *)binding->client);
|
|
goto done;
|
|
}
|
|
|
|
AWS_NAPI_CALL(env, napi_create_uint32(env, connection_result_ud->error_code, ¶ms[1]), { goto done; });
|
|
|
|
if (s_create_napi_connack_packet(env, connection_result_ud, ¶ms[2])) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_on_connection_failure_call - failed to create connack object",
|
|
(void *)binding->client);
|
|
goto done;
|
|
}
|
|
|
|
AWS_NAPI_ENSURE(
|
|
env,
|
|
aws_napi_dispatch_threadsafe_function(
|
|
env, binding->on_connection_failure, NULL, function, num_params, params));
|
|
}
|
|
|
|
done:
|
|
|
|
s_on_connection_result_user_data_destroy(connection_result_ud);
|
|
}
|
|
|
|
/* Builds a napi object that represents DISCONNECT packet, using the AwsMqtt5PacketDisconnect interface */
|
|
static int s_create_napi_disconnect_packet(
|
|
napi_env env,
|
|
const struct on_disconnection_user_data *disconnection_ud,
|
|
napi_value *packet_out) {
|
|
|
|
if (env == NULL) {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_THREADSAFE_FUNCTION_NULL_NAPI_ENV);
|
|
}
|
|
|
|
if (!disconnection_ud->is_disconnect_valid) {
|
|
AWS_NAPI_CALL(
|
|
env, napi_get_null(env, packet_out), { return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE); });
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
napi_value packet = NULL;
|
|
AWS_NAPI_CALL(
|
|
env, napi_create_object(env, &packet), { return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE); });
|
|
|
|
if (aws_napi_attach_object_property_u32(packet, env, AWS_NAPI_KEY_TYPE, (uint32_t)AWS_MQTT5_PT_DISCONNECT)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
const struct aws_mqtt5_packet_disconnect_view *disconnect_view = &disconnection_ud->disconnect_storage.storage_view;
|
|
|
|
if (aws_napi_attach_object_property_u32(packet, env, AWS_NAPI_KEY_REASON_CODE, disconnect_view->reason_code)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_u32(
|
|
packet,
|
|
env,
|
|
AWS_NAPI_KEY_SESSION_EXPIRY_INTERVAL_SECONDS,
|
|
disconnect_view->session_expiry_interval_seconds)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_string(
|
|
packet, env, AWS_NAPI_KEY_REASON_STRING, disconnect_view->reason_string)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (s_attach_object_property_user_properties(
|
|
packet, env, disconnect_view->user_property_count, disconnect_view->user_properties)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
*packet_out = packet;
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
/* in-node/libuv-thread function to trigger the emission of a DISCONNECTION client lifecycle event */
|
|
static void s_napi_on_disconnection(napi_env env, napi_value function, void *context, void *user_data) {
|
|
(void)context;
|
|
|
|
struct on_disconnection_user_data *disconnection_ud = user_data;
|
|
struct aws_mqtt5_client_binding *binding = disconnection_ud->binding;
|
|
|
|
if (env) {
|
|
napi_value params[3];
|
|
const size_t num_params = AWS_ARRAY_SIZE(params);
|
|
|
|
/*
|
|
* If we can't resolve the weak ref to the mqtt5 client, then it's been garbage collected and we should not
|
|
* do anything.
|
|
*/
|
|
params[0] = NULL;
|
|
if (napi_get_reference_value(env, binding->node_mqtt5_client_ref, ¶ms[0]) != napi_ok || params[0] == NULL) {
|
|
AWS_LOGF_INFO(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_on_disconnection_call - mqtt5_client node wrapper no longer resolvable",
|
|
(void *)binding->client);
|
|
goto done;
|
|
}
|
|
|
|
AWS_NAPI_CALL(env, napi_create_uint32(env, disconnection_ud->error_code, ¶ms[1]), { goto done; });
|
|
|
|
if (s_create_napi_disconnect_packet(env, disconnection_ud, ¶ms[2])) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_on_disconnection_call - failed to create disconnect object",
|
|
(void *)binding->client);
|
|
goto done;
|
|
}
|
|
|
|
AWS_NAPI_ENSURE(
|
|
env,
|
|
aws_napi_dispatch_threadsafe_function(env, binding->on_disconnection, NULL, function, num_params, params));
|
|
}
|
|
|
|
done:
|
|
|
|
s_on_disconnection_user_data_destroy(disconnection_ud);
|
|
}
|
|
|
|
static int s_create_napi_publish_packet(
|
|
napi_env env,
|
|
struct on_message_received_user_data *message_received_ud,
|
|
napi_value *packet_out) {
|
|
|
|
if (env == NULL) {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_THREADSAFE_FUNCTION_NULL_NAPI_ENV);
|
|
}
|
|
|
|
const struct aws_mqtt5_packet_publish_view *publish_view = &message_received_ud->publish_storage.storage_view;
|
|
|
|
napi_value packet = NULL;
|
|
AWS_NAPI_CALL(
|
|
env, napi_create_object(env, &packet), { return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE); });
|
|
|
|
if (aws_napi_attach_object_property_u32(packet, env, AWS_NAPI_KEY_TYPE, (uint32_t)AWS_MQTT5_PT_PUBLISH)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_string(packet, env, AWS_NAPI_KEY_TOPIC_NAME, publish_view->topic)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_binary_as_finalizable_external(
|
|
packet, env, AWS_NAPI_KEY_PAYLOAD, message_received_ud->payload)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
message_received_ud->payload = NULL;
|
|
|
|
if (aws_napi_attach_object_property_u32(packet, env, AWS_NAPI_KEY_QOS, (uint32_t)publish_view->qos)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_boolean(packet, env, AWS_NAPI_KEY_RETAIN, publish_view->retain)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (publish_view->payload_format != NULL) {
|
|
if (aws_napi_attach_object_property_u32(
|
|
packet, env, AWS_NAPI_KEY_PAYLOAD_FORMAT, (uint32_t)(*publish_view->payload_format))) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_u32(
|
|
packet, env, AWS_NAPI_KEY_MESSAGE_EXPIRY_INTERVAL_SECONDS, publish_view->message_expiry_interval_seconds)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_u16(
|
|
packet, env, AWS_NAPI_KEY_TOPIC_ALIAS, publish_view->topic_alias)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_string(
|
|
packet, env, AWS_NAPI_KEY_RESPONSE_TOPIC, publish_view->response_topic)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (message_received_ud->correlation_data != NULL) {
|
|
if (aws_napi_attach_object_property_binary_as_finalizable_external(
|
|
packet, env, AWS_NAPI_KEY_CORRELATION_DATA, message_received_ud->correlation_data)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
message_received_ud->correlation_data = NULL;
|
|
}
|
|
|
|
if (publish_view->subscription_identifier_count > 0) {
|
|
napi_value subscription_identifier_array = NULL;
|
|
AWS_NAPI_CALL(
|
|
env,
|
|
napi_create_array_with_length(
|
|
env, publish_view->subscription_identifier_count, &subscription_identifier_array),
|
|
{ return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE); });
|
|
|
|
for (size_t i = 0; i < publish_view->subscription_identifier_count; ++i) {
|
|
uint32_t subscription_identifier = publish_view->subscription_identifiers[i];
|
|
|
|
napi_value napi_subscription_identifier = NULL;
|
|
AWS_NAPI_CALL(env, napi_create_uint32(env, subscription_identifier, &napi_subscription_identifier), {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE);
|
|
});
|
|
|
|
AWS_NAPI_CALL(
|
|
env, napi_set_element(env, subscription_identifier_array, (uint32_t)i, napi_subscription_identifier), {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE);
|
|
});
|
|
}
|
|
|
|
AWS_NAPI_CALL(
|
|
env,
|
|
napi_set_named_property(env, packet, AWS_NAPI_KEY_SUSBCRIPTION_IDENTIFIERS, subscription_identifier_array),
|
|
{ return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE); });
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_string(
|
|
packet, env, AWS_NAPI_KEY_CONTENT_TYPE, publish_view->content_type)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (s_attach_object_property_user_properties(
|
|
packet, env, publish_view->user_property_count, publish_view->user_properties)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
*packet_out = packet;
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
/* in-node/libuv-thread function to trigger the emission of a PUBLISH packet on the messageReceived event */
|
|
static void s_napi_on_message_received(napi_env env, napi_value function, void *context, void *user_data) {
|
|
(void)context;
|
|
|
|
struct on_message_received_user_data *on_message_received_ud = user_data;
|
|
struct aws_mqtt5_client_binding *binding = on_message_received_ud->binding;
|
|
|
|
if (env) {
|
|
napi_value params[2];
|
|
const size_t num_params = AWS_ARRAY_SIZE(params);
|
|
|
|
/*
|
|
* If we can't resolve the weak ref to the mqtt5 client, then it's been garbage collected and we should not
|
|
* do anything.
|
|
*/
|
|
params[0] = NULL;
|
|
if (napi_get_reference_value(env, binding->node_mqtt5_client_ref, ¶ms[0]) != napi_ok || params[0] == NULL) {
|
|
AWS_LOGF_INFO(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_napi_on_message_received - mqtt5_client node wrapper no longer resolvable",
|
|
(void *)binding->client);
|
|
goto done;
|
|
}
|
|
|
|
if (s_create_napi_publish_packet(env, on_message_received_ud, ¶ms[1])) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_napi_on_message_received - failed to create publish object",
|
|
(void *)binding->client);
|
|
goto done;
|
|
}
|
|
|
|
AWS_NAPI_ENSURE(
|
|
env,
|
|
aws_napi_dispatch_threadsafe_function(
|
|
env, binding->on_message_received, NULL, function, num_params, params));
|
|
}
|
|
|
|
done:
|
|
|
|
s_on_message_received_user_data_destroy(on_message_received_ud);
|
|
}
|
|
|
|
/*
|
|
* Persistent storage for user properties.
|
|
*/
|
|
struct aws_napi_mqtt5_user_property_storage {
|
|
struct aws_array_list user_properties;
|
|
struct aws_byte_buf user_property_storage;
|
|
};
|
|
|
|
/* Extract a set of user properties from a Napi object. */
|
|
static int s_aws_mqtt5_user_properties_extract_from_js_object(
|
|
struct aws_mqtt5_client_binding *binding,
|
|
struct aws_napi_mqtt5_user_property_storage *user_properties_storage,
|
|
napi_env env,
|
|
napi_value node_container,
|
|
size_t *user_property_count_out,
|
|
const struct aws_mqtt5_user_property **user_properties_out) {
|
|
|
|
napi_value node_user_properties = NULL;
|
|
enum aws_napi_get_named_property_result gpr = aws_napi_get_named_property(
|
|
env, node_container, AWS_NAPI_KEY_USER_PROPERTIES, napi_object, &node_user_properties);
|
|
if (gpr != AWS_NGNPR_VALID_VALUE) {
|
|
return (gpr == AWS_NGNPR_NO_VALUE) ? AWS_OP_SUCCESS : AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_is_null_or_undefined(env, node_user_properties)) {
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
struct aws_allocator *allocator = aws_napi_get_allocator();
|
|
|
|
/* len of js array */
|
|
uint32_t user_property_count = 0;
|
|
AWS_NAPI_CALL(env, napi_get_array_length(env, node_user_properties, &user_property_count), {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_aws_mqtt5_user_properties_extract_from_js_object - user properties is not an array",
|
|
(void *)binding->client);
|
|
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
|
|
});
|
|
|
|
/* compute storage size */
|
|
size_t total_property_length = 0;
|
|
for (uint32_t i = 0; i < user_property_count; ++i) {
|
|
napi_value array_element;
|
|
AWS_NAPI_CALL(env, napi_get_element(env, node_user_properties, i, &array_element), {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_aws_mqtt5_user_properties_extract_from_js_object - user properties is not indexable",
|
|
(void *)binding->client);
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE);
|
|
});
|
|
|
|
struct aws_byte_buf name_buf;
|
|
AWS_ZERO_STRUCT(name_buf);
|
|
struct aws_byte_buf value_buf;
|
|
AWS_ZERO_STRUCT(value_buf);
|
|
|
|
enum aws_napi_get_named_property_result name_gpr =
|
|
aws_napi_get_named_property_as_bytebuf(env, array_element, AWS_NAPI_KEY_NAME, napi_string, &name_buf);
|
|
enum aws_napi_get_named_property_result value_gpr =
|
|
aws_napi_get_named_property_as_bytebuf(env, array_element, AWS_NAPI_KEY_VALUE, napi_string, &value_buf);
|
|
|
|
total_property_length += name_buf.len + value_buf.len;
|
|
|
|
aws_byte_buf_clean_up(&name_buf);
|
|
aws_byte_buf_clean_up(&value_buf);
|
|
|
|
if (name_gpr != AWS_NGNPR_VALID_VALUE || value_gpr != AWS_NGNPR_VALID_VALUE) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_aws_mqtt5_user_properties_extract_from_js_object - malformed property name/value pair",
|
|
(void *)binding->client);
|
|
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
|
|
}
|
|
}
|
|
|
|
/* allocate space */
|
|
if (aws_array_list_init_dynamic(
|
|
&user_properties_storage->user_properties,
|
|
allocator,
|
|
user_property_count,
|
|
sizeof(struct aws_mqtt5_user_property))) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_byte_buf_init(&user_properties_storage->user_property_storage, allocator, total_property_length)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
/* persist each property */
|
|
for (uint32_t i = 0; i < user_property_count; ++i) {
|
|
napi_value array_element;
|
|
AWS_NAPI_CALL(env, napi_get_element(env, node_user_properties, i, &array_element), {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE);
|
|
});
|
|
|
|
struct aws_byte_buf name_buf;
|
|
AWS_ZERO_STRUCT(name_buf);
|
|
struct aws_byte_buf value_buf;
|
|
AWS_ZERO_STRUCT(value_buf);
|
|
|
|
aws_napi_get_named_property_as_bytebuf(env, array_element, AWS_NAPI_KEY_NAME, napi_string, &name_buf);
|
|
aws_napi_get_named_property_as_bytebuf(env, array_element, AWS_NAPI_KEY_VALUE, napi_string, &value_buf);
|
|
|
|
struct aws_mqtt5_user_property user_property;
|
|
AWS_ZERO_STRUCT(user_property);
|
|
|
|
user_property.name = aws_byte_cursor_from_buf(&name_buf);
|
|
user_property.value = aws_byte_cursor_from_buf(&value_buf);
|
|
|
|
bool success =
|
|
aws_byte_buf_append_and_update(&user_properties_storage->user_property_storage, &user_property.name) ==
|
|
AWS_OP_SUCCESS &&
|
|
aws_byte_buf_append_and_update(&user_properties_storage->user_property_storage, &user_property.value) ==
|
|
AWS_OP_SUCCESS;
|
|
|
|
aws_byte_buf_clean_up(&name_buf);
|
|
aws_byte_buf_clean_up(&value_buf);
|
|
|
|
if (!success) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
aws_array_list_push_back(&user_properties_storage->user_properties, &user_property);
|
|
}
|
|
|
|
*user_property_count_out = user_property_count;
|
|
*user_properties_out = user_properties_storage->user_properties.data;
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
static void s_aws_mqtt5_user_properties_clean_up(struct aws_napi_mqtt5_user_property_storage *user_properties_storage) {
|
|
aws_array_list_clean_up(&user_properties_storage->user_properties);
|
|
aws_byte_buf_clean_up(&user_properties_storage->user_property_storage);
|
|
}
|
|
|
|
/*
|
|
* Persistent storage for a publish packet.
|
|
*/
|
|
struct aws_napi_mqtt5_publish_storage {
|
|
struct aws_byte_buf topic;
|
|
struct aws_byte_buf payload;
|
|
|
|
enum aws_mqtt5_payload_format_indicator payload_format;
|
|
uint32_t message_expiry_interval_seconds;
|
|
uint16_t topic_alias;
|
|
|
|
struct aws_byte_buf response_topic;
|
|
struct aws_byte_cursor response_topic_cursor;
|
|
|
|
struct aws_byte_buf correlation_data;
|
|
struct aws_byte_cursor correlation_data_cursor;
|
|
|
|
struct aws_byte_buf content_type;
|
|
struct aws_byte_cursor content_type_cursor;
|
|
|
|
struct aws_napi_mqtt5_user_property_storage user_properties;
|
|
};
|
|
|
|
static void s_aws_napi_mqtt5_publish_storage_clean_up(struct aws_napi_mqtt5_publish_storage *storage) {
|
|
aws_byte_buf_clean_up(&storage->topic);
|
|
aws_byte_buf_clean_up(&storage->payload);
|
|
aws_byte_buf_clean_up(&storage->response_topic);
|
|
aws_byte_buf_clean_up(&storage->correlation_data);
|
|
aws_byte_buf_clean_up(&storage->content_type);
|
|
|
|
s_aws_mqtt5_user_properties_clean_up(&storage->user_properties);
|
|
}
|
|
|
|
static void s_log_get_property_error(
|
|
void *context,
|
|
const char *function_name,
|
|
const char *message,
|
|
const char *property_name) {
|
|
AWS_LOGF_ERROR(AWS_LS_NODEJS_CRT_GENERAL, "id=%p %s - %s: %s", context, function_name, message, property_name);
|
|
}
|
|
|
|
#define PARSE_REQUIRED_NAPI_PROPERTY(property_name, function_name, call_expression, success_block) \
|
|
{ \
|
|
enum aws_napi_get_named_property_result gpr = call_expression; \
|
|
if (gpr == AWS_NGNPR_VALID_VALUE) { \
|
|
success_block; \
|
|
} else if (gpr == AWS_NGNPR_INVALID_VALUE) { \
|
|
s_log_get_property_error( \
|
|
(void *)binding->client, function_name, "invalid value for property", property_name); \
|
|
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); \
|
|
} else { \
|
|
s_log_get_property_error( \
|
|
(void *)binding->client, function_name, "failed to extract required property", property_name); \
|
|
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); \
|
|
} \
|
|
}
|
|
|
|
#define PARSE_OPTIONAL_NAPI_PROPERTY(property_name, function_name, call_expression, success_block) \
|
|
{ \
|
|
enum aws_napi_get_named_property_result gpr = call_expression; \
|
|
if (gpr == AWS_NGNPR_VALID_VALUE) { \
|
|
success_block; \
|
|
} else if (gpr == AWS_NGNPR_INVALID_VALUE) { \
|
|
s_log_get_property_error( \
|
|
(void *)binding->client, function_name, "invalid value for property", property_name); \
|
|
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); \
|
|
} \
|
|
}
|
|
|
|
/* Extract a PUBLISH packet view from a Napi object (AwsMqtt5PacketPublish) and persist its data in storage. */
|
|
static int s_init_publish_options_from_napi(
|
|
struct aws_mqtt5_client_binding *binding,
|
|
napi_env env,
|
|
napi_value node_publish_config,
|
|
struct aws_mqtt5_packet_publish_view *publish_options,
|
|
struct aws_napi_mqtt5_publish_storage *publish_storage) {
|
|
|
|
PARSE_REQUIRED_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_TOPIC_NAME,
|
|
"s_init_publish_options_from_napi",
|
|
aws_napi_get_named_property_as_bytebuf(
|
|
env, node_publish_config, AWS_NAPI_KEY_TOPIC_NAME, napi_string, &publish_storage->topic),
|
|
{ publish_options->topic = aws_byte_cursor_from_buf(&publish_storage->topic); });
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_PAYLOAD,
|
|
"s_init_publish_options_from_napi",
|
|
aws_napi_get_named_property_as_bytebuf(
|
|
env, node_publish_config, AWS_NAPI_KEY_PAYLOAD, napi_undefined, &publish_storage->payload),
|
|
{ publish_options->payload = aws_byte_cursor_from_buf(&publish_storage->payload); });
|
|
|
|
uint32_t qos = 0;
|
|
PARSE_REQUIRED_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_QOS,
|
|
"s_init_publish_options_from_napi",
|
|
aws_napi_get_named_property_as_uint32(env, node_publish_config, AWS_NAPI_KEY_QOS, &qos),
|
|
{ publish_options->qos = qos; });
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_RETAIN,
|
|
"s_init_publish_options_from_napi",
|
|
aws_napi_get_named_property_as_boolean(env, node_publish_config, AWS_NAPI_KEY_RETAIN, &publish_options->retain),
|
|
{});
|
|
|
|
uint32_t payload_format = 0;
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_PAYLOAD_FORMAT,
|
|
"s_init_publish_options_from_napi",
|
|
aws_napi_get_named_property_as_uint32(env, node_publish_config, AWS_NAPI_KEY_PAYLOAD_FORMAT, &payload_format),
|
|
{
|
|
publish_storage->payload_format = payload_format;
|
|
publish_options->payload_format = &publish_storage->payload_format;
|
|
});
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_MESSAGE_EXPIRY_INTERVAL_SECONDS,
|
|
"s_init_publish_options_from_napi",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env,
|
|
node_publish_config,
|
|
AWS_NAPI_KEY_MESSAGE_EXPIRY_INTERVAL_SECONDS,
|
|
&publish_storage->message_expiry_interval_seconds),
|
|
{ publish_options->message_expiry_interval_seconds = &publish_storage->message_expiry_interval_seconds; });
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_TOPIC_ALIAS,
|
|
"s_init_publish_options_from_napi",
|
|
aws_napi_get_named_property_as_uint16(
|
|
env, node_publish_config, AWS_NAPI_KEY_TOPIC_ALIAS, &publish_storage->topic_alias),
|
|
{ publish_options->topic_alias = &publish_storage->topic_alias; });
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_RESPONSE_TOPIC,
|
|
"s_init_publish_options_from_napi",
|
|
aws_napi_get_named_property_as_bytebuf(
|
|
env, node_publish_config, AWS_NAPI_KEY_RESPONSE_TOPIC, napi_string, &publish_storage->response_topic),
|
|
{
|
|
publish_storage->response_topic_cursor = aws_byte_cursor_from_buf(&publish_storage->response_topic);
|
|
publish_options->response_topic = &publish_storage->response_topic_cursor;
|
|
});
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_CORRELATION_DATA,
|
|
"s_init_publish_options_from_napi",
|
|
aws_napi_get_named_property_as_bytebuf(
|
|
env,
|
|
node_publish_config,
|
|
AWS_NAPI_KEY_CORRELATION_DATA,
|
|
napi_undefined,
|
|
&publish_storage->correlation_data),
|
|
{
|
|
publish_storage->correlation_data_cursor = aws_byte_cursor_from_buf(&publish_storage->correlation_data);
|
|
publish_options->correlation_data = &publish_storage->correlation_data_cursor;
|
|
});
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_CONTENT_TYPE,
|
|
"s_init_publish_options_from_napi",
|
|
aws_napi_get_named_property_as_bytebuf(
|
|
env, node_publish_config, AWS_NAPI_KEY_CONTENT_TYPE, napi_string, &publish_storage->content_type),
|
|
{
|
|
publish_storage->content_type_cursor = aws_byte_cursor_from_buf(&publish_storage->content_type);
|
|
publish_options->content_type = &publish_storage->content_type_cursor;
|
|
});
|
|
|
|
if (s_aws_mqtt5_user_properties_extract_from_js_object(
|
|
binding,
|
|
&publish_storage->user_properties,
|
|
env,
|
|
node_publish_config,
|
|
&publish_options->user_property_count,
|
|
&publish_options->user_properties)) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_init_publish_options_from_napi - failed to extract userProperties",
|
|
(void *)binding->client);
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
/* Persistent storage for a CONNECT packet. */
|
|
struct aws_napi_mqtt5_connect_storage {
|
|
struct aws_byte_buf client_id;
|
|
struct aws_byte_cursor client_id_cursor;
|
|
|
|
struct aws_byte_buf username;
|
|
struct aws_byte_cursor username_cursor;
|
|
|
|
struct aws_byte_buf password;
|
|
struct aws_byte_cursor password_cursor;
|
|
|
|
uint32_t session_expiry_interval_seconds;
|
|
uint8_t request_response_information;
|
|
uint8_t request_problem_information;
|
|
uint16_t receive_maximum;
|
|
uint32_t maximum_packet_size_bytes;
|
|
uint32_t will_delay_interval_seconds;
|
|
|
|
struct aws_napi_mqtt5_publish_storage will_storage;
|
|
|
|
struct aws_napi_mqtt5_user_property_storage user_properties;
|
|
};
|
|
|
|
static void s_aws_napi_mqtt5_connect_storage_clean_up(struct aws_napi_mqtt5_connect_storage *storage) {
|
|
aws_byte_buf_clean_up(&storage->client_id);
|
|
aws_byte_buf_clean_up(&storage->username);
|
|
aws_byte_buf_clean_up(&storage->password);
|
|
|
|
s_aws_napi_mqtt5_publish_storage_clean_up(&storage->will_storage);
|
|
|
|
s_aws_mqtt5_user_properties_clean_up(&storage->user_properties);
|
|
}
|
|
|
|
/* Extract a CONNECT packet view from a Napi object (AwsMqtt5PacketConnect) and persist its data in storage. */
|
|
static int s_init_connect_options_from_napi(
|
|
struct aws_mqtt5_client_binding *binding,
|
|
napi_env env,
|
|
napi_value node_connect_config,
|
|
struct aws_mqtt5_packet_connect_view *connect_options,
|
|
struct aws_mqtt5_packet_publish_view *will_options,
|
|
struct aws_napi_mqtt5_connect_storage *connect_storage) {
|
|
|
|
PARSE_REQUIRED_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_KEEP_ALIVE_INTERVAL_SECONDS,
|
|
"s_init_connect_options_from_napi",
|
|
aws_napi_get_named_property_as_uint16(
|
|
env,
|
|
node_connect_config,
|
|
AWS_NAPI_KEY_KEEP_ALIVE_INTERVAL_SECONDS,
|
|
&connect_options->keep_alive_interval_seconds),
|
|
{});
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_CLIENT_ID,
|
|
"s_init_connect_options_from_napi",
|
|
aws_napi_get_named_property_as_bytebuf(
|
|
env, node_connect_config, AWS_NAPI_KEY_CLIENT_ID, napi_string, &connect_storage->client_id),
|
|
{ connect_options->client_id = aws_byte_cursor_from_buf(&connect_storage->client_id); });
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_USERNAME,
|
|
"s_init_connect_options_from_napi",
|
|
aws_napi_get_named_property_as_bytebuf(
|
|
env, node_connect_config, AWS_NAPI_KEY_USERNAME, napi_string, &connect_storage->username),
|
|
{
|
|
connect_storage->username_cursor = aws_byte_cursor_from_buf(&connect_storage->username);
|
|
connect_options->username = &connect_storage->username_cursor;
|
|
});
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_PASSWORD,
|
|
"s_init_connect_options_from_napi",
|
|
aws_napi_get_named_property_as_bytebuf(
|
|
env, node_connect_config, AWS_NAPI_KEY_PASSWORD, napi_undefined, &connect_storage->password),
|
|
{
|
|
connect_storage->password_cursor = aws_byte_cursor_from_buf(&connect_storage->password);
|
|
connect_options->password = &connect_storage->password_cursor;
|
|
});
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_SESSION_EXPIRY_INTERVAL_SECONDS,
|
|
"s_init_connect_options_from_napi",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env,
|
|
node_connect_config,
|
|
AWS_NAPI_KEY_SESSION_EXPIRY_INTERVAL_SECONDS,
|
|
&connect_storage->session_expiry_interval_seconds),
|
|
{ connect_options->session_expiry_interval_seconds = &connect_storage->session_expiry_interval_seconds; });
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_REQUEST_RESPONSE_INFORMATION,
|
|
"s_init_connect_options_from_napi",
|
|
aws_napi_get_named_property_boolean_as_uint8(
|
|
env,
|
|
node_connect_config,
|
|
AWS_NAPI_KEY_REQUEST_RESPONSE_INFORMATION,
|
|
&connect_storage->request_response_information),
|
|
{ connect_options->request_response_information = &connect_storage->request_response_information; });
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_REQUEST_PROBLEM_INFORMATION,
|
|
"s_init_connect_options_from_napi",
|
|
aws_napi_get_named_property_boolean_as_uint8(
|
|
env,
|
|
node_connect_config,
|
|
AWS_NAPI_KEY_REQUEST_PROBLEM_INFORMATION,
|
|
&connect_storage->request_problem_information),
|
|
{ connect_options->request_problem_information = &connect_storage->request_problem_information; });
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_RECEIVE_MAXIMUM,
|
|
"s_init_connect_options_from_napi",
|
|
aws_napi_get_named_property_as_uint16(
|
|
env, node_connect_config, AWS_NAPI_KEY_RECEIVE_MAXIMUM, &connect_storage->receive_maximum),
|
|
{ connect_options->receive_maximum = &connect_storage->receive_maximum; });
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_MAXIMUM_PACKET_SIZE_BYTES,
|
|
"s_init_connect_options_from_napi",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env,
|
|
node_connect_config,
|
|
AWS_NAPI_KEY_MAXIMUM_PACKET_SIZE_BYTES,
|
|
&connect_storage->maximum_packet_size_bytes),
|
|
{ connect_options->maximum_packet_size_bytes = &connect_storage->maximum_packet_size_bytes; });
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_WILL_DELAY_INTERVAL_SECONDS,
|
|
"s_init_connect_options_from_napi",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env,
|
|
node_connect_config,
|
|
AWS_NAPI_KEY_WILL_DELAY_INTERVAL_SECONDS,
|
|
&connect_storage->will_delay_interval_seconds),
|
|
{ connect_options->will_delay_interval_seconds = &connect_storage->will_delay_interval_seconds; });
|
|
|
|
napi_value napi_will = NULL;
|
|
if (AWS_NGNPR_VALID_VALUE ==
|
|
aws_napi_get_named_property(env, node_connect_config, AWS_NAPI_KEY_WILL, napi_object, &napi_will)) {
|
|
if (!aws_napi_is_null_or_undefined(env, napi_will)) {
|
|
if (s_init_publish_options_from_napi(
|
|
binding, env, napi_will, will_options, &connect_storage->will_storage)) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"s_init_connect_options_from_napi - failed to destructure will properties");
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
connect_options->will = will_options;
|
|
}
|
|
}
|
|
|
|
if (s_aws_mqtt5_user_properties_extract_from_js_object(
|
|
binding,
|
|
&connect_storage->user_properties,
|
|
env,
|
|
node_connect_config,
|
|
&connect_options->user_property_count,
|
|
&connect_options->user_properties)) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL, "s_init_connect_options_from_napi - failed to extract userProperties");
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
/* Extract topic aliasing configuration from a node object */
|
|
static int s_init_topic_aliasing_options_from_napi(
|
|
struct aws_mqtt5_client_binding *binding,
|
|
napi_env env,
|
|
napi_value node_topic_aliasing_config,
|
|
struct aws_mqtt5_client_topic_alias_options *topic_aliasing_options) {
|
|
|
|
uint32_t outbound_behavior = 0;
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_OUTBOUND_BEHAVIOR,
|
|
"s_init_topic_aliasing_options_from_napi",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env, node_topic_aliasing_config, AWS_NAPI_KEY_OUTBOUND_BEHAVIOR, (uint32_t *)&outbound_behavior),
|
|
{
|
|
topic_aliasing_options->outbound_topic_alias_behavior =
|
|
(enum aws_mqtt5_client_outbound_topic_alias_behavior_type)outbound_behavior;
|
|
});
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_OUTBOUND_CACHE_MAX_SIZE,
|
|
"s_init_topic_aliasing_options_from_napi",
|
|
aws_napi_get_named_property_as_uint16(
|
|
env,
|
|
node_topic_aliasing_config,
|
|
AWS_NAPI_KEY_OUTBOUND_CACHE_MAX_SIZE,
|
|
&topic_aliasing_options->outbound_alias_cache_max_size),
|
|
{});
|
|
|
|
uint32_t inbound_behavior = 0;
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_INBOUND_BEHAVIOR,
|
|
"s_init_topic_aliasing_options_from_napi",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env, node_topic_aliasing_config, AWS_NAPI_KEY_INBOUND_BEHAVIOR, (uint32_t *)&inbound_behavior),
|
|
{
|
|
topic_aliasing_options->inbound_topic_alias_behavior =
|
|
(enum aws_mqtt5_client_inbound_topic_alias_behavior_type)inbound_behavior;
|
|
});
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_INBOUND_CACHE_MAX_SIZE,
|
|
"s_init_topic_aliasing_options_from_napi",
|
|
aws_napi_get_named_property_as_uint16(
|
|
env,
|
|
node_topic_aliasing_config,
|
|
AWS_NAPI_KEY_INBOUND_CACHE_MAX_SIZE,
|
|
&topic_aliasing_options->inbound_alias_cache_size),
|
|
{});
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Persistent storage for mqtt5 client options
|
|
*/
|
|
struct aws_napi_mqtt5_client_creation_storage {
|
|
struct aws_byte_buf host_name;
|
|
|
|
struct aws_napi_mqtt5_connect_storage connect_storage;
|
|
};
|
|
|
|
static void s_aws_napi_mqtt5_client_creation_storage_clean_up(struct aws_napi_mqtt5_client_creation_storage *storage) {
|
|
aws_byte_buf_clean_up(&storage->host_name);
|
|
|
|
s_aws_napi_mqtt5_connect_storage_clean_up(&storage->connect_storage);
|
|
}
|
|
|
|
/* persistent storage for all the data necessary to transform the websocket handshake */
|
|
struct mqtt5_transform_websocket_args {
|
|
struct aws_allocator *allocator;
|
|
struct aws_mqtt5_client_binding *binding;
|
|
|
|
struct aws_http_message *request;
|
|
|
|
aws_mqtt5_transform_websocket_handshake_complete_fn *complete_fn;
|
|
void *complete_ctx;
|
|
};
|
|
|
|
static void s_mqtt5_transform_websocket_args_destroy(struct mqtt5_transform_websocket_args *args) {
|
|
if (args == NULL) {
|
|
return;
|
|
}
|
|
|
|
args->binding = s_aws_mqtt5_client_binding_release(args->binding);
|
|
|
|
aws_mem_release(args->allocator, args);
|
|
}
|
|
|
|
/* invoked from node once the JS handshake transform callback has completed */
|
|
static napi_value s_napi_mqtt5_transform_websocket_complete(napi_env env, napi_callback_info cb_info) {
|
|
|
|
struct mqtt5_transform_websocket_args *args = NULL;
|
|
int error_code = AWS_ERROR_SUCCESS;
|
|
|
|
napi_value node_args[1];
|
|
size_t num_args = AWS_ARRAY_SIZE(node_args);
|
|
napi_value *arg = &node_args[0];
|
|
AWS_NAPI_CALL(env, napi_get_cb_info(env, cb_info, &num_args, node_args, NULL, (void **)&args), {
|
|
napi_throw_error(env, NULL, "mqtt5_transform_websocket_complete - Failed to retrieve callback information");
|
|
goto cleanup;
|
|
});
|
|
if (num_args > 1) {
|
|
napi_throw_error(env, NULL, "mqtt5_transform_websocket_complete - needs exactly 0 or 1 arguments");
|
|
goto cleanup;
|
|
}
|
|
|
|
napi_value node_error_code = *arg++;
|
|
/* If the user didn't provide an error_code, the napi_value will be undefined, so we can ignore it */
|
|
if (!aws_napi_is_null_or_undefined(env, node_error_code)) {
|
|
AWS_NAPI_CALL(env, napi_get_value_int32(env, node_error_code, &error_code), {
|
|
napi_throw_type_error(
|
|
env, NULL, "mqtt5_transform_websocket_complete - error_code must be a number or undefined");
|
|
goto cleanup;
|
|
});
|
|
}
|
|
|
|
args->complete_fn(args->request, error_code, args->complete_ctx);
|
|
|
|
cleanup:
|
|
|
|
s_mqtt5_transform_websocket_args_destroy(args);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* in-node/libuv-thread function to trigger websocket handshake transform callback */
|
|
static void s_napi_mqtt5_transform_websocket(
|
|
napi_env env,
|
|
napi_value transform_websocket,
|
|
void *context,
|
|
void *user_data) {
|
|
|
|
(void)context;
|
|
struct mqtt5_transform_websocket_args *args = user_data;
|
|
|
|
if (env) {
|
|
napi_value params[2];
|
|
const size_t num_params = AWS_ARRAY_SIZE(params);
|
|
|
|
AWS_NAPI_ENSURE(env, aws_napi_http_message_wrap(env, args->request, ¶ms[0]));
|
|
AWS_NAPI_ENSURE(
|
|
env,
|
|
napi_create_function(
|
|
env,
|
|
"mqtt5_transform_websocket_complete",
|
|
NAPI_AUTO_LENGTH,
|
|
&s_napi_mqtt5_transform_websocket_complete,
|
|
args,
|
|
¶ms[1]));
|
|
|
|
AWS_NAPI_ENSURE(
|
|
env,
|
|
aws_napi_dispatch_threadsafe_function(
|
|
env, args->binding->transform_websocket, NULL, transform_websocket, num_params, params));
|
|
} else {
|
|
args->complete_fn(args->request, AWS_CRT_NODEJS_ERROR_THREADSAFE_FUNCTION_NULL_NAPI_ENV, args->complete_ctx);
|
|
|
|
s_mqtt5_transform_websocket_args_destroy(args);
|
|
}
|
|
}
|
|
|
|
static void s_mqtt5_transform_websocket(
|
|
struct aws_http_message *request,
|
|
void *user_data,
|
|
aws_mqtt5_transform_websocket_handshake_complete_fn *complete_fn,
|
|
void *complete_ctx) {
|
|
|
|
struct aws_mqtt5_client_binding *binding = user_data;
|
|
|
|
struct mqtt5_transform_websocket_args *args =
|
|
aws_mem_calloc(binding->allocator, 1, sizeof(struct mqtt5_transform_websocket_args));
|
|
|
|
args->binding = s_aws_mqtt5_client_binding_acquire(binding);
|
|
args->allocator = binding->allocator;
|
|
args->request = request;
|
|
args->complete_fn = complete_fn;
|
|
args->complete_ctx = complete_ctx;
|
|
|
|
AWS_NAPI_ENSURE(NULL, aws_napi_queue_threadsafe_function(binding->transform_websocket, args));
|
|
}
|
|
|
|
/* Extracts all mqtt5 client configuration from a napi Mqtt5ClientConfig object */
|
|
static int s_init_client_configuration_from_js_client_configuration(
|
|
napi_env env,
|
|
napi_value node_client_config,
|
|
struct aws_mqtt5_client_binding *binding,
|
|
struct aws_mqtt5_client_options *client_options,
|
|
struct aws_mqtt5_packet_connect_view *connect_options,
|
|
struct aws_mqtt5_packet_publish_view *will_options,
|
|
struct aws_mqtt5_client_topic_alias_options *topic_aliasing_options,
|
|
struct aws_napi_mqtt5_client_creation_storage *options_storage) {
|
|
|
|
/* required config parameters */
|
|
PARSE_REQUIRED_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_HOST_NAME,
|
|
"s_init_client_configuration_from_js_client_configuration",
|
|
aws_napi_get_named_property_as_bytebuf(
|
|
env, node_client_config, AWS_NAPI_KEY_HOST_NAME, napi_string, &options_storage->host_name),
|
|
{ client_options->host_name = aws_byte_cursor_from_buf(&options_storage->host_name); });
|
|
|
|
PARSE_REQUIRED_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_PORT,
|
|
"s_init_client_configuration_from_js_client_configuration",
|
|
aws_napi_get_named_property_as_uint32(env, node_client_config, AWS_NAPI_KEY_PORT, &client_options->port),
|
|
{});
|
|
|
|
/* optional config parameters */
|
|
uint32_t session_behavior = 0;
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_SESSION_BEHAVIOR,
|
|
"s_init_client_configuration_from_js_client_configuration",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env, node_client_config, AWS_NAPI_KEY_SESSION_BEHAVIOR, (uint32_t *)&session_behavior),
|
|
{ client_options->session_behavior = (enum aws_mqtt5_client_session_behavior_type)session_behavior; });
|
|
|
|
uint32_t extended_validation_and_flow_control_behavior = 0;
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_EXTENDED_VALIDATION_AND_FLOW_CONTROL_OPTIONS,
|
|
"s_init_client_configuration_from_js_client_configuration",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env,
|
|
node_client_config,
|
|
AWS_NAPI_KEY_EXTENDED_VALIDATION_AND_FLOW_CONTROL_OPTIONS,
|
|
(uint32_t *)&extended_validation_and_flow_control_behavior),
|
|
{
|
|
client_options->extended_validation_and_flow_control_options =
|
|
(enum aws_mqtt5_extended_validation_and_flow_control_options)
|
|
extended_validation_and_flow_control_behavior;
|
|
});
|
|
|
|
uint32_t offline_queue_behavior = 0;
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_OFFLINE_QUEUE_BEHAVIOR,
|
|
"s_init_client_configuration_from_js_client_configuration",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env, node_client_config, AWS_NAPI_KEY_OFFLINE_QUEUE_BEHAVIOR, (uint32_t *)&offline_queue_behavior),
|
|
{
|
|
client_options->offline_queue_behavior =
|
|
(enum aws_mqtt5_client_operation_queue_behavior_type)offline_queue_behavior;
|
|
});
|
|
|
|
uint32_t retry_jitter_mode = 0;
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_RETRY_JITTER_MODE,
|
|
"s_init_client_configuration_from_js_client_configuration",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env, node_client_config, AWS_NAPI_KEY_RETRY_JITTER_MODE, (uint32_t *)&retry_jitter_mode),
|
|
{ client_options->retry_jitter_mode = (enum aws_exponential_backoff_jitter_mode)retry_jitter_mode; });
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_MIN_RECONNECT_DELAY_MS,
|
|
"s_init_client_configuration_from_js_client_configuration",
|
|
aws_napi_get_named_property_as_uint64(
|
|
env, node_client_config, AWS_NAPI_KEY_MIN_RECONNECT_DELAY_MS, &client_options->min_reconnect_delay_ms),
|
|
{});
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_MAX_RECONNECT_DELAY_MS,
|
|
"s_init_client_configuration_from_js_client_configuration",
|
|
aws_napi_get_named_property_as_uint64(
|
|
env, node_client_config, AWS_NAPI_KEY_MAX_RECONNECT_DELAY_MS, &client_options->max_reconnect_delay_ms),
|
|
{});
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_MIN_CONNECTED_TIME_TO_RESET_RECONNECT_DELAY_MS,
|
|
"s_init_client_configuration_from_js_client_configuration",
|
|
aws_napi_get_named_property_as_uint64(
|
|
env,
|
|
node_client_config,
|
|
AWS_NAPI_KEY_MIN_CONNECTED_TIME_TO_RESET_RECONNECT_DELAY_MS,
|
|
&client_options->min_connected_time_to_reset_reconnect_delay_ms),
|
|
{});
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_PING_TIMEOUT_MS,
|
|
"s_init_client_configuration_from_js_client_configuration",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env, node_client_config, AWS_NAPI_KEY_PING_TIMEOUT_MS, &client_options->ping_timeout_ms),
|
|
{});
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_CONNACK_TIMEOUT_MS,
|
|
"s_init_client_configuration_from_js_client_configuration",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env, node_client_config, AWS_NAPI_KEY_CONNACK_TIMEOUT_MS, &client_options->connack_timeout_ms),
|
|
{});
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_CONNACK_TIMEOUT_MS,
|
|
"s_init_client_configuration_from_js_client_configuration",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env, node_client_config, AWS_NAPI_KEY_ACK_TIMEOUT_SECONDS, &client_options->ack_timeout_seconds),
|
|
{});
|
|
|
|
napi_value napi_value_connect = NULL;
|
|
if (AWS_NGNPR_VALID_VALUE ==
|
|
aws_napi_get_named_property(
|
|
env, node_client_config, AWS_NAPI_KEY_CONNECT_PROPERTIES, napi_object, &napi_value_connect)) {
|
|
if (s_init_connect_options_from_napi(
|
|
binding, env, napi_value_connect, connect_options, will_options, &options_storage->connect_storage)) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"s_init_client_configuration_from_js_client_configuration - failed to destructure connect properties");
|
|
return AWS_OP_ERR;
|
|
}
|
|
}
|
|
|
|
napi_value napi_value_topic_aliasing_options = NULL;
|
|
if (AWS_NGNPR_VALID_VALUE == aws_napi_get_named_property(
|
|
env,
|
|
node_client_config,
|
|
AWS_NAPI_KEY_TOPIC_ALIASING_OPTIONS,
|
|
napi_object,
|
|
&napi_value_topic_aliasing_options)) {
|
|
if (s_init_topic_aliasing_options_from_napi(
|
|
binding, env, napi_value_topic_aliasing_options, topic_aliasing_options)) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"s_init_client_configuration_from_js_client_configuration - failed to destructure topic aliasing "
|
|
"properties");
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
client_options->topic_aliasing_options = topic_aliasing_options;
|
|
}
|
|
|
|
napi_value node_transform_websocket = NULL;
|
|
if (AWS_NGNPR_VALID_VALUE == aws_napi_get_named_property(
|
|
env,
|
|
node_client_config,
|
|
AWS_NAPI_KEY_WEBSOCKET_HANDSHAKE_TRANSFORM,
|
|
napi_function,
|
|
&node_transform_websocket)) {
|
|
if (!aws_napi_is_null_or_undefined(env, node_transform_websocket)) {
|
|
AWS_NAPI_CALL(
|
|
env,
|
|
aws_napi_create_threadsafe_function(
|
|
env,
|
|
node_transform_websocket,
|
|
"aws_mqtt5_client_transform_websocket",
|
|
s_napi_mqtt5_transform_websocket,
|
|
binding,
|
|
&binding->transform_websocket),
|
|
{ return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE); });
|
|
|
|
client_options->websocket_handshake_transform = s_mqtt5_transform_websocket;
|
|
client_options->websocket_handshake_transform_user_data = binding;
|
|
}
|
|
}
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
static int s_init_event_handler_threadsafe_function(
|
|
napi_env env,
|
|
napi_value node_event_handler,
|
|
const char *threadsafe_name,
|
|
napi_threadsafe_function_call_js callback_function,
|
|
napi_threadsafe_function *function_out) {
|
|
|
|
AWS_FATAL_ASSERT(function_out != NULL && *function_out == NULL);
|
|
|
|
AWS_NAPI_CALL(
|
|
env,
|
|
aws_napi_create_threadsafe_function(
|
|
env, node_event_handler, threadsafe_name, callback_function, NULL, function_out),
|
|
{ return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); });
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Shared configuration defaults. These are required parameters at the C level, but we make them optional and give
|
|
* them sensible defaults at the binding level.
|
|
*/
|
|
static const uint16_t s_default_mqtt_keep_alive_interval_seconds = 1200;
|
|
|
|
static void s_init_default_mqtt5_client_options(
|
|
struct aws_mqtt5_client_options *client_options,
|
|
struct aws_mqtt5_packet_connect_view *connect_options) {
|
|
|
|
connect_options->keep_alive_interval_seconds = s_default_mqtt_keep_alive_interval_seconds;
|
|
client_options->connect_options = connect_options;
|
|
}
|
|
|
|
napi_value aws_napi_mqtt5_client_new(napi_env env, napi_callback_info info) {
|
|
|
|
napi_value node_args[12];
|
|
size_t num_args = AWS_ARRAY_SIZE(node_args);
|
|
napi_value *arg = &node_args[0];
|
|
AWS_NAPI_CALL(env, napi_get_cb_info(env, info, &num_args, node_args, NULL, NULL), {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - Failed to retrieve arguments");
|
|
return NULL;
|
|
});
|
|
|
|
if (num_args != AWS_ARRAY_SIZE(node_args)) {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - needs exactly 12 arguments");
|
|
return NULL;
|
|
}
|
|
|
|
napi_value napi_client_wrapper = NULL;
|
|
napi_value node_external = NULL;
|
|
struct aws_allocator *allocator = aws_napi_get_allocator();
|
|
|
|
struct aws_mqtt5_client_binding *binding = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_client_binding));
|
|
binding->allocator = allocator;
|
|
aws_ref_count_init(&binding->ref_count, binding, s_aws_mqtt5_client_binding_on_zero);
|
|
|
|
AWS_NAPI_CALL(env, napi_create_external(env, binding, s_aws_mqtt5_client_extern_finalize, NULL, &node_external), {
|
|
aws_mem_release(allocator, binding);
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - Failed to create n-api external");
|
|
goto cleanup;
|
|
});
|
|
|
|
struct aws_mqtt5_client_options client_options;
|
|
AWS_ZERO_STRUCT(client_options);
|
|
|
|
struct aws_mqtt5_packet_connect_view connect_options;
|
|
AWS_ZERO_STRUCT(connect_options);
|
|
|
|
struct aws_mqtt5_packet_publish_view will_options;
|
|
AWS_ZERO_STRUCT(will_options);
|
|
|
|
struct aws_mqtt5_client_topic_alias_options topic_aliasing_options;
|
|
AWS_ZERO_STRUCT(topic_aliasing_options);
|
|
|
|
struct aws_napi_mqtt5_client_creation_storage options_storage;
|
|
AWS_ZERO_STRUCT(options_storage);
|
|
|
|
s_init_default_mqtt5_client_options(&client_options, &connect_options);
|
|
|
|
/* Arg #1: the mqtt5 client */
|
|
napi_value node_client = *arg++;
|
|
if (aws_napi_is_null_or_undefined(env, node_client)) {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - Required client parameter is null");
|
|
goto cleanup;
|
|
}
|
|
|
|
AWS_NAPI_CALL(env, napi_create_reference(env, node_client, 1, &binding->node_mqtt5_client_ref), {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - Failed to create reference to node mqtt5 client");
|
|
goto cleanup;
|
|
});
|
|
|
|
/* Arg #2: the mqtt5 client config object */
|
|
napi_value node_client_config = *arg++;
|
|
if (aws_napi_is_null_or_undefined(env, node_client_config)) {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - Required configuration parameter is null");
|
|
goto post_ref_error;
|
|
}
|
|
|
|
if (s_init_client_configuration_from_js_client_configuration(
|
|
env,
|
|
node_client_config,
|
|
binding,
|
|
&client_options,
|
|
&connect_options,
|
|
&will_options,
|
|
&topic_aliasing_options,
|
|
&options_storage)) {
|
|
napi_throw_error(
|
|
env,
|
|
NULL,
|
|
"mqtt5_client_new - failed to initialize native client configuration from js client configuration");
|
|
goto post_ref_error;
|
|
}
|
|
|
|
/* Arg #3: on stopped event */
|
|
napi_value on_stopped_event_handler = *arg++;
|
|
if (aws_napi_is_null_or_undefined(env, on_stopped_event_handler)) {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - required on_stopped event handler is null");
|
|
goto post_ref_error;
|
|
}
|
|
|
|
if (s_init_event_handler_threadsafe_function(
|
|
env, on_stopped_event_handler, "aws_mqtt5_client_on_stopped", s_napi_on_stopped, &binding->on_stopped)) {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - failed to initialize on_stopped event handler");
|
|
goto post_ref_error;
|
|
}
|
|
|
|
/* Arg #4: on attempting connect event */
|
|
napi_value on_attempting_connect_event_handler = *arg++;
|
|
if (aws_napi_is_null_or_undefined(env, on_attempting_connect_event_handler)) {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - required on_attempting_connect event handler is null");
|
|
goto post_ref_error;
|
|
}
|
|
|
|
if (s_init_event_handler_threadsafe_function(
|
|
env,
|
|
on_attempting_connect_event_handler,
|
|
"aws_mqtt5_client_on_attempting_connect",
|
|
s_napi_on_attempting_connect,
|
|
&binding->on_attempting_connect)) {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - failed to initialize on_attempting_connect event handler");
|
|
goto post_ref_error;
|
|
}
|
|
|
|
/* Arg #5: on connection success event */
|
|
napi_value on_connection_success_event_handler = *arg++;
|
|
if (aws_napi_is_null_or_undefined(env, on_connection_success_event_handler)) {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - required on_connection_success event handler is null");
|
|
goto post_ref_error;
|
|
}
|
|
|
|
if (s_init_event_handler_threadsafe_function(
|
|
env,
|
|
on_connection_success_event_handler,
|
|
"aws_mqtt5_client_on_connection_success",
|
|
s_napi_on_connection_success,
|
|
&binding->on_connection_success)) {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - failed to initialize on_connection_success event handler");
|
|
goto post_ref_error;
|
|
}
|
|
|
|
/* Arg #6: on connection failure event */
|
|
napi_value on_connection_failure_event_handler = *arg++;
|
|
if (aws_napi_is_null_or_undefined(env, on_connection_failure_event_handler)) {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - required on_connection_failure event handler is null");
|
|
goto post_ref_error;
|
|
}
|
|
|
|
if (s_init_event_handler_threadsafe_function(
|
|
env,
|
|
on_connection_failure_event_handler,
|
|
"aws_mqtt5_client_on_connection_failure",
|
|
s_napi_on_connection_failure,
|
|
&binding->on_connection_failure)) {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - failed to initialize on_connection_failure event handler");
|
|
goto post_ref_error;
|
|
}
|
|
|
|
/* Arg #7: on disconnection event */
|
|
napi_value on_disconnection_event_handler = *arg++;
|
|
if (aws_napi_is_null_or_undefined(env, on_disconnection_event_handler)) {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - required on_disconnection event handler is null");
|
|
goto post_ref_error;
|
|
}
|
|
|
|
if (s_init_event_handler_threadsafe_function(
|
|
env,
|
|
on_disconnection_event_handler,
|
|
"aws_mqtt5_client_on_disconnection",
|
|
s_napi_on_disconnection,
|
|
&binding->on_disconnection)) {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - failed to initialize on_disconnection event handler");
|
|
goto post_ref_error;
|
|
}
|
|
|
|
/* Arg #8: on message received event */
|
|
napi_value on_message_received_event_handler = *arg++;
|
|
if (aws_napi_is_null_or_undefined(env, on_message_received_event_handler)) {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - required on_message_received event handler is null");
|
|
goto post_ref_error;
|
|
}
|
|
|
|
if (s_init_event_handler_threadsafe_function(
|
|
env,
|
|
on_message_received_event_handler,
|
|
"aws_mqtt5_client_on_message_received",
|
|
s_napi_on_message_received,
|
|
&binding->on_message_received)) {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - failed to initialize on_message_received event handler");
|
|
goto post_ref_error;
|
|
}
|
|
|
|
/* Arg #9: client bootstrap */
|
|
napi_value node_client_bootstrap = *arg++;
|
|
if (!aws_napi_is_null_or_undefined(env, node_client_bootstrap)) {
|
|
struct client_bootstrap_binding *client_bootstrap_binding = NULL;
|
|
napi_get_value_external(env, node_client_bootstrap, (void **)&client_bootstrap_binding);
|
|
|
|
client_options.bootstrap = aws_napi_get_client_bootstrap(client_bootstrap_binding);
|
|
}
|
|
|
|
if (client_options.bootstrap == NULL) {
|
|
client_options.bootstrap = aws_napi_get_default_client_bootstrap();
|
|
}
|
|
|
|
/* Arg #10: socket options */
|
|
napi_value node_socket_options = *arg++;
|
|
if (!aws_napi_is_null_or_undefined(env, node_socket_options)) {
|
|
AWS_NAPI_CALL(env, napi_get_value_external(env, node_socket_options, (void **)&client_options.socket_options), {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - Unable to extract socket_options from external");
|
|
goto post_ref_error;
|
|
});
|
|
}
|
|
|
|
/* Arg #11: tls options */
|
|
napi_value node_tls = *arg++;
|
|
if (!aws_napi_is_null_or_undefined(env, node_tls)) {
|
|
struct aws_tls_ctx *tls_ctx;
|
|
AWS_NAPI_CALL(env, napi_get_value_external(env, node_tls, (void **)&tls_ctx), {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - Failed to extract tls_ctx from external");
|
|
goto post_ref_error;
|
|
});
|
|
|
|
aws_tls_connection_options_init_from_ctx(&binding->tls_connection_options, tls_ctx);
|
|
|
|
client_options.tls_options = &binding->tls_connection_options;
|
|
}
|
|
|
|
/* Arg #12: http proxy options */
|
|
napi_value node_proxy_options = *arg++;
|
|
if (!aws_napi_is_null_or_undefined(env, node_proxy_options)) {
|
|
struct http_proxy_options_binding *proxy_binding = NULL;
|
|
AWS_NAPI_CALL(env, napi_get_value_external(env, node_proxy_options, (void **)&proxy_binding), {
|
|
napi_throw_type_error(env, NULL, "mqtt5_client_new - failed to extract http proxy options from external");
|
|
goto post_ref_error;
|
|
});
|
|
/* proxy_options are copied internally, no need to go nuts on copies */
|
|
client_options.http_proxy_options = aws_napi_get_http_proxy_options(proxy_binding);
|
|
}
|
|
|
|
client_options.publish_received_handler = s_on_publish_received;
|
|
client_options.publish_received_handler_user_data = binding;
|
|
|
|
client_options.lifecycle_event_handler = s_lifecycle_event_callback;
|
|
client_options.lifecycle_event_handler_user_data = binding;
|
|
|
|
client_options.client_termination_handler = s_aws_mqtt5_client_binding_on_client_terminate;
|
|
client_options.client_termination_handler_user_data = binding;
|
|
|
|
binding->client = aws_mqtt5_client_new(allocator, &client_options);
|
|
if (binding->client == NULL) {
|
|
aws_napi_throw_last_error_with_context(env, "mqtt5_client_new - failed to create client");
|
|
goto post_ref_error;
|
|
}
|
|
|
|
AWS_NAPI_CALL(env, napi_create_reference(env, node_external, 1, &binding->node_client_external_ref), {
|
|
napi_throw_error(env, NULL, "mqtt5_client_new - Failed to create one count reference to napi external");
|
|
goto post_ref_error;
|
|
});
|
|
|
|
napi_client_wrapper = node_external;
|
|
|
|
goto cleanup;
|
|
|
|
post_ref_error:
|
|
|
|
if (binding->node_mqtt5_client_ref != NULL) {
|
|
napi_delete_reference(env, binding->node_mqtt5_client_ref);
|
|
binding->node_mqtt5_client_ref = NULL;
|
|
}
|
|
|
|
cleanup:
|
|
|
|
s_aws_napi_mqtt5_client_creation_storage_clean_up(&options_storage);
|
|
|
|
return napi_client_wrapper;
|
|
}
|
|
|
|
napi_value aws_napi_mqtt5_client_start(napi_env env, napi_callback_info info) {
|
|
|
|
napi_value node_args[1];
|
|
size_t num_args = AWS_ARRAY_SIZE(node_args);
|
|
napi_value *arg = &node_args[0];
|
|
AWS_NAPI_CALL(env, napi_get_cb_info(env, info, &num_args, node_args, NULL, NULL), {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_start - Failed to extract parameter array");
|
|
return NULL;
|
|
});
|
|
|
|
if (num_args != AWS_ARRAY_SIZE(node_args)) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_start - needs exactly 1 argument");
|
|
return NULL;
|
|
}
|
|
|
|
struct aws_mqtt5_client_binding *binding = NULL;
|
|
napi_value node_binding = *arg++;
|
|
AWS_NAPI_CALL(env, napi_get_value_external(env, node_binding, (void **)&binding), {
|
|
napi_throw_error(
|
|
env, NULL, "aws_napi_mqtt5_client_start - Failed to extract client binding from first argument");
|
|
return NULL;
|
|
});
|
|
|
|
if (binding == NULL) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_start - binding was null");
|
|
return NULL;
|
|
}
|
|
|
|
if (binding->client == NULL) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_start - client was null");
|
|
return NULL;
|
|
}
|
|
|
|
if (aws_mqtt5_client_start(binding->client)) {
|
|
aws_napi_throw_last_error_with_context(
|
|
env, "aws_napi_mqtt5_client_start - Failure invoking aws_mqtt5_client_start");
|
|
return NULL;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Persistent storage for a DISCONNECT packet. */
|
|
struct aws_napi_mqtt5_packet_disconnect_storage {
|
|
uint32_t session_expiry_interval_seconds;
|
|
|
|
struct aws_byte_buf reason_string;
|
|
struct aws_byte_cursor reason_string_cursor;
|
|
|
|
struct aws_napi_mqtt5_user_property_storage user_properties;
|
|
|
|
struct aws_byte_buf server_reference;
|
|
struct aws_byte_cursor server_reference_cursor;
|
|
};
|
|
|
|
static void s_aws_napi_mqtt5_packet_disconnect_storage_clean_up(
|
|
struct aws_napi_mqtt5_packet_disconnect_storage *storage) {
|
|
aws_byte_buf_clean_up(&storage->reason_string);
|
|
|
|
s_aws_mqtt5_user_properties_clean_up(&storage->user_properties);
|
|
|
|
aws_byte_buf_clean_up(&storage->server_reference);
|
|
}
|
|
|
|
/* Extract a DISCONNECT packet view from a Napi object (AwsMqtt5PacketDisconnect) and persist its data in storage. */
|
|
static int s_aws_napi_mqtt5_packet_disconnect_storage_initialize_from_js_object(
|
|
struct aws_mqtt5_client_binding *binding,
|
|
struct aws_napi_mqtt5_packet_disconnect_storage *disconnect_storage,
|
|
struct aws_mqtt5_packet_disconnect_view *disconnect_packet,
|
|
napi_env env,
|
|
napi_value node_disconnect_packet) {
|
|
uint32_t reason_code = 0;
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_REASON_CODE,
|
|
"s_aws_napi_mqtt5_packet_disconnect_storage_initialize_from_js_object",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env, node_disconnect_packet, AWS_NAPI_KEY_REASON_CODE, (uint32_t *)&reason_code),
|
|
{ disconnect_packet->reason_code = (enum aws_mqtt5_disconnect_reason_code)reason_code; });
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_SESSION_EXPIRY_INTERVAL_SECONDS,
|
|
"s_aws_napi_mqtt5_packet_disconnect_storage_initialize_from_js_object",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env,
|
|
node_disconnect_packet,
|
|
AWS_NAPI_KEY_SESSION_EXPIRY_INTERVAL_SECONDS,
|
|
&disconnect_storage->session_expiry_interval_seconds),
|
|
{ disconnect_packet->session_expiry_interval_seconds = &disconnect_storage->session_expiry_interval_seconds; });
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_REASON_STRING,
|
|
"s_aws_napi_mqtt5_packet_disconnect_storage_initialize_from_js_object",
|
|
aws_napi_get_named_property_as_bytebuf(
|
|
env, node_disconnect_packet, AWS_NAPI_KEY_REASON_STRING, napi_string, &disconnect_storage->reason_string),
|
|
{
|
|
disconnect_storage->reason_string_cursor = aws_byte_cursor_from_buf(&disconnect_storage->reason_string);
|
|
disconnect_packet->reason_string = &disconnect_storage->reason_string_cursor;
|
|
});
|
|
|
|
if (s_aws_mqtt5_user_properties_extract_from_js_object(
|
|
binding,
|
|
&disconnect_storage->user_properties,
|
|
env,
|
|
node_disconnect_packet,
|
|
&disconnect_packet->user_property_count,
|
|
&disconnect_packet->user_properties)) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_aws_napi_mqtt5_packet_disconnect_storage_initialize_from_js_object - failed to extract "
|
|
"userProperties",
|
|
(void *)binding->client);
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
/* Intentionally ignore server reference because it's a client error to send it */
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
napi_value aws_napi_mqtt5_client_stop(napi_env env, napi_callback_info info) {
|
|
|
|
napi_value node_args[2];
|
|
size_t num_args = AWS_ARRAY_SIZE(node_args);
|
|
napi_value *arg = &node_args[0];
|
|
AWS_NAPI_CALL(env, napi_get_cb_info(env, info, &num_args, node_args, NULL, NULL), {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_stop - Failed to extract parameter array");
|
|
return NULL;
|
|
});
|
|
|
|
if (num_args != AWS_ARRAY_SIZE(node_args)) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_stop - needs exactly 2 arguments");
|
|
return NULL;
|
|
}
|
|
|
|
struct aws_mqtt5_client_binding *binding = NULL;
|
|
napi_value node_binding = *arg++;
|
|
AWS_NAPI_CALL(env, napi_get_value_external(env, node_binding, (void **)&binding), {
|
|
napi_throw_error(
|
|
env, NULL, "aws_napi_mqtt5_client_stop - Failed to extract client binding from first argument");
|
|
return NULL;
|
|
});
|
|
|
|
if (binding == NULL) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_stop - binding was null");
|
|
return NULL;
|
|
}
|
|
|
|
if (binding->client == NULL) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_stop - client was null");
|
|
return NULL;
|
|
}
|
|
|
|
struct aws_napi_mqtt5_packet_disconnect_storage disconnect_storage;
|
|
AWS_ZERO_STRUCT(disconnect_storage);
|
|
|
|
struct aws_mqtt5_packet_disconnect_view *disconnect_view_ptr = NULL;
|
|
struct aws_mqtt5_packet_disconnect_view disconnect_view;
|
|
AWS_ZERO_STRUCT(disconnect_view);
|
|
|
|
napi_value node_disconnect_packet = *arg++;
|
|
if (!aws_napi_is_null_or_undefined(env, node_disconnect_packet)) {
|
|
if (s_aws_napi_mqtt5_packet_disconnect_storage_initialize_from_js_object(
|
|
binding, &disconnect_storage, &disconnect_view, env, node_disconnect_packet)) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_stop - could not initialize disconnect packet");
|
|
goto done;
|
|
}
|
|
|
|
disconnect_view_ptr = &disconnect_view;
|
|
}
|
|
|
|
if (aws_mqtt5_client_stop(binding->client, disconnect_view_ptr, NULL)) {
|
|
aws_napi_throw_last_error_with_context(
|
|
env, "aws_napi_mqtt5_client_stop - Failure invoking aws_mqtt5_client_stop");
|
|
goto done;
|
|
}
|
|
|
|
done:
|
|
|
|
s_aws_napi_mqtt5_packet_disconnect_storage_clean_up(&disconnect_storage);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct aws_napi_mqtt5_operation_binding {
|
|
struct aws_allocator *allocator;
|
|
|
|
struct aws_mqtt5_client_binding *client_binding;
|
|
|
|
napi_threadsafe_function on_operation_completion;
|
|
|
|
int error_code;
|
|
|
|
enum aws_mqtt5_packet_type valid_storage;
|
|
|
|
union {
|
|
struct aws_mqtt5_packet_suback_storage suback;
|
|
struct aws_mqtt5_packet_puback_storage puback;
|
|
struct aws_mqtt5_packet_unsuback_storage unsuback;
|
|
} packet_storage;
|
|
};
|
|
|
|
static void s_aws_napi_mqtt5_operation_binding_destroy(struct aws_napi_mqtt5_operation_binding *binding) {
|
|
if (binding == NULL) {
|
|
return;
|
|
}
|
|
|
|
binding->client_binding = s_aws_mqtt5_client_binding_release(binding->client_binding);
|
|
|
|
AWS_CLEAN_THREADSAFE_FUNCTION(binding, on_operation_completion);
|
|
|
|
switch (binding->valid_storage) {
|
|
case AWS_MQTT5_PT_SUBACK:
|
|
aws_mqtt5_packet_suback_storage_clean_up(&binding->packet_storage.suback);
|
|
break;
|
|
|
|
case AWS_MQTT5_PT_PUBACK:
|
|
aws_mqtt5_packet_puback_storage_clean_up(&binding->packet_storage.puback);
|
|
break;
|
|
|
|
case AWS_MQTT5_PT_UNSUBACK:
|
|
aws_mqtt5_packet_unsuback_storage_clean_up(&binding->packet_storage.unsuback);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
aws_mem_release(binding->allocator, binding);
|
|
}
|
|
|
|
static int s_create_napi_suback_packet(
|
|
napi_env env,
|
|
const struct aws_mqtt5_packet_suback_view *suback,
|
|
napi_value *packet_out) {
|
|
|
|
if (env == NULL) {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_THREADSAFE_FUNCTION_NULL_NAPI_ENV);
|
|
}
|
|
|
|
napi_value napi_suback = NULL;
|
|
AWS_NAPI_CALL(
|
|
env, napi_create_object(env, &napi_suback), { return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE); });
|
|
|
|
if (aws_napi_attach_object_property_u32(napi_suback, env, AWS_NAPI_KEY_TYPE, (uint32_t)AWS_MQTT5_PT_SUBACK)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_string(
|
|
napi_suback, env, AWS_NAPI_KEY_REASON_STRING, suback->reason_string)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (s_attach_object_property_user_properties(
|
|
napi_suback, env, suback->user_property_count, suback->user_properties)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
size_t reason_code_count = suback->reason_code_count;
|
|
if (reason_code_count == 0) {
|
|
AWS_LOGF_ERROR(AWS_LS_NODEJS_CRT_GENERAL, "s_create_napi_suback_packet - missing reason codes");
|
|
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
|
|
}
|
|
|
|
napi_value suback_code_array = NULL;
|
|
AWS_NAPI_CALL(env, napi_create_array_with_length(env, reason_code_count, &suback_code_array), {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE);
|
|
});
|
|
|
|
for (size_t i = 0; i < reason_code_count; ++i) {
|
|
napi_value napi_reason_code = NULL;
|
|
AWS_NAPI_CALL(env, napi_create_uint32(env, (uint32_t)suback->reason_codes[i], &napi_reason_code), {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE);
|
|
});
|
|
|
|
AWS_NAPI_CALL(env, napi_set_element(env, suback_code_array, (uint32_t)i, napi_reason_code), {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE);
|
|
});
|
|
}
|
|
|
|
AWS_NAPI_CALL(env, napi_set_named_property(env, napi_suback, AWS_NAPI_KEY_REASON_CODES, suback_code_array), {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE);
|
|
});
|
|
|
|
*packet_out = napi_suback;
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
/* in-node/libuv-thread function to trigger the emission of an ATTEMPTING_CONNECT client lifecycle event */
|
|
static void s_napi_on_subscribe_complete(napi_env env, napi_value function, void *context, void *user_data) {
|
|
(void)user_data;
|
|
|
|
struct aws_napi_mqtt5_operation_binding *binding = context;
|
|
|
|
if (env) {
|
|
napi_value params[3];
|
|
const size_t num_params = AWS_ARRAY_SIZE(params);
|
|
|
|
/*
|
|
* If we can't resolve the weak ref to the mqtt5 client, then it's been garbage collected and we should not
|
|
* do anything.
|
|
*/
|
|
params[0] = NULL;
|
|
if (napi_get_reference_value(env, binding->client_binding->node_mqtt5_client_ref, ¶ms[0]) != napi_ok ||
|
|
params[0] == NULL) {
|
|
AWS_LOGF_INFO(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"s_napi_on_subscribe_complete - mqtt5_client node wrapper no longer resolvable");
|
|
goto done;
|
|
}
|
|
|
|
AWS_NAPI_CALL(env, napi_create_uint32(env, binding->error_code, ¶ms[1]), { goto done; });
|
|
|
|
if (binding->valid_storage == AWS_MQTT5_PT_SUBACK) {
|
|
if (s_create_napi_suback_packet(env, &binding->packet_storage.suback.storage_view, ¶ms[2])) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL, "s_napi_on_subscribe_complete - could not build suback napi value");
|
|
goto done;
|
|
}
|
|
} else {
|
|
if (napi_get_undefined(env, ¶ms[2]) != napi_ok) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL, "s_napi_on_subscribe_complete - could not get undefined napi value");
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
AWS_NAPI_ENSURE(
|
|
env,
|
|
aws_napi_dispatch_threadsafe_function(
|
|
env, binding->on_operation_completion, NULL, function, num_params, params));
|
|
}
|
|
|
|
done:
|
|
|
|
s_aws_napi_mqtt5_operation_binding_destroy(binding);
|
|
}
|
|
|
|
static void s_on_subscribe_complete(
|
|
const struct aws_mqtt5_packet_suback_view *suback,
|
|
int error_code,
|
|
void *complete_ctx) {
|
|
|
|
struct aws_allocator *allocator = aws_napi_get_allocator();
|
|
struct aws_napi_mqtt5_operation_binding *binding = complete_ctx;
|
|
|
|
binding->error_code = error_code;
|
|
if (suback != NULL &&
|
|
aws_mqtt5_packet_suback_storage_init(&binding->packet_storage.suback, allocator, suback) == AWS_OP_SUCCESS) {
|
|
binding->valid_storage = AWS_MQTT5_PT_SUBACK;
|
|
} else if (binding->error_code == AWS_ERROR_SUCCESS) {
|
|
binding->error_code = aws_last_error();
|
|
}
|
|
|
|
/* queue a callback in node's libuv thread */
|
|
AWS_NAPI_ENSURE(NULL, aws_napi_queue_threadsafe_function(binding->on_operation_completion, binding));
|
|
}
|
|
|
|
struct aws_napi_mqtt5_subscribe_storage {
|
|
struct aws_array_list subscriptions;
|
|
struct aws_byte_buf topics;
|
|
|
|
uint32_t subscription_identifier;
|
|
|
|
struct aws_napi_mqtt5_user_property_storage user_properties;
|
|
};
|
|
|
|
static void s_aws_napi_mqtt5_subscribe_storage_clean_up(struct aws_napi_mqtt5_subscribe_storage *storage) {
|
|
aws_array_list_clean_up(&storage->subscriptions);
|
|
aws_byte_buf_clean_up(&storage->topics);
|
|
|
|
s_aws_mqtt5_user_properties_clean_up(&storage->user_properties);
|
|
}
|
|
|
|
static int s_aws_mqtt5_subscription_init_from_napi(
|
|
struct aws_mqtt5_subscription_view *subscription,
|
|
struct aws_mqtt5_client_binding *binding,
|
|
napi_env env,
|
|
napi_value node_subscription) {
|
|
|
|
uint32_t qos = 0;
|
|
PARSE_REQUIRED_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_QOS,
|
|
"s_aws_mqtt5_subscription_init_from_napi",
|
|
aws_napi_get_named_property_as_uint32(env, node_subscription, AWS_NAPI_KEY_QOS, &qos),
|
|
{ subscription->qos = qos; });
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_NO_LOCAL,
|
|
"s_aws_mqtt5_subscription_init_from_napi",
|
|
aws_napi_get_named_property_as_boolean(env, node_subscription, AWS_NAPI_KEY_NO_LOCAL, &subscription->no_local),
|
|
{});
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_RETAIN_AS_PUBLISHED,
|
|
"s_aws_mqtt5_subscription_init_from_napi",
|
|
aws_napi_get_named_property_as_boolean(
|
|
env, node_subscription, AWS_NAPI_KEY_RETAIN_AS_PUBLISHED, &subscription->retain_as_published),
|
|
{});
|
|
|
|
uint32_t retain_handling_type = 0;
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_RETAIN_HANDLING_TYPE,
|
|
"s_aws_mqtt5_subscription_init_from_napi",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env, node_subscription, AWS_NAPI_KEY_RETAIN_HANDLING_TYPE, &retain_handling_type),
|
|
{ subscription->retain_handling_type = retain_handling_type; });
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
static int s_aws_mqtt5_packet_subscribe_storage_init_from_napi(
|
|
struct aws_napi_mqtt5_subscribe_storage *subscribe_storage,
|
|
struct aws_mqtt5_packet_subscribe_view *subscribe_view,
|
|
struct aws_mqtt5_client_binding *binding,
|
|
napi_env env,
|
|
napi_value node_subscribe_packet) {
|
|
if (env == NULL) {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_THREADSAFE_FUNCTION_NULL_NAPI_ENV);
|
|
}
|
|
|
|
napi_value napi_subscriptions = NULL;
|
|
if (AWS_NGNPR_VALID_VALUE !=
|
|
aws_napi_get_named_property(
|
|
env, node_subscribe_packet, AWS_NAPI_KEY_SUBSCRIPTIONS, napi_object, &napi_subscriptions)) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_aws_mqtt5_packet_subscribe_storage_init_from_napi - missing require parameter: subscriptions",
|
|
(void *)binding->client);
|
|
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
|
|
}
|
|
|
|
/* how many subscriptions and total topic length */
|
|
uint32_t subscription_count = 0;
|
|
AWS_NAPI_CALL(env, napi_get_array_length(env, napi_subscriptions, &subscription_count), {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_aws_mqtt5_packet_subscribe_storage_init_from_napi - subscriptions is not an array",
|
|
(void *)binding->client);
|
|
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
|
|
});
|
|
|
|
size_t topic_length_sum = 0;
|
|
for (uint32_t i = 0; i < subscription_count; ++i) {
|
|
napi_value napi_subscription = NULL;
|
|
AWS_NAPI_CALL(env, napi_get_element(env, napi_subscriptions, i, &napi_subscription), { return AWS_OP_ERR; });
|
|
|
|
struct aws_byte_buf topic_filter_buf;
|
|
AWS_ZERO_STRUCT(topic_filter_buf);
|
|
|
|
bool success = AWS_NGNPR_VALID_VALUE ==
|
|
aws_napi_get_named_property_as_bytebuf(
|
|
env, napi_subscription, AWS_NAPI_KEY_TOPIC_FILTER, napi_string, &topic_filter_buf);
|
|
|
|
topic_length_sum += topic_filter_buf.len;
|
|
|
|
aws_byte_buf_clean_up(&topic_filter_buf);
|
|
if (!success) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_aws_mqtt5_packet_subscribe_storage_init_from_napi - missing require parameter: topicFilter",
|
|
(void *)binding->client);
|
|
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
|
|
}
|
|
}
|
|
|
|
struct aws_allocator *allocator = aws_napi_get_allocator();
|
|
|
|
if (aws_array_list_init_dynamic(
|
|
&subscribe_storage->subscriptions,
|
|
allocator,
|
|
subscription_count,
|
|
sizeof(struct aws_mqtt5_subscription_view))) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_byte_buf_init(&subscribe_storage->topics, allocator, topic_length_sum)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < subscription_count; ++i) {
|
|
napi_value napi_subscription = NULL;
|
|
AWS_NAPI_CALL(env, napi_get_element(env, napi_subscriptions, i, &napi_subscription), { return AWS_OP_ERR; });
|
|
|
|
struct aws_byte_buf topic_filter_buf;
|
|
AWS_ZERO_STRUCT(topic_filter_buf);
|
|
|
|
aws_napi_get_named_property_as_bytebuf(
|
|
env, napi_subscription, AWS_NAPI_KEY_TOPIC_FILTER, napi_string, &topic_filter_buf);
|
|
|
|
struct aws_mqtt5_subscription_view subscription;
|
|
AWS_ZERO_STRUCT(subscription);
|
|
|
|
subscription.topic_filter = aws_byte_cursor_from_buf(&topic_filter_buf);
|
|
|
|
bool success =
|
|
aws_byte_buf_append_and_update(&subscribe_storage->topics, &subscription.topic_filter) == AWS_OP_SUCCESS;
|
|
|
|
aws_byte_buf_clean_up(&topic_filter_buf);
|
|
|
|
if (!success || s_aws_mqtt5_subscription_init_from_napi(&subscription, binding, env, napi_subscription)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
aws_array_list_push_back(&subscribe_storage->subscriptions, &subscription);
|
|
}
|
|
|
|
subscribe_view->subscription_count = subscription_count;
|
|
subscribe_view->subscriptions = subscribe_storage->subscriptions.data;
|
|
|
|
PARSE_OPTIONAL_NAPI_PROPERTY(
|
|
AWS_NAPI_KEY_SUBSCRIPTION_IDENTIFIER,
|
|
"s_aws_mqtt5_packet_subscribe_storage_init_from_napi",
|
|
aws_napi_get_named_property_as_uint32(
|
|
env,
|
|
node_subscribe_packet,
|
|
AWS_NAPI_KEY_SUBSCRIPTION_IDENTIFIER,
|
|
&subscribe_storage->subscription_identifier),
|
|
{ subscribe_view->subscription_identifier = &subscribe_storage->subscription_identifier; });
|
|
|
|
if (s_aws_mqtt5_user_properties_extract_from_js_object(
|
|
binding,
|
|
&subscribe_storage->user_properties,
|
|
env,
|
|
node_subscribe_packet,
|
|
&subscribe_view->user_property_count,
|
|
&subscribe_view->user_properties)) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"s_aws_mqtt5_packet_subscribe_storage_init_from_napi - failed to extract userProperties");
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
napi_value aws_napi_mqtt5_client_subscribe(napi_env env, napi_callback_info info) {
|
|
struct aws_allocator *allocator = aws_napi_get_allocator();
|
|
|
|
napi_value node_args[3];
|
|
size_t num_args = AWS_ARRAY_SIZE(node_args);
|
|
napi_value *arg = &node_args[0];
|
|
AWS_NAPI_CALL(env, napi_get_cb_info(env, info, &num_args, node_args, NULL, NULL), {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_subscribe - Failed to extract parameter array");
|
|
return NULL;
|
|
});
|
|
|
|
if (num_args != AWS_ARRAY_SIZE(node_args)) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_subscribe - needs exactly 3 arguments");
|
|
return NULL;
|
|
}
|
|
|
|
bool successful = false;
|
|
struct aws_mqtt5_client_binding *client_binding = NULL;
|
|
napi_value node_binding = *arg++;
|
|
AWS_NAPI_CALL(env, napi_get_value_external(env, node_binding, (void **)&client_binding), {
|
|
napi_throw_error(
|
|
env, NULL, "aws_napi_mqtt5_client_subscribe - Failed to extract client binding from first argument");
|
|
return NULL;
|
|
});
|
|
|
|
if (client_binding == NULL) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_subscribe - binding was null");
|
|
return NULL;
|
|
}
|
|
|
|
if (client_binding->client == NULL) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_subscribe - client was null");
|
|
return NULL;
|
|
}
|
|
|
|
struct aws_napi_mqtt5_operation_binding *binding =
|
|
aws_mem_calloc(allocator, 1, sizeof(struct aws_napi_mqtt5_operation_binding));
|
|
binding->allocator = allocator;
|
|
binding->client_binding = s_aws_mqtt5_client_binding_acquire(client_binding);
|
|
|
|
napi_value node_subscribe_packet = *arg++;
|
|
|
|
struct aws_napi_mqtt5_subscribe_storage subscribe_storage;
|
|
AWS_ZERO_STRUCT(subscribe_storage);
|
|
struct aws_mqtt5_packet_subscribe_view subscribe_view;
|
|
AWS_ZERO_STRUCT(subscribe_view);
|
|
if (s_aws_mqtt5_packet_subscribe_storage_init_from_napi(
|
|
&subscribe_storage, &subscribe_view, client_binding, env, node_subscribe_packet)) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_subscribe - storage init failure");
|
|
goto done;
|
|
}
|
|
|
|
napi_value completion_callback = *arg++;
|
|
AWS_NAPI_CALL(
|
|
env,
|
|
aws_napi_create_threadsafe_function(
|
|
env,
|
|
completion_callback,
|
|
"aws_mqtt5_on_subsription_complete",
|
|
s_napi_on_subscribe_complete,
|
|
binding,
|
|
&binding->on_operation_completion),
|
|
{
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_subscribe - failed to create threadsafe function");
|
|
goto done;
|
|
});
|
|
|
|
struct aws_mqtt5_subscribe_completion_options completion_options = {
|
|
.completion_callback = s_on_subscribe_complete,
|
|
.completion_user_data = binding,
|
|
};
|
|
|
|
if (aws_mqtt5_client_subscribe(client_binding->client, &subscribe_view, &completion_options)) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_subscribe - failure invoking native client subscribe");
|
|
goto done;
|
|
}
|
|
|
|
successful = true;
|
|
|
|
done:
|
|
|
|
s_aws_napi_mqtt5_subscribe_storage_clean_up(&subscribe_storage);
|
|
|
|
if (!successful) {
|
|
s_aws_napi_mqtt5_operation_binding_destroy(binding);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int s_create_napi_unsuback_packet(
|
|
napi_env env,
|
|
const struct aws_mqtt5_packet_unsuback_view *unsuback,
|
|
napi_value *packet_out) {
|
|
|
|
if (env == NULL) {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_THREADSAFE_FUNCTION_NULL_NAPI_ENV);
|
|
}
|
|
|
|
napi_value napi_unsuback = NULL;
|
|
AWS_NAPI_CALL(
|
|
env, napi_create_object(env, &napi_unsuback), { return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE); });
|
|
|
|
if (aws_napi_attach_object_property_u32(napi_unsuback, env, AWS_NAPI_KEY_TYPE, (uint32_t)AWS_MQTT5_PT_UNSUBACK)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_string(
|
|
napi_unsuback, env, AWS_NAPI_KEY_REASON_STRING, unsuback->reason_string)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (s_attach_object_property_user_properties(
|
|
napi_unsuback, env, unsuback->user_property_count, unsuback->user_properties)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
size_t reason_code_count = unsuback->reason_code_count;
|
|
if (reason_code_count == 0) {
|
|
AWS_LOGF_ERROR(AWS_LS_NODEJS_CRT_GENERAL, "s_create_napi_unsuback_packet - missing reason codes");
|
|
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
|
|
}
|
|
|
|
napi_value unsuback_code_array = NULL;
|
|
AWS_NAPI_CALL(env, napi_create_array_with_length(env, reason_code_count, &unsuback_code_array), {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE);
|
|
});
|
|
|
|
for (size_t i = 0; i < reason_code_count; ++i) {
|
|
napi_value napi_reason_code = NULL;
|
|
AWS_NAPI_CALL(env, napi_create_uint32(env, (uint32_t)unsuback->reason_codes[i], &napi_reason_code), {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE);
|
|
});
|
|
|
|
AWS_NAPI_CALL(env, napi_set_element(env, unsuback_code_array, (uint32_t)i, napi_reason_code), {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE);
|
|
});
|
|
}
|
|
|
|
AWS_NAPI_CALL(env, napi_set_named_property(env, napi_unsuback, AWS_NAPI_KEY_REASON_CODES, unsuback_code_array), {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE);
|
|
});
|
|
|
|
*packet_out = napi_unsuback;
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
/* in-node/libuv-thread function to trigger the emission of an ATTEMPTING_CONNECT client lifecycle event */
|
|
static void s_napi_on_unsubscribe_complete(napi_env env, napi_value function, void *context, void *user_data) {
|
|
(void)user_data;
|
|
|
|
struct aws_napi_mqtt5_operation_binding *binding = context;
|
|
|
|
if (env) {
|
|
napi_value params[3];
|
|
const size_t num_params = AWS_ARRAY_SIZE(params);
|
|
|
|
/*
|
|
* If we can't resolve the weak ref to the mqtt5 client, then it's been garbage collected and we should not
|
|
* do anything.
|
|
*/
|
|
params[0] = NULL;
|
|
if (napi_get_reference_value(env, binding->client_binding->node_mqtt5_client_ref, ¶ms[0]) != napi_ok ||
|
|
params[0] == NULL) {
|
|
AWS_LOGF_INFO(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"s_napi_on_unsubscribe_complete - mqtt5_client node wrapper no longer resolvable");
|
|
goto done;
|
|
}
|
|
|
|
AWS_NAPI_CALL(env, napi_create_uint32(env, binding->error_code, ¶ms[1]), { goto done; });
|
|
|
|
if (binding->valid_storage == AWS_MQTT5_PT_UNSUBACK) {
|
|
if (s_create_napi_unsuback_packet(env, &binding->packet_storage.unsuback.storage_view, ¶ms[2])) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL, "s_napi_on_unsubscribe_complete - could not build suback napi value");
|
|
goto done;
|
|
}
|
|
} else if (napi_get_undefined(env, ¶ms[2]) != napi_ok) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL, "s_napi_on_unsubscribe_complete - could not get undefined napi_value");
|
|
goto done;
|
|
}
|
|
|
|
AWS_NAPI_ENSURE(
|
|
env,
|
|
aws_napi_dispatch_threadsafe_function(
|
|
env, binding->on_operation_completion, NULL, function, num_params, params));
|
|
}
|
|
|
|
done:
|
|
|
|
s_aws_napi_mqtt5_operation_binding_destroy(binding);
|
|
}
|
|
|
|
static void s_on_unsubscribe_complete(
|
|
const struct aws_mqtt5_packet_unsuback_view *unsuback,
|
|
int error_code,
|
|
void *complete_ctx) {
|
|
|
|
struct aws_allocator *allocator = aws_napi_get_allocator();
|
|
struct aws_napi_mqtt5_operation_binding *binding = complete_ctx;
|
|
|
|
binding->error_code = error_code;
|
|
if (unsuback != NULL && aws_mqtt5_packet_unsuback_storage_init(
|
|
&binding->packet_storage.unsuback, allocator, unsuback) == AWS_OP_SUCCESS) {
|
|
binding->valid_storage = AWS_MQTT5_PT_UNSUBACK;
|
|
} else if (binding->error_code == AWS_ERROR_SUCCESS) {
|
|
binding->error_code = aws_last_error();
|
|
}
|
|
|
|
/* queue a callback in node's libuv thread */
|
|
AWS_NAPI_ENSURE(NULL, aws_napi_queue_threadsafe_function(binding->on_operation_completion, binding));
|
|
}
|
|
|
|
struct aws_napi_mqtt5_unsubscribe_storage {
|
|
struct aws_array_list topic_filter_cursors;
|
|
struct aws_byte_buf topic_filters;
|
|
|
|
struct aws_napi_mqtt5_user_property_storage user_properties;
|
|
};
|
|
|
|
static void s_aws_napi_mqtt5_unsubscribe_storage_clean_up(struct aws_napi_mqtt5_unsubscribe_storage *storage) {
|
|
aws_array_list_clean_up(&storage->topic_filter_cursors);
|
|
aws_byte_buf_clean_up(&storage->topic_filters);
|
|
|
|
s_aws_mqtt5_user_properties_clean_up(&storage->user_properties);
|
|
}
|
|
|
|
static int s_aws_mqtt5_packet_unsubscribe_storage_init_from_napi(
|
|
struct aws_napi_mqtt5_unsubscribe_storage *unsubscribe_storage,
|
|
struct aws_mqtt5_packet_unsubscribe_view *unsubscribe_view,
|
|
struct aws_mqtt5_client_binding *binding,
|
|
napi_env env,
|
|
napi_value node_unsubscribe_packet) {
|
|
if (env == NULL) {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_THREADSAFE_FUNCTION_NULL_NAPI_ENV);
|
|
}
|
|
|
|
napi_value napi_topic_filters = NULL;
|
|
if (AWS_NGNPR_VALID_VALUE !=
|
|
aws_napi_get_named_property(
|
|
env, node_unsubscribe_packet, AWS_NAPI_KEY_TOPIC_FILTERS, napi_object, &napi_topic_filters)) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_aws_mqtt5_packet_unsubscribe_storage_init_from_napi - missing require parameter: subscriptions",
|
|
(void *)binding->client);
|
|
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
|
|
}
|
|
|
|
/* how many topic filters and total topic filter length */
|
|
uint32_t topic_filter_count = 0;
|
|
AWS_NAPI_CALL(env, napi_get_array_length(env, napi_topic_filters, &topic_filter_count), {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"id=%p s_aws_mqtt5_packet_unsubscribe_storage_init_from_napi - topic filters is not an array",
|
|
(void *)binding->client);
|
|
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
|
|
});
|
|
|
|
size_t topic_filter_length_sum = 0;
|
|
for (uint32_t i = 0; i < topic_filter_count; ++i) {
|
|
napi_value napi_topic_filter = NULL;
|
|
AWS_NAPI_CALL(env, napi_get_element(env, napi_topic_filters, i, &napi_topic_filter), { return AWS_OP_ERR; });
|
|
|
|
struct aws_byte_buf topic_filter_buf;
|
|
AWS_ZERO_STRUCT(topic_filter_buf);
|
|
|
|
AWS_NAPI_CALL(env, aws_byte_buf_init_from_napi(&topic_filter_buf, env, napi_topic_filter), {
|
|
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
|
|
});
|
|
|
|
topic_filter_length_sum += topic_filter_buf.len;
|
|
|
|
aws_byte_buf_clean_up(&topic_filter_buf);
|
|
}
|
|
|
|
struct aws_allocator *allocator = aws_napi_get_allocator();
|
|
|
|
if (aws_array_list_init_dynamic(
|
|
&unsubscribe_storage->topic_filter_cursors,
|
|
allocator,
|
|
topic_filter_count,
|
|
sizeof(struct aws_byte_cursor))) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_byte_buf_init(&unsubscribe_storage->topic_filters, allocator, topic_filter_length_sum)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < topic_filter_count; ++i) {
|
|
napi_value napi_topic_filter = NULL;
|
|
AWS_NAPI_CALL(env, napi_get_element(env, napi_topic_filters, i, &napi_topic_filter), { return AWS_OP_ERR; });
|
|
|
|
struct aws_byte_buf topic_filter_buf;
|
|
AWS_ZERO_STRUCT(topic_filter_buf);
|
|
|
|
AWS_NAPI_CALL(env, aws_byte_buf_init_from_napi(&topic_filter_buf, env, napi_topic_filter), {
|
|
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
|
|
});
|
|
|
|
struct aws_byte_cursor topic_filter = aws_byte_cursor_from_buf(&topic_filter_buf);
|
|
|
|
bool success =
|
|
aws_byte_buf_append_and_update(&unsubscribe_storage->topic_filters, &topic_filter) == AWS_OP_SUCCESS;
|
|
|
|
aws_byte_buf_clean_up(&topic_filter_buf);
|
|
|
|
if (!success) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
aws_array_list_push_back(&unsubscribe_storage->topic_filter_cursors, &topic_filter);
|
|
}
|
|
|
|
unsubscribe_view->topic_filter_count = topic_filter_count;
|
|
unsubscribe_view->topic_filters = unsubscribe_storage->topic_filter_cursors.data;
|
|
|
|
if (s_aws_mqtt5_user_properties_extract_from_js_object(
|
|
binding,
|
|
&unsubscribe_storage->user_properties,
|
|
env,
|
|
node_unsubscribe_packet,
|
|
&unsubscribe_view->user_property_count,
|
|
&unsubscribe_view->user_properties)) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"s_aws_mqtt5_packet_unsubscribe_storage_init_from_napi - failed to extract userProperties");
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
napi_value aws_napi_mqtt5_client_unsubscribe(napi_env env, napi_callback_info info) {
|
|
struct aws_allocator *allocator = aws_napi_get_allocator();
|
|
|
|
napi_value node_args[3];
|
|
size_t num_args = AWS_ARRAY_SIZE(node_args);
|
|
napi_value *arg = &node_args[0];
|
|
AWS_NAPI_CALL(env, napi_get_cb_info(env, info, &num_args, node_args, NULL, NULL), {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_unsubscribe - Failed to extract parameter array");
|
|
return NULL;
|
|
});
|
|
|
|
if (num_args != AWS_ARRAY_SIZE(node_args)) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_unsubscribe - needs exactly 3 arguments");
|
|
return NULL;
|
|
}
|
|
|
|
bool successful = false;
|
|
struct aws_mqtt5_client_binding *client_binding = NULL;
|
|
napi_value node_binding = *arg++;
|
|
AWS_NAPI_CALL(env, napi_get_value_external(env, node_binding, (void **)&client_binding), {
|
|
napi_throw_error(
|
|
env, NULL, "aws_napi_mqtt5_client_unsubscribe - Failed to extract client binding from first argument");
|
|
return NULL;
|
|
});
|
|
|
|
if (client_binding == NULL) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_unsubscribe - binding was null");
|
|
return NULL;
|
|
}
|
|
|
|
if (client_binding->client == NULL) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_unsubscribe - client was null");
|
|
return NULL;
|
|
}
|
|
|
|
struct aws_napi_mqtt5_operation_binding *binding =
|
|
aws_mem_calloc(allocator, 1, sizeof(struct aws_napi_mqtt5_operation_binding));
|
|
binding->allocator = allocator;
|
|
binding->client_binding = s_aws_mqtt5_client_binding_acquire(client_binding);
|
|
|
|
napi_value node_unsubscribe_packet = *arg++;
|
|
|
|
struct aws_napi_mqtt5_unsubscribe_storage unsubscribe_storage;
|
|
AWS_ZERO_STRUCT(unsubscribe_storage);
|
|
struct aws_mqtt5_packet_unsubscribe_view unsubscribe_view;
|
|
AWS_ZERO_STRUCT(unsubscribe_view);
|
|
if (s_aws_mqtt5_packet_unsubscribe_storage_init_from_napi(
|
|
&unsubscribe_storage, &unsubscribe_view, client_binding, env, node_unsubscribe_packet)) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_unsubscribe - storage init failure");
|
|
goto done;
|
|
}
|
|
|
|
napi_value completion_callback = *arg++;
|
|
AWS_NAPI_CALL(
|
|
env,
|
|
aws_napi_create_threadsafe_function(
|
|
env,
|
|
completion_callback,
|
|
"aws_mqtt5_on_unsubscribe_complete",
|
|
s_napi_on_unsubscribe_complete,
|
|
binding,
|
|
&binding->on_operation_completion),
|
|
{
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_unsubscribe - failed to create threadsafe function");
|
|
goto done;
|
|
});
|
|
|
|
struct aws_mqtt5_unsubscribe_completion_options completion_options = {
|
|
.completion_callback = s_on_unsubscribe_complete,
|
|
.completion_user_data = binding,
|
|
};
|
|
|
|
if (aws_mqtt5_client_unsubscribe(client_binding->client, &unsubscribe_view, &completion_options)) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_unsubscribe - failure invoking native client unsubscribe");
|
|
goto done;
|
|
}
|
|
|
|
successful = true;
|
|
|
|
done:
|
|
|
|
s_aws_napi_mqtt5_unsubscribe_storage_clean_up(&unsubscribe_storage);
|
|
|
|
if (!successful) {
|
|
s_aws_napi_mqtt5_operation_binding_destroy(binding);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int s_create_napi_puback_packet(
|
|
napi_env env,
|
|
const struct aws_mqtt5_packet_puback_view *puback,
|
|
napi_value *packet_out) {
|
|
|
|
if (env == NULL) {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_THREADSAFE_FUNCTION_NULL_NAPI_ENV);
|
|
}
|
|
|
|
napi_value napi_puback = NULL;
|
|
AWS_NAPI_CALL(
|
|
env, napi_create_object(env, &napi_puback), { return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE); });
|
|
|
|
if (aws_napi_attach_object_property_u32(napi_puback, env, AWS_NAPI_KEY_TYPE, (uint32_t)AWS_MQTT5_PT_PUBACK)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_u32(napi_puback, env, AWS_NAPI_KEY_REASON_CODE, puback->reason_code)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_optional_string(
|
|
napi_puback, env, AWS_NAPI_KEY_REASON_STRING, puback->reason_string)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (s_attach_object_property_user_properties(
|
|
napi_puback, env, puback->user_property_count, puback->user_properties)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
*packet_out = napi_puback;
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
/* in-node/libuv-thread function to trigger the completion of a publish promise */
|
|
static void s_napi_on_publish_complete(napi_env env, napi_value function, void *context, void *user_data) {
|
|
(void)user_data;
|
|
|
|
struct aws_napi_mqtt5_operation_binding *binding = context;
|
|
|
|
if (env) {
|
|
napi_value params[3];
|
|
const size_t num_params = AWS_ARRAY_SIZE(params);
|
|
|
|
/*
|
|
* If we can't resolve the weak ref to the mqtt5 client, then it's been garbage collected and we should not
|
|
* do anything.
|
|
*/
|
|
params[0] = NULL;
|
|
if (napi_get_reference_value(env, binding->client_binding->node_mqtt5_client_ref, ¶ms[0]) != napi_ok ||
|
|
params[0] == NULL) {
|
|
AWS_LOGF_INFO(
|
|
AWS_LS_NODEJS_CRT_GENERAL,
|
|
"s_napi_on_publish_complete - mqtt5_client node wrapper no longer resolvable");
|
|
goto done;
|
|
}
|
|
|
|
AWS_NAPI_CALL(env, napi_create_uint32(env, binding->error_code, ¶ms[1]), { goto done; });
|
|
|
|
if (binding->valid_storage == AWS_MQTT5_PT_PUBACK) {
|
|
if (s_create_napi_puback_packet(env, &binding->packet_storage.puback.storage_view, ¶ms[2])) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL, "s_napi_on_publish_complete - could not build puback napi_value");
|
|
goto done;
|
|
}
|
|
} else {
|
|
if (napi_get_undefined(env, ¶ms[2]) != napi_ok) {
|
|
AWS_LOGF_ERROR(
|
|
AWS_LS_NODEJS_CRT_GENERAL, "s_napi_on_publish_complete - could not get undefined napi_value");
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
AWS_NAPI_ENSURE(
|
|
env,
|
|
aws_napi_dispatch_threadsafe_function(
|
|
env, binding->on_operation_completion, NULL, function, num_params, params));
|
|
}
|
|
|
|
done:
|
|
|
|
s_aws_napi_mqtt5_operation_binding_destroy(binding);
|
|
}
|
|
|
|
static void s_on_publish_complete(
|
|
enum aws_mqtt5_packet_type packet_type,
|
|
const void *packet,
|
|
int error_code,
|
|
void *complete_ctx) {
|
|
|
|
struct aws_allocator *allocator = aws_napi_get_allocator();
|
|
struct aws_napi_mqtt5_operation_binding *binding = complete_ctx;
|
|
|
|
binding->error_code = error_code;
|
|
|
|
if (packet_type == AWS_MQTT5_PT_PUBACK) {
|
|
const struct aws_mqtt5_packet_puback_view *puback = packet;
|
|
if (aws_mqtt5_packet_puback_storage_init(&binding->packet_storage.puback, allocator, puback) ==
|
|
AWS_OP_SUCCESS) {
|
|
binding->valid_storage = AWS_MQTT5_PT_PUBACK;
|
|
} else if (binding->error_code == AWS_ERROR_SUCCESS) {
|
|
binding->error_code = aws_last_error();
|
|
}
|
|
}
|
|
|
|
/* queue a callback in node's libuv thread */
|
|
AWS_NAPI_ENSURE(NULL, aws_napi_queue_threadsafe_function(binding->on_operation_completion, binding));
|
|
}
|
|
|
|
napi_value aws_napi_mqtt5_client_publish(napi_env env, napi_callback_info info) {
|
|
struct aws_allocator *allocator = aws_napi_get_allocator();
|
|
|
|
napi_value node_args[3];
|
|
size_t num_args = AWS_ARRAY_SIZE(node_args);
|
|
napi_value *arg = &node_args[0];
|
|
AWS_NAPI_CALL(env, napi_get_cb_info(env, info, &num_args, node_args, NULL, NULL), {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_publish - Failed to extract parameter array");
|
|
return NULL;
|
|
});
|
|
|
|
if (num_args != AWS_ARRAY_SIZE(node_args)) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_publish - needs exactly 3 arguments");
|
|
return NULL;
|
|
}
|
|
|
|
bool successful = false;
|
|
struct aws_mqtt5_client_binding *client_binding = NULL;
|
|
napi_value node_binding = *arg++;
|
|
AWS_NAPI_CALL(env, napi_get_value_external(env, node_binding, (void **)&client_binding), {
|
|
napi_throw_error(
|
|
env, NULL, "aws_napi_mqtt5_client_publish - Failed to extract client binding from first argument");
|
|
return NULL;
|
|
});
|
|
|
|
if (client_binding == NULL) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_publish - binding was null");
|
|
return NULL;
|
|
}
|
|
|
|
if (client_binding->client == NULL) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_publish - client was null");
|
|
return NULL;
|
|
}
|
|
|
|
struct aws_napi_mqtt5_operation_binding *binding =
|
|
aws_mem_calloc(allocator, 1, sizeof(struct aws_napi_mqtt5_operation_binding));
|
|
binding->allocator = allocator;
|
|
binding->client_binding = s_aws_mqtt5_client_binding_acquire(client_binding);
|
|
|
|
napi_value node_publish_packet = *arg++;
|
|
|
|
struct aws_napi_mqtt5_publish_storage publish_storage;
|
|
AWS_ZERO_STRUCT(publish_storage);
|
|
struct aws_mqtt5_packet_publish_view publish_view;
|
|
AWS_ZERO_STRUCT(publish_view);
|
|
if (s_init_publish_options_from_napi(client_binding, env, node_publish_packet, &publish_view, &publish_storage)) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_publish - storage init failure");
|
|
goto done;
|
|
}
|
|
|
|
napi_value completion_callback = *arg++;
|
|
AWS_NAPI_CALL(
|
|
env,
|
|
aws_napi_create_threadsafe_function(
|
|
env,
|
|
completion_callback,
|
|
"aws_mqtt5_on_publish_complete",
|
|
s_napi_on_publish_complete,
|
|
binding,
|
|
&binding->on_operation_completion),
|
|
{
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_publish - failed to create threadsafe function");
|
|
goto done;
|
|
});
|
|
|
|
struct aws_mqtt5_publish_completion_options completion_options = {
|
|
.completion_callback = s_on_publish_complete,
|
|
.completion_user_data = binding,
|
|
};
|
|
|
|
if (aws_mqtt5_client_publish(client_binding->client, &publish_view, &completion_options)) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_publish - failure invoking native client publish");
|
|
goto done;
|
|
}
|
|
|
|
successful = true;
|
|
|
|
done:
|
|
|
|
s_aws_napi_mqtt5_publish_storage_clean_up(&publish_storage);
|
|
|
|
if (!successful) {
|
|
s_aws_napi_mqtt5_operation_binding_destroy(binding);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int s_create_napi_mqtt5_client_statistics(
|
|
napi_env env,
|
|
const struct aws_mqtt5_client_operation_statistics *stats,
|
|
napi_value *stats_out) {
|
|
|
|
if (env == NULL) {
|
|
return aws_raise_error(AWS_CRT_NODEJS_ERROR_THREADSAFE_FUNCTION_NULL_NAPI_ENV);
|
|
}
|
|
|
|
napi_value napi_stats = NULL;
|
|
AWS_NAPI_CALL(
|
|
env, napi_create_object(env, &napi_stats), { return aws_raise_error(AWS_CRT_NODEJS_ERROR_NAPI_FAILURE); });
|
|
|
|
if (aws_napi_attach_object_property_u64(
|
|
napi_stats, env, AWS_NAPI_KEY_INCOMPLETE_OPERATION_COUNT, stats->incomplete_operation_count)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_u64(
|
|
napi_stats, env, AWS_NAPI_KEY_INCOMPLETE_OPERATION_SIZE, stats->incomplete_operation_size)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_u64(
|
|
napi_stats, env, AWS_NAPI_KEY_UNACKED_OPERATION_COUNT, stats->unacked_operation_count)) {
|
|
return AWS_OP_ERR;
|
|
}
|
|
|
|
if (aws_napi_attach_object_property_u64(
|
|
napi_stats, env, AWS_NAPI_KEY_UNACKED_OPERATION_SIZE, stats->unacked_operation_size)) {
|
|
return AWS_OP_ERR;
|
|
};
|
|
|
|
*stats_out = napi_stats;
|
|
|
|
return AWS_OP_SUCCESS;
|
|
}
|
|
|
|
napi_value aws_napi_mqtt5_client_get_queue_statistics(napi_env env, napi_callback_info info) {
|
|
|
|
napi_value node_args[1];
|
|
size_t num_args = AWS_ARRAY_SIZE(node_args);
|
|
napi_value *arg = &node_args[0];
|
|
AWS_NAPI_CALL(env, napi_get_cb_info(env, info, &num_args, node_args, NULL, NULL), {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_get_queue_statistics - Failed to extract parameter array");
|
|
return NULL;
|
|
});
|
|
|
|
if (num_args != AWS_ARRAY_SIZE(node_args)) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_get_queue_statistics - needs exactly 1 argument");
|
|
return NULL;
|
|
}
|
|
|
|
struct aws_mqtt5_client_binding *client_binding = NULL;
|
|
napi_value node_binding = *arg++;
|
|
AWS_NAPI_CALL(env, napi_get_value_external(env, node_binding, (void **)&client_binding), {
|
|
napi_throw_error(
|
|
env,
|
|
NULL,
|
|
"aws_napi_mqtt5_client_get_queue_statistics - Failed to extract client binding from first argument");
|
|
return NULL;
|
|
});
|
|
|
|
if (client_binding == NULL) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_get_queue_statistics - binding was null");
|
|
return NULL;
|
|
}
|
|
|
|
if (client_binding->client == NULL) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_get_queue_statistics - client was null");
|
|
return NULL;
|
|
}
|
|
|
|
struct aws_mqtt5_client_operation_statistics stats;
|
|
AWS_ZERO_STRUCT(stats);
|
|
|
|
aws_mqtt5_client_get_stats(client_binding->client, &stats);
|
|
|
|
napi_value napi_stats = NULL;
|
|
if (s_create_napi_mqtt5_client_statistics(env, &stats, &napi_stats)) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_get_queue_statistics - failed to build statistics value");
|
|
return NULL;
|
|
}
|
|
|
|
return napi_stats;
|
|
}
|
|
|
|
napi_value aws_napi_mqtt5_client_close(napi_env env, napi_callback_info info) {
|
|
napi_value node_args[1];
|
|
size_t num_args = AWS_ARRAY_SIZE(node_args);
|
|
napi_value *arg = &node_args[0];
|
|
AWS_NAPI_CALL(env, napi_get_cb_info(env, info, &num_args, node_args, NULL, NULL), {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_close - Failed to retrieve arguments");
|
|
return NULL;
|
|
});
|
|
|
|
if (num_args != AWS_ARRAY_SIZE(node_args)) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_close - needs exactly 1 argument");
|
|
return NULL;
|
|
}
|
|
|
|
struct aws_mqtt5_client_binding *binding = NULL;
|
|
napi_value node_binding = *arg++;
|
|
AWS_NAPI_CALL(env, napi_get_value_external(env, node_binding, (void **)&binding), {
|
|
napi_throw_error(
|
|
env, NULL, "aws_napi_mqtt5_client_close - Failed to extract client binding from first argument");
|
|
return NULL;
|
|
});
|
|
|
|
if (binding == NULL) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_close - binding was null");
|
|
return NULL;
|
|
}
|
|
|
|
if (binding->client == NULL) {
|
|
napi_throw_error(env, NULL, "aws_napi_mqtt5_client_close - client was null");
|
|
return NULL;
|
|
}
|
|
|
|
napi_ref node_client_external_ref = binding->node_client_external_ref;
|
|
binding->node_client_external_ref = NULL;
|
|
|
|
napi_ref node_mqtt5_client_ref = binding->node_mqtt5_client_ref;
|
|
binding->node_mqtt5_client_ref = NULL;
|
|
|
|
if (node_client_external_ref != NULL) {
|
|
napi_delete_reference(env, node_client_external_ref);
|
|
}
|
|
|
|
if (node_mqtt5_client_ref != NULL) {
|
|
napi_delete_reference(env, node_mqtt5_client_ref);
|
|
}
|
|
|
|
return NULL;
|
|
}
|