mirror of
https://github.com/google/pebble.git
synced 2025-03-25 04:49:06 +00:00
971 lines
34 KiB
C
971 lines
34 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "clar.h"
|
|
#include "test_jerry_port_common.h"
|
|
#include "test_rocky_common.h"
|
|
|
|
#include "applib/rockyjs/api/rocky_api_global.h"
|
|
#include "applib/rockyjs/api/rocky_api_app_message.h"
|
|
#include "applib/rockyjs/pbl_jerry_port.h"
|
|
|
|
#include "applib/app_message/app_message.h"
|
|
#include "util/dict.h"
|
|
#include "util/size.h"
|
|
|
|
#include <string.h>
|
|
|
|
// Fakes
|
|
#include "fake_app_timer.h"
|
|
#include "fake_event_service.h"
|
|
#include "fake_pbl_malloc.h"
|
|
#include "fake_time.h"
|
|
|
|
// Stubs
|
|
#include "stubs_app_state.h"
|
|
#include "stubs_comm_session.h"
|
|
#include "stubs_logging.h"
|
|
#include "stubs_passert.h"
|
|
#include "stubs_serial.h"
|
|
#include "stubs_sys_exit.h"
|
|
|
|
extern PostMessageState rocky_api_app_message_get_state(void);
|
|
extern AppTimer *rocky_api_app_message_get_app_msg_retry_timer(void);
|
|
extern AppTimer *rocky_api_app_message_get_session_closed_object_queue_timer(void);
|
|
|
|
T_STATIC jerry_value_t prv_json_stringify(jerry_value_t object);
|
|
T_STATIC jerry_value_t prv_json_parse(const char *);
|
|
|
|
T_STATIC void prv_handle_connection(void);
|
|
T_STATIC void prv_handle_disconnection(void);
|
|
|
|
// App message mocks
|
|
|
|
static AppMessageInboxReceived s_received_callback;
|
|
AppMessageInboxReceived app_message_register_inbox_received(
|
|
AppMessageInboxReceived received_callback) {
|
|
AppMessageInboxReceived prev_cb = s_received_callback;
|
|
s_received_callback = received_callback;
|
|
return prev_cb;
|
|
}
|
|
|
|
static AppMessageInboxDropped s_dropped_callback;
|
|
AppMessageInboxDropped app_message_register_inbox_dropped(AppMessageInboxDropped dropped_callback) {
|
|
AppMessageInboxDropped prev_cb = s_dropped_callback;
|
|
s_dropped_callback = dropped_callback;
|
|
return prev_cb;
|
|
}
|
|
|
|
static AppMessageOutboxSent s_sent_callback;
|
|
AppMessageOutboxSent app_message_register_outbox_sent(AppMessageOutboxSent sent_callback) {
|
|
AppMessageOutboxSent prev_cb = s_sent_callback;
|
|
s_sent_callback = sent_callback;
|
|
return prev_cb;
|
|
}
|
|
|
|
static AppMessageOutboxFailed s_failed_callback;
|
|
AppMessageOutboxFailed app_message_register_outbox_failed(AppMessageOutboxFailed failed_callback) {
|
|
AppMessageOutboxFailed prev_cb = s_failed_callback;
|
|
s_failed_callback = failed_callback;
|
|
return prev_cb;
|
|
}
|
|
|
|
void app_message_deregister_callbacks(void) {
|
|
s_received_callback = NULL;
|
|
s_dropped_callback = NULL;
|
|
s_sent_callback = NULL;
|
|
s_failed_callback = NULL;
|
|
}
|
|
|
|
static uint32_t s_inbox_size;
|
|
static uint32_t s_outbox_size;
|
|
AppMessageResult app_message_open(const uint32_t size_inbound, const uint32_t size_outbound) {
|
|
s_inbox_size = size_inbound;
|
|
s_outbox_size = size_outbound;
|
|
return APP_MSG_OK;
|
|
}
|
|
|
|
static bool s_is_outbox_message_pending;
|
|
static DictionaryIterator s_outbox_iterator;
|
|
static uint8_t *s_outbox_buffer;
|
|
AppMessageResult app_message_outbox_begin(DictionaryIterator **iterator) {
|
|
cl_assert_equal_b(s_is_outbox_message_pending, false);
|
|
if (!s_outbox_buffer) {
|
|
s_outbox_buffer = malloc(s_outbox_size);
|
|
}
|
|
dict_write_begin(&s_outbox_iterator, s_outbox_buffer, s_outbox_size);
|
|
*iterator = &s_outbox_iterator;
|
|
|
|
return APP_MSG_OK;
|
|
}
|
|
|
|
static int s_app_message_outbox_send_call_count;
|
|
AppMessageResult app_message_outbox_send(void) {
|
|
++s_app_message_outbox_send_call_count;
|
|
s_is_outbox_message_pending = true;
|
|
return APP_MSG_OK;
|
|
}
|
|
|
|
static bool s_comm_session_connected;
|
|
CommSession *sys_app_pp_get_comm_session(void) {
|
|
return (CommSession *)s_comm_session_connected;
|
|
}
|
|
|
|
static void prv_rcv_app_message_ack(AppMessageResult result) {
|
|
void *context = NULL;
|
|
cl_assert_equal_b(s_is_outbox_message_pending, true);
|
|
s_is_outbox_message_pending = false;
|
|
if (result == APP_MSG_OK) {
|
|
s_sent_callback(&s_outbox_iterator, context);
|
|
} else {
|
|
s_failed_callback(&s_outbox_iterator, result, context);
|
|
}
|
|
}
|
|
|
|
static void prv_app_message_setup(void) {
|
|
s_inbox_size = 0;
|
|
s_outbox_size = 0;
|
|
s_outbox_buffer = NULL;
|
|
s_app_message_outbox_send_call_count = 0;
|
|
s_is_outbox_message_pending = false;
|
|
app_message_deregister_callbacks();
|
|
}
|
|
|
|
static void prv_app_message_teardown(void) {
|
|
if (s_outbox_buffer) {
|
|
free(s_outbox_buffer);
|
|
}
|
|
}
|
|
|
|
// Statics and Utilities
|
|
|
|
static void prv_init_and_goto_session_open(void);
|
|
|
|
static void prv_simulate_transport_connection_event(bool is_connected) {
|
|
// FIXME: use events here instead of poking at the internals!
|
|
if (is_connected) {
|
|
prv_handle_connection();
|
|
} else {
|
|
prv_handle_disconnection();
|
|
}
|
|
}
|
|
|
|
static const RockyGlobalAPI *s_app_message_api[] = {
|
|
&APP_MESSAGE_APIS,
|
|
NULL,
|
|
};
|
|
|
|
static void prv_init_api(bool start_connected) {
|
|
s_comm_session_connected = start_connected;
|
|
rocky_global_init(s_app_message_api);
|
|
}
|
|
|
|
// Setup
|
|
|
|
void test_rocky_api_app_message__initialize(void) {
|
|
fake_app_timer_init();
|
|
fake_pbl_malloc_clear_tracking();
|
|
prv_app_message_setup();
|
|
|
|
s_process_manager_callback = NULL;
|
|
s_process_manager_callback_data = NULL;
|
|
|
|
rocky_runtime_context_init();
|
|
jerry_init(JERRY_INIT_EMPTY);
|
|
}
|
|
|
|
void test_rocky_api_app_message__cleanup(void) {
|
|
rocky_global_deinit();
|
|
jerry_cleanup();
|
|
rocky_runtime_context_deinit();
|
|
prv_app_message_teardown();
|
|
fake_pbl_malloc_check_net_allocs();
|
|
fake_app_timer_deinit();
|
|
}
|
|
|
|
static const PostMessageResetCompletePayload VALID_RESET_COMPLETE = {
|
|
.min_supported_version = POSTMESSAGE_PROTOCOL_MIN_VERSION,
|
|
.max_supported_version = POSTMESSAGE_PROTOCOL_MAX_VERSION,
|
|
.max_tx_chunk_size = POSTMESSAGE_PROTOCOL_MAX_TX_CHUNK_SIZE,
|
|
.max_rx_chunk_size = POSTMESSAGE_PROTOCOL_MAX_RX_CHUNK_SIZE,
|
|
};
|
|
|
|
static const size_t TINY_CHUNK_SIZE = 4;
|
|
|
|
static const PostMessageResetCompletePayload TINY_RESET_COMPLETE = {
|
|
.min_supported_version = POSTMESSAGE_PROTOCOL_MIN_VERSION,
|
|
.max_supported_version = POSTMESSAGE_PROTOCOL_MAX_VERSION,
|
|
.max_tx_chunk_size = TINY_CHUNK_SIZE,
|
|
.max_rx_chunk_size = TINY_CHUNK_SIZE,
|
|
};
|
|
|
|
#define RCV_APP_MESSAGE(...) \
|
|
do { \
|
|
Tuplet tuplets[] = { __VA_ARGS__ }; \
|
|
uint32_t buffer_size = dict_calc_buffer_size_from_tuplets(tuplets, ARRAY_LENGTH(tuplets)); \
|
|
uint8_t buffer[buffer_size]; \
|
|
DictionaryIterator it; \
|
|
const DictionaryResult result = \
|
|
dict_serialize_tuplets_to_buffer_with_iter(&it, tuplets, ARRAY_LENGTH(tuplets), \
|
|
buffer, &buffer_size); \
|
|
cl_assert_equal_i(DICT_OK, result); \
|
|
if (s_received_callback) { \
|
|
s_received_callback(&it, NULL); \
|
|
} \
|
|
} while(0);
|
|
|
|
|
|
#define RCV_RESET_REQUEST() \
|
|
RCV_APP_MESSAGE(TupletBytes(PostMessageKeyResetRequest, NULL, 0));
|
|
|
|
#define RCV_RESET_COMPLETE() \
|
|
RCV_APP_MESSAGE(TupletBytes(PostMessageKeyResetComplete, \
|
|
(const uint8_t *)&VALID_RESET_COMPLETE, sizeof(VALID_RESET_COMPLETE)));
|
|
|
|
#define RCV_DUMMY_CHUNK() \
|
|
do { \
|
|
PostMessageChunkPayload chunk = {}; \
|
|
RCV_APP_MESSAGE(TupletBytes(PostMessageKeyChunk, (const uint8_t *) &chunk, sizeof(chunk))); \
|
|
} while(0);
|
|
|
|
//! Asserts whether the outbox has a pending message containing the tuples passed to this macro.
|
|
//! The value and type of the tuples is also asserted.
|
|
//! @note Only asserts if expected tuples are MISSING. It will not trip if there are other
|
|
//! (non-expected) tuples in the set.
|
|
#define EXPECT_OUTBOX_MESSAGE_PENDING(...) \
|
|
do { \
|
|
cl_assert_equal_b(true, s_is_outbox_message_pending); \
|
|
/* The cursor must be updated! */ \
|
|
cl_assert(s_outbox_iterator.cursor != s_outbox_iterator.dictionary->head); \
|
|
Tuplet tuplets[] = { __VA_ARGS__ }; \
|
|
uint32_t buffer_size = dict_calc_buffer_size_from_tuplets(tuplets, ARRAY_LENGTH(tuplets)); \
|
|
uint8_t buffer[buffer_size]; \
|
|
DictionaryIterator expected_it; \
|
|
const DictionaryResult result = \
|
|
dict_serialize_tuplets_to_buffer_with_iter(&expected_it, tuplets, ARRAY_LENGTH(tuplets), \
|
|
buffer, &buffer_size); \
|
|
cl_assert_equal_i(DICT_OK, result); \
|
|
for (Tuple *expected_t = dict_read_first(&expected_it); expected_t != NULL; \
|
|
expected_t = dict_read_next(&expected_it)) { \
|
|
Tuple *found_t = dict_find(&s_outbox_iterator, expected_t->key); \
|
|
cl_assert(found_t); \
|
|
cl_assert_equal_i(found_t->type, expected_t->type); \
|
|
cl_assert_equal_i(found_t->length, expected_t->length); \
|
|
if (expected_t->length) { \
|
|
cl_assert_equal_i(0, memcmp(found_t->value[0].data, expected_t->value[0].data, \
|
|
expected_t->length)); \
|
|
} \
|
|
} \
|
|
} while (0);
|
|
|
|
#define EXPECT_OUTBOX_NO_MESSAGE_PENDING() \
|
|
cl_assert_equal_b(false, s_is_outbox_message_pending);
|
|
|
|
#define EXPECT_OUTBOX_RESET_REQUEST() \
|
|
EXPECT_OUTBOX_MESSAGE_PENDING(TupletBytes(PostMessageKeyResetRequest, NULL, 0));
|
|
|
|
#define EXPECT_OUTBOX_RESET_COMPLETE_PENDING() \
|
|
EXPECT_OUTBOX_MESSAGE_PENDING(TupletBytes(PostMessageKeyResetComplete, \
|
|
(const uint8_t *) &VALID_RESET_COMPLETE, \
|
|
sizeof(VALID_RESET_COMPLETE)));
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Negotiation Steps
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void test_rocky_api_app_message__disconnected__ignore_any_app_message(void) {
|
|
prv_init_api(false /* start_connected */);
|
|
|
|
for (PostMessageKey key = PostMessageKeyResetRequest; key < PostMessageKey_Count; ++key) {
|
|
uint8_t dummy_data[] = {0, 1, 2};
|
|
RCV_APP_MESSAGE(TupletBytes(key, dummy_data, sizeof(dummy_data)));
|
|
}
|
|
|
|
cl_assert_equal_i(0, s_app_message_outbox_send_call_count);
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(), PostMessageStateDisconnected);
|
|
}
|
|
|
|
void test_rocky_api_app_message__awaiting_reset_request__receive_reset_request(void) {
|
|
prv_init_api(true /* start_connected */);
|
|
|
|
RCV_RESET_REQUEST();
|
|
|
|
// Expect responding with a ResetComplete:
|
|
EXPECT_OUTBOX_RESET_COMPLETE_PENDING();
|
|
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(),
|
|
PostMessageStateAwaitingResetCompleteRemoteInitiated);
|
|
}
|
|
|
|
void test_rocky_api_app_message__awaiting_reset_request__receive_chunk(void) {
|
|
prv_init_api(false /* start_connected */);
|
|
prv_simulate_transport_connection_event(true /* is_connected */);
|
|
|
|
RCV_DUMMY_CHUNK();
|
|
// https://pebbletechnology.atlassian.net/browse/PBL-42466
|
|
// TODO: assert that app message was NACK'd
|
|
|
|
// Expect responding with a ResetRequest:
|
|
EXPECT_OUTBOX_RESET_REQUEST();
|
|
EXPECT_OUTBOX_MESSAGE_PENDING(TupletBytes(PostMessageKeyResetRequest, NULL, 0));
|
|
// TODO: check fields
|
|
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(),
|
|
PostMessageStateAwaitingResetCompleteLocalInitiated);
|
|
}
|
|
|
|
void test_rocky_api_app_message__awaiting_reset_request__disconnect(void) {
|
|
prv_init_api(false /* start_connected */);
|
|
prv_simulate_transport_connection_event(true /* is_connected */);
|
|
prv_simulate_transport_connection_event(false /* is_connected */);
|
|
EXPECT_OUTBOX_NO_MESSAGE_PENDING();
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(), PostMessageStateDisconnected);
|
|
}
|
|
|
|
static void prv_init_and_goto_awaiting_reset_complete_remote_initiated(void) {
|
|
prv_init_api(true /* start_connected */);
|
|
|
|
RCV_RESET_REQUEST();
|
|
|
|
EXPECT_OUTBOX_RESET_COMPLETE_PENDING();
|
|
prv_rcv_app_message_ack(APP_MSG_OK);
|
|
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(),
|
|
PostMessageStateAwaitingResetCompleteRemoteInitiated);
|
|
}
|
|
|
|
static void prv_init_and_goto_awaiting_reset_complete_local_initiated(void) {
|
|
prv_init_and_goto_awaiting_reset_complete_remote_initiated();
|
|
RCV_DUMMY_CHUNK();
|
|
EXPECT_OUTBOX_RESET_REQUEST();
|
|
prv_rcv_app_message_ack(APP_MSG_OK);
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(),
|
|
PostMessageStateAwaitingResetCompleteLocalInitiated);
|
|
}
|
|
|
|
void test_rocky_api_app_message__awaiting_reset_complete_rem_init__receive_complete_valid_version(void) {
|
|
prv_init_and_goto_awaiting_reset_complete_remote_initiated();
|
|
|
|
RCV_RESET_COMPLETE();
|
|
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(), PostMessageStateSessionOpen);
|
|
EXPECT_OUTBOX_NO_MESSAGE_PENDING();
|
|
}
|
|
|
|
void test_rocky_api_app_message__awaiting_reset_complete_rem_init__receive_complete_unsupported_ver(void) {
|
|
prv_init_and_goto_awaiting_reset_complete_remote_initiated();
|
|
|
|
const PostMessageResetCompletePayload unsupported = {
|
|
.min_supported_version = POSTMESSAGE_PROTOCOL_MAX_VERSION + 1,
|
|
.max_supported_version = POSTMESSAGE_PROTOCOL_MAX_VERSION + 1,
|
|
.max_tx_chunk_size = POSTMESSAGE_PROTOCOL_MAX_TX_CHUNK_SIZE,
|
|
.max_rx_chunk_size = POSTMESSAGE_PROTOCOL_MAX_RX_CHUNK_SIZE,
|
|
};
|
|
RCV_APP_MESSAGE(TupletBytes(PostMessageKeyResetComplete,
|
|
(const uint8_t *)&unsupported, sizeof(unsupported)));
|
|
|
|
// Expect No UnsupportedError!
|
|
|
|
// Immediately go back to AwaitingResetRequest:
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(), PostMessageStateAwaitingResetRequest);
|
|
EXPECT_OUTBOX_NO_MESSAGE_PENDING();
|
|
}
|
|
|
|
void test_rocky_api_app_message__awaiting_reset_complete_rem_init__malformed_reset_complete(void) {
|
|
prv_init_and_goto_awaiting_reset_complete_remote_initiated();
|
|
|
|
// Receive malformed ResetComplete:
|
|
uint8_t malformed_payload[sizeof(PostMessageResetCompletePayload) - 1] = {};
|
|
RCV_APP_MESSAGE(TupletBytes(PostMessageKeyResetComplete,
|
|
malformed_payload, sizeof(malformed_payload)));
|
|
|
|
// Expect Error:
|
|
const PostMessageUnsupportedErrorPayload expected_error = {
|
|
.error_code = PostMessageErrorMalformedResetComplete,
|
|
};
|
|
EXPECT_OUTBOX_MESSAGE_PENDING(TupletBytes(PostMessageKeyUnsupportedError,
|
|
(const uint8_t *) &expected_error,
|
|
sizeof(expected_error)));
|
|
|
|
// Immediately go back to AwaitingResetRequest:
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(), PostMessageStateAwaitingResetRequest);
|
|
prv_rcv_app_message_ack(APP_MSG_OK);
|
|
EXPECT_OUTBOX_NO_MESSAGE_PENDING();
|
|
}
|
|
|
|
void test_rocky_api_app_message__awaiting_reset_complete_rem_init__receive_request(void) {
|
|
prv_init_and_goto_awaiting_reset_complete_remote_initiated();
|
|
|
|
RCV_RESET_REQUEST();
|
|
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(),
|
|
PostMessageStateAwaitingResetCompleteRemoteInitiated);
|
|
|
|
EXPECT_OUTBOX_RESET_COMPLETE_PENDING();
|
|
}
|
|
|
|
void test_rocky_api_app_message__awaiting_reset_complete_rem_init__receive_chunk(void) {
|
|
prv_init_and_goto_awaiting_reset_complete_remote_initiated();
|
|
|
|
RCV_DUMMY_CHUNK();
|
|
|
|
EXPECT_OUTBOX_RESET_REQUEST();
|
|
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(),
|
|
PostMessageStateAwaitingResetCompleteLocalInitiated);
|
|
|
|
// Receive yet another chunk in "Awaiting Reset Complete Local Initiated":
|
|
RCV_DUMMY_CHUNK();
|
|
// https://pebbletechnology.atlassian.net/browse/PBL-42466
|
|
// TODO: assert that chunk is NACKd
|
|
|
|
// Receive ACK for the ResetRequest:
|
|
prv_rcv_app_message_ack(APP_MSG_OK);
|
|
|
|
// Chunk is ignored, no new reset request is sent out.
|
|
EXPECT_OUTBOX_NO_MESSAGE_PENDING();
|
|
|
|
// TODO: timeout + retry ResetRequest if no ResetComplete follows within N secs.
|
|
}
|
|
|
|
void test_rocky_api_app_message__awaiting_reset_complete_loc_init__(void) {
|
|
prv_init_and_goto_awaiting_reset_complete_local_initiated();
|
|
}
|
|
|
|
void test_rocky_api_app_message__awaiting_reset_complete_loc_init__rcv_reset_request(void) {
|
|
prv_init_and_goto_awaiting_reset_complete_local_initiated();
|
|
|
|
RCV_RESET_REQUEST();
|
|
|
|
EXPECT_OUTBOX_RESET_COMPLETE_PENDING();
|
|
prv_rcv_app_message_ack(APP_MSG_OK);
|
|
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(),
|
|
PostMessageStateAwaitingResetCompleteRemoteInitiated);
|
|
|
|
RCV_RESET_COMPLETE();
|
|
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(), PostMessageStateSessionOpen);
|
|
}
|
|
|
|
void test_rocky_api_app_message__awaiting_reset_complete_loc_init__rcv_chunk(void) {
|
|
prv_init_and_goto_awaiting_reset_complete_local_initiated();
|
|
|
|
RCV_DUMMY_CHUNK();
|
|
|
|
// https://pebbletechnology.atlassian.net/browse/PBL-42466
|
|
// TODO: assert that chunk is NACK'd
|
|
|
|
// Chunk is ignored, state isn't changed:
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(),
|
|
PostMessageStateAwaitingResetCompleteLocalInitiated);
|
|
EXPECT_OUTBOX_NO_MESSAGE_PENDING();
|
|
}
|
|
|
|
static void prv_init_and_goto_session_open(void) {
|
|
prv_init_and_goto_awaiting_reset_complete_remote_initiated();
|
|
RCV_RESET_COMPLETE();
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(), PostMessageStateSessionOpen);
|
|
}
|
|
|
|
void test_rocky_api_app_message__session_open__rcv_reset_request(void) {
|
|
prv_init_and_goto_session_open();
|
|
|
|
EXECUTE_SCRIPT("var isCalled = false;"
|
|
"_rocky.on('postmessagedisconnected', function() { isCalled = true; });");
|
|
|
|
ASSERT_JS_GLOBAL_EQUALS_B("isCalled", false);
|
|
|
|
RCV_RESET_REQUEST();
|
|
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(),
|
|
PostMessageStateAwaitingResetCompleteRemoteInitiated);
|
|
EXPECT_OUTBOX_RESET_COMPLETE_PENDING();
|
|
|
|
ASSERT_JS_GLOBAL_EQUALS_B("isCalled", true);
|
|
|
|
// TODO: assert:
|
|
// - flushed recv chunk reassembly buffer
|
|
}
|
|
|
|
void test_rocky_api_app_message__session_open__rcv_reset_complete(void) {
|
|
prv_init_and_goto_session_open();
|
|
|
|
EXECUTE_SCRIPT("var isCalled = false;"
|
|
"_rocky.on('postmessagedisconnected', function() { isCalled = true; });");
|
|
|
|
ASSERT_JS_GLOBAL_EQUALS_B("isCalled", false);
|
|
|
|
RCV_RESET_COMPLETE();
|
|
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(),
|
|
PostMessageStateAwaitingResetCompleteLocalInitiated);
|
|
EXPECT_OUTBOX_RESET_REQUEST();
|
|
|
|
ASSERT_JS_GLOBAL_EQUALS_B("isCalled", true);
|
|
|
|
// TODO: assert:
|
|
// - flushed recv chunk reassembly buffer
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// postmessageconnected / postmessagedisconnected
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void prv_postmessageconnected_postmessagedisconnected_init(bool start_connected) {
|
|
prv_init_api(start_connected);
|
|
|
|
EXECUTE_SCRIPT("var c = 0; var d = 0;\n"
|
|
"_rocky.on('postmessageconnected', function() { c++; });\n"
|
|
"_rocky.on('postmessagedisconnected', function() { d++; });\n");
|
|
|
|
// Make sure this race is handled (see comment in prv_handle_connection()):
|
|
prv_simulate_transport_connection_event(start_connected /* is_connected */);
|
|
}
|
|
|
|
static void prv_postmessageconnected_postmessagedisconnected_negotiate_to_open_session(void) {
|
|
// Negotiate:
|
|
RCV_RESET_REQUEST();
|
|
|
|
EXPECT_OUTBOX_RESET_COMPLETE_PENDING();
|
|
prv_rcv_app_message_ack(APP_MSG_OK);
|
|
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(),
|
|
PostMessageStateAwaitingResetCompleteRemoteInitiated);
|
|
|
|
RCV_RESET_COMPLETE();
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(), PostMessageStateSessionOpen);
|
|
}
|
|
|
|
void test_rocky_api_app_message__postmessageconnected_and_postmessagedisconnected_remote_rr(void) {
|
|
prv_postmessageconnected_postmessagedisconnected_init(false /* start_connected */);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("d", 1);
|
|
prv_simulate_transport_connection_event(true /* is_connected */);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("c", 0);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("d", 1);
|
|
prv_postmessageconnected_postmessagedisconnected_negotiate_to_open_session();
|
|
ASSERT_JS_GLOBAL_EQUALS_I("c", 1);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("d", 1);
|
|
|
|
// Get a ResetRequest:
|
|
RCV_RESET_REQUEST();
|
|
ASSERT_JS_GLOBAL_EQUALS_I("d", 2);
|
|
}
|
|
|
|
void test_rocky_api_app_message__postmessageconnected_and_postmessagedisconnected_local_rr(void) {
|
|
prv_postmessageconnected_postmessagedisconnected_init(false /* start_connected */);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("d", 1);
|
|
prv_simulate_transport_connection_event(true /* is_connected */);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("c", 0);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("d", 1);
|
|
prv_postmessageconnected_postmessagedisconnected_negotiate_to_open_session();
|
|
ASSERT_JS_GLOBAL_EQUALS_I("c", 1);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("d", 1);
|
|
|
|
// Get a ResetComplete (unexpected message), should trigger initiating (local) ResetRequest:
|
|
RCV_RESET_COMPLETE();
|
|
ASSERT_JS_GLOBAL_EQUALS_I("d", 2);
|
|
}
|
|
|
|
void test_rocky_api_app_message__postmessageconnected_and_postmessagedisconnected_start_conn(void) {
|
|
prv_postmessageconnected_postmessagedisconnected_init(true /* start_connected */);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("c", 0);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("d", 1);
|
|
|
|
prv_postmessageconnected_postmessagedisconnected_negotiate_to_open_session();
|
|
|
|
ASSERT_JS_GLOBAL_EQUALS_I("c", 1);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("d", 1);
|
|
}
|
|
|
|
// TODO: test various min/max version combos
|
|
// TODO: test RX/TX buffer size combos
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Generic Tests
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void test_rocky_api_app_message__json_stringify(void) {
|
|
JS_VAR obj = jerry_create_object();
|
|
JS_VAR json_str = prv_json_stringify(obj);
|
|
char *json_c_str = rocky_string_alloc_and_copy(json_str);
|
|
cl_assert_equal_s(json_c_str, "{}");
|
|
task_free(json_c_str);
|
|
}
|
|
|
|
void test_rocky_api_app_message__json_parse(void) {
|
|
JS_VAR number = prv_json_parse("1");
|
|
cl_assert(jerry_value_is_number(number));
|
|
cl_assert_equal_d(jerry_get_number_value(number), 1.0);
|
|
|
|
JS_VAR object = prv_json_parse("{ \"x\" : 42 }");
|
|
cl_assert(jerry_value_is_object(object));
|
|
JS_VAR x = jerry_get_object_field(object, "x");
|
|
cl_assert(jerry_value_is_number(x));
|
|
cl_assert_equal_d(jerry_get_number_value(x), 42.0);
|
|
}
|
|
|
|
void test_rocky_api_app_message__json_parse_stress(void) {
|
|
const int num_attempts = 0x3ff + 10; // Want this to be higher than the max refcount,
|
|
// which will also be high enough for a memory stress test
|
|
for (int i = 0; i < num_attempts; ++i) {
|
|
JS_UNUSED_VAL = prv_json_parse(
|
|
"var msg = { "
|
|
"\"key\" : "
|
|
"\"Bacon ipsum dolor amet kevin filet mignon id ut, aute sausage tri-tip "
|
|
"frankfurter pork loin. Boudin ullamco landjaeger, kevin tongue minim tri-tip "
|
|
"ground round dolore. Ham hock tongue swine, cillum jowl pancetta fugiat "
|
|
"deserunt sirloin fatback tenderloin culpa andouille. Incididunt qui bacon "
|
|
"nostrud ham hock adipisicing et ham. Ullamco esse eu capicola, ea culpa irure "
|
|
"meatball proident laboris ut reprehenderit ex incididunt.\" };\n");
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// .postMessage() Tests
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#define SIMPLE_TEST_OBJECT "{ \"x\" : 1 }"
|
|
|
|
static void prv_assert_simple_test_object_pending(void) {
|
|
const char * const expected_json = "{\"x\":1}";
|
|
const size_t expected_json_size = strlen(expected_json) + 1;
|
|
const size_t expected_size = sizeof(PostMessageChunkPayload) + strlen(expected_json) + 1;
|
|
uint8_t *buffer = task_malloc(expected_size);
|
|
|
|
PostMessageChunkPayload *chunk = (PostMessageChunkPayload *)buffer;
|
|
*chunk = (PostMessageChunkPayload) {
|
|
.total_size_bytes = expected_json_size,
|
|
.is_first = true,
|
|
};
|
|
memcpy(chunk->chunk_data, expected_json, expected_json_size);
|
|
|
|
EXPECT_OUTBOX_MESSAGE_PENDING(TupletBytes(PostMessageKeyChunk,
|
|
(const uint8_t *) chunk, expected_size));
|
|
|
|
// Compare with hard-coded byte array, to catch accidental changes to the ABI:
|
|
const uint8_t raw_bytes_v1[] = {
|
|
0x08, 0x00, 0x00, 0x80, 0x7b, 0x22, 0x78, 0x22, 0x3a, 0x31, 0x7d, 0x00,
|
|
};
|
|
cl_assert_equal_i(sizeof(raw_bytes_v1), expected_size);
|
|
cl_assert_equal_m(raw_bytes_v1, buffer, expected_size);
|
|
|
|
task_free(buffer);
|
|
}
|
|
|
|
void test_rocky_api_app_message__postmessage_just_before_connected(void) {
|
|
prv_init_api(false /* start_connected */);
|
|
|
|
EXECUTE_SCRIPT("var x = " SIMPLE_TEST_OBJECT ";"
|
|
"var hasError = false;"
|
|
"_rocky.on('postmessageerror', function() { hasError = true; });"
|
|
"_rocky.postMessage(x);");
|
|
|
|
// First send attempt fails because not in SessionOpen
|
|
ASSERT_JS_GLOBAL_EQUALS_B("hasError", false);
|
|
|
|
prv_simulate_transport_connection_event(true /* is_connected */);
|
|
prv_postmessageconnected_postmessagedisconnected_negotiate_to_open_session();
|
|
|
|
prv_assert_simple_test_object_pending();
|
|
|
|
prv_rcv_app_message_ack(APP_MSG_OK);
|
|
|
|
EXPECT_OUTBOX_NO_MESSAGE_PENDING();
|
|
|
|
ASSERT_JS_GLOBAL_EQUALS_B("hasError", false);
|
|
}
|
|
|
|
void test_rocky_api_app_message__post_message_single_chunk(void) {
|
|
prv_init_and_goto_session_open();
|
|
|
|
EXECUTE_SCRIPT("var x = " SIMPLE_TEST_OBJECT "; _rocky.postMessage(x);");
|
|
prv_assert_simple_test_object_pending();
|
|
|
|
prv_rcv_app_message_ack(APP_MSG_OK);
|
|
|
|
EXPECT_OUTBOX_NO_MESSAGE_PENDING();
|
|
}
|
|
|
|
static void prv_init_and_goto_session_open_with_tiny_buffers(void) {
|
|
prv_init_and_goto_awaiting_reset_complete_remote_initiated();
|
|
RCV_APP_MESSAGE(TupletBytes(PostMessageKeyResetComplete, \
|
|
(const uint8_t *)&TINY_RESET_COMPLETE, sizeof(TINY_RESET_COMPLETE)));
|
|
cl_assert_equal_i(rocky_api_app_message_get_state(), PostMessageStateSessionOpen);
|
|
}
|
|
|
|
void test_rocky_api_app_message__post_message_multi_chunk(void) {
|
|
prv_init_and_goto_session_open_with_tiny_buffers();
|
|
|
|
EXECUTE_SCRIPT("var x = { \"x\" : 123 }; _rocky.postMessage(x);");
|
|
|
|
const char * const expected_json = "{\"x\":123}";
|
|
const size_t expected_json_size = strlen(expected_json) + 1;
|
|
size_t json_bytes_remaining = expected_json_size;
|
|
|
|
uint8_t *buffer = task_malloc(sizeof(PostMessageChunkPayload) + TINY_CHUNK_SIZE);
|
|
|
|
// Chunk 1:
|
|
{
|
|
const size_t json_bytes_size = MIN(TINY_CHUNK_SIZE, json_bytes_remaining);
|
|
const size_t expected_size = sizeof(PostMessageChunkPayload) + json_bytes_size;
|
|
|
|
PostMessageChunkPayload *chunk = (PostMessageChunkPayload *)buffer;
|
|
*chunk = (PostMessageChunkPayload) {
|
|
.total_size_bytes = expected_json_size,
|
|
.is_first = true,
|
|
};
|
|
memcpy(chunk->chunk_data, expected_json, TINY_CHUNK_SIZE);
|
|
|
|
EXPECT_OUTBOX_MESSAGE_PENDING(TupletBytes(PostMessageKeyChunk,
|
|
(const uint8_t *) chunk, expected_size));
|
|
|
|
// Compare with hard-coded byte array, to catch accidental changes to the ABI:
|
|
const uint8_t raw_bytes_v1[] = {
|
|
0x0a, 0x00, 0x00, 0x80, '{', '"', 'x', '"',
|
|
};
|
|
cl_assert_equal_i(sizeof(raw_bytes_v1), expected_size);
|
|
cl_assert_equal_m(raw_bytes_v1, buffer, expected_size);
|
|
|
|
prv_rcv_app_message_ack(APP_MSG_OK);
|
|
json_bytes_remaining -= json_bytes_size;
|
|
}
|
|
|
|
// Chunk 2:
|
|
{
|
|
const size_t json_bytes_size = MIN(TINY_CHUNK_SIZE, json_bytes_remaining);
|
|
const size_t expected_size = sizeof(PostMessageChunkPayload) + json_bytes_size;
|
|
const int payload_offset = expected_json_size - json_bytes_remaining;
|
|
|
|
PostMessageChunkPayload *chunk = (PostMessageChunkPayload *)buffer;
|
|
*chunk = (PostMessageChunkPayload) {
|
|
.offset_bytes = payload_offset,
|
|
.continuation_is_first = false,
|
|
};
|
|
memcpy(chunk->chunk_data, expected_json + payload_offset, TINY_CHUNK_SIZE);
|
|
|
|
EXPECT_OUTBOX_MESSAGE_PENDING(TupletBytes(PostMessageKeyChunk,
|
|
(const uint8_t *) chunk, expected_size));
|
|
|
|
// Compare with hard-coded byte array, to catch accidental changes to the ABI:
|
|
const uint8_t raw_bytes_v1[] = {
|
|
0x04, 0x00, 0x00, 0x00, ':', '1', '2', '3',
|
|
};
|
|
cl_assert_equal_i(sizeof(raw_bytes_v1), expected_size);
|
|
cl_assert_equal_m(raw_bytes_v1, buffer, expected_size);
|
|
|
|
prv_rcv_app_message_ack(APP_MSG_OK);
|
|
json_bytes_remaining -= json_bytes_size;
|
|
}
|
|
|
|
// Chunk 3:
|
|
{
|
|
const size_t json_bytes_size = MIN(TINY_CHUNK_SIZE, json_bytes_remaining);
|
|
const size_t expected_size = sizeof(PostMessageChunkPayload) + json_bytes_size;
|
|
const int payload_offset = expected_json_size - json_bytes_remaining;
|
|
|
|
PostMessageChunkPayload *chunk = (PostMessageChunkPayload *)buffer;
|
|
*chunk = (PostMessageChunkPayload) {
|
|
.offset_bytes = payload_offset,
|
|
.continuation_is_first = false,
|
|
};
|
|
memcpy(chunk->chunk_data, expected_json + payload_offset, TINY_CHUNK_SIZE);
|
|
|
|
EXPECT_OUTBOX_MESSAGE_PENDING(TupletBytes(PostMessageKeyChunk,
|
|
(const uint8_t *) chunk, expected_size));
|
|
|
|
// Compare with hard-coded byte array, to catch accidental changes to the ABI:
|
|
const uint8_t raw_bytes_v1[] = {
|
|
0x08, 0x00, 0x00, 0x00, '}', '\0',
|
|
};
|
|
cl_assert_equal_i(sizeof(raw_bytes_v1), expected_size);
|
|
cl_assert_equal_m(raw_bytes_v1, buffer, expected_size);
|
|
|
|
prv_rcv_app_message_ack(APP_MSG_OK);
|
|
json_bytes_remaining -= json_bytes_size;
|
|
}
|
|
|
|
EXPECT_OUTBOX_NO_MESSAGE_PENDING();
|
|
|
|
task_free(buffer);
|
|
}
|
|
|
|
void test_rocky_api_app_message__postmessage_not_jsonable(void) {
|
|
prv_init_and_goto_session_open();
|
|
|
|
const char *not_jsonable_error =
|
|
"TypeError: Argument at index 0 is not a JSON.stringify()-able object";
|
|
|
|
EXECUTE_SCRIPT_EXPECT_ERROR("_rocky.postMessage(undefined);", not_jsonable_error);
|
|
EXECUTE_SCRIPT_EXPECT_ERROR("_rocky.postMessage(function() {});", not_jsonable_error);
|
|
EXECUTE_SCRIPT_EXPECT_ERROR("_rocky.postMessage({toJSON: function() {throw 'toJSONError';}});",
|
|
"toJSONError");
|
|
}
|
|
|
|
void test_rocky_api_app_message__postmessage_no_args(void) {
|
|
prv_init_api(false /* start_connected */);
|
|
EXECUTE_SCRIPT_EXPECT_ERROR("_rocky.postMessage();", "TypeError: Not enough arguments");
|
|
}
|
|
|
|
void test_rocky_api_app_message__postmessage_oom(void) {
|
|
prv_init_api(false /* start_connected */);
|
|
|
|
fake_malloc_set_largest_free_block(0);
|
|
|
|
EXECUTE_SCRIPT_EXPECT_ERROR("_rocky.postMessage('x');",
|
|
"RangeError: Out of memory: can't postMessage() -- object too large");
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Receive Tests
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void test_rocky_api_app_message__receive_message_multi_chunk(void) {
|
|
prv_init_and_goto_session_open_with_tiny_buffers();
|
|
|
|
EXECUTE_SCRIPT("var event = null;\n"
|
|
"var json_str = null;\n"
|
|
"_rocky.on('message', function(e) {\n"
|
|
" json_str = JSON.stringify(e.data);\n" // stringify again to make assert simple
|
|
" event = e;\n"
|
|
"});");
|
|
JS_VAR event_null = prv_js_global_get_value("event");
|
|
cl_assert_equal_b(true, jerry_value_is_null(event_null));
|
|
|
|
// Get 3x the same message in a row:
|
|
for (int j = 0; j < 3; ++j) {
|
|
|
|
// Chunks for: {"x":123}
|
|
const struct {
|
|
uint8_t byte_array[8];
|
|
size_t length;
|
|
} chunk_msg_defs[] = {
|
|
{
|
|
.byte_array = {0x0a, 0x00, 0x00, 0x80, '{', '"', 'x', '"'},
|
|
.length = 8,
|
|
},
|
|
{
|
|
.byte_array = {0x04, 0x00, 0x00, 0x00, ':', '1', '2', '3'},
|
|
.length = 8,
|
|
},
|
|
{
|
|
.byte_array = {0x08, 0x00, 0x00, 0x00, '}', '\0'},
|
|
.length = 6,
|
|
}
|
|
};
|
|
|
|
for (int i = 0; i < ARRAY_LENGTH(chunk_msg_defs); ++i) {
|
|
RCV_APP_MESSAGE(TupletBytes(PostMessageKeyChunk,
|
|
(const uint8_t *) chunk_msg_defs[i].byte_array,
|
|
chunk_msg_defs[i].length));
|
|
}
|
|
|
|
JS_VAR event_valid = prv_js_global_get_value("event");
|
|
cl_assert_equal_b(true, jerry_value_is_object(event_valid));
|
|
|
|
// Make sure that there is a "data" property
|
|
JS_VAR data_prop = jerry_get_object_field(event_valid, "data");
|
|
cl_assert_equal_b(true, jerry_value_is_object(data_prop));
|
|
|
|
// Make sure the re-serialized object matches:
|
|
ASSERT_JS_GLOBAL_EQUALS_S("json_str", "{\"x\":123}");
|
|
|
|
EXECUTE_SCRIPT("json_str = null;\n"
|
|
"event = null");
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// "postmessageerror" event
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void test_rocky_api_app_message__postmessageerror(void) {
|
|
prv_init_and_goto_session_open();
|
|
|
|
EXECUTE_SCRIPT("var didError = false;"
|
|
"var x = { \"x\" : 1 };"
|
|
"var dataJSON = undefined;"
|
|
"_rocky.on('postmessageerror', "
|
|
" function(e) { didError = true; dataJSON = JSON.stringify(e.data); });"
|
|
"_rocky.postMessage(x);"
|
|
"x.x = 2;");
|
|
|
|
ASSERT_JS_GLOBAL_EQUALS_B("didError", false);
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
cl_assert_equal_b(s_is_outbox_message_pending, true);
|
|
|
|
// NACK
|
|
prv_rcv_app_message_ack(APP_MSG_BUSY);
|
|
|
|
AppTimer *t = rocky_api_app_message_get_app_msg_retry_timer();
|
|
cl_assert(t != EVENTED_TIMER_INVALID_ID);
|
|
cl_assert_equal_b(fake_app_timer_is_scheduled(t), true);
|
|
|
|
// Enqueuing more objects shouldn't affect the pace at which things are retried:
|
|
EXECUTE_SCRIPT("_rocky.postMessage('')");
|
|
|
|
EXPECT_OUTBOX_NO_MESSAGE_PENDING();
|
|
|
|
cl_assert_equal_b(app_timer_trigger(t), true);
|
|
}
|
|
|
|
ASSERT_JS_GLOBAL_EQUALS_B("didError", true);
|
|
ASSERT_JS_GLOBAL_EQUALS_S("dataJSON", "{\"x\":1}");
|
|
}
|
|
|
|
void test_rocky_api_app_message__postmessageerror_while_disconnected(void) {
|
|
prv_init_api(false /* start_connected */);
|
|
|
|
EXECUTE_SCRIPT("var didError = false;"
|
|
"var x = " SIMPLE_TEST_OBJECT ";"
|
|
"_rocky.on('postmessageerror', "
|
|
" function(e) { didError = true; dataJSON = JSON.stringify(e.data); });"
|
|
/* 3x postMessage(): */
|
|
"_rocky.postMessage(x);"
|
|
"_rocky.postMessage(x);"
|
|
"_rocky.postMessage(x);");
|
|
|
|
// Let the first 2 timeout:
|
|
for (int i = 0; i < 2; ++i) {
|
|
ASSERT_JS_GLOBAL_EQUALS_B("didError", false);
|
|
|
|
AppTimer *t = rocky_api_app_message_get_session_closed_object_queue_timer();
|
|
cl_assert(t != EVENTED_TIMER_INVALID_ID);
|
|
cl_assert_equal_b(fake_app_timer_is_scheduled(t), true);
|
|
|
|
EXPECT_OUTBOX_NO_MESSAGE_PENDING();
|
|
|
|
cl_assert_equal_b(app_timer_trigger(t), true);
|
|
|
|
ASSERT_JS_GLOBAL_EQUALS_B("didError", true);
|
|
|
|
EXECUTE_SCRIPT("didError = false;");
|
|
}
|
|
|
|
// Timer for the 3rd should be set:
|
|
AppTimer *t = rocky_api_app_message_get_session_closed_object_queue_timer();
|
|
cl_assert(t != EVENTED_TIMER_INVALID_ID);
|
|
cl_assert_equal_b(fake_app_timer_is_scheduled(t), true);
|
|
|
|
// Connect:
|
|
prv_simulate_transport_connection_event(true /* is_connected */);
|
|
prv_postmessageconnected_postmessagedisconnected_negotiate_to_open_session();
|
|
|
|
// Timer for the 3rd should be cancelled now:
|
|
cl_assert(EVENTED_TIMER_INVALID_ID == rocky_api_app_message_get_session_closed_object_queue_timer());
|
|
|
|
prv_assert_simple_test_object_pending();
|
|
}
|