diff --git a/Makefile b/Makefile index ede84579..4307b1c5 100644 --- a/Makefile +++ b/Makefile @@ -439,7 +439,15 @@ UNIT_TEST_SRCS:=src/test/unit/unit.c \ src/test/unit/unit_tests_tcp_flow.c \ src/test/unit/unit_tests_proto.c \ src/test/unit/unit_tests_multicast.c \ - src/test/unit/unit_tests_tftp.c + src/test/unit/unit_tests_tftp.c \ + src/test/unit/unit_tests_branches.c \ + src/test/unit/unit_tests_socket_api_arms.c \ + src/test/unit/unit_tests_tcp_state.c \ + src/test/unit/unit_tests_poll_dispatcher.c \ + src/test/unit/unit_tests_dhcp_edges.c \ + src/test/unit/unit_tests_ip_arp_recv.c \ + src/test/unit/unit_tests_dns_edges.c \ + src/test/unit/unit_tests_misc_edges.c unit: build/test/unit @@ -574,6 +582,18 @@ autocov-multicast: unit-multicast $(COV_MCAST_UNIT) --merge-mode-functions=merge-use-line-min \ --html-details -o build/coverage/multicast.html +cov-multicast: unit-multicast $(COV_MCAST_UNIT) + @echo "[RUN] unit multicast (coverage)" + @rm -f $(COV_DIR)/*.gcda + @$(COV_MCAST_UNIT) + @echo "[COV] gcovr multicast html" + @mkdir -p build/coverage + @gcovr -r . --exclude "src/test/unit/.*" \ + --gcov-ignore-errors=no_working_dir_found \ + --merge-mode-functions=merge-use-line-min \ + --html-details -o build/coverage/multicast.html + @$(OPEN_CMD) build/coverage/multicast.html + # Install dynamic library to re-link linux applications # install: @@ -672,7 +692,7 @@ build/test/test-wolfguard-interop: src/test/test_wolfguard_interop.c src/port/po clean-test-wolfguard-interop: @rm -f build/test/test-wolfguard-interop build/test/test_wolfguard_interop.o build/test/linux_tun.o -.PHONY: clean all static cppcheck cov autocov autocov-multicast unit-multicast unit-asan unit-ubsan unit-leaksan clean-unit \ +.PHONY: clean all static cppcheck cov autocov autocov-multicast cov-multicast unit-multicast unit-asan unit-ubsan unit-leaksan clean-unit \ unit-esp-asan unit-esp-ubsan unit-esp-leaksan clean-unit-esp \ unit-wolfguard unit-wolfguard-asan unit-wolfguard-ubsan clean-unit-wolfguard \ test-wolfguard-loopback test-wolfguard-loopback-asan test-wolfguard-loopback-ubsan \ diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index da0b410d..69743bc8 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -1,3 +1,24 @@ +/* unit.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + #include "unit_shared.c" #include "unit_tests_fifo.c" #include "unit_tests_api.c" @@ -7,6 +28,14 @@ #include "unit_tests_proto.c" #include "unit_tests_multicast.c" #include "unit_tests_tftp.c" +#include "unit_tests_branches.c" +#include "unit_tests_socket_api_arms.c" +#include "unit_tests_tcp_state.c" +#include "unit_tests_poll_dispatcher.c" +#include "unit_tests_dhcp_edges.c" +#include "unit_tests_ip_arp_recv.c" +#include "unit_tests_dns_edges.c" +#include "unit_tests_misc_edges.c" Suite *wolf_suite(void) { @@ -902,6 +931,549 @@ Suite *wolf_suite(void) tcase_add_test(tc_wolfssl, test_wolfssl_io_send_behaviors); tcase_add_test(tc_wolfssl, test_wolfssl_io_send_invalid_desc); tcase_add_test(tc_wolfssl, test_wolfssl_io_send_want_write_keeps_buffer); + + /* Branch-coverage tests backported from the trimmed wolfIP suite. */ + tcase_add_test(tc_core, test_socket_from_fd_invalid_inputs); + tcase_add_test(tc_core, test_can_read_write_icmp_socket); + tcase_add_test(tc_core, test_sock_socket_wrong_domain); + tcase_add_test(tc_core, test_sock_socket_unsupported_protocol); + tcase_add_test(tc_core, test_sock_socket_pool_exhaustion); + tcase_add_test(tc_core, test_sock_close_invalid_inputs); + tcase_add_test(tc_core, test_sock_close_udp_dispatches_filter); + tcase_add_test(tc_core, test_sock_close_icmp_dispatches_filter); + tcase_add_test(tc_core, test_sock_connect_invalid_args_udp_icmp); + tcase_add_test(tc_core, test_sock_connect_udp_descriptor_oor); + tcase_add_test(tc_core, test_sock_connect_udp_short_addrlen_or_wrong_family); + tcase_add_test(tc_core, test_sock_connect_udp_uses_bound_local_ip); + tcase_add_test(tc_core, test_sock_connect_udp_unbound_uses_route); + tcase_add_test(tc_core, test_sock_connect_icmp_basic); + tcase_add_test(tc_core, test_sock_connect_icmp_wrong_family_or_short); + tcase_add_test(tc_core, test_sock_connect_icmp_bound_local_mismatch); + tcase_add_test(tc_core, test_sock_bind_null_or_short); + tcase_add_test(tc_core, test_sock_bind_oor_and_unmarked); + tcase_add_test(tc_core, test_sock_bind_udp_rebind_rejected); + tcase_add_test(tc_core, test_sock_bind_udp_wrong_family); + tcase_add_test(tc_core, test_sock_bind_udp_any_uses_primary); + tcase_add_test(tc_core, test_sock_bind_icmp_basic_and_rebind); + tcase_add_test(tc_core, test_sock_bind_icmp_wrong_family); + tcase_add_test(tc_core, test_sock_bind_filter_block_rolls_back); + tcase_add_test(tc_core, test_sendto_arg_validation); + tcase_add_test(tc_core, test_sendto_udp_short_addrlen_and_zero_dest); + tcase_add_test(tc_core, test_sendto_udp_auto_assigns_src_port); + tcase_add_test(tc_core, test_sendto_icmp_branches); + tcase_add_test(tc_core, test_recvfrom_arg_validation); + tcase_add_test(tc_core, test_recvfrom_icmp_populates_sin); + tcase_add_test(tc_core, test_setsockopt_invalid_socket); + tcase_add_test(tc_core, test_setsockopt_recvttl_argument_checks); + tcase_add_test(tc_core, test_setsockopt_unknown_optname_returns_zero); +#ifdef IP_MULTICAST + tcase_add_test(tc_core, test_setsockopt_multicast_short_optlen); + tcase_add_test(tc_core, test_setsockopt_multicast_ttl_out_of_range); + tcase_add_test(tc_core, test_setsockopt_multicast_on_icmp_is_noop); + tcase_add_test(tc_core, test_setsockopt_drop_unjoined_returns_einval); +#endif + tcase_add_test(tc_core, test_getsockopt_invalid_inputs); +#ifdef IP_MULTICAST + tcase_add_test(tc_core, test_getsockopt_multicast_if_short_optlen); +#endif + tcase_add_test(tc_core, test_poll_dispatches_socket_callback); + tcase_add_test(tc_core, test_poll_fires_expired_timer); + tcase_add_test(tc_core, test_poll_arp_pending_when_nexthop_unresolved); + tcase_add_test(tc_core, test_poll_filter_block_holds_tx); + tcase_add_test(tc_core, test_poll_drains_icmp_tx); + tcase_add_test(tc_core, test_arp_pending_record_replaces_oldest_slot); + tcase_add_test(tc_core, test_arp_flush_pending_drops_ttl1); + tcase_add_test(tc_core, test_icmp_input_short_frame_dropped); + tcase_add_test(tc_core, test_icmp_input_bad_checksum_dropped); + tcase_add_test(tc_core, test_icmp_input_filter_blocked); + tcase_add_test(tc_core, test_udp_try_recv_short_frame_dropped); + tcase_add_test(tc_core, test_udp_try_recv_bad_udp_checksum_dropped); + tcase_add_test(tc_core, test_udp_try_recv_filter_blocked); +#ifdef IP_MULTICAST + tcase_add_test(tc_core, test_mcast_if_from_addr_validation); +#endif + tcase_add_test(tc_core, test_udp_send_and_receive_through_poll); + tcase_add_test(tc_core, test_sock_getsockname_udp_success); + tcase_add_test(tc_core, test_sock_getpeername_negative_sockfd); + tcase_add_test(tc_core, test_arp_lookup_missing_returns_neg1); + tcase_add_test(tc_core, test_fifo_can_push_len_validation); + tcase_add_test(tc_core, test_fifo_can_push_len_full_no_wrap_returns_zero); + tcase_add_test(tc_core, test_fifo_can_push_len_head_wraps_when_h_wrap_matches); + tcase_add_test(tc_core, test_fifo_can_push_len_end_space_insufficient_then_wraps); + tcase_add_test(tc_core, test_fifo_can_push_len_end_space_insufficient_tail_too_close); + tcase_add_test(tc_core, test_fifo_can_push_len_h_wrap_insufficient_space); + tcase_add_test(tc_core, test_fifo_can_push_len_h_wrap_head_ge_tail_no_space); + tcase_add_test(tc_core, test_fifo_push_returns_minus_one_when_full); + tcase_add_test(tc_core, test_route_for_ip_gateway_fallback); + tcase_add_test(tc_core, test_route_for_ip_first_non_loop_fallback); + tcase_add_test(tc_core, test_sendto_udp_txbuf_full_eagain); + tcase_add_test(tc_core, test_sendto_icmp_no_remote_after_addr_zero); + tcase_add_test(tc_core, test_ip_recv_with_ip_options_strips_and_dispatches); + tcase_add_test(tc_core, test_ip_recv_wrong_version_dropped); + tcase_add_test(tc_core, test_ip_recv_short_ihl_dropped); + tcase_add_test(tc_core, test_ip_recv_fragment_dropped); + tcase_add_test(tc_core, test_ip_recv_bad_header_checksum_dropped); + tcase_add_test(tc_core, test_arp_recv_short_frame_dropped); + tcase_add_test(tc_core, test_arp_recv_request_for_other_ip_ignored); + tcase_add_test(tc_core, test_arp_recv_request_refreshes_existing_neighbor); + tcase_add_test(tc_core, test_arp_recv_reply_overwrite_blocked_when_no_pending); + tcase_add_test(tc_core, test_arp_request_rate_limited); +#ifdef IP_MULTICAST + tcase_add_test(tc_core, test_close_releases_multicast); +#endif + tcase_add_test(tc_core, test_send_port_unreachable_filter_blocked_at_eth); + tcase_add_test(tc_core, test_send_ttl_exceeded_filter_blocked_at_icmp); +#ifdef IP_MULTICAST + tcase_add_test(tc_core, test_mcast_join_non_multicast_rejected_direct); + tcase_add_test(tc_core, test_mcast_join_exhausts_socket_slots); + tcase_add_test(tc_core, test_mcast_drop_unjoined_returns_einval_direct); +#endif + tcase_add_test(tc_core, test_arp_store_neighbor_refresh_existing); + tcase_add_test(tc_core, test_arp_store_neighbor_full_table); + tcase_add_test(tc_core, test_arp_pending_record_rejects_null_and_oversized); + tcase_add_test(tc_core, test_sock_bind_icmp_filter_rolls_back); +#ifdef IP_MULTICAST + tcase_add_test(tc_core, test_setsockopt_multicast_ttl_int_path); + tcase_add_test(tc_core, test_setsockopt_multicast_loop_int_and_uint8); + tcase_add_test(tc_core, test_setsockopt_multicast_ttl_uint8_path); +#endif + tcase_add_test(tc_core, test_icmp_input_echo_reply_path_filter_at_eth); + tcase_add_test(tc_core, test_ip_recv_with_options_oversize_dropped); + tcase_add_test(tc_core, test_wolfip_recv_on_null_stack_returns); + + /* Socket API arms: TCP, RAW, PACKET */ + tcase_add_test(tc_core, test_register_callback_tcp_stores_handle); + tcase_add_test(tc_core, test_register_callback_tcp_oor_ignored); +#if WOLFIP_RAWSOCKETS + tcase_add_test(tc_core, test_register_callback_raw_stores_handle); + tcase_add_test(tc_core, test_register_callback_raw_oor_ignored); +#endif +#if WOLFIP_PACKET_SOCKETS + tcase_add_test(tc_core, test_register_callback_packet_stores_handle); + tcase_add_test(tc_core, test_register_callback_packet_oor_ignored); +#endif + tcase_add_test(tc_core, test_sock_can_read_tcp_established_empty); + tcase_add_test(tc_core, test_sock_can_read_tcp_close_wait_returns_one); + tcase_add_test(tc_core, test_sock_can_read_tcp_invalid_fd); + tcase_add_test(tc_core, test_sock_can_write_tcp_syn_sent_returns_zero); + tcase_add_test(tc_core, test_sock_can_write_tcp_established_with_space); + tcase_add_test(tc_core, test_sock_can_write_tcp_closed_returns_one); + tcase_add_test(tc_core, test_sock_can_write_tcp_invalid_fd); +#if WOLFIP_RAWSOCKETS + tcase_add_test(tc_core, test_sock_can_read_raw_empty); + tcase_add_test(tc_core, test_sock_can_read_raw_invalid_fd); + tcase_add_test(tc_core, test_sock_can_write_raw_with_space); + tcase_add_test(tc_core, test_sock_can_write_raw_invalid_fd); +#endif +#if WOLFIP_PACKET_SOCKETS + tcase_add_test(tc_core, test_sock_can_read_packet_empty); + tcase_add_test(tc_core, test_sock_can_write_packet_with_space); +#endif + tcase_add_test(tc_core, test_sock_bind_tcp_success); + tcase_add_test(tc_core, test_sock_bind_tcp_any_ip_uses_primary); + tcase_add_test(tc_core, test_sock_bind_tcp_oor_fd); + tcase_add_test(tc_core, test_sock_bind_tcp_state_closed_required); +#if WOLFIP_RAWSOCKETS + tcase_add_test(tc_core, test_sock_bind_raw_specific_interface); + tcase_add_test(tc_core, test_sock_bind_raw_any_ip_uses_primary); + tcase_add_test(tc_core, test_sock_bind_raw_wrong_family); + tcase_add_test(tc_core, test_sock_bind_raw_oor_fd); +#endif +#if WOLFIP_PACKET_SOCKETS + tcase_add_test(tc_core, test_sock_bind_packet_success); + tcase_add_test(tc_core, test_sock_bind_packet_wrong_family); + tcase_add_test(tc_core, test_sock_bind_packet_out_of_range_ifindex); + tcase_add_test(tc_core, test_sock_bind_packet_oor_fd); +#endif + tcase_add_test(tc_core, test_sock_connect_tcp_invalid_fd); + tcase_add_test(tc_core, test_sock_connect_tcp_syn_sent_returns_eagain); + tcase_add_test(tc_core, test_sock_connect_tcp_established_arm_returns_zero); + tcase_add_test(tc_core, test_sock_connect_tcp_bad_state_returns_einval); +#if WOLFIP_RAWSOCKETS + tcase_add_test(tc_core, test_sock_connect_raw_sets_remote_ip); + tcase_add_test(tc_core, test_sock_connect_raw_invalid_fd); + tcase_add_test(tc_core, test_sock_connect_raw_wrong_family); + tcase_add_test(tc_core, test_sock_connect_raw_with_bound_local_ip); +#endif + tcase_add_test(tc_core, test_sock_sendto_tcp_established_sends_data); + tcase_add_test(tc_core, test_sock_sendto_tcp_invalid_fd); + tcase_add_test(tc_core, test_sock_sendto_tcp_close_wait_sends_data); +#if WOLFIP_RAWSOCKETS + tcase_add_test(tc_core, test_sock_sendto_raw_null_dest_uses_stored_remote_ip); + tcase_add_test(tc_core, test_sock_sendto_raw_null_dest_no_remote_ip); + tcase_add_test(tc_core, test_sock_sendto_raw_hdrincl_dst_from_buf); + tcase_add_test(tc_core, test_sock_sendto_raw_invalid_fd); + tcase_add_test(tc_core, test_sock_sendto_raw_fifo_full_returns_eagain); + tcase_add_test(tc_core, test_sock_setsockopt_raw_hdrincl); + tcase_add_test(tc_core, test_sock_setsockopt_raw_dontroute); + tcase_add_test(tc_core, test_sock_setsockopt_raw_recvttl); + tcase_add_test(tc_core, test_sock_setsockopt_raw_unknown_option); + tcase_add_test(tc_core, test_sock_setsockopt_raw_null_optval); + tcase_add_test(tc_core, test_sock_setsockopt_raw_invalid_fd); +#endif +#if WOLFIP_PACKET_SOCKETS + tcase_add_test(tc_core, test_sock_setsockopt_packet_returns_einval); + tcase_add_test(tc_core, test_sock_sendto_packet_writes_raw_frame); + tcase_add_test(tc_core, test_sock_sendto_packet_with_sll_updates_dst_mac); + tcase_add_test(tc_core, test_sock_sendto_packet_too_small); + tcase_add_test(tc_core, test_sock_sendto_packet_invalid_fd); +#endif + tcase_add_test(tc_core, test_sock_recvfrom_tcp_not_established); + tcase_add_test(tc_core, test_sock_recvfrom_tcp_established_empty_queue); + tcase_add_test(tc_core, test_sock_recvfrom_tcp_invalid_fd); +#if WOLFIP_RAWSOCKETS + tcase_add_test(tc_core, test_sock_recvfrom_raw_empty); + tcase_add_test(tc_core, test_sock_recvfrom_raw_invalid_fd); + tcase_add_test(tc_core, test_sock_recvfrom_raw_with_sin); +#endif +#if WOLFIP_PACKET_SOCKETS + tcase_add_test(tc_core, test_sock_recvfrom_packet_empty); + tcase_add_test(tc_core, test_sock_recvfrom_packet_invalid_fd); + tcase_add_test(tc_core, test_sock_recvfrom_packet_null_addrlen_with_sll); +#endif + tcase_add_test(tc_core, test_sock_getsockname_tcp_success); + tcase_add_test(tc_core, test_sock_getsockname_tcp_invalid_fd); +#if WOLFIP_RAWSOCKETS + tcase_add_test(tc_core, test_sock_getsockname_raw_success); + tcase_add_test(tc_core, test_sock_getsockname_raw_invalid_fd); +#endif +#if WOLFIP_PACKET_SOCKETS + tcase_add_test(tc_core, test_sock_getsockname_packet_success); +#endif + tcase_add_test(tc_core, test_sock_getpeername_tcp_success); + tcase_add_test(tc_core, test_sock_getpeername_tcp_invalid_fd); + tcase_add_test(tc_core, test_sock_getpeername_tcp_null_addr); +#if WOLFIP_RAWSOCKETS + tcase_add_test(tc_core, test_sock_getpeername_raw_success); + tcase_add_test(tc_core, test_sock_getpeername_raw_no_remote_ip); + tcase_add_test(tc_core, test_sock_getpeername_raw_invalid_fd); + tcase_add_test(tc_core, test_sock_get_recv_ttl_raw_disabled); + tcase_add_test(tc_core, test_sock_get_recv_ttl_raw_enabled); + tcase_add_test(tc_core, test_sock_get_recv_ttl_raw_null_ttl); + tcase_add_test(tc_core, test_sock_get_recv_ttl_raw_invalid_fd); +#endif + tcase_add_test(tc_core, test_notify_loopback_tcp_sets_writable); + tcase_add_test(tc_core, test_notify_loopback_tcp_non_loopback_not_notified); + tcase_add_test(tc_core, test_notify_loopback_null_stack_no_crash); + + /* === Branch-coverage tests from fleet ===*/ + /* --- unit_tests_tcp_state.c (62 tests) --- */ + tcase_add_test(tc_core, test_tcp_send_reset_reply_ignores_rst_input); + tcase_add_test(tc_core, test_tcp_send_reset_reply_ack_in_uses_ack_seq); + tcase_add_test(tc_core, test_tcp_send_reset_reply_syn_no_ack_sets_rst_ack); + tcase_add_test(tc_core, test_tcp_parse_options_ws_clamped_to_14); + tcase_add_test(tc_core, test_tcp_parse_options_sack_bad_olen_ignored); + tcase_add_test(tc_core, test_tcp_parse_options_nop_advances); + tcase_add_test(tc_core, test_tcp_parse_options_zero_olen_breaks); + tcase_add_test(tc_core, test_tcp_parse_options_timestamp_parsed); + tcase_add_test(tc_core, test_tcp_parse_options_mss_zero_ignored); + tcase_add_test(tc_core, test_tcp_parse_options_sack_permitted_parsed); + tcase_add_test(tc_core, test_tcp_input_syn_rcvd_rst_bad_seq_ignored); + tcase_add_test(tc_core, test_tcp_input_syn_rcvd_rst_good_seq_reverts_to_listen); + tcase_add_test(tc_core, test_tcp_input_time_wait_sends_ack_on_any_segment); + tcase_add_test(tc_core, test_tcp_input_last_ack_unacceptable_sends_ack); + tcase_add_test(tc_core, test_tcp_input_last_ack_syn_sends_challenge_ack); + tcase_add_test(tc_core, test_tcp_input_established_syn_sends_challenge_ack); + tcase_add_test(tc_core, test_tcp_input_established_out_of_window_sends_ack); + tcase_add_test(tc_core, test_tcp_input_established_no_ack_dropped); + tcase_add_test(tc_core, test_tcp_input_established_fin_ooo_no_close_wait); + tcase_add_test(tc_core, test_tcp_input_syn_rcvd_ack_with_fin_enters_close_wait); + tcase_add_test(tc_core, test_tcp_input_window_grows_from_zero_stops_persist); + tcase_add_test(tc_core, test_tcp_rto_cb_fin_wait_2_timeout_closes_socket); + tcase_add_test(tc_core, test_tcp_rto_cb_fin_wait_2_wrong_state_stops_timer); + tcase_add_test(tc_core, test_tcp_rto_cb_ctrl_not_needed_stops); + tcase_add_test(tc_core, test_tcp_rto_cb_ctrl_maxretries_nonlistener_closes); + tcase_add_test(tc_core, test_tcp_ack_duplicate_zero_inflight_early_return); + tcase_add_test(tc_core, test_tcp_ack_duplicate_ack_ne_snd_una_returns); + tcase_add_test(tc_core, test_tcp_ack_fourth_dupack_inflates_cwnd); + tcase_add_test(tc_core, test_tcp_ack_close_wait_processes_ack); + tcase_add_test(tc_core, test_tcp_mark_unsacked_no_cover_no_sack_returns_zero); + tcase_add_test(tc_core, test_tcp_resync_inflight_null_args); + tcase_add_test(tc_core, test_tcp_resync_inflight_skips_when_ctrl_rto_active); + tcase_add_test(tc_core, test_tcp_resync_inflight_arms_timer_on_sent_payload); + tcase_add_test(tc_core, test_tcp_resync_inflight_cancels_timer_when_no_payload); + tcase_add_test(tc_core, test_tcp_find_pending_retrans_null_args); + tcase_add_test(tc_core, test_tcp_find_pending_retrans_already_sent_skipped); + tcase_add_test(tc_core, test_tcp_find_pending_retrans_unsent_retrans_returned); + tcase_add_test(tc_core, test_tcp_send_empty_immediate_null_tsocket); + tcase_add_test(tc_core, test_tcp_send_empty_immediate_null_seg); + tcase_add_test(tc_core, test_tcp_send_empty_immediate_short_frame_len); + tcase_add_test(tc_core, test_tcp_send_empty_immediate_arp_hit_sends); + tcase_add_test(tc_core, test_tcp_send_zero_wnd_probe_null_ts); + tcase_add_test(tc_core, test_tcp_send_zero_wnd_probe_non_tcp_proto); + tcase_add_test(tc_core, test_tcp_send_zero_wnd_probe_empty_txbuf); + tcase_add_test(tc_core, test_tcp_send_zero_wnd_probe_sends_probe); + tcase_add_test(tc_core, test_icmp_try_deliver_tcp_error_null_args); + tcase_add_test(tc_core, test_icmp_try_deliver_tcp_error_wrong_type_ignored); + tcase_add_test(tc_core, test_icmp_try_deliver_tcp_error_ttl_exceeded_syn_sent); + tcase_add_test(tc_core, test_icmp_try_deliver_tcp_error_port_unreach_syn_sent_closes); + tcase_add_test(tc_core, test_icmp_try_deliver_tcp_error_src_ip_mismatch_not_closed); + tcase_add_test(tc_core, test_icmp_try_deliver_tcp_error_frag_needed_reduces_mss); + tcase_add_test(tc_core, test_icmp_try_deliver_tcp_error_avail_too_small); +#if WOLFIP_RAWSOCKETS + tcase_add_test(tc_core, test_raw_route_for_ip_null_stack); + tcase_add_test(tc_core, test_raw_route_for_ip_null_rs_uses_route); + tcase_add_test(tc_core, test_raw_route_for_ip_bound_local_ip_match); + tcase_add_test(tc_core, test_raw_route_for_ip_bound_local_ip_no_match); + tcase_add_test(tc_core, test_raw_route_for_ip_dontroute_local_match); + tcase_add_test(tc_core, test_raw_route_for_ip_dontroute_no_local_match); +#endif /* WOLFIP_RAWSOCKETS */ + tcase_add_test(tc_core, test_tcp_input_listen_rst_ignored); + tcase_add_test(tc_core, test_tcp_input_fin_wait_1_fin_enters_closing); + tcase_add_test(tc_core, test_tcp_input_fin_wait_2_fin_enters_time_wait); + tcase_add_test(tc_core, test_tcp_input_rst_in_window_not_exact_sends_ack); + /* --- unit_tests_poll_dispatcher.c (47 tests) --- */ + tcase_add_test(tc_core, test_poll_device_poll_returns_zero_exits_loop); + tcase_add_test(tc_core, test_poll_device_poll_returns_negative_exits_loop); + tcase_add_test(tc_core, test_poll_device_non_ethernet_path_receives); + tcase_add_test(tc_core, test_poll_device_non_ethernet_minimum_mtu_clamped); + tcase_add_test(tc_core, test_poll_device_budget_exhaustion_stops_at_budget); + tcase_add_test(tc_core, test_poll_device_no_poll_callback_skipped); + tcase_add_test(tc_core, test_poll_timer_fires_multiple_in_one_tick); + tcase_add_test(tc_core, test_poll_timer_cancelled_tombstone_drained_before_live_timer); + tcase_add_test(tc_core, test_poll_timer_callback_rearms_itself); + tcase_add_test(tc_core, test_poll_timer_callback_cancels_sibling); + tcase_add_test(tc_core, test_poll_icmp_socket_callback_dispatched); + tcase_add_test(tc_core, test_poll_tcp_socket_callback_dispatched); + tcase_add_test(tc_core, test_poll_udp_socket_callback_dispatched); +#if WOLFIP_RAWSOCKETS + tcase_add_test(tc_core, test_poll_raw_socket_callback_dispatched); +#endif /* WOLFIP_RAWSOCKETS */ +#if WOLFIP_PACKET_SOCKETS + tcase_add_test(tc_core, test_poll_packet_socket_callback_dispatched); +#endif /* WOLFIP_PACKET_SOCKETS */ + tcase_add_test(tc_core, test_poll_tx_tcp_pkt_flag_sent_desc_skipped); + tcase_add_test(tc_core, test_poll_tx_tcp_arp_miss_emits_arp_request); + tcase_add_test(tc_core, test_poll_tx_tcp_filter_tcp_blocks_send); + tcase_add_test(tc_core, test_poll_tx_tcp_send_eagain_breaks_loop); + tcase_add_test(tc_core, test_poll_tx_tcp_zero_window_starts_persist); + tcase_add_test(tc_core, test_poll_tx_tcp_retransmit_replay); + tcase_add_test(tc_core, test_poll_tx_tcp_loopback_path); + tcase_add_test(tc_core, test_poll_tx_udp_sends_on_arp_hit); + tcase_add_test(tc_core, test_poll_tx_udp_filter_ip_blocks_send); + tcase_add_test(tc_core, test_poll_tx_udp_eagain_retains_queue); + tcase_add_test(tc_core, test_poll_tx_udp_broadcast_sets_ff_mac); + tcase_add_test(tc_core, test_poll_tx_udp_loopback_path_no_crash); + tcase_add_test(tc_core, test_poll_tx_icmp_sends_on_arp_hit); + tcase_add_test(tc_core, test_poll_tx_icmp_filter_blocks_send); + tcase_add_test(tc_core, test_poll_tx_icmp_eagain_retains_queue); + tcase_add_test(tc_core, test_poll_tx_icmp_broadcast_sets_ff_mac); + tcase_add_test(tc_core, test_poll_tx_icmp_loopback_path_no_crash); +#if WOLFIP_RAWSOCKETS + tcase_add_test(tc_core, test_poll_tx_raw_sends_on_arp_hit); + tcase_add_test(tc_core, test_poll_tx_raw_arp_miss_emits_request); + tcase_add_test(tc_core, test_poll_tx_raw_dst_zero_skips_descriptor); + tcase_add_test(tc_core, test_poll_tx_raw_filter_blocks_send); + tcase_add_test(tc_core, test_poll_tx_raw_loopback_path); +#endif /* WOLFIP_RAWSOCKETS */ +#if WOLFIP_PACKET_SOCKETS + tcase_add_test(tc_core, test_poll_tx_packet_sends_frame); + tcase_add_test(tc_core, test_poll_tx_packet_filter_blocks_advances_desc); +#endif /* WOLFIP_PACKET_SOCKETS */ + tcase_add_test(tc_core, test_poll_combined_timer_and_socket_cb_in_same_tick); + tcase_add_test(tc_core, test_poll_no_timers_and_no_events_is_noop); + tcase_add_test(tc_core, test_poll_last_tick_updated); + tcase_add_test(tc_core, test_poll_loopback_interface_iterated); + tcase_add_test(tc_core, test_poll_multiple_udp_sockets_both_cbs_dispatched); + tcase_add_test(tc_core, test_poll_tcp_cb_not_dispatched_when_closed); + tcase_add_test(tc_core, test_poll_udp_cb_not_dispatched_without_events); +#ifdef IP_MULTICAST + tcase_add_test(tc_core, test_poll_tx_udp_multicast_arp_skipped_uses_mcast_mac); +#endif /* IP_MULTICAST */ + /* --- unit_tests_dhcp_edges.c (43 tests) --- */ + tcase_add_test(tc_core, test_dhcp_schedule_lease_timer_zero_lease_noop); + tcase_add_test(tc_core, test_dhcp_schedule_lease_timer_null_noop); + tcase_add_test(tc_core, test_dhcp_schedule_lease_timer_renew_gt_lease_clamped); + tcase_add_test(tc_core, test_dhcp_schedule_lease_timer_rebind_lt_renew_fixed); + tcase_add_test(tc_core, test_dhcp_schedule_lease_timer_rebind_gt_lease_clamped); + tcase_add_test(tc_core, test_dhcp_schedule_lease_timer_explicit_t1_t2); + tcase_add_test(tc_core, test_dhcp_msg_type_returns_offer); + tcase_add_test(tc_core, test_dhcp_msg_type_returns_nak); + tcase_add_test(tc_core, test_dhcp_msg_type_returns_ack); + tcase_add_test(tc_core, test_dhcp_msg_type_returns_request); + tcase_add_test(tc_core, test_dhcp_msg_type_returns_discover); + tcase_add_test(tc_core, test_dhcp_msg_type_bad_xid_returns_neg1); + tcase_add_test(tc_core, test_dhcp_msg_type_bad_magic_returns_neg1); + tcase_add_test(tc_core, test_dhcp_msg_type_boot_request_returns_neg1); + tcase_add_test(tc_core, test_dhcp_msg_type_len_ne_1_not_returned); + tcase_add_test(tc_core, test_dhcp_msg_type_pad_bytes_skipped); + tcase_add_test(tc_core, test_dhcp_msg_type_truncated_option_returns_neg1); + tcase_add_test(tc_core, test_dhcp_parse_offer_type_ack_not_offer_rejected); + tcase_add_test(tc_core, test_dhcp_parse_offer_subnet_mask_len_lt4_rejected); + tcase_add_test(tc_core, test_dhcp_parse_offer_inner_truncated_opt2_rejected); + tcase_add_test(tc_core, test_dhcp_parse_offer_inner_truncated_data_rejected); + tcase_add_test(tc_core, test_dhcp_parse_offer_inner_pad_then_end); + tcase_add_test(tc_core, test_dhcp_parse_offer_outer_end_with_state_already_set); + tcase_add_test(tc_core, test_dhcp_parse_ack_mismatched_server_id_rejected); + tcase_add_test(tc_core, test_dhcp_parse_ack_server_id_len_lt4_rejected); + tcase_add_test(tc_core, test_dhcp_parse_ack_offer_ip_len_lt4_rejected); + tcase_add_test(tc_core, test_dhcp_parse_ack_subnet_mask_len_lt4_rejected); + tcase_add_test(tc_core, test_dhcp_parse_ack_router_len_lt4_rejected); + tcase_add_test(tc_core, test_dhcp_parse_ack_dns_len_lt4_rejected); + tcase_add_test(tc_core, test_dhcp_parse_ack_lease_time_len_lt4_rejected); + tcase_add_test(tc_core, test_dhcp_parse_ack_renewal_time_len_lt4_rejected); + tcase_add_test(tc_core, test_dhcp_parse_ack_rebind_time_len_lt4_rejected); + tcase_add_test(tc_core, test_dhcp_parse_ack_no_ip_after_ack_rejected); + tcase_add_test(tc_core, test_dhcp_parse_ack_no_mask_after_ack_rejected); + tcase_add_test(tc_core, test_dhcp_parse_ack_with_renewal_and_rebind_times); + tcase_add_test(tc_core, test_dhcp_parse_ack_dns_already_set_skipped); + tcase_add_test(tc_core, test_dhcp_parse_ack_inner_pad_bytes_skipped); + tcase_add_test(tc_core, test_dhcp_timer_cb_renewing_not_yet_rebind_sends_request); + tcase_add_test(tc_core, test_dhcp_timer_cb_renewing_past_rebind_transitions_to_rebinding); + tcase_add_test(tc_core, test_dhcp_timer_cb_rebinding_not_expired_sends_request); + tcase_add_test(tc_core, test_dhcp_timer_cb_bound_lease_not_expired_starts_renew); + tcase_add_test(tc_core, test_dhcp_timer_cb_default_state_noop); + tcase_add_test(tc_core, test_dhcp_timer_cb_null_arg_noop); + /* --- unit_tests_ip_arp_recv.c (34 tests) --- */ + tcase_add_test(tc_core, test_ip_recv_limited_broadcast_dst_is_local); + tcase_add_test(tc_core, test_ip_recv_directed_broadcast_dst_is_local); + tcase_add_test(tc_core, test_ip_recv_ipaddr_any_dst_is_local); + tcase_add_test(tc_core, test_ip_recv_forward_arp_hit_sends_immediately); + tcase_add_test(tc_core, test_ip_recv_forward_unconfigured_iface_skipped); + tcase_add_test(tc_core, test_ip_recv_forward_link_local_src_rpf_drop); + tcase_add_test(tc_core, test_ip_recv_options_nop_delivered); + tcase_add_test(tc_core, test_ip_recv_options_rr_stripped_and_delivered); + tcase_add_test(tc_core, test_ip_recv_options_bad_length_aborts_parse); + tcase_add_test(tc_core, test_ip_recv_options_strip_checksum_recomputed); + tcase_add_test(tc_core, test_ip_recv_options_ssrr_dropped); + tcase_add_test(tc_core, test_ip_recv_loopback_dst_on_non_loopback_dropped); + tcase_add_test(tc_core, test_ip_recv_loopback_src_on_non_loopback_dropped); + tcase_add_test(tc_core, test_ip_recv_forward_ttl_normal_decremented); + tcase_add_test(tc_core, test_ip_recv_forward_ttl1_short_frame_dropped); + tcase_add_test(tc_core, test_ip_recv_dest_matches_secondary_iface_ip_is_local); + tcase_add_test(tc_core, test_ip_recv_multicast_dst_not_forwarded); + tcase_add_test(tc_core, test_arp_recv_htype_not_ethernet_dropped); + tcase_add_test(tc_core, test_arp_recv_ptype_not_ipv4_dropped); + tcase_add_test(tc_core, test_arp_recv_hlen_not_6_dropped); + tcase_add_test(tc_core, test_arp_recv_plen_not_4_dropped); + tcase_add_test(tc_core, test_arp_recv_sender_own_ip_rejected); + tcase_add_test(tc_core, test_arp_recv_sender_ipaddr_any_rejected); + tcase_add_test(tc_core, test_arp_recv_reply_sender_broadcast_rejected); + tcase_add_test(tc_core, test_arp_recv_reply_sender_multicast_rejected); + tcase_add_test(tc_core, test_arp_recv_reply_sender_own_ip_rejected); + tcase_add_test(tc_core, test_arp_recv_reply_sender_zero_ip_rejected); + tcase_add_test(tc_core, test_arp_recv_valid_request_caches_neighbor_when_pending); + tcase_add_test(tc_core, test_arp_recv_runt_packet_dropped); + tcase_add_test(tc_core, test_ip_recv_wrong_version_dropped_v6); + tcase_add_test(tc_core, test_ip_recv_ihl_too_small_dropped); + tcase_add_test(tc_core, test_ip_recv_ip_len_less_than_hlen_dropped); + tcase_add_test(tc_core, test_ip_recv_bad_ip_checksum_dropped); +#ifdef IP_MULTICAST + tcase_add_test(tc_core, test_ip_recv_multicast_not_joined_dropped); +#endif /* IP_MULTICAST */ + /* --- unit_tests_dns_edges.c (22 tests) --- */ + tcase_add_test(tc_core, test_dns_callback_recvfrom_error_closes_socket); + tcase_add_test(tc_core, test_dns_callback_rcode_nonzero_aborts_query); + tcase_add_test(tc_core, test_dns_callback_zero_ancount_no_delivery); + tcase_add_test(tc_core, test_dns_callback_aaaa_answer_skipped_for_a_query); + tcase_add_test(tc_core, test_dns_callback_rr_rdlen_truncated_aborts_query); + tcase_add_test(tc_core, test_dns_callback_bad_question_name_aborts_query); + tcase_add_test(tc_core, test_dns_callback_answer_forward_ptr_aborts_query); + tcase_add_test(tc_core, test_dns_cancel_timer_null_noop); + tcase_add_test(tc_core, test_dns_schedule_timer_null_noop); + tcase_add_test(tc_core, test_dns_timeout_cb_null_noop); + tcase_add_test(tc_core, test_dns_timeout_cb_zero_id_noop); + tcase_add_test(tc_core, test_dns_timeout_cb_resend_failure_aborts_query); + tcase_add_test(tc_core, test_dns_copy_name_label_too_big_for_output); + tcase_add_test(tc_core, test_dns_copy_name_zero_out_len_rejects_terminator_write); + tcase_add_test(tc_core, test_dns_copy_name_ptr_at_end_of_buffer); + tcase_add_test(tc_core, test_dns_copy_name_label_past_end); + tcase_add_test(tc_core, test_dns_copy_name_separator_overflow); + tcase_add_test(tc_core, test_dns_copy_name_label_overflow_output); + tcase_add_test(tc_core, test_dns_skip_name_label_past_end); + tcase_add_test(tc_core, test_dns_copy_name_second_label_separator_and_label_fit); + tcase_add_test(tc_core, test_dns_callback_ptr_bad_copy_name_stays_pending); + tcase_add_test(tc_core, test_dns_copy_name_jumped_no_pos_increment); + tcase_add_test(tc_core, test_dns_send_query_socket_alloc_failure); + /* --- unit_tests_misc_edges.c (75 tests) --- */ + tcase_add_test(tc_core, test_wolfip_init_null_stack); + tcase_add_test(tc_core, test_wolfip_init_static_null_ptr); + tcase_add_test(tc_core, test_wolfip_ipconfig_set_ex_null_stack); + tcase_add_test(tc_core, test_wolfip_ipconfig_set_ex_bad_ifidx); + tcase_add_test(tc_core, test_wolfip_ipconfig_get_ex_null_stack); + tcase_add_test(tc_core, test_wolfip_ipconfig_get_ex_null_out_ptrs); + tcase_add_test(tc_core, test_wolfip_mtu_set_zero_resets_to_default); + tcase_add_test(tc_core, test_wolfip_mtu_set_below_min_clamps); + tcase_add_test(tc_core, test_wolfip_mtu_set_above_max_clamps); + tcase_add_test(tc_core, test_wolfip_mtu_get_null_mtu_ptr); + tcase_add_test(tc_core, test_wolfip_arp_lookup_ex_null_stack); + tcase_add_test(tc_core, test_wolfip_arp_lookup_ex_null_mac); + tcase_add_test(tc_core, test_wolfip_arp_lookup_ex_found); + tcase_add_test(tc_core, test_fifo_push_full_hwrap_head_eq_tail); + tcase_add_test(tc_core, test_fifo_push_hwrap_head_ge_tail_space_zero); + tcase_add_test(tc_core, test_fifo_push_no_end_space_tail_too_small); + tcase_add_test(tc_core, test_fifo_can_push_len_hwrap_head_plus_needed_gt_tail); + tcase_add_test(tc_core, test_fifo_can_push_len_no_hwrap_head_plus_needed_gt_size); + tcase_add_test(tc_core, test_fifo_next_pos_out_of_range); + tcase_add_test(tc_core, test_fifo_next_desc_len_too_large); + tcase_add_test(tc_core, test_fifo_len_tail_gt_head_with_hwrap); + tcase_add_test(tc_core, test_iphdr_verify_checksum_bad); + tcase_add_test(tc_core, test_iphdr_verify_checksum_good); + tcase_add_test(tc_core, test_wolfip_ip_is_broadcast_null_stack); + tcase_add_test(tc_core, test_wolfip_ip_is_broadcast_all_ones); + tcase_add_test(tc_core, test_wolfip_ip_is_broadcast_full_mask_skipped); + tcase_add_test(tc_core, test_wolfip_select_nexthop_null_conf); + tcase_add_test(tc_core, test_wolfip_select_nexthop_broadcast); +#ifdef IP_MULTICAST + tcase_add_test(tc_core, test_eth_is_ipv4_multicast_mac_true); + tcase_add_test(tc_core, test_eth_is_ipv4_multicast_mac_high_bit_set); + tcase_add_test(tc_core, test_eth_is_ipv4_multicast_mac_wrong_prefix); + tcase_add_test(tc_core, test_mcast_membership_find_null_stack); + tcase_add_test(tc_core, test_udp_socket_has_mcast_null_tsocket); +#endif /* IP_MULTICAST */ + tcase_add_test(tc_core, test_wolfip_ip_is_multicast_boundary); + tcase_add_test(tc_core, test_close_socket_null); + tcase_add_test(tc_core, test_close_socket_non_tcp_udp); + tcase_add_test(tc_core, test_tx_has_writable_space_unknown_proto); + tcase_add_test(tc_core, test_tx_has_writable_space_null); + tcase_add_test(tc_core, test_bind_port_in_use_port_zero); + tcase_add_test(tc_core, test_arp_pending_record_refresh_existing); + tcase_add_test(tc_core, test_arp_pending_record_replaces_oldest); + tcase_add_test(tc_core, test_arp_pending_match_and_clear_expires_stale); + tcase_add_test(tc_core, test_arp_pending_match_and_clear_null_stack); + tcase_add_test(tc_core, test_arp_neighbor_index_aged_out); + tcase_add_test(tc_core, test_arp_neighbor_index_null_stack); + tcase_add_test(tc_core, test_wolfip_route_for_ip_null_stack); + tcase_add_test(tc_core, test_wolfip_route_for_ip_broadcast_address); + tcase_add_test(tc_core, test_wolfip_forward_interface_null_or_single_iface); + tcase_add_test(tc_core, test_wolfip_forward_interface_local_dest_rejected); + tcase_add_test(tc_core, test_wolfip_loopback_send_null_ll); + tcase_add_test(tc_core, test_wolfip_send_ttl_exceeded_null_ll); + tcase_add_test(tc_core, test_wolfip_send_port_unreachable_null_ll_misc); + tcase_add_test(tc_core, test_wolfip_sock_socket_icmp_success); +#if WOLFIP_RAWSOCKETS + tcase_add_test(tc_core, test_wolfip_rawsocket_from_fd_not_used); +#endif /* WOLFIP_RAWSOCKETS */ +#if WOLFIP_PACKET_SOCKETS + tcase_add_test(tc_core, test_wolfip_packetsocket_from_fd_not_used); +#endif /* WOLFIP_PACKET_SOCKETS */ + tcase_add_test(tc_core, test_wolfip_recv_on_short_frame); + tcase_add_test(tc_core, test_filter_mask_for_proto_default_branch); + tcase_add_test(tc_core, test_filter_dispatch_null_meta_initializes); + tcase_add_test(tc_core, test_wolfip_sock_listen_udp_fd); + tcase_add_test(tc_core, test_wolfip_sock_accept_udp_fd); + tcase_add_test(tc_core, test_wolfip_sock_close_negative_fd); + tcase_add_test(tc_core, test_ipcounter_next_increments); + tcase_add_test(tc_core, test_ipcounter_next_wraps); + tcase_add_test(tc_core, test_queue_insert_pos_equals_size_returns_error); + tcase_add_test(tc_core, test_wolfip_sock_socket_tcp_all_sockets); +#if WOLFIP_RAWSOCKETS + tcase_add_test(tc_core, test_raw_try_recv_filter_blocks_frame); +#endif /* WOLFIP_RAWSOCKETS */ + tcase_add_test(tc_core, test_fifo_can_push_len_head_lt_tail_no_hwrap); + tcase_add_test(tc_core, test_fifo_can_push_len_hwrap_head_equals_hwrap); + tcase_add_test(tc_core, test_fifo_push_hwrap_head_equals_hwrap_succeeds); + tcase_add_test(tc_core, test_fifo_push_no_hwrap_wraps_to_front_succeeds); + tcase_add_test(tc_core, test_fifo_push_exact_end_sets_hwrap); + tcase_add_test(tc_core, test_wolfip_send_port_unreachable_large_ihl); +#if WOLFIP_RAWSOCKETS + tcase_add_test(tc_core, test_wolfip_rawsocket_from_fd_negative_fd); +#endif /* WOLFIP_RAWSOCKETS */ +#if WOLFIP_PACKET_SOCKETS + tcase_add_test(tc_core, test_wolfip_packetsocket_from_fd_negative_fd); +#endif /* WOLFIP_PACKET_SOCKETS */ + tcase_add_test(tc_core, test_bind_port_in_use_different_ips_no_collision); + suite_add_tcase(s, tc_core); suite_add_tcase(s, tc_utils); suite_add_tcase(s, tc_proto); diff --git a/src/test/unit/unit_esp.c b/src/test/unit/unit_esp.c index 1bbdc9ee..1dbe906d 100644 --- a/src/test/unit/unit_esp.c +++ b/src/test/unit/unit_esp.c @@ -1,6 +1,6 @@ /* unit_esp.c * - * Copyright (C) 2026 wolfSSL Inc. + * Copyright (C) 2024 wolfSSL Inc. * * This file is part of wolfIP TCP/IP stack. * @@ -18,7 +18,6 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ - #ifndef WOLFIP_ESP #define WOLFIP_ESP #endif diff --git a/src/test/unit/unit_noeth.c b/src/test/unit/unit_noeth.c index 9917a278..80379d92 100644 --- a/src/test/unit/unit_noeth.c +++ b/src/test/unit/unit_noeth.c @@ -1,3 +1,24 @@ +/* unit_noeth.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + #include "../../../config.h" #ifdef ETHERNET #undef ETHERNET diff --git a/src/test/unit/unit_shared.c b/src/test/unit/unit_shared.c index 53ada602..928a59d0 100644 --- a/src/test/unit/unit_shared.c +++ b/src/test/unit/unit_shared.c @@ -1,6 +1,6 @@ /* unit_shared.c * - * Copyright (C) 2026 wolfSSL Inc. + * Copyright (C) 2024 wolfSSL Inc. * * This file is part of wolfIP TCP/IP stack. * diff --git a/src/test/unit/unit_tests_api.c b/src/test/unit/unit_tests_api.c index 3ae1bb1e..162d04f0 100644 --- a/src/test/unit/unit_tests_api.c +++ b/src/test/unit/unit_tests_api.c @@ -1,3 +1,24 @@ +/* unit_tests_api.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + static struct wolfIP *poll_rearm_stack; static int poll_rearm_cb_calls; static int poll_rearm_recv_len; diff --git a/src/test/unit/unit_tests_branches.c b/src/test/unit/unit_tests_branches.c new file mode 100644 index 00000000..ad684f72 --- /dev/null +++ b/src/test/unit/unit_tests_branches.c @@ -0,0 +1,2380 @@ +/* unit_tests_branches.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +/* ---- wolfIP_socket_from_fd / wolfIP_sock_can_read / wolfIP_sock_can_write ---- */ + +START_TEST(test_socket_from_fd_invalid_inputs) +{ + struct wolfIP s; + int udp_sd; + + wolfIP_init(&s); + mock_link_init(&s); + + /* can_read / can_write reject negative descriptors via socket_from_fd */ + ck_assert_int_eq(wolfIP_sock_can_read(&s, -1), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_can_write(&s, -1), -WOLFIP_EINVAL); + + /* Out-of-range UDP/ICMP descriptors */ + ck_assert_int_eq(wolfIP_sock_can_read(&s, MARK_UDP_SOCKET | MAX_UDPSOCKETS), + -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_can_write(&s, MARK_ICMP_SOCKET | MAX_ICMPSOCKETS), + -WOLFIP_EINVAL); + + /* Unmarked descriptor (neither UDP nor ICMP) is rejected */ + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ck_assert_int_eq(wolfIP_sock_can_read(&s, 0), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_can_write(&s, 0), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_can_read_write_icmp_socket) +{ + struct wolfIP s; + struct tsocket *ts; + int sd; + uint8_t payload[8] = {0}; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(sd, 0); + ts = &s.icmpsockets[SOCKET_UNMARK(sd)]; + ck_assert_int_eq(wolfIP_sock_can_read(&s, sd), 0); + ck_assert_int_eq(wolfIP_sock_can_write(&s, sd), 1); + /* enqueue a fake ICMP frame */ + { + uint8_t buf[sizeof(struct wolfIP_icmp_packet) + sizeof(payload)]; + struct wolfIP_icmp_packet *icmp = (struct wolfIP_icmp_packet *)buf; + memset(buf, 0, sizeof(buf)); + icmp->ip.len = ee16(IP_HEADER_LEN + ICMP_HEADER_LEN + sizeof(payload)); + ck_assert_int_eq(fifo_push(&ts->sock.udp.rxbuf, buf, sizeof(buf)), 0); + } + ck_assert_int_eq(wolfIP_sock_can_read(&s, sd), 1); +} +END_TEST + +/* ---- wolfIP_sock_socket ---- */ + +START_TEST(test_sock_socket_wrong_domain) +{ + struct wolfIP s; + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_socket(&s, AF_INET + 1, + IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP), -1); +} +END_TEST + +START_TEST(test_sock_socket_unsupported_protocol) +{ + struct wolfIP s; + wolfIP_init(&s); + mock_link_init(&s); + /* SOCK_DGRAM with a protocol number outside {0, UDP, ICMP} is rejected. */ + ck_assert_int_eq(wolfIP_sock_socket(&s, AF_INET, + IPSTACK_SOCK_DGRAM, 0x7f), -1); +} +END_TEST + +START_TEST(test_sock_socket_pool_exhaustion) +{ + struct wolfIP s; + int i; + int sd; + wolfIP_init(&s); + mock_link_init(&s); + for (i = 0; i < MAX_UDPSOCKETS; i++) { + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(sd, 0); + } + /* Pool exhausted: next allocation fails. */ + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_eq(sd, -1); + + for (i = 0; i < MAX_ICMPSOCKETS; i++) { + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(sd, 0); + } + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_eq(sd, -1); +} +END_TEST + +/* ---- wolfIP_sock_close ---- */ + +START_TEST(test_sock_close_invalid_inputs) +{ + struct wolfIP s; + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_close(&s, -1), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_close(&s, MARK_UDP_SOCKET | MAX_UDPSOCKETS), + -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_close(&s, MARK_ICMP_SOCKET | MAX_ICMPSOCKETS), + -WOLFIP_EINVAL); + /* Unmarked descriptor */ + ck_assert_int_eq(wolfIP_sock_close(&s, 0), -1); +} +END_TEST + +START_TEST(test_sock_close_udp_dispatches_filter) +{ + struct wolfIP s; + int sd; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(sd, 0); + + filter_cb_calls = 0; + memset(&filter_last_event, 0, sizeof(filter_last_event)); + wolfIP_filter_set_callback(test_filter_cb, NULL); + wolfIP_filter_set_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_DISSOCIATE)); + + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); + ck_assert_int_eq(filter_cb_calls, 1); + ck_assert_int_eq(filter_last_event.reason, WOLFIP_FILT_DISSOCIATE); + + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_mask(0); +} +END_TEST + +START_TEST(test_sock_close_icmp_dispatches_filter) +{ + struct wolfIP s; + int sd; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(sd, 0); + + filter_cb_calls = 0; + memset(&filter_last_event, 0, sizeof(filter_last_event)); + wolfIP_filter_set_callback(test_filter_cb, NULL); + wolfIP_filter_set_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_DISSOCIATE)); + + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); + ck_assert_int_eq(filter_cb_calls, 1); + + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_mask(0); +} +END_TEST + +/* ---- wolfIP_sock_connect ---- */ + +START_TEST(test_sock_connect_invalid_args_udp_icmp) +{ + struct wolfIP s; + int udp_sd; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + + /* NULL addr */ + ck_assert_int_eq(wolfIP_sock_connect(&s, udp_sd, NULL, sizeof(sin)), + -WOLFIP_EINVAL); + /* sockfd<0 */ + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + ck_assert_int_eq(wolfIP_sock_connect(&s, -1, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), -WOLFIP_EINVAL); + /* Unmarked descriptor */ + ck_assert_int_eq(wolfIP_sock_connect(&s, 0, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_connect_udp_descriptor_oor) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(1234); + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_connect(&s, MARK_UDP_SOCKET | MAX_UDPSOCKETS, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_connect(&s, MARK_ICMP_SOCKET | MAX_ICMPSOCKETS, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_connect_udp_short_addrlen_or_wrong_family) +{ + struct wolfIP s; + int udp_sd; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(1234); + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_connect(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, 1), -WOLFIP_EINVAL); + sin.sin_family = AF_INET + 1; + ck_assert_int_eq(wolfIP_sock_connect(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_connect_udp_uses_bound_local_ip) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + const ip4 primary_ip = 0xC0A80001U; + const ip4 secondary_ip = 0xC0A80101U; + + setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + + /* Set bound_local_ip directly so connect picks if_idx from it. */ + ts->bound_local_ip = secondary_ip; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(9000); + sin.sin_addr.s_addr = ee32(0xC0A80155U); + ck_assert_int_eq(wolfIP_sock_connect(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + ck_assert_uint_eq(ts->if_idx, TEST_SECOND_IF); + ck_assert_uint_eq(ts->local_ip, secondary_ip); + + /* bound_local_ip that doesn't match any interface fails. */ + ts->bound_local_ip = 0xDEADBEEFU; + ck_assert_int_eq(wolfIP_sock_connect(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_connect_udp_unbound_uses_route) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + const ip4 primary_ip = 0x0A000001U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, primary_ip, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(9000); + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_connect(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + ck_assert_uint_eq(ts->local_ip, primary_ip); + ck_assert_uint_eq(ts->remote_ip, 0x0A000002U); + ck_assert_uint_eq(ts->dst_port, 9000); +} +END_TEST + +START_TEST(test_sock_connect_icmp_basic) +{ + struct wolfIP s; + int icmp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + const ip4 primary_ip = 0x0A000001U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, primary_ip, 0xFFFFFF00U, 0); + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + ts = &s.icmpsockets[SOCKET_UNMARK(icmp_sd)]; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_connect(&s, icmp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + ck_assert_uint_eq(ts->remote_ip, 0x0A000002U); + ck_assert_uint_eq(ts->local_ip, primary_ip); +} +END_TEST + +START_TEST(test_sock_connect_icmp_wrong_family_or_short) +{ + struct wolfIP s; + int icmp_sd; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET + 1; + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_connect(&s, icmp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -WOLFIP_EINVAL); + sin.sin_family = AF_INET; + ck_assert_int_eq(wolfIP_sock_connect(&s, icmp_sd, + (struct wolfIP_sockaddr *)&sin, 1), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_connect_icmp_bound_local_mismatch) +{ + struct wolfIP s; + int icmp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + ts = &s.icmpsockets[SOCKET_UNMARK(icmp_sd)]; + ts->bound_local_ip = 0xDEADBEEFU; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_connect(&s, icmp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -WOLFIP_EINVAL); +} +END_TEST + +/* ---- wolfIP_sock_bind extras ---- */ + +START_TEST(test_sock_bind_null_or_short) +{ + struct wolfIP s; + int udp_sd; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ck_assert_int_eq(wolfIP_sock_bind(&s, udp_sd, NULL, sizeof(sin)), + -WOLFIP_EINVAL); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(IPADDR_ANY); + ck_assert_int_eq(wolfIP_sock_bind(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, 1), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_bind(&s, -1, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_bind_oor_and_unmarked) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in sin; + wolfIP_init(&s); + mock_link_init(&s); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(1234); + sin.sin_addr.s_addr = ee32(IPADDR_ANY); + ck_assert_int_eq(wolfIP_sock_bind(&s, MARK_UDP_SOCKET | MAX_UDPSOCKETS, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_bind(&s, MARK_ICMP_SOCKET | MAX_ICMPSOCKETS, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_bind(&s, 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -1); +} +END_TEST + +START_TEST(test_sock_bind_udp_rebind_rejected) +{ + struct wolfIP s; + int udp_sd; + struct wolfIP_sockaddr_in sin; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(1234); + sin.sin_addr.s_addr = ee32(IPADDR_ANY); + ck_assert_int_eq(wolfIP_sock_bind(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + /* Already bound -> reject. */ + ck_assert_int_eq(wolfIP_sock_bind(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -1); +} +END_TEST + +START_TEST(test_sock_bind_udp_wrong_family) +{ + struct wolfIP s; + int udp_sd; + struct wolfIP_sockaddr_in sin; + wolfIP_init(&s); + mock_link_init(&s); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET + 1; + sin.sin_port = ee16(1234); + sin.sin_addr.s_addr = ee32(IPADDR_ANY); + ck_assert_int_eq(wolfIP_sock_bind(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -1); +} +END_TEST + +START_TEST(test_sock_bind_udp_any_uses_primary) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(1234); + sin.sin_addr.s_addr = ee32(IPADDR_ANY); + ck_assert_int_eq(wolfIP_sock_bind(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + /* bind to IPADDR_ANY should fall through to the primary interface config. */ + ck_assert_uint_eq(ts->local_ip, 0x0A000001U); + ck_assert_uint_eq(ts->bound_local_ip, IPADDR_ANY); +} +END_TEST + +START_TEST(test_sock_bind_icmp_basic_and_rebind) +{ + struct wolfIP s; + int icmp_sd; + struct wolfIP_sockaddr_in sin; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(7); + sin.sin_addr.s_addr = ee32(0x0A000001U); + ck_assert_int_eq(wolfIP_sock_bind(&s, icmp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + /* Wrong family on rebind path: existing src_port != 0 takes precedence, + * returning -1 before family check. */ + ck_assert_int_eq(wolfIP_sock_bind(&s, icmp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -1); +} +END_TEST + +START_TEST(test_sock_bind_icmp_wrong_family) +{ + struct wolfIP s; + int icmp_sd; + struct wolfIP_sockaddr_in sin; + wolfIP_init(&s); + mock_link_init(&s); + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET + 1; + sin.sin_port = ee16(7); + sin.sin_addr.s_addr = ee32(IPADDR_ANY); + ck_assert_int_eq(wolfIP_sock_bind(&s, icmp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -1); +} +END_TEST + +START_TEST(test_sock_bind_filter_block_rolls_back) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + ip4 prev_ip; + uint16_t prev_port; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + prev_ip = ts->local_ip; + prev_port = ts->src_port; + + filter_block_reason = WOLFIP_FILT_BINDING; + filter_block_calls = 0; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_BINDING)); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(1234); + sin.sin_addr.s_addr = ee32(IPADDR_ANY); + ck_assert_int_eq(wolfIP_sock_bind(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -1); + /* State rolled back. */ + ck_assert_uint_eq(ts->local_ip, prev_ip); + ck_assert_uint_eq(ts->src_port, prev_port); + + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_mask(0); +} +END_TEST + +/* ---- wolfIP_sock_sendto extras ---- */ + +START_TEST(test_sendto_arg_validation) +{ + struct wolfIP s; + int udp_sd; + int icmp_sd; + uint8_t buf[4] = {1, 2, 3, 4}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(udp_sd, 0); + ck_assert_int_gt(icmp_sd, 0); + + /* sockfd<0 */ + ck_assert_int_eq(wolfIP_sock_sendto(&s, -1, buf, sizeof(buf), 0, NULL, 0), + -WOLFIP_EINVAL); + /* NULL buf */ + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, NULL, sizeof(buf), 0, NULL, 0), + -1); + /* len=0 */ + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, buf, 0, 0, NULL, 0), + -1); + /* OOR descriptors */ + ck_assert_int_eq(wolfIP_sock_sendto(&s, MARK_UDP_SOCKET | MAX_UDPSOCKETS, + buf, sizeof(buf), 0, NULL, 0), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_sendto(&s, MARK_ICMP_SOCKET | MAX_ICMPSOCKETS, + buf, sizeof(buf), 0, NULL, 0), -WOLFIP_EINVAL); + /* Unmarked descriptor */ + ck_assert_int_eq(wolfIP_sock_sendto(&s, 0, buf, sizeof(buf), 0, NULL, 0), + -1); + /* UDP: no dest_port, no dest_addr */ + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, buf, sizeof(buf), 0, NULL, 0), + -1); +} +END_TEST + +START_TEST(test_sendto_udp_short_addrlen_and_zero_dest) +{ + struct wolfIP s; + int udp_sd; + struct wolfIP_sockaddr_in sin; + uint8_t buf[4] = {0}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(9000); + sin.sin_addr.s_addr = ee32(0x0A000002U); + /* Short addrlen with non-NULL sin */ + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, buf, sizeof(buf), 0, + (struct wolfIP_sockaddr *)&sin, 1), -1); + + /* sin with zero port/addr after copy -> dst_port/remote_ip==0 path */ + sin.sin_port = 0; + sin.sin_addr.s_addr = 0; + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, buf, sizeof(buf), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -1); +} +END_TEST + +START_TEST(test_sendto_udp_auto_assigns_src_port) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + uint8_t buf[4] = {0xAA, 0xBB, 0xCC, 0xDD}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + ck_assert_uint_eq(ts->src_port, 0); + + /* Force low-value random to exercise the "< 1024" rollover branch. */ + test_rand_override_enabled = 1; + test_rand_override_value = 5; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(9000); + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, buf, sizeof(buf), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(buf)); + ck_assert_uint_ge(ts->src_port, 1024U); + test_rand_override_enabled = 0; +} +END_TEST + +START_TEST(test_sendto_icmp_branches) +{ + struct wolfIP s; + int icmp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + uint8_t small[ICMP_HEADER_LEN - 1] = {0}; + uint8_t payload[ICMP_HEADER_LEN] = {0}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + ts = &s.icmpsockets[SOCKET_UNMARK(icmp_sd)]; + + /* ICMP: no remote yet, no dest_addr -> -1 */ + ck_assert_int_eq(wolfIP_sock_sendto(&s, icmp_sd, payload, sizeof(payload), + 0, NULL, 0), -1); + + /* Short addrlen with sin -> -1 */ + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_sendto(&s, icmp_sd, payload, sizeof(payload), + 0, (struct wolfIP_sockaddr *)&sin, 1), -1); + + /* payload < ICMP_HEADER_LEN -> EINVAL */ + ck_assert_int_eq(wolfIP_sock_sendto(&s, icmp_sd, small, sizeof(small), + 0, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -WOLFIP_EINVAL); + /* Valid send: payload exactly ICMP_HEADER_LEN, dest from sin */ + payload[0] = ICMP_ECHO_REQUEST; + ck_assert_int_eq(wolfIP_sock_sendto(&s, icmp_sd, payload, sizeof(payload), + 0, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), + (int)sizeof(payload)); + ck_assert_uint_eq(ts->remote_ip, 0x0A000002U); + + /* bound_local_ip mismatch -> EINVAL */ + ts->bound_local_ip = 0xDEADBEEFU; + ck_assert_int_eq(wolfIP_sock_sendto(&s, icmp_sd, payload, sizeof(payload), + 0, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -WOLFIP_EINVAL); +} +END_TEST + +/* ---- wolfIP_sock_recvfrom extras ---- */ + +START_TEST(test_recvfrom_arg_validation) +{ + struct wolfIP s; + int udp_sd; + int icmp_sd; + struct wolfIP_sockaddr_in sin; + socklen_t slen = sizeof(sin); + uint8_t buf[8]; + + wolfIP_init(&s); + mock_link_init(&s); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(udp_sd, 0); + ck_assert_int_gt(icmp_sd, 0); + + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, -1, buf, sizeof(buf), 0, NULL, NULL), + -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, MARK_UDP_SOCKET | MAX_UDPSOCKETS, + buf, sizeof(buf), 0, NULL, NULL), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, MARK_ICMP_SOCKET | MAX_ICMPSOCKETS, + buf, sizeof(buf), 0, NULL, NULL), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, 0, buf, sizeof(buf), 0, NULL, NULL), + -WOLFIP_EINVAL); + /* sin with no addrlen */ + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, udp_sd, buf, sizeof(buf), 0, + (struct wolfIP_sockaddr *)&sin, NULL), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, icmp_sd, buf, sizeof(buf), 0, + (struct wolfIP_sockaddr *)&sin, NULL), -WOLFIP_EINVAL); + /* sin with short addrlen */ + slen = 1; + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, udp_sd, buf, sizeof(buf), 0, + (struct wolfIP_sockaddr *)&sin, &slen), -WOLFIP_EINVAL); + slen = 1; + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, icmp_sd, buf, sizeof(buf), 0, + (struct wolfIP_sockaddr *)&sin, &slen), -WOLFIP_EINVAL); + /* Empty UDP returns EAGAIN, empty ICMP returns EAGAIN */ + slen = sizeof(sin); + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, udp_sd, buf, sizeof(buf), 0, + NULL, NULL), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, icmp_sd, buf, sizeof(buf), 0, + NULL, NULL), -WOLFIP_EAGAIN); +} +END_TEST + +START_TEST(test_recvfrom_icmp_populates_sin) +{ + struct wolfIP s; + int icmp_sd; + struct tsocket *ts; + uint8_t out[ICMP_HEADER_LEN]; + struct wolfIP_sockaddr_in from; + socklen_t fromlen = sizeof(from); + uint8_t buf[sizeof(struct wolfIP_icmp_packet)]; + struct wolfIP_icmp_packet *icmp = (struct wolfIP_icmp_packet *)buf; + + wolfIP_init(&s); + mock_link_init(&s); + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + ts = &s.icmpsockets[SOCKET_UNMARK(icmp_sd)]; + + memset(buf, 0, sizeof(buf)); + icmp->ip.src = ee32(0x0A000002U); + icmp->ip.len = ee16(IP_HEADER_LEN + ICMP_HEADER_LEN); + icmp->type = ICMP_ECHO_REPLY; + ck_assert_int_eq(fifo_push(&ts->sock.udp.rxbuf, buf, sizeof(buf)), 0); + ts->events |= CB_EVENT_READABLE; + + memset(&from, 0, sizeof(from)); + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, icmp_sd, out, sizeof(out), 0, + (struct wolfIP_sockaddr *)&from, &fromlen), ICMP_HEADER_LEN); + ck_assert_uint_eq(from.sin_family, AF_INET); + ck_assert_uint_eq(from.sin_addr.s_addr, ee32(0x0A000002U)); + ck_assert_uint_eq(ts->events & CB_EVENT_READABLE, 0U); +} +END_TEST + +/* ---- wolfIP_sock_setsockopt extras ---- */ + +START_TEST(test_setsockopt_invalid_socket) +{ + struct wolfIP s; + int v = 1; + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, -1, + WOLFIP_SOL_IP, WOLFIP_IP_RECVTTL, &v, sizeof(v)), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, MARK_UDP_SOCKET | MAX_UDPSOCKETS, + WOLFIP_SOL_IP, WOLFIP_IP_RECVTTL, &v, sizeof(v)), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_setsockopt_recvttl_argument_checks) +{ + struct wolfIP s; + int udp_sd; + int enable = 1; + struct tsocket *ts; + wolfIP_init(&s); + mock_link_init(&s); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_RECVTTL, NULL, sizeof(enable)), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_RECVTTL, &enable, 1), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_RECVTTL, &enable, sizeof(enable)), 0); + ck_assert_uint_eq(ts->recv_ttl, 1); + enable = 0; + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_RECVTTL, &enable, sizeof(enable)), 0); + ck_assert_uint_eq(ts->recv_ttl, 0); +} +END_TEST + +START_TEST(test_setsockopt_unknown_optname_returns_zero) +{ + struct wolfIP s; + int udp_sd; + int v = 42; + wolfIP_init(&s); + mock_link_init(&s); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, udp_sd, + 0xFEED, 0xBEEF, &v, sizeof(v)), 0); +} +END_TEST + +#ifdef IP_MULTICAST +START_TEST(test_setsockopt_multicast_short_optlen) +{ + struct wolfIP s; + int udp_sd; + uint8_t small[1] = {0}; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_ADD_MEMBERSHIP, small, sizeof(small)), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_DROP_MEMBERSHIP, NULL, 0), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_MULTICAST_IF, small, sizeof(small)), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_MULTICAST_TTL, NULL, sizeof(int)), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_MULTICAST_TTL, small, 0), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_MULTICAST_LOOP, NULL, sizeof(int)), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_MULTICAST_LOOP, small, 0), -WOLFIP_EINVAL); +} +END_TEST +#endif /* IP_MULTICAST */ + +#ifdef IP_MULTICAST +START_TEST(test_setsockopt_multicast_ttl_out_of_range) +{ + struct wolfIP s; + int udp_sd; + int ttl; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ttl = -1; + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_MULTICAST_TTL, &ttl, sizeof(ttl)), -WOLFIP_EINVAL); + ttl = 256; + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_MULTICAST_TTL, &ttl, sizeof(ttl)), -WOLFIP_EINVAL); +} +END_TEST +#endif /* IP_MULTICAST */ + +#ifdef IP_MULTICAST +START_TEST(test_setsockopt_multicast_on_icmp_is_noop) +{ + struct wolfIP s; + int icmp_sd; + struct wolfIP_ip_mreq mreq; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr.s_addr = ee32(0xE9010210U); + /* On ICMP socket, IP_ADD_MEMBERSHIP is ignored and returns 0 (fall-through). */ + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, icmp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)), 0); +} +END_TEST +#endif /* IP_MULTICAST */ + +#ifdef IP_MULTICAST +START_TEST(test_setsockopt_drop_unjoined_returns_einval) +{ + struct wolfIP s; + int udp_sd; + struct wolfIP_ip_mreq mreq; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr.s_addr = ee32(0xE9010211U); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)), -WOLFIP_EINVAL); +} +END_TEST +#endif /* IP_MULTICAST */ + +/* ---- wolfIP_sock_getsockopt extras ---- */ + +START_TEST(test_getsockopt_invalid_inputs) +{ + struct wolfIP s; + int udp_sd; + int value; + socklen_t len = sizeof(value); + wolfIP_init(&s); + mock_link_init(&s); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + + /* Invalid socket */ + ck_assert_int_eq(wolfIP_sock_getsockopt(&s, -1, WOLFIP_SOL_IP, + WOLFIP_IP_RECVTTL, &value, &len), -WOLFIP_EINVAL); + /* Null optval / optlen / short len */ + ck_assert_int_eq(wolfIP_sock_getsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_RECVTTL, NULL, &len), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_getsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_RECVTTL, &value, NULL), -WOLFIP_EINVAL); + len = 1; + ck_assert_int_eq(wolfIP_sock_getsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_RECVTTL, &value, &len), -WOLFIP_EINVAL); + /* Unknown optname returns 0 (fall-through). */ + len = sizeof(value); + ck_assert_int_eq(wolfIP_sock_getsockopt(&s, udp_sd, 0xFEED, 0xBEEF, + &value, &len), 0); +} +END_TEST + +#ifdef IP_MULTICAST +START_TEST(test_getsockopt_multicast_if_short_optlen) +{ + struct wolfIP s; + int udp_sd; + uint8_t small[1]; + socklen_t len = sizeof(small); + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ck_assert_int_eq(wolfIP_sock_getsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_MULTICAST_IF, small, &len), -WOLFIP_EINVAL); +} +END_TEST +#endif /* IP_MULTICAST */ + +/* ---- wolfIP_poll ---- */ + +START_TEST(test_poll_dispatches_socket_callback) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + wolfIP_init(&s); + mock_link_init(&s); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + wolfIP_register_callback(&s, udp_sd, test_socket_cb, NULL); + ts->events = CB_EVENT_READABLE; + socket_cb_calls = 0; + socket_cb_last_fd = -1; + ck_assert_int_eq(wolfIP_poll(&s, 1), 0); + ck_assert_int_eq(socket_cb_calls, 1); + ck_assert_int_eq(socket_cb_last_fd, SOCKET_UNMARK(udp_sd) | MARK_UDP_SOCKET); +} +END_TEST + +START_TEST(test_poll_fires_expired_timer) +{ + struct wolfIP s; + struct wolfIP_timer tmr; + wolfIP_init(&s); + mock_link_init(&s); + memset(&tmr, 0, sizeof(tmr)); + tmr.expires = 100; + tmr.cb = test_timer_cb; + timers_binheap_insert(&s.timers, tmr); + timer_cb_calls = 0; + ck_assert_int_eq(wolfIP_poll(&s, 200), 0); + ck_assert_int_eq(timer_cb_calls, 1); +} +END_TEST + +START_TEST(test_poll_arp_pending_when_nexthop_unresolved) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + uint8_t buf[4] = {1, 2, 3, 4}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(9000); + sin.sin_addr.s_addr = ee32(0x0A0000FEU); /* not in ARP cache */ + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, buf, sizeof(buf), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(buf)); + ck_assert_uint_gt(fifo_len(&ts->sock.udp.txbuf), 0U); + last_frame_sent_size = 0; + /* Use now > 1000 so the ARP rate-limit window has elapsed. */ + ck_assert_int_eq(wolfIP_poll(&s, 2000), 0); + /* Poll should have emitted an ARP request and left the datagram queued. */ + ck_assert_uint_eq(last_frame_sent_size, sizeof(struct arp_packet)); + ck_assert_uint_gt(fifo_len(&ts->sock.udp.txbuf), 0U); +} +END_TEST + +START_TEST(test_poll_filter_block_holds_tx) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + uint8_t buf[4] = {0}; + uint8_t neighbor_mac[6] = {0x02, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + /* Pre-load ARP so that sendto can be flushed by poll without ARP wait. */ + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + s.arp.neighbors[0].ts = 1; + memcpy(s.arp.neighbors[0].mac, neighbor_mac, 6); + s.last_tick = 1; + + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(9000); + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, buf, sizeof(buf), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(buf)); + + /* Block UDP sends via filter. */ + filter_block_reason = WOLFIP_FILT_SENDING; + filter_block_calls = 0; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_udp_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_SENDING)); + last_frame_sent_size = 0; + ck_assert_int_eq(wolfIP_poll(&s, 2), 0); + /* Filter blocked send: nothing transmitted, packet still in txbuf. */ + ck_assert_uint_eq(last_frame_sent_size, 0U); + ck_assert_uint_gt(fifo_len(&ts->sock.udp.txbuf), 0U); + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_udp_mask(0); +} +END_TEST + +START_TEST(test_poll_drains_icmp_tx) +{ + struct wolfIP s; + int icmp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + uint8_t payload[ICMP_HEADER_LEN] = {0}; + uint8_t neighbor_mac[6] = {0x02, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + s.arp.neighbors[0].ts = 1; + memcpy(s.arp.neighbors[0].mac, neighbor_mac, 6); + s.last_tick = 1; + + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + ts = &s.icmpsockets[SOCKET_UNMARK(icmp_sd)]; + + payload[0] = ICMP_ECHO_REQUEST; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_sendto(&s, icmp_sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + last_frame_sent_size = 0; + ck_assert_int_eq(wolfIP_poll(&s, 2), 0); + ck_assert_uint_gt(last_frame_sent_size, 0U); + ck_assert_uint_eq(fifo_len(&ts->sock.udp.txbuf), 0U); +} +END_TEST + +/* ---- ARP queue eviction / replacement ---- */ + +START_TEST(test_arp_pending_record_replaces_oldest_slot) +{ + struct wolfIP s; + struct wolfIP_ip_packet ip; + uint32_t len = sizeof(ip); + int i; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + memset(&ip, 0, sizeof(ip)); + ip.ver_ihl = 0x45; + ip.ttl = 64; + ip.len = ee16(IP_HEADER_LEN); + + /* Fill the pending pool. */ + for (i = 0; i < WOLFIP_ARP_PENDING_MAX; i++) { + ip.dst = ee32(0x0A000010U + i); + arp_queue_packet(&s, TEST_PRIMARY_IF, 0x0A000010U + i, &ip, len); + } + /* Same dst hits the existing slot, no new slot allocated. */ + ip.dst = ee32(0x0A000010U); + arp_queue_packet(&s, TEST_PRIMARY_IF, 0x0A000010U, &ip, len); + ck_assert_uint_eq(s.arp_pending[0].dest, 0x0A000010U); + + /* A new destination with no free slot falls back to slot 0. */ + ip.dst = ee32(0x0A0000A0U); + arp_queue_packet(&s, TEST_PRIMARY_IF, 0x0A0000A0U, &ip, len); + ck_assert_uint_eq(s.arp_pending[0].dest, 0x0A0000A0U); +} +END_TEST + +START_TEST(test_arp_flush_pending_drops_ttl1) +{ + struct wolfIP s; + struct wolfIP_ip_packet ip; + struct arp_packet reply; + uint8_t mac[6] = {0x02, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + memset(&ip, 0, sizeof(ip)); + ip.ver_ihl = 0x45; + ip.ttl = 1; + ip.len = ee16(IP_HEADER_LEN); + ip.dst = ee32(0x0A000050U); + arp_queue_packet(&s, TEST_PRIMARY_IF, 0x0A000050U, &ip, sizeof(ip)); + + memset(&reply, 0, sizeof(reply)); + reply.htype = ee16(1); + reply.ptype = ee16(0x0800); + reply.hlen = 6; + reply.plen = 4; + reply.opcode = ee16(ARP_REPLY); + reply.sip = ee32(0x0A000050U); + memcpy(reply.sma, mac, 6); + last_frame_sent_size = 0; + arp_recv(&s, TEST_PRIMARY_IF, &reply, sizeof(reply)); + /* Queue should be drained even though no frame was emitted (TTL would drop to 0). */ + ck_assert_uint_eq(s.arp_pending[0].dest, IPADDR_ANY); + ck_assert_uint_eq(s.arp_pending[0].len, 0U); +} +END_TEST + +/* ---- icmp_input extras ---- */ + +START_TEST(test_icmp_input_short_frame_dropped) +{ + struct wolfIP s; + uint8_t buf[sizeof(struct wolfIP_icmp_packet) - 1]; + wolfIP_init(&s); + mock_link_init(&s); + last_frame_sent_size = 0; + memset(buf, 0, sizeof(buf)); + icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)buf, sizeof(buf)); + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + +START_TEST(test_icmp_input_bad_checksum_dropped) +{ + struct wolfIP s; + struct wolfIP_icmp_packet icmp; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + memset(&icmp, 0, sizeof(icmp)); + icmp.ip.src = ee32(0x0A000002U); + icmp.ip.dst = ee32(0x0A000001U); + icmp.ip.ttl = 64; + icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_HEADER_LEN); + icmp.type = ICMP_ECHO_REQUEST; + icmp.csum = 0; /* wrong checksum */ + icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_HEADER_LEN)); + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + +START_TEST(test_icmp_input_filter_blocked) +{ + struct wolfIP s; + struct wolfIP_icmp_packet icmp; + wolfIP_init(&s); + mock_link_init(&s); + last_frame_sent_size = 0; + memset(&icmp, 0, sizeof(icmp)); + icmp.ip.src = ee32(0x0A000002U); + icmp.ip.dst = ee32(0x0A000001U); + icmp.ip.ttl = 64; + icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_HEADER_LEN); + icmp.type = ICMP_ECHO_REQUEST; + icmp.csum = ee16(icmp_checksum(&icmp, ICMP_HEADER_LEN)); + + filter_block_reason = WOLFIP_FILT_RECEIVING; + filter_block_calls = 0; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_icmp_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_RECEIVING)); + icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_HEADER_LEN)); + ck_assert_uint_eq(last_frame_sent_size, 0U); + ck_assert_int_gt(filter_block_calls, 0); + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_icmp_mask(0); +} +END_TEST + +/* ---- udp_try_recv extras ---- */ + +START_TEST(test_udp_try_recv_short_frame_dropped) +{ + struct wolfIP s; + uint8_t buf[sizeof(struct wolfIP_udp_datagram) - 1]; + wolfIP_init(&s); + mock_link_init(&s); + memset(buf, 0, sizeof(buf)); + last_frame_sent_size = 0; + udp_try_recv(&s, TEST_PRIMARY_IF, (struct wolfIP_udp_datagram *)buf, + (uint32_t)sizeof(buf)); + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + +START_TEST(test_udp_try_recv_bad_udp_checksum_dropped) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + uint8_t buf[sizeof(struct wolfIP_udp_datagram) + 4]; + struct wolfIP_udp_datagram *udp = (struct wolfIP_udp_datagram *)buf; + ip4 local_ip = 0x0A000001U; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + ts->src_port = 1234; + ts->local_ip = local_ip; + + memset(buf, 0, sizeof(buf)); + udp->ip.src = ee32(0x0A000002U); + udp->ip.dst = ee32(local_ip); + udp->ip.ver_ihl = 0x45; + udp->ip.ttl = 64; + udp->ip.proto = WI_IPPROTO_UDP; + udp->ip.len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN + 4); + udp->src_port = ee16(9999); + udp->dst_port = ee16(1234); + udp->len = ee16(UDP_HEADER_LEN + 4); + udp->csum = ee16(0xABCD); /* wrong */ + udp_try_recv(&s, TEST_PRIMARY_IF, udp, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + 4)); + ck_assert_ptr_eq(fifo_peek(&ts->sock.udp.rxbuf), NULL); +} +END_TEST + +START_TEST(test_udp_try_recv_filter_blocked) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + uint8_t buf[sizeof(struct wolfIP_udp_datagram) + 4]; + struct wolfIP_udp_datagram *udp = (struct wolfIP_udp_datagram *)buf; + ip4 local_ip = 0x0A000001U; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + ts->src_port = 1234; + ts->local_ip = local_ip; + + memset(buf, 0, sizeof(buf)); + udp->ip.src = ee32(0x0A000002U); + udp->ip.dst = ee32(local_ip); + udp->ip.ver_ihl = 0x45; + udp->ip.ttl = 64; + udp->ip.proto = WI_IPPROTO_UDP; + udp->ip.len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN + 4); + udp->src_port = ee16(9999); + udp->dst_port = ee16(1234); + udp->len = ee16(UDP_HEADER_LEN + 4); + fix_udp_checksums(udp); + + filter_block_reason = WOLFIP_FILT_RECEIVING; + filter_block_calls = 0; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_udp_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_RECEIVING)); + udp_try_recv(&s, TEST_PRIMARY_IF, udp, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + 4)); + ck_assert_ptr_eq(fifo_peek(&ts->sock.udp.rxbuf), NULL); + ck_assert_int_gt(filter_block_calls, 0); + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_udp_mask(0); +} +END_TEST + +/* ---- mcast_if_from_addr error edges ---- */ + +#ifdef IP_MULTICAST +START_TEST(test_mcast_if_from_addr_validation) +{ + struct wolfIP s; + unsigned int if_idx = 0xFF; + wolfIP_init(&s); + mock_link_init(&s); + /* Non-multicast group rejected. */ + ck_assert_int_eq(mcast_if_from_addr(&s, IPADDR_ANY, 0x0A000001U, &if_idx), + -WOLFIP_EINVAL); + /* NULL if_idx pointer rejected. */ + ck_assert_int_eq(mcast_if_from_addr(&s, IPADDR_ANY, 0xE9010220U, NULL), + -WOLFIP_EINVAL); + /* Address that doesn't match any interface fails. */ + ck_assert_int_eq(mcast_if_from_addr(&s, 0xDEADBEEFU, 0xE9010220U, &if_idx), + -WOLFIP_EINVAL); +} +END_TEST +#endif /* IP_MULTICAST */ + +/* ---- ip_output_add_header / ip_recv coverage via direct unicast UDP send-and-receive ---- */ + +START_TEST(test_udp_send_and_receive_through_poll) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + uint8_t buf[8] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80}; + uint8_t rxbuf[8]; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + uint8_t mac[6] = {0x02, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + s.arp.neighbors[0].ip = remote_ip; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + s.arp.neighbors[0].ts = 1; + memcpy(s.arp.neighbors[0].mac, mac, 6); + s.last_tick = 1; + + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(local_ip & 0xFFFFU); + sin.sin_addr.s_addr = ee32(local_ip); + ck_assert_int_eq(wolfIP_sock_bind(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + + sin.sin_port = ee16(7000); + sin.sin_addr.s_addr = ee32(remote_ip); + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, buf, sizeof(buf), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(buf)); + last_frame_sent_size = 0; + ck_assert_int_eq(wolfIP_poll(&s, 2), 0); + ck_assert_uint_gt(last_frame_sent_size, 0U); + ck_assert_uint_eq(fifo_len(&ts->sock.udp.txbuf), 0U); + + /* Loop the frame back as if received and verify recvfrom returns it. */ + inject_udp_datagram(&s, TEST_PRIMARY_IF, remote_ip, local_ip, 7000, + local_ip & 0xFFFFU, buf, sizeof(buf)); + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, udp_sd, rxbuf, sizeof(rxbuf), + 0, NULL, NULL), (int)sizeof(buf)); + ck_assert_mem_eq(rxbuf, buf, sizeof(buf)); +} +END_TEST + +/* ---- wolfIP_sock_getsockname / wolfIP_sock_getpeername extras ---- */ + +START_TEST(test_sock_getsockname_udp_success) +{ + struct wolfIP s; + int udp_sd; + struct wolfIP_sockaddr_in sin; + struct wolfIP_sockaddr_in out; + socklen_t len = sizeof(out); + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(1234); + sin.sin_addr.s_addr = ee32(0x0A000001U); + ck_assert_int_eq(wolfIP_sock_bind(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + memset(&out, 0, sizeof(out)); + ck_assert_int_eq(wolfIP_sock_getsockname(&s, udp_sd, + (struct wolfIP_sockaddr *)&out, &len), 0); + ck_assert_uint_eq(out.sin_family, AF_INET); + ck_assert_uint_eq(out.sin_port, ee16(1234)); +} +END_TEST + +START_TEST(test_sock_getpeername_negative_sockfd) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in out; + socklen_t len = sizeof(out); + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_getpeername(&s, -2, + (struct wolfIP_sockaddr *)&out, &len), -WOLFIP_EINVAL); + /* Marked but otherwise valid: still -1 in trimmed scope. */ + ck_assert_int_eq(wolfIP_sock_getpeername(&s, MARK_UDP_SOCKET | 0, + (struct wolfIP_sockaddr *)&out, &len), -1); +} +END_TEST + +/* ---- arp_request rate-limit / arp_lookup non-existent ---- */ + +START_TEST(test_arp_lookup_missing_returns_neg1) +{ + struct wolfIP s; + uint8_t out[6]; + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(arp_lookup(&s, TEST_PRIMARY_IF, 0xDEADBEEFU, out), -1); +} +END_TEST + +/* ---- fifo_can_push_len wrap-state branches ---- */ + +START_TEST(test_fifo_can_push_len_validation) +{ + struct fifo f; + uint8_t data[128]; + + fifo_init(&f, data, sizeof(data)); + /* NULL fifo */ + ck_assert_int_eq(fifo_can_push_len(NULL, 4), 0); + /* Length larger than the FIFO's total capacity */ + ck_assert_int_eq(fifo_can_push_len(&f, sizeof(data) + 1), 0); + /* Valid trivial push */ + ck_assert_int_eq(fifo_can_push_len(&f, 4), 1); +} +END_TEST + +START_TEST(test_fifo_can_push_len_full_no_wrap_returns_zero) +{ + struct fifo f; + uint8_t data[64]; + uint8_t payload[4] = {0}; + /* Two pkt_desc(16)+payload(4)=20-byte slots fit 3 times in 64 bytes + * (60 used, 4 unused). Use a smaller capacity to make full easy. */ + fifo_init(&f, data, sizeof(data)); + /* Force full: head == tail with no wrap means empty; we want the + * "head == tail && h_wrap != 0" full-state branch. */ + f.head = 4; + f.tail = 4; + f.h_wrap = sizeof(data); + ck_assert_int_eq(fifo_can_push_len(&f, sizeof(payload)), 0); +} +END_TEST + +START_TEST(test_fifo_can_push_len_head_wraps_when_h_wrap_matches) +{ + struct fifo f; + uint8_t data[64]; + + fifo_init(&f, data, sizeof(data)); + /* Simulate state where head sits exactly at h_wrap so the function + * wraps head to 0 before testing space. */ + f.tail = 16; + f.head = 48; + f.h_wrap = 48; + /* Now head == h_wrap; head should be wrapped to 0, then needed (4+16=20) + * compared against tail==16. 0+20 > 16 -> not enough -> 0. */ + ck_assert_int_eq(fifo_can_push_len(&f, 4), 0); +} +END_TEST + +START_TEST(test_fifo_can_push_len_end_space_insufficient_then_wraps) +{ + struct fifo f; + uint8_t data[64]; + + fifo_init(&f, data, sizeof(data)); + f.tail = 32; + f.head = 56; + f.h_wrap = 0; + /* end_space = 64 - 56 = 8 bytes; pkt_desc(16)+4 = 20 needed. + * end_space < needed: requires wrap. tail(32) >= needed(20) so wrap path + * succeeds and returns 1. */ + ck_assert_int_eq(fifo_can_push_len(&f, 4), 1); +} +END_TEST + +START_TEST(test_fifo_can_push_len_end_space_insufficient_tail_too_close) +{ + struct fifo f; + uint8_t data[64]; + + fifo_init(&f, data, sizeof(data)); + f.tail = 4; + f.head = 56; + f.h_wrap = 0; + /* end_space = 8 < needed=20 and tail=4 < needed=20 -> return 0. */ + ck_assert_int_eq(fifo_can_push_len(&f, 4), 0); +} +END_TEST + +START_TEST(test_fifo_can_push_len_h_wrap_insufficient_space) +{ + struct fifo f; + uint8_t data[64]; + + fifo_init(&f, data, sizeof(data)); + /* h_wrap state with head return 0. */ + ck_assert_int_eq(fifo_can_push_len(&f, 4), 0); +} +END_TEST + +START_TEST(test_fifo_can_push_len_h_wrap_head_ge_tail_no_space) +{ + struct fifo f; + uint8_t data[64]; + + fifo_init(&f, data, sizeof(data)); + /* h_wrap set but head >= tail (degenerate state) -> space = 0 -> return 0. */ + f.tail = 8; + f.head = 16; + f.h_wrap = 48; + ck_assert_int_eq(fifo_can_push_len(&f, 4), 0); +} +END_TEST + +/* ---- fifo_push insufficient space ---- */ + +START_TEST(test_fifo_push_returns_minus_one_when_full) +{ + struct fifo f; + uint8_t data[64]; + uint8_t payload[8]; + + fifo_init(&f, data, sizeof(data)); + memset(payload, 0xAB, sizeof(payload)); + /* Fill the FIFO (2 * (16+8) = 48 bytes). */ + ck_assert_int_eq(fifo_push(&f, payload, sizeof(payload)), 0); + ck_assert_int_eq(fifo_push(&f, payload, sizeof(payload)), 0); + /* Third push would exceed capacity: 48 + 24 = 72 > 64. */ + ck_assert_int_lt(fifo_push(&f, payload, sizeof(payload)), 0); +} +END_TEST + +/* ---- wolfIP_route_for_ip gateway fallback ---- */ + +START_TEST(test_route_for_ip_gateway_fallback) +{ + struct wolfIP s; + unsigned int if_idx; + wolfIP_init(&s); + mock_link_init(&s); + /* Configure primary with a gateway so non-local destinations route there. */ + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0x0A000002U); + /* Destination outside the configured subnet -> use gw_fallback path. */ + if_idx = wolfIP_route_for_ip(&s, 0x14000001U); + ck_assert_uint_eq(if_idx, TEST_PRIMARY_IF); + /* Destination IPADDR_ANY -> default_if (loopback-aware). */ + if_idx = wolfIP_route_for_ip(&s, IPADDR_ANY); + ck_assert_uint_eq(if_idx, WOLFIP_PRIMARY_IF_IDX); +} +END_TEST + +START_TEST(test_route_for_ip_first_non_loop_fallback) +{ + struct wolfIP s; + unsigned int if_idx; + wolfIP_init(&s); + mock_link_init(&s); + /* Set a primary IP but no gateway and destination outside subnet: + * has_gw_fallback false, has_non_loop true -> returns first_non_loop. */ + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + if_idx = wolfIP_route_for_ip(&s, 0x14000001U); + ck_assert_uint_eq(if_idx, TEST_PRIMARY_IF); +} +END_TEST + +/* ---- wolfIP_sock_sendto: TX buf full -> EAGAIN ---- */ + +START_TEST(test_sendto_udp_txbuf_full_eagain) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + uint8_t buf[512]; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(9000); + sin.sin_addr.s_addr = ee32(0x0A000002U); + memset(buf, 0xCC, sizeof(buf)); + + /* Push enough datagrams to overflow the txbuf. Each push reserves + * sizeof(pkt_desc)+sizeof(wolfIP_udp_datagram)+len bytes. */ + while (1) { + int r = wolfIP_sock_sendto(&s, udp_sd, buf, sizeof(buf), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)); + if (r != (int)sizeof(buf)) + break; + } + /* Final attempt returns -EAGAIN because txbuf is full. */ + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, buf, sizeof(buf), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -WOLFIP_EAGAIN); + (void)ts; +} +END_TEST + +/* ---- wolfIP_sock_sendto: bound_local_ip mismatch on ICMP ---- */ + +START_TEST(test_sendto_icmp_no_remote_after_addr_zero) +{ + struct wolfIP s; + int icmp_sd; + struct wolfIP_sockaddr_in sin; + uint8_t payload[ICMP_HEADER_LEN] = {0}; + + wolfIP_init(&s); + mock_link_init(&s); + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = 0; /* will set ts->remote_ip = 0 -> reject */ + ck_assert_int_eq(wolfIP_sock_sendto(&s, icmp_sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -1); +} +END_TEST + +/* ---- ip_recv with IP options (options-stripping path) ---- */ + +START_TEST(test_ip_recv_with_ip_options_strips_and_dispatches) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + uint8_t frame[ETH_HEADER_LEN + 24 /* IP+4 opts */ + UDP_HEADER_LEN + 4]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + uint8_t *udp_hdr; + ip4 local_ip = 0x0A000001U; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(5000); + ck_assert_int_eq(wolfIP_sock_bind(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + + memset(frame, 0, sizeof(frame)); + { + struct wolfIP_ll_dev *ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + memcpy(ip->eth.dst, ll->mac, 6); + } + memcpy(ip->eth.src, "\x10\x20\x30\x40\x50\x60", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + /* IHL=6 -> 24-byte IP header (4 bytes of options) */ + ip->ver_ihl = 0x46; + ip->ttl = 32; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(24 + UDP_HEADER_LEN + 4); + ip->src = ee32(0x0A000002U); + ip->dst = ee32(local_ip); + /* 4 bytes of option = NOP NOP NOP END */ + ip->data[0] = 0x01; + ip->data[1] = 0x01; + ip->data[2] = 0x01; + ip->data[3] = 0x00; + fix_ip_checksum_with_hlen(ip, 24); + + udp_hdr = frame + ETH_HEADER_LEN + 24; + { + uint16_t sp = ee16(4000); + uint16_t dp = ee16(5000); + uint16_t ulen = ee16(UDP_HEADER_LEN + 4); + memcpy(udp_hdr + 0, &sp, 2); + memcpy(udp_hdr + 2, &dp, 2); + memcpy(udp_hdr + 4, &ulen, 2); + memset(udp_hdr + 6, 0, 2); /* csum 0 = no validation */ + memcpy(udp_hdr + UDP_HEADER_LEN, "abcd", 4); + fix_udp_checksum_raw(ip, udp_hdr, UDP_HEADER_LEN + 4); + } + + wolfIP_recv_ex(&s, TEST_PRIMARY_IF, frame, sizeof(frame)); + /* After options-stripping the datagram should land in the UDP queue. */ + ck_assert_uint_gt(fifo_len(&ts->sock.udp.rxbuf), 0U); +} +END_TEST + +START_TEST(test_ip_recv_wrong_version_dropped) +{ + struct wolfIP s; + struct wolfIP_ip_packet ip; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + memset(&ip, 0, sizeof(ip)); + ip.ver_ihl = 0x65; /* version=6 */ + ip.ttl = 64; + ip.len = ee16(IP_HEADER_LEN); + ip.src = ee32(0x0A000002U); + ip.dst = ee32(0x0A000001U); + last_frame_sent_size = 0; + ip_recv(&s, TEST_PRIMARY_IF, &ip, sizeof(ip)); + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + +START_TEST(test_ip_recv_short_ihl_dropped) +{ + struct wolfIP s; + struct wolfIP_ip_packet ip; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + memset(&ip, 0, sizeof(ip)); + ip.ver_ihl = 0x44; /* IHL=4 -> 16 bytes, below IP_HEADER_LEN (20) */ + ip.ttl = 64; + ip.len = ee16(IP_HEADER_LEN); + last_frame_sent_size = 0; + ip_recv(&s, TEST_PRIMARY_IF, &ip, sizeof(ip)); + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + +START_TEST(test_ip_recv_fragment_dropped) +{ + struct wolfIP s; + struct wolfIP_ip_packet ip; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + memset(&ip, 0, sizeof(ip)); + ip.ver_ihl = 0x45; + ip.ttl = 64; + ip.flags_fo = ee16(0x2000); /* MF flag set */ + ip.len = ee16(IP_HEADER_LEN); + ip.src = ee32(0x0A000002U); + ip.dst = ee32(0x0A000001U); + fix_ip_checksum(&ip); + last_frame_sent_size = 0; + ip_recv(&s, TEST_PRIMARY_IF, &ip, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN)); + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + +START_TEST(test_ip_recv_bad_header_checksum_dropped) +{ + struct wolfIP s; + struct wolfIP_ip_packet ip; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + memset(&ip, 0, sizeof(ip)); + ip.ver_ihl = 0x45; + ip.ttl = 64; + ip.len = ee16(IP_HEADER_LEN); + ip.src = ee32(0x0A000002U); + ip.dst = ee32(0x0A000001U); + ip.csum = ee16(0x1234); /* wrong */ + last_frame_sent_size = 0; + ip_recv(&s, TEST_PRIMARY_IF, &ip, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN)); + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + +/* ---- arp_recv extras ---- */ + +START_TEST(test_arp_recv_short_frame_dropped) +{ + struct wolfIP s; + uint8_t buf[8] = {0}; + wolfIP_init(&s); + mock_link_init(&s); + /* Short ARP frame is dropped; no neighbor learned. */ + arp_recv(&s, TEST_PRIMARY_IF, buf, sizeof(buf)); + ck_assert_uint_eq(s.arp.neighbors[0].ip, IPADDR_ANY); +} +END_TEST + +START_TEST(test_arp_recv_request_for_other_ip_ignored) +{ + struct wolfIP s; + struct arp_packet req; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + memset(&req, 0, sizeof(req)); + req.htype = ee16(1); + req.ptype = ee16(0x0800); + req.hlen = 6; + req.plen = 4; + req.opcode = ee16(ARP_REQUEST); + req.sip = ee32(0x0A000002U); + req.tip = ee32(0x0A0000AAU); /* not our IP */ + memcpy(req.sma, "\x10\x20\x30\x40\x50\x60", 6); + last_frame_sent_size = 0; + arp_recv(&s, TEST_PRIMARY_IF, &req, sizeof(req)); + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + +START_TEST(test_arp_recv_request_refreshes_existing_neighbor) +{ + struct wolfIP s; + struct arp_packet req; + uint8_t sender_mac[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60}; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + s.arp.neighbors[0].ts = 100; + memcpy(s.arp.neighbors[0].mac, sender_mac, 6); + s.last_tick = 5000; + + memset(&req, 0, sizeof(req)); + req.htype = ee16(1); + req.ptype = ee16(0x0800); + req.hlen = 6; + req.plen = 4; + req.opcode = ee16(ARP_REQUEST); + req.sip = ee32(0x0A000002U); + req.tip = ee32(0x0A000001U); + memcpy(req.sma, sender_mac, 6); + arp_recv(&s, TEST_PRIMARY_IF, &req, sizeof(req)); + /* Existing neighbor with matching MAC: ts refreshed. */ + ck_assert_uint_eq(s.arp.neighbors[0].ts, 5000U); +} +END_TEST + +START_TEST(test_arp_recv_reply_overwrite_blocked_when_no_pending) +{ + struct wolfIP s; + struct arp_packet reply; + uint8_t mac1[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60}; + uint8_t mac2[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + s.arp.neighbors[0].ts = 100; + memcpy(s.arp.neighbors[0].mac, mac1, 6); + + memset(&reply, 0, sizeof(reply)); + reply.htype = ee16(1); + reply.ptype = ee16(0x0800); + reply.hlen = 6; + reply.plen = 4; + reply.opcode = ee16(ARP_REPLY); + reply.sip = ee32(0x0A000002U); + memcpy(reply.sma, mac2, 6); + arp_recv(&s, TEST_PRIMARY_IF, &reply, sizeof(reply)); + /* Without a pending request the existing MAC must NOT be overwritten. */ + ck_assert_mem_eq(s.arp.neighbors[0].mac, mac1, 6); +} +END_TEST + +/* ---- arp_request rate-limit branch ---- */ + +START_TEST(test_arp_request_rate_limited) +{ + struct wolfIP s; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + s.last_tick = 5000; + arp_request(&s, TEST_PRIMARY_IF, 0x0A000002U); + last_frame_sent_size = 0; + /* A second request within 1000 ticks is suppressed. */ + s.last_tick = 5500; + arp_request(&s, TEST_PRIMARY_IF, 0x0A000003U); + ck_assert_uint_eq(last_frame_sent_size, 0U); + /* After the rate-limit window elapses, the request fires again. */ + s.last_tick = 6500; + arp_request(&s, TEST_PRIMARY_IF, 0x0A000003U); + ck_assert_uint_eq(last_frame_sent_size, sizeof(struct arp_packet)); +} +END_TEST + +/* ---- close_socket releases multicast memberships ---- */ + +#ifdef IP_MULTICAST +START_TEST(test_close_releases_multicast) +{ + struct wolfIP s; + int sd; + struct wolfIP_ip_mreq mreq; + ip4 group = 0xE901020FU; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(sd, 0); + memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr.s_addr = ee32(group); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP, + WOLFIP_IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)), 0); + ck_assert_uint_eq(s.mcast[0].refs, 1U); + /* Close should drop the membership and emit the leave report. */ + last_frame_sent_size = 0; + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); + ck_assert_uint_eq(s.mcast[0].refs, 0U); + ck_assert_uint_gt(last_frame_sent_size, 0U); +} +END_TEST +#endif /* IP_MULTICAST */ + +/* ---- wolfIP_send_ttl_exceeded / wolfIP_send_port_unreachable filter dispatch ---- */ + +START_TEST(test_send_port_unreachable_filter_blocked_at_eth) +{ + struct wolfIP s; + uint8_t buf[sizeof(struct wolfIP_udp_datagram) + 4]; + struct wolfIP_udp_datagram *udp = (struct wolfIP_udp_datagram *)buf; + uint32_t local_ip = 0x0A000001U; + uint32_t remote_ip = 0x0A000002U; + uint8_t src_mac[6] = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + memset(buf, 0, sizeof(buf)); + memcpy(udp->ip.eth.src, src_mac, sizeof(src_mac)); + memcpy(udp->ip.eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + udp->ip.eth.type = ee16(ETH_TYPE_IP); + udp->ip.ver_ihl = 0x45; + udp->ip.ttl = 64; + udp->ip.proto = WI_IPPROTO_UDP; + udp->ip.len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN + 4); + udp->ip.src = ee32(remote_ip); + udp->ip.dst = ee32(local_ip); + udp->src_port = ee16(4321); + udp->dst_port = ee16(1234); + udp->len = ee16(UDP_HEADER_LEN + 4); + memcpy(udp->data, "test", 4); + fix_udp_checksums(udp); + + /* Block the outgoing ICMP-port-unreachable via the eth-level filter. */ + filter_block_reason = WOLFIP_FILT_SENDING; + filter_block_calls = 0; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_eth_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_SENDING)); + last_frame_sent_size = 0; + udp_try_recv(&s, TEST_PRIMARY_IF, udp, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + 4)); + /* Filter blocked: no port-unreachable frame transmitted. */ + ck_assert_uint_eq(last_frame_sent_size, 0U); + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_eth_mask(0); +} +END_TEST + +START_TEST(test_send_ttl_exceeded_filter_blocked_at_icmp) +{ + struct wolfIP s; + uint8_t buf[ETH_HEADER_LEN + TTL_EXCEEDED_ORIG_PACKET_SIZE_MAX]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)buf; + ip4 primary_ip = 0x0A000001U; + ip4 secondary_ip = 0xC0A80101U; + + setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip); + + memset(buf, 0, sizeof(buf)); + ip->eth.type = ee16(ETH_TYPE_IP); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->ver_ihl = 0x45; + ip->ttl = 1; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + 8); + ip->src = ee32(primary_ip); + ip->dst = ee32(0xC0A80155U); + fix_ip_checksum(ip); + + /* Block the outgoing ICMP-TTL-exceeded via the icmp-level filter. */ + filter_block_reason = WOLFIP_FILT_SENDING; + filter_block_calls = 0; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_icmp_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_SENDING)); + last_frame_sent_size = 0; + ip_recv(&s, TEST_PRIMARY_IF, ip, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + 8)); + ck_assert_uint_eq(last_frame_sent_size, 0U); + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_icmp_mask(0); +} +END_TEST + +/* ---- udp_mcast_join error edges ---- */ + +#ifdef IP_MULTICAST +START_TEST(test_mcast_join_non_multicast_rejected_direct) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(sd)]; + /* Non-multicast group / null pointers rejected. */ + ck_assert_int_eq(udp_mcast_join(NULL, ts, 0xE9010220U, TEST_PRIMARY_IF), + -WOLFIP_EINVAL); + ck_assert_int_eq(udp_mcast_join(&s, NULL, 0xE9010220U, TEST_PRIMARY_IF), + -WOLFIP_EINVAL); + ck_assert_int_eq(udp_mcast_join(&s, ts, 0x0A000001U, TEST_PRIMARY_IF), + -WOLFIP_EINVAL); + /* if_idx out of range. */ + ck_assert_int_eq(udp_mcast_join(&s, ts, 0xE9010220U, WOLFIP_MAX_INTERFACES), + -WOLFIP_EINVAL); +} +END_TEST +#endif /* IP_MULTICAST */ + +#ifdef IP_MULTICAST +START_TEST(test_mcast_join_exhausts_socket_slots) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + unsigned int i; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(sd)]; + + /* Fill all per-socket membership slots. */ + for (i = 0; i < WOLFIP_UDP_MCAST_MEMBERSHIPS; i++) { + ck_assert_int_eq(udp_mcast_join(&s, ts, 0xE9010300U + i, TEST_PRIMARY_IF), + 0); + } + /* One more should return ENOMEM (slot exhaustion). */ + ck_assert_int_eq(udp_mcast_join(&s, ts, 0xE901031FU, TEST_PRIMARY_IF), + -WOLFIP_ENOMEM); +} +END_TEST +#endif /* IP_MULTICAST */ + +#ifdef IP_MULTICAST +START_TEST(test_mcast_drop_unjoined_returns_einval_direct) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(sd)]; + /* No join performed -> drop fails. */ + ck_assert_int_eq(udp_mcast_drop(&s, ts, 0xE9010221U, TEST_PRIMARY_IF), + -WOLFIP_EINVAL); +} +END_TEST +#endif /* IP_MULTICAST */ + +/* ---- arp_store_neighbor: existing entry refresh / full table ---- */ + +START_TEST(test_arp_store_neighbor_refresh_existing) +{ + struct wolfIP s; + uint8_t mac1[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60}; + uint8_t mac2[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + wolfIP_init(&s); + mock_link_init(&s); + s.last_tick = 100; + arp_store_neighbor(&s, TEST_PRIMARY_IF, 0x0A000002U, mac1); + ck_assert_mem_eq(s.arp.neighbors[0].mac, mac1, 6); + s.last_tick = 200; + /* Re-store same ip/if_idx -> mac is updated in place. */ + arp_store_neighbor(&s, TEST_PRIMARY_IF, 0x0A000002U, mac2); + ck_assert_mem_eq(s.arp.neighbors[0].mac, mac2, 6); + ck_assert_uint_eq(s.arp.neighbors[0].ts, 200U); +} +END_TEST + +START_TEST(test_arp_store_neighbor_full_table) +{ + struct wolfIP s; + uint8_t mac[6] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; + int i; + wolfIP_init(&s); + mock_link_init(&s); + for (i = 0; i < MAX_NEIGHBORS; i++) { + s.arp.neighbors[i].ip = 0x0A000010U + (uint32_t)i; + s.arp.neighbors[i].if_idx = TEST_PRIMARY_IF; + s.arp.neighbors[i].ts = 1; + memset(s.arp.neighbors[i].mac, (uint8_t)i, 6); + } + /* Table is full and no slot matches: store should silently fail. */ + arp_store_neighbor(&s, TEST_PRIMARY_IF, 0x0A0000A1U, mac); + /* Confirm none of the existing slots was overwritten. */ + for (i = 0; i < MAX_NEIGHBORS; i++) { + ck_assert_uint_ne(s.arp.neighbors[i].ip, 0x0A0000A1U); + } +} +END_TEST + +/* ---- arp_pending_record validation ---- */ + +START_TEST(test_arp_pending_record_rejects_null_and_oversized) +{ + struct wolfIP s; + struct wolfIP_ip_packet ip; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + memset(&ip, 0, sizeof(ip)); + /* NULL s */ + arp_queue_packet(NULL, TEST_PRIMARY_IF, 0x0A000050U, &ip, sizeof(ip)); + /* Zero len */ + arp_queue_packet(&s, TEST_PRIMARY_IF, 0x0A000050U, &ip, 0); + /* Length larger than MTU */ + arp_queue_packet(&s, TEST_PRIMARY_IF, 0x0A000050U, &ip, LINK_MTU * 2); + /* None of the above should have inserted into the queue. */ + ck_assert_uint_eq(s.arp_pending[0].dest, IPADDR_ANY); +} +END_TEST + +/* ---- wolfIP_sock_bind: ICMP filter rollback ---- */ + +START_TEST(test_sock_bind_icmp_filter_rolls_back) +{ + struct wolfIP s; + int icmp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + ts = &s.icmpsockets[SOCKET_UNMARK(icmp_sd)]; + + filter_block_reason = WOLFIP_FILT_BINDING; + filter_block_calls = 0; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_BINDING)); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(9); + sin.sin_addr.s_addr = ee32(IPADDR_ANY); + ck_assert_int_eq(wolfIP_sock_bind(&s, icmp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -1); + ck_assert_uint_eq(ts->src_port, 0U); + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_mask(0); +} +END_TEST + +/* ---- wolfIP_sock_setsockopt: int sized multicast TTL / LOOP ---- */ + +#ifdef IP_MULTICAST +START_TEST(test_setsockopt_multicast_ttl_int_path) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + int ttl = 17; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(sd)]; + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP, + WOLFIP_IP_MULTICAST_TTL, &ttl, sizeof(ttl)), 0); + ck_assert_uint_eq(ts->sock.udp.mcast_ttl, 17U); +} +END_TEST +#endif /* IP_MULTICAST */ + +#ifdef IP_MULTICAST +START_TEST(test_setsockopt_multicast_loop_int_and_uint8) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + int loop_i = 1; + uint8_t loop_b = 0; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(sd)]; + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP, + WOLFIP_IP_MULTICAST_LOOP, &loop_i, sizeof(loop_i)), 0); + ck_assert_uint_eq(ts->sock.udp.mcast_loop, 1U); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP, + WOLFIP_IP_MULTICAST_LOOP, &loop_b, sizeof(loop_b)), 0); + ck_assert_uint_eq(ts->sock.udp.mcast_loop, 0U); +} +END_TEST +#endif /* IP_MULTICAST */ + +#ifdef IP_MULTICAST +START_TEST(test_setsockopt_multicast_ttl_uint8_path) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + uint8_t ttl8 = 42; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(sd)]; + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP, + WOLFIP_IP_MULTICAST_TTL, &ttl8, sizeof(ttl8)), 0); + ck_assert_uint_eq(ts->sock.udp.mcast_ttl, 42U); +} +END_TEST +#endif /* IP_MULTICAST */ + +/* ---- icmp_input filter dispatch when icmp filter blocks reply send ---- */ + +START_TEST(test_icmp_input_echo_reply_path_filter_at_eth) +{ + struct wolfIP s; + struct wolfIP_icmp_packet icmp; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + memset(&icmp, 0, sizeof(icmp)); + memcpy(icmp.ip.eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + icmp.ip.src = ee32(0x0A000002U); + icmp.ip.dst = ee32(0x0A000001U); + icmp.ip.ttl = 64; + icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_HEADER_LEN); + icmp.type = ICMP_ECHO_REQUEST; + icmp.csum = ee16(icmp_checksum(&icmp, ICMP_HEADER_LEN)); + + /* Block at eth send level. */ + filter_block_reason = WOLFIP_FILT_SENDING; + filter_block_calls = 0; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_eth_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_SENDING)); + last_frame_sent_size = 0; + icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_HEADER_LEN)); + ck_assert_uint_eq(last_frame_sent_size, 0U); + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_eth_mask(0); +} +END_TEST + +/* ---- ip_recv with options too large drops ---- */ + +START_TEST(test_ip_recv_with_options_oversize_dropped) +{ + struct wolfIP s; + uint8_t buf[LINK_MTU + 100]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)buf; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + memset(buf, 0, sizeof(buf)); + ip->ver_ihl = 0x46; /* IHL=6, 24-byte hdr (one option word) */ + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + 4 + UDP_HEADER_LEN + 4); + ip->src = ee32(0x0A000002U); + ip->dst = ee32(0x0A000001U); + /* Pretend the frame size is larger than LINK_MTU to hit the + * "len > LINK_MTU -> return" guard in ip_recv. */ + fix_ip_checksum_with_hlen(ip, 24); + last_frame_sent_size = 0; + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)(LINK_MTU + 50)); + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + +/* ---- wolfIP_recv_on short ETH header ---- */ + +START_TEST(test_wolfip_recv_on_null_stack_returns) +{ + /* exercises the `!s` early return */ + uint8_t buf[ETH_HEADER_LEN + IP_HEADER_LEN]; + memset(buf, 0, sizeof(buf)); + wolfIP_recv_on(NULL, TEST_PRIMARY_IF, buf, sizeof(buf)); +} +END_TEST diff --git a/src/test/unit/unit_tests_dhcp_edges.c b/src/test/unit/unit_tests_dhcp_edges.c new file mode 100644 index 00000000..f5f5c82f --- /dev/null +++ b/src/test/unit/unit_tests_dhcp_edges.c @@ -0,0 +1,1075 @@ +/* unit_tests_dhcp_edges.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +/* Helper: build a minimal valid ACK with required fields and place it in msg. + * Sets xid from s->dhcp_xid. Caller may append/override options before use. + */ +static void build_dhcp_msg_base(struct wolfIP *s, struct dhcp_msg *msg, + uint8_t msg_type) +{ + struct dhcp_option *opt; + memset(msg, 0, sizeof(*msg)); + msg->op = BOOT_REPLY; + msg->magic = ee32(DHCP_MAGIC); + msg->xid = ee32(s->dhcp_xid); + opt = (struct dhcp_option *)msg->options; + opt->code = DHCP_OPTION_MSG_TYPE; + opt->len = 1; + opt->data[0] = msg_type; +} + +/* Append a 4-byte TLV option at *ptr and advance it. */ +static void append_opt4(uint8_t **ptr, uint8_t code, uint32_t val) +{ + struct dhcp_option *o = (struct dhcp_option *)*ptr; + o->code = code; + o->len = 4; + o->data[0] = (val >> 24) & 0xFF; + o->data[1] = (val >> 16) & 0xFF; + o->data[2] = (val >> 8) & 0xFF; + o->data[3] = (val >> 0) & 0xFF; + *ptr += 6; +} + +static void append_end(uint8_t **ptr) +{ + (*ptr)[0] = DHCP_OPTION_END; + (*ptr)++; +} + +/* Build a complete, valid ACK in msg with all required options. */ +static void build_full_ack(struct wolfIP *s, struct dhcp_msg *msg, + uint32_t server_ip, uint32_t client_ip, + uint32_t mask, uint32_t router, uint32_t dns, + uint32_t lease_s) +{ + uint8_t *p; + build_dhcp_msg_base(s, msg, DHCP_ACK); + p = (uint8_t *)msg->options + 3; /* after MSG_TYPE TLV */ + append_opt4(&p, DHCP_OPTION_SERVER_ID, server_ip); + append_opt4(&p, DHCP_OPTION_SUBNET_MASK, mask); + append_opt4(&p, DHCP_OPTION_ROUTER, router); + append_opt4(&p, DHCP_OPTION_DNS, dns); + append_opt4(&p, DHCP_OPTION_LEASE_TIME, lease_s); + if (client_ip) + append_opt4(&p, DHCP_OPTION_OFFER_IP, client_ip); + append_end(&p); +} + +/* ------------------------------------------------------------------------- + * dhcp_schedule_lease_timer — uncovered branches + * ---------------------------------------------------------------------- */ + +START_TEST(test_dhcp_schedule_lease_timer_zero_lease_noop) +{ + struct wolfIP s; + + wolfIP_init(&s); + s.last_tick = 1000U; + s.dhcp_timer = NO_TIMER; + + /* lease_s == 0 → early return; no timer should be scheduled */ + dhcp_schedule_lease_timer(&s, 0U, 0U, 0U); + + ck_assert_int_eq(s.dhcp_timer, NO_TIMER); +} +END_TEST + +START_TEST(test_dhcp_schedule_lease_timer_null_noop) +{ + /* NULL pointer → early return, no crash */ + dhcp_schedule_lease_timer(NULL, 120U, 0U, 0U); +} +END_TEST + +START_TEST(test_dhcp_schedule_lease_timer_renew_gt_lease_clamped) +{ + struct wolfIP s; + + wolfIP_init(&s); + s.last_tick = 0U; + + /* renew_s > lease_s → clamp renew to lease/2 */ + dhcp_schedule_lease_timer(&s, 100U, 200U, 0U); + + /* renew_s should be 50 (100/2), rebind_s = 87 (100*7/8) */ + ck_assert_uint_eq(s.dhcp_renew_at, 50000U); + ck_assert_uint_eq(s.dhcp_rebind_at, 87000U); + ck_assert_uint_eq(s.dhcp_lease_expires, 100000U); +} +END_TEST + +START_TEST(test_dhcp_schedule_lease_timer_rebind_lt_renew_fixed) +{ + struct wolfIP s; + + wolfIP_init(&s); + s.last_tick = 0U; + + /* rebind_s < renew_s → set rebind = renew */ + dhcp_schedule_lease_timer(&s, 100U, 80U, 20U); + + /* rebind_s (20) < renew_s (80), so rebind becomes 80 */ + ck_assert_uint_eq(s.dhcp_renew_at, 80000U); + ck_assert_uint_eq(s.dhcp_rebind_at, 80000U); +} +END_TEST + +START_TEST(test_dhcp_schedule_lease_timer_rebind_gt_lease_clamped) +{ + struct wolfIP s; + + wolfIP_init(&s); + s.last_tick = 0U; + + /* Provide explicit rebind > lease; per code the rebind branch resets it + * to (lease*7)/8 = 87, then 87 > renew_s(50) OK, 87 <= lease_s(100) OK */ + dhcp_schedule_lease_timer(&s, 100U, 50U, 150U); + + /* rebind_s (150) > lease_s (100): reset to (100*7)/8 = 87 */ + ck_assert_uint_eq(s.dhcp_rebind_at, 87000U); + ck_assert_uint_eq(s.dhcp_lease_expires, 100000U); +} +END_TEST + +START_TEST(test_dhcp_schedule_lease_timer_explicit_t1_t2) +{ + struct wolfIP s; + + wolfIP_init(&s); + s.last_tick = 2000U; + + /* All three values supplied and valid */ + dhcp_schedule_lease_timer(&s, 3600U, 1800U, 3150U); + + ck_assert_uint_eq(s.dhcp_renew_at, 2000U + 1800U * 1000U); + ck_assert_uint_eq(s.dhcp_rebind_at, 2000U + 3150U * 1000U); + ck_assert_uint_eq(s.dhcp_lease_expires, 2000U + 3600U * 1000U); + ck_assert_int_ne(s.dhcp_timer, NO_TIMER); +} +END_TEST + +/* ------------------------------------------------------------------------- + * dhcp_msg_type — return each message type and validate + * ---------------------------------------------------------------------- */ + +START_TEST(test_dhcp_msg_type_returns_offer) +{ + struct wolfIP s; + struct dhcp_msg msg; + + wolfIP_init(&s); + s.dhcp_xid = 0xDEADBEEFU; + + build_dhcp_msg_base(&s, &msg, DHCP_OFFER); + /* no further options needed for msg_type */ + ((uint8_t *)msg.options)[3] = DHCP_OPTION_END; + + ck_assert_int_eq(dhcp_msg_type(&s, &msg, sizeof(msg)), DHCP_OFFER); +} +END_TEST + +START_TEST(test_dhcp_msg_type_returns_nak) +{ + struct wolfIP s; + struct dhcp_msg msg; + + wolfIP_init(&s); + s.dhcp_xid = 0xDEADBEEFU; + + build_dhcp_msg_base(&s, &msg, DHCP_NAK); + ((uint8_t *)msg.options)[3] = DHCP_OPTION_END; + + ck_assert_int_eq(dhcp_msg_type(&s, &msg, sizeof(msg)), DHCP_NAK); +} +END_TEST + +START_TEST(test_dhcp_msg_type_returns_ack) +{ + struct wolfIP s; + struct dhcp_msg msg; + + wolfIP_init(&s); + s.dhcp_xid = 0xABCD1234U; + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + ((uint8_t *)msg.options)[3] = DHCP_OPTION_END; + + ck_assert_int_eq(dhcp_msg_type(&s, &msg, sizeof(msg)), DHCP_ACK); +} +END_TEST + +START_TEST(test_dhcp_msg_type_returns_request) +{ + struct wolfIP s; + struct dhcp_msg msg; + + wolfIP_init(&s); + s.dhcp_xid = 0x11223344U; + + build_dhcp_msg_base(&s, &msg, DHCP_REQUEST); + ((uint8_t *)msg.options)[3] = DHCP_OPTION_END; + + ck_assert_int_eq(dhcp_msg_type(&s, &msg, sizeof(msg)), DHCP_REQUEST); +} +END_TEST + +START_TEST(test_dhcp_msg_type_returns_discover) +{ + struct wolfIP s; + struct dhcp_msg msg; + + wolfIP_init(&s); + s.dhcp_xid = 0x55667788U; + + build_dhcp_msg_base(&s, &msg, DHCP_DISCOVER); + ((uint8_t *)msg.options)[3] = DHCP_OPTION_END; + + ck_assert_int_eq(dhcp_msg_type(&s, &msg, sizeof(msg)), DHCP_DISCOVER); +} +END_TEST + +START_TEST(test_dhcp_msg_type_bad_xid_returns_neg1) +{ + struct wolfIP s; + struct dhcp_msg msg; + + wolfIP_init(&s); + s.dhcp_xid = 0x11111111U; + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + msg.xid = ee32(0x22222222U); /* mismatch */ + ((uint8_t *)msg.options)[3] = DHCP_OPTION_END; + + ck_assert_int_eq(dhcp_msg_type(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_msg_type_bad_magic_returns_neg1) +{ + struct wolfIP s; + struct dhcp_msg msg; + + wolfIP_init(&s); + s.dhcp_xid = 0x11111111U; + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + msg.magic = ee32(0xDEADBEEFU); /* corrupt magic */ + ((uint8_t *)msg.options)[3] = DHCP_OPTION_END; + + ck_assert_int_eq(dhcp_msg_type(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_msg_type_boot_request_returns_neg1) +{ + struct wolfIP s; + struct dhcp_msg msg; + + wolfIP_init(&s); + s.dhcp_xid = 0x11111111U; + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + msg.op = BOOT_REQUEST; /* not a reply */ + + ck_assert_int_eq(dhcp_msg_type(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_msg_type_len_ne_1_not_returned) +{ + struct wolfIP s; + struct dhcp_msg msg; + uint8_t *p; + + wolfIP_init(&s); + s.dhcp_xid = 0x12345678U; + + memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; + msg.magic = ee32(DHCP_MAGIC); + msg.xid = ee32(s.dhcp_xid); + p = (uint8_t *)msg.options; + /* MSG_TYPE with len=2 (not 1) — should NOT return the type */ + p[0] = DHCP_OPTION_MSG_TYPE; + p[1] = 2; + p[2] = DHCP_ACK; + p[3] = 0x00; + p += 4; + p[0] = DHCP_OPTION_END; + + /* len != 1 means the branch `code == DHCP_OPTION_MSG_TYPE && len == 1` + * is false; the option is skipped and -1 is returned */ + ck_assert_int_eq(dhcp_msg_type(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_msg_type_pad_bytes_skipped) +{ + struct wolfIP s; + struct dhcp_msg msg; + uint8_t *p; + + wolfIP_init(&s); + s.dhcp_xid = 0xBEEF0001U; + + memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; + msg.magic = ee32(DHCP_MAGIC); + msg.xid = ee32(s.dhcp_xid); + p = (uint8_t *)msg.options; + /* two pad bytes (code=0) then MSG_TYPE */ + p[0] = 0; + p[1] = 0; + p[2] = DHCP_OPTION_MSG_TYPE; + p[3] = 1; + p[4] = DHCP_NAK; + p[5] = DHCP_OPTION_END; + + ck_assert_int_eq(dhcp_msg_type(&s, &msg, sizeof(msg)), DHCP_NAK); +} +END_TEST + +START_TEST(test_dhcp_msg_type_truncated_option_returns_neg1) +{ + struct wolfIP s; + struct dhcp_msg msg; + uint8_t *p; + + wolfIP_init(&s); + s.dhcp_xid = 0x12340000U; + + memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; + msg.magic = ee32(DHCP_MAGIC); + msg.xid = ee32(s.dhcp_xid); + p = (uint8_t *)msg.options; + /* option says len=10 but we only give 3 bytes total → opt+2+len > opt_end */ + p[0] = DHCP_OPTION_SERVER_ID; + p[1] = 10; + p[2] = 0; + + /* only DHCP_HEADER_LEN + 3 bytes of options → truncated */ + ck_assert_int_eq(dhcp_msg_type(&s, &msg, DHCP_HEADER_LEN + 3), -1); +} +END_TEST + +/* ------------------------------------------------------------------------- + * dhcp_parse_offer — additional branches + * ---------------------------------------------------------------------- */ + +START_TEST(test_dhcp_parse_offer_type_ack_not_offer_rejected) +{ + struct wolfIP s; + struct dhcp_msg msg; + uint8_t *p; + + wolfIP_init(&s); + s.dhcp_xid = 0xCAFEBABEU; + + memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; + msg.magic = ee32(DHCP_MAGIC); + msg.xid = ee32(s.dhcp_xid); + p = (uint8_t *)msg.options; + /* Type = ACK (not OFFER) → inner body should not be entered. + * After advancing past MSG_TYPE we have no DHCP_OFFER match, so + * execution falls through to the outer saw_end check. */ + p[0] = DHCP_OPTION_MSG_TYPE; + p[1] = 1; + p[2] = DHCP_ACK; + p += 3; + p[0] = DHCP_OPTION_END; + + /* saw_end=1 after the END but no dhcp_ip/server set → -1 */ + ck_assert_int_eq(dhcp_parse_offer(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_parse_offer_subnet_mask_len_lt4_rejected) +{ + struct wolfIP s; + struct dhcp_msg msg; + uint8_t *p; + + wolfIP_init(&s); + s.dhcp_xid = 0x1001U; + + memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; + msg.magic = ee32(DHCP_MAGIC); + msg.xid = ee32(s.dhcp_xid); + msg.yiaddr = ee32(0x0A000064U); + p = (uint8_t *)msg.options; + p[0] = DHCP_OPTION_MSG_TYPE; p[1] = 1; p[2] = DHCP_OFFER; p += 3; + /* SERVER_ID valid */ + p[0] = DHCP_OPTION_SERVER_ID; p[1] = 4; + p[2] = 10; p[3] = 0; p[4] = 0; p[5] = 1; + p += 6; + /* SUBNET_MASK with len=2 < 4 → -1 */ + p[0] = DHCP_OPTION_SUBNET_MASK; p[1] = 2; p[2] = 0xFF; p[3] = 0xFF; + p += 4; + p[0] = DHCP_OPTION_END; + + ck_assert_int_eq(dhcp_parse_offer(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_parse_offer_inner_truncated_opt2_rejected) +{ + struct wolfIP s; + struct dhcp_msg msg; + uint8_t *p; + uint32_t msg_used; + + wolfIP_init(&s); + s.dhcp_xid = 0x2002U; + + memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; + msg.magic = ee32(DHCP_MAGIC); + msg.xid = ee32(s.dhcp_xid); + msg.yiaddr = ee32(0x0A000064U); + p = (uint8_t *)msg.options; + p[0] = DHCP_OPTION_MSG_TYPE; p[1] = 1; p[2] = DHCP_OFFER; p += 3; + /* One more byte: code only, no len byte → opt+2 > opt_end */ + p[0] = DHCP_OPTION_SERVER_ID; + p += 1; + msg_used = (uint32_t)(p - (uint8_t *)msg.options); + + ck_assert_int_eq(dhcp_parse_offer(&s, &msg, DHCP_HEADER_LEN + msg_used), -1); +} +END_TEST + +START_TEST(test_dhcp_parse_offer_inner_truncated_data_rejected) +{ + struct wolfIP s; + struct dhcp_msg msg; + uint8_t *p; + uint32_t msg_used; + + wolfIP_init(&s); + s.dhcp_xid = 0x3003U; + + memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; + msg.magic = ee32(DHCP_MAGIC); + msg.xid = ee32(s.dhcp_xid); + msg.yiaddr = ee32(0x0A000064U); + p = (uint8_t *)msg.options; + p[0] = DHCP_OPTION_MSG_TYPE; p[1] = 1; p[2] = DHCP_OFFER; p += 3; + /* SERVER_ID says len=4 but only 2 data bytes follow → opt+2+len > opt_end */ + p[0] = DHCP_OPTION_SERVER_ID; p[1] = 4; p[2] = 10; p[3] = 0; + p += 4; + msg_used = (uint32_t)(p - (uint8_t *)msg.options); + + ck_assert_int_eq(dhcp_parse_offer(&s, &msg, DHCP_HEADER_LEN + msg_used), -1); +} +END_TEST + +START_TEST(test_dhcp_parse_offer_inner_pad_then_end) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct ipconf *primary; + uint8_t *p; + + wolfIP_init(&s); + s.dhcp_xid = 0x4004U; + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + + memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; + msg.magic = ee32(DHCP_MAGIC); + msg.xid = ee32(s.dhcp_xid); + msg.yiaddr = ee32(0x0A000064U); + p = (uint8_t *)msg.options; + p[0] = DHCP_OPTION_MSG_TYPE; p[1] = 1; p[2] = DHCP_OFFER; p += 3; + /* Pad byte (code=0) inside inner OFFER loop */ + p[0] = 0; p += 1; + /* SERVER_ID */ + p[0] = DHCP_OPTION_SERVER_ID; p[1] = 4; + p[2] = 10; p[3] = 0; p[4] = 0; p[5] = 1; + p += 6; + p[0] = DHCP_OPTION_END; + + ck_assert_int_eq(dhcp_parse_offer(&s, &msg, sizeof(msg)), 0); + ck_assert_uint_eq(s.dhcp_server_ip, 0x0A000001U); + ck_assert_uint_eq(s.dhcp_ip, 0x0A000064U); +} +END_TEST + +START_TEST(test_dhcp_parse_offer_outer_end_with_state_already_set) +{ + /* Covers the branch at line ~7487: + * saw_end=1 after outer END, dhcp_server_ip != 0 && dhcp_ip != 0 → 0 */ + struct wolfIP s; + struct dhcp_msg msg; + uint8_t *p; + + wolfIP_init(&s); + s.dhcp_xid = 0x5005U; + /* Pre-set as if an OFFER was already processed */ + s.dhcp_server_ip = 0x0A000001U; + s.dhcp_ip = 0x0A000064U; + + memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; + msg.magic = ee32(DHCP_MAGIC); + msg.xid = ee32(s.dhcp_xid); + p = (uint8_t *)msg.options; + /* MSG_TYPE = ACK (not OFFER) so inner body not entered; outer loop sees END */ + p[0] = DHCP_OPTION_MSG_TYPE; p[1] = 1; p[2] = DHCP_ACK; p += 3; + p[0] = DHCP_OPTION_END; + + /* outer saw_end=1, server_ip != 0, dhcp_ip != 0 → returns 0 */ + ck_assert_int_eq(dhcp_parse_offer(&s, &msg, sizeof(msg)), 0); + ck_assert_int_eq(s.dhcp_state, DHCP_REQUEST_SENT); +} +END_TEST + +/* ------------------------------------------------------------------------- + * dhcp_parse_ack — additional branches + * ---------------------------------------------------------------------- */ + +START_TEST(test_dhcp_parse_ack_mismatched_server_id_rejected) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct ipconf *primary; + + wolfIP_init(&s); + s.dhcp_xid = 0x1234U; + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + primary->ip = 0x0A000064U; + s.dhcp_server_ip = 0x0A000001U; /* committed to this server */ + + build_full_ack(&s, &msg, 0x0A000002U /* different server */, + 0x0A000064U, 0xFFFFFF00U, 0x0A000001U, 0x08080808U, 120U); + + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_parse_ack_server_id_len_lt4_rejected) +{ + struct wolfIP s; + struct dhcp_msg msg; + uint8_t *p; + + wolfIP_init(&s); + s.dhcp_xid = 0xABCDU; + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + p = (uint8_t *)msg.options + 3; + /* SERVER_ID len=2 < 4 → -1 */ + p[0] = DHCP_OPTION_SERVER_ID; p[1] = 2; p[2] = 10; p[3] = 0; + p += 4; + p[0] = DHCP_OPTION_END; + + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_parse_ack_offer_ip_len_lt4_rejected) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct ipconf *primary; + uint8_t *p; + uint32_t server_ip = 0x0A000001U; + + wolfIP_init(&s); + s.dhcp_xid = 0xBEEFU; + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + p = (uint8_t *)msg.options + 3; + append_opt4(&p, DHCP_OPTION_SERVER_ID, server_ip); + /* OFFER_IP with len=2 < 4 → -1 */ + p[0] = DHCP_OPTION_OFFER_IP; p[1] = 2; p[2] = 10; p[3] = 0; + p += 4; + append_end(&p); + + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_parse_ack_subnet_mask_len_lt4_rejected) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct ipconf *primary; + uint8_t *p; + uint32_t server_ip = 0x0A000001U; + + wolfIP_init(&s); + s.dhcp_xid = 0xCCCCU; + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + p = (uint8_t *)msg.options + 3; + append_opt4(&p, DHCP_OPTION_SERVER_ID, server_ip); + /* SUBNET_MASK with len=2 < 4 → -1 */ + p[0] = DHCP_OPTION_SUBNET_MASK; p[1] = 2; p[2] = 0xFF; p[3] = 0xFF; + p += 4; + append_end(&p); + + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_parse_ack_router_len_lt4_rejected) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct ipconf *primary; + uint8_t *p; + uint32_t server_ip = 0x0A000001U; + + wolfIP_init(&s); + s.dhcp_xid = 0xDDDDU; + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + p = (uint8_t *)msg.options + 3; + append_opt4(&p, DHCP_OPTION_SERVER_ID, server_ip); + append_opt4(&p, DHCP_OPTION_SUBNET_MASK, 0xFFFFFF00U); + /* ROUTER with len=2 < 4 → -1 */ + p[0] = DHCP_OPTION_ROUTER; p[1] = 2; p[2] = 10; p[3] = 0; + p += 4; + append_end(&p); + + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_parse_ack_dns_len_lt4_rejected) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct ipconf *primary; + uint8_t *p; + uint32_t server_ip = 0x0A000001U; + + wolfIP_init(&s); + s.dhcp_xid = 0xEEEEU; + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + s.dns_server = 0; /* allow DNS update */ + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + p = (uint8_t *)msg.options + 3; + append_opt4(&p, DHCP_OPTION_SERVER_ID, server_ip); + append_opt4(&p, DHCP_OPTION_SUBNET_MASK, 0xFFFFFF00U); + append_opt4(&p, DHCP_OPTION_ROUTER, 0x0A000001U); + /* DNS with len=2 < 4 → -1 */ + p[0] = DHCP_OPTION_DNS; p[1] = 2; p[2] = 8; p[3] = 8; + p += 4; + append_end(&p); + + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_parse_ack_lease_time_len_lt4_rejected) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct ipconf *primary; + uint8_t *p; + uint32_t server_ip = 0x0A000001U; + + wolfIP_init(&s); + s.dhcp_xid = 0xFF00U; + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + p = (uint8_t *)msg.options + 3; + append_opt4(&p, DHCP_OPTION_SERVER_ID, server_ip); + append_opt4(&p, DHCP_OPTION_SUBNET_MASK, 0xFFFFFF00U); + append_opt4(&p, DHCP_OPTION_ROUTER, 0x0A000001U); + /* LEASE_TIME with len=2 < 4 → -1 */ + p[0] = DHCP_OPTION_LEASE_TIME; p[1] = 2; p[2] = 0; p[3] = 60; + p += 4; + append_end(&p); + + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_parse_ack_renewal_time_len_lt4_rejected) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct ipconf *primary; + uint8_t *p; + uint32_t server_ip = 0x0A000001U; + + wolfIP_init(&s); + s.dhcp_xid = 0xAA11U; + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + p = (uint8_t *)msg.options + 3; + append_opt4(&p, DHCP_OPTION_SERVER_ID, server_ip); + append_opt4(&p, DHCP_OPTION_SUBNET_MASK, 0xFFFFFF00U); + append_opt4(&p, DHCP_OPTION_ROUTER, 0x0A000001U); + append_opt4(&p, DHCP_OPTION_LEASE_TIME, 3600U); + /* RENEWAL_TIME with len=2 < 4 → -1 */ + p[0] = DHCP_OPTION_RENEWAL_TIME; p[1] = 2; p[2] = 0; p[3] = 30; + p += 4; + append_end(&p); + + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_parse_ack_rebind_time_len_lt4_rejected) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct ipconf *primary; + uint8_t *p; + uint32_t server_ip = 0x0A000001U; + + wolfIP_init(&s); + s.dhcp_xid = 0xBB22U; + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + p = (uint8_t *)msg.options + 3; + append_opt4(&p, DHCP_OPTION_SERVER_ID, server_ip); + append_opt4(&p, DHCP_OPTION_SUBNET_MASK, 0xFFFFFF00U); + append_opt4(&p, DHCP_OPTION_ROUTER, 0x0A000001U); + append_opt4(&p, DHCP_OPTION_LEASE_TIME, 3600U); + append_opt4(&p, DHCP_OPTION_RENEWAL_TIME, 1800U); + /* REBIND_TIME with len=2 < 4 → -1 */ + p[0] = DHCP_OPTION_REBIND_TIME; p[1] = 2; p[2] = 0; p[3] = 45; + p += 4; + append_end(&p); + + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_parse_ack_no_ip_after_ack_rejected) +{ + /* saw_server_id=1, mask set, but primary->ip == 0 → -1 */ + struct wolfIP s; + struct dhcp_msg msg; + struct ipconf *primary; + uint8_t *p; + uint32_t server_ip = 0x0A000001U; + + wolfIP_init(&s); + s.dhcp_xid = 0xCC33U; + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + primary->ip = 0U; /* no IP set */ + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + p = (uint8_t *)msg.options + 3; + append_opt4(&p, DHCP_OPTION_SERVER_ID, server_ip); + append_opt4(&p, DHCP_OPTION_SUBNET_MASK, 0xFFFFFF00U); + append_opt4(&p, DHCP_OPTION_ROUTER, 0x0A000001U); + append_opt4(&p, DHCP_OPTION_LEASE_TIME, 120U); + append_end(&p); + + /* primary->ip == 0 → condition fails → -1 */ + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_parse_ack_no_mask_after_ack_rejected) +{ + /* saw_server_id=1, ip set, but primary->mask == 0 → -1 */ + struct wolfIP s; + struct dhcp_msg msg; + struct ipconf *primary; + uint8_t *p; + uint32_t server_ip = 0x0A000001U; + + wolfIP_init(&s); + s.dhcp_xid = 0xDD44U; + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + primary->ip = 0x0A000064U; + primary->mask = 0U; /* no mask */ + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + p = (uint8_t *)msg.options + 3; + append_opt4(&p, DHCP_OPTION_SERVER_ID, server_ip); + /* deliberately omit SUBNET_MASK so primary->mask stays 0 */ + append_opt4(&p, DHCP_OPTION_ROUTER, 0x0A000001U); + append_opt4(&p, DHCP_OPTION_LEASE_TIME, 120U); + append_end(&p); + + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), -1); +} +END_TEST + +START_TEST(test_dhcp_parse_ack_with_renewal_and_rebind_times) +{ + /* Happy path: ACK with T1 (renewal) and T2 (rebind) options set. */ + struct wolfIP s; + struct dhcp_msg msg; + struct ipconf *primary; + uint8_t *p; + uint32_t server_ip = 0x0A000001U; + uint32_t client_ip = 0x0A000064U; + + wolfIP_init(&s); + s.dhcp_xid = 0xEE55U; + s.last_tick = 0U; + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + primary->ip = client_ip; + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + p = (uint8_t *)msg.options + 3; + append_opt4(&p, DHCP_OPTION_SERVER_ID, server_ip); + append_opt4(&p, DHCP_OPTION_SUBNET_MASK, 0xFFFFFF00U); + append_opt4(&p, DHCP_OPTION_ROUTER, 0x0A000001U); + append_opt4(&p, DHCP_OPTION_LEASE_TIME, 3600U); + append_opt4(&p, DHCP_OPTION_RENEWAL_TIME, 1800U); + append_opt4(&p, DHCP_OPTION_REBIND_TIME, 3150U); + append_end(&p); + + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), 0); + ck_assert_int_eq(s.dhcp_state, DHCP_BOUND); + ck_assert_uint_eq(s.dhcp_renew_at, 1800000U); + ck_assert_uint_eq(s.dhcp_rebind_at, 3150000U); + ck_assert_uint_eq(s.dhcp_lease_expires, 3600000U); +} +END_TEST + +START_TEST(test_dhcp_parse_ack_dns_already_set_skipped) +{ + /* dns_server already set → DNS option branch not taken */ + struct wolfIP s; + struct dhcp_msg msg; + struct ipconf *primary; + uint8_t *p; + uint32_t server_ip = 0x0A000001U; + uint32_t client_ip = 0x0A000064U; + uint32_t old_dns = 0x01020304U; + + wolfIP_init(&s); + s.dhcp_xid = 0xFF66U; + s.dns_server = old_dns; /* already configured */ + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + primary->ip = client_ip; + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + p = (uint8_t *)msg.options + 3; + append_opt4(&p, DHCP_OPTION_SERVER_ID, server_ip); + append_opt4(&p, DHCP_OPTION_SUBNET_MASK, 0xFFFFFF00U); + append_opt4(&p, DHCP_OPTION_ROUTER, 0x0A000001U); + append_opt4(&p, DHCP_OPTION_DNS, 0x08080808U); /* different DNS */ + append_opt4(&p, DHCP_OPTION_LEASE_TIME, 120U); + append_end(&p); + + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), 0); + /* dns_server must not be overwritten */ + ck_assert_uint_eq(s.dns_server, old_dns); +} +END_TEST + +START_TEST(test_dhcp_parse_ack_inner_pad_bytes_skipped) +{ + /* Pad bytes (code=0) inside the ACK inner loop are skipped. */ + struct wolfIP s; + struct dhcp_msg msg; + struct ipconf *primary; + uint8_t *p; + uint32_t server_ip = 0x0A000001U; + uint32_t client_ip = 0x0A000064U; + + wolfIP_init(&s); + s.dhcp_xid = 0x1122U; + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + primary->ip = client_ip; + + build_dhcp_msg_base(&s, &msg, DHCP_ACK); + p = (uint8_t *)msg.options + 3; + /* pad byte */ + p[0] = 0; p += 1; + append_opt4(&p, DHCP_OPTION_SERVER_ID, server_ip); + append_opt4(&p, DHCP_OPTION_SUBNET_MASK, 0xFFFFFF00U); + append_opt4(&p, DHCP_OPTION_ROUTER, 0x0A000002U); + append_opt4(&p, DHCP_OPTION_LEASE_TIME, 120U); + append_end(&p); + + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), 0); + ck_assert_int_eq(s.dhcp_state, DHCP_BOUND); +} +END_TEST + +/* ------------------------------------------------------------------------- + * dhcp_timer_cb — additional branches + * ---------------------------------------------------------------------- */ + +START_TEST(test_dhcp_timer_cb_renewing_not_yet_rebind_sends_request) +{ + /* RENEWING but last_tick < dhcp_rebind_at → stays RENEWING, sends REQUEST */ + struct wolfIP s; + + wolfIP_init(&s); + mock_link_init(&s); + s.dhcp_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dhcp_udp_sd, 0); + s.dhcp_xid = 0xAAAA1234U; + wolfIP_ipconfig_set(&s, 0x0A000064U, 0xFFFFFF00U, 0x0A000001U); + s.dhcp_ip = 0x0A000064U; + s.dhcp_server_ip = 0x0A000001U; + s.last_tick = 1000U; + s.dhcp_rebind_at = 9999999U; /* far future */ + s.dhcp_state = DHCP_RENEWING; + s.dhcp_timeout_count = 0; + + last_frame_sent_size = 0; + dhcp_timer_cb(&s); + (void)wolfIP_poll(&s, s.last_tick); + + /* Must have sent a REQUEST and incremented counter */ + ck_assert_uint_gt(last_frame_sent_size, 0U); + ck_assert_int_eq(s.dhcp_timeout_count, 1); + ck_assert_int_eq(s.dhcp_state, DHCP_RENEWING); +} +END_TEST + +START_TEST(test_dhcp_timer_cb_renewing_past_rebind_transitions_to_rebinding) +{ + /* RENEWING with last_tick >= rebind_at → transitions to REBINDING */ + struct wolfIP s; + + wolfIP_init(&s); + mock_link_init(&s); + s.dhcp_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dhcp_udp_sd, 0); + s.dhcp_xid = 0xBBBB5678U; + wolfIP_ipconfig_set(&s, 0x0A000064U, 0xFFFFFF00U, 0x0A000001U); + s.dhcp_ip = 0x0A000064U; + s.dhcp_server_ip = 0x0A000001U; + s.last_tick = 5000U; + s.dhcp_rebind_at = 5000U; /* expired exactly */ + s.dhcp_state = DHCP_RENEWING; + s.dhcp_timeout_count = 2; + + dhcp_timer_cb(&s); + + ck_assert_int_eq(s.dhcp_state, DHCP_REBINDING); + ck_assert_uint_eq(s.dhcp_start_tick, 5000U); + /* After transitioning to REBINDING, dhcp_send_request is also called + * (count reset to 0 then incremented on success = 1) */ + ck_assert_uint_eq(s.dhcp_timeout_count, 1U); +} +END_TEST + +START_TEST(test_dhcp_timer_cb_rebinding_not_expired_sends_request) +{ + /* REBINDING, lease not expired → sends REQUEST */ + struct wolfIP s; + + wolfIP_init(&s); + mock_link_init(&s); + s.dhcp_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dhcp_udp_sd, 0); + s.dhcp_xid = 0xCCCC9ABCU; + wolfIP_ipconfig_set(&s, 0x0A000064U, 0xFFFFFF00U, 0x0A000001U); + s.dhcp_ip = 0x0A000064U; + s.dhcp_server_ip = 0x0A000001U; + s.last_tick = 1000U; + s.dhcp_lease_expires = 9999999U; /* far future */ + s.dhcp_state = DHCP_REBINDING; + s.dhcp_timeout_count = 0; + + last_frame_sent_size = 0; + dhcp_timer_cb(&s); + (void)wolfIP_poll(&s, s.last_tick); + + ck_assert_uint_gt(last_frame_sent_size, 0U); + ck_assert_int_eq(s.dhcp_timeout_count, 1); + ck_assert_int_eq(s.dhcp_state, DHCP_REBINDING); +} +END_TEST + +START_TEST(test_dhcp_timer_cb_bound_lease_not_expired_starts_renew) +{ + /* BOUND state, timer fires but lease has not expired → start RENEWING */ + struct wolfIP s; + + wolfIP_init(&s); + mock_link_init(&s); + s.dhcp_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dhcp_udp_sd, 0); + s.dhcp_xid = 0xDDDD0001U; + wolfIP_ipconfig_set(&s, 0x0A000064U, 0xFFFFFF00U, 0x0A000001U); + s.dhcp_ip = 0x0A000064U; + s.dhcp_server_ip = 0x0A000001U; + s.last_tick = 1000U; + s.dhcp_lease_expires = 9999999U; /* not expired */ + s.dhcp_state = DHCP_BOUND; + s.dhcp_timeout_count = 0; + + dhcp_timer_cb(&s); + + ck_assert_int_eq(s.dhcp_state, DHCP_RENEWING); + ck_assert_uint_eq(s.dhcp_start_tick, 1000U); + ck_assert_uint_eq(s.dhcp_timeout_count, 1U); +} +END_TEST + +START_TEST(test_dhcp_timer_cb_default_state_noop) +{ + /* State not handled by any case → default branch (no-op) */ + struct wolfIP s; + + wolfIP_init(&s); + mock_link_init(&s); + s.dhcp_xid = 0x5678U; + s.dhcp_state = DHCP_OFF; /* unhandled in switch */ + s.dhcp_timeout_count = 0; + + dhcp_timer_cb(&s); + + /* Timer must reset to NO_TIMER and state must stay DHCP_OFF */ + ck_assert_int_eq(s.dhcp_timer, NO_TIMER); + ck_assert_int_eq(s.dhcp_state, DHCP_OFF); +} +END_TEST + +START_TEST(test_dhcp_timer_cb_null_arg_noop) +{ + /* NULL arg → early return, no crash */ + dhcp_timer_cb(NULL); +} +END_TEST diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 81d9aef5..737fcf34 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -1,3 +1,24 @@ +/* unit_tests_dns_dhcp.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + static uint64_t find_timer_expiry(const struct wolfIP *s, uint32_t timer_id) { uint32_t i; diff --git a/src/test/unit/unit_tests_dns_edges.c b/src/test/unit/unit_tests_dns_edges.c new file mode 100644 index 00000000..720fa363 --- /dev/null +++ b/src/test/unit/unit_tests_dns_edges.c @@ -0,0 +1,672 @@ +/* unit_tests_dns_edges.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +/* ------------------------------------------------------------------ * + * Helper: build a minimal valid DNS A-response header + question into + * buf[], return the offset of the first answer NAME field so callers + * can splice their own answer sections. + * ------------------------------------------------------------------ */ +static int build_dns_a_response_header(uint8_t *buf, size_t buf_sz, + uint16_t id, uint16_t flags, + uint16_t qdcount, uint16_t ancount, + int *q_end_out) +{ + struct dns_header *hdr = (struct dns_header *)buf; + struct dns_question *q; + int pos; + + (void)buf_sz; + memset(buf, 0, buf_sz); + hdr->id = ee16(id); + hdr->flags = ee16(flags); + hdr->qdcount = ee16(qdcount); + hdr->ancount = ee16(ancount); + pos = (int)sizeof(struct dns_header); + + /* question: "example.com" */ + buf[pos++] = 7; memcpy(&buf[pos], "example", 7); pos += 7; + buf[pos++] = 3; memcpy(&buf[pos], "com", 3); pos += 3; + buf[pos++] = 0; + q = (struct dns_question *)(buf + pos); + q->qtype = ee16(DNS_A); + q->qclass = ee16(DNS_CLASS_IN); + pos += (int)sizeof(struct dns_question); + + if (q_end_out) + *q_end_out = pos; + return pos; +} + +/* ------------------------------------------------------------------ * + * dns_callback: recvfrom returns < 0 (empty socket → EAGAIN) + * Exercises lines 8954-8957: close-socket + abort_query path. + * ------------------------------------------------------------------ */ +START_TEST(test_dns_callback_recvfrom_error_closes_socket) +{ + struct wolfIP s; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x0A000001U; + s.dns_id = 0x1234; + s.dns_query_type = DNS_QUERY_TYPE_A; + s.dns_lookup_cb = test_dns_lookup_cb; + + /* Open a real UDP socket but put NO data in it so recvfrom → -EAGAIN */ + s.dns_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dns_udp_sd, 0); + + dns_callback(s.dns_udp_sd, CB_EVENT_READABLE, &s); + + /* Socket must have been closed and state cleared */ + ck_assert_int_eq(s.dns_udp_sd, -1); + ck_assert_uint_eq(s.dns_id, 0); + ck_assert_int_eq(s.dns_query_type, DNS_QUERY_TYPE_NONE); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_callback: RCODE != 0 (NXDOMAIN) → abort query + * Exercises the DNS_RCODE_MASK branch inside the response path. + * ------------------------------------------------------------------ */ +START_TEST(test_dns_callback_rcode_nonzero_aborts_query) +{ + struct wolfIP s; + uint8_t response[64]; + struct dns_header *hdr = (struct dns_header *)response; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x0A000001U; + s.dns_id = 0x5678; + s.dns_query_type = DNS_QUERY_TYPE_A; + s.dns_lookup_cb = test_dns_lookup_cb; + s.dns_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dns_udp_sd, 0); + + memset(response, 0, sizeof(response)); + hdr->id = ee16(s.dns_id); + /* QR=1, RD=1, RCODE=3 (NXDOMAIN) */ + hdr->flags = ee16(0x8103); + hdr->qdcount = ee16(0); + hdr->ancount = ee16(0); + + enqueue_udp_rx(&s.udpsockets[SOCKET_UNMARK(s.dns_udp_sd)], + response, sizeof(response), DNS_PORT); + dns_callback(s.dns_udp_sd, CB_EVENT_READABLE, &s); + + ck_assert_uint_eq(s.dns_id, 0); + ck_assert_int_eq(s.dns_query_type, DNS_QUERY_TYPE_NONE); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_callback: ancount == 0 → no callback, query left pending + * ------------------------------------------------------------------ */ +START_TEST(test_dns_callback_zero_ancount_no_delivery) +{ + struct wolfIP s; + uint8_t response[64]; + int pos; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x0A000001U; + s.dns_id = 0xABCD; + s.dns_query_type = DNS_QUERY_TYPE_A; + dns_lookup_calls = 0; + dns_lookup_ip = 0; + s.dns_lookup_cb = test_dns_lookup_cb; + s.dns_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dns_udp_sd, 0); + + pos = build_dns_a_response_header(response, sizeof(response), + s.dns_id, 0x8100, 1, 0, NULL); + + enqueue_udp_rx(&s.udpsockets[SOCKET_UNMARK(s.dns_udp_sd)], + response, (uint16_t)pos, DNS_PORT); + dns_callback(s.dns_udp_sd, CB_EVENT_READABLE, &s); + + /* No answer → callback never called, id still set */ + ck_assert_int_eq(dns_lookup_calls, 0); + ck_assert_uint_eq(s.dns_id, 0xABCD); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_callback: answer rr->type == AAAA (28) while query_type == A + * → neither branch fires, answer skipped, query stays pending. + * ------------------------------------------------------------------ */ +START_TEST(test_dns_callback_aaaa_answer_skipped_for_a_query) +{ + struct wolfIP s; + uint8_t response[128]; + int pos; + struct dns_rr *rr; + uint8_t aaaa_rdata[16] = {0}; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x0A000001U; + s.dns_id = 0x1111; + s.dns_query_type = DNS_QUERY_TYPE_A; + dns_lookup_calls = 0; + dns_lookup_ip = 0; + s.dns_lookup_cb = test_dns_lookup_cb; + s.dns_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dns_udp_sd, 0); + + pos = build_dns_a_response_header(response, sizeof(response), + s.dns_id, 0x8100, 1, 1, NULL); + /* Answer NAME: compressed pointer to question name at offset 12 */ + response[pos++] = 0xC0; + response[pos++] = (uint8_t)sizeof(struct dns_header); + rr = (struct dns_rr *)(response + pos); + rr->type = ee16(28); /* AAAA */ + rr->class = ee16(DNS_CLASS_IN); + rr->ttl = ee32(60); + rr->rdlength = ee16((uint16_t)sizeof(aaaa_rdata)); + pos += (int)sizeof(struct dns_rr); + memcpy(&response[pos], aaaa_rdata, sizeof(aaaa_rdata)); + pos += (int)sizeof(aaaa_rdata); + + enqueue_udp_rx(&s.udpsockets[SOCKET_UNMARK(s.dns_udp_sd)], + response, (uint16_t)pos, DNS_PORT); + dns_callback(s.dns_udp_sd, CB_EVENT_READABLE, &s); + + ck_assert_int_eq(dns_lookup_calls, 0); + ck_assert_uint_eq(s.dns_id, 0x1111); /* still pending */ +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_callback: answer rdlen advertised larger than remaining buffer + * → abort query (line 8997-8999) + * ------------------------------------------------------------------ */ +START_TEST(test_dns_callback_rr_rdlen_truncated_aborts_query) +{ + struct wolfIP s; + uint8_t response[80]; + int pos; + struct dns_rr *rr; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x0A000001U; + s.dns_id = 0x2222; + s.dns_query_type = DNS_QUERY_TYPE_A; + dns_lookup_calls = 0; + dns_lookup_ip = 0; + s.dns_lookup_cb = test_dns_lookup_cb; + s.dns_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dns_udp_sd, 0); + + pos = build_dns_a_response_header(response, sizeof(response), + s.dns_id, 0x8100, 1, 1, NULL); + response[pos++] = 0xC0; + response[pos++] = (uint8_t)sizeof(struct dns_header); + rr = (struct dns_rr *)(response + pos); + rr->type = ee16(DNS_A); + rr->class = ee16(DNS_CLASS_IN); + rr->ttl = ee32(60); + /* rdlength says 100 bytes but we stop writing after 2 bytes of rdata */ + rr->rdlength = ee16(100); + pos += (int)sizeof(struct dns_rr); + response[pos++] = 0x0A; + response[pos++] = 0x00; /* only 2 bytes of rdata */ + + enqueue_udp_rx(&s.udpsockets[SOCKET_UNMARK(s.dns_udp_sd)], + response, (uint16_t)pos, DNS_PORT); + dns_callback(s.dns_udp_sd, CB_EVENT_READABLE, &s); + + ck_assert_int_eq(dns_lookup_calls, 0); + ck_assert_uint_eq(s.dns_id, 0); + ck_assert_int_eq(s.dns_query_type, DNS_QUERY_TYPE_NONE); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_callback: question section name is malformed (label goes past + * end-of-packet) → dns_skip_name returns < 0 → abort query + * ------------------------------------------------------------------ */ +START_TEST(test_dns_callback_bad_question_name_aborts_query) +{ + struct wolfIP s; + uint8_t response[32]; + struct dns_header *hdr = (struct dns_header *)response; + int pos; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x0A000001U; + s.dns_id = 0x3333; + s.dns_query_type = DNS_QUERY_TYPE_A; + s.dns_lookup_cb = test_dns_lookup_cb; + s.dns_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dns_udp_sd, 0); + + memset(response, 0, sizeof(response)); + hdr->id = ee16(s.dns_id); + hdr->flags = ee16(0x8100); + hdr->qdcount = ee16(1); + hdr->ancount = ee16(1); + pos = (int)sizeof(struct dns_header); + /* Label length 20 but only 2 bytes of data before EOF */ + response[pos++] = 20; + response[pos++] = 'a'; + response[pos++] = 'b'; + + enqueue_udp_rx(&s.udpsockets[SOCKET_UNMARK(s.dns_udp_sd)], + response, (uint16_t)pos, DNS_PORT); + dns_callback(s.dns_udp_sd, CB_EVENT_READABLE, &s); + + ck_assert_uint_eq(s.dns_id, 0); + ck_assert_int_eq(s.dns_query_type, DNS_QUERY_TYPE_NONE); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_callback: answer NAME compressed ptr points forward (invalid) + * → dns_skip_name returns -1 in answer section → abort query + * ------------------------------------------------------------------ */ +START_TEST(test_dns_callback_answer_forward_ptr_aborts_query) +{ + struct wolfIP s; + uint8_t response[80]; + int pos; + struct dns_question *q; + struct dns_header *hdr = (struct dns_header *)response; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x0A000001U; + s.dns_id = 0x4444; + s.dns_query_type = DNS_QUERY_TYPE_A; + s.dns_lookup_cb = test_dns_lookup_cb; + s.dns_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dns_udp_sd, 0); + + memset(response, 0, sizeof(response)); + hdr->id = ee16(s.dns_id); + hdr->flags = ee16(0x8100); + hdr->qdcount = ee16(1); + hdr->ancount = ee16(1); + pos = (int)sizeof(struct dns_header); + response[pos++] = 1; response[pos++] = 'a'; response[pos++] = 0; + q = (struct dns_question *)(response + pos); + q->qtype = ee16(DNS_A); + q->qclass = ee16(DNS_CLASS_IN); + pos += (int)sizeof(struct dns_question); + + /* Answer NAME: compression pointer pointing FORWARD (invalid) */ + response[pos] = 0xC0; + response[pos + 1] = (uint8_t)(pos + 4); /* forward reference */ + pos += 2; + + enqueue_udp_rx(&s.udpsockets[SOCKET_UNMARK(s.dns_udp_sd)], + response, (uint16_t)pos, DNS_PORT); + dns_callback(s.dns_udp_sd, CB_EVENT_READABLE, &s); + + ck_assert_uint_eq(s.dns_id, 0); + ck_assert_int_eq(s.dns_query_type, DNS_QUERY_TYPE_NONE); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_cancel_timer: NULL stack pointer is a no-op (line 8856) + * ------------------------------------------------------------------ */ +START_TEST(test_dns_cancel_timer_null_noop) +{ + dns_cancel_timer(NULL); /* must not crash */ +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_schedule_timer: NULL stack pointer is a no-op (line 8870) + * ------------------------------------------------------------------ */ +START_TEST(test_dns_schedule_timer_null_noop) +{ + dns_schedule_timer(NULL); /* must not crash */ +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_timeout_cb: NULL arg is a no-op (line 8922) + * ------------------------------------------------------------------ */ +START_TEST(test_dns_timeout_cb_null_noop) +{ + dns_timeout_cb(NULL); /* must not crash */ +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_timeout_cb: dns_id == 0 → early return (line 8925) + * ------------------------------------------------------------------ */ +START_TEST(test_dns_timeout_cb_zero_id_noop) +{ + struct wolfIP s; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_id = 0; /* no outstanding query */ + s.dns_timer = NO_TIMER; + + dns_timeout_cb(&s); /* must be silent: dns_id == 0 */ + ck_assert_uint_eq(s.dns_id, 0); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_timeout_cb: resend fails → abort (lines 8927-8929) + * Trigger by setting dns_udp_sd = -1 so dns_resend_query() returns < 0. + * ------------------------------------------------------------------ */ +START_TEST(test_dns_timeout_cb_resend_failure_aborts_query) +{ + struct wolfIP s; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x0A000001U; + s.dns_id = 0x9999; + s.dns_timer = NO_TIMER; + s.dns_retry_count = 0; /* still within retry budget */ + s.dns_query_type = DNS_QUERY_TYPE_A; + s.dns_lookup_cb = test_dns_lookup_cb; + /* Invalidate socket so resend fails */ + s.dns_udp_sd = -1; + + dns_timeout_cb(&s); + + /* After failed resend the query must be aborted */ + ck_assert_uint_eq(s.dns_id, 0); + ck_assert_int_eq(s.dns_query_type, DNS_QUERY_TYPE_NONE); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_copy_name: label does not fit in caller buffer. + * Build: "ab" → label = [2,'a','b',0]. With out_len == 2 the + * label-bound guard (o + c >= out_len) fires before the terminator + * write — this exercises the label-copy capacity path, NOT the + * terminator guard (see test_dns_copy_name_zero_out_len_... below). + * ------------------------------------------------------------------ */ +START_TEST(test_dns_copy_name_label_too_big_for_output) +{ + /* buf: [2,'a','b',0] */ + const uint8_t buf[4] = { 2, 'a', 'b', 0 }; + char out[2]; /* exactly "ab" with no room for NUL */ + int ret; + + /* out_len == 2: 0 + 2 >= 2 → label-bound guard fires → -1 */ + ret = dns_copy_name(buf, (int)sizeof(buf), 0, out, sizeof(out)); + ck_assert_int_eq(ret, -1); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_copy_name: terminator-write guard when out_len == 0. + * Build: bare terminator [0] with out_len == 0. The label-copy guards + * are not reached for an empty name, so this uniquely exercises the + * 'o >= out_len' check at the NAME_TERMINATOR write site. + * ------------------------------------------------------------------ */ +START_TEST(test_dns_copy_name_zero_out_len_rejects_terminator_write) +{ + const uint8_t buf[1] = { 0 }; + char out[1]; /* not written; placeholder */ + int ret; + + ret = dns_copy_name(buf, (int)sizeof(buf), 0, out, 0); + ck_assert_int_eq(ret, -1); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_copy_name: compression pointer at the very last byte of buffer + * (line 8823: pos + 1 >= len) + * buf ends with 0xC0 at pos = len-1, no room for the second byte. + * ------------------------------------------------------------------ */ +START_TEST(test_dns_copy_name_ptr_at_end_of_buffer) +{ + /* Simple name "a." followed by a truncated 0xC0 ptr */ + const uint8_t buf[4] = { 1, 'a', 0xC0 /* no second byte */ }; + char out[32]; + int ret; + + ret = dns_copy_name(buf, 3, 2, out, sizeof(out)); + ck_assert_int_eq(ret, -1); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_copy_name: label extends past end of buffer (line 8836) + * ------------------------------------------------------------------ */ +START_TEST(test_dns_copy_name_label_past_end) +{ + /* Label length 10 but only 3 bytes follow before EOF */ + const uint8_t buf[5] = { 10, 'a', 'b', 'c', 'd' }; + char out[32]; + int ret; + + ret = dns_copy_name(buf, (int)sizeof(buf), 0, out, sizeof(out)); + ck_assert_int_eq(ret, -1); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_copy_name: separator write would overflow output buffer + * (line 8839: o + 1 >= out_len when o != 0) + * "ab.cd" needs out_len >= 6; give it 4 so separator fits but not "cd". + * With out_len == 4: after "ab" o == 2; separator needs o+1 < 4 (ok, + * so go further)... actually need out_len == 3 so o+1 == 3 >= 3. + * ------------------------------------------------------------------ */ +START_TEST(test_dns_copy_name_separator_overflow) +{ + /* "ab.cd" encoded as [2,'a','b',2,'c','d',0] */ + const uint8_t buf[8] = { 2, 'a', 'b', 2, 'c', 'd', 0 }; + /* out_len == 3: after copying "ab", o == 2; writing '.' needs o < 2 + * i.e. o + 1 = 3 >= out_len(3) → -1 */ + char out[3]; + int ret; + + ret = dns_copy_name(buf, (int)sizeof(buf), 0, out, sizeof(out)); + ck_assert_int_eq(ret, -1); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_copy_name: label itself would overflow output buffer (line 8843) + * "abc" with out_len == 3: o + c == 3 >= 3 → -1. + * ------------------------------------------------------------------ */ +START_TEST(test_dns_copy_name_label_overflow_output) +{ + /* "abc" encoded as [3,'a','b','c',0] */ + const uint8_t buf[5] = { 3, 'a', 'b', 'c', 0 }; + /* out_len == 3: o + c == 3 >= 3 → overflow */ + char out[3]; + int ret; + + ret = dns_copy_name(buf, (int)sizeof(buf), 0, out, sizeof(out)); + ck_assert_int_eq(ret, -1); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_skip_name: pos overshoots len (label length field causes + * pos > len → line 8795-8796 taken). + * ------------------------------------------------------------------ */ +START_TEST(test_dns_skip_name_label_past_end) +{ + /* [5, 'a', 'b'] — label says 5 bytes but only 2 follow */ + const uint8_t buf[3] = { 5, 'a', 'b' }; + int ret; + + ret = dns_skip_name(buf, (int)sizeof(buf), 0); + ck_assert_int_eq(ret, -1); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_copy_name: compression pointer followed by second label that + * overflows the output (separator + label together exceed out_len). + * Covers the o + 1 >= out_len guard (line 8838-8839) where o != 0. + * ------------------------------------------------------------------ */ +START_TEST(test_dns_copy_name_second_label_separator_and_label_fit) +{ + /* "ab.cd" → [2,'a','b',2,'c','d',0] — verify success with + * enough buffer and failure when truncated by exactly 1. */ + const uint8_t buf[8] = { 2, 'a', 'b', 2, 'c', 'd', 0 }; + char out_ok[7]; /* "ab.cd\0" = 6 chars + NUL → exactly fits */ + char out_small[5]; /* room for "ab." + 1, not enough for "cd\0" */ + int ret; + + /* Should succeed with enough room */ + ret = dns_copy_name(buf, (int)sizeof(buf), 0, out_ok, sizeof(out_ok)); + ck_assert_int_eq(ret, 0); + ck_assert_str_eq(out_ok, "ab.cd"); + + /* Should fail: out_len == 5, after "ab" o=2, need o+1 < 5 (ok), + * then o+c = 2+1+2 = 5 >= 5 → overflow at label copy */ + ret = dns_copy_name(buf, (int)sizeof(buf), 0, out_small, sizeof(out_small)); + ck_assert_int_eq(ret, -1); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_callback: PTR response where dns_copy_name fails (bad rdata) + * → the failure branch of dns_copy_name inside the PTR arm is taken, + * pos advances by rdlen, and the query stays pending. + * ------------------------------------------------------------------ */ +START_TEST(test_dns_callback_ptr_bad_copy_name_stays_pending) +{ + struct wolfIP s; + uint8_t response[128]; + struct dns_header *hdr = (struct dns_header *)response; + struct dns_question *q; + struct dns_rr *rr; + int pos; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x0A000001U; + s.dns_id = 0xBBBB; + s.dns_query_type = DNS_QUERY_TYPE_PTR; + s.dns_ptr_cb = test_dns_ptr_cb; + s.dns_lookup_cb = NULL; + s.dns_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dns_udp_sd, 0); + + memset(response, 0, sizeof(response)); + hdr->id = ee16(s.dns_id); + hdr->flags = ee16(0x8100); + hdr->qdcount = ee16(1); + hdr->ancount = ee16(1); + pos = (int)sizeof(struct dns_header); + response[pos++] = 1; response[pos++] = 'a'; response[pos++] = 0; + q = (struct dns_question *)(response + pos); + q->qtype = ee16(DNS_PTR); + q->qclass = ee16(DNS_CLASS_IN); + pos += (int)sizeof(struct dns_question); + + /* Answer NAME: simple label */ + response[pos++] = 0xC0; + response[pos++] = (uint8_t)sizeof(struct dns_header); + + /* RR header */ + rr = (struct dns_rr *)(response + pos); + rr->type = ee16(DNS_PTR); + rr->class = ee16(DNS_CLASS_IN); + rr->ttl = ee32(60); + /* RDATA claims 4 bytes but contains a label [10,'a','b',0] that + * overruns pos+c > len: the rdlength says 4 so only 4 bytes follow, + * but the label length is 10 → dns_copy_name returns -1 */ + rr->rdlength = ee16(4); + pos += (int)sizeof(struct dns_rr); + response[pos++] = 10; /* label length exceeds remaining rdata */ + response[pos++] = 'a'; + response[pos++] = 'b'; + response[pos++] = 0; + + enqueue_udp_rx(&s.udpsockets[SOCKET_UNMARK(s.dns_udp_sd)], + response, (uint16_t)pos, DNS_PORT); + dns_callback(s.dns_udp_sd, CB_EVENT_READABLE, &s); + + /* copy failed → ptr_cb never called → query still pending */ + ck_assert_uint_eq(s.dns_id, 0xBBBB); + ck_assert_int_eq(s.dns_query_type, DNS_QUERY_TYPE_PTR); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_copy_name: jumped == 1, so pos is NOT incremented after reading + * the NUL terminator (line 8813-8814 true branch). + * Use a valid compression pointer followed by the NUL terminator. + * ------------------------------------------------------------------ */ +START_TEST(test_dns_copy_name_jumped_no_pos_increment) +{ + /* buf layout: + * [0] = 0 ← NUL (empty name at offset 0) + * [1] = 0xC0 + * [2] = 0x00 ← ptr to offset 0 + */ + const uint8_t buf[3] = { 0, 0xC0, 0x00 }; + char out[32]; + int ret; + + /* Start at the compression pointer (offset 1). + * The pointer lands at offset 0 which is '\0', so jumped == 1 and + * the NUL-terminator branch sets out[0]='\0' without touching pos. */ + ret = dns_copy_name(buf, (int)sizeof(buf), 1, out, sizeof(out)); + ck_assert_int_eq(ret, 0); + ck_assert_uint_eq((uint8_t)out[0], 0); +} +END_TEST + +/* ------------------------------------------------------------------ * + * dns_send_query: socket allocation failure returns -1 (line 9050) + * Exhaust all UDP sockets before calling dns_send_query so that + * wolfIP_sock_socket returns < 0 inside it. + * ------------------------------------------------------------------ */ +START_TEST(test_dns_send_query_socket_alloc_failure) +{ + struct wolfIP s; + uint16_t id; + int i, sd; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0x0A000001U); + s.dns_server = 0x08080808U; + s.dns_id = 0; /* no outstanding query */ + s.dns_udp_sd = -1; /* force socket allocation path */ + + /* Open all available UDP sockets to exhaust the pool */ + for (i = 0; i < MAX_UDPSOCKETS; i++) { + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + /* Stop early if we run out (pool may be smaller than MAX_UDPSOCKETS) */ + if (sd < 0) + break; + } + + /* Now socket allocation inside dns_send_query must fail → -1 */ + ck_assert_int_eq(dns_send_query(&s, "example.com", &id, DNS_A), -1); + /* Query state must not have been set */ + ck_assert_uint_eq(s.dns_id, 0); +} +END_TEST diff --git a/src/test/unit/unit_tests_fifo.c b/src/test/unit/unit_tests_fifo.c index 73480496..351a9ba2 100644 --- a/src/test/unit/unit_tests_fifo.c +++ b/src/test/unit/unit_tests_fifo.c @@ -1,3 +1,24 @@ +/* unit_tests_fifo.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + START_TEST(test_fifo_init) { struct fifo f; diff --git a/src/test/unit/unit_tests_ip_arp_recv.c b/src/test/unit/unit_tests_ip_arp_recv.c new file mode 100644 index 00000000..1c2daaeb --- /dev/null +++ b/src/test/unit/unit_tests_ip_arp_recv.c @@ -0,0 +1,1463 @@ +/* unit_tests_ip_arp_recv.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +/* ========================================================================= + * Helpers shared by multiple tests + * ========================================================================= */ + +/* Build a valid ARP packet in 'arp_out'. Caller fills htype/ptype/hlen/plen + * and sip after calling this. */ +static void build_valid_arp_request(struct arp_packet *arp_out, + struct wolfIP_ll_dev *ll, + ip4 own_ip, + ip4 sender_ip) +{ + static const uint8_t sender_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x01}; + + memset(arp_out, 0, sizeof(*arp_out)); + memcpy(arp_out->eth.dst, ll->mac, 6); + memcpy(arp_out->eth.src, sender_mac, 6); + arp_out->eth.type = ee16(ETH_TYPE_ARP); + arp_out->htype = ee16(1); + arp_out->ptype = ee16(0x0800); + arp_out->hlen = 6; + arp_out->plen = 4; + arp_out->opcode = ee16(ARP_REQUEST); + memcpy(arp_out->sma, sender_mac, 6); + arp_out->sip = ee32(sender_ip); + memset(arp_out->tma, 0, 6); + arp_out->tip = ee32(own_ip); +} + +/* ========================================================================= + * ip_recv: limited broadcast destination (255.255.255.255) treated as local + * ========================================================================= + * Branch: wolfIP_ip_is_broadcast(s, dest) → is_local = 1 + * We verify this via the forwarding path: with two interfaces, a broadcast + * dst must not cause a forwarding ARP request (it is local, not forwarded). + */ +START_TEST(test_ip_recv_limited_broadcast_dst_is_local) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + 8]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 primary_ip = 0x0A000001U; + ip4 secondary_ip = 0xC0A80101U; + ip4 src_ip = 0x0A000002U; + ip4 bcast_dst = 0xFFFFFFFFU; /* 255.255.255.255 limited broadcast */ + + /* Two interfaces enable forwarding logic; broadcast must stay is_local=1 */ + setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN); + ip->src = ee32(src_ip); + ip->dst = ee32(bcast_dst); + fix_ip_checksum(ip); + { + uint16_t *udp = (uint16_t *)(frame + ETH_HEADER_LEN + IP_HEADER_LEN); + udp[0] = ee16(9999); udp[1] = ee16(1234); + udp[2] = ee16(UDP_HEADER_LEN); udp[3] = 0; + } + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN)); + + /* Broadcast dst → is_local=1 → no forwarding → no ARP request sent for + * a remote host (last_frame_sent may be 0 or an ARP request, but NO + * forwarding ARP pending for the broadcast address should exist) */ + ck_assert_uint_eq(s.arp_pending[0].dest, IPADDR_ANY); +} +END_TEST + +/* ========================================================================= + * ip_recv: directed broadcast hits the is_local=1 branch too + * ========================================================================= + * Branch: wolfIP_ip_is_broadcast(s, dest) where dest == directed broadcast + * 10.0.0.255 is the directed broadcast for 10.0.0.0/24. + * With two interfaces, this must NOT be forwarded out the second interface. + */ +START_TEST(test_ip_recv_directed_broadcast_dst_is_local) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 primary_ip = 0x0A000001U; /* 10.0.0.1/24 */ + ip4 secondary_ip = 0xC0A80101U; /* 192.168.1.1/24 */ + ip4 src_ip = 0x0A000002U; + ip4 dir_bcast = 0x0A0000FFU; /* 10.0.0.255 — directed broadcast */ + + setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN); + ip->src = ee32(src_ip); + ip->dst = ee32(dir_bcast); + fix_ip_checksum(ip); + { + uint16_t *udp = (uint16_t *)(frame + ETH_HEADER_LEN + IP_HEADER_LEN); + udp[0] = ee16(9999); udp[1] = ee16(1234); + udp[2] = ee16(UDP_HEADER_LEN); udp[3] = 0; + } + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + /* Directed broadcast → is_local=1 → not forwarded to secondary iface */ + ck_assert_uint_eq(s.arp_pending[0].dest, IPADDR_ANY); +} +END_TEST + +/* ========================================================================= + * ip_recv: IPADDR_ANY destination treated as local + * ========================================================================= + * Branch: dest == IPADDR_ANY → is_local = 1 + * Packet to 0.0.0.0 must not be forwarded, even with two interfaces. + */ +START_TEST(test_ip_recv_ipaddr_any_dst_is_local) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 primary_ip = 0x0A000001U; + ip4 secondary_ip = 0xC0A80101U; + ip4 src_ip = 0x0A000002U; + + setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN); + ip->src = ee32(src_ip); + ip->dst = ee32(IPADDR_ANY); /* 0.0.0.0 */ + fix_ip_checksum(ip); + { + uint16_t *udp = (uint16_t *)(frame + ETH_HEADER_LEN + IP_HEADER_LEN); + udp[0] = ee16(9999); udp[1] = ee16(1234); + udp[2] = ee16(UDP_HEADER_LEN); udp[3] = 0; + } + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + /* 0.0.0.0 dst → is_local=1 → never forwarded */ + ck_assert_uint_eq(s.arp_pending[0].dest, IPADDR_ANY); +} +END_TEST + +/* ========================================================================= + * ip_recv: forward with ARP cache HIT — packet forwarded immediately + * ========================================================================= + * Branch: wolfIP_forward_prepare returns 1 (ARP hit) → wolfIP_forward_packet + * called without queuing. Tests the "direct forward" arm at line 8468. + */ +START_TEST(test_ip_recv_forward_arp_hit_sends_immediately) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 primary_ip = 0x0A000001U; /* 10.0.0.1 on if1 */ + ip4 secondary_ip = 0xC0A80101U; /* 192.168.1.1 on if2 */ + ip4 dest_ip = 0xC0A80155U; /* 192.168.1.85 — local to if2 */ + ip4 src_ip = 0x0A000002U; /* sender */ + static const uint8_t dest_mac[6] = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15}; + + setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip); + wolfIP_filter_set_callback(NULL, NULL); + + /* Pre-populate ARP cache for dest on if2 so prepare returns 1 (HIT) */ + arp_store_neighbor(&s, TEST_SECOND_IF, dest_ip, dest_mac); + + last_frame_sent_size = 0; + + /* Build a packet to forward: src on primary subnet, dst on secondary */ + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN); + ip->src = ee32(src_ip); + ip->dst = ee32(dest_ip); + fix_ip_checksum(ip); + /* Append minimal UDP header so length arithmetic holds */ + { + uint16_t *udp = (uint16_t *)(frame + ETH_HEADER_LEN + IP_HEADER_LEN); + udp[0] = ee16(9999); udp[1] = ee16(53); + udp[2] = ee16(UDP_HEADER_LEN); udp[3] = 0; + } + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + /* Frame must have been sent out immediately (ARP cache hit) */ + ck_assert_uint_gt(last_frame_sent_size, 0); + /* Destination MAC in sent frame must match the cached entry */ + ck_assert_mem_eq(last_frame_sent + 0, dest_mac, 6); + /* No pending ARP slot should have been allocated */ + ck_assert_uint_eq(s.arp_pending[0].dest, IPADDR_ANY); +} +END_TEST + +/* ========================================================================= + * ip_recv: forward interface with no configured IP is skipped + * ========================================================================= + * Branch: in wolfIP_forward_interface, conf->ip == IPADDR_ANY → continue + * A second interface with no IP should never be selected as forward egress. + */ +START_TEST(test_ip_recv_forward_unconfigured_iface_skipped) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + 8]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 primary_ip = 0x0A000001U; + ip4 dest_ip = 0xC0A80155U; /* 192.168.1.85 — not local anywhere */ + ip4 src_ip = 0x0A000002U; + + /* Only configure primary interface; leave second with ip=IPADDR_ANY */ + wolfIP_init(&s); + mock_link_init(&s); + mock_link_init_idx(&s, TEST_SECOND_IF, NULL); + wolfIP_ipconfig_set(&s, primary_ip, 0xFFFFFF00U, 0); + /* TEST_SECOND_IF ip remains IPADDR_ANY (no call to set_ex) */ + + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN); + ip->src = ee32(src_ip); + ip->dst = ee32(dest_ip); + fix_ip_checksum(ip); + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN)); + + /* No forward route found → no packet sent */ + ck_assert_uint_eq(last_frame_sent_size, 0); + /* No ARP pending slot allocated */ + ck_assert_uint_eq(s.arp_pending[0].dest, IPADDR_ANY); +} +END_TEST + +/* ========================================================================= + * ip_recv: link-local source (169.254.x.x) is not routable — RPF drop + * ========================================================================= + * Branch: (src & 0xFFFF0000U) == 0xA9FE0000U → rpf_drop = 1 + */ +START_TEST(test_ip_recv_forward_link_local_src_rpf_drop) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 primary_ip = 0x0A000001U; + ip4 secondary_ip = 0xC0A80101U; + ip4 src_ip = 0xA9FE0002U; /* 169.254.0.2 — link-local */ + ip4 dest_ip = 0xC0A80155U; /* would be forwarded if src were ok */ + + setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN); + ip->src = ee32(src_ip); + ip->dst = ee32(dest_ip); + fix_ip_checksum(ip); + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + /* Link-local source is not routable; must be dropped silently */ + ck_assert_uint_eq(last_frame_sent_size, 0); +} +END_TEST + +/* ========================================================================= + * ip_recv: IP with NOP options — options parsed, payload delivered + * ========================================================================= + * Branch: type == 1 (NOP) inside option parser → opt++ continue + * Ensures NOP bytes in the option list are skipped and not mis-identified + * as bad options. + */ +START_TEST(test_ip_recv_options_nop_delivered) +{ + struct wolfIP s; + /* 4 bytes of IP options: NOP NOP NOP EOL */ + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + 4 + UDP_HEADER_LEN + 4]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + uint8_t *udp_hdr = frame + ETH_HEADER_LEN + IP_HEADER_LEN + 4; + uint16_t udp_len = UDP_HEADER_LEN + 4; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = udp_new_socket(&s); + ck_assert_ptr_nonnull(ts); + ts->src_port = 1234; + ts->local_ip = local_ip; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x46; /* IHL=6 → 24-byte header, 4 bytes of options */ + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + 4 + udp_len); + ip->src = ee32(remote_ip); + ip->dst = ee32(local_ip); + /* Options: NOP NOP NOP EOL */ + ip->data[0] = 1; ip->data[1] = 1; ip->data[2] = 1; ip->data[3] = 0; + + /* UDP header right after options */ + ((uint16_t *)udp_hdr)[0] = ee16(9999); /* sport */ + ((uint16_t *)udp_hdr)[1] = ee16(1234); /* dport */ + ((uint16_t *)udp_hdr)[2] = ee16(udp_len); + memcpy(udp_hdr + UDP_HEADER_LEN, "nop!", 4); + + fix_udp_checksum_raw(ip, udp_hdr, udp_len); + fix_ip_checksum_with_hlen(ip, (uint16_t)(IP_HEADER_LEN + 4)); + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + /* NOP options must be stripped and UDP payload delivered */ + ck_assert_ptr_nonnull(fifo_peek(&ts->sock.udp.rxbuf)); + ck_assert_int_ne(ts->events & CB_EVENT_READABLE, 0); +} +END_TEST + +/* ========================================================================= + * ip_recv: option type unknown (non-NOP, non-EOL, non-source-route) + * ========================================================================= + * Branch: generic option walking (opt += opt[1]) for type != 0/1/0x83/0x89 + * RR (record-route = 0x07) is a benign option that must be parsed, stripped, + * then the payload delivered. + */ +START_TEST(test_ip_recv_options_rr_stripped_and_delivered) +{ + struct wolfIP s; + /* 4-byte RR option: type=0x07, len=3, ptr=4, then EOL */ + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + 4 + UDP_HEADER_LEN + 4]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + uint8_t *udp_hdr = frame + ETH_HEADER_LEN + IP_HEADER_LEN + 4; + uint16_t udp_len = UDP_HEADER_LEN + 4; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = udp_new_socket(&s); + ck_assert_ptr_nonnull(ts); + ts->src_port = 1234; + ts->local_ip = local_ip; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x46; + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + 4 + udp_len); + ip->src = ee32(remote_ip); + ip->dst = ee32(local_ip); + /* Options: type=0x07 (RR), len=3, ptr=4, then EOL */ + ip->data[0] = 0x07; ip->data[1] = 3; ip->data[2] = 4; ip->data[3] = 0; + + ((uint16_t *)udp_hdr)[0] = ee16(9999); + ((uint16_t *)udp_hdr)[1] = ee16(1234); + ((uint16_t *)udp_hdr)[2] = ee16(udp_len); + memcpy(udp_hdr + UDP_HEADER_LEN, "rr!!", 4); + + fix_udp_checksum_raw(ip, udp_hdr, udp_len); + fix_ip_checksum_with_hlen(ip, (uint16_t)(IP_HEADER_LEN + 4)); + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + /* RR option stripped, UDP payload delivered */ + ck_assert_ptr_nonnull(fifo_peek(&ts->sock.udp.rxbuf)); + ck_assert_int_ne(ts->events & CB_EVENT_READABLE, 0); +} +END_TEST + +/* ========================================================================= + * ip_recv: option bad length (opt[1] < 2) — parsing aborts (break) + * ========================================================================= + * Branch: opt + 1 >= opt_end || opt[1] < 2 → break + * A malformed option with length=1 must not loop infinitely. + */ +START_TEST(test_ip_recv_options_bad_length_aborts_parse) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + 4 + UDP_HEADER_LEN + 4]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + uint8_t *udp_hdr = frame + ETH_HEADER_LEN + IP_HEADER_LEN + 4; + uint16_t udp_len = UDP_HEADER_LEN + 4; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = udp_new_socket(&s); + ck_assert_ptr_nonnull(ts); + ts->src_port = 1234; + ts->local_ip = local_ip; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x46; + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + 4 + udp_len); + ip->src = ee32(remote_ip); + ip->dst = ee32(local_ip); + /* Options: type=0x44 (timestamp), length=1 (bad — must be >=2) */ + ip->data[0] = 0x44; ip->data[1] = 1; ip->data[2] = 0; ip->data[3] = 0; + + ((uint16_t *)udp_hdr)[0] = ee16(9999); + ((uint16_t *)udp_hdr)[1] = ee16(1234); + ((uint16_t *)udp_hdr)[2] = ee16(udp_len); + memcpy(udp_hdr + UDP_HEADER_LEN, "bad!", 4); + + fix_udp_checksum_raw(ip, udp_hdr, udp_len); + fix_ip_checksum_with_hlen(ip, (uint16_t)(IP_HEADER_LEN + 4)); + + /* Must not crash; packet may or may not be delivered, but parse terminates */ + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + /* (no assertion on delivery — the goal is no infinite loop / crash) */ +} +END_TEST + +/* ========================================================================= + * ip_recv: IP options present but total length is well-formed (normal strip) + * ========================================================================= + * Branch: ip_hlen > IP_HEADER_LEN → option-strip code path executed + * Options stripped, adjusted csum recomputed, payload dispatched. + */ +START_TEST(test_ip_recv_options_strip_checksum_recomputed) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + 4 + UDP_HEADER_LEN]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + uint8_t *udp_hdr = frame + ETH_HEADER_LEN + IP_HEADER_LEN + 4; + uint16_t udp_len = UDP_HEADER_LEN; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = udp_new_socket(&s); + ck_assert_ptr_nonnull(ts); + ts->src_port = 1234; + ts->local_ip = local_ip; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x46; + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + 4 + udp_len); + ip->src = ee32(remote_ip); + ip->dst = ee32(local_ip); + /* Options: 4 NOPs */ + ip->data[0] = 1; ip->data[1] = 1; ip->data[2] = 1; ip->data[3] = 1; + + ((uint16_t *)udp_hdr)[0] = ee16(8888); /* sport */ + ((uint16_t *)udp_hdr)[1] = ee16(1234); /* dport */ + ((uint16_t *)udp_hdr)[2] = ee16(udp_len); + ((uint16_t *)udp_hdr)[3] = 0; + + fix_udp_checksum_raw(ip, udp_hdr, udp_len); + fix_ip_checksum_with_hlen(ip, (uint16_t)(IP_HEADER_LEN + 4)); + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + ck_assert_ptr_nonnull(fifo_peek(&ts->sock.udp.rxbuf)); +} +END_TEST + +/* ========================================================================= + * ip_recv: LSRR source route option — packet dropped + * ========================================================================= + * Branch: type == 0x83 (LSRR) → return immediately (RFC 7126) + * Already registered as test_ip_recv_drops_source_routed_packet but we + * verify the LSRR (0x83) path specifically in our own test to cover both + * sub-arms of the || check. This test uses SSRR (0x89) for the alt arm. + * NOTE: Only adding SSRR test since LSRR (0x83) is already registered. + */ +START_TEST(test_ip_recv_options_ssrr_dropped) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + 8 + UDP_HEADER_LEN + 4]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + uint8_t *udp_hdr = frame + ETH_HEADER_LEN + IP_HEADER_LEN + 8; + uint16_t udp_len = UDP_HEADER_LEN + 4; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = udp_new_socket(&s); + ck_assert_ptr_nonnull(ts); + ts->src_port = 1234; + ts->local_ip = local_ip; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x47; /* IHL=7 → 28 bytes (8 bytes of options) */ + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + 8 + udp_len); + ip->src = ee32(remote_ip); + ip->dst = ee32(local_ip); + /* Options: SSRR (0x89), len=7, ptr=4, then 3-byte route + EOL */ + ip->data[0] = 0x89; ip->data[1] = 7; ip->data[2] = 4; + ip->data[3] = 0x0A; ip->data[4] = 0x00; ip->data[5] = 0x00; ip->data[6] = 0x01; + ip->data[7] = 0; /* EOL */ + + ((uint16_t *)udp_hdr)[0] = ee16(9999); + ((uint16_t *)udp_hdr)[1] = ee16(1234); + ((uint16_t *)udp_hdr)[2] = ee16(udp_len); + memcpy(udp_hdr + UDP_HEADER_LEN, "ssrr", 4); + + fix_udp_checksum_raw(ip, udp_hdr, udp_len); + fix_ip_checksum_with_hlen(ip, (uint16_t)(IP_HEADER_LEN + 8)); + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + /* SSRR option → packet must be silently dropped */ + ck_assert_ptr_eq(fifo_peek(&ts->sock.udp.rxbuf), NULL); + ck_assert_uint_eq(ts->events & CB_EVENT_READABLE, 0); +} +END_TEST + +/* ========================================================================= + * ip_recv: loopback destination on non-loopback interface — dropped + * ========================================================================= + * Branch: (dest & WOLFIP_LOOPBACK_MASK) == loopback prefix && !loopback_if + */ +START_TEST(test_ip_recv_loopback_dst_on_non_loopback_dropped) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + ip4 loop_dst = 0x7F000001U; /* 127.0.0.1 */ + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = udp_new_socket(&s); + ck_assert_ptr_nonnull(ts); + ts->src_port = 1234; + ts->local_ip = IPADDR_ANY; + + /* Inject from non-loopback interface to loopback destination */ + inject_udp_datagram(&s, TEST_PRIMARY_IF, remote_ip, loop_dst, + 9999, 1234, NULL, 0); + + /* Must be dropped — loopback addresses must not arrive on wire */ + ck_assert_ptr_eq(fifo_peek(&ts->sock.udp.rxbuf), NULL); + ck_assert_uint_eq(ts->events & CB_EVENT_READABLE, 0); +} +END_TEST + +/* ========================================================================= + * ip_recv: loopback source on non-loopback interface — dropped + * ========================================================================= + * Branch: (src & WOLFIP_LOOPBACK_MASK) == loopback prefix && !loopback_if + */ +START_TEST(test_ip_recv_loopback_src_on_non_loopback_dropped) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 loop_src = 0x7F000002U; /* 127.0.0.2 as source */ + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = udp_new_socket(&s); + ck_assert_ptr_nonnull(ts); + ts->src_port = 1234; + ts->local_ip = IPADDR_ANY; + + inject_udp_datagram(&s, TEST_PRIMARY_IF, loop_src, local_ip, + 9999, 1234, NULL, 0); + + ck_assert_ptr_eq(fifo_peek(&ts->sock.udp.rxbuf), NULL); +} +END_TEST + +/* ========================================================================= + * ip_recv: TTL=2 forwarding (normal forward, no TTL exceeded) + * ========================================================================= + * Branch: ip->ttl > 1 → ttl-- and forward, not TTL exceeded + */ +START_TEST(test_ip_recv_forward_ttl_normal_decremented) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 primary_ip = 0x0A000001U; + ip4 secondary_ip = 0xC0A80101U; + ip4 dest_ip = 0xC0A80155U; + ip4 src_ip = 0x0A000002U; + static const uint8_t dest_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + uint8_t *sent_ip_ttl; + + setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip); + wolfIP_filter_set_callback(NULL, NULL); + + arp_store_neighbor(&s, TEST_SECOND_IF, dest_ip, dest_mac); + last_frame_sent_size = 0; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->ttl = 64; /* TTL > 1: normal forward path */ + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN); + ip->src = ee32(src_ip); + ip->dst = ee32(dest_ip); + fix_ip_checksum(ip); + { + uint16_t *udp = (uint16_t *)(frame + ETH_HEADER_LEN + IP_HEADER_LEN); + udp[0] = ee16(9999); udp[1] = ee16(53); + udp[2] = ee16(UDP_HEADER_LEN); udp[3] = 0; + } + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + ck_assert_uint_gt(last_frame_sent_size, 0); + /* TTL in forwarded frame must be decremented to 63 */ + sent_ip_ttl = last_frame_sent + ETH_HEADER_LEN + 8; /* IP TTL offset */ + ck_assert_uint_eq(*sent_ip_ttl, 63); +} +END_TEST + +/* ========================================================================= + * ip_recv: TTL=1 short-frame — dropped before TTL-exceeded sent + * ========================================================================= + * Branch: ip->ttl <= 1 && len < ETH_HEADER_LEN + ip_hlen + 8 → return + */ +START_TEST(test_ip_recv_forward_ttl1_short_frame_dropped) +{ + struct wolfIP s; + /* Frame too short: ETH + IP only, missing the required 8 transport bytes */ + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 primary_ip = 0x0A000001U; + ip4 secondary_ip = 0xC0A80101U; + ip4 dest_ip = 0xC0A80155U; + ip4 src_ip = 0x0A000002U; + + setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->ttl = 1; /* TTL == 1 → would send TTL exceeded if frame ok */ + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN); + ip->src = ee32(src_ip); + ip->dst = ee32(dest_ip); + fix_ip_checksum(ip); + + /* Pass only ETH+IP — total 34 bytes, missing the 8 transport bytes + * required by wolfIP_send_ttl_exceeded */ + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + /* Short frame: TTL-exceeded must NOT have been sent */ + ck_assert_uint_eq(last_frame_sent_size, 0); +} +END_TEST + +/* ========================================================================= + * ip_recv: dest matches own IP on secondary interface → is_local=1, no fwd + * ========================================================================= + * Branch: conf->ip == dest (in the loop) → is_local = 1 + */ +START_TEST(test_ip_recv_dest_matches_secondary_iface_ip_is_local) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 primary_ip = 0x0A000001U; + ip4 secondary_ip = 0xC0A80101U; + ip4 remote_src = 0x0A000002U; + + setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip); + wolfIP_filter_set_callback(NULL, NULL); + + ts = udp_new_socket(&s); + ck_assert_ptr_nonnull(ts); + ts->src_port = 1234; + ts->local_ip = secondary_ip; /* listening on secondary IP */ + + /* Inject on primary iface, dst=secondary IP → local, not forwarded */ + inject_udp_datagram(&s, TEST_PRIMARY_IF, remote_src, secondary_ip, + 9999, 1234, NULL, 0); + + /* Must be delivered locally */ + ck_assert_int_ne(ts->events & CB_EVENT_READABLE, 0); +} +END_TEST + +/* ========================================================================= + * ip_recv: multicast destination (not broadcast, not own IP) — no forwarding + * ========================================================================= + * Branch (under WOLFIP_ENABLE_FORWARDING): dest is multicast → not in is_local + * loop, not broadcast, forwarding check runs but forward_interface returns -1. + * Packet is not forwarded; without IP_MULTICAST it is also not delivered. + */ +START_TEST(test_ip_recv_multicast_dst_not_forwarded) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 primary_ip = 0x0A000001U; + ip4 secondary_ip = 0xC0A80101U; + ip4 mcast_dst = 0xE0000001U; /* 224.0.0.1 — not joined */ + ip4 src_ip = 0x0A000002U; + + setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN); + ip->src = ee32(src_ip); + ip->dst = ee32(mcast_dst); + fix_ip_checksum(ip); + { + uint16_t *udp = (uint16_t *)(frame + ETH_HEADER_LEN + IP_HEADER_LEN); + udp[0] = ee16(9999); udp[1] = ee16(1234); + udp[2] = ee16(UDP_HEADER_LEN); udp[3] = 0; + } + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + /* Multicast not joined and not a broadcast subnet; nothing forwarded */ + ck_assert_uint_eq(last_frame_sent_size, 0); +} +END_TEST + +/* ========================================================================= + * arp_recv: htype != 1 — ARP packet silently dropped + * ========================================================================= + * Branch: arp->htype != ee16(1) → return (at the compound validation check) + */ +START_TEST(test_arp_recv_htype_not_ethernet_dropped) +{ + struct wolfIP s; + struct arp_packet arp; + struct wolfIP_ll_dev *ll; + struct ipconf *conf; + ip4 sender_ip = 0x0A000099U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + conf = wolfIP_ipconf_at(&s, TEST_PRIMARY_IF); + + build_valid_arp_request(&arp, ll, conf->ip, sender_ip); + /* Corrupt htype: 6 = IEEE 802 hardware type (not Ethernet) */ + arp.htype = ee16(6); + + last_frame_sent_size = 0; + arp_recv(&s, TEST_PRIMARY_IF, &arp, sizeof(arp)); + + /* No reply, no neighbor cached */ + ck_assert_uint_eq(last_frame_sent_size, 0); + ck_assert_int_lt(arp_neighbor_index(&s, TEST_PRIMARY_IF, sender_ip), 0); +} +END_TEST + +/* ========================================================================= + * arp_recv: ptype != 0x0800 — not IPv4, dropped + * ========================================================================= + * Branch: arp->ptype != ee16(0x0800) → return + */ +START_TEST(test_arp_recv_ptype_not_ipv4_dropped) +{ + struct wolfIP s; + struct arp_packet arp; + struct wolfIP_ll_dev *ll; + struct ipconf *conf; + ip4 sender_ip = 0x0A000098U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + conf = wolfIP_ipconf_at(&s, TEST_PRIMARY_IF); + + build_valid_arp_request(&arp, ll, conf->ip, sender_ip); + /* ptype = 0x0806 = ARP protocol type (wrong, we want IPv4 = 0x0800) */ + arp.ptype = ee16(0x86DD); /* IPv6 — not accepted */ + + last_frame_sent_size = 0; + arp_recv(&s, TEST_PRIMARY_IF, &arp, sizeof(arp)); + + ck_assert_uint_eq(last_frame_sent_size, 0); + ck_assert_int_lt(arp_neighbor_index(&s, TEST_PRIMARY_IF, sender_ip), 0); +} +END_TEST + +/* ========================================================================= + * arp_recv: hlen != 6 — hardware address length mismatch, dropped + * ========================================================================= + * Branch: arp->hlen != 6 → return + */ +START_TEST(test_arp_recv_hlen_not_6_dropped) +{ + struct wolfIP s; + struct arp_packet arp; + struct wolfIP_ll_dev *ll; + struct ipconf *conf; + ip4 sender_ip = 0x0A000097U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + conf = wolfIP_ipconf_at(&s, TEST_PRIMARY_IF); + + build_valid_arp_request(&arp, ll, conf->ip, sender_ip); + arp.hlen = 8; /* 8 bytes — not standard Ethernet (6) */ + + last_frame_sent_size = 0; + arp_recv(&s, TEST_PRIMARY_IF, &arp, sizeof(arp)); + + ck_assert_uint_eq(last_frame_sent_size, 0); + ck_assert_int_lt(arp_neighbor_index(&s, TEST_PRIMARY_IF, sender_ip), 0); +} +END_TEST + +/* ========================================================================= + * arp_recv: plen != 4 — protocol address length mismatch, dropped + * ========================================================================= + * Branch: arp->plen != 4 → return + */ +START_TEST(test_arp_recv_plen_not_4_dropped) +{ + struct wolfIP s; + struct arp_packet arp; + struct wolfIP_ll_dev *ll; + struct ipconf *conf; + ip4 sender_ip = 0x0A000096U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + conf = wolfIP_ipconf_at(&s, TEST_PRIMARY_IF); + + build_valid_arp_request(&arp, ll, conf->ip, sender_ip); + arp.plen = 6; /* 6 bytes — not standard IPv4 (4) */ + + last_frame_sent_size = 0; + arp_recv(&s, TEST_PRIMARY_IF, &arp, sizeof(arp)); + + ck_assert_uint_eq(last_frame_sent_size, 0); + ck_assert_int_lt(arp_neighbor_index(&s, TEST_PRIMARY_IF, sender_ip), 0); +} +END_TEST + +/* ========================================================================= + * arp_recv: sender IP == our configured IP — rejected (own-address poisoning) + * ========================================================================= + * Branch: sip == conf->ip → skip cache update (in ARP REQUEST handler) + * Without this check an attacker can poison our own ARP entry. + */ +START_TEST(test_arp_recv_sender_own_ip_rejected) +{ + struct wolfIP s; + struct arp_packet arp; + struct wolfIP_ll_dev *ll; + struct ipconf *conf; + ip4 own_ip = 0x0A000001U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, own_ip, 0xFFFFFF00U, 0); + + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + conf = wolfIP_ipconf_at(&s, TEST_PRIMARY_IF); + + /* ARP request targeting our IP, but sender claims to also own our IP */ + build_valid_arp_request(&arp, ll, conf->ip, own_ip); + + arp_recv(&s, TEST_PRIMARY_IF, &arp, sizeof(arp)); + + /* Our own IP must never be cached as a neighbor */ + ck_assert_int_lt(arp_neighbor_index(&s, TEST_PRIMARY_IF, own_ip), 0); +} +END_TEST + +/* ========================================================================= + * arp_recv: sender IP == IPADDR_ANY (0.0.0.0) — rejected (probe-like) + * ========================================================================= + * Branch: sip == IPADDR_ANY → skip cache update in REQUEST handler + * ARP probes (RFC 5227) use 0.0.0.0 as sender and must not be cached. + */ +START_TEST(test_arp_recv_sender_ipaddr_any_rejected) +{ + struct wolfIP s; + struct arp_packet arp; + struct wolfIP_ll_dev *ll; + struct ipconf *conf; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + conf = wolfIP_ipconf_at(&s, TEST_PRIMARY_IF); + + build_valid_arp_request(&arp, ll, conf->ip, IPADDR_ANY); + + arp_recv(&s, TEST_PRIMARY_IF, &arp, sizeof(arp)); + + /* 0.0.0.0 must not appear in the neighbor cache */ + ck_assert_int_lt(arp_neighbor_index(&s, TEST_PRIMARY_IF, IPADDR_ANY), 0); +} +END_TEST + +/* ========================================================================= + * arp_recv: sender IP == limited broadcast (255.255.255.255) — rejected + * ========================================================================= + * Branch: wolfIP_ip_is_broadcast(s, sip) → skip cache update + * Checked in ARP REPLY path (sender is broadcast → return early). + */ +START_TEST(test_arp_recv_reply_sender_broadcast_rejected) +{ + struct wolfIP s; + struct arp_packet arp; + struct wolfIP_ll_dev *ll; + static const uint8_t bcast_mac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + + memset(&arp, 0, sizeof(arp)); + memcpy(arp.eth.dst, ll->mac, 6); + memcpy(arp.eth.src, bcast_mac, 6); + arp.eth.type = ee16(ETH_TYPE_ARP); + arp.htype = ee16(1); + arp.ptype = ee16(0x0800); + arp.hlen = 6; + arp.plen = 4; + arp.opcode = ee16(ARP_REPLY); + memcpy(arp.sma, bcast_mac, 6); + arp.sip = ee32(0xFFFFFFFFU); /* broadcast sender IP in REPLY */ + memset(arp.tma, 0, 6); + arp.tip = ee32(0x0A000001U); + + arp_recv(&s, TEST_PRIMARY_IF, &arp, sizeof(arp)); + + /* 255.255.255.255 must not be cached */ + ck_assert_int_lt(arp_neighbor_index(&s, TEST_PRIMARY_IF, 0xFFFFFFFFU), 0); +} +END_TEST + +/* ========================================================================= + * arp_recv: sender IP == multicast address — rejected in ARP REPLY + * ========================================================================= + * Branch: wolfIP_ip_is_multicast(sip) → return in REPLY handler + */ +START_TEST(test_arp_recv_reply_sender_multicast_rejected) +{ + struct wolfIP s; + struct arp_packet arp; + struct wolfIP_ll_dev *ll; + ip4 mcast_ip = 0xE0000001U; /* 224.0.0.1 */ + static const uint8_t mcast_mac[6] = {0x01, 0x00, 0x5E, 0x00, 0x00, 0x01}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + + memset(&arp, 0, sizeof(arp)); + memcpy(arp.eth.dst, ll->mac, 6); + memcpy(arp.eth.src, mcast_mac, 6); + arp.eth.type = ee16(ETH_TYPE_ARP); + arp.htype = ee16(1); + arp.ptype = ee16(0x0800); + arp.hlen = 6; + arp.plen = 4; + arp.opcode = ee16(ARP_REPLY); + memcpy(arp.sma, mcast_mac, 6); + arp.sip = ee32(mcast_ip); + memset(arp.tma, 0, 6); + arp.tip = ee32(0x0A000001U); + + arp_recv(&s, TEST_PRIMARY_IF, &arp, sizeof(arp)); + + /* Multicast source IP in a reply must not be cached */ + ck_assert_int_lt(arp_neighbor_index(&s, TEST_PRIMARY_IF, mcast_ip), 0); +} +END_TEST + +/* ========================================================================= + * arp_recv: sender IP == own IP in ARP REPLY — rejected (Gratuitous ARP + * from attacker claiming they own our IP via REPLY). + * ========================================================================= + * Branch: sip == conf->ip → return in REPLY handler + */ +START_TEST(test_arp_recv_reply_sender_own_ip_rejected) +{ + struct wolfIP s; + struct arp_packet arp; + struct wolfIP_ll_dev *ll; + ip4 own_ip = 0x0A000001U; + static const uint8_t attacker_mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, own_ip, 0xFFFFFF00U, 0); + + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + + memset(&arp, 0, sizeof(arp)); + memcpy(arp.eth.dst, ll->mac, 6); + memcpy(arp.eth.src, attacker_mac, 6); + arp.eth.type = ee16(ETH_TYPE_ARP); + arp.htype = ee16(1); + arp.ptype = ee16(0x0800); + arp.hlen = 6; + arp.plen = 4; + arp.opcode = ee16(ARP_REPLY); + memcpy(arp.sma, attacker_mac, 6); + arp.sip = ee32(own_ip); /* attacker claims to own our IP */ + memset(arp.tma, 0, 6); + arp.tip = ee32(own_ip); + + arp_recv(&s, TEST_PRIMARY_IF, &arp, sizeof(arp)); + + /* Own IP must not appear as a neighbor in the cache */ + ck_assert_int_lt(arp_neighbor_index(&s, TEST_PRIMARY_IF, own_ip), 0); +} +END_TEST + +/* ========================================================================= + * arp_recv: reply with IPADDR_ANY sender — rejected + * ========================================================================= + * Branch: sip == IPADDR_ANY → return in REPLY handler + */ +START_TEST(test_arp_recv_reply_sender_zero_ip_rejected) +{ + struct wolfIP s; + struct arp_packet arp; + struct wolfIP_ll_dev *ll; + static const uint8_t sender_mac[6] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + + memset(&arp, 0, sizeof(arp)); + memcpy(arp.eth.dst, ll->mac, 6); + memcpy(arp.eth.src, sender_mac, 6); + arp.eth.type = ee16(ETH_TYPE_ARP); + arp.htype = ee16(1); + arp.ptype = ee16(0x0800); + arp.hlen = 6; + arp.plen = 4; + arp.opcode = ee16(ARP_REPLY); + memcpy(arp.sma, sender_mac, 6); + arp.sip = ee32(IPADDR_ANY); /* 0.0.0.0 sender in reply */ + memset(arp.tma, 0, 6); + arp.tip = ee32(0x0A000001U); + + arp_recv(&s, TEST_PRIMARY_IF, &arp, sizeof(arp)); + + /* 0.0.0.0 must not be cached */ + ck_assert_int_lt(arp_neighbor_index(&s, TEST_PRIMARY_IF, IPADDR_ANY), 0); +} +END_TEST + +/* ========================================================================= + * arp_recv: valid ARP request IS cached when sender IP is legitimate + * ========================================================================= + * Positive test: confirms the happy path around the sender validation + * runs (sip valid → arp_store_neighbor called when pending match exists). + * We use arp_pending_record directly to bypass the arp_request rate-limit. + */ +START_TEST(test_arp_recv_valid_request_caches_neighbor_when_pending) +{ + struct wolfIP s; + struct arp_packet arp; + struct wolfIP_ll_dev *ll; + struct ipconf *conf; + ip4 sender_ip = 0x0A000020U; + static const uint8_t sender_mac[6] = {0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x01}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + s.last_tick = 5000; /* advance tick so arp_request rate-limit does not fire */ + + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + conf = wolfIP_ipconf_at(&s, TEST_PRIMARY_IF); + + /* Pre-register a pending ARP request so learn path is triggered. + * arp_pending_record is the low-level helper; we use it directly. */ + arp_pending_record(&s, TEST_PRIMARY_IF, sender_ip); + + build_valid_arp_request(&arp, ll, conf->ip, sender_ip); + memcpy(arp.sma, sender_mac, 6); + + last_frame_sent_size = 0; + arp_recv(&s, TEST_PRIMARY_IF, &arp, sizeof(arp)); + + /* A reply should have been sent */ + ck_assert_uint_gt(last_frame_sent_size, 0); + /* Neighbor must now be in the cache */ + ck_assert_int_ge(arp_neighbor_index(&s, TEST_PRIMARY_IF, sender_ip), 0); +} +END_TEST + +/* ========================================================================= + * arp_recv: runt packet (too short) — dropped + * ========================================================================= + * Branch: len < sizeof(struct arp_packet) → return + */ +START_TEST(test_arp_recv_runt_packet_dropped) +{ + struct wolfIP s; + struct arp_packet arp; + struct wolfIP_ll_dev *ll; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + + memset(&arp, 0, sizeof(arp)); + memcpy(arp.eth.dst, ll->mac, 6); + + last_frame_sent_size = 0; + /* Pass only 10 bytes — much less than sizeof(arp_packet) */ + arp_recv(&s, TEST_PRIMARY_IF, &arp, 10); + + ck_assert_uint_eq(last_frame_sent_size, 0); +} +END_TEST + +/* ========================================================================= + * ip_recv: IP version != 4 — dropped + * ========================================================================= + * Branch: version != 4 → return + */ +START_TEST(test_ip_recv_wrong_version_dropped_v6) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = udp_new_socket(&s); + ck_assert_ptr_nonnull(ts); + ts->src_port = 1234; + ts->local_ip = local_ip; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x65; /* version=6 (IPv6!), IHL=5 */ + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN); + ip->src = ee32(remote_ip); + ip->dst = ee32(local_ip); + ip->csum = 0; + /* do NOT fix checksum — we want version check to fire first */ + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + /* Version != 4 must be dropped */ + ck_assert_ptr_eq(fifo_peek(&ts->sock.udp.rxbuf), NULL); +} +END_TEST + +/* ========================================================================= + * ip_recv: IP header length < 20 — dropped + * ========================================================================= + * Branch: ip_hlen < IP_HEADER_LEN (i.e. IHL < 5) → return + */ +START_TEST(test_ip_recv_ihl_too_small_dropped) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = udp_new_socket(&s); + ck_assert_ptr_nonnull(ts); + ts->src_port = 1234; + ts->local_ip = local_ip; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x44; /* version=4, IHL=4 → 16 bytes < 20 */ + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN); + ip->src = ee32(remote_ip); + ip->dst = ee32(local_ip); + ip->csum = 0; + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + ck_assert_ptr_eq(fifo_peek(&ts->sock.udp.rxbuf), NULL); +} +END_TEST + +/* ========================================================================= + * ip_recv: ip->len < ip_hlen — dropped (ip length shorter than header) + * ========================================================================= + * Branch: ee16(ip->len) < ip_hlen → return + */ +START_TEST(test_ip_recv_ip_len_less_than_hlen_dropped) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = udp_new_socket(&s); + ck_assert_ptr_nonnull(ts); + ts->src_port = 1234; + ts->local_ip = local_ip; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; /* IHL=5, header=20 */ + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(10); /* ip->len=10 < ip_hlen=20 → drop */ + ip->src = ee32(remote_ip); + ip->dst = ee32(local_ip); + ip->csum = 0; + iphdr_set_checksum(ip); + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + ck_assert_ptr_eq(fifo_peek(&ts->sock.udp.rxbuf), NULL); +} +END_TEST + +/* ========================================================================= + * ip_recv: bad IP checksum — dropped + * ========================================================================= + * Branch: iphdr_verify_checksum(ip) != 0 → return + */ +START_TEST(test_ip_recv_bad_ip_checksum_dropped) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = udp_new_socket(&s); + ck_assert_ptr_nonnull(ts); + ts->src_port = 1234; + ts->local_ip = local_ip; + + { + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN); + ip->src = ee32(remote_ip); + ip->dst = ee32(local_ip); + ip->csum = ee16(0xDEAD); /* deliberately wrong */ + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + } + + ck_assert_ptr_eq(fifo_peek(&ts->sock.udp.rxbuf), NULL); +} +END_TEST + +#ifdef IP_MULTICAST +/* ========================================================================= + * ip_recv (MULTICAST): dst is multicast not joined on ingress iface — dropped + * ========================================================================= + * Branch (wolfIP_recv_on): !mcast_is_joined && dst != IGMPV3_REPORT_DST && + * dst != IGMP_ALL_HOSTS → return (not for us) + * This covers the multicast Ethernet demux gate in wolfIP_recv_on. + */ +START_TEST(test_ip_recv_multicast_not_joined_dropped) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + 4]; + struct wolfIP_udp_datagram *udp = (struct wolfIP_udp_datagram *)frame; + ip4 local_ip = 0x0A000001U; + ip4 src_ip = 0x0A000002U; + ip4 mcast_grp = 0xEF010203U; /* 239.1.2.3 — not joined */ + uint8_t mcast_eth[6]; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + /* No IGMP join for mcast_grp */ + + /* Build Ethernet frame with multicast dst MAC */ + mcast_ip_to_eth(mcast_grp, mcast_eth); + memset(frame, 0, sizeof(frame)); + memcpy(udp->ip.eth.dst, mcast_eth, 6); + memcpy(udp->ip.eth.src, "\x01\x02\x03\x04\x05\x06", 6); + udp->ip.eth.type = ee16(ETH_TYPE_IP); + udp->ip.ver_ihl = 0x45; + udp->ip.ttl = 64; + udp->ip.proto = WI_IPPROTO_UDP; + udp->ip.len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN + 4); + udp->ip.src = ee32(src_ip); + udp->ip.dst = ee32(mcast_grp); + udp->src_port = ee16(9999); + udp->dst_port = ee16(1234); + udp->len = ee16(UDP_HEADER_LEN + 4); + memcpy(udp->data, "test", 4); + fix_udp_checksums(udp); + + last_frame_sent_size = 0; + wolfIP_recv_on(&s, TEST_PRIMARY_IF, frame, (uint32_t)sizeof(frame)); + + /* Not joined → must be silently dropped at ETH demux */ + ck_assert_uint_eq(last_frame_sent_size, 0); +} +END_TEST +#endif /* IP_MULTICAST */ diff --git a/src/test/unit/unit_tests_misc_edges.c b/src/test/unit/unit_tests_misc_edges.c new file mode 100644 index 00000000..23553e2e --- /dev/null +++ b/src/test/unit/unit_tests_misc_edges.c @@ -0,0 +1,1255 @@ +/* unit_tests_misc_edges.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +/* ===================================================================== + * wolfIP_init -- NULL stack pointer branch + * ===================================================================== */ +START_TEST(test_wolfip_init_null_stack) +{ + wolfIP_init(NULL); + ck_assert(1); +} +END_TEST + +/* ===================================================================== + * wolfIP_init_static -- NULL output pointer branch + * ===================================================================== */ +START_TEST(test_wolfip_init_static_null_ptr) +{ +#ifndef WOLFIP_NOSTATIC + wolfIP_init_static(NULL); + ck_assert(1); +#endif +} +END_TEST + +/* ===================================================================== + * wolfIP_ipconfig_set_ex -- NULL stack + * ===================================================================== */ +START_TEST(test_wolfip_ipconfig_set_ex_null_stack) +{ + wolfIP_ipconfig_set_ex(NULL, 0, 0x0a000001U, 0xffffff00U, 0); + ck_assert(1); +} +END_TEST + +START_TEST(test_wolfip_ipconfig_set_ex_bad_ifidx) +{ + struct wolfIP s; + wolfIP_init(&s); + wolfIP_ipconfig_set_ex(&s, WOLFIP_MAX_INTERFACES + 5, 0x0a000001U, 0xffffff00U, 0); + ck_assert(1); +} +END_TEST + +/* ===================================================================== + * wolfIP_ipconfig_get_ex -- NULL stack / NULL output pointers + * ===================================================================== */ +START_TEST(test_wolfip_ipconfig_get_ex_null_stack) +{ + ip4 ip = 1; + ip4 mask = 1; + ip4 gw = 1; + wolfIP_ipconfig_get_ex(NULL, 0, &ip, &mask, &gw); + ck_assert_int_eq((int)ip, 1); +} +END_TEST + +START_TEST(test_wolfip_ipconfig_get_ex_null_out_ptrs) +{ + struct wolfIP s; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + wolfIP_ipconfig_get_ex(&s, TEST_PRIMARY_IF, NULL, NULL, NULL); + ck_assert(1); +} +END_TEST + +/* ===================================================================== + * wolfIP_mtu_set -- zero, below min, above max branches + * ===================================================================== */ +START_TEST(test_wolfip_mtu_set_zero_resets_to_default) +{ + struct wolfIP s; + uint32_t mtu = 0; + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_mtu_set(&s, TEST_PRIMARY_IF, 0), 0); + wolfIP_mtu_get(&s, TEST_PRIMARY_IF, &mtu); + ck_assert_uint_eq(mtu, LINK_MTU); +} +END_TEST + +START_TEST(test_wolfip_mtu_set_below_min_clamps) +{ + struct wolfIP s; + uint32_t mtu = 0; + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_mtu_set(&s, TEST_PRIMARY_IF, LINK_MTU_MIN - 1), 0); + wolfIP_mtu_get(&s, TEST_PRIMARY_IF, &mtu); + ck_assert_uint_eq(mtu, LINK_MTU_MIN); +} +END_TEST + +START_TEST(test_wolfip_mtu_set_above_max_clamps) +{ + struct wolfIP s; + uint32_t mtu = 0; + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_mtu_set(&s, TEST_PRIMARY_IF, LINK_MTU + 1000), 0); + wolfIP_mtu_get(&s, TEST_PRIMARY_IF, &mtu); + ck_assert_uint_eq(mtu, LINK_MTU); +} +END_TEST + +START_TEST(test_wolfip_mtu_get_null_mtu_ptr) +{ + struct wolfIP s; + int ret; + wolfIP_init(&s); + mock_link_init(&s); + ret = wolfIP_mtu_get(&s, TEST_PRIMARY_IF, NULL); + ck_assert_int_lt(ret, 0); +} +END_TEST + +/* ===================================================================== + * wolfIP_arp_lookup_ex -- NULL stack, NULL mac, success branches + * ===================================================================== */ +#ifdef ETHERNET +START_TEST(test_wolfip_arp_lookup_ex_null_stack) +{ + uint8_t mac[6] = {0}; + int ret = wolfIP_arp_lookup_ex(NULL, 0, 0x0a000002U, mac); + ck_assert_int_lt(ret, 0); +} +END_TEST + +START_TEST(test_wolfip_arp_lookup_ex_null_mac) +{ + struct wolfIP s; + int ret; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + ret = wolfIP_arp_lookup_ex(&s, TEST_PRIMARY_IF, 0x0a000002U, NULL); + ck_assert_int_lt(ret, 0); +} +END_TEST + +START_TEST(test_wolfip_arp_lookup_ex_found) +{ + struct wolfIP s; + ip4 ip = 0x0a000002U; + static const uint8_t stored_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + uint8_t mac[6] = {0}; + int ret; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + s.arp.neighbors[0].ip = ip; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, stored_mac, 6); + s.arp.neighbors[0].ts = s.last_tick; + ret = wolfIP_arp_lookup_ex(&s, TEST_PRIMARY_IF, ip, mac); + ck_assert_int_eq(ret, 0); + ck_assert_mem_eq(mac, stored_mac, 6); +} +END_TEST +#endif /* ETHERNET */ + +/* ===================================================================== + * fifo_push -- h_wrap defensive branches + * ===================================================================== */ +START_TEST(test_fifo_push_full_hwrap_head_eq_tail) +{ + /* head==tail and h_wrap!=0: space=0 */ + uint8_t buf[128]; + uint8_t data[8] = {1,2,3,4,5,6,7,8}; + struct fifo f; + int ret; + memset(&f, 0, sizeof(f)); + f.data = buf; + f.size = 128; + f.h_wrap = 128; + f.head = 0; + f.tail = 0; + ret = fifo_push(&f, data, sizeof(data)); + ck_assert_int_eq(ret, -1); +} +END_TEST + +START_TEST(test_fifo_push_hwrap_head_ge_tail_space_zero) +{ + /* h_wrap set, head > tail → space=0 */ + uint8_t buf[128]; + uint8_t data[8] = {0}; + struct fifo f; + int ret; + memset(&f, 0, sizeof(f)); + f.data = buf; + f.size = 128; + f.h_wrap = 128; + f.head = 64; + f.tail = 32; + ret = fifo_push(&f, data, sizeof(data)); + ck_assert_int_eq(ret, -1); +} +END_TEST + +START_TEST(test_fifo_push_no_end_space_tail_too_small) +{ + /* h_wrap==0, head >= tail, end_space < needed, tail < needed */ + uint8_t buf[64]; + uint8_t data[8] = {0}; + struct fifo f; + int ret; + memset(&f, 0, sizeof(f)); + f.data = buf; + f.size = 64; + f.head = 60; + f.tail = 4; + f.h_wrap = 0; + ret = fifo_push(&f, data, sizeof(data)); + ck_assert_int_eq(ret, -1); +} +END_TEST + +/* ===================================================================== + * fifo_can_push_len -- hwrap branch returns 0 + * ===================================================================== */ +START_TEST(test_fifo_can_push_len_hwrap_head_plus_needed_gt_tail) +{ + uint8_t buf[128]; + struct fifo f; + int ret; + memset(&f, 0, sizeof(f)); + f.data = buf; + f.size = 128; + f.h_wrap = 128; + f.head = 50; + f.tail = 56; + ret = fifo_can_push_len(&f, 32); + ck_assert_int_eq(ret, 0); +} +END_TEST + +START_TEST(test_fifo_can_push_len_no_hwrap_head_plus_needed_gt_size) +{ + uint8_t buf[64]; + struct fifo f; + int ret; + memset(&f, 0, sizeof(f)); + f.data = buf; + f.size = 64; + f.head = 60; + f.tail = 0; + f.h_wrap = 0; + ret = fifo_can_push_len(&f, 40); + ck_assert_int_eq(ret, 0); +} +END_TEST + +/* ===================================================================== + * fifo_next -- pos out of range, desc->len too large + * ===================================================================== */ +START_TEST(test_fifo_next_pos_out_of_range) +{ + uint8_t buf[128]; + struct fifo f; + struct pkt_desc *desc; + struct pkt_desc *ret; + memset(&f, 0, sizeof(f)); + f.data = buf; + f.size = 128; + /* desc pointer outside the buffer */ + desc = (struct pkt_desc *)(buf + 200); + ret = fifo_next(&f, desc); + ck_assert_ptr_null(ret); +} +END_TEST + +START_TEST(test_fifo_next_desc_len_too_large) +{ + uint8_t buf[128]; + struct fifo f; + struct pkt_desc *d; + struct pkt_desc *ret; + memset(&f, 0, sizeof(f)); + f.data = buf; + f.size = 128; + f.head = 64; + f.tail = 0; + d = (struct pkt_desc *)buf; + d->len = 50000; + d->pos = 0; + ret = fifo_next(&f, d); + ck_assert_ptr_null(ret); +} +END_TEST + +/* ===================================================================== + * fifo_len -- tail > head with h_wrap > 0 branch + * ===================================================================== */ +START_TEST(test_fifo_len_tail_gt_head_with_hwrap) +{ + uint8_t buf[128]; + struct fifo f; + uint32_t len; + memset(&f, 0, sizeof(f)); + f.data = buf; + f.size = 128; + f.tail = 80; + f.head = 20; + f.h_wrap = 100; + f.last_valid = 1; + f.last_pos = 0; + len = fifo_len(&f); + /* len = (100 - 80) + 20 = 40 */ + ck_assert_uint_eq(len, 40); +} +END_TEST + +/* ===================================================================== + * iphdr_verify_checksum -- bad and good checksum + * ===================================================================== */ +START_TEST(test_iphdr_verify_checksum_bad) +{ + struct wolfIP s; + uint8_t framebuf[ETH_HEADER_LEN + IP_HEADER_LEN + 8]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)framebuf; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + memset(framebuf, 0, sizeof(framebuf)); + ip->ver_ihl = 0x45; + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + 8); + ip->src = ee32(0x0a0000a1U); + ip->dst = ee32(0x0a000001U); + ip->csum = 0xDEAD; + wolfIP_recv(&s, framebuf, sizeof(framebuf)); + ck_assert(1); +} +END_TEST + +START_TEST(test_iphdr_verify_checksum_good) +{ + struct wolfIP s; + uint8_t framebuf[ETH_HEADER_LEN + IP_HEADER_LEN + 8]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)framebuf; + struct wolfIP_ll_dev *ll; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + memset(framebuf, 0, sizeof(framebuf)); + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + memcpy(ip->eth.dst, ll->mac, 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + 8); + ip->src = ee32(0x0a0000a1U); + ip->dst = ee32(0x0a000001U); + ip->csum = 0; + iphdr_set_checksum(ip); + wolfIP_recv(&s, framebuf, sizeof(framebuf)); + ck_assert(1); +} +END_TEST + +/* ===================================================================== + * wolfIP_ip_is_broadcast -- edge cases + * ===================================================================== */ +START_TEST(test_wolfip_ip_is_broadcast_null_stack) +{ + int r = wolfIP_ip_is_broadcast(NULL, 0x0a000001U); + ck_assert_int_eq(r, 0); +} +END_TEST + +START_TEST(test_wolfip_ip_is_broadcast_all_ones) +{ + struct wolfIP s; + int r; + wolfIP_init(&s); + mock_link_init(&s); + r = wolfIP_ip_is_broadcast(&s, 0xFFFFFFFFU); + ck_assert_int_eq(r, 1); +} +END_TEST + +START_TEST(test_wolfip_ip_is_broadcast_full_mask_skipped) +{ + struct wolfIP s; + int r; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xFFFFFFFFU, 0); + r = wolfIP_ip_is_broadcast(&s, 0x0a0000FFU); + ck_assert_int_eq(r, 0); +} +END_TEST + +/* ===================================================================== + * wolfIP_select_nexthop -- NULL conf, broadcast branches + * ===================================================================== */ +START_TEST(test_wolfip_select_nexthop_null_conf) +{ + ip4 dest = 0x0a000002U; + ip4 ret = wolfIP_select_nexthop(NULL, dest); + ck_assert_uint_eq(ret, dest); +} +END_TEST + +START_TEST(test_wolfip_select_nexthop_broadcast) +{ + struct ipconf conf; + ip4 ret; + memset(&conf, 0, sizeof(conf)); + conf.ip = 0x0a000001U; + conf.mask = 0xffffff00U; + conf.gw = 0x0a0000feU; + ret = wolfIP_select_nexthop(&conf, 0xFFFFFFFFU); + ck_assert_uint_eq(ret, 0xFFFFFFFFU); +} +END_TEST + +/* ===================================================================== + * eth_is_ipv4_multicast_mac / mcast_membership_find / udp_socket_has_mcast + * ===================================================================== */ +#ifdef IP_MULTICAST +START_TEST(test_eth_is_ipv4_multicast_mac_true) +{ + uint8_t mac[6] = {0x01, 0x00, 0x5e, 0x00, 0x01, 0x01}; + ck_assert_int_eq(eth_is_ipv4_multicast_mac(mac), 1); +} +END_TEST + +START_TEST(test_eth_is_ipv4_multicast_mac_high_bit_set) +{ + uint8_t mac[6] = {0x01, 0x00, 0x5e, 0x80, 0x01, 0x01}; + ck_assert_int_eq(eth_is_ipv4_multicast_mac(mac), 0); +} +END_TEST + +START_TEST(test_eth_is_ipv4_multicast_mac_wrong_prefix) +{ + uint8_t mac[6] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + ck_assert_int_eq(eth_is_ipv4_multicast_mac(mac), 0); +} +END_TEST + +START_TEST(test_mcast_membership_find_null_stack) +{ + struct wolfIP_mcast_membership *ret = + mcast_membership_find(NULL, 0, 0xe0000001U); + ck_assert_ptr_null(ret); +} +END_TEST + +START_TEST(test_udp_socket_has_mcast_null_tsocket) +{ + int ret = udp_socket_has_mcast(NULL, 0, 0xe0000001U); + ck_assert_int_eq(ret, 0); +} +END_TEST +#endif /* IP_MULTICAST */ + +/* ===================================================================== + * wolfIP_ip_is_multicast -- boundary values + * ===================================================================== */ +START_TEST(test_wolfip_ip_is_multicast_boundary) +{ + ck_assert_int_ne(wolfIP_ip_is_multicast(0xE0000000U), 0); + ck_assert_int_eq(wolfIP_ip_is_multicast(0xDFFFFFFFU), 0); + ck_assert_int_ne(wolfIP_ip_is_multicast(0xEFFFFFFFU), 0); + ck_assert_int_eq(wolfIP_ip_is_multicast(0xF0000000U), 0); +} +END_TEST + +/* ===================================================================== + * close_socket -- NULL and non-TCP/UDP variants + * ===================================================================== */ +START_TEST(test_close_socket_null) +{ + close_socket(NULL); + ck_assert(1); +} +END_TEST + +START_TEST(test_close_socket_non_tcp_udp) +{ + /* ICMP tsocket: close_socket with proto == WI_IPPROTO_ICMP */ + struct wolfIP s; + struct tsocket *ts; + int fd; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + /* Use DGRAM + ICMP to get an icmpsocket (a tsocket) */ + fd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_ge(fd, 0); + ts = wolfIP_socket_from_fd(&s, fd); + ck_assert_ptr_nonnull(ts); + close_socket(ts); + ck_assert(1); +} +END_TEST + +/* ===================================================================== + * tx_has_writable_space -- unknown proto and NULL + * ===================================================================== */ +START_TEST(test_tx_has_writable_space_unknown_proto) +{ + struct tsocket ts; + int ret; + memset(&ts, 0, sizeof(ts)); + ts.proto = 0xFF; + ret = tx_has_writable_space(&ts); + ck_assert_int_eq(ret, 0); +} +END_TEST + +START_TEST(test_tx_has_writable_space_null) +{ + int ret = tx_has_writable_space(NULL); + ck_assert_int_eq(ret, 0); +} +END_TEST + +/* ===================================================================== + * bind_port_in_use -- port==0 early return + * ===================================================================== */ +START_TEST(test_bind_port_in_use_port_zero) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in sin; + int fd1; + int fd2; + int ret; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + fd1 = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + fd2 = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_ge(fd1, 0); + ck_assert_int_ge(fd2, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(80); + sin.sin_addr.s_addr = ee32(0x0a000001U); + ck_assert_int_eq(wolfIP_sock_bind(&s, fd1, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), 0); + sin.sin_port = 0; + ret = wolfIP_sock_bind(&s, fd2, (struct wolfIP_sockaddr *)&sin, sizeof(sin)); + ck_assert_int_eq(ret, 0); +} +END_TEST + +/* ===================================================================== + * arp_pending_record -- refresh existing entry, replace oldest + * ===================================================================== */ +#ifdef ETHERNET +START_TEST(test_arp_pending_record_refresh_existing) +{ + struct wolfIP s; + ip4 ip = 0x0a000002U; + int i; + int found = 0; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + s.last_tick = 0; + arp_pending_record(&s, TEST_PRIMARY_IF, ip); + s.last_tick = 100; + arp_pending_record(&s, TEST_PRIMARY_IF, ip); + for (i = 0; i < WOLFIP_ARP_PENDING_MAX; i++) { + if (s.arp.pending[i].ip == ip) { + ck_assert_uint_eq((uint32_t)s.arp.pending[i].ts, 100); + found = 1; + break; + } + } + ck_assert_int_eq(found, 1); +} +END_TEST + +START_TEST(test_arp_pending_record_replaces_oldest) +{ + struct wolfIP s; + ip4 new_ip = 0x0a0000FFU; + int i; + int found = 0; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + s.last_tick = 1000; + for (i = 0; i < WOLFIP_ARP_PENDING_MAX; i++) { + s.arp.pending[i].ip = (ip4)(0x0a000010U + (uint32_t)i); + s.arp.pending[i].if_idx = TEST_PRIMARY_IF; + s.arp.pending[i].ts = (uint64_t)(100 * (i + 1)); + } + s.last_tick = 2000; + arp_pending_record(&s, TEST_PRIMARY_IF, new_ip); + for (i = 0; i < WOLFIP_ARP_PENDING_MAX; i++) { + if (s.arp.pending[i].ip == new_ip) { + found = 1; + break; + } + } + ck_assert_int_eq(found, 1); +} +END_TEST + +/* ===================================================================== + * arp_pending_match_and_clear -- expire stale entries, NULL stack + * ===================================================================== */ +START_TEST(test_arp_pending_match_and_clear_expires_stale) +{ + struct wolfIP s; + ip4 target = 0x0a000002U; + int ret; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + s.arp.pending[0].ip = target; + s.arp.pending[0].if_idx = TEST_PRIMARY_IF; + s.arp.pending[0].ts = 0; + s.last_tick = (uint64_t)ARP_PENDING_TTL_MS + 1000; + ret = arp_pending_match_and_clear(&s, TEST_PRIMARY_IF, target); + ck_assert_int_eq(ret, 0); + ck_assert_uint_eq(s.arp.pending[0].ip, IPADDR_ANY); +} +END_TEST + +START_TEST(test_arp_pending_match_and_clear_null_stack) +{ + int ret = arp_pending_match_and_clear(NULL, 0, 0x0a000002U); + ck_assert_int_eq(ret, 0); +} +END_TEST + +/* ===================================================================== + * arp_neighbor_index -- aged-out entry, NULL stack + * ===================================================================== */ +START_TEST(test_arp_neighbor_index_aged_out) +{ + struct wolfIP s; + ip4 ip = 0x0a000002U; + static const uint8_t mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + int idx; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + s.arp.neighbors[0].ip = ip; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, mac, 6); + s.arp.neighbors[0].ts = 0; + s.last_tick = (uint64_t)ARP_AGING_TIMEOUT_MS + 1000; + idx = arp_neighbor_index(&s, TEST_PRIMARY_IF, ip); + ck_assert_int_eq(idx, -1); + ck_assert_uint_eq(s.arp.neighbors[0].ip, IPADDR_ANY); +} +END_TEST + +START_TEST(test_arp_neighbor_index_null_stack) +{ + int idx = arp_neighbor_index(NULL, 0, 0x0a000002U); + ck_assert_int_eq(idx, -1); +} +END_TEST +#endif /* ETHERNET */ + +/* ===================================================================== + * wolfIP_route_for_ip -- NULL stack, broadcast/any dest + * ===================================================================== */ +START_TEST(test_wolfip_route_for_ip_null_stack) +{ + unsigned int ret = wolfIP_route_for_ip(NULL, 0x0a000002U); + ck_assert_uint_eq(ret, 0); +} +END_TEST + +START_TEST(test_wolfip_route_for_ip_broadcast_address) +{ + struct wolfIP s; + unsigned int ret; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + ret = wolfIP_route_for_ip(&s, IPADDR_ANY); + ck_assert_uint_eq(ret, WOLFIP_PRIMARY_IF_IDX); +} +END_TEST + +/* ===================================================================== + * wolfIP_forward_interface -- NULL/single-iface, dest == local ip + * ===================================================================== */ +#if WOLFIP_ENABLE_FORWARDING +START_TEST(test_wolfip_forward_interface_null_or_single_iface) +{ + int ret = wolfIP_forward_interface(NULL, 0, 0x0a000002U); + ck_assert_int_lt(ret, 0); +} +END_TEST + +START_TEST(test_wolfip_forward_interface_local_dest_rejected) +{ + struct wolfIP s; + int ret; + wolfIP_init(&s); + mock_link_init(&s); + mock_link_init_idx(&s, TEST_SECOND_IF, NULL); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + wolfIP_ipconfig_set_ex(&s, TEST_SECOND_IF, 0x0a000101U, 0xffffff00U, 0); + ret = wolfIP_forward_interface(&s, TEST_PRIMARY_IF, 0x0a000101U); + ck_assert_int_lt(ret, 0); +} +END_TEST +#endif + +/* ===================================================================== + * wolfIP_loopback_send -- NULL ll argument + * ===================================================================== */ +#if WOLFIP_ENABLE_LOOPBACK +START_TEST(test_wolfip_loopback_send_null_ll) +{ + int ret = wolfIP_loopback_send(NULL, NULL, 0); + ck_assert_int_lt(ret, 0); +} +END_TEST +#endif + +/* ===================================================================== + * wolfIP_send_ttl_exceeded -- no send function (null ll) + * ===================================================================== */ +#if WOLFIP_ENABLE_FORWARDING && defined(ETHERNET) +START_TEST(test_wolfip_send_ttl_exceeded_null_ll) +{ + struct wolfIP s; + uint8_t framebuf[ETH_HEADER_LEN + IP_HEADER_LEN + 8]; + struct wolfIP_ip_packet *orig = (struct wolfIP_ip_packet *)framebuf; + wolfIP_init(&s); + memset(framebuf, 0, sizeof(framebuf)); + orig->ver_ihl = 0x45; + orig->ttl = 1; + orig->proto = WI_IPPROTO_UDP; + wolfIP_send_ttl_exceeded(&s, TEST_PRIMARY_IF, orig); + ck_assert(1); +} +END_TEST +#endif + +/* ===================================================================== + * wolfIP_send_port_unreachable -- no send function + * ===================================================================== */ +#ifdef ETHERNET +START_TEST(test_wolfip_send_port_unreachable_null_ll_misc) +{ + struct wolfIP s; + uint8_t framebuf[ETH_HEADER_LEN + IP_HEADER_LEN + 8]; + struct wolfIP_ip_packet *orig = (struct wolfIP_ip_packet *)framebuf; + wolfIP_init(&s); + memset(framebuf, 0, sizeof(framebuf)); + orig->ver_ihl = 0x45; + orig->proto = WI_IPPROTO_UDP; + wolfIP_send_port_unreachable(&s, TEST_PRIMARY_IF, orig); + ck_assert(1); +} +END_TEST +#endif + +/* ===================================================================== + * wolfIP_sock_socket -- ICMP success + * ===================================================================== */ +START_TEST(test_wolfip_sock_socket_icmp_success) +{ + struct wolfIP s; + int fd; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + fd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_ICMP); + ck_assert_int_ge(fd, 0); + wolfIP_sock_close(&s, fd); +} +END_TEST + +/* ===================================================================== + * wolfIP_rawsocket_from_fd -- !used branch + * ===================================================================== */ +#if WOLFIP_RAWSOCKETS +START_TEST(test_wolfip_rawsocket_from_fd_not_used) +{ + struct wolfIP s; + struct rawsocket *rs; + int fd; + wolfIP_init(&s); + mock_link_init(&s); + fd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(fd, 0); + wolfIP_sock_close(&s, fd); + rs = wolfIP_rawsocket_from_fd(&s, fd); + ck_assert_ptr_null(rs); +} +END_TEST +#endif + +/* ===================================================================== + * wolfIP_packetsocket_from_fd -- !used branch + * ===================================================================== */ +#if WOLFIP_PACKET_SOCKETS +START_TEST(test_wolfip_packetsocket_from_fd_not_used) +{ + struct wolfIP s; + struct packetsocket *ps; + int fd; + wolfIP_init(&s); + mock_link_init(&s); + fd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, 0); + ck_assert_int_ge(fd, 0); + wolfIP_sock_close(&s, fd); + ps = wolfIP_packetsocket_from_fd(&s, fd); + ck_assert_ptr_null(ps); +} +END_TEST +#endif + +/* ===================================================================== + * wolfIP_recv_on -- short frame dropped without crash + * ===================================================================== */ +START_TEST(test_wolfip_recv_on_short_frame) +{ + struct wolfIP s; + uint8_t tiny[4] = {0}; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + wolfIP_recv_on(&s, TEST_PRIMARY_IF, tiny, sizeof(tiny)); + ck_assert(1); +} +END_TEST + +/* ===================================================================== + * wolfIP_filter_mask_for_proto -- default (unknown proto) branch + * ===================================================================== */ +START_TEST(test_filter_mask_for_proto_default_branch) +{ + uint32_t old_mask = wolfIP_filter_get_mask(); + uint32_t ret; + wolfIP_filter_set_mask(0xABCD1234U); + ret = wolfIP_filter_mask_for_proto(0xFF00); + ck_assert_uint_eq(ret, 0xABCD1234U); + wolfIP_filter_set_mask(old_mask); +} +END_TEST + +/* ===================================================================== + * wolfIP_filter_dispatch -- meta==NULL branch + * ===================================================================== */ +START_TEST(test_filter_dispatch_null_meta_initializes) +{ + struct wolfIP s; + wolfIP_init(&s); + filter_cb_calls = 0; + wolfIP_filter_set_callback(test_filter_cb, NULL); + wolfIP_filter_set_mask(~0U); + wolfIP_filter_dispatch(WOLFIP_FILT_SENDING, &s, 0, NULL, 0, NULL); + ck_assert_int_ge(filter_cb_calls, 1); + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_mask(0); +} +END_TEST + +/* ===================================================================== + * wolfIP_sock_listen -- non-TCP socket returns error + * ===================================================================== */ +START_TEST(test_wolfip_sock_listen_udp_fd) +{ + struct wolfIP s; + int fd; + int ret; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + fd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_ge(fd, 0); + ret = wolfIP_sock_listen(&s, fd, 5); + ck_assert_int_lt(ret, 0); + wolfIP_sock_close(&s, fd); +} +END_TEST + +/* ===================================================================== + * wolfIP_sock_accept -- non-TCP socket returns error + * ===================================================================== */ +START_TEST(test_wolfip_sock_accept_udp_fd) +{ + struct wolfIP s; + socklen_t addrlen = sizeof(struct wolfIP_sockaddr_in); + int fd; + int ret; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + fd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_ge(fd, 0); + ret = wolfIP_sock_accept(&s, fd, NULL, &addrlen); + ck_assert_int_lt(ret, 0); + wolfIP_sock_close(&s, fd); +} +END_TEST + +/* ===================================================================== + * wolfIP_sock_close -- negative fd + * ===================================================================== */ +START_TEST(test_wolfip_sock_close_negative_fd) +{ + struct wolfIP s; + int ret; + wolfIP_init(&s); + ret = wolfIP_sock_close(&s, -1); + ck_assert_int_lt(ret, 0); +} +END_TEST + +/* ===================================================================== + * ipcounter_next -- increments and wraps + * ===================================================================== */ +START_TEST(test_ipcounter_next_increments) +{ + struct wolfIP s; + uint16_t v1; + uint16_t v2; + wolfIP_init(&s); + s.ipcounter = 0; + v1 = ipcounter_next(&s); + v2 = ipcounter_next(&s); + ck_assert_uint_eq(ee16(v1), 0); + ck_assert_uint_eq(ee16(v2), 1); +} +END_TEST + +START_TEST(test_ipcounter_next_wraps) +{ + struct wolfIP s; + wolfIP_init(&s); + s.ipcounter = 0xFFFF; + ipcounter_next(&s); + ck_assert_uint_eq(s.ipcounter, 0); +} +END_TEST + +/* ===================================================================== + * queue_insert -- len > size-1 returns error + * ===================================================================== */ +START_TEST(test_queue_insert_pos_equals_size_returns_error) +{ + uint8_t buf[8]; + uint8_t data[16]; + struct queue q; + int ret; + memset(&q, 0, sizeof(q)); + memset(data, 0xAB, sizeof(data)); + q.data = buf; + q.size = 8; + q.head = 0; + q.tail = 0; + ret = queue_insert(&q, data, 0, 16); + ck_assert_int_lt(ret, 0); +} +END_TEST + +/* ===================================================================== + * Full TCP socket table -- exercises boundary check + * ===================================================================== */ +START_TEST(test_wolfip_sock_socket_tcp_all_sockets) +{ + struct wolfIP s; + int fds[MAX_TCPSOCKETS]; + int count = 0; + int extra; + int i; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + for (i = 0; i < MAX_TCPSOCKETS; i++) { + fds[i] = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + if (fds[i] < 0) + break; + count++; + } + extra = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_lt(extra, 0); + for (i = 0; i < count; i++) + wolfIP_sock_close(&s, fds[i]); +} +END_TEST + +/* ===================================================================== + * raw_try_recv -- filter blocks incoming frame + * ===================================================================== */ +#if WOLFIP_RAWSOCKETS +START_TEST(test_raw_try_recv_filter_blocks_frame) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + 8 + sizeof(struct wolfIP_udp_datagram)]; + struct wolfIP_udp_datagram *udp; + struct wolfIP_ll_dev *ll; + int fd; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + fd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(fd, 0); + filter_cb_calls = 0; + filter_block_calls = 0; + filter_block_reason = WOLFIP_FILT_RECEIVING; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_mask(~0U); + memset(frame, 0, sizeof(frame)); + udp = (struct wolfIP_udp_datagram *)(frame + ETH_HEADER_LEN); + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + memcpy(((struct wolfIP_eth_frame *)frame)->dst, ll->mac, 6); + ((struct wolfIP_eth_frame *)frame)->type = ee16(ETH_TYPE_IP); + udp->ip.ver_ihl = 0x45; + udp->ip.ttl = 64; + udp->ip.proto = WI_IPPROTO_UDP; + udp->ip.len = ee16(IP_HEADER_LEN + (uint16_t)sizeof(struct wolfIP_udp_datagram)); + udp->ip.src = ee32(0x0a0000a1U); + udp->ip.dst = ee32(0x0a000001U); + iphdr_set_checksum(&udp->ip); + udp->dst_port = ee16(1234); + udp->src_port = ee16(5678); + udp->len = ee16(8); + wolfIP_recv(&s, frame, sizeof(frame)); + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_mask(0); + wolfIP_sock_close(&s, fd); +} +END_TEST +#endif + +/* ===================================================================== + * fifo_can_push_len -- h_wrap==0, head < tail branch (space = tail - head) + * ===================================================================== */ +START_TEST(test_fifo_can_push_len_head_lt_tail_no_hwrap) +{ + uint8_t buf[128]; + struct fifo f; + int ret; + memset(&f, 0, sizeof(f)); + f.data = buf; + f.size = 128; + /* h_wrap==0, head < tail → space = tail - head */ + f.h_wrap = 0; + f.head = 10; + f.tail = 80; + /* needed = sizeof(pkt_desc) + 4 -- should fit within space=70 */ + ret = fifo_can_push_len(&f, 4); + ck_assert_int_eq(ret, 1); +} +END_TEST + +/* ===================================================================== + * fifo_can_push_len -- h_wrap && head == h_wrap → reset head to 0 + * h_wrap=50, head=50 (==h_wrap), tail=80: space = tail-head = 80-50 = 30 + * after reset head=0; needed=20 < tail(80) → fits → return 1 + * ===================================================================== */ +START_TEST(test_fifo_can_push_len_hwrap_head_equals_hwrap) +{ + uint8_t buf[128]; + struct fifo f; + int ret; + memset(&f, 0, sizeof(f)); + f.data = buf; + f.size = 128; + /* h_wrap=50, head=50 (==h_wrap), tail=80 + * space calculation: h_wrap set, head(50) < tail(80) → space = 30 > needed(20) + * Then h_wrap && head == h_wrap → head = 0 + * Then if(h_wrap): 0+20 <= 80 → return 1 */ + f.h_wrap = 50; + f.head = 50; + f.tail = 80; + ret = fifo_can_push_len(&f, 4); + ck_assert_int_eq(ret, 1); +} +END_TEST + +/* ===================================================================== + * fifo_push -- h_wrap set, head == h_wrap: wraps head to 0 then succeeds + * h_wrap=50, head=50, tail=80 → space=30>20, reset head=0, 0+20<80 → ok + * ===================================================================== */ +START_TEST(test_fifo_push_hwrap_head_equals_hwrap_succeeds) +{ + uint8_t buf[128]; + uint8_t data[4] = {0xDE, 0xAD, 0xBE, 0xEF}; + struct fifo f; + int ret; + memset(&f, 0, sizeof(f)); + f.data = buf; + f.size = 128; + f.h_wrap = 50; + f.head = 50; /* == h_wrap → will wrap to 0 */ + f.tail = 80; /* space = 80-50 = 30 > 20; after wrap head=0, 0+20<=80 */ + ret = fifo_push(&f, data, sizeof(data)); + ck_assert_int_eq(ret, 0); +} +END_TEST + +/* ===================================================================== + * fifo_push -- h_wrap==0, head >= tail, end_space < needed but tail >= needed + * head=120, tail=64: end_space=8 < needed(20), tail(64)>=needed(20) + * → wrap: h_wrap=120, head=0; then h_wrap && 0+20<=64 → succeeds + * ===================================================================== */ +START_TEST(test_fifo_push_no_hwrap_wraps_to_front_succeeds) +{ + uint8_t buf[128]; + uint8_t data[4] = {1, 2, 3, 4}; + struct fifo f; + int ret; + memset(&f, 0, sizeof(f)); + f.data = buf; + f.size = 128; + f.h_wrap = 0; + f.head = 120; + f.tail = 64; + ret = fifo_push(&f, data, sizeof(data)); + ck_assert_int_eq(ret, 0); +} +END_TEST + +/* ===================================================================== + * fifo_push -- head + needed == f->size (lands exactly at end) + * sizeof(pkt_desc)=16, data=4 → needed=20 + * head=108, size=128 → 108+20=128 == size → head=0, h_wrap=128 + * ===================================================================== */ +START_TEST(test_fifo_push_exact_end_sets_hwrap) +{ + uint8_t buf[128]; + uint8_t data[4] = {0xAA, 0xBB, 0xCC, 0xDD}; + struct fifo f; + int ret; + memset(&f, 0, sizeof(f)); + f.data = buf; + f.size = 128; + f.h_wrap = 0; + /* head=108: 108 + 16(pkt_desc) + 4(data) = 128 = size → land at end */ + f.head = 108; + f.tail = 0; + ret = fifo_push(&f, data, sizeof(data)); + ck_assert_int_eq(ret, 0); + ck_assert_uint_eq(f.h_wrap, 128); + ck_assert_uint_eq(f.head, 0); +} +END_TEST + +/* ===================================================================== + * wolfIP_send_port_unreachable -- orig_ihl > TTL_EXCEEDED_ORIG_PACKET_SIZE_MAX + * (exercises orig_copy clamping branch at line 2037) + * ===================================================================== */ +#ifdef ETHERNET +START_TEST(test_wolfip_send_port_unreachable_large_ihl) +{ + struct wolfIP s; + uint8_t framebuf[ETH_HEADER_LEN + 100]; + struct wolfIP_ip_packet *orig = (struct wolfIP_ip_packet *)framebuf; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + memset(framebuf, 0, sizeof(framebuf)); + /* Set ihl to an inflated value */ + orig->ver_ihl = 0x4F; /* ihl = 15 * 4 = 60 bytes */ + orig->proto = WI_IPPROTO_UDP; + orig->src = ee32(0x0a0000a1U); + orig->ttl = 64; + wolfIP_send_port_unreachable(&s, TEST_PRIMARY_IF, orig); + ck_assert(1); +} +END_TEST +#endif + +/* ===================================================================== + * wolfIP_rawsocket_from_fd -- negative fd + * ===================================================================== */ +#if WOLFIP_RAWSOCKETS +START_TEST(test_wolfip_rawsocket_from_fd_negative_fd) +{ + struct wolfIP s; + struct rawsocket *rs; + wolfIP_init(&s); + rs = wolfIP_rawsocket_from_fd(&s, -1); + ck_assert_ptr_null(rs); +} +END_TEST +#endif + +/* ===================================================================== + * wolfIP_packetsocket_from_fd -- negative fd + * ===================================================================== */ +#if WOLFIP_PACKET_SOCKETS +START_TEST(test_wolfip_packetsocket_from_fd_negative_fd) +{ + struct wolfIP s; + struct packetsocket *ps; + wolfIP_init(&s); + ps = wolfIP_packetsocket_from_fd(&s, -1); + ck_assert_ptr_null(ps); +} +END_TEST +#endif + +/* ===================================================================== + * bind_port_in_use -- port in use, different local IPs (skip collision) + * ===================================================================== */ +START_TEST(test_bind_port_in_use_different_ips_no_collision) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in sin; + int fd1; + int fd2; + int ret; + wolfIP_init(&s); + mock_link_init(&s); + mock_link_init_idx(&s, TEST_SECOND_IF, NULL); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + wolfIP_ipconfig_set_ex(&s, TEST_SECOND_IF, 0x0a000101U, 0xffffff00U, 0); + fd1 = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + fd2 = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_ge(fd1, 0); + ck_assert_int_ge(fd2, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(5000); + sin.sin_addr.s_addr = ee32(0x0a000001U); + ck_assert_int_eq(wolfIP_sock_bind(&s, fd1, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), 0); + /* Different IP, same port -- should succeed (different local IPs) */ + sin.sin_addr.s_addr = ee32(0x0a000101U); + ret = wolfIP_sock_bind(&s, fd2, (struct wolfIP_sockaddr *)&sin, sizeof(sin)); + ck_assert_int_eq(ret, 0); +} +END_TEST diff --git a/src/test/unit/unit_tests_multicast.c b/src/test/unit/unit_tests_multicast.c index 7a771629..9c93096a 100644 --- a/src/test/unit/unit_tests_multicast.c +++ b/src/test/unit/unit_tests_multicast.c @@ -1,3 +1,24 @@ +/* unit_tests_multicast.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + #ifdef IP_MULTICAST static void multicast_mreq(struct wolfIP_ip_mreq *mreq, ip4 group, ip4 if_addr) diff --git a/src/test/unit/unit_tests_poll_dispatcher.c b/src/test/unit/unit_tests_poll_dispatcher.c new file mode 100644 index 00000000..13769e52 --- /dev/null +++ b/src/test/unit/unit_tests_poll_dispatcher.c @@ -0,0 +1,1614 @@ +/* unit_tests_poll_dispatcher.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +/* ------------------------------------------------------------------ */ +/* Helper state local to this file */ +/* ------------------------------------------------------------------ */ + +static int poll_dispatcher_poll_calls; +static int poll_dispatcher_frames_left; +/* Custom poll callback that synthesises minimal non-Ethernet frames */ +static int poll_noneth_frame_poll(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) +{ + (void)dev; + (void)len; + if (poll_dispatcher_frames_left <= 0) + return 0; + poll_dispatcher_frames_left--; + poll_dispatcher_poll_calls++; + /* Return a minimal payload of 1 byte so len > 0 */ + memset(frame, 0, 1); + return 1; +} + +/* Poll that always returns 0 (no data) */ +static int poll_returns_zero(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) +{ + (void)dev; (void)frame; (void)len; + poll_dispatcher_poll_calls++; + return 0; +} + +/* Poll that returns negative (error) */ +static int poll_returns_negative(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) +{ + (void)dev; (void)frame; (void)len; + poll_dispatcher_poll_calls++; + return -1; +} + +/* Timer that re-arms itself once */ +static int poll_rearm_timer_count; +static struct wolfIP *poll_rearm_stack_ptr; +static void poll_rearm_timer_cb(void *arg) +{ + struct wolfIP_timer new_tmr; + (void)arg; + poll_rearm_timer_count++; + if (poll_rearm_timer_count == 1 && poll_rearm_stack_ptr) { + memset(&new_tmr, 0, sizeof(new_tmr)); + new_tmr.cb = test_timer_cb; + new_tmr.expires = 50; /* already expired at t=100 */ + timers_binheap_insert(&poll_rearm_stack_ptr->timers, new_tmr); + } +} + +/* Timer that cancels the next head in the heap */ +static int poll_cancel_next_count; +static struct wolfIP *poll_cancel_next_stack; +static int poll_cancel_next_handle; +static void poll_cancel_first_timer_cb(void *arg) +{ + (void)arg; + poll_cancel_next_count++; + if (poll_cancel_next_stack && poll_cancel_next_handle != NO_TIMER) + timer_binheap_cancel(&poll_cancel_next_stack->timers, poll_cancel_next_handle); +} + +/* EAGAIN-returning send mock */ +static int eagain_send_count; +static int eagain_send(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) +{ + (void)dev; (void)frame; (void)len; + eagain_send_count++; + return -WOLFIP_EAGAIN; +} + +/* ------------------------------------------------------------------ */ +/* Device-poll path tests */ +/* ------------------------------------------------------------------ */ + +START_TEST(test_poll_device_poll_returns_zero_exits_loop) +{ + struct wolfIP s; + struct wolfIP_ll_dev *ll; + + wolfIP_init(&s); + mock_link_init(&s); + ll = wolfIP_ll_at(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + + poll_dispatcher_poll_calls = 0; + ll->poll = poll_returns_zero; + ll->non_ethernet = 0; + + (void)wolfIP_poll(&s, 100); + /* poll was called exactly once (returned 0, loop exits) */ + ck_assert_int_eq(poll_dispatcher_poll_calls, 1); +} +END_TEST + +START_TEST(test_poll_device_poll_returns_negative_exits_loop) +{ + struct wolfIP s; + struct wolfIP_ll_dev *ll; + + wolfIP_init(&s); + mock_link_init(&s); + ll = wolfIP_ll_at(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + + poll_dispatcher_poll_calls = 0; + ll->poll = poll_returns_negative; + ll->non_ethernet = 0; + + (void)wolfIP_poll(&s, 100); + /* poll was called once, negative means len <= 0, loop exits */ + ck_assert_int_eq(poll_dispatcher_poll_calls, 1); +} +END_TEST + +START_TEST(test_poll_device_non_ethernet_path_receives) +{ + struct wolfIP s; + struct wolfIP_ll_dev *ll; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + ll = wolfIP_ll_at(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + + /* Configure as non-ethernet device and give it one frame to deliver */ + ll->non_ethernet = 1; + ll->mtu = 128; + poll_dispatcher_poll_calls = 0; + poll_dispatcher_frames_left = 1; + ll->poll = poll_noneth_frame_poll; + + (void)wolfIP_poll(&s, 100); + /* poll was called at least once (the frame was fetched) */ + ck_assert_int_ge(poll_dispatcher_poll_calls, 1); +} +END_TEST + +START_TEST(test_poll_device_non_ethernet_minimum_mtu_clamped) +{ + struct wolfIP s; + struct wolfIP_ll_dev *ll; + + wolfIP_init(&s); + mock_link_init(&s); + ll = wolfIP_ll_at(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + + ll->non_ethernet = 1; + /* Set mtu to LINK_MTU_MIN (64); wolfIP_ll_frame_mtu returns 64. + * frame_mtu - ETH_HEADER_LEN == 64-14 = 50 bytes of payload space. + * The poll loop should proceed (not break) since 64 > ETH_HEADER_LEN. */ + ll->mtu = LINK_MTU_MIN; + poll_dispatcher_poll_calls = 0; + poll_dispatcher_frames_left = 1; + ll->poll = poll_noneth_frame_poll; + + (void)wolfIP_poll(&s, 100); + /* poll was called (frame fetched) because frame_mtu > ETH_HEADER_LEN */ + ck_assert_int_ge(poll_dispatcher_poll_calls, 1); +} +END_TEST + +START_TEST(test_poll_device_budget_exhaustion_stops_at_budget) +{ + struct wolfIP s; + struct wolfIP_ll_dev *ll; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + ll = wolfIP_ll_at(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + + /* Give budget+5 frames; poll should consume exactly WOLFIP_POLL_BUDGET */ + poll_dispatcher_frames_left = WOLFIP_POLL_BUDGET + 5; + poll_dispatcher_poll_calls = 0; + ll->non_ethernet = 1; + ll->mtu = 256; + ll->poll = poll_noneth_frame_poll; + + (void)wolfIP_poll(&s, 100); + ck_assert_int_le(poll_dispatcher_poll_calls, WOLFIP_POLL_BUDGET + 1); + /* At least WOLFIP_POLL_BUDGET calls were made */ + ck_assert_int_ge(poll_dispatcher_poll_calls, WOLFIP_POLL_BUDGET); +} +END_TEST + +START_TEST(test_poll_device_no_poll_callback_skipped) +{ + struct wolfIP s; + struct wolfIP_ll_dev *ll; + + wolfIP_init(&s); + mock_link_init(&s); + ll = wolfIP_ll_at(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + + /* Remove poll callback entirely; wolfIP_poll should not crash */ + ll->poll = NULL; + (void)wolfIP_poll(&s, 100); + /* no assertion needed beyond "does not crash" */ + ck_assert_int_eq(0, 0); +} +END_TEST + +/* ------------------------------------------------------------------ */ +/* Timer tests */ +/* ------------------------------------------------------------------ */ + +START_TEST(test_poll_timer_fires_multiple_in_one_tick) +{ + struct wolfIP s; + struct wolfIP_timer tmr; + int i; + + wolfIP_init(&s); + mock_link_init(&s); + timer_cb_calls = 0; + + for (i = 0; i < 5; i++) { + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = (uint64_t)(50 + i * 10); + timers_binheap_insert(&s.timers, tmr); + } + /* Poll at t=100: all 5 timers are expired */ + (void)wolfIP_poll(&s, 100); + ck_assert_int_eq(timer_cb_calls, 5); + ck_assert_uint_eq(s.timers.size, 0U); +} +END_TEST + +START_TEST(test_poll_timer_cancelled_tombstone_drained_before_live_timer) +{ + struct wolfIP s; + struct wolfIP_timer tmr; + int handle; + + wolfIP_init(&s); + mock_link_init(&s); + timer_cb_calls = 0; + + /* Insert one timer and cancel it immediately — tombstone only */ + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 50; + handle = timers_binheap_insert(&s.timers, tmr); + + /* Cancel to create a tombstone */ + timer_binheap_cancel(&s.timers, handle); + + /* Insert a second live timer after the cancelled one */ + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 60; + timers_binheap_insert(&s.timers, tmr); + + /* Poll at t=100: the tombstone is at the heap head; is_timer_expired + * drains it via timers_binheap_pop which also consumes the next timer. + * Verify poll does not crash and heap is empty after draining. */ + (void)wolfIP_poll(&s, 100); + /* Heap must be empty after tombstone draining */ + ck_assert_uint_eq(s.timers.size, 0U); +} +END_TEST + +START_TEST(test_poll_timer_callback_rearms_itself) +{ + struct wolfIP s; + struct wolfIP_timer tmr; + + wolfIP_init(&s); + mock_link_init(&s); + timer_cb_calls = 0; + poll_rearm_timer_count = 0; + poll_rearm_stack_ptr = &s; + + /* Insert the rearm-on-first-fire timer */ + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = poll_rearm_timer_cb; + tmr.expires = 50; + timers_binheap_insert(&s.timers, tmr); + + /* First poll fires the timer, which inserts another timer at t=50 */ + (void)wolfIP_poll(&s, 100); + ck_assert_int_eq(poll_rearm_timer_count, 1); + /* The re-armed timer (expires=50, already expired) should also fire */ + ck_assert_int_eq(timer_cb_calls, 1); + /* Total timers used = 0 now (heap should be empty) */ + ck_assert_uint_eq(s.timers.size, 0U); + + poll_rearm_stack_ptr = NULL; +} +END_TEST + +START_TEST(test_poll_timer_callback_cancels_sibling) +{ + struct wolfIP s; + struct wolfIP_timer tmr; + + wolfIP_init(&s); + mock_link_init(&s); + timer_cb_calls = 0; + poll_cancel_next_count = 0; + poll_cancel_next_stack = &s; + + /* Insert the sibling timer first so it gets a known handle */ + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 60; + poll_cancel_next_handle = timers_binheap_insert(&s.timers, tmr); + + /* Insert the canceller at lower expiry so it fires first */ + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = poll_cancel_first_timer_cb; + tmr.expires = 30; + timers_binheap_insert(&s.timers, tmr); + + /* At t=100 both are expired; canceller fires first and cancels sibling */ + (void)wolfIP_poll(&s, 100); + ck_assert_int_eq(poll_cancel_next_count, 1); + /* The sibling was cancelled; timer_cb should NOT have fired */ + ck_assert_int_eq(timer_cb_calls, 0); + + poll_cancel_next_stack = NULL; + poll_cancel_next_handle = NO_TIMER; +} +END_TEST + +/* ------------------------------------------------------------------ */ +/* Socket callback dispatch */ +/* ------------------------------------------------------------------ */ + +START_TEST(test_poll_icmp_socket_callback_dispatched) +{ + struct wolfIP s; + int icmp_sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + socket_cb_calls = 0; + socket_cb_last_fd = -1; + + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + wolfIP_register_callback(&s, icmp_sd, test_socket_cb, NULL); + ts = &s.icmpsockets[SOCKET_UNMARK(icmp_sd)]; + ts->events = CB_EVENT_READABLE; + + (void)wolfIP_poll(&s, 100); + ck_assert_int_eq(socket_cb_calls, 1); + ck_assert_int_eq(socket_cb_last_fd, icmp_sd); + ck_assert_uint_eq(ts->events, 0U); /* cleared after dispatch */ +} +END_TEST + +START_TEST(test_poll_tcp_socket_callback_dispatched) +{ + struct wolfIP s; + int tcp_sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + socket_cb_calls = 0; + socket_cb_last_fd = -1; + + tcp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(tcp_sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(tcp_sd)]; + ts->sock.tcp.state = TCP_ESTABLISHED; + wolfIP_register_callback(&s, tcp_sd, test_socket_cb, NULL); + ts->events = CB_EVENT_READABLE; + + (void)wolfIP_poll(&s, 100); + ck_assert_int_eq(socket_cb_calls, 1); + ck_assert_int_eq(socket_cb_last_fd, tcp_sd); + ck_assert_uint_eq(ts->events, 0U); +} +END_TEST + +START_TEST(test_poll_udp_socket_callback_dispatched) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + socket_cb_calls = 0; + socket_cb_last_fd = -1; + + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + wolfIP_register_callback(&s, udp_sd, test_socket_cb, NULL); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + ts->events = CB_EVENT_WRITABLE; + + (void)wolfIP_poll(&s, 100); + ck_assert_int_eq(socket_cb_calls, 1); + ck_assert_int_eq(socket_cb_last_fd, udp_sd); + ck_assert_uint_eq(ts->events, 0U); +} +END_TEST + +#if WOLFIP_RAWSOCKETS +START_TEST(test_poll_raw_socket_callback_dispatched) +{ + struct wolfIP s; + int raw_sd; + struct rawsocket *r; + + wolfIP_init(&s); + mock_link_init(&s); + socket_cb_calls = 0; + socket_cb_last_fd = -1; + + raw_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_ICMP); + ck_assert_int_ge(raw_sd, 0); + wolfIP_register_callback(&s, raw_sd, test_socket_cb, NULL); + r = &s.rawsockets[SOCKET_UNMARK(raw_sd)]; + r->events = CB_EVENT_READABLE; + + (void)wolfIP_poll(&s, 100); + ck_assert_int_eq(socket_cb_calls, 1); + ck_assert_int_eq(socket_cb_last_fd, raw_sd); + ck_assert_uint_eq(r->events, 0U); +} +END_TEST +#endif /* WOLFIP_RAWSOCKETS */ + +#if WOLFIP_PACKET_SOCKETS +START_TEST(test_poll_packet_socket_callback_dispatched) +{ + struct wolfIP s; + int pkt_sd; + struct packetsocket *p; + + wolfIP_init(&s); + mock_link_init(&s); + socket_cb_calls = 0; + socket_cb_last_fd = -1; + + pkt_sd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, ee16(ETH_TYPE_IP)); + ck_assert_int_ge(pkt_sd, 0); + wolfIP_register_callback(&s, pkt_sd, test_socket_cb, NULL); + p = &s.packetsockets[SOCKET_UNMARK(pkt_sd)]; + p->events = CB_EVENT_READABLE; + + (void)wolfIP_poll(&s, 100); + ck_assert_int_eq(socket_cb_calls, 1); + ck_assert_int_eq(socket_cb_last_fd, pkt_sd); + ck_assert_uint_eq(p->events, 0U); +} +END_TEST +#endif /* WOLFIP_PACKET_SOCKETS */ + +/* ------------------------------------------------------------------ */ +/* TCP TX loop */ +/* ------------------------------------------------------------------ */ + +/* Helper: set up a minimal ESTABLISHED TCP socket */ +static void setup_tcp_socket(struct wolfIP *s, struct tsocket *ts, + ip4 local_ip, ip4 remote_ip, + unsigned int if_idx) +{ + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->if_idx = (uint8_t)if_idx; + ts->src_port = 9000; + ts->dst_port = 80; + ts->sock.tcp.rto = 100; + ts->sock.tcp.cwnd = TCP_MSS * 4; + ts->sock.tcp.peer_rwnd = TCP_MSS * 4; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); +} + +START_TEST(test_poll_tx_tcp_pkt_flag_sent_desc_skipped) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc *desc; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + uint8_t peer_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x01}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + + /* Pre-populate ARP so send can proceed */ + s.arp.neighbors[0].ip = remote_ip; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, 6); + + ts = &s.tcpsockets[0]; + setup_tcp_socket(&s, ts, local_ip, remote_ip, TEST_PRIMARY_IF); + + /* Enqueue one segment and mark it PKT_FLAG_SENT */ + ck_assert_int_eq(enqueue_tcp_tx(ts, 4, TCP_FLAG_ACK | TCP_FLAG_PSH), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + desc->flags |= PKT_FLAG_SENT; + + last_frame_sent_size = 0; + (void)wolfIP_poll(&s, 200); + + /* The already-sent descriptor is skipped; nothing new sent */ + ck_assert_uint_eq(last_frame_sent_size, 0U); + ck_assert_ptr_nonnull(fifo_peek(&ts->sock.tcp.txbuf)); +} +END_TEST + +START_TEST(test_poll_tx_tcp_arp_miss_emits_arp_request) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000FEU; /* no ARP entry */ + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + ts = &s.tcpsockets[0]; + setup_tcp_socket(&s, ts, local_ip, remote_ip, TEST_PRIMARY_IF); + + ck_assert_int_eq(enqueue_tcp_tx(ts, 4, TCP_FLAG_ACK | TCP_FLAG_PSH), 0); + + /* Use now >= 1000 to pass the ARP rate-limit check (last_arp + 1000 > now) */ + (void)wolfIP_poll(&s, 2000); + + /* An ARP request (EtherType 0x0806) should have been emitted */ + ck_assert_uint_gt(last_frame_sent_size, 0U); + ck_assert_uint_eq(last_frame_sent[12], 0x08); + ck_assert_uint_eq(last_frame_sent[13], 0x06); + /* The queued segment is still pending */ + ck_assert_ptr_nonnull(fifo_peek(&ts->sock.tcp.txbuf)); +} +END_TEST + +START_TEST(test_poll_tx_tcp_filter_tcp_blocks_send) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + uint8_t peer_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x02}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + s.arp.neighbors[0].ip = remote_ip; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, 6); + + /* Block all SENDING events from the filter */ + filter_block_calls = 0; + filter_block_reason = WOLFIP_FILT_SENDING; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_SENDING)); + + ts = &s.tcpsockets[0]; + setup_tcp_socket(&s, ts, local_ip, remote_ip, TEST_PRIMARY_IF); + + ck_assert_int_eq(enqueue_tcp_tx(ts, 4, TCP_FLAG_ACK | TCP_FLAG_PSH), 0); + last_frame_sent_size = 0; + + (void)wolfIP_poll(&s, 200); + + /* Filter should have been invoked and blocked the send */ + ck_assert_int_ge(filter_block_calls, 1); + ck_assert_uint_eq(last_frame_sent_size, 0U); + + wolfIP_filter_set_callback(NULL, NULL); +} +END_TEST + +START_TEST(test_poll_tx_tcp_send_eagain_breaks_loop) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_ll_dev *ll; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + uint8_t peer_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x03}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + + s.arp.neighbors[0].ip = remote_ip; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, 6); + + ll = wolfIP_ll_at(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + eagain_send_count = 0; + ll->send = eagain_send; + + ts = &s.tcpsockets[0]; + setup_tcp_socket(&s, ts, local_ip, remote_ip, TEST_PRIMARY_IF); + /* Add TX space so CB_EVENT_WRITABLE may be set */ + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, 0); + + ck_assert_int_eq(enqueue_tcp_tx(ts, 4, TCP_FLAG_ACK | TCP_FLAG_PSH), 0); + + (void)wolfIP_poll(&s, 200); + ck_assert_int_ge(eagain_send_count, 1); + /* Descriptor remains in queue after EAGAIN */ + ck_assert_ptr_nonnull(fifo_peek(&ts->sock.tcp.txbuf)); +} +END_TEST + +START_TEST(test_poll_tx_tcp_zero_window_starts_persist) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + uint8_t peer_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x04}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + + s.arp.neighbors[0].ip = remote_ip; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, 6); + + ts = &s.tcpsockets[0]; + setup_tcp_socket(&s, ts, local_ip, remote_ip, TEST_PRIMARY_IF); + /* Force zero peer window */ + ts->sock.tcp.peer_rwnd = 0; + ts->sock.tcp.cwnd = TCP_MSS; + + ck_assert_int_eq(enqueue_tcp_tx(ts, 4, TCP_FLAG_ACK | TCP_FLAG_PSH), 0); + ck_assert_int_eq(ts->sock.tcp.persist_active, 0); + + (void)wolfIP_poll(&s, 200); + /* Persist must have been started because window == 0 */ + ck_assert_int_eq(ts->sock.tcp.persist_active, 1); +} +END_TEST + +START_TEST(test_poll_tx_tcp_retransmit_replay) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc *desc; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + uint8_t peer_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x05}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + + s.arp.neighbors[0].ip = remote_ip; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, 6); + + ts = &s.tcpsockets[0]; + setup_tcp_socket(&s, ts, local_ip, remote_ip, TEST_PRIMARY_IF); + + ck_assert_int_eq(enqueue_tcp_tx(ts, 4, TCP_FLAG_ACK | TCP_FLAG_PSH), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + /* Mark as retransmit */ + desc->flags |= PKT_FLAG_RETRANS; + last_frame_sent_size = 0; + + (void)wolfIP_poll(&s, 200); + /* Retransmit path was taken; frame was sent */ + ck_assert_uint_gt(last_frame_sent_size, 0U); + ck_assert_uint_eq(last_frame_sent[12], 0x08); + ck_assert_uint_eq(last_frame_sent[13], 0x00); +} +END_TEST + +/* ------------------------------------------------------------------ */ +/* TCP TX loop – loopback path */ +/* ------------------------------------------------------------------ */ + +#if WOLFIP_ENABLE_LOOPBACK +START_TEST(test_poll_tx_tcp_loopback_path) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 loopback_ip = 0x7F000001U; /* 127.0.0.1 */ + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_filter_set_callback(NULL, NULL); + + ts = &s.tcpsockets[0]; + setup_tcp_socket(&s, ts, loopback_ip, loopback_ip, TEST_LOOPBACK_IF); + + ck_assert_int_eq(enqueue_tcp_tx(ts, 4, TCP_FLAG_ACK | TCP_FLAG_PSH), 0); + last_frame_sent_size = 0; + + (void)wolfIP_poll(&s, 200); + /* On the loopback path, the MAC is filled from the loop device */ + /* The segment is either sent or retains; no crash expected */ + ck_assert_int_eq(0, 0); +} +END_TEST +#endif /* WOLFIP_ENABLE_LOOPBACK */ + +/* ------------------------------------------------------------------ */ +/* UDP TX loop */ +/* ------------------------------------------------------------------ */ + +START_TEST(test_poll_tx_udp_sends_on_arp_hit) +{ + struct wolfIP s; + int udp_sd; + struct wolfIP_sockaddr_in sin; + uint8_t payload[4] = {0xDE, 0xAD, 0xBE, 0xEF}; + uint8_t peer_mac[6] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, 6); + + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(5000); + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + + (void)wolfIP_poll(&s, 200); + ck_assert_uint_gt(last_frame_sent_size, 0U); + ck_assert_uint_eq(last_frame_sent[12], 0x08); + ck_assert_uint_eq(last_frame_sent[13], 0x00); +} +END_TEST + +START_TEST(test_poll_tx_udp_filter_ip_blocks_send) +{ + struct wolfIP s; + int udp_sd; + struct wolfIP_sockaddr_in sin; + uint8_t payload[4] = {1, 2, 3, 4}; + uint8_t peer_mac[6] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x77}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, 6); + + filter_block_calls = 0; + filter_block_reason = WOLFIP_FILT_SENDING; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_SENDING)); + + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(5001); + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + + last_frame_sent_size = 0; + (void)wolfIP_poll(&s, 200); + + ck_assert_int_ge(filter_block_calls, 1); + ck_assert_uint_eq(last_frame_sent_size, 0U); + + wolfIP_filter_set_callback(NULL, NULL); +} +END_TEST + +START_TEST(test_poll_tx_udp_eagain_retains_queue) +{ + struct wolfIP s; + int udp_sd; + struct wolfIP_ll_dev *ll; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + uint8_t payload[4] = {1, 2, 3, 4}; + uint8_t peer_mac[6] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x88}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, 6); + + ll = wolfIP_ll_at(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + eagain_send_count = 0; + ll->send = eagain_send; + + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(5002); + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + ts->if_idx = TEST_PRIMARY_IF; + + (void)wolfIP_poll(&s, 200); + ck_assert_int_ge(eagain_send_count, 1); + /* Descriptor must still be in queue */ + ck_assert_ptr_nonnull(fifo_peek(&ts->sock.udp.txbuf)); + + ll->send = mock_send; +} +END_TEST + +START_TEST(test_poll_tx_udp_broadcast_sets_ff_mac) +{ + struct wolfIP s; + int udp_sd; + struct wolfIP_sockaddr_in sin; + uint8_t payload[4] = {1, 2, 3, 4}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(5003); + /* Subnet-directed broadcast: 10.0.0.255 */ + sin.sin_addr.s_addr = ee32(0x0A0000FFU); + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + + (void)wolfIP_poll(&s, 200); + ck_assert_uint_gt(last_frame_sent_size, 0U); + /* Destination MAC should be FF:FF:FF:FF:FF:FF */ + ck_assert_uint_eq(last_frame_sent[0], 0xFF); + ck_assert_uint_eq(last_frame_sent[1], 0xFF); + ck_assert_uint_eq(last_frame_sent[2], 0xFF); +} +END_TEST + +#if WOLFIP_ENABLE_LOOPBACK +START_TEST(test_poll_tx_udp_loopback_path_no_crash) +{ + struct wolfIP s; + struct tsocket *t; + struct wolfIP_sockaddr_in sin; + int udp_sd; + uint8_t payload[4] = {0x11, 0x22, 0x33, 0x44}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x7F000001U, 0xFF000000U, 0); + wolfIP_filter_set_callback(NULL, NULL); + + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + t = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(6000); + sin.sin_addr.s_addr = ee32(0x7F000001U); + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + /* Force the socket to use the loopback interface */ + t->if_idx = TEST_LOOPBACK_IF; + + (void)wolfIP_poll(&s, 200); + /* No crash expected; loopback MAC fill executed */ + ck_assert_int_eq(0, 0); +} +END_TEST +#endif /* WOLFIP_ENABLE_LOOPBACK */ + +/* ------------------------------------------------------------------ */ +/* ICMP TX loop */ +/* ------------------------------------------------------------------ */ + +START_TEST(test_poll_tx_icmp_sends_on_arp_hit) +{ + struct wolfIP s; + int icmp_sd; + struct wolfIP_sockaddr_in sin; + uint8_t payload[ICMP_HEADER_LEN + 4]; + uint8_t peer_mac[6] = {0xAA, 0x11, 0x22, 0x33, 0x44, 0x55}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, 6); + + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + memset(payload, 0, sizeof(payload)); + payload[0] = ICMP_ECHO_REQUEST; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_sendto(&s, icmp_sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + + (void)wolfIP_poll(&s, 200); + ck_assert_uint_gt(last_frame_sent_size, 0U); + ck_assert_uint_eq(last_frame_sent[12], 0x08); + ck_assert_uint_eq(last_frame_sent[13], 0x00); +} +END_TEST + +START_TEST(test_poll_tx_icmp_filter_blocks_send) +{ + struct wolfIP s; + int icmp_sd; + struct wolfIP_sockaddr_in sin; + uint8_t payload[ICMP_HEADER_LEN + 4]; + uint8_t peer_mac[6] = {0xAA, 0x11, 0x22, 0x33, 0x44, 0x66}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, 6); + + filter_block_calls = 0; + filter_block_reason = WOLFIP_FILT_SENDING; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_SENDING)); + + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + memset(payload, 0, sizeof(payload)); + payload[0] = ICMP_ECHO_REQUEST; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_sendto(&s, icmp_sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + + last_frame_sent_size = 0; + (void)wolfIP_poll(&s, 200); + + ck_assert_int_ge(filter_block_calls, 1); + ck_assert_uint_eq(last_frame_sent_size, 0U); + + wolfIP_filter_set_callback(NULL, NULL); +} +END_TEST + +START_TEST(test_poll_tx_icmp_eagain_retains_queue) +{ + struct wolfIP s; + int icmp_sd; + struct wolfIP_ll_dev *ll; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + uint8_t payload[ICMP_HEADER_LEN + 4]; + uint8_t peer_mac[6] = {0xAA, 0x11, 0x22, 0x33, 0x44, 0x77}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, 6); + + ll = wolfIP_ll_at(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + eagain_send_count = 0; + ll->send = eagain_send; + + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + ts = &s.icmpsockets[SOCKET_UNMARK(icmp_sd)]; + memset(payload, 0, sizeof(payload)); + payload[0] = ICMP_ECHO_REQUEST; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_sendto(&s, icmp_sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + ts->if_idx = TEST_PRIMARY_IF; + + (void)wolfIP_poll(&s, 200); + ck_assert_int_ge(eagain_send_count, 1); + ck_assert_ptr_nonnull(fifo_peek(&ts->sock.udp.txbuf)); + + ll->send = mock_send; +} +END_TEST + +START_TEST(test_poll_tx_icmp_broadcast_sets_ff_mac) +{ + struct wolfIP s; + int icmp_sd; + struct wolfIP_sockaddr_in sin; + uint8_t payload[ICMP_HEADER_LEN + 4]; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + memset(payload, 0, sizeof(payload)); + payload[0] = ICMP_ECHO_REQUEST; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A0000FFU); /* .255 broadcast */ + ck_assert_int_eq(wolfIP_sock_sendto(&s, icmp_sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + + (void)wolfIP_poll(&s, 200); + ck_assert_uint_gt(last_frame_sent_size, 0U); + ck_assert_uint_eq(last_frame_sent[0], 0xFF); + ck_assert_uint_eq(last_frame_sent[1], 0xFF); + ck_assert_uint_eq(last_frame_sent[2], 0xFF); +} +END_TEST + +#if WOLFIP_ENABLE_LOOPBACK +START_TEST(test_poll_tx_icmp_loopback_path_no_crash) +{ + struct wolfIP s; + struct tsocket *t; + struct wolfIP_sockaddr_in sin; + int icmp_sd; + uint8_t payload[ICMP_HEADER_LEN + 4]; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x7F000001U, 0xFF000000U, 0); + wolfIP_filter_set_callback(NULL, NULL); + + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + t = &s.icmpsockets[SOCKET_UNMARK(icmp_sd)]; + memset(payload, 0, sizeof(payload)); + payload[0] = ICMP_ECHO_REQUEST; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x7F000001U); + ck_assert_int_eq(wolfIP_sock_sendto(&s, icmp_sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + t->if_idx = TEST_LOOPBACK_IF; + + (void)wolfIP_poll(&s, 200); + ck_assert_int_eq(0, 0); +} +END_TEST +#endif /* WOLFIP_ENABLE_LOOPBACK */ + +/* ------------------------------------------------------------------ */ +/* RAW TX loop */ +/* ------------------------------------------------------------------ */ + +#if WOLFIP_RAWSOCKETS +START_TEST(test_poll_tx_raw_sends_on_arp_hit) +{ + struct wolfIP s; + int sd; + uint8_t payload[8]; + struct wolfIP_sockaddr_in sin; + uint8_t nh_mac[6] = {0x50, 0x51, 0x52, 0x53, 0x54, 0x55}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, nh_mac, 6); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + memset(payload, 0xCC, sizeof(payload)); + ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + + last_frame_sent_size = 0; + (void)wolfIP_poll(&s, 100); + ck_assert_uint_gt(last_frame_sent_size, 0U); + ck_assert_uint_eq(last_frame_sent[12], 0x08); + ck_assert_uint_eq(last_frame_sent[13], 0x00); +} +END_TEST + +START_TEST(test_poll_tx_raw_arp_miss_emits_request) +{ + struct wolfIP s; + int sd; + uint8_t payload[8]; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A00FFFEU); /* no ARP entry */ + memset(payload, 0, sizeof(payload)); + ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + + /* Use now >= 1000 to pass the ARP rate-limit check */ + (void)wolfIP_poll(&s, 2000); + /* ARP request must have been emitted */ + ck_assert_uint_gt(last_frame_sent_size, 0U); + ck_assert_uint_eq(last_frame_sent[12], 0x08); + ck_assert_uint_eq(last_frame_sent[13], 0x06); +} +END_TEST + +START_TEST(test_poll_tx_raw_dst_zero_skips_descriptor) +{ + struct wolfIP s; + int sd; + struct rawsocket *r; + struct wolfIP_ip_packet *ip_pkt; + uint8_t buf[ETH_HEADER_LEN + IP_HEADER_LEN + 4]; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + r = &s.rawsockets[SOCKET_UNMARK(sd)]; + + /* struct wolfIP_ip_packet already includes the Ethernet header at + * offset 0; cast the whole buffer (do NOT advance by ETH_HEADER_LEN). */ + memset(buf, 0, sizeof(buf)); + ip_pkt = (struct wolfIP_ip_packet *)buf; + ip_pkt->dst = 0; /* zero dst triggers skip */ + ip_pkt->src = ee32(0x0A000001U); + ip_pkt->ver_ihl = 0x45; + ip_pkt->proto = WI_IPPROTO_UDP; + ip_pkt->len = ee16(IP_HEADER_LEN + 4); + iphdr_set_checksum(ip_pkt); + ck_assert_int_eq(fifo_push(&r->txbuf, buf, sizeof(buf)), 0); + + last_frame_sent_size = 0; + (void)wolfIP_poll(&s, 100); + /* Descriptor with dst==0 must have been silently discarded */ + ck_assert_ptr_eq(fifo_peek(&r->txbuf), NULL); + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + +START_TEST(test_poll_tx_raw_filter_blocks_send) +{ + struct wolfIP s; + int sd; + uint8_t payload[8]; + struct wolfIP_sockaddr_in sin; + uint8_t nh_mac[6] = {0x60, 0x61, 0x62, 0x63, 0x64, 0x65}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, nh_mac, 6); + + filter_block_calls = 0; + filter_block_reason = WOLFIP_FILT_SENDING; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_SENDING)); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + memset(payload, 0, sizeof(payload)); + ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + + last_frame_sent_size = 0; + (void)wolfIP_poll(&s, 100); + ck_assert_int_ge(filter_block_calls, 1); + ck_assert_uint_eq(last_frame_sent_size, 0U); + + wolfIP_filter_set_callback(NULL, NULL); +} +END_TEST + +#if WOLFIP_ENABLE_LOOPBACK +START_TEST(test_poll_tx_raw_loopback_path) +{ + struct wolfIP s; + int sd; + struct rawsocket *r; + uint8_t payload[8]; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x7F000001U, 0xFF000000U, 0); + wolfIP_filter_set_callback(NULL, NULL); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + r = &s.rawsockets[SOCKET_UNMARK(sd)]; + r->if_idx = TEST_LOOPBACK_IF; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x7F000001U); + memset(payload, 0xAB, sizeof(payload)); + ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + + (void)wolfIP_poll(&s, 100); + ck_assert_int_eq(0, 0); +} +END_TEST +#endif /* WOLFIP_ENABLE_LOOPBACK */ + +#endif /* WOLFIP_RAWSOCKETS */ + +/* ------------------------------------------------------------------ */ +/* PACKET TX loop */ +/* ------------------------------------------------------------------ */ + +#if WOLFIP_PACKET_SOCKETS +START_TEST(test_poll_tx_packet_sends_frame) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_ll sll; + uint8_t frame_buf[ETH_HEADER_LEN + 8]; + struct wolfIP_eth_frame *ethf = (struct wolfIP_eth_frame *)frame_buf; + struct wolfIP_sockaddr_ll bind_sll; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + sd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, ee16(ETH_TYPE_IP)); + ck_assert_int_ge(sd, 0); + + memset(&bind_sll, 0, sizeof(bind_sll)); + bind_sll.sll_family = AF_PACKET; + bind_sll.sll_protocol = ee16(ETH_TYPE_IP); + bind_sll.sll_ifindex = TEST_PRIMARY_IF; + bind_sll.sll_halen = 6; + memset(bind_sll.sll_addr, 0xFF, 6); + ck_assert_int_eq(wolfIP_sock_bind(&s, sd, + (struct wolfIP_sockaddr *)&bind_sll, sizeof(bind_sll)), 0); + + memset(&sll, 0, sizeof(sll)); + sll.sll_family = AF_PACKET; + sll.sll_protocol = ee16(ETH_TYPE_IP); + sll.sll_ifindex = TEST_PRIMARY_IF; + sll.sll_halen = 6; + memset(sll.sll_addr, 0xFF, 6); + + memset(frame_buf, 0, sizeof(frame_buf)); + memcpy(ethf->dst, "\xff\xff\xff\xff\xff\xff", 6); + memcpy(ethf->src, "\x01\x02\x03\x04\x05\x06", 6); + ethf->type = ee16(ETH_TYPE_IP); + memset(ethf->data, 0x5A, 8); + + ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, frame_buf, sizeof(frame_buf), 0, + (struct wolfIP_sockaddr *)&sll, sizeof(sll)), (int)sizeof(frame_buf)); + + (void)wolfIP_poll(&s, 100); + ck_assert_uint_eq(last_frame_sent_size, sizeof(frame_buf)); +} +END_TEST + +START_TEST(test_poll_tx_packet_filter_blocks_advances_desc) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_ll sll; + uint8_t frame1[ETH_HEADER_LEN + 4]; + uint8_t frame2[ETH_HEADER_LEN + 4]; + struct wolfIP_eth_frame *ethf1 = (struct wolfIP_eth_frame *)frame1; + struct wolfIP_eth_frame *ethf2 = (struct wolfIP_eth_frame *)frame2; + struct wolfIP_sockaddr_ll bind_sll; + + wolfIP_init(&s); + mock_link_init(&s); + + /* Block all SENDING events — packet socket loop calls fifo_next on block */ + filter_block_calls = 0; + filter_block_reason = WOLFIP_FILT_SENDING; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_SENDING)); + + sd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, ee16(ETH_TYPE_IP)); + ck_assert_int_ge(sd, 0); + + memset(&bind_sll, 0, sizeof(bind_sll)); + bind_sll.sll_family = AF_PACKET; + bind_sll.sll_protocol = ee16(ETH_TYPE_IP); + bind_sll.sll_ifindex = TEST_PRIMARY_IF; + bind_sll.sll_halen = 6; + memset(bind_sll.sll_addr, 0xFF, 6); + ck_assert_int_eq(wolfIP_sock_bind(&s, sd, + (struct wolfIP_sockaddr *)&bind_sll, sizeof(bind_sll)), 0); + + memset(&sll, 0, sizeof(sll)); + sll.sll_family = AF_PACKET; + sll.sll_protocol = ee16(ETH_TYPE_IP); + sll.sll_ifindex = TEST_PRIMARY_IF; + sll.sll_halen = 6; + memset(sll.sll_addr, 0xFF, 6); + + memset(frame1, 0, sizeof(frame1)); + memcpy(ethf1->dst, "\xff\xff\xff\xff\xff\xff", 6); + ethf1->type = ee16(ETH_TYPE_IP); + ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, frame1, sizeof(frame1), 0, + (struct wolfIP_sockaddr *)&sll, sizeof(sll)), (int)sizeof(frame1)); + + memset(frame2, 0, sizeof(frame2)); + memcpy(ethf2->dst, "\xff\xff\xff\xff\xff\xff", 6); + ethf2->type = ee16(ETH_TYPE_IP); + ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, frame2, sizeof(frame2), 0, + (struct wolfIP_sockaddr *)&sll, sizeof(sll)), (int)sizeof(frame2)); + + last_frame_sent_size = 0; + (void)wolfIP_poll(&s, 100); + + /* Filter blocked both frames; calls should be > 0 */ + ck_assert_int_ge(filter_block_calls, 1); + ck_assert_uint_eq(last_frame_sent_size, 0U); + + wolfIP_filter_set_callback(NULL, NULL); +} +END_TEST +#endif /* WOLFIP_PACKET_SOCKETS */ + +/* ------------------------------------------------------------------ */ +/* Combined / integration tests */ +/* ------------------------------------------------------------------ */ + +START_TEST(test_poll_combined_timer_and_socket_cb_in_same_tick) +{ + struct wolfIP s; + struct wolfIP_timer tmr; + int udp_sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + timer_cb_calls = 0; + socket_cb_calls = 0; + + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 50; + timers_binheap_insert(&s.timers, tmr); + + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + wolfIP_register_callback(&s, udp_sd, test_socket_cb, NULL); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + ts->events = CB_EVENT_READABLE; + + (void)wolfIP_poll(&s, 100); + ck_assert_int_eq(timer_cb_calls, 1); + ck_assert_int_eq(socket_cb_calls, 1); +} +END_TEST + +START_TEST(test_poll_no_timers_and_no_events_is_noop) +{ + struct wolfIP s; + + wolfIP_init(&s); + mock_link_init(&s); + /* Nothing registered; should return 0 silently */ + ck_assert_int_eq(wolfIP_poll(&s, 100), 0); + ck_assert_int_eq(wolfIP_poll(&s, 101), 0); +} +END_TEST + +START_TEST(test_poll_last_tick_updated) +{ + struct wolfIP s; + + wolfIP_init(&s); + mock_link_init(&s); + + (void)wolfIP_poll(&s, 12345ULL); + ck_assert_uint_eq(s.last_tick, 12345ULL); + (void)wolfIP_poll(&s, 99999ULL); + ck_assert_uint_eq(s.last_tick, 99999ULL); +} +END_TEST + +#if WOLFIP_ENABLE_LOOPBACK +START_TEST(test_poll_loopback_interface_iterated) +{ + struct wolfIP s; + struct wolfIP_ll_dev *ll; + + wolfIP_init(&s); + mock_link_init(&s); + + /* Attach a counter poll to the loopback device */ + ll = wolfIP_ll_at(&s, TEST_LOOPBACK_IF); + ck_assert_ptr_nonnull(ll); + + poll_dispatcher_poll_calls = 0; + ll->poll = poll_returns_zero; + + (void)wolfIP_poll(&s, 100); + /* Loopback has its own poll; should have been called */ + ck_assert_int_ge(poll_dispatcher_poll_calls, 1); +} +END_TEST +#endif /* WOLFIP_ENABLE_LOOPBACK */ + +START_TEST(test_poll_multiple_udp_sockets_both_cbs_dispatched) +{ + struct wolfIP s; + int udp_sd1, udp_sd2; + struct tsocket *ts1, *ts2; + + wolfIP_init(&s); + mock_link_init(&s); + socket_cb_calls = 0; + + udp_sd1 = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd1, 0); + wolfIP_register_callback(&s, udp_sd1, test_socket_cb, NULL); + ts1 = &s.udpsockets[SOCKET_UNMARK(udp_sd1)]; + ts1->events = CB_EVENT_READABLE; + + udp_sd2 = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd2, 0); + wolfIP_register_callback(&s, udp_sd2, test_socket_cb, NULL); + ts2 = &s.udpsockets[SOCKET_UNMARK(udp_sd2)]; + ts2->events = CB_EVENT_WRITABLE; + + (void)wolfIP_poll(&s, 100); + ck_assert_int_eq(socket_cb_calls, 2); +} +END_TEST + +START_TEST(test_poll_tcp_cb_not_dispatched_when_closed) +{ + struct wolfIP s; + int tcp_sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + socket_cb_calls = 0; + + tcp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(tcp_sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(tcp_sd)]; + /* Leave state == TCP_CLOSED */ + ts->sock.tcp.state = TCP_CLOSED; + wolfIP_register_callback(&s, tcp_sd, test_socket_cb, NULL); + ts->events = CB_EVENT_READABLE; + + (void)wolfIP_poll(&s, 100); + /* Callback must NOT fire for CLOSED state */ + ck_assert_int_eq(socket_cb_calls, 0); +} +END_TEST + +START_TEST(test_poll_udp_cb_not_dispatched_without_events) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + socket_cb_calls = 0; + + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + wolfIP_register_callback(&s, udp_sd, test_socket_cb, NULL); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + /* No events set */ + ts->events = 0; + + (void)wolfIP_poll(&s, 100); + ck_assert_int_eq(socket_cb_calls, 0); +} +END_TEST + +#ifdef IP_MULTICAST +START_TEST(test_poll_tx_udp_multicast_arp_skipped_uses_mcast_mac) +{ + struct wolfIP s; + int udp_sd; + struct wolfIP_sockaddr_in sin; + struct wolfIP_ip_mreq mreq; + uint8_t payload[4] = {1, 2, 3, 4}; + int one = 1; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + + memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr.s_addr = ee32(0xE0000001U); /* 224.0.0.1 */ + mreq.imr_interface.s_addr = ee32(0x0A000001U); + (void)wolfIP_sock_setsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + (void)wolfIP_sock_setsockopt(&s, udp_sd, WOLFIP_SOL_IP, + WOLFIP_IP_MULTICAST_LOOP, &one, sizeof(one)); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(5500); + sin.sin_addr.s_addr = ee32(0xE0000001U); + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + + (void)wolfIP_poll(&s, 200); + /* Frame sent (multicast MAC derived from IP, no ARP needed) */ + ck_assert_uint_gt(last_frame_sent_size, 0U); + /* Destination MAC should be 01:00:5E:xx:xx:xx */ + ck_assert_uint_eq(last_frame_sent[0], 0x01); + ck_assert_uint_eq(last_frame_sent[1], 0x00); + ck_assert_uint_eq(last_frame_sent[2], 0x5E); +} +END_TEST +#endif /* IP_MULTICAST */ diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 9cb38c5a..ec30cd33 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -1,3 +1,24 @@ +/* unit_tests_proto.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + START_TEST(test_tcp_input_fin_wait_1_fin_with_payload_returns) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_socket_api_arms.c b/src/test/unit/unit_tests_socket_api_arms.c new file mode 100644 index 00000000..b845822d --- /dev/null +++ b/src/test/unit/unit_tests_socket_api_arms.c @@ -0,0 +1,1721 @@ +/* unit_tests_socket_api_arms.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +/* ---- wolfIP_register_callback: TCP, RAW, and PACKET arms ---- */ + +START_TEST(test_register_callback_tcp_stores_handle) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + + wolfIP_register_callback(&s, sd, test_socket_cb, (void *)0x42); + ck_assert_ptr_eq(ts->callback, test_socket_cb); + ck_assert_ptr_eq(ts->callback_arg, (void *)0x42); +} +END_TEST + +START_TEST(test_register_callback_tcp_oor_ignored) +{ + struct wolfIP s; + wolfIP_init(&s); + mock_link_init(&s); + /* out-of-range TCP fd — must not crash */ + wolfIP_register_callback(&s, MARK_TCP_SOCKET | MAX_TCPSOCKETS, + test_socket_cb, NULL); +} +END_TEST + +#if WOLFIP_RAWSOCKETS +START_TEST(test_register_callback_raw_stores_handle) +{ + struct wolfIP s; + int sd; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + + wolfIP_register_callback(&s, sd, test_socket_cb, (void *)0xBEEF); + ck_assert_ptr_eq(s.rawsockets[SOCKET_UNMARK(sd)].callback, test_socket_cb); + ck_assert_ptr_eq(s.rawsockets[SOCKET_UNMARK(sd)].callback_arg, (void *)0xBEEF); +} +END_TEST + +START_TEST(test_register_callback_raw_oor_ignored) +{ + struct wolfIP s; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_register_callback(&s, MARK_RAW_SOCKET | WOLFIP_MAX_RAWSOCKETS, + test_socket_cb, NULL); +} +END_TEST +#endif /* WOLFIP_RAWSOCKETS */ + +#if WOLFIP_PACKET_SOCKETS +START_TEST(test_register_callback_packet_stores_handle) +{ + struct wolfIP s; + int sd; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, 0); + ck_assert_int_ge(sd, 0); + + wolfIP_register_callback(&s, sd, test_socket_cb, (void *)0xCAFE); + ck_assert_ptr_eq(s.packetsockets[SOCKET_UNMARK(sd)].callback, test_socket_cb); +} +END_TEST + +START_TEST(test_register_callback_packet_oor_ignored) +{ + struct wolfIP s; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_register_callback(&s, MARK_PACKET_SOCKET | WOLFIP_MAX_PACKETSOCKETS, + test_socket_cb, NULL); +} +END_TEST +#endif /* WOLFIP_PACKET_SOCKETS */ + +/* ---- wolfIP_sock_can_read / wolfIP_sock_can_write: TCP and RAW arms ---- */ + +START_TEST(test_sock_can_read_tcp_established_empty) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_ESTABLISHED; + + /* empty queue and ESTABLISHED → not readable */ + ck_assert_int_eq(wolfIP_sock_can_read(&s, sd), 0); +} +END_TEST + +START_TEST(test_sock_can_read_tcp_close_wait_returns_one) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_CLOSE_WAIT; + + /* CLOSE_WAIT always reports readable so caller sees EOF */ + ck_assert_int_eq(wolfIP_sock_can_read(&s, sd), 1); +} +END_TEST + +START_TEST(test_sock_can_read_tcp_invalid_fd) +{ + struct wolfIP s; + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_can_read(&s, MARK_TCP_SOCKET | MAX_TCPSOCKETS), + -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_can_write_tcp_syn_sent_returns_zero) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_SYN_SENT; + + ck_assert_int_eq(wolfIP_sock_can_write(&s, sd), 0); +} +END_TEST + +START_TEST(test_sock_can_write_tcp_established_with_space) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_ESTABLISHED; + + ck_assert_int_eq(wolfIP_sock_can_write(&s, sd), 1); +} +END_TEST + +START_TEST(test_sock_can_write_tcp_closed_returns_one) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_CLOSED; + + /* not ESTABLISHED and not SYN_SENT → reports writable */ + ck_assert_int_eq(wolfIP_sock_can_write(&s, sd), 1); +} +END_TEST + +START_TEST(test_sock_can_write_tcp_invalid_fd) +{ + struct wolfIP s; + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_can_write(&s, MARK_TCP_SOCKET | MAX_TCPSOCKETS), + -WOLFIP_EINVAL); +} +END_TEST + +#if WOLFIP_RAWSOCKETS +START_TEST(test_sock_can_read_raw_empty) +{ + struct wolfIP s; + int sd; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + ck_assert_int_eq(wolfIP_sock_can_read(&s, sd), 0); +} +END_TEST + +START_TEST(test_sock_can_read_raw_invalid_fd) +{ + struct wolfIP s; + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_can_read(&s, MARK_RAW_SOCKET | WOLFIP_MAX_RAWSOCKETS), + -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_can_write_raw_with_space) +{ + struct wolfIP s; + int sd; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + ck_assert_int_eq(wolfIP_sock_can_write(&s, sd), 1); +} +END_TEST + +START_TEST(test_sock_can_write_raw_invalid_fd) +{ + struct wolfIP s; + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_can_write(&s, MARK_RAW_SOCKET | WOLFIP_MAX_RAWSOCKETS), + -WOLFIP_EINVAL); +} +END_TEST +#endif /* WOLFIP_RAWSOCKETS */ + +#if WOLFIP_PACKET_SOCKETS +START_TEST(test_sock_can_read_packet_empty) +{ + struct wolfIP s; + int sd; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, 0); + ck_assert_int_ge(sd, 0); + ck_assert_int_eq(wolfIP_sock_can_read(&s, sd), 0); +} +END_TEST + +START_TEST(test_sock_can_write_packet_with_space) +{ + struct wolfIP s; + int sd; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, 0); + ck_assert_int_ge(sd, 0); + ck_assert_int_eq(wolfIP_sock_can_write(&s, sd), 1); +} +END_TEST +#endif /* WOLFIP_PACKET_SOCKETS */ + +/* ---- wolfIP_sock_bind: TCP, RAW, and PACKET arms ---- */ + +START_TEST(test_sock_bind_tcp_success) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000001U); + sin.sin_port = ee16(8080); + + ck_assert_int_eq(wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), 0); + ck_assert_uint_eq(s.tcpsockets[SOCKET_UNMARK(sd)].src_port, 8080); + ck_assert_uint_eq(s.tcpsockets[SOCKET_UNMARK(sd)].bound_local_ip, + 0x0A000001U); +} +END_TEST + +START_TEST(test_sock_bind_tcp_any_ip_uses_primary) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = IPADDR_ANY; /* 0.0.0.0 */ + sin.sin_port = ee16(9090); + + ck_assert_int_eq(wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), 0); + /* bound_local_ip stays IPADDR_ANY but local_ip gets primary */ + ck_assert_uint_eq(s.tcpsockets[SOCKET_UNMARK(sd)].bound_local_ip, + IPADDR_ANY); + ck_assert_uint_eq(s.tcpsockets[SOCKET_UNMARK(sd)].local_ip, + 0x0A000001U); + ck_assert_uint_eq(s.tcpsockets[SOCKET_UNMARK(sd)].src_port, 9090); +} +END_TEST + +START_TEST(test_sock_bind_tcp_oor_fd) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000001U); + sin.sin_port = ee16(1234); + + ck_assert_int_eq(wolfIP_sock_bind(&s, MARK_TCP_SOCKET | MAX_TCPSOCKETS, + (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_bind_tcp_state_closed_required) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_LISTEN; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000001U); + sin.sin_port = ee16(80); + + /* bind on non-CLOSED socket must fail */ + ck_assert_int_eq(wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), -1); +} +END_TEST + +#if WOLFIP_RAWSOCKETS +START_TEST(test_sock_bind_raw_specific_interface) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000001U); + + ck_assert_int_eq(wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), 0); + ck_assert_uint_eq(s.rawsockets[SOCKET_UNMARK(sd)].local_ip, 0x0A000001U); + ck_assert_uint_eq(s.rawsockets[SOCKET_UNMARK(sd)].bound_local_ip, 0x0A000001U); +} +END_TEST + +START_TEST(test_sock_bind_raw_any_ip_uses_primary) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = IPADDR_ANY; + + ck_assert_int_eq(wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), 0); + ck_assert_uint_eq(s.rawsockets[SOCKET_UNMARK(sd)].bound_local_ip, IPADDR_ANY); + ck_assert_uint_eq(s.rawsockets[SOCKET_UNMARK(sd)].local_ip, 0x0A000001U); +} +END_TEST + +START_TEST(test_sock_bind_raw_wrong_family) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_PACKET; /* wrong */ + sin.sin_addr.s_addr = ee32(0x0A000001U); + + ck_assert_int_eq(wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_bind_raw_oor_fd) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000001U); + + ck_assert_int_eq(wolfIP_sock_bind(&s, MARK_RAW_SOCKET | WOLFIP_MAX_RAWSOCKETS, + (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), -WOLFIP_EINVAL); +} +END_TEST +#endif /* WOLFIP_RAWSOCKETS */ + +#if WOLFIP_PACKET_SOCKETS +START_TEST(test_sock_bind_packet_success) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_ll sll; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, 0); + ck_assert_int_ge(sd, 0); + + memset(&sll, 0, sizeof(sll)); + sll.sll_family = AF_PACKET; + sll.sll_protocol = ee16(ETH_TYPE_IP); + sll.sll_ifindex = (int)TEST_PRIMARY_IF; + sll.sll_halen = 6; + + ck_assert_int_eq(wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sll, + sizeof(sll)), 0); + ck_assert_uint_eq(s.packetsockets[SOCKET_UNMARK(sd)].if_idx, + (uint8_t)TEST_PRIMARY_IF); +} +END_TEST + +START_TEST(test_sock_bind_packet_wrong_family) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_ll sll; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, 0); + ck_assert_int_ge(sd, 0); + + memset(&sll, 0, sizeof(sll)); + sll.sll_family = AF_INET; /* wrong */ + sll.sll_ifindex = (int)TEST_PRIMARY_IF; + + ck_assert_int_eq(wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sll, + sizeof(sll)), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_bind_packet_out_of_range_ifindex) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_ll sll; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, 0); + ck_assert_int_ge(sd, 0); + + memset(&sll, 0, sizeof(sll)); + sll.sll_family = AF_PACKET; + sll.sll_ifindex = (int)s.if_count; /* == if_count is out of range */ + + ck_assert_int_eq(wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sll, + sizeof(sll)), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_bind_packet_oor_fd) +{ + struct wolfIP s; + struct wolfIP_sockaddr_ll sll; + + wolfIP_init(&s); + mock_link_init(&s); + + memset(&sll, 0, sizeof(sll)); + sll.sll_family = AF_PACKET; + sll.sll_ifindex = 0; + + ck_assert_int_eq(wolfIP_sock_bind(&s, MARK_PACKET_SOCKET | WOLFIP_MAX_PACKETSOCKETS, + (struct wolfIP_sockaddr *)&sll, + sizeof(sll)), -WOLFIP_EINVAL); +} +END_TEST +#endif /* WOLFIP_PACKET_SOCKETS */ + +/* ---- wolfIP_sock_connect: TCP and RAW arms ---- */ + +START_TEST(test_sock_connect_tcp_invalid_fd) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + sin.sin_port = ee16(80); + + ck_assert_int_eq(wolfIP_sock_connect(&s, MARK_TCP_SOCKET | MAX_TCPSOCKETS, + (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_connect_tcp_syn_sent_returns_eagain) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_SYN_SENT; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + sin.sin_port = ee16(80); + + ck_assert_int_eq(wolfIP_sock_connect(&s, sd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), -WOLFIP_EAGAIN); +} +END_TEST + +START_TEST(test_sock_connect_tcp_established_arm_returns_zero) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_ESTABLISHED; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + sin.sin_port = ee16(80); + + ck_assert_int_eq(wolfIP_sock_connect(&s, sd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), 0); +} +END_TEST + +START_TEST(test_sock_connect_tcp_bad_state_returns_einval) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_LISTEN; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + sin.sin_port = ee16(80); + + ck_assert_int_eq(wolfIP_sock_connect(&s, sd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), -WOLFIP_EINVAL); +} +END_TEST + +#if WOLFIP_RAWSOCKETS +START_TEST(test_sock_connect_raw_sets_remote_ip) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + + ck_assert_int_eq(wolfIP_sock_connect(&s, sd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), 0); + ck_assert_uint_eq(s.rawsockets[SOCKET_UNMARK(sd)].remote_ip, 0x0A000002U); +} +END_TEST + +START_TEST(test_sock_connect_raw_invalid_fd) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + + ck_assert_int_eq(wolfIP_sock_connect(&s, MARK_RAW_SOCKET | WOLFIP_MAX_RAWSOCKETS, + (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_connect_raw_wrong_family) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_PACKET; /* wrong */ + sin.sin_addr.s_addr = ee32(0x0A000002U); + + ck_assert_int_eq(wolfIP_sock_connect(&s, sd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_connect_raw_with_bound_local_ip) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + /* pre-bind so connect uses the bound IP path */ + s.rawsockets[SOCKET_UNMARK(sd)].bound_local_ip = 0x0A000001U; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + + ck_assert_int_eq(wolfIP_sock_connect(&s, sd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), 0); + ck_assert_uint_eq(s.rawsockets[SOCKET_UNMARK(sd)].local_ip, 0x0A000001U); +} +END_TEST +#endif /* WOLFIP_RAWSOCKETS */ + +/* ---- wolfIP_sock_sendto: TCP, RAW, and PACKET arms ---- */ + +START_TEST(test_sock_sendto_tcp_established_sends_data) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + uint8_t buf[64]; + int ret; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->remote_ip = 0x0A000002U; + ts->src_port = 12345; + ts->dst_port = 80; + ts->sock.tcp.peer_rwnd = 65535; + ts->sock.tcp.cwnd = 65535; + + memset(buf, 'A', sizeof(buf)); + ret = wolfIP_sock_sendto(&s, sd, buf, sizeof(buf), 0, NULL, 0); + ck_assert_int_gt(ret, 0); +} +END_TEST + +START_TEST(test_sock_sendto_tcp_invalid_fd) +{ + struct wolfIP s; + uint8_t buf[8] = {0}; + + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_sendto(&s, MARK_TCP_SOCKET | MAX_TCPSOCKETS, + buf, sizeof(buf), 0, NULL, 0), + -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_sendto_tcp_close_wait_sends_data) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + uint8_t buf[64]; + int ret; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_CLOSE_WAIT; + ts->remote_ip = 0x0A000002U; + ts->src_port = 54321; + ts->dst_port = 80; + ts->sock.tcp.peer_rwnd = 65535; + ts->sock.tcp.cwnd = 65535; + + memset(buf, 'B', sizeof(buf)); + ret = wolfIP_sock_sendto(&s, sd, buf, sizeof(buf), 0, NULL, 0); + ck_assert_int_gt(ret, 0); +} +END_TEST + +#if WOLFIP_RAWSOCKETS +START_TEST(test_sock_sendto_raw_null_dest_uses_stored_remote_ip) +{ + struct wolfIP s; + int sd; + uint8_t payload[8] = {1, 2, 3, 4, 5, 6, 7, 8}; + uint8_t nh_mac[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60}; + int ret; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, nh_mac, 6); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + /* Pre-set remote_ip so sendto with NULL dest uses it */ + s.rawsockets[SOCKET_UNMARK(sd)].remote_ip = 0x0A000002U; + + ret = wolfIP_sock_sendto(&s, sd, payload, sizeof(payload), 0, NULL, 0); + ck_assert_int_eq(ret, (int)sizeof(payload)); +} +END_TEST + +START_TEST(test_sock_sendto_raw_null_dest_no_remote_ip) +{ + struct wolfIP s; + int sd; + uint8_t payload[8] = {0}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + /* remote_ip is 0 and no dest_addr provided */ + + ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, payload, sizeof(payload), 0, + NULL, 0), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_sendto_raw_hdrincl_dst_from_buf) +{ + struct wolfIP s; + int sd; + int one = 1; + /* struct wolfIP_ip_packet embeds an Ethernet header at offset 0; the + * IP fields live at offset ETH_HEADER_LEN, so the backing buffer must + * include both. */ + uint8_t ip_buf[ETH_HEADER_LEN + IP_HEADER_LEN + 4]; + struct wolfIP_ip_packet *ip; + uint8_t nh_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + int ret; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, nh_mac, 6); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP, + WOLFIP_IP_HDRINCL, &one, sizeof(one)), 0); + + /* Build a minimal IP packet within the buffer; the Ethernet bytes are + * harmless filler that sendto ignores when IP_HDRINCL is set. */ + memset(ip_buf, 0, sizeof(ip_buf)); + ip = (struct wolfIP_ip_packet *)ip_buf; + ip->ver_ihl = 0x45; + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + 4); + ip->src = ee32(0x0A000001U); + ip->dst = ee32(0x0A000002U); + iphdr_set_checksum(ip); + + /* pass the raw IP header (after ETH offset) to sendto */ + ret = wolfIP_sock_sendto(&s, sd, + (uint8_t *)ip + ETH_HEADER_LEN, + IP_HEADER_LEN + 4, + 0, NULL, 0); + ck_assert_int_eq(ret, IP_HEADER_LEN + 4); +} +END_TEST + +START_TEST(test_sock_sendto_raw_invalid_fd) +{ + struct wolfIP s; + uint8_t buf[8] = {0}; + + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_sendto(&s, MARK_RAW_SOCKET | WOLFIP_MAX_RAWSOCKETS, + buf, sizeof(buf), 0, NULL, 0), + -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_sendto_raw_fifo_full_returns_eagain) +{ + struct wolfIP s; + int sd; + uint8_t payload[8] = {0}; + struct wolfIP_sockaddr_in sin; + struct rawsocket *rs; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + rs = &s.rawsockets[SOCKET_UNMARK(sd)]; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A000002U); + + /* Fill the txbuf completely by writing dummy bytes */ + rs->txbuf.tail = 0; + rs->txbuf.head = 0; + rs->txbuf.size = 0; /* force fifo_space to return 0 */ + + ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), -WOLFIP_EAGAIN); +} +END_TEST + +START_TEST(test_sock_setsockopt_raw_hdrincl) +{ + struct wolfIP s; + int sd; + int enable = 1; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP, + WOLFIP_IP_HDRINCL, &enable, sizeof(enable)), 0); + ck_assert_int_eq(s.rawsockets[SOCKET_UNMARK(sd)].ipheader_include, 1); + + enable = 0; + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP, + WOLFIP_IP_HDRINCL, &enable, sizeof(enable)), 0); + ck_assert_int_eq(s.rawsockets[SOCKET_UNMARK(sd)].ipheader_include, 0); +} +END_TEST + +START_TEST(test_sock_setsockopt_raw_dontroute) +{ + struct wolfIP s; + int sd; + int enable = 1; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_SOCKET, + WOLFIP_SO_DONTROUTE, &enable, sizeof(enable)), 0); + ck_assert_int_eq(s.rawsockets[SOCKET_UNMARK(sd)].dontroute, 1); +} +END_TEST + +START_TEST(test_sock_setsockopt_raw_recvttl) +{ + struct wolfIP s; + int sd; + int enable = 1; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP, + WOLFIP_IP_RECVTTL, &enable, sizeof(enable)), 0); + ck_assert_int_eq(s.rawsockets[SOCKET_UNMARK(sd)].recv_ttl, 1); +} +END_TEST + +START_TEST(test_sock_setsockopt_raw_unknown_option) +{ + struct wolfIP s; + int sd; + int enable = 1; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + + /* unknown combination returns EINVAL for raw sockets */ + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_SOCKET, + 0x7FFF, &enable, sizeof(enable)), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_setsockopt_raw_null_optval) +{ + struct wolfIP s; + int sd; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP, + WOLFIP_IP_HDRINCL, NULL, sizeof(int)), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_setsockopt_raw_invalid_fd) +{ + struct wolfIP s; + int enable = 1; + + wolfIP_init(&s); + mock_link_init(&s); + + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, + MARK_RAW_SOCKET | WOLFIP_MAX_RAWSOCKETS, + WOLFIP_SOL_IP, WOLFIP_IP_HDRINCL, &enable, sizeof(enable)), + -WOLFIP_EINVAL); +} +END_TEST +#endif /* WOLFIP_RAWSOCKETS */ + +#if WOLFIP_PACKET_SOCKETS +START_TEST(test_sock_setsockopt_packet_returns_einval) +{ + struct wolfIP s; + int sd; + int enable = 1; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, 0); + ck_assert_int_ge(sd, 0); + + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP, + WOLFIP_IP_HDRINCL, &enable, sizeof(enable)), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_sendto_packet_writes_raw_frame) +{ + struct wolfIP s; + int sd; + uint8_t frame[ETH_HEADER_LEN + 4]; + struct wolfIP_eth_frame *eth = (struct wolfIP_eth_frame *)frame; + int ret; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, 0); + ck_assert_int_ge(sd, 0); + + memset(frame, 0, sizeof(frame)); + memset(eth->dst, 0xFF, 6); /* broadcast */ + memset(eth->src, 0x11, 6); + eth->type = ee16(ETH_TYPE_IP); + + ret = wolfIP_sock_sendto(&s, sd, frame, sizeof(frame), 0, NULL, 0); + ck_assert_int_eq(ret, (int)sizeof(frame)); +} +END_TEST + +START_TEST(test_sock_sendto_packet_with_sll_updates_dst_mac) +{ + struct wolfIP s; + int sd; + uint8_t frame[ETH_HEADER_LEN + 4]; + struct wolfIP_eth_frame *eth = (struct wolfIP_eth_frame *)frame; + struct wolfIP_sockaddr_ll sll; + int ret; + uint8_t expected_mac[6]; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, 0); + ck_assert_int_ge(sd, 0); + + memset(frame, 0, sizeof(frame)); + eth->type = ee16(ETH_TYPE_IP); + + memset(&sll, 0, sizeof(sll)); + sll.sll_family = AF_PACKET; + sll.sll_ifindex = (int)TEST_PRIMARY_IF; + sll.sll_halen = 6; + memset(sll.sll_addr, 0xAA, 6); + memset(expected_mac, 0xAA, 6); + + ret = wolfIP_sock_sendto(&s, sd, frame, sizeof(frame), 0, + (struct wolfIP_sockaddr *)&sll, sizeof(sll)); + ck_assert_int_eq(ret, (int)sizeof(frame)); + + /* sendto copies to internal pkt_frame and pushes to fifo; poll drains it. + * Verify by polling and checking last_frame_sent. */ + wolfIP_poll(&s, 0); + ck_assert_uint_ge(last_frame_sent_size, ETH_HEADER_LEN); + ck_assert_mem_eq(last_frame_sent, expected_mac, 6); +} +END_TEST + +START_TEST(test_sock_sendto_packet_too_small) +{ + struct wolfIP s; + int sd; + uint8_t tiny[ETH_HEADER_LEN - 1]; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, 0); + ck_assert_int_ge(sd, 0); + memset(tiny, 0, sizeof(tiny)); + + ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, tiny, sizeof(tiny), 0, + NULL, 0), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_sendto_packet_invalid_fd) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + 4]; + wolfIP_init(&s); + mock_link_init(&s); + memset(frame, 0, sizeof(frame)); + ck_assert_int_eq(wolfIP_sock_sendto(&s, + MARK_PACKET_SOCKET | WOLFIP_MAX_PACKETSOCKETS, + frame, sizeof(frame), 0, NULL, 0), -WOLFIP_EINVAL); +} +END_TEST +#endif /* WOLFIP_PACKET_SOCKETS */ + +/* ---- wolfIP_sock_recvfrom: TCP, RAW, and PACKET arms ---- */ + +START_TEST(test_sock_recvfrom_tcp_not_established) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + uint8_t buf[64]; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_CLOSED; + + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, sd, buf, sizeof(buf), 0, + NULL, NULL), -1); +} +END_TEST + +START_TEST(test_sock_recvfrom_tcp_established_empty_queue) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + uint8_t buf[64]; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_ESTABLISHED; + + /* empty queue → queue_pop returns -WOLFIP_EAGAIN which propagates */ + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, sd, buf, sizeof(buf), 0, + NULL, NULL), -WOLFIP_EAGAIN); +} +END_TEST + +START_TEST(test_sock_recvfrom_tcp_invalid_fd) +{ + struct wolfIP s; + uint8_t buf[64]; + + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, MARK_TCP_SOCKET | MAX_TCPSOCKETS, + buf, sizeof(buf), 0, NULL, NULL), + -WOLFIP_EINVAL); +} +END_TEST + +#if WOLFIP_RAWSOCKETS +START_TEST(test_sock_recvfrom_raw_empty) +{ + struct wolfIP s; + int sd; + uint8_t buf[64]; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, sd, buf, sizeof(buf), 0, + NULL, NULL), -WOLFIP_EAGAIN); +} +END_TEST + +START_TEST(test_sock_recvfrom_raw_invalid_fd) +{ + struct wolfIP s; + uint8_t buf[64]; + + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, MARK_RAW_SOCKET | WOLFIP_MAX_RAWSOCKETS, + buf, sizeof(buf), 0, NULL, NULL), + -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_recvfrom_raw_with_sin) +{ + struct wolfIP s; + int sd; + uint8_t frame_buf[ETH_HEADER_LEN + IP_HEADER_LEN + 8]; + struct wolfIP_ip_packet *frame = (struct wolfIP_ip_packet *)frame_buf; + uint8_t payload[8] = {1, 2, 3, 4, 5, 6, 7, 8}; + struct wolfIP_sockaddr_in sin; + socklen_t sin_len = sizeof(sin); + uint8_t rxbuf[64]; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + + memset(frame_buf, 0, sizeof(frame_buf)); + memcpy(frame->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + frame->eth.type = ee16(ETH_TYPE_IP); + frame->ver_ihl = 0x45; + frame->ttl = 64; + frame->proto = WI_IPPROTO_UDP; + frame->len = ee16(IP_HEADER_LEN + sizeof(payload)); + frame->src = ee32(0x0A000002U); + frame->dst = ee32(0x0A000001U); + memcpy(frame->data, payload, sizeof(payload)); + iphdr_set_checksum(frame); + + wolfIP_recv_ex(&s, TEST_PRIMARY_IF, frame, sizeof(frame_buf)); + + memset(&sin, 0, sizeof(sin)); + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, sd, rxbuf, sizeof(rxbuf), 0, + (struct wolfIP_sockaddr *)&sin, + &sin_len), + IP_HEADER_LEN + (int)sizeof(payload)); + ck_assert_uint_eq(sin.sin_family, AF_INET); + ck_assert_uint_eq(ee32(sin.sin_addr.s_addr), 0x0A000002U); +} +END_TEST +#endif /* WOLFIP_RAWSOCKETS */ + +#if WOLFIP_PACKET_SOCKETS +START_TEST(test_sock_recvfrom_packet_empty) +{ + struct wolfIP s; + int sd; + uint8_t buf[LINK_MTU]; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, 0); + ck_assert_int_ge(sd, 0); + + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, sd, buf, sizeof(buf), 0, + NULL, NULL), -WOLFIP_EAGAIN); +} +END_TEST + +START_TEST(test_sock_recvfrom_packet_invalid_fd) +{ + struct wolfIP s; + uint8_t buf[64]; + + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, + MARK_PACKET_SOCKET | WOLFIP_MAX_PACKETSOCKETS, + buf, sizeof(buf), 0, NULL, NULL), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_recvfrom_packet_null_addrlen_with_sll) +{ + struct wolfIP s; + int sd; + uint8_t buf[LINK_MTU]; + struct wolfIP_sockaddr_ll sll; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, 0); + ck_assert_int_ge(sd, 0); + + ck_assert_int_eq(wolfIP_sock_recvfrom(&s, sd, buf, sizeof(buf), 0, + (struct wolfIP_sockaddr *)&sll, + NULL), -WOLFIP_EINVAL); +} +END_TEST +#endif /* WOLFIP_PACKET_SOCKETS */ + +/* ---- wolfIP_sock_getsockname: TCP, RAW, and PACKET arms ---- */ + +START_TEST(test_sock_getsockname_tcp_success) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in out; + socklen_t outlen = sizeof(out); + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->local_ip = 0x0A000001U; + ts->src_port = 12345; + + ck_assert_int_eq(wolfIP_sock_getsockname(&s, sd, (struct wolfIP_sockaddr *)&out, + &outlen), 0); + ck_assert_uint_eq(out.sin_family, AF_INET); + ck_assert_uint_eq(ee32(out.sin_addr.s_addr), 0x0A000001U); + ck_assert_uint_eq(ee16(out.sin_port), 12345); +} +END_TEST + +START_TEST(test_sock_getsockname_tcp_invalid_fd) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in out; + socklen_t outlen = sizeof(out); + + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_getsockname(&s, MARK_TCP_SOCKET | MAX_TCPSOCKETS, + (struct wolfIP_sockaddr *)&out, + &outlen), -WOLFIP_EINVAL); +} +END_TEST + +#if WOLFIP_RAWSOCKETS +START_TEST(test_sock_getsockname_raw_success) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_in out; + socklen_t outlen = sizeof(out); + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + s.rawsockets[SOCKET_UNMARK(sd)].local_ip = 0x0A000001U; + + ck_assert_int_eq(wolfIP_sock_getsockname(&s, sd, (struct wolfIP_sockaddr *)&out, + &outlen), 0); + ck_assert_uint_eq(out.sin_family, AF_INET); + ck_assert_uint_eq(ee32(out.sin_addr.s_addr), 0x0A000001U); + ck_assert_uint_eq(ee16(out.sin_port), 0); +} +END_TEST + +START_TEST(test_sock_getsockname_raw_invalid_fd) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in out; + socklen_t outlen = sizeof(out); + + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_getsockname(&s, + MARK_RAW_SOCKET | WOLFIP_MAX_RAWSOCKETS, + (struct wolfIP_sockaddr *)&out, &outlen), -WOLFIP_EINVAL); +} +END_TEST +#endif /* WOLFIP_RAWSOCKETS */ + +#if WOLFIP_PACKET_SOCKETS +START_TEST(test_sock_getsockname_packet_success) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_ll sll_bind, sll_out; + socklen_t outlen = sizeof(sll_out); + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_PACKET, IPSTACK_SOCK_RAW, 0); + ck_assert_int_ge(sd, 0); + + memset(&sll_bind, 0, sizeof(sll_bind)); + sll_bind.sll_family = AF_PACKET; + sll_bind.sll_ifindex = (int)TEST_PRIMARY_IF; + sll_bind.sll_halen = 6; + wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sll_bind, sizeof(sll_bind)); + + ck_assert_int_eq(wolfIP_sock_getsockname(&s, sd, (struct wolfIP_sockaddr *)&sll_out, + &outlen), 0); + ck_assert_uint_eq(sll_out.sll_family, AF_PACKET); +} +END_TEST +#endif /* WOLFIP_PACKET_SOCKETS */ + +/* ---- wolfIP_sock_getpeername: TCP and RAW arms ---- */ + +START_TEST(test_sock_getpeername_tcp_success) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in out; + socklen_t outlen = sizeof(out); + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->remote_ip = 0x0A000002U; + ts->dst_port = 80; + + ck_assert_int_eq(wolfIP_sock_getpeername(&s, sd, (struct wolfIP_sockaddr *)&out, + &outlen), 0); + ck_assert_uint_eq(out.sin_family, AF_INET); + ck_assert_uint_eq(ee32(out.sin_addr.s_addr), 0x0A000002U); + ck_assert_uint_eq(ee16(out.sin_port), 80); +} +END_TEST + +START_TEST(test_sock_getpeername_tcp_invalid_fd) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in out; + socklen_t outlen = sizeof(out); + + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_getpeername(&s, MARK_TCP_SOCKET | MAX_TCPSOCKETS, + (struct wolfIP_sockaddr *)&out, + &outlen), -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_sock_getpeername_tcp_null_addr) +{ + struct wolfIP s; + int sd; + socklen_t outlen = sizeof(struct wolfIP_sockaddr_in); + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + + /* null addr pointer → -1 */ + ck_assert_int_eq(wolfIP_sock_getpeername(&s, sd, NULL, &outlen), -1); +} +END_TEST + +#if WOLFIP_RAWSOCKETS +START_TEST(test_sock_getpeername_raw_success) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_in out; + socklen_t outlen = sizeof(out); + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + s.rawsockets[SOCKET_UNMARK(sd)].remote_ip = 0x0A000002U; + + ck_assert_int_eq(wolfIP_sock_getpeername(&s, sd, (struct wolfIP_sockaddr *)&out, + &outlen), 0); + ck_assert_uint_eq(out.sin_family, AF_INET); + ck_assert_uint_eq(ee32(out.sin_addr.s_addr), 0x0A000002U); +} +END_TEST + +START_TEST(test_sock_getpeername_raw_no_remote_ip) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_in out; + socklen_t outlen = sizeof(out); + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + /* remote_ip defaults to 0 → returns -1 */ + + ck_assert_int_eq(wolfIP_sock_getpeername(&s, sd, (struct wolfIP_sockaddr *)&out, + &outlen), -1); +} +END_TEST + +START_TEST(test_sock_getpeername_raw_invalid_fd) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in out; + socklen_t outlen = sizeof(out); + + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_getpeername(&s, + MARK_RAW_SOCKET | WOLFIP_MAX_RAWSOCKETS, + (struct wolfIP_sockaddr *)&out, &outlen), -WOLFIP_EINVAL); +} +END_TEST +#endif /* WOLFIP_RAWSOCKETS */ + +/* ---- wolfIP_sock_get_recv_ttl: RAW arm ---- */ + +#if WOLFIP_RAWSOCKETS +START_TEST(test_sock_get_recv_ttl_raw_disabled) +{ + struct wolfIP s; + int sd; + int ttl = -1; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + + /* recv_ttl defaults to 0 → returns 0 */ + ck_assert_int_eq(wolfIP_sock_get_recv_ttl(&s, sd, &ttl), 0); +} +END_TEST + +START_TEST(test_sock_get_recv_ttl_raw_enabled) +{ + struct wolfIP s; + int sd; + int enable = 1; + int ttl = -1; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP, + WOLFIP_IP_RECVTTL, &enable, sizeof(enable)), 0); + s.rawsockets[SOCKET_UNMARK(sd)].last_pkt_ttl = 64; + + ck_assert_int_eq(wolfIP_sock_get_recv_ttl(&s, sd, &ttl), 1); + ck_assert_int_eq(ttl, 64); +} +END_TEST + +START_TEST(test_sock_get_recv_ttl_raw_null_ttl) +{ + struct wolfIP s; + int sd; + int enable = 1; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP, + WOLFIP_IP_RECVTTL, &enable, sizeof(enable)), 0); + s.rawsockets[SOCKET_UNMARK(sd)].last_pkt_ttl = 128; + + /* NULL ttl pointer should still return 1 and not crash */ + ck_assert_int_eq(wolfIP_sock_get_recv_ttl(&s, sd, NULL), 1); +} +END_TEST + +START_TEST(test_sock_get_recv_ttl_raw_invalid_fd) +{ + struct wolfIP s; + int ttl; + + wolfIP_init(&s); + mock_link_init(&s); + ck_assert_int_eq(wolfIP_sock_get_recv_ttl(&s, + MARK_RAW_SOCKET | WOLFIP_MAX_RAWSOCKETS, &ttl), + -WOLFIP_EINVAL); +} +END_TEST +#endif /* WOLFIP_RAWSOCKETS */ + +/* ---- wolfIP_notify_loopback_space_available ---- */ + +START_TEST(test_notify_loopback_tcp_sets_writable) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, WOLFIP_LOOPBACK_IP, WOLFIP_LOOPBACK_MASK, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->proto = WI_IPPROTO_TCP; + ts->if_idx = (uint8_t)WOLFIP_LOOPBACK_IF_IDX; + ts->events = 0; + + /* calling the loopback notifier indirectly via wolfIP_poll triggers notify */ + wolfIP_notify_loopback_space_available(&s); + + ck_assert_int_ne(ts->events & CB_EVENT_WRITABLE, 0); +} +END_TEST + +START_TEST(test_notify_loopback_tcp_non_loopback_not_notified) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->proto = WI_IPPROTO_TCP; + ts->if_idx = (uint8_t)TEST_PRIMARY_IF; /* not loopback */ + ts->events = 0; + + wolfIP_notify_loopback_space_available(&s); + + /* not on loopback → events unchanged */ + ck_assert_int_eq(ts->events & CB_EVENT_WRITABLE, 0); +} +END_TEST + +START_TEST(test_notify_loopback_null_stack_no_crash) +{ + /* must not crash */ + wolfIP_notify_loopback_space_available(NULL); +} +END_TEST diff --git a/src/test/unit/unit_tests_tcp_ack.c b/src/test/unit/unit_tests_tcp_ack.c index 8fb5a8a7..0e593d78 100644 --- a/src/test/unit/unit_tests_tcp_ack.c +++ b/src/test/unit/unit_tests_tcp_ack.c @@ -1,3 +1,24 @@ +/* unit_tests_tcp_ack.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + START_TEST(test_dns_abort_query_null_noop) { dns_abort_query(NULL); diff --git a/src/test/unit/unit_tests_tcp_flow.c b/src/test/unit/unit_tests_tcp_flow.c index 87ee0376..a1ee2874 100644 --- a/src/test/unit/unit_tests_tcp_flow.c +++ b/src/test/unit/unit_tests_tcp_flow.c @@ -1,3 +1,24 @@ +/* unit_tests_tcp_flow.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + START_TEST(test_tcp_ack_wraparound_delta_reduces_inflight) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_tcp_state.c b/src/test/unit/unit_tests_tcp_state.c new file mode 100644 index 00000000..a3b2bd38 --- /dev/null +++ b/src/test/unit/unit_tests_tcp_state.c @@ -0,0 +1,2053 @@ +/* unit_tests_tcp_state.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +/* ----------------------------------------------------------------------- + * Helper: build and feed a TCP segment with options + optional payload. + * ----------------------------------------------------------------------- */ +static void inject_tcp_segment_with_opts(struct wolfIP *s, unsigned int if_idx, + ip4 src_ip, ip4 dst_ip, + uint16_t src_port, uint16_t dst_port, + uint32_t seq, uint32_t ack_val, uint8_t flags, + const uint8_t *opts, uint8_t opts_len, + const uint8_t *payload, uint16_t payload_len) +{ + uint8_t buf[LINK_MTU]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)buf; + struct wolfIP_ll_dev *ll = wolfIP_getdev_ex(s, if_idx); + union transport_pseudo_header ph; + static const uint8_t src_mac[6] = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35}; + uint8_t hdr_len_bytes; + uint16_t total_tcp_len; + uint16_t ip_len; + uint32_t frame_len; + uint8_t *opt_dst; + + ck_assert_ptr_nonnull(ll); + hdr_len_bytes = (uint8_t)(TCP_HEADER_LEN + ((opts_len + 3) & ~3)); + total_tcp_len = (uint16_t)(hdr_len_bytes + payload_len); + ip_len = (uint16_t)(IP_HEADER_LEN + total_tcp_len); + frame_len = (uint32_t)(ETH_HEADER_LEN + ip_len); + + ck_assert_uint_le(frame_len, sizeof(buf)); + memset(buf, 0, frame_len); + + memcpy(seg->ip.eth.dst, ll->mac, 6); + memcpy(seg->ip.eth.src, src_mac, 6); + seg->ip.eth.type = ee16(ETH_TYPE_IP); + seg->ip.ver_ihl = 0x45; + seg->ip.ttl = 64; + seg->ip.proto = WI_IPPROTO_TCP; + seg->ip.len = ee16(ip_len); + seg->ip.src = ee32(src_ip); + seg->ip.dst = ee32(dst_ip); + iphdr_set_checksum(&seg->ip); + + seg->src_port = ee16(src_port); + seg->dst_port = ee16(dst_port); + seg->seq = ee32(seq); + seg->ack = ee32(ack_val); + seg->hlen = (uint8_t)((hdr_len_bytes >> 2) << 4); + seg->flags = flags; + seg->win = ee16(32768); + seg->csum = 0; + seg->urg = 0; + + opt_dst = (uint8_t *)buf + sizeof(struct wolfIP_tcp_seg); + if (opts && opts_len > 0) + memcpy(opt_dst, opts, opts_len); + + if (payload && payload_len > 0) + memcpy(opt_dst + hdr_len_bytes - TCP_HEADER_LEN, payload, payload_len); + + memset(&ph, 0, sizeof(ph)); + ph.ph.src = seg->ip.src; + ph.ph.dst = seg->ip.dst; + ph.ph.proto = WI_IPPROTO_TCP; + ph.ph.len = ee16(total_tcp_len); + seg->csum = ee16(transport_checksum(&ph, &seg->src_port)); + + tcp_input(s, if_idx, seg, frame_len); +} + +/* ------------------------------------------------------------------ + * Helper: set up a connected ESTABLISHED socket in tcpsockets[0]. + * ------------------------------------------------------------------ */ +static const uint8_t setup_peer_mac[6] = {0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + +static struct tsocket *setup_established_socket(struct wolfIP *s, + ip4 local_ip, ip4 remote_ip, + uint16_t local_port, uint16_t remote_port) +{ + struct tsocket *ts = &s->tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.ack = 100; + ts->sock.tcp.seq = 200; + ts->sock.tcp.snd_una = 200; + ts->sock.tcp.last = 200; + ts->sock.tcp.peer_rwnd = TCP_MSS * 4; + ts->sock.tcp.cwnd = TCP_MSS * 4; + ts->sock.tcp.ssthresh = TCP_MSS * 8; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->src_port = local_port; + ts->dst_port = remote_port; + ts->if_idx = TEST_PRIMARY_IF; + ts->sock.tcp.tmr_rto = NO_TIMER; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + /* Initialize rxbuf so queue_space() > 0 (needed for tcp_segment_acceptable) */ + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, 0); + /* Pre-populate ARP so tcp_send_ack() can send a frame */ + arp_store_neighbor(s, TEST_PRIMARY_IF, remote_ip, (uint8_t *)setup_peer_mac); + return ts; +} + +/* =================================================================== + * tcp_send_reset_reply — 8 missing branches + * =================================================================== */ + +/* RST bit set in incoming segment: reset-reply is suppressed */ +START_TEST(test_tcp_send_reset_reply_ignores_rst_input) +{ + struct wolfIP s; + struct wolfIP_tcp_seg in; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + last_frame_sent_size = 0; + + memset(&in, 0, sizeof(in)); + in.ip.ver_ihl = 0x45; + in.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + in.ip.src = ee32(0x0A000002U); + in.ip.dst = ee32(0x0A000001U); + in.ip.proto = WI_IPPROTO_TCP; + in.hlen = TCP_HEADER_LEN << 2; + /* RST flag set: the reply must be suppressed (RFC 793 §3.4) */ + in.flags = TCP_FLAG_RST; + in.src_port = ee16(4000); + in.dst_port = ee16(8080); + iphdr_set_checksum(&in.ip); + + tcp_send_reset_reply(&s, TEST_PRIMARY_IF, &in); + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + +/* Incoming has ACK: outgoing RST copies seq from in->ack, no ACK flag */ +START_TEST(test_tcp_send_reset_reply_ack_in_uses_ack_seq) +{ + static const uint8_t peer_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + struct wolfIP s; + struct wolfIP_tcp_seg in; + struct wolfIP_tcp_seg *out; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + memset(&in, 0, sizeof(in)); + in.ip.ver_ihl = 0x45; + in.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + in.ip.src = ee32(0x0A000002U); + in.ip.dst = ee32(0x0A000001U); + in.ip.proto = WI_IPPROTO_TCP; + in.hlen = TCP_HEADER_LEN << 2; + in.flags = TCP_FLAG_ACK; /* ACK branch */ + in.ack = ee32(0x12345678U); + in.src_port = ee16(4000); + in.dst_port = ee16(8080); + memcpy(in.ip.eth.src, peer_mac, 6); + iphdr_set_checksum(&in.ip); + + arp_store_neighbor(&s, TEST_PRIMARY_IF, 0x0A000002U, (uint8_t *)peer_mac); + + last_frame_sent_size = 0; + tcp_send_reset_reply(&s, TEST_PRIMARY_IF, &in); + + ck_assert_uint_gt(last_frame_sent_size, 0U); + out = (struct wolfIP_tcp_seg *)last_frame_sent; + ck_assert_uint_eq(ee32(out->seq), 0x12345678U); + ck_assert_uint_eq(out->flags & (TCP_FLAG_RST | TCP_FLAG_ACK), TCP_FLAG_RST); +} +END_TEST + +/* No ACK in incoming SYN: reply carries RST|ACK with ack = seq+1 */ +START_TEST(test_tcp_send_reset_reply_syn_no_ack_sets_rst_ack) +{ + static const uint8_t peer_mac2[6] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; + struct wolfIP s; + struct wolfIP_tcp_seg in; + struct wolfIP_tcp_seg *out; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + memset(&in, 0, sizeof(in)); + in.ip.ver_ihl = 0x45; + in.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + in.ip.src = ee32(0x0A000002U); + in.ip.dst = ee32(0x0A000001U); + in.ip.proto = WI_IPPROTO_TCP; + in.hlen = TCP_HEADER_LEN << 2; + in.flags = TCP_FLAG_SYN; /* SYN, no ACK: reply must be RST|ACK */ + in.seq = ee32(1000); + in.src_port = ee16(5000); + in.dst_port = ee16(8080); + memcpy(in.ip.eth.src, peer_mac2, 6); + iphdr_set_checksum(&in.ip); + + arp_store_neighbor(&s, TEST_PRIMARY_IF, 0x0A000002U, (uint8_t *)peer_mac2); + + last_frame_sent_size = 0; + tcp_send_reset_reply(&s, TEST_PRIMARY_IF, &in); + + ck_assert_uint_gt(last_frame_sent_size, 0U); + out = (struct wolfIP_tcp_seg *)last_frame_sent; + /* ack = seq + 1 (SYN consumes one sequence number) */ + ck_assert_uint_eq(ee32(out->ack), 1001U); + ck_assert_uint_eq(out->flags & (TCP_FLAG_RST | TCP_FLAG_ACK), + TCP_FLAG_RST | TCP_FLAG_ACK); +} +END_TEST + +/* =================================================================== + * tcp_parse_options — 8 missing branches + * =================================================================== */ + +/* WS option with shift > 14 is clamped to 14 */ +START_TEST(test_tcp_parse_options_ws_clamped_to_14) +{ + /* SYN with WS option shift = 15 (must be clamped to 14) */ + uint8_t opts[] = { + TCP_OPTION_WS, 3, 15, /* kind=WS len=3 shift=15 */ + TCP_OPTION_EOO + }; + struct wolfIP s; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_LISTEN; + ts->src_port = 8080; + + inject_tcp_segment_with_opts(&s, TEST_PRIMARY_IF, + 0x0A0000A1U, 0x0A000001U, 40000, 8080, + 1, 0, TCP_FLAG_SYN, + opts, (uint8_t)sizeof(opts), NULL, 0); + + ck_assert_int_eq(ts->sock.tcp.ws_enabled, 1); + ck_assert_uint_eq(ts->sock.tcp.snd_wscale, 14); +} +END_TEST + +/* SACK option with invalid olen (not >=10 or not %8==0) is ignored */ +START_TEST(test_tcp_parse_options_sack_bad_olen_ignored) +{ + /* SACK with olen=4 is invalid (min valid is 10); must be skipped */ + uint8_t opts[] = { + TCP_OPTION_SACK, 4, 0, 0, /* invalid SACK block, olen=4 */ + TCP_OPTION_EOO + }; + struct wolfIP s; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_LISTEN; + ts->src_port = 8080; + + inject_tcp_segment_with_opts(&s, TEST_PRIMARY_IF, + 0x0A0000A1U, 0x0A000001U, 40000, 8080, + 1, 0, TCP_FLAG_SYN, + opts, (uint8_t)sizeof(opts), NULL, 0); + + ck_assert_uint_eq(ts->sock.tcp.sack_permitted, 0); +} +END_TEST + +/* NOP option advances the pointer by one */ +START_TEST(test_tcp_parse_options_nop_advances) +{ + /* NOP padding before MSS */ + uint8_t opts[] = { + TCP_OPTION_NOP, TCP_OPTION_NOP, + TCP_OPTION_MSS, 4, 0x05, 0xB4, /* MSS=1460 */ + TCP_OPTION_EOO + }; + struct wolfIP s; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_LISTEN; + ts->src_port = 8080; + + inject_tcp_segment_with_opts(&s, TEST_PRIMARY_IF, + 0x0A0000A1U, 0x0A000001U, 40000, 8080, + 1, 0, TCP_FLAG_SYN, + opts, (uint8_t)sizeof(opts), NULL, 0); + + ck_assert_uint_eq(ts->sock.tcp.peer_mss, 1460); +} +END_TEST + +/* Option olen=0: parser must break (loop termination guard) */ +START_TEST(test_tcp_parse_options_zero_olen_breaks) +{ + /* Unknown option with olen=0: would infinite-loop without the guard */ + uint8_t opts[] = { + 0xFF, 0, /* unknown kind, olen=0 */ + TCP_OPTION_EOO + }; + struct wolfIP s; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_LISTEN; + ts->src_port = 8080; + + /* Must complete without hanging */ + inject_tcp_segment_with_opts(&s, TEST_PRIMARY_IF, + 0x0A0000A1U, 0x0A000001U, 40000, 8080, + 1, 0, TCP_FLAG_SYN, + opts, (uint8_t)sizeof(opts), NULL, 0); +} +END_TEST + +/* Timestamp option correctly parsed from SYN */ +START_TEST(test_tcp_parse_options_timestamp_parsed) +{ + /* TS option: kind=8, len=10, TSval=0x0102, TSEcr=0 */ + uint8_t opts[] = { + TCP_OPTION_NOP, TCP_OPTION_NOP, + TCP_OPTION_TS, 10, + 0x00, 0x00, 0x01, 0x02, /* TSval = 0x0102 */ + 0x00, 0x00, 0x00, 0x00, /* TSEcr = 0 */ + TCP_OPTION_EOO + }; + struct wolfIP s; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_LISTEN; + ts->src_port = 8080; + ts->sock.tcp.sack_offer = 1; + + inject_tcp_segment_with_opts(&s, TEST_PRIMARY_IF, + 0x0A0000A1U, 0x0A000001U, 40000, 8080, + 1, 0, TCP_FLAG_SYN, + opts, (uint8_t)sizeof(opts), NULL, 0); + + ck_assert_int_eq(ts->sock.tcp.ts_enabled, 1); +} +END_TEST + +/* MSS option value 0 is ignored (falls back to default) */ +START_TEST(test_tcp_parse_options_mss_zero_ignored) +{ + uint8_t opts[] = { + TCP_OPTION_MSS, 4, 0x00, 0x00, /* MSS=0 must be ignored */ + TCP_OPTION_EOO + }; + struct wolfIP s; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_LISTEN; + ts->src_port = 8080; + + inject_tcp_segment_with_opts(&s, TEST_PRIMARY_IF, + 0x0A0000A1U, 0x0A000001U, 40000, 8080, + 1, 0, TCP_FLAG_SYN, + opts, (uint8_t)sizeof(opts), NULL, 0); + + /* When mss_found=0, tcp_input falls back to TCP_DEFAULT_MSS */ + ck_assert_uint_eq(ts->sock.tcp.peer_mss, TCP_DEFAULT_MSS); +} +END_TEST + +/* SACK-permitted option is parsed */ +START_TEST(test_tcp_parse_options_sack_permitted_parsed) +{ + uint8_t opts[] = { + TCP_OPTION_SACK_PERMITTED, 2, /* kind=4, len=2 */ + TCP_OPTION_EOO + }; + struct wolfIP s; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_LISTEN; + ts->src_port = 8080; + ts->sock.tcp.sack_offer = 1; /* we also offer SACK */ + + inject_tcp_segment_with_opts(&s, TEST_PRIMARY_IF, + 0x0A0000A1U, 0x0A000001U, 40000, 8080, + 1, 0, TCP_FLAG_SYN, + opts, (uint8_t)sizeof(opts), NULL, 0); + + ck_assert_int_eq(ts->sock.tcp.sack_permitted, 1); +} +END_TEST + +/* =================================================================== + * tcp_input — state machine transitions + * =================================================================== */ + +/* RST in SYN_RCVD without matching seq is ignored (stays SYN_RCVD) */ +START_TEST(test_tcp_input_syn_rcvd_rst_bad_seq_ignored) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 8080, rport = 40000; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_SYN_RCVD; + ts->sock.tcp.ack = 2; /* rcv_nxt = 2 */ + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->src_port = lport; + ts->dst_port = rport; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + /* RST with wrong seq: must be silently dropped, socket stays SYN_RCVD */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 999, 0, TCP_FLAG_RST); + + ck_assert_int_eq(ts->sock.tcp.state, TCP_SYN_RCVD); + ck_assert_int_ne(ts->proto, 0); +} +END_TEST + +/* RST in SYN_RCVD with matching seq → revert to LISTEN */ +START_TEST(test_tcp_input_syn_rcvd_rst_good_seq_reverts_to_listen) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 8080, rport = 40000; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_SYN_RCVD; + ts->sock.tcp.ack = 2; /* rcv_nxt = 2 */ + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->src_port = lport; + ts->dst_port = rport; + ts->sock.tcp.tmr_rto = NO_TIMER; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + /* RST with seq == rcv_nxt: should revert to LISTEN */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 2, 0, TCP_FLAG_RST); + + ck_assert_int_eq(ts->sock.tcp.state, TCP_LISTEN); + ck_assert_int_ne(ts->proto, 0); /* socket not destroyed */ +} +END_TEST + +/* Time-wait state re-ACKs any incoming segment */ +START_TEST(test_tcp_input_time_wait_sends_ack_on_any_segment) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 9000, rport = 40001; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_TIME_WAIT; + ts->sock.tcp.ack = 100; + ts->sock.tcp.seq = 200; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->src_port = lport; + ts->dst_port = rport; + ts->if_idx = TEST_PRIMARY_IF; + ts->sock.tcp.peer_rwnd = 32768; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, 0); + arp_store_neighbor(&s, TEST_PRIMARY_IF, remote_ip, (uint8_t *)setup_peer_mac); + + last_frame_sent_size = 0; + /* Any ACK arriving in TIME_WAIT should cause us to re-send an ACK */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 99, 200, TCP_FLAG_ACK | TCP_FLAG_FIN); + + /* ACK is queued in txbuf (tcp_send_ack uses fifo_push, not immediate tx) */ + ck_assert_ptr_nonnull(fifo_peek(&ts->sock.tcp.txbuf)); + ck_assert_int_eq(ts->sock.tcp.state, TCP_TIME_WAIT); +} +END_TEST + +/* LAST_ACK: unacceptable segment causes challenge ACK */ +START_TEST(test_tcp_input_last_ack_unacceptable_sends_ack) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 9001, rport = 40002; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_LAST_ACK; + ts->sock.tcp.ack = 100; + ts->sock.tcp.seq = 200; + ts->sock.tcp.last = 200; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->src_port = lport; + ts->dst_port = rport; + ts->if_idx = TEST_PRIMARY_IF; + ts->sock.tcp.peer_rwnd = 0; /* zero window */ + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + /* rxbuf size=0 → queue_space=0 → rcv_wnd=0 → segment not acceptable */ + arp_store_neighbor(&s, TEST_PRIMARY_IF, remote_ip, (uint8_t *)setup_peer_mac); + + last_frame_sent_size = 0; + /* Segment with wrong seq (outside window) → challenge ACK */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 999, 201, TCP_FLAG_ACK); + + /* ACK is queued in txbuf (tcp_send_ack uses fifo_push, not immediate tx) */ + ck_assert_ptr_nonnull(fifo_peek(&ts->sock.tcp.txbuf)); + ck_assert_int_eq(ts->sock.tcp.state, TCP_LAST_ACK); +} +END_TEST + +/* SYN on LAST_ACK synchronized connection → challenge ACK (RFC 5961) */ +START_TEST(test_tcp_input_last_ack_syn_sends_challenge_ack) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 9002, rport = 40003; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_LAST_ACK; + ts->sock.tcp.ack = 100; + ts->sock.tcp.seq = 200; + ts->sock.tcp.last = 200; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->src_port = lport; + ts->dst_port = rport; + ts->if_idx = TEST_PRIMARY_IF; + ts->sock.tcp.peer_rwnd = 32768; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, 0); + arp_store_neighbor(&s, TEST_PRIMARY_IF, remote_ip, (uint8_t *)setup_peer_mac); + + last_frame_sent_size = 0; + /* SYN within window → challenge ACK, not close */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 100, 200, TCP_FLAG_SYN | TCP_FLAG_ACK); + + /* ACK is queued in txbuf (tcp_send_ack uses fifo_push, not immediate tx) */ + ck_assert_ptr_nonnull(fifo_peek(&ts->sock.tcp.txbuf)); + ck_assert_int_eq(ts->sock.tcp.state, TCP_LAST_ACK); +} +END_TEST + +/* SYN in ESTABLISHED (synchronized) → challenge ACK per RFC 5961 */ +START_TEST(test_tcp_input_established_syn_sends_challenge_ack) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 9003, rport = 40004; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = setup_established_socket(&s, local_ip, remote_ip, lport, rport); + + last_frame_sent_size = 0; + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 100, 200, TCP_FLAG_SYN); + + /* ACK is queued in txbuf (tcp_send_ack uses fifo_push, not immediate tx) */ + ck_assert_ptr_nonnull(fifo_peek(&ts->sock.tcp.txbuf)); + ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); +} +END_TEST + +/* Unacceptable out-of-window segment in ESTABLISHED sends challenge ACK */ +START_TEST(test_tcp_input_established_out_of_window_sends_ack) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 9004, rport = 40005; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = setup_established_socket(&s, local_ip, remote_ip, lport, rport); + ts->sock.tcp.peer_rwnd = 32768; + + last_frame_sent_size = 0; + /* seq far in the past → out of window */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 1, 200, TCP_FLAG_ACK); + + /* ACK is queued in txbuf (tcp_send_ack uses fifo_push, not immediate tx) */ + ck_assert_ptr_nonnull(fifo_peek(&ts->sock.tcp.txbuf)); + ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); +} +END_TEST + +/* No ACK bit in ESTABLISHED → segment dropped without reply */ +START_TEST(test_tcp_input_established_no_ack_dropped) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 9005, rport = 40006; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = setup_established_socket(&s, local_ip, remote_ip, lport, rport); + + last_frame_sent_size = 0; + /* No ACK flag: must be silently dropped */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 100, 0, TCP_FLAG_PSH); + + ck_assert_uint_eq(last_frame_sent_size, 0U); + ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); +} +END_TEST + +/* ESTABLISHED FIN with non-matching seq (out-of-order) does not advance state */ +START_TEST(test_tcp_input_established_fin_ooo_no_close_wait) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 9006, rport = 40007; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = setup_established_socket(&s, local_ip, remote_ip, lport, rport); + ts->sock.tcp.ack = 100; /* expect seq=100 for in-order FIN */ + + /* FIN at seq=200 (not rcv_nxt=100) → out-of-order, no state transition */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 200, 200, TCP_FLAG_ACK | TCP_FLAG_FIN); + + ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); +} +END_TEST + +/* SYN_RCVD ACK with FIN in same segment → transitions directly to CLOSE_WAIT */ +START_TEST(test_tcp_input_syn_rcvd_ack_with_fin_enters_close_wait) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 9007, rport = 40008; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_SYN_RCVD; + ts->sock.tcp.ack = 2; /* rcv_nxt = 2 */ + ts->sock.tcp.seq = 1; + ts->sock.tcp.snd_una = 1; /* SYN-ACK not yet acked */ + ts->sock.tcp.last = 1; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->src_port = lport; + ts->dst_port = rport; + ts->if_idx = TEST_PRIMARY_IF; + ts->sock.tcp.peer_rwnd = 32768; + ts->sock.tcp.tmr_rto = NO_TIMER; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + /* ACK + FIN completing the handshake: expected_ack = snd_una+1 = 2 */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 2, 2, TCP_FLAG_ACK | TCP_FLAG_FIN); + + ck_assert_int_eq(ts->sock.tcp.state, TCP_CLOSE_WAIT); +} +END_TEST + +/* Peer window growth from 0 → non-zero stops persist timer */ +START_TEST(test_tcp_input_window_grows_from_zero_stops_persist) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 9008, rport = 40009; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = setup_established_socket(&s, local_ip, remote_ip, lport, rport); + ts->sock.tcp.peer_rwnd = 0; + ts->sock.tcp.persist_active = 1; + + /* Peer sends ACK reopening the window */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 100, 200, TCP_FLAG_ACK); + + ck_assert_int_eq(ts->sock.tcp.persist_active, 0); +} +END_TEST + +/* =================================================================== + * tcp_rto_cb — additional missing branches + * =================================================================== */ + +/* fin_wait_2_timeout fires and closes the socket */ +START_TEST(test_tcp_rto_cb_fin_wait_2_timeout_closes_socket) +{ + struct wolfIP s; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_FIN_WAIT_2; + ts->sock.tcp.tmr_rto = NO_TIMER; + ts->sock.tcp.fin_wait_2_timeout_active = 1; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + tcp_rto_cb(ts); + + ck_assert_int_eq(ts->proto, 0); +} +END_TEST + +/* fin_wait_2_timeout fires but socket is no longer in FIN_WAIT_2 → just stop */ +START_TEST(test_tcp_rto_cb_fin_wait_2_wrong_state_stops_timer) +{ + struct wolfIP s; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_CLOSING; /* not FIN_WAIT_2 */ + ts->sock.tcp.tmr_rto = NO_TIMER; + ts->sock.tcp.fin_wait_2_timeout_active = 1; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + tcp_rto_cb(ts); + + ck_assert_int_ne(ts->proto, 0); + ck_assert_int_eq(ts->sock.tcp.fin_wait_2_timeout_active, 0); +} +END_TEST + +/* ctrl_rto fires when not needed (e.g. already in ESTABLISHED): stops */ +START_TEST(test_tcp_rto_cb_ctrl_not_needed_stops) +{ + struct wolfIP s; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; /* does NOT need ctrl RTO */ + ts->sock.tcp.ctrl_rto_active = 1; /* but timer thinks it is active */ + ts->sock.tcp.tmr_rto = NO_TIMER; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + tcp_rto_cb(ts); + + ck_assert_int_eq(ts->sock.tcp.ctrl_rto_active, 0); +} +END_TEST + +/* ctrl_rto max retries for a non-listener SYN_RCVD closes the socket */ +START_TEST(test_tcp_rto_cb_ctrl_maxretries_nonlistener_closes) +{ + struct wolfIP s; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_SYN_RCVD; + ts->sock.tcp.ctrl_rto_active = 1; + ts->sock.tcp.ctrl_rto_retries = TCP_CTRL_RTO_MAXRTX; /* exhausted */ + ts->sock.tcp.is_listener = 0; /* not a listener: must close */ + ts->sock.tcp.tmr_rto = NO_TIMER; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + tcp_rto_cb(ts); + + ck_assert_int_eq(ts->proto, 0); +} +END_TEST + +/* =================================================================== + * tcp_ack — additional missing branches + * =================================================================== */ + +/* ack == snd_una (duplicate) with zero inflight: returns early */ +START_TEST(test_tcp_ack_duplicate_zero_inflight_early_return) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_tcp_seg ackseg; + + wolfIP_init(&s); + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.snd_una = 100; + ts->sock.tcp.seq = 200; + ts->sock.tcp.bytes_in_flight = 0; /* zero inflight → early return */ + ts->sock.tcp.peer_rwnd = 32768; + ts->sock.tcp.cwnd = TCP_MSS * 4; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + memset(&ackseg, 0, sizeof(ackseg)); + ackseg.ack = ee32(100); /* duplicate: same as snd_una */ + ackseg.hlen = TCP_HEADER_LEN << 2; + ackseg.flags = TCP_FLAG_ACK; + ackseg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + + tcp_ack(ts, &ackseg); + /* dup_acks should not have been incremented */ + ck_assert_uint_eq(ts->sock.tcp.dup_acks, 0); +} +END_TEST + +/* Duplicate ACK where ack != snd_una: returns immediately */ +START_TEST(test_tcp_ack_duplicate_ack_ne_snd_una_returns) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_tcp_seg ackseg; + + wolfIP_init(&s); + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.snd_una = 100; + ts->sock.tcp.seq = 300; + ts->sock.tcp.bytes_in_flight = 50; + ts->sock.tcp.peer_rwnd = 32768; + ts->sock.tcp.cwnd = TCP_MSS * 4; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + memset(&ackseg, 0, sizeof(ackseg)); + /* ack=50 is below snd_una=100: neither new progress nor dup-ack of snd_una */ + ackseg.ack = ee32(50); + ackseg.hlen = TCP_HEADER_LEN << 2; + ackseg.flags = TCP_FLAG_ACK; + ackseg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + + tcp_ack(ts, &ackseg); + ck_assert_uint_eq(ts->sock.tcp.dup_acks, 0); + ck_assert_uint_eq(ts->sock.tcp.snd_una, 100); +} +END_TEST + +/* Fast recovery inflation on 4th duplicate ACK */ +START_TEST(test_tcp_ack_fourth_dupack_inflates_cwnd) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_tcp_seg ackseg; + uint32_t cwnd_before; + + wolfIP_init(&s); + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.snd_una = 100; + ts->sock.tcp.seq = 600; + ts->sock.tcp.bytes_in_flight = 500; + ts->sock.tcp.peer_rwnd = 32768; + ts->sock.tcp.cwnd = TCP_MSS * 4; + ts->sock.tcp.ssthresh = TCP_MSS * 2; + ts->sock.tcp.fast_recovery = 1; + ts->sock.tcp.dup_acks = 3; /* already 3 → 4th dup-ack inflates cwnd */ + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + cwnd_before = ts->sock.tcp.cwnd; + + memset(&ackseg, 0, sizeof(ackseg)); + ackseg.ack = ee32(100); /* duplicate ACK */ + ackseg.hlen = TCP_HEADER_LEN << 2; + ackseg.flags = TCP_FLAG_ACK; + ackseg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + + tcp_ack(ts, &ackseg); + /* cwnd should be inflated by one SMSS */ + ck_assert_uint_gt(ts->sock.tcp.cwnd, cwnd_before); +} +END_TEST + +/* CLOSE_WAIT state processes ACK normally */ +START_TEST(test_tcp_ack_close_wait_processes_ack) +{ + struct wolfIP s; + struct tsocket *ts; + struct tcp_seg_buf segbuf; + struct wolfIP_tcp_seg *seg; + struct pkt_desc *desc; + struct wolfIP_tcp_seg ackseg; + + wolfIP_init(&s); + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_CLOSE_WAIT; + ts->sock.tcp.snd_una = 100; + ts->sock.tcp.seq = 120; + ts->sock.tcp.last = 120; + ts->sock.tcp.bytes_in_flight = 20; + ts->sock.tcp.cwnd = TCP_MSS * 4; + ts->sock.tcp.peer_rwnd = TCP_MSS * 4; + ts->sock.tcp.ssthresh = TCP_MSS * 8; + ts->sock.tcp.tmr_rto = NO_TIMER; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + /* Push a sent segment */ + memset(&segbuf, 0, sizeof(segbuf)); + seg = &segbuf.seg; + seg->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + 20); + seg->hlen = TCP_HEADER_LEN << 2; + seg->seq = ee32(100); + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, &segbuf, sizeof(segbuf)), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + desc->flags |= PKT_FLAG_SENT; + + memset(&ackseg, 0, sizeof(ackseg)); + ackseg.ack = ee32(120); + ackseg.hlen = TCP_HEADER_LEN << 2; + ackseg.flags = TCP_FLAG_ACK; + ackseg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + + tcp_ack(ts, &ackseg); + ck_assert_uint_eq(ts->sock.tcp.snd_una, 120); + ck_assert_int_eq(ts->sock.tcp.state, TCP_CLOSE_WAIT); +} +END_TEST + +/* =================================================================== + * tcp_mark_unsacked_for_retransmit — additional missing branches + * =================================================================== */ + +/* cover_found=0, peer_sack_count=0: returns 0 without rescan */ +START_TEST(test_tcp_mark_unsacked_no_cover_no_sack_returns_zero) +{ + struct wolfIP s; + struct tsocket *ts; + struct tcp_seg_buf segbuf; + struct wolfIP_tcp_seg *seg; + struct pkt_desc *desc; + int ret; + + wolfIP_init(&s); + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.snd_una = 100; + ts->sock.tcp.peer_sack_count = 0; /* no sack → no rescan */ + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + /* Push an unsent segment ahead of snd_una */ + memset(&segbuf, 0, sizeof(segbuf)); + seg = &segbuf.seg; + seg->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + 10); + seg->hlen = TCP_HEADER_LEN << 2; + seg->seq = ee32(200); /* above ack=100, no cover → returns 0 */ + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, &segbuf, sizeof(segbuf)), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + desc->flags &= ~PKT_FLAG_SENT; + + ret = tcp_mark_unsacked_for_retransmit(ts, 100); + ck_assert_int_eq(ret, 0); +} +END_TEST + +/* =================================================================== + * tcp_resync_inflight — 9 missing branches + * =================================================================== */ + +/* Null stack or null ts → noop */ +START_TEST(test_tcp_resync_inflight_null_args) +{ + struct wolfIP s; + struct tsocket ts; + + wolfIP_init(&s); + + tcp_resync_inflight(NULL, &ts, 0); + tcp_resync_inflight(&s, NULL, 0); +} +END_TEST + +/* ctrl_rto_active: resync is skipped entirely */ +START_TEST(test_tcp_resync_inflight_skips_when_ctrl_rto_active) +{ + struct wolfIP s; + struct tsocket *ts; + + wolfIP_init(&s); + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_SYN_RCVD; /* ctrl state */ + ts->sock.tcp.ctrl_rto_active = 1; + ts->sock.tcp.bytes_in_flight = 42; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + tcp_resync_inflight(&s, ts, 0); + + ck_assert_uint_eq(ts->sock.tcp.bytes_in_flight, 42); +} +END_TEST + +/* Sent payload present → timer is armed */ +START_TEST(test_tcp_resync_inflight_arms_timer_on_sent_payload) +{ + struct wolfIP s; + struct tsocket *ts; + struct tcp_seg_buf segbuf; + struct wolfIP_tcp_seg *seg; + struct pkt_desc *desc; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.tmr_rto = NO_TIMER; + ts->sock.tcp.rto = 1000; + ts->sock.tcp.rto_backoff = 0; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + memset(&segbuf, 0, sizeof(segbuf)); + seg = &segbuf.seg; + seg->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + 10); + seg->hlen = TCP_HEADER_LEN << 2; + seg->seq = ee32(100); + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, &segbuf, sizeof(segbuf)), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + desc->flags |= PKT_FLAG_SENT; + + tcp_resync_inflight(&s, ts, 0); + + ck_assert_uint_eq(ts->sock.tcp.bytes_in_flight, 10); + ck_assert_int_ne((int)ts->sock.tcp.tmr_rto, NO_TIMER); +} +END_TEST + +/* No sent payload but timer armed → cancel timer */ +START_TEST(test_tcp_resync_inflight_cancels_timer_when_no_payload) +{ + struct wolfIP s; + struct tsocket *ts; + struct tcp_seg_buf segbuf; + struct wolfIP_tcp_seg *seg; + struct pkt_desc *desc; + struct wolfIP_timer tmr; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + + /* Insert a dummy timer so tmr_rto != NO_TIMER */ + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = tcp_rto_cb; + tmr.expires = 9999; + tmr.arg = ts; + ts->sock.tcp.tmr_rto = timers_binheap_insert(&s.timers, tmr); + ck_assert_int_ne((int)ts->sock.tcp.tmr_rto, NO_TIMER); + + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + /* Unsent (not PKT_FLAG_SENT) zero-len ACK segment in the queue */ + memset(&segbuf, 0, sizeof(segbuf)); + seg = &segbuf.seg; + seg->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); /* zero payload */ + seg->hlen = TCP_HEADER_LEN << 2; + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, &segbuf, sizeof(segbuf)), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + /* Not flagged as SENT → no payload in flight */ + + tcp_resync_inflight(&s, ts, 0); + + ck_assert_uint_eq(ts->sock.tcp.bytes_in_flight, 0); + ck_assert_int_eq((int)ts->sock.tcp.tmr_rto, NO_TIMER); +} +END_TEST + +/* =================================================================== + * tcp_find_pending_retrans — 12 missing branches + * =================================================================== */ + +/* NULL ts or NULL start: returns NULL */ +START_TEST(test_tcp_find_pending_retrans_null_args) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc dummy; + + wolfIP_init(&s); + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ck_assert_ptr_null(tcp_find_pending_retrans(NULL, &dummy)); + ck_assert_ptr_null(tcp_find_pending_retrans(ts, NULL)); +} +END_TEST + +/* Segment with RETRANS flag but already SENT → not returned */ +START_TEST(test_tcp_find_pending_retrans_already_sent_skipped) +{ + struct wolfIP s; + struct tsocket *ts; + struct tcp_seg_buf segbuf; + struct wolfIP_tcp_seg *seg; + struct pkt_desc *desc; + struct pkt_desc *result; + + wolfIP_init(&s); + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + memset(&segbuf, 0, sizeof(segbuf)); + seg = &segbuf.seg; + seg->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + 10); + seg->hlen = TCP_HEADER_LEN << 2; + seg->seq = ee32(100); + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, &segbuf, sizeof(segbuf)), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + /* RETRANS set but also SENT → must NOT be returned */ + desc->flags = PKT_FLAG_RETRANS | PKT_FLAG_SENT; + + result = tcp_find_pending_retrans(ts, desc); + ck_assert_ptr_null(result); +} +END_TEST + +/* Segment with RETRANS and NOT SENT and payload → returned */ +START_TEST(test_tcp_find_pending_retrans_unsent_retrans_returned) +{ + struct wolfIP s; + struct tsocket *ts; + struct tcp_seg_buf segbuf; + struct wolfIP_tcp_seg *seg; + struct pkt_desc *desc; + struct pkt_desc *result; + + wolfIP_init(&s); + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + memset(&segbuf, 0, sizeof(segbuf)); + seg = &segbuf.seg; + seg->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + 10); + seg->hlen = TCP_HEADER_LEN << 2; + seg->seq = ee32(100); + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, &segbuf, sizeof(segbuf)), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + desc->flags = PKT_FLAG_RETRANS; /* RETRANS + not SENT */ + + result = tcp_find_pending_retrans(ts, desc); + ck_assert_ptr_nonnull(result); + ck_assert_ptr_eq(result, desc); +} +END_TEST + +/* =================================================================== + * tcp_send_empty_immediate — 10 missing branches + * =================================================================== */ + +/* NULL tsocket → returns -1 */ +START_TEST(test_tcp_send_empty_immediate_null_tsocket) +{ + struct wolfIP_tcp_seg seg; + int ret; + memset(&seg, 0, sizeof(seg)); + ret = tcp_send_empty_immediate(NULL, &seg, + ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN); + ck_assert_int_eq(ret, -1); +} +END_TEST + +/* NULL tcp segment → returns -1 */ +START_TEST(test_tcp_send_empty_immediate_null_seg) +{ + struct wolfIP s; + struct tsocket *ts; + int ret; + + wolfIP_init(&s); + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->if_idx = TEST_PRIMARY_IF; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ret = tcp_send_empty_immediate(ts, NULL, + ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN); + ck_assert_int_eq(ret, -1); +} +END_TEST + +/* frame_len too small → returns -1 */ +START_TEST(test_tcp_send_empty_immediate_short_frame_len) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_tcp_seg seg; + int ret; + + wolfIP_init(&s); + mock_link_init(&s); + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->if_idx = TEST_PRIMARY_IF; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + memset(&seg, 0, sizeof(seg)); + + /* Deliberately short */ + ret = tcp_send_empty_immediate(ts, &seg, ETH_HEADER_LEN); + ck_assert_int_eq(ret, -1); +} +END_TEST + +/* Happy path: ARP hit → frame sent */ +START_TEST(test_tcp_send_empty_immediate_arp_hit_sends) +{ + static const uint8_t peer_mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01}; + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_tcp_seg seg; + int ret; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + ts->src_port = 9009; + ts->dst_port = 40010; + ts->if_idx = TEST_PRIMARY_IF; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.ack = 100; + ts->sock.tcp.peer_rwnd = 32768; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + arp_store_neighbor(&s, TEST_PRIMARY_IF, 0x0A000002U, (uint8_t *)peer_mac); + + memset(&seg, 0, sizeof(seg)); + seg.src_port = ee16(ts->src_port); + seg.dst_port = ee16(ts->dst_port); + seg.hlen = TCP_HEADER_LEN << 2; + seg.flags = TCP_FLAG_ACK; + + last_frame_sent_size = 0; + ret = tcp_send_empty_immediate(ts, &seg, + ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN); + + ck_assert_int_eq(ret, 0); + ck_assert_uint_gt(last_frame_sent_size, 0U); +} +END_TEST + +/* =================================================================== + * tcp_send_zero_wnd_probe — 11 missing branches + * =================================================================== */ + +/* NULL tsocket → -1 */ +START_TEST(test_tcp_send_zero_wnd_probe_null_ts) +{ + int ret = tcp_send_zero_wnd_probe(NULL); + ck_assert_int_eq(ret, -1); +} +END_TEST + +/* Non-TCP proto → -1 */ +START_TEST(test_tcp_send_zero_wnd_probe_non_tcp_proto) +{ + struct wolfIP s; + struct tsocket *ts; + int ret; + + wolfIP_init(&s); + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_UDP; /* not TCP */ + ts->S = &s; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ret = tcp_send_zero_wnd_probe(ts); + ck_assert_int_eq(ret, -1); +} +END_TEST + +/* Empty txbuf (no desc) → -1 */ +START_TEST(test_tcp_send_zero_wnd_probe_empty_txbuf) +{ + struct wolfIP s; + struct tsocket *ts; + int ret; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.snd_una = 100; + ts->if_idx = TEST_PRIMARY_IF; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ret = tcp_send_zero_wnd_probe(ts); + ck_assert_int_eq(ret, -1); +} +END_TEST + +/* Probe sent successfully when payload exists and ARP hit */ +START_TEST(test_tcp_send_zero_wnd_probe_sends_probe) +{ + static const uint8_t peer_mac[6] = {0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x01}; + static const uint8_t payload[4] = {0xAA, 0xBB, 0xCC, 0xDD}; + struct wolfIP s; + struct tsocket *ts; + struct tcp_seg_buf segbuf; + struct wolfIP_tcp_seg *seg; + int ret; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + ts->src_port = 9010; + ts->dst_port = 40011; + ts->if_idx = TEST_PRIMARY_IF; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.ack = 100; + ts->sock.tcp.snd_una = 200; + ts->sock.tcp.peer_rwnd = 0; /* zero window */ + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + /* Push a sent segment that overlaps snd_una */ + memset(&segbuf, 0, sizeof(segbuf)); + seg = &segbuf.seg; + seg->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + (uint16_t)sizeof(payload)); + seg->hlen = TCP_HEADER_LEN << 2; + seg->seq = ee32(200); /* starts at snd_una */ + memcpy((uint8_t *)seg->ip.data + TCP_HEADER_LEN, payload, sizeof(payload)); + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, &segbuf, sizeof(segbuf)), 0); + + arp_store_neighbor(&s, TEST_PRIMARY_IF, 0x0A000002U, (uint8_t *)peer_mac); + + last_frame_sent_size = 0; + ret = tcp_send_zero_wnd_probe(ts); + ck_assert_int_eq(ret, 0); + ck_assert_uint_gt(last_frame_sent_size, 0U); +} +END_TEST + +/* =================================================================== + * icmp_try_deliver_tcp_error — 17 missing branches + * =================================================================== */ + +/* Null pointer args: noop */ +START_TEST(test_icmp_try_deliver_tcp_error_null_args) +{ + struct wolfIP s; + wolfIP_init(&s); + icmp_try_deliver_tcp_error(NULL, NULL); + icmp_try_deliver_tcp_error(&s, NULL); +} +END_TEST + +/* Wrong ICMP type (echo request) is ignored */ +START_TEST(test_icmp_try_deliver_tcp_error_wrong_type_ignored) +{ + struct wolfIP s; + struct wolfIP_icmp_dest_unreachable_packet pkt; + + wolfIP_init(&s); + memset(&pkt, 0, sizeof(pkt)); + pkt.ip.len = ee16(IP_HEADER_LEN + sizeof(pkt) - sizeof(pkt.ip)); + pkt.type = 8; /* echo request: must be ignored */ + pkt.code = 0; + + icmp_try_deliver_tcp_error(&s, (const struct wolfIP_icmp_packet *)&pkt); +} +END_TEST + +/* ICMP TTL-exceeded matching socket in SYN_SENT: state unchanged (only DEST_UNREACH closes) */ +START_TEST(test_icmp_try_deliver_tcp_error_ttl_exceeded_syn_sent) +{ + struct wolfIP s; + struct tsocket *ts; + uint8_t buf[sizeof(struct wolfIP_icmp_packet) + IP_HEADER_LEN + 8]; + struct wolfIP_icmp_packet *icmp = (struct wolfIP_icmp_packet *)buf; + struct wolfIP_ip_wire *orig_ip; + uint8_t *orig_tcp_hdr; + uint16_t sp, dp; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_SYN_SENT; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000064U; + ts->src_port = 12345; + ts->dst_port = 80; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + memset(buf, 0, sizeof(buf)); + icmp->ip.len = ee16((uint16_t)(sizeof(buf) - ETH_HEADER_LEN)); + icmp->type = ICMP_TTL_EXCEEDED; + icmp->code = 0; + + orig_ip = (struct wolfIP_ip_wire *)(buf + sizeof(struct wolfIP_icmp_packet)); + orig_ip->ver_ihl = 0x45; + orig_ip->proto = WI_IPPROTO_TCP; + orig_ip->src = ee32(ts->local_ip); + orig_ip->dst = ee32(ts->remote_ip); + + orig_tcp_hdr = (uint8_t *)orig_ip + IP_HEADER_LEN; + sp = ee16(ts->src_port); + dp = ee16(ts->dst_port); + memcpy(orig_tcp_hdr, &sp, 2); + memcpy(orig_tcp_hdr + 2, &dp, 2); + + icmp_try_deliver_tcp_error(&s, icmp); + + /* TTL-exceeded does not close SYN_SENT */ + ck_assert_int_eq(ts->sock.tcp.state, TCP_SYN_SENT); +} +END_TEST + +/* ICMP DEST_UNREACH PORT_UNREACH matching SYN_SENT closes socket */ +START_TEST(test_icmp_try_deliver_tcp_error_port_unreach_syn_sent_closes) +{ + struct wolfIP s; + struct tsocket *ts; + uint8_t buf[sizeof(struct wolfIP_icmp_packet) + IP_HEADER_LEN + 8]; + struct wolfIP_icmp_packet *icmp = (struct wolfIP_icmp_packet *)buf; + struct wolfIP_ip_wire *orig_ip; + uint8_t *orig_tcp_hdr; + uint16_t sp, dp; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_SYN_SENT; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000064U; + ts->src_port = 54321; + ts->dst_port = 443; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + memset(buf, 0, sizeof(buf)); + icmp->ip.len = ee16((uint16_t)(sizeof(buf) - ETH_HEADER_LEN)); + icmp->type = ICMP_DEST_UNREACH; + icmp->code = ICMP_PORT_UNREACH; + + orig_ip = (struct wolfIP_ip_wire *)(buf + sizeof(struct wolfIP_icmp_packet)); + orig_ip->ver_ihl = 0x45; + orig_ip->proto = WI_IPPROTO_TCP; + orig_ip->src = ee32(ts->local_ip); + orig_ip->dst = ee32(ts->remote_ip); + + orig_tcp_hdr = (uint8_t *)orig_ip + IP_HEADER_LEN; + sp = ee16(ts->src_port); + dp = ee16(ts->dst_port); + memcpy(orig_tcp_hdr, &sp, 2); + memcpy(orig_tcp_hdr + 2, &dp, 2); + + icmp_try_deliver_tcp_error(&s, icmp); + + /* PORT_UNREACH on SYN_SENT must close the socket */ + ck_assert_int_eq(ts->proto, 0); +} +END_TEST + +/* ICMP DEST_UNREACH with mismatched src_ip: socket not closed */ +START_TEST(test_icmp_try_deliver_tcp_error_src_ip_mismatch_not_closed) +{ + struct wolfIP s; + struct tsocket *ts; + uint8_t buf[sizeof(struct wolfIP_icmp_packet) + IP_HEADER_LEN + 8]; + struct wolfIP_icmp_packet *icmp = (struct wolfIP_icmp_packet *)buf; + struct wolfIP_ip_wire *orig_ip; + uint8_t *orig_tcp_hdr; + uint16_t sp, dp; + + wolfIP_init(&s); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_SYN_SENT; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000064U; + ts->src_port = 11111; + ts->dst_port = 80; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + memset(buf, 0, sizeof(buf)); + icmp->ip.len = ee16((uint16_t)(sizeof(buf) - ETH_HEADER_LEN)); + icmp->type = ICMP_DEST_UNREACH; + icmp->code = ICMP_PORT_UNREACH; + + orig_ip = (struct wolfIP_ip_wire *)(buf + sizeof(struct wolfIP_icmp_packet)); + orig_ip->ver_ihl = 0x45; + orig_ip->proto = WI_IPPROTO_TCP; + /* Different source IP from what the socket has */ + orig_ip->src = ee32(0x0A000099U); /* mismatch */ + orig_ip->dst = ee32(ts->remote_ip); + + orig_tcp_hdr = (uint8_t *)orig_ip + IP_HEADER_LEN; + sp = ee16(ts->src_port); + dp = ee16(ts->dst_port); + memcpy(orig_tcp_hdr, &sp, 2); + memcpy(orig_tcp_hdr + 2, &dp, 2); + + icmp_try_deliver_tcp_error(&s, icmp); + + /* Mismatch: socket must not be closed */ + ck_assert_int_ne(ts->proto, 0); +} +END_TEST + +/* ICMP DEST_UNREACH FRAG_NEEDED reduces peer MSS */ +START_TEST(test_icmp_try_deliver_tcp_error_frag_needed_reduces_mss) +{ + struct wolfIP s; + struct tsocket *ts; + uint8_t buf[sizeof(struct wolfIP_icmp_packet) + IP_HEADER_LEN + 8]; + struct wolfIP_icmp_packet *icmp = (struct wolfIP_icmp_packet *)buf; + struct wolfIP_ip_wire *orig_ip; + uint8_t *orig_tcp_hdr; + uint16_t sp, dp, next_hop_mtu; + + wolfIP_init(&s); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000064U; + ts->src_port = 22222; + ts->dst_port = 80; + ts->sock.tcp.peer_mss = 1460; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + memset(buf, 0, sizeof(buf)); + icmp->ip.len = ee16((uint16_t)(sizeof(buf) - ETH_HEADER_LEN)); + icmp->type = ICMP_DEST_UNREACH; + icmp->code = ICMP_FRAG_NEEDED; + + /* next-hop MTU = 700 → new_mss = 700 - 40 = 660 */ + next_hop_mtu = ee16(700); + memcpy(&icmp->unused[2], &next_hop_mtu, sizeof(next_hop_mtu)); + + orig_ip = (struct wolfIP_ip_wire *)(buf + sizeof(struct wolfIP_icmp_packet)); + orig_ip->ver_ihl = 0x45; + orig_ip->proto = WI_IPPROTO_TCP; + orig_ip->src = ee32(ts->local_ip); + orig_ip->dst = ee32(ts->remote_ip); + + orig_tcp_hdr = (uint8_t *)orig_ip + IP_HEADER_LEN; + sp = ee16(ts->src_port); + dp = ee16(ts->dst_port); + memcpy(orig_tcp_hdr, &sp, 2); + memcpy(orig_tcp_hdr + 2, &dp, 2); + + icmp_try_deliver_tcp_error(&s, icmp); + + /* peer_mss must be reduced */ + ck_assert_uint_lt(ts->sock.tcp.peer_mss, 1460); +} +END_TEST + +/* icmp avail < IP_HEADER_LEN: returns without touching any socket */ +START_TEST(test_icmp_try_deliver_tcp_error_avail_too_small) +{ + struct wolfIP s; + struct wolfIP_icmp_packet icmp; + + wolfIP_init(&s); + + memset(&icmp, 0, sizeof(icmp)); + icmp.type = ICMP_DEST_UNREACH; + icmp.code = ICMP_PORT_UNREACH; + /* ip.len just barely covers ICMP header but not enough for orig IP header */ + icmp.ip.len = ee16((uint16_t)(IP_HEADER_LEN + sizeof(uint8_t) * 8 + 3)); + + icmp_try_deliver_tcp_error(&s, &icmp); +} +END_TEST + +/* =================================================================== + * raw_route_for_ip — 14 missing branches + * =================================================================== */ + +#if WOLFIP_RAWSOCKETS + +/* NULL stack → returns 0 */ +START_TEST(test_raw_route_for_ip_null_stack) +{ + unsigned int ret = raw_route_for_ip(NULL, NULL, 0x0A000001U, 0); + ck_assert_uint_eq(ret, 0); +} +END_TEST + +/* rs=NULL, dontroute=0 → falls through to wolfIP_route_for_ip */ +START_TEST(test_raw_route_for_ip_null_rs_uses_route) +{ + struct wolfIP s; + unsigned int ret; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ret = raw_route_for_ip(&s, NULL, 0x0A000002U, 0); + ck_assert_uint_lt(ret, s.if_count); +} +END_TEST + +/* rs has bound_local_ip that matches an interface */ +START_TEST(test_raw_route_for_ip_bound_local_ip_match) +{ + struct wolfIP s; + struct rawsocket rs; + unsigned int ret; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + memset(&rs, 0, sizeof(rs)); + rs.bound_local_ip = 0x0A000001U; /* matches primary interface */ + + ret = raw_route_for_ip(&s, &rs, 0x0A000002U, 0); + ck_assert_uint_eq(ret, TEST_PRIMARY_IF); +} +END_TEST + +/* rs has bound_local_ip that doesn't match any interface → falls through */ +START_TEST(test_raw_route_for_ip_bound_local_ip_no_match) +{ + struct wolfIP s; + struct rawsocket rs; + unsigned int ret; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + memset(&rs, 0, sizeof(rs)); + rs.bound_local_ip = 0x01020304U; /* no interface has this IP */ + + ret = raw_route_for_ip(&s, &rs, 0x0A000002U, 0); + ck_assert_uint_lt(ret, s.if_count); +} +END_TEST + +/* dontroute=1 with dest on local subnet → returns matching if */ +START_TEST(test_raw_route_for_ip_dontroute_local_match) +{ + struct wolfIP s; + unsigned int ret; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + /* 0x0A000002 is on the same /24 subnet as 0x0A000001 */ + ret = raw_route_for_ip(&s, NULL, 0x0A000002U, 1 /* dontroute */); + ck_assert_uint_eq(ret, TEST_PRIMARY_IF); +} +END_TEST + +/* dontroute=1 with non-local dest → falls through to route lookup */ +START_TEST(test_raw_route_for_ip_dontroute_no_local_match) +{ + struct wolfIP s; + unsigned int ret; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + /* 0xC0A80001 (192.168.0.1) is not on the 10.0.0.0/24 subnet */ + ret = raw_route_for_ip(&s, NULL, 0xC0A80001U, 1 /* dontroute */); + ck_assert_uint_lt(ret, s.if_count); +} +END_TEST + +#endif /* WOLFIP_RAWSOCKETS */ + +/* =================================================================== + * Additional tcp_input branches + * =================================================================== */ + +/* LISTEN state: RST is silently ignored (server stays open) */ +START_TEST(test_tcp_input_listen_rst_ignored) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + uint16_t lport = 7070; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_LISTEN; + ts->src_port = lport; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A000002U, local_ip, + 40000, lport, 1, 0, TCP_FLAG_RST); + + ck_assert_int_eq(ts->sock.tcp.state, TCP_LISTEN); + ck_assert_int_ne(ts->proto, 0); +} +END_TEST + +/* FIN_WAIT_1 receiving a FIN → CLOSING state */ +START_TEST(test_tcp_input_fin_wait_1_fin_enters_closing) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 8181, rport = 40020; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_FIN_WAIT_1; + ts->sock.tcp.ack = 100; + ts->sock.tcp.seq = 300; + ts->sock.tcp.last = 300; + ts->sock.tcp.snd_una = 300; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->src_port = lport; + ts->dst_port = rport; + ts->if_idx = TEST_PRIMARY_IF; + ts->sock.tcp.peer_rwnd = 32768; + ts->sock.tcp.tmr_rto = NO_TIMER; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, 0); + arp_store_neighbor(&s, TEST_PRIMARY_IF, remote_ip, (uint8_t *)setup_peer_mac); + + /* Peer sends FIN at rcv_nxt=100, ack=300 doesn't ack our FIN (needs 301) */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 100, 300, TCP_FLAG_ACK | TCP_FLAG_FIN); + + ck_assert_int_eq(ts->sock.tcp.state, TCP_CLOSING); +} +END_TEST + +/* FIN_WAIT_2 receiving a FIN → TIME_WAIT */ +START_TEST(test_tcp_input_fin_wait_2_fin_enters_time_wait) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 8282, rport = 40021; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_FIN_WAIT_2; + ts->sock.tcp.ack = 100; + ts->sock.tcp.seq = 300; + ts->sock.tcp.snd_una = 300; + ts->sock.tcp.last = 300; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->src_port = lport; + ts->dst_port = rport; + ts->if_idx = TEST_PRIMARY_IF; + ts->sock.tcp.peer_rwnd = 32768; + ts->sock.tcp.tmr_rto = NO_TIMER; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, 0); + arp_store_neighbor(&s, TEST_PRIMARY_IF, remote_ip, (uint8_t *)setup_peer_mac); + + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 100, 300, TCP_FLAG_ACK | TCP_FLAG_FIN); + + ck_assert_int_eq(ts->sock.tcp.state, TCP_TIME_WAIT); +} +END_TEST + +/* RST in ESTABLISHED with seq inside window but != rcv_nxt → sends ACK */ +START_TEST(test_tcp_input_rst_in_window_not_exact_sends_ack) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 8383, rport = 40022; + static uint8_t rxmem[RXBUF_SIZE]; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.ack = 100; /* rcv_nxt = 100 */ + ts->sock.tcp.seq = 200; + ts->sock.tcp.peer_rwnd = 32768; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->src_port = lport; + ts->dst_port = rport; + ts->if_idx = TEST_PRIMARY_IF; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + queue_init(&ts->sock.tcp.rxbuf, rxmem, RXBUF_SIZE, 0); + arp_store_neighbor(&s, TEST_PRIMARY_IF, remote_ip, (uint8_t *)setup_peer_mac); + + last_frame_sent_size = 0; + /* seq=105 is inside window but != rcv_nxt=100 → ACK, no close */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 105, 200, TCP_FLAG_RST); + + /* ACK is queued in txbuf (tcp_send_ack uses fifo_push, not immediate tx) */ + ck_assert_ptr_nonnull(fifo_peek(&ts->sock.tcp.txbuf)); + ck_assert_int_ne(ts->proto, 0); +} +END_TEST diff --git a/src/test/unit/unit_tests_tftp.c b/src/test/unit/unit_tests_tftp.c index 9531dc88..e1207cd6 100644 --- a/src/test/unit/unit_tests_tftp.c +++ b/src/test/unit/unit_tests_tftp.c @@ -1,6 +1,6 @@ /* unit_tests_tftp.c * - * Copyright (C) 2026 wolfSSL Inc. + * Copyright (C) 2024 wolfSSL Inc. * * This file is part of wolfIP TCP/IP stack. * @@ -18,7 +18,6 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ - struct tftp_test_ctx { uint8_t sent[32][WOLFTFTP_PKT_MAX]; uint16_t sent_len[32]; diff --git a/src/test/unit/unit_wolfguard.c b/src/test/unit/unit_wolfguard.c index fa0e9c79..01cce6c3 100644 --- a/src/test/unit/unit_wolfguard.c +++ b/src/test/unit/unit_wolfguard.c @@ -1,8 +1,6 @@ /* unit_wolfguard.c * - * Unit tests for wolfGuard — FIPS-compliant WireGuard for wolfIP - * - * Copyright (C) 2026 wolfSSL Inc. + * Copyright (C) 2024 wolfSSL Inc. * * This file is part of wolfIP TCP/IP stack. * @@ -10,8 +8,16 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. + * + * wolfIP 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ - #ifndef WOLFGUARD #define WOLFGUARD #endif diff --git a/src/wolfip.c b/src/wolfip.c index 8a1b3c05..1ef50815 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -8795,6 +8795,11 @@ static int dns_skip_name(const uint8_t *buf, int len, int offset) if (pos > len) return -1; } + /* Defensive: bound the number of label iterations to `len`. The in-loop + * guards above already prevent runaway pos advance, but this catch-all + * protects against any future refactor that lets the loop body advance + * pos by zero (e.g. a malformed compression pointer that doesn't break + * out). DoS via crafted name compression must NOT be possible. */ if (loop >= len) return -1; return pos; @@ -8812,6 +8817,9 @@ static int dns_copy_name(const uint8_t *buf, int len, int offset, char *out, if (c == DNS_NAME_TERMINATOR) { if (!jumped) pos++; + /* Defensive: when out_len == 0 the label-copy guards below + * never run, so this catches the empty-name + zero-capacity + * case. Required to prevent an out-of-bounds write to out[0]. */ if (o >= out_len) return -1; out[o] = DNS_NAME_TERMINATOR; @@ -9003,10 +9011,6 @@ void dns_callback(int dns_sd, uint16_t ev, void *arg) ee16(rr->class) == DNS_CLASS_IN && rdlen >= DNS_IPV4_RDATA_LEN) { uint32_t ip; - if (pos + DNS_IPV4_RDATA_LEN > dns_len) { - dns_abort_query(s); - return; - } ip = get_be32((const uint8_t *)buf + pos); if (s->dns_lookup_cb) s->dns_lookup_cb(ip);