diff --git a/system/inc/system_event.h b/system/inc/system_event.h index ec1a58ac91..8cb328ba65 100644 --- a/system/inc/system_event.h +++ b/system/inc/system_event.h @@ -91,6 +91,8 @@ enum SystemEventsParam { // Cloud connection status cloud_status_disconnected = 0, cloud_status_connecting = 1, + cloud_status_handshake = 2, + cloud_status_session_resume = 3, cloud_status_connected = 8, cloud_status_disconnecting = 9, diff --git a/system/src/system_cloud_connection.h b/system/src/system_cloud_connection.h index 92d56d404b..ccb3599d4b 100644 --- a/system/src/system_cloud_connection.h +++ b/system/src/system_cloud_connection.h @@ -42,6 +42,7 @@ int system_internet_test(void* reserved); int system_multicast_announce_presence(void* reserved); int system_cloud_set_inet_family_keepalive(int af, unsigned int value, int flags); int system_cloud_get_inet_family_keepalive(int af, unsigned int* value); +sock_handle_t system_cloud_get_socket_handle(); #ifdef __cplusplus } diff --git a/system/src/system_cloud_connection_compat.cpp b/system/src/system_cloud_connection_compat.cpp index 07640c8449..1c2818c0d9 100644 --- a/system/src/system_cloud_connection_compat.cpp +++ b/system/src/system_cloud_connection_compat.cpp @@ -323,4 +323,10 @@ void HAL_NET_notify_socket_closed(sock_handle_t socket) } } +sock_handle_t system_cloud_get_socket_handle() +{ + return s_state.socket; +} + + #endif /* !HAL_USE_SOCKET_HAL_POSIX && HAL_USE_SOCKET_HAL_COMPAT */ diff --git a/system/src/system_cloud_connection_posix.cpp b/system/src/system_cloud_connection_posix.cpp index f8bc99cc8e..9574bb320d 100644 --- a/system/src/system_cloud_connection_posix.cpp +++ b/system/src/system_cloud_connection_posix.cpp @@ -348,4 +348,9 @@ int system_cloud_is_connected(void* reserved) return s_state.socket >= 0 ? 0 : -1; } +sock_handle_t system_cloud_get_socket_handle() +{ + return s_state.socket; +} + #endif /* HAL_USE_SOCKET_HAL_POSIX */ diff --git a/system/src/system_cloud_internal.cpp b/system/src/system_cloud_internal.cpp index d16f1e8bc0..7c0466ca07 100644 --- a/system/src/system_cloud_internal.cpp +++ b/system/src/system_cloud_internal.cpp @@ -1152,11 +1152,19 @@ int Spark_Handshake(bool presence_announce) #endif // HAL_PLATFORM_MUXER_MAY_NEED_DELAY_IN_TX if (err == protocol::SESSION_RESUMED) { + // XXX: ideally this event should be generated before we perform the handshake + // but the current semantic of indicating after the handshake/session-resumption are done + // also deserves a chance. + system_notify_event(cloud_status, cloud_status_session_resume); session_resumed = true; } else if (err != 0) { return spark_protocol_to_system_error(err); } if (!session_resumed) { + // XXX: ideally this event should be generated before we perform the handshake + // but the current semantic of indicating after the handshake/session-resumption are done + // also deserves a chance. + system_notify_event(cloud_status, cloud_status_handshake); char buf[CLAIM_CODE_SIZE + 1]; if (!HAL_Get_Claim_Code(buf, sizeof(buf)) && buf[0] != 0 && (uint8_t)buf[0] != 0xff) { LOG(INFO,"Send spark/device/claim/code event for code %s", buf); diff --git a/system/src/system_task.cpp b/system/src/system_task.cpp index b27f82fd56..b6af5f181a 100644 --- a/system/src/system_task.cpp +++ b/system/src/system_task.cpp @@ -729,6 +729,9 @@ void* system_internal(int item, void* reserved) return mutex_usb_serial(); } #endif + case 3: { + return reinterpret_cast(system_cloud_get_socket_handle()); + } default: return nullptr; } diff --git a/user/tests/wiring/no_fixture/cloud.cpp b/user/tests/wiring/no_fixture/cloud.cpp index 9f3555e90d..eb71ecb7ad 100644 --- a/user/tests/wiring/no_fixture/cloud.cpp +++ b/user/tests/wiring/no_fixture/cloud.cpp @@ -1,5 +1,22 @@ #include "application.h" #include "unit-test/unit-test.h" +#include "scope_guard.h" + +namespace { + +struct HandshakeState { + volatile int handshakeType = -1; + bool operator()() { + return handshakeType != -1; + } + void reset() { + handshakeType = -1; + } +}; + +HandshakeState handshakeState; + +} test(CLOUD_01_Particle_Connect_Does_Not_Block_In_SemiAutomatic_Mode) { Particle.disconnect(); @@ -18,3 +35,94 @@ test(CLOUD_01_Particle_Connect_Does_Not_Block_In_SemiAutomatic_Mode) { test(CLOUD_03_Restore_System_Mode) { set_system_mode(AUTOMATIC); } + +#if HAL_PLATFORM_CLOUD_UDP +test(CLOUD_04_socket_errors_do_not_cause_a_full_handshake) { + const int GET_CLOUD_SOCKET_HANDLE_INTERNAL_ID = 3; + + Particle.connect(); + assertTrue(waitFor(Particle.connected, 120000)); + + sock_handle_t cloudSock = (sock_handle_t)system_internal(GET_CLOUD_SOCKET_HANDLE_INTERNAL_ID, nullptr); + assertTrue(socket_handle_valid(cloudSock)); + + auto evHandler = [](system_event_t event, int param, void* ctx) { + if (event == cloud_status) { + if (param == cloud_status_handshake || param == cloud_status_session_resume) { + if (handshakeState.handshakeType == -1) { + handshakeState.handshakeType = param; + } + } + } + }; + + handshakeState.reset(); + System.on(cloud_status, evHandler); + SCOPE_GUARD({ + System.off(cloud_status, evHandler); + }); + // Pull the rug, this should cause a socket error on recv/send +#if HAL_USE_SOCKET_HAL_POSIX + assertEqual(0, sock_close(cloudSock)); +#else + assertEqual(0, socket_close(cloudSock)); +#endif // HAL_USE_SOCKET_HAL_POSIX + // Force a publish just in case + (void)Particle.publish("test", "test"); + assertTrue(waitFor(handshakeState, 120000)); + assertEqual((int)handshakeState.handshakeType, (int)cloud_status_session_resume); + assertTrue(waitFor(Particle.connected, 60000)); +} + +#if HAL_PLATFORM_CELLULAR +test(CLOUD_05_loss_of_cellular_network_connectivity_does_not_cause_full_handshake) { + Particle.connect(); + assertTrue(waitFor(Particle.connected, 120000)); + + auto evHandler = [](system_event_t event, int param, void* ctx) { + if (event == cloud_status) { + if (param == cloud_status_handshake || param == cloud_status_session_resume) { + if (handshakeState.handshakeType == -1) { + handshakeState.handshakeType = param; + } + } + } + }; + + handshakeState.reset(); + System.on(cloud_status, evHandler); + SCOPE_GUARD({ + System.off(cloud_status, evHandler); + }); + // Pull the rug, this should cause a socket error on recv/send +#if HAL_PLATFORM_NCP_AT + assertEqual((int)RESP_OK, Cellular.command("AT+CFUN=0,0\r\n")); + // Force a publish just in case + (void)Particle.publish("test", "test"); + assertEqual((int)RESP_OK, Cellular.command("AT+CFUN=1,0\r\n")); +#else + CellularDevice devInfo = {}; + devInfo.size = sizeof(devInfo); + assertEqual(0, cellular_device_info(&devInfo, nullptr)); + // Electrons don't take too well to being switched to minimum functionality mode + // and perform a reset. Deactivating internal context or disconnecting (COPS=2) seems + // to work better. + if (devInfo.dev == DEV_SARA_R410) { + assertEqual((int)RESP_OK, Cellular.command("AT+COPS=2,0\r\n")); + // Force a publish just in case + (void)Particle.publish("test", "test"); + assertEqual((int)RESP_OK, Cellular.command("AT+COPS=0,0\r\n")); + } else { + assertEqual((int)RESP_OK, Cellular.command("AT+UPSDA=0,4\r\n")); + // Force a publish just in case + (void)Particle.publish("test", "test"); + assertEqual((int)RESP_OK, Cellular.command("AT+UPSDA=0,3\r\n")); + } +#endif // HAL_PLATFORM_NCP_AT + assertTrue(waitFor(handshakeState, 120000)); + assertEqual((int)handshakeState.handshakeType, (int)cloud_status_session_resume); + assertTrue(waitFor(Particle.connected, 60000)); +} + +#endif // HAL_PLATFORM_CELLULAR +#endif // HAL_PLATFORM_CLOUD_UDP diff --git a/user/tests/wiring/no_fixture/system.cpp b/user/tests/wiring/no_fixture/system.cpp index c27d35fa05..c9f13f9141 100644 --- a/user/tests/wiring/no_fixture/system.cpp +++ b/user/tests/wiring/no_fixture/system.cpp @@ -131,143 +131,3 @@ test(SYSTEM_05_button_mirror_disable) #endif // defined(BUTTON1_MIRROR_SUPPORTED) #endif // !HAL_PLATFORM_NRF52840 - -// platform supports out of memory notifiation - -bool oomEventReceived = false; -size_t oomSizeReceived = 0; -void handle_oom(system_event_t event, int param, void*) { - // Serial is not thread-safe - // Serial.printlnf("got event %d %d", event, param); - if (out_of_memory==event) { - oomEventReceived = true; - oomSizeReceived = param; - } -}; - -void register_oom() { - oomEventReceived = false; - oomSizeReceived = 0; - System.on(out_of_memory, handle_oom); -} - -void unregister_oom() { - System.off(out_of_memory, handle_oom); -} - -test(SYSTEM_06_out_of_memory) -{ - // Disconnect from the cloud and network just in case - Particle.disconnect(); - Network.disconnect(); - - const size_t size = 1024*1024*1024; - register_oom(); - auto ptr = malloc(size); - (void)ptr; - Particle.process(); - unregister_oom(); - - assertTrue(oomEventReceived); - assertEqual(oomSizeReceived, size); -} - -test(SYSTEM_07_fragmented_heap) { - struct Block { - Block() { - // Write garbage data to more easily corrupt the RAM - // in case of issues like static RAM / heap overlap or - // just simple heap corruption - Random rng; - rng.gen(data, sizeof(data)); - next = nullptr; - } - char data[508]; - Block* next; - }; - register_oom(); - - Block* next = nullptr; - - // exhaust memory - for (;;) { - Block* b = new Block(); - if (!b) { - break; - } else { - b->next = next; - next = b; - } - } - - assertTrue(oomEventReceived); - assertEqual(oomSizeReceived, sizeof(Block)); - - runtime_info_t info; - info.size = sizeof(info); - HAL_Core_Runtime_Info(&info, nullptr); - - // we can't really say about the free heap but the block size should be less - assertLessOrEqual(info.largest_free_block_heap, sizeof(Block)); - size_t low_heap = info.freeheap; - - // free every 2nd block - Block* head = next; - int count = 0; - for (;head;) { - Block* free = head->next; - if (free) { - // skip the next block - head->next = free->next; - delete free; - count++; - head = head->next; - } else { - head = nullptr; - } - } - - HAL_Core_Runtime_Info(&info, nullptr); - const size_t half_fragment_block_size = info.largest_free_block_heap; - const size_t half_fragment_free = info.freeheap; - - unregister_oom(); - register_oom(); - const size_t BLOCKS_TO_MALLOC = 3; - Block* b = new Block[BLOCKS_TO_MALLOC]; // no room for 3 blocks, memory is clearly fragmented - delete[] b; - - // free the remaining blocks - for (;next;) { - Block* b = next; - next = b->next; - delete b; - } - - assertMoreOrEqual(half_fragment_block_size, sizeof(Block)); // there should definitely be one block available - assertLessOrEqual(half_fragment_block_size, BLOCKS_TO_MALLOC*sizeof(Block)-1); // we expect malloc of 3 blocks to fail, so this better allow up to that size less 1 - assertMoreOrEqual(half_fragment_free, low_heap+(sizeof(Block)*count)); - - assertTrue(oomEventReceived); - assertMoreOrEqual(oomSizeReceived, sizeof(Block)*BLOCKS_TO_MALLOC); -} - -test(SYSTEM_08_out_of_memory_not_raised_for_0_size_malloc) -{ - const size_t size = 0; - register_oom(); - auto ptr = malloc(size); - (void)ptr; - Particle.process(); - unregister_oom(); - - assertFalse(oomEventReceived); -} - -test(SYSTEM_09_out_of_memory_restore_state) -{ - // Restore connection to the cloud and network - Network.connect(); - Particle.connect(); - waitFor(Particle.connected, 6*60*1000); -} diff --git a/user/tests/wiring/no_fixture_long_running/network.cpp b/user/tests/wiring/no_fixture_long_running/network.cpp index 4ea2ed6485..2348a94199 100644 --- a/user/tests/wiring/no_fixture_long_running/network.cpp +++ b/user/tests/wiring/no_fixture_long_running/network.cpp @@ -24,6 +24,11 @@ namespace { +struct NetworkState { + volatile bool disconnected = false; +}; +NetworkState networkState; + template T divRoundClosest(T n, DT d) { return ((n + (d / 2)) / d); @@ -76,15 +81,9 @@ test(NETWORK_01_LargePacketsDontCauseIssues_ResolveMtu) { waitFor(Network.ready, WAIT_TIMEOUT); assertTrue(Network.ready()); - struct State { - volatile bool disconnected; - }; - State state = {}; - auto evHandler = [](system_event_t event, int param, void* ctx) { - State* state = static_cast(ctx); if (event == network_status && param == network_status_disconnected) { - state->disconnected = true; + networkState.disconnected = true; } }; @@ -164,7 +163,7 @@ test(NETWORK_01_LargePacketsDontCauseIssues_ResolveMtu) { if (millis() - start < MINIMUM_TEST_TIME) { delay(millis() - start); } - assertFalse((bool)state.disconnected); + assertFalse((bool)networkState.disconnected); #if PLATFORM_ID != PLATFORM_BORON && PLATFORM_ID != PLATFORM_BSOM assertMoreOrEqual((mtu - IPV4_PLUS_UDP_HEADER_LENGTH), MBEDTLS_SSL_MAX_CONTENT_LEN); #else diff --git a/user/tests/wiring/no_fixture_stress/memory.cpp b/user/tests/wiring/no_fixture_stress/memory.cpp new file mode 100644 index 0000000000..54b93bec25 --- /dev/null +++ b/user/tests/wiring/no_fixture_stress/memory.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2021 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "application.h" +#include "unit-test/unit-test.h" +#include "random.h" + +// platform supports out of memory notifiation + +bool oomEventReceived = false; +size_t oomSizeReceived = 0; +void handle_oom(system_event_t event, int param, void*) { + // Serial is not thread-safe + // Serial.printlnf("got event %d %d", event, param); + if (out_of_memory==event) { + oomEventReceived = true; + oomSizeReceived = param; + } +}; + +void register_oom() { + oomEventReceived = false; + oomSizeReceived = 0; + System.on(out_of_memory, handle_oom); +} + +void unregister_oom() { + System.off(out_of_memory, handle_oom); +} + +test(SYSTEM_06_out_of_memory) +{ + // Disconnect from the cloud and network just in case + Particle.disconnect(); + Network.disconnect(); + + const size_t size = 1024*1024*1024; + register_oom(); + auto ptr = malloc(size); + (void)ptr; + Particle.process(); + unregister_oom(); + + assertTrue(oomEventReceived); + assertEqual(oomSizeReceived, size); +} + +test(SYSTEM_07_fragmented_heap) { + struct Block { + Block() { + // Write garbage data to more easily corrupt the RAM + // in case of issues like static RAM / heap overlap or + // just simple heap corruption + Random rng; + rng.gen(data, sizeof(data)); + next = nullptr; + } + char data[508]; + Block* next; + }; + register_oom(); + + Block* next = nullptr; + + // exhaust memory + for (;;) { + Block* b = new Block(); + if (!b) { + break; + } else { + b->next = next; + next = b; + } + } + + assertTrue(oomEventReceived); + assertEqual(oomSizeReceived, sizeof(Block)); + + runtime_info_t info; + info.size = sizeof(info); + HAL_Core_Runtime_Info(&info, nullptr); + + // we can't really say about the free heap but the block size should be less + assertLessOrEqual(info.largest_free_block_heap, sizeof(Block)); + size_t low_heap = info.freeheap; + + // free every 2nd block + Block* head = next; + int count = 0; + for (;head;) { + Block* free = head->next; + if (free) { + // skip the next block + head->next = free->next; + delete free; + count++; + head = head->next; + } else { + head = nullptr; + } + } + + HAL_Core_Runtime_Info(&info, nullptr); + const size_t half_fragment_block_size = info.largest_free_block_heap; + const size_t half_fragment_free = info.freeheap; + + unregister_oom(); + register_oom(); + const size_t BLOCKS_TO_MALLOC = 3; + Block* b = new Block[BLOCKS_TO_MALLOC]; // no room for 3 blocks, memory is clearly fragmented + delete[] b; + + // free the remaining blocks + for (;next;) { + Block* b = next; + next = b->next; + delete b; + } + + assertMoreOrEqual(half_fragment_block_size, sizeof(Block)); // there should definitely be one block available + assertLessOrEqual(half_fragment_block_size, BLOCKS_TO_MALLOC*sizeof(Block)-1); // we expect malloc of 3 blocks to fail, so this better allow up to that size less 1 + assertMoreOrEqual(half_fragment_free, low_heap+(sizeof(Block)*count)); + + assertTrue(oomEventReceived); + assertMoreOrEqual(oomSizeReceived, sizeof(Block)*BLOCKS_TO_MALLOC); +} + +test(SYSTEM_08_out_of_memory_not_raised_for_0_size_malloc) +{ + const size_t size = 0; + register_oom(); + auto ptr = malloc(size); + (void)ptr; + Particle.process(); + unregister_oom(); + + assertFalse(oomEventReceived); +} + +test(SYSTEM_09_out_of_memory_restore_state) +{ + // Restore connection to the cloud and network + Network.connect(); + Particle.connect(); + waitFor(Particle.connected, 6*60*1000); +} diff --git a/user/tests/wiring/no_fixture_stress/no_fixture_stress.spec.js b/user/tests/wiring/no_fixture_stress/no_fixture_stress.spec.js index c764bc89c8..805a24358c 100644 --- a/user/tests/wiring/no_fixture_stress/no_fixture_stress.spec.js +++ b/user/tests/wiring/no_fixture_stress/no_fixture_stress.spec.js @@ -1,3 +1,3 @@ suite('No fixture stress'); -platform('gen3'); +platform('gen3', 'gen2');