/*************************************************************************** * Copyright (C) 2026 Eclipse ThreadX contributors * * This program and the accompanying materials are made available under the * terms of the MIT License which is available at * https://opensource.org/licenses/MIT. * * AI Disclosure: This file was largely AI-generated by Copilot (Sonnet 4.6). * The AI-generated portions may be considered public domain (CC0-1.0) * and not subject to the project's licence. The human contributor has * reviewed and verified that the code is correct. * * SPDX-License-Identifier: MIT and CC0-1.0 **************************************************************************/ /* FTP server activity timeout regression test. * * Verifies that when the FTP server inactivity timer fires it: * 1. Calls the logout callback. * 2. Resets the authenticated flag so that the next connection on the same * socket slot cannot issue commands without a fresh USER/PASS exchange. * 3. Allows a new connection to log in and operate normally afterwards. */ #include "tx_api.h" #include "fx_api.h" #include "nx_api.h" #include "nxd_ftp_client.h" #include "nxd_ftp_server.h" extern void test_control_return(UINT); #if !defined(NX_DISABLE_IPV4) #define DEMO_STACK_SIZE 4096 #define FTP_SERVER_ADDRESS IP_ADDRESS(1,2,3,4) #define FTP_CLIENT_ADDRESS IP_ADDRESS(1,2,3,5) /* ThreadX / NetX / FileX control blocks. */ static TX_THREAD server_thread; static TX_THREAD client_thread; static NX_PACKET_POOL server_pool; static NX_IP server_ip; static NX_PACKET_POOL client_pool; static NX_IP client_ip; static FX_MEDIA ram_disk; static NX_FTP_CLIENT ftp_client; static NX_FTP_SERVER ftp_server; /* Raw TCP socket used to probe auth-state after timeout. */ static NX_TCP_SOCKET raw_socket; /* Test state. */ static ULONG error_counter = 0; static ULONG logout_counter = 0; static UINT test_done = NX_FALSE; /* RAM disk memory. */ static UCHAR ram_disk_memory[32000]; static UCHAR ram_disk_sector_cache[512]; extern UINT _fx_media_format(FX_MEDIA *media_ptr, VOID (*driver)(FX_MEDIA *media), VOID *driver_info_ptr, UCHAR *memory_ptr, UINT memory_size, CHAR *volume_name, UINT number_of_fats, UINT directory_entries, UINT hidden_sectors, ULONG total_sectors, UINT bytes_per_sector, UINT sectors_per_cluster, UINT heads, UINT sectors_per_track); extern void _fx_ram_driver(FX_MEDIA *media_ptr); extern void _nx_ram_network_driver_512(NX_IP_DRIVER *driver_req_ptr); static void client_thread_entry(ULONG thread_input); static void thread_server_entry(ULONG thread_input); static UINT server_login(struct NX_FTP_SERVER_STRUCT *ftp_server_ptr, ULONG client_ip_address, UINT client_port, CHAR *name, CHAR *password, CHAR *extra_info); static UINT server_logout(struct NX_FTP_SERVER_STRUCT *ftp_server_ptr, ULONG client_ip_address, UINT client_port, CHAR *name, CHAR *password, CHAR *extra_info); #ifdef CTEST VOID test_application_define(void *first_unused_memory) #else void netx_ftp_server_activity_timeout_test_application_define(void *first_unused_memory) #endif { UINT status; UCHAR *pointer = (UCHAR *)first_unused_memory; tx_thread_create(&server_thread, "FTP Server thread", thread_server_entry, 0, pointer, DEMO_STACK_SIZE, 4, 4, TX_NO_TIME_SLICE, TX_AUTO_START); pointer += DEMO_STACK_SIZE; nx_system_initialize(); status = nx_packet_pool_create(&server_pool, "NetX Server Packet Pool", 512, pointer, 8192); pointer += 8192; if (status) error_counter++; status = nx_ip_create(&server_ip, "NetX Server IP Instance", FTP_SERVER_ADDRESS, 0xFFFFFF00UL, &server_pool, _nx_ram_network_driver_512, pointer, 2048, 1); pointer += 2048; if (status) error_counter++; status = nx_arp_enable(&server_ip, (void *)pointer, 1024); pointer += 1024; if (status) error_counter++; status = nx_tcp_enable(&server_ip); if (status) error_counter++; status = nx_ftp_server_create(&ftp_server, "FTP Server Instance", &server_ip, &ram_disk, pointer, DEMO_STACK_SIZE, &server_pool, server_login, server_logout); pointer += DEMO_STACK_SIZE; if (status) error_counter++; status = tx_thread_create(&client_thread, "FTP Client thread", client_thread_entry, 0, pointer, DEMO_STACK_SIZE, 6, 6, TX_NO_TIME_SLICE, TX_AUTO_START); pointer += DEMO_STACK_SIZE; if (status) error_counter++; status = nx_packet_pool_create(&client_pool, "NetX Client Packet Pool", 256, pointer, 8192); pointer += 8192; if (status) error_counter++; status = nx_ip_create(&client_ip, "NetX Client IP Instance", FTP_CLIENT_ADDRESS, 0xFFFFFF00UL, &client_pool, _nx_ram_network_driver_512, pointer, 2048, 1); pointer += 2048; if (status) error_counter++; status = nx_arp_enable(&client_ip, (void *)pointer, 1024); pointer += 1024; if (status) error_counter++; status = nx_tcp_enable(&client_ip); if (status) error_counter++; } void client_thread_entry(ULONG thread_input) { UINT status; UINT i; NX_PACKET *packet_ptr; /* Format and open the RAM disk. */ status = _fx_media_format(&ram_disk, _fx_ram_driver, ram_disk_memory, ram_disk_sector_cache, sizeof(ram_disk_sector_cache), "MY_RAM_DISK", 1, 32, 0, 256, 128, 1, 1, 1); if (status) error_counter++; status = fx_media_open(&ram_disk, "RAM DISK", _fx_ram_driver, ram_disk_memory, ram_disk_sector_cache, sizeof(ram_disk_sector_cache)); if (status) error_counter++; /* --- Phase 1: connect and log in via the FTP client API. --- */ status = nx_ftp_client_create(&ftp_client, "FTP Client", &client_ip, 2000, &client_pool); if (status) error_counter++; status = nx_ftp_client_connect(&ftp_client, FTP_SERVER_ADDRESS, "name", "password", NX_IP_PERIODIC_RATE); if (status) error_counter++; /* --- Phase 2: force the inactivity timeout on the server. --- * * Write 1 directly into the server's activity-timeout counter for this * client slot so the next timer tick decrements it to zero. This is a * deliberate white-box test hook – see the precedent in * netx_ftp_access_control_commands_03_test.c. */ ftp_server.nx_ftp_server_client_list[0].nx_ftp_client_request_activity_timeout = 1; /* Poll until the server's timeout processing runs. Bound the loop to * avoid an infinite hang; each iteration advances simulation time by one * second. Two full NX_FTP_TIMEOUT_PERIOD intervals is a safe upper bound. */ for (i = 0; i < (UINT)(NX_FTP_TIMEOUT_PERIOD * 2 + 10); i++) { if (ftp_server.nx_ftp_server_activity_timeouts > 0) break; tx_thread_sleep(NX_IP_PERIODIC_RATE); } if (ftp_server.nx_ftp_server_activity_timeouts == 0) error_counter++; /* Timeout never fired. */ /* Verify the logout callback was invoked. */ if (logout_counter != 1) error_counter++; /* Clean up the FTP client – the server already closed its end. */ nx_ftp_client_disconnect(&ftp_client, NX_IP_PERIODIC_RATE); nx_ftp_client_delete(&ftp_client); /* Give the server a moment to relisten. */ tx_thread_sleep(NX_IP_PERIODIC_RATE); /* --- Phase 3: raw TCP connection to verify stale-auth is cleared. * * Connect without sending USER/PASS and send a NOOP command. If the * authenticated flag was left set (the pre-fix bug), the server would * accept the command and return "200". With the fix it must return "530". */ status = nx_tcp_socket_create(&client_ip, &raw_socket, "Raw Client Socket", NX_IP_NORMAL, NX_FRAGMENT_OKAY, NX_IP_TIME_TO_LIVE, 512, NX_NULL, NX_NULL); if (status) error_counter++; status = nx_tcp_client_socket_bind(&raw_socket, NX_ANY_PORT, NX_IP_PERIODIC_RATE); if (status) error_counter++; status = nx_tcp_client_socket_connect(&raw_socket, FTP_SERVER_ADDRESS, NX_FTP_SERVER_CONTROL_PORT, 5 * NX_IP_PERIODIC_RATE); if (status) error_counter++; /* Read and discard the "220 Connection Ready" banner. */ status = nx_tcp_socket_receive(&raw_socket, &packet_ptr, 5 * NX_IP_PERIODIC_RATE); if (status) { error_counter++; } else { nx_packet_release(packet_ptr); } /* Send "NOOP\r\n" without USER/PASS. */ status = nx_packet_allocate(&client_pool, &packet_ptr, NX_TCP_PACKET, NX_IP_PERIODIC_RATE); if (status) error_counter++; if (!status) { (VOID)memcpy(packet_ptr -> nx_packet_prepend_ptr, "NOOP\r\n", 6); /* MISRA 21.17: bounded */ packet_ptr -> nx_packet_length = 6; packet_ptr -> nx_packet_append_ptr = packet_ptr -> nx_packet_prepend_ptr + 6; status = nx_tcp_socket_send(&raw_socket, packet_ptr, NX_IP_PERIODIC_RATE); if (status) error_counter++; } /* Read the server response and verify it is "530" (not logged in). */ status = nx_tcp_socket_receive(&raw_socket, &packet_ptr, 5 * NX_IP_PERIODIC_RATE); if (status) { error_counter++; } else { /* Response must start with "530". */ if (memcmp(packet_ptr -> nx_packet_prepend_ptr, "530", 3) != 0) error_counter++; nx_packet_release(packet_ptr); } nx_tcp_socket_disconnect(&raw_socket, NX_IP_PERIODIC_RATE); nx_tcp_client_socket_unbind(&raw_socket); nx_tcp_socket_delete(&raw_socket); /* --- Phase 4: fresh login must succeed and commands must work. --- */ status = nx_ftp_client_create(&ftp_client, "FTP Client 2", &client_ip, 2000, &client_pool); if (status) error_counter++; status = nx_ftp_client_connect(&ftp_client, FTP_SERVER_ADDRESS, "name", "password", 5 * NX_IP_PERIODIC_RATE); if (status) error_counter++; /* nx_ftp_client_connect success means USER/PASS was accepted, verifying * that the server correctly resets authentication on timeout. */ status = nx_ftp_client_disconnect(&ftp_client, NX_IP_PERIODIC_RATE); if (status) error_counter++; status = nx_ftp_client_delete(&ftp_client); if (status) error_counter++; test_done = NX_TRUE; } void thread_server_entry(ULONG thread_input) { UINT status; printf("NetX Test: FTP Server Activity Timeout Test.........................."); if (error_counter) { printf("ERROR!\n"); test_control_return(1); } status = nx_ftp_server_start(&ftp_server); if (status) error_counter++; while (test_done == NX_FALSE) tx_thread_sleep(NX_IP_PERIODIC_RATE); status = nx_ftp_server_delete(&ftp_server); if (status) error_counter++; if (error_counter != 0) { printf("ERROR!\n"); test_control_return(1); } else { printf("SUCCESS!\n"); test_control_return(0); } } static UINT server_login(struct NX_FTP_SERVER_STRUCT *ftp_server_ptr, ULONG client_ip_address, UINT client_port, CHAR *name, CHAR *password, CHAR *extra_info) { NX_PARAMETER_NOT_USED(ftp_server_ptr); NX_PARAMETER_NOT_USED(client_ip_address); NX_PARAMETER_NOT_USED(client_port); NX_PARAMETER_NOT_USED(name); NX_PARAMETER_NOT_USED(password); NX_PARAMETER_NOT_USED(extra_info); return(NX_SUCCESS); } static UINT server_logout(struct NX_FTP_SERVER_STRUCT *ftp_server_ptr, ULONG client_ip_address, UINT client_port, CHAR *name, CHAR *password, CHAR *extra_info) { NX_PARAMETER_NOT_USED(ftp_server_ptr); NX_PARAMETER_NOT_USED(client_ip_address); NX_PARAMETER_NOT_USED(client_port); NX_PARAMETER_NOT_USED(name); NX_PARAMETER_NOT_USED(password); NX_PARAMETER_NOT_USED(extra_info); logout_counter++; return(NX_SUCCESS); } #else #ifdef CTEST VOID test_application_define(void *first_unused_memory) #else void netx_ftp_server_activity_timeout_test_application_define(void *first_unused_memory) #endif { printf("NetX Test: FTP Server Activity Timeout Test..........................N/A\n"); test_control_return(3); } #endif /* NX_DISABLE_IPV4 */