diff --git a/include/tinycsocket.h b/include/tinycsocket.h index b449de1..4ed150c 100644 --- a/include/tinycsocket.h +++ b/include/tinycsocket.h @@ -33,6 +33,8 @@ #include #include +#define tcs_static_assert(name, expr) typedef char tcs_sa_##name[(expr) ? 1 : -1] + static const char* const TCS_VERSION_TXT = "v0.4-dev"; static const char* const TCS_LICENSE_TXT = "Copyright 2018 Markus Lindelöw\n" @@ -211,6 +213,14 @@ typedef enum TCS_AF_PACKET, /**< Layer 2 interface */ } TcsAddressFamily; +/** + * @brief IPv6 address (16 bytes), analogous to POSIX struct in6_addr. + */ +struct TcsIp6Address +{ + uint8_t bytes[16]; +}; + /** * @brief Network Address */ @@ -219,6 +229,7 @@ struct TcsAddress TcsAddressFamily family; union { + char _storage[24]; /**< Ensures full zero-initialization when copied from TCS_ADDRESS_NONE */ struct { uint32_t address; /**< Same byte order as the host */ @@ -227,7 +238,7 @@ struct TcsAddress } ip4; struct { - uint8_t address[16]; /**< Same byte order as the host */ + struct TcsIp6Address address; /**< Network byte order */ TcsInterfaceId scope_id; /**< Native type. Only valid for local link addresses. See ::tcs_interface_list(). */ uint16_t port; /**< Same byte order as the host */ @@ -257,12 +268,14 @@ struct TcsInterfaceAddress struct TcsAddress address; }; +tcs_static_assert(address_storage_size, sizeof(((struct TcsAddress*)0)->data) <= 24); + // gcc may trigger bug #53119 #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-braces" #endif -static const struct TcsAddress TCS_ADDRESS_NONE = {TCS_AF_ANY, {0, 0}}; +static const struct TcsAddress TCS_ADDRESS_NONE = {(TcsAddressFamily)0, {{0}}}; #ifdef __GNUC__ #pragma GCC diagnostic pop #endif @@ -272,6 +285,9 @@ extern const uint32_t TCS_ADDRESS_LOOPBACK_IP4; extern const uint32_t TCS_ADDRESS_BROADCAST_IP4; extern const uint32_t TCS_ADDRESS_NONE_IP4; +extern const struct TcsIp6Address TCS_ADDRESS_ANY_IP6; +extern const struct TcsIp6Address TCS_ADDRESS_LOOPBACK_IP6; + /** * @brief Used when sending/receiving an array of buffers. * @@ -2195,17 +2211,24 @@ TcsResult tcs_address_socket_remote(TcsSocket socket_ctx, struct TcsAddress* rem TcsResult tcs_address_socket_family(TcsSocket socket_ctx, TcsAddressFamily* out_family); /** - * @brief Get an address from a string. + * @brief Parse a network address from a string. * - * For example: + * Supports IPv4, IPv6 (RFC 4291 all three forms), MAC, and bracket/port notation (RFC 3986). + * IPv6 zone IDs are limited to numeric values per the minimum requirement of RFC 4007. + * String-based zone IDs (e.g. "%%eth0") are not supported. + * + * Examples: * - "192.168.0.1:1212" - * - "localhost:80" - * - "[::1]:443". + * - "::1" + * - "[::1]:443" + * - "fe80::1%%3" + * - "::ffff:192.168.1.1" + * - "91:E0:F0:00:FE:00" * * Note that this function will not perform DNS resolution. Use ::tcs_address_resolve() for that. * - * @param str - * @param out_address + * @param str The string to parse. + * @param out_address The parsed address. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_address_parse(const char str[], struct TcsAddress* out_address); @@ -2705,6 +2728,9 @@ const uint32_t TCS_ADDRESS_LOOPBACK_IP4 = INADDR_LOOPBACK; const uint32_t TCS_ADDRESS_BROADCAST_IP4 = INADDR_BROADCAST; const uint32_t TCS_ADDRESS_NONE_IP4 = INADDR_NONE; +const struct TcsIp6Address TCS_ADDRESS_ANY_IP6 = {{0}}; +const struct TcsIp6Address TCS_ADDRESS_LOOPBACK_IP6 = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}; + // Type const int TCS_SOCK_STREAM = SOCK_STREAM; const int TCS_SOCK_DGRAM = SOCK_DGRAM; @@ -2848,7 +2874,14 @@ static TcsResult sockaddr2native(const struct TcsAddress* tcs_address, } else if (tcs_address->family == TCS_AF_IP6) { - return TCS_ERROR_NOT_IMPLEMENTED; + struct sockaddr_in6* addr = (struct sockaddr_in6*)out_address; + addr->sin6_family = (sa_family_t)AF_INET6; + addr->sin6_port = (in_port_t)htons(tcs_address->data.ip6.port); + memcpy(&addr->sin6_addr, tcs_address->data.ip6.address.bytes, 16); + addr->sin6_scope_id = (uint32_t)tcs_address->data.ip6.scope_id; + if (out_address_size != NULL) + *out_address_size = sizeof(struct sockaddr_in6); + return TCS_SUCCESS; } else if (tcs_address->family == TCS_AF_PACKET) { @@ -2911,7 +2944,11 @@ static TcsResult native2sockaddr(const struct sockaddr* in_addr, struct TcsAddre } else if (in_addr->sa_family == AF_INET6) { - return TCS_ERROR_NOT_IMPLEMENTED; + struct sockaddr_in6 const* addr = (struct sockaddr_in6 const*)(const void*)in_addr; + out_addr->family = TCS_AF_IP6; + out_addr->data.ip6.port = ntohs((uint16_t)addr->sin6_port); + memcpy(out_addr->data.ip6.address.bytes, &addr->sin6_addr, 16); + out_addr->data.ip6.scope_id = (TcsInterfaceId)addr->sin6_scope_id; } #if TCS_AVAILABLE_AF_PACKET else if (in_addr->sa_family == AF_PACKET) @@ -3807,7 +3844,15 @@ TcsResult tcs_opt_membership_add_to(TcsSocket socket_ctx, } else if (multicast_address->family == TCS_AF_IP6) { - return TCS_ERROR_NOT_IMPLEMENTED; + struct ipv6_mreq mreq6; + memset(&mreq6, 0, sizeof mreq6); + memcpy(&mreq6.ipv6mr_multiaddr, multicast_address->data.ip6.address.bytes, 16); + mreq6.ipv6mr_interface = (unsigned int)local_address->data.ip6.scope_id; + + TcsResult sts_opt = tcs_opt_set(socket_ctx, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6, sizeof(mreq6)); + if (sts_opt != TCS_SUCCESS) + return sts_opt; + return TCS_SUCCESS; } else if (multicast_address->family == TCS_AF_PACKET) { @@ -3900,7 +3945,15 @@ TcsResult tcs_opt_membership_drop_from(TcsSocket socket_ctx, } else if (multicast_address->family == TCS_AF_IP6) { - return TCS_ERROR_NOT_IMPLEMENTED; + struct ipv6_mreq mreq6; + memset(&mreq6, 0, sizeof mreq6); + memcpy(&mreq6.ipv6mr_multiaddr, multicast_address->data.ip6.address.bytes, 16); + mreq6.ipv6mr_interface = (unsigned int)local_address->data.ip6.scope_id; + + TcsResult sts_opt = tcs_opt_set(socket_ctx, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &mreq6, sizeof(mreq6)); + if (sts_opt != TCS_SUCCESS) + return sts_opt; + return TCS_SUCCESS; } else if (multicast_address->family == TCS_AF_PACKET) { @@ -4338,6 +4391,9 @@ const uint32_t TCS_ADDRESS_LOOPBACK_IP4 = INADDR_LOOPBACK; const uint32_t TCS_ADDRESS_BROADCAST_IP4 = INADDR_BROADCAST; const uint32_t TCS_ADDRESS_NONE_IP4 = INADDR_NONE; +const struct TcsIp6Address TCS_ADDRESS_ANY_IP6 = {{0}}; +const struct TcsIp6Address TCS_ADDRESS_LOOPBACK_IP6 = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}; + // Type const int TCS_SOCK_STREAM = SOCK_STREAM; const int TCS_SOCK_DGRAM = SOCK_DGRAM; @@ -4464,7 +4520,14 @@ static TcsResult sockaddr2native(const struct TcsAddress* in_addr, PSOCKADDR out } else if (in_addr->family == TCS_AF_IP6) { - return TCS_ERROR_NOT_IMPLEMENTED; + PSOCKADDR_IN6 addr = (PSOCKADDR_IN6)out_addr; + addr->sin6_family = (ADDRESS_FAMILY)AF_INET6; + addr->sin6_port = htons((USHORT)in_addr->data.ip6.port); + memcpy(&addr->sin6_addr, in_addr->data.ip6.address.bytes, 16); + addr->sin6_scope_id = (ULONG)in_addr->data.ip6.scope_id; + if (out_addrlen != NULL) + *out_addrlen = sizeof(SOCKADDR_IN6); + return TCS_SUCCESS; } else if (in_addr->family == TCS_AF_PACKET) { @@ -4508,7 +4571,11 @@ static TcsResult native2sockaddr(const PSOCKADDR in_addr, struct TcsAddress* out } else if (in_addr->sa_family == AF_INET6) { - return TCS_ERROR_NOT_IMPLEMENTED; + PSOCKADDR_IN6 addr = (PSOCKADDR_IN6)in_addr; + out_addr->family = TCS_AF_IP6; + out_addr->data.ip6.port = ntohs((uint16_t)addr->sin6_port); + memcpy(out_addr->data.ip6.address.bytes, &addr->sin6_addr, 16); + out_addr->data.ip6.scope_id = (TcsInterfaceId)addr->sin6_scope_id; } else if (in_addr->sa_family == AF_UNSPEC) { @@ -5509,17 +5576,25 @@ TcsResult tcs_opt_membership_add_to(TcsSocket socket_ctx, if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; - // TODO(markusl): Add ipv6 support - if (multicast_address->family != TCS_AF_IP4) - return TCS_ERROR_NOT_IMPLEMENTED; - - struct ip_mreq imr; - memset(&imr, 0, sizeof imr); - imr.imr_multiaddr.s_addr = htonl(multicast_address->data.ip4.address); - if (local_address != NULL) - imr.imr_interface.s_addr = htonl(local_address->data.ip4.address); - - return tcs_opt_set(socket_ctx, TCS_SOL_IP, TCS_SO_IP_MEMBERSHIP_ADD, &imr, sizeof(imr)); + if (multicast_address->family == TCS_AF_IP4) + { + struct ip_mreq imr; + memset(&imr, 0, sizeof imr); + imr.imr_multiaddr.s_addr = htonl(multicast_address->data.ip4.address); + if (local_address != NULL) + imr.imr_interface.s_addr = htonl(local_address->data.ip4.address); + return tcs_opt_set(socket_ctx, TCS_SOL_IP, TCS_SO_IP_MEMBERSHIP_ADD, &imr, sizeof(imr)); + } + else if (multicast_address->family == TCS_AF_IP6) + { + struct ipv6_mreq imr6; + memset(&imr6, 0, sizeof imr6); + memcpy(&imr6.ipv6mr_multiaddr, multicast_address->data.ip6.address.bytes, 16); + if (local_address != NULL) + imr6.ipv6mr_interface = (unsigned long)local_address->data.ip6.scope_id; + return tcs_opt_set(socket_ctx, IPPROTO_IPV6, IPV6_JOIN_GROUP, &imr6, sizeof(imr6)); + } + return TCS_ERROR_NOT_IMPLEMENTED; } TcsResult tcs_opt_membership_drop(TcsSocket socket_ctx, const struct TcsAddress* multicast_address) @@ -5556,17 +5631,25 @@ TcsResult tcs_opt_membership_drop_from(TcsSocket socket_ctx, if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; - // TODO(markusl): Add ipv6 support - if (multicast_address->family != TCS_AF_IP4) - return TCS_ERROR_NOT_IMPLEMENTED; - - struct ip_mreq imr; - memset(&imr, 0, sizeof imr); - imr.imr_multiaddr.s_addr = htonl(multicast_address->data.ip4.address); - if (local_address != NULL) - imr.imr_interface.s_addr = htonl(local_address->data.ip4.address); - - return tcs_opt_set(socket_ctx, TCS_SOL_IP, TCS_SO_IP_MEMBERSHIP_DROP, &imr, sizeof(imr)); + if (multicast_address->family == TCS_AF_IP4) + { + struct ip_mreq imr; + memset(&imr, 0, sizeof imr); + imr.imr_multiaddr.s_addr = htonl(multicast_address->data.ip4.address); + if (local_address != NULL) + imr.imr_interface.s_addr = htonl(local_address->data.ip4.address); + return tcs_opt_set(socket_ctx, TCS_SOL_IP, TCS_SO_IP_MEMBERSHIP_DROP, &imr, sizeof(imr)); + } + else if (multicast_address->family == TCS_AF_IP6) + { + struct ipv6_mreq imr6; + memset(&imr6, 0, sizeof imr6); + memcpy(&imr6.ipv6mr_multiaddr, multicast_address->data.ip6.address.bytes, 16); + if (local_address != NULL) + imr6.ipv6mr_interface = (unsigned long)local_address->data.ip6.scope_id; + return tcs_opt_set(socket_ctx, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &imr6, sizeof(imr6)); + } + return TCS_ERROR_NOT_IMPLEMENTED; } // ######## Address and Interface Utilities ######## @@ -7057,7 +7140,7 @@ TcsResult tcs_address_parse(const char str[], struct TcsAddress* out_address) int n_dots = 0; int double_colons = 0; - for (int i = 0; i < 21 && str[i] != '\0'; ++i) // max ipv4 string length with port colon + for (int i = 0; str[i] != '\0' && i < 70; ++i) { if (str[i] == '.') { @@ -7077,9 +7160,6 @@ TcsResult tcs_address_parse(const char str[], struct TcsAddress* out_address) if (is_ipv4 + is_mac + is_ipv6 != 1) return TCS_ERROR_INVALID_ARGUMENT; - // AVTP Multicast address format: - // 91:E0:F0:00:FE:00 - 91:E0:F0:00:FE:FF - if (is_ipv4) { int b1; @@ -7104,7 +7184,286 @@ TcsResult tcs_address_parse(const char str[], struct TcsAddress* out_address) } else if (is_ipv6) { - return TCS_ERROR_NOT_IMPLEMENTED; + // Table-driven DFA transducer (RFC 4291 + RFC 3986 + RFC 4007 + RFC 5952) + + // Character classes (CC_OTHER = 0 so uninitialized LUT entries default to it) + enum + { + CC_OTHER = 0, + CC_DIGIT, + CC_HEX, + CC_COLON, + CC_DOT, + CC_PERCENT, + CC_LBRACKET, + CC_RBRACKET, + CC_NUL, + CC_COUNT + }; + + // clang-format off + static const uint8_t cc_lut[256] = { + /* 0x00 NUL */ + CC_NUL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + /* 0x10 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + /* 0x20 SPACE ! " # $ % & ' ( ) * + , - . / */ + 0, 0, 0, 0, 0, CC_PERCENT, 0, 0, 0, 0, 0, 0, 0, 0, CC_DOT, 0, + + /* 0x30 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */ + CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_COLON, 0, 0, 0, 0, 0, + + /* 0x40 @ A B C D E F G H I J K L M N O */ + 0, CC_HEX, CC_HEX, CC_HEX, CC_HEX, CC_HEX, CC_HEX, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + /* 0x50 P Q R S T U V W X Y Z [ \ ] ^ _ */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CC_LBRACKET,0, CC_RBRACKET,0, 0, + + /* 0x60 ` a b c d e f */ + 0, CC_HEX, CC_HEX, CC_HEX, CC_HEX, CC_HEX, CC_HEX, /* ... zeros ... */ + }; + // clang-format on + + // DFA states + enum + { + S_START, + S_BRACKET, + S_HEX, + S_HEX_DEC, + S_COLON, + S_DCOLON, + S_DOT, + S_IPV4, + S_RBRACKET, + S_PORT_COLON, + S_PORT, + S_PERCENT, + S_SCOPE, + S_ACCEPT, + S_REJECT, + S_COUNT + }; + + // Actions + enum + { + A_NONE, + A_OPEN_BRACKET, + A_HEX_ACCUM, + A_HEX_DEC_ACCUM, + A_COMMIT, + A_COMMIT_BRACKET, + A_SET_GAP, + A_CHECK_BRACKET, + A_START_IPV4, + A_IPV4_ACCUM, + A_IPV4_DOT, + A_IPV4_DONE, + A_IPV4_DONE_BRACKET, + A_PORT_ACCUM, + A_SCOPE_ACCUM, + A_SCOPE_BRACKET + }; + + // clang-format off + static const struct + { + /* StateType */ uint8_t next; + /* ActionType */ uint8_t action; + } dfa_table[S_COUNT][CC_COUNT] /* States x Characters */ = { + /* CC_OTHER CC_DIGIT CC_HEX CC_COLON CC_DOT CC_PERCENT CC_LBRACKET CC_RBRACKET CC_NUL */ + /* S_START */ {{S_REJECT,A_NONE}, {S_HEX_DEC,A_HEX_DEC_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_COLON,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_BRACKET,A_OPEN_BRACKET}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, + /* S_BRACKET */ {{S_REJECT,A_NONE}, {S_HEX_DEC,A_HEX_DEC_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_COLON,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, + /* S_HEX */ {{S_REJECT,A_NONE}, {S_HEX,A_HEX_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_COLON,A_COMMIT}, {S_REJECT,A_NONE}, {S_PERCENT,A_COMMIT}, {S_REJECT,A_NONE}, {S_RBRACKET,A_COMMIT_BRACKET}, {S_ACCEPT,A_COMMIT}}, + /* S_HEX_DEC */ {{S_REJECT,A_NONE}, {S_HEX_DEC,A_HEX_DEC_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_COLON,A_COMMIT}, {S_DOT,A_START_IPV4}, {S_PERCENT,A_COMMIT}, {S_REJECT,A_NONE}, {S_RBRACKET,A_COMMIT_BRACKET}, {S_ACCEPT,A_COMMIT}}, + /* S_COLON */ {{S_REJECT,A_NONE}, {S_HEX_DEC,A_HEX_DEC_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_DCOLON,A_SET_GAP}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, + /* S_DCOLON */ {{S_REJECT,A_NONE}, {S_HEX_DEC,A_HEX_DEC_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_RBRACKET,A_CHECK_BRACKET}, {S_ACCEPT,A_NONE}}, + /* S_DOT */ {{S_REJECT,A_NONE}, {S_IPV4,A_IPV4_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, + /* S_IPV4 */ {{S_REJECT,A_NONE}, {S_IPV4,A_IPV4_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_DOT,A_IPV4_DOT}, {S_PERCENT,A_IPV4_DONE},{S_REJECT,A_NONE}, {S_RBRACKET,A_IPV4_DONE_BRACKET},{S_ACCEPT,A_IPV4_DONE}}, + /* S_RBRACKET */ {{S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_PORT_COLON,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_ACCEPT,A_NONE}}, + /* S_PORT_COLON*/ {{S_REJECT,A_NONE}, {S_PORT,A_PORT_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, + /* S_PORT */ {{S_REJECT,A_NONE}, {S_PORT,A_PORT_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_ACCEPT,A_NONE}}, + /* S_PERCENT */ {{S_REJECT,A_NONE}, {S_SCOPE,A_SCOPE_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, + /* S_SCOPE */ {{S_REJECT,A_NONE}, {S_SCOPE,A_SCOPE_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_RBRACKET,A_SCOPE_BRACKET}, {S_ACCEPT,A_NONE}}, + /* S_ACCEPT */ {{S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, + /* S_REJECT */ {{S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, + }; + // clang-format on + + struct + { + uint16_t groups[8]; + int group_count; + int gap_pos; + int32_t hex_val; + int32_t dec_val; + int digits; + int32_t port_val; + int64_t scope_id; + int32_t ipv4_octets[4]; + int ipv4_index; + uint8_t state; + bool in_bracket; + } ctx = {{0}, 0, -1, 0, 0, 0, 0, 0, {0}, 0, S_START, false}; + + const char* c = str; + + while (ctx.state != S_ACCEPT && ctx.state != S_REJECT) + { + int cc = cc_lut[(unsigned char)*c]; + + int next = dfa_table[ctx.state][cc].next; + int action = dfa_table[ctx.state][cc].action; + + // Execute action + switch (action) + { + case A_NONE: + break; + case A_OPEN_BRACKET: + ctx.in_bracket = true; + break; + case A_HEX_ACCUM: { + int32_t d = (*c >= '0' && *c <= '9') ? (*c - '0') + : (*c >= 'a' && *c <= 'f') ? (*c - 'a' + 10) + : (*c - 'A' + 10); + ctx.hex_val = (ctx.hex_val << 4) | d; + ctx.digits++; + if (ctx.digits > 4) + return TCS_ERROR_INVALID_ARGUMENT; + break; + } + case A_HEX_DEC_ACCUM: { + int32_t d = *c - '0'; + ctx.hex_val = (ctx.hex_val << 4) | d; + ctx.dec_val = ctx.dec_val * 10 + d; + ctx.digits++; + if (ctx.digits > 4) + return TCS_ERROR_INVALID_ARGUMENT; + break; + } + case A_COMMIT: + if (ctx.group_count >= 8) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.groups[ctx.group_count++] = (uint16_t)ctx.hex_val; + ctx.hex_val = 0; + ctx.dec_val = 0; + ctx.digits = 0; + break; + case A_COMMIT_BRACKET: + if (!ctx.in_bracket) + return TCS_ERROR_INVALID_ARGUMENT; + if (ctx.group_count >= 8) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.groups[ctx.group_count++] = (uint16_t)ctx.hex_val; + ctx.hex_val = 0; + ctx.dec_val = 0; + ctx.digits = 0; + ctx.in_bracket = false; + break; + case A_SET_GAP: + if (ctx.gap_pos >= 0) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.gap_pos = ctx.group_count; + break; + case A_CHECK_BRACKET: + if (!ctx.in_bracket) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.in_bracket = false; + break; + case A_START_IPV4: + if (ctx.dec_val > 255) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.ipv4_octets[0] = ctx.dec_val; + ctx.ipv4_index = 1; + ctx.dec_val = 0; + ctx.hex_val = 0; + ctx.digits = 0; + break; + case A_IPV4_ACCUM: + ctx.dec_val = ctx.dec_val * 10 + (*c - '0'); + if (ctx.dec_val > 255) + return TCS_ERROR_INVALID_ARGUMENT; + break; + case A_IPV4_DOT: + if (ctx.ipv4_index >= 4) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.ipv4_octets[ctx.ipv4_index++] = ctx.dec_val; + ctx.dec_val = 0; + break; + case A_IPV4_DONE: + case A_IPV4_DONE_BRACKET: + if (action == A_IPV4_DONE_BRACKET) + { + if (!ctx.in_bracket) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.in_bracket = false; + } + if (ctx.ipv4_index != 3) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.ipv4_octets[3] = ctx.dec_val; + if (ctx.group_count + 2 > 8) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.groups[ctx.group_count++] = (uint16_t)(ctx.ipv4_octets[0] << 8 | ctx.ipv4_octets[1]); + ctx.groups[ctx.group_count++] = (uint16_t)(ctx.ipv4_octets[2] << 8 | ctx.ipv4_octets[3]); + break; + case A_PORT_ACCUM: + ctx.port_val = ctx.port_val * 10 + (*c - '0'); + if (ctx.port_val > UINT16_MAX) + return TCS_ERROR_INVALID_ARGUMENT; + break; + case A_SCOPE_ACCUM: + ctx.scope_id = ctx.scope_id * 10 + (*c - '0'); + if (ctx.scope_id > UINT32_MAX) + return TCS_ERROR_INVALID_ARGUMENT; + break; + case A_SCOPE_BRACKET: + if (!ctx.in_bracket) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.in_bracket = false; + break; + } + + ctx.state = next; + if (*c != '\0') + c++; + } + + if (ctx.state == S_REJECT) + return TCS_ERROR_INVALID_ARGUMENT; + + // Unclosed bracket + if (ctx.in_bracket) + return TCS_ERROR_INVALID_ARGUMENT; + + // Expand :: gap into zero groups + if (ctx.gap_pos >= 0) + { + int32_t gap_size = 8 - ctx.group_count; + if (gap_size < 1) + return TCS_ERROR_INVALID_ARGUMENT; + int32_t after_gap = ctx.group_count - ctx.gap_pos; + for (int32_t i = after_gap - 1; i >= 0; i--) + ctx.groups[ctx.gap_pos + gap_size + i] = ctx.groups[ctx.gap_pos + i]; + for (int32_t i = 0; i < gap_size; i++) + ctx.groups[ctx.gap_pos + i] = 0; + } + else if (ctx.group_count != 8) + { + return TCS_ERROR_INVALID_ARGUMENT; + } + + out_address->family = TCS_AF_IP6; + for (int i = 0; i < 8; i++) + { + out_address->data.ip6.address.bytes[i * 2] = (uint8_t)(ctx.groups[i] >> 8); + out_address->data.ip6.address.bytes[i * 2 + 1] = (uint8_t)(ctx.groups[i] & 0xFF); + } + out_address->data.ip6.port = (uint16_t)ctx.port_val; + out_address->data.ip6.scope_id = (TcsInterfaceId)ctx.scope_id; } else if (is_mac) { @@ -7164,7 +7523,64 @@ TcsResult tcs_address_to_str(const struct TcsAddress* address, char str[70]) } else if (address->family == TCS_AF_IP6) { - return TCS_ERROR_NOT_IMPLEMENTED; + uint16_t groups[8]; + for (int i = 0; i < 8; i++) + groups[i] = (uint16_t)((unsigned int)address->data.ip6.address.bytes[i * 2] << 8 | + address->data.ip6.address.bytes[i * 2 + 1]); + + // Find longest run of consecutive zero groups for :: compression (RFC 5952) + int best_start = -1; + int best_len = 0; + int cur_start = -1; + int cur_len = 0; + for (int i = 0; i < 8; i++) + { + if (groups[i] == 0) + { + if (cur_start < 0) + cur_start = i; + cur_len++; + if (cur_len > best_len) + { + best_start = cur_start; + best_len = cur_len; + } + } + else + { + cur_start = -1; + cur_len = 0; + } + } + if (best_len < 2) + best_start = -1; + + char addr_str[46]; + int pos = 0; + for (int i = 0; i < 8; i++) + { + if (i == best_start) + { + pos += snprintf(addr_str + pos, sizeof addr_str - (size_t)pos, "::"); + i += best_len - 1; + continue; + } + if (i > 0 && (best_start < 0 || i != best_start + best_len)) + addr_str[pos++] = ':'; + pos += snprintf(addr_str + pos, sizeof addr_str - (size_t)pos, "%x", (unsigned int)groups[i]); + } + addr_str[pos] = '\0'; + + uint16_t p = address->data.ip6.port; + TcsInterfaceId sc = address->data.ip6.scope_id; + if (p != 0 && sc != 0) + snprintf(str, 70, "[%s%%%u]:%u", addr_str, (unsigned int)sc, (unsigned int)p); + else if (p != 0) + snprintf(str, 70, "[%s]:%u", addr_str, (unsigned int)p); + else if (sc != 0) + snprintf(str, 70, "%s%%%u", addr_str, (unsigned int)sc); + else + snprintf(str, 70, "%s", addr_str); } else if (address->family == TCS_AF_PACKET) { @@ -7202,7 +7618,8 @@ bool tcs_address_is_equal(const struct TcsAddress* l, const struct TcsAddress* r case TCS_AF_IP4: return l->data.ip4.address == r->data.ip4.address && l->data.ip4.port == r->data.ip4.port; case TCS_AF_IP6: - return memcmp(l->data.ip6.address, r->data.ip6.address, 16) == 0 && l->data.ip6.port == r->data.ip6.port; + return memcmp(l->data.ip6.address.bytes, r->data.ip6.address.bytes, 16) == 0 && + l->data.ip6.port == r->data.ip6.port; case TCS_AF_PACKET: return memcmp(l->data.packet.mac, r->data.packet.mac, 6) == 0 && l->data.packet.protocol == r->data.packet.protocol && @@ -7222,7 +7639,7 @@ bool tcs_address_is_any(const struct TcsAddress* addr) return addr->data.ip4.address == TCS_ADDRESS_ANY_IP4; case TCS_AF_IP6: { static const uint8_t any6[16] = {0}; - return memcmp(addr->data.ip6.address, any6, 16) == 0; + return memcmp(addr->data.ip6.address.bytes, any6, 16) == 0; } default: return false; @@ -7238,7 +7655,8 @@ bool tcs_address_is_local(const struct TcsAddress* addr) case TCS_AF_IP4: return (addr->data.ip4.address >> 16) == 0xA9FE; // 169.254.0.0/16 case TCS_AF_IP6: - return addr->data.ip6.address[0] == 0xFE && (addr->data.ip6.address[1] & 0xC0) == 0x80; // fe80::/10 + return addr->data.ip6.address.bytes[0] == 0xFE && + (addr->data.ip6.address.bytes[1] & 0xC0) == 0x80; // fe80::/10 default: return false; } @@ -7253,13 +7671,14 @@ bool tcs_address_is_loopback(const struct TcsAddress* addr) case TCS_AF_IP4: return addr->data.ip4.address == TCS_ADDRESS_LOOPBACK_IP4; case TCS_AF_IP6: - return addr->data.ip6.address[0] == 0 && addr->data.ip6.address[1] == 0 && addr->data.ip6.address[2] == 0 && - addr->data.ip6.address[3] == 0 && addr->data.ip6.address[4] == 0 && addr->data.ip6.address[5] == 0 && - addr->data.ip6.address[6] == 0 && addr->data.ip6.address[7] == 0 && addr->data.ip6.address[8] == 0 && - addr->data.ip6.address[9] == 0 && addr->data.ip6.address[10] == 0 && - addr->data.ip6.address[11] == 0 && addr->data.ip6.address[12] == 0 && - addr->data.ip6.address[13] == 0 && addr->data.ip6.address[14] == 0 && - addr->data.ip6.address[15] == 1; + return addr->data.ip6.address.bytes[0] == 0 && addr->data.ip6.address.bytes[1] == 0 && + addr->data.ip6.address.bytes[2] == 0 && addr->data.ip6.address.bytes[3] == 0 && + addr->data.ip6.address.bytes[4] == 0 && addr->data.ip6.address.bytes[5] == 0 && + addr->data.ip6.address.bytes[6] == 0 && addr->data.ip6.address.bytes[7] == 0 && + addr->data.ip6.address.bytes[8] == 0 && addr->data.ip6.address.bytes[9] == 0 && + addr->data.ip6.address.bytes[10] == 0 && addr->data.ip6.address.bytes[11] == 0 && + addr->data.ip6.address.bytes[12] == 0 && addr->data.ip6.address.bytes[13] == 0 && + addr->data.ip6.address.bytes[14] == 0 && addr->data.ip6.address.bytes[15] == 1; case TCS_AF_PACKET: { static const uint8_t zero_mac[6] = {0}; return memcmp(addr->data.packet.mac, zero_mac, 6) == 0; @@ -7278,7 +7697,7 @@ bool tcs_address_is_multicast(const struct TcsAddress* addr) case TCS_AF_IP4: return (addr->data.ip4.address >> 24) >= 224 && (addr->data.ip4.address >> 24) <= 239; case TCS_AF_IP6: - return addr->data.ip6.address[0] == 0xFF; + return addr->data.ip6.address.bytes[0] == 0xFF; case TCS_AF_PACKET: return (addr->data.packet.mac[0] & 0x01) != 0; default: diff --git a/src/tinycsocket_common.c b/src/tinycsocket_common.c index 97f49c8..2907add 100644 --- a/src/tinycsocket_common.c +++ b/src/tinycsocket_common.c @@ -1050,7 +1050,7 @@ TcsResult tcs_address_parse(const char str[], struct TcsAddress* out_address) int n_dots = 0; int double_colons = 0; - for (int i = 0; i < 21 && str[i] != '\0'; ++i) // max ipv4 string length with port colon + for (int i = 0; str[i] != '\0' && i < 70; ++i) { if (str[i] == '.') { @@ -1070,9 +1070,6 @@ TcsResult tcs_address_parse(const char str[], struct TcsAddress* out_address) if (is_ipv4 + is_mac + is_ipv6 != 1) return TCS_ERROR_INVALID_ARGUMENT; - // AVTP Multicast address format: - // 91:E0:F0:00:FE:00 - 91:E0:F0:00:FE:FF - if (is_ipv4) { int b1; @@ -1097,7 +1094,286 @@ TcsResult tcs_address_parse(const char str[], struct TcsAddress* out_address) } else if (is_ipv6) { - return TCS_ERROR_NOT_IMPLEMENTED; + // Table-driven DFA transducer (RFC 4291 + RFC 3986 + RFC 4007 + RFC 5952) + + // Character classes (CC_OTHER = 0 so uninitialized LUT entries default to it) + enum + { + CC_OTHER = 0, + CC_DIGIT, + CC_HEX, + CC_COLON, + CC_DOT, + CC_PERCENT, + CC_LBRACKET, + CC_RBRACKET, + CC_NUL, + CC_COUNT + }; + + // clang-format off + static const uint8_t cc_lut[256] = { + /* 0x00 NUL */ + CC_NUL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + /* 0x10 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + /* 0x20 SPACE ! " # $ % & ' ( ) * + , - . / */ + 0, 0, 0, 0, 0, CC_PERCENT, 0, 0, 0, 0, 0, 0, 0, 0, CC_DOT, 0, + + /* 0x30 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */ + CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_COLON, 0, 0, 0, 0, 0, + + /* 0x40 @ A B C D E F G H I J K L M N O */ + 0, CC_HEX, CC_HEX, CC_HEX, CC_HEX, CC_HEX, CC_HEX, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + /* 0x50 P Q R S T U V W X Y Z [ \ ] ^ _ */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CC_LBRACKET,0, CC_RBRACKET,0, 0, + + /* 0x60 ` a b c d e f */ + 0, CC_HEX, CC_HEX, CC_HEX, CC_HEX, CC_HEX, CC_HEX, /* ... zeros ... */ + }; + // clang-format on + + // DFA states + enum + { + S_START, + S_BRACKET, + S_HEX, + S_HEX_DEC, + S_COLON, + S_DCOLON, + S_DOT, + S_IPV4, + S_RBRACKET, + S_PORT_COLON, + S_PORT, + S_PERCENT, + S_SCOPE, + S_ACCEPT, + S_REJECT, + S_COUNT + }; + + // Actions + enum + { + A_NONE, + A_OPEN_BRACKET, + A_HEX_ACCUM, + A_HEX_DEC_ACCUM, + A_COMMIT, + A_COMMIT_BRACKET, + A_SET_GAP, + A_CHECK_BRACKET, + A_START_IPV4, + A_IPV4_ACCUM, + A_IPV4_DOT, + A_IPV4_DONE, + A_IPV4_DONE_BRACKET, + A_PORT_ACCUM, + A_SCOPE_ACCUM, + A_SCOPE_BRACKET + }; + + // clang-format off + static const struct + { + /* StateType */ uint8_t next; + /* ActionType */ uint8_t action; + } dfa_table[S_COUNT][CC_COUNT] /* States x Characters */ = { + /* CC_OTHER CC_DIGIT CC_HEX CC_COLON CC_DOT CC_PERCENT CC_LBRACKET CC_RBRACKET CC_NUL */ + /* S_START */ {{S_REJECT,A_NONE}, {S_HEX_DEC,A_HEX_DEC_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_COLON,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_BRACKET,A_OPEN_BRACKET}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, + /* S_BRACKET */ {{S_REJECT,A_NONE}, {S_HEX_DEC,A_HEX_DEC_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_COLON,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, + /* S_HEX */ {{S_REJECT,A_NONE}, {S_HEX,A_HEX_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_COLON,A_COMMIT}, {S_REJECT,A_NONE}, {S_PERCENT,A_COMMIT}, {S_REJECT,A_NONE}, {S_RBRACKET,A_COMMIT_BRACKET}, {S_ACCEPT,A_COMMIT}}, + /* S_HEX_DEC */ {{S_REJECT,A_NONE}, {S_HEX_DEC,A_HEX_DEC_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_COLON,A_COMMIT}, {S_DOT,A_START_IPV4}, {S_PERCENT,A_COMMIT}, {S_REJECT,A_NONE}, {S_RBRACKET,A_COMMIT_BRACKET}, {S_ACCEPT,A_COMMIT}}, + /* S_COLON */ {{S_REJECT,A_NONE}, {S_HEX_DEC,A_HEX_DEC_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_DCOLON,A_SET_GAP}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, + /* S_DCOLON */ {{S_REJECT,A_NONE}, {S_HEX_DEC,A_HEX_DEC_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_RBRACKET,A_CHECK_BRACKET}, {S_ACCEPT,A_NONE}}, + /* S_DOT */ {{S_REJECT,A_NONE}, {S_IPV4,A_IPV4_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, + /* S_IPV4 */ {{S_REJECT,A_NONE}, {S_IPV4,A_IPV4_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_DOT,A_IPV4_DOT}, {S_PERCENT,A_IPV4_DONE},{S_REJECT,A_NONE}, {S_RBRACKET,A_IPV4_DONE_BRACKET},{S_ACCEPT,A_IPV4_DONE}}, + /* S_RBRACKET */ {{S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_PORT_COLON,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_ACCEPT,A_NONE}}, + /* S_PORT_COLON*/ {{S_REJECT,A_NONE}, {S_PORT,A_PORT_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, + /* S_PORT */ {{S_REJECT,A_NONE}, {S_PORT,A_PORT_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_ACCEPT,A_NONE}}, + /* S_PERCENT */ {{S_REJECT,A_NONE}, {S_SCOPE,A_SCOPE_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, + /* S_SCOPE */ {{S_REJECT,A_NONE}, {S_SCOPE,A_SCOPE_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_RBRACKET,A_SCOPE_BRACKET}, {S_ACCEPT,A_NONE}}, + /* S_ACCEPT */ {{S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, + /* S_REJECT */ {{S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, + }; + // clang-format on + + struct + { + uint16_t groups[8]; + int group_count; + int gap_pos; + int32_t hex_val; + int32_t dec_val; + int digits; + int32_t port_val; + int64_t scope_id; + int32_t ipv4_octets[4]; + int ipv4_index; + uint8_t state; + bool in_bracket; + } ctx = {{0}, 0, -1, 0, 0, 0, 0, 0, {0}, 0, S_START, false}; + + const char* c = str; + + while (ctx.state != S_ACCEPT && ctx.state != S_REJECT) + { + int cc = cc_lut[(unsigned char)*c]; + + int next = dfa_table[ctx.state][cc].next; + int action = dfa_table[ctx.state][cc].action; + + // Execute action + switch (action) + { + case A_NONE: + break; + case A_OPEN_BRACKET: + ctx.in_bracket = true; + break; + case A_HEX_ACCUM: { + int32_t d = (*c >= '0' && *c <= '9') ? (*c - '0') + : (*c >= 'a' && *c <= 'f') ? (*c - 'a' + 10) + : (*c - 'A' + 10); + ctx.hex_val = (ctx.hex_val << 4) | d; + ctx.digits++; + if (ctx.digits > 4) + return TCS_ERROR_INVALID_ARGUMENT; + break; + } + case A_HEX_DEC_ACCUM: { + int32_t d = *c - '0'; + ctx.hex_val = (ctx.hex_val << 4) | d; + ctx.dec_val = ctx.dec_val * 10 + d; + ctx.digits++; + if (ctx.digits > 4) + return TCS_ERROR_INVALID_ARGUMENT; + break; + } + case A_COMMIT: + if (ctx.group_count >= 8) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.groups[ctx.group_count++] = (uint16_t)ctx.hex_val; + ctx.hex_val = 0; + ctx.dec_val = 0; + ctx.digits = 0; + break; + case A_COMMIT_BRACKET: + if (!ctx.in_bracket) + return TCS_ERROR_INVALID_ARGUMENT; + if (ctx.group_count >= 8) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.groups[ctx.group_count++] = (uint16_t)ctx.hex_val; + ctx.hex_val = 0; + ctx.dec_val = 0; + ctx.digits = 0; + ctx.in_bracket = false; + break; + case A_SET_GAP: + if (ctx.gap_pos >= 0) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.gap_pos = ctx.group_count; + break; + case A_CHECK_BRACKET: + if (!ctx.in_bracket) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.in_bracket = false; + break; + case A_START_IPV4: + if (ctx.dec_val > 255) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.ipv4_octets[0] = ctx.dec_val; + ctx.ipv4_index = 1; + ctx.dec_val = 0; + ctx.hex_val = 0; + ctx.digits = 0; + break; + case A_IPV4_ACCUM: + ctx.dec_val = ctx.dec_val * 10 + (*c - '0'); + if (ctx.dec_val > 255) + return TCS_ERROR_INVALID_ARGUMENT; + break; + case A_IPV4_DOT: + if (ctx.ipv4_index >= 4) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.ipv4_octets[ctx.ipv4_index++] = ctx.dec_val; + ctx.dec_val = 0; + break; + case A_IPV4_DONE: + case A_IPV4_DONE_BRACKET: + if (action == A_IPV4_DONE_BRACKET) + { + if (!ctx.in_bracket) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.in_bracket = false; + } + if (ctx.ipv4_index != 3) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.ipv4_octets[3] = ctx.dec_val; + if (ctx.group_count + 2 > 8) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.groups[ctx.group_count++] = (uint16_t)(ctx.ipv4_octets[0] << 8 | ctx.ipv4_octets[1]); + ctx.groups[ctx.group_count++] = (uint16_t)(ctx.ipv4_octets[2] << 8 | ctx.ipv4_octets[3]); + break; + case A_PORT_ACCUM: + ctx.port_val = ctx.port_val * 10 + (*c - '0'); + if (ctx.port_val > UINT16_MAX) + return TCS_ERROR_INVALID_ARGUMENT; + break; + case A_SCOPE_ACCUM: + ctx.scope_id = ctx.scope_id * 10 + (*c - '0'); + if (ctx.scope_id > UINT32_MAX) + return TCS_ERROR_INVALID_ARGUMENT; + break; + case A_SCOPE_BRACKET: + if (!ctx.in_bracket) + return TCS_ERROR_INVALID_ARGUMENT; + ctx.in_bracket = false; + break; + } + + ctx.state = next; + if (*c != '\0') + c++; + } + + if (ctx.state == S_REJECT) + return TCS_ERROR_INVALID_ARGUMENT; + + // Unclosed bracket + if (ctx.in_bracket) + return TCS_ERROR_INVALID_ARGUMENT; + + // Expand :: gap into zero groups + if (ctx.gap_pos >= 0) + { + int32_t gap_size = 8 - ctx.group_count; + if (gap_size < 1) + return TCS_ERROR_INVALID_ARGUMENT; + int32_t after_gap = ctx.group_count - ctx.gap_pos; + for (int32_t i = after_gap - 1; i >= 0; i--) + ctx.groups[ctx.gap_pos + gap_size + i] = ctx.groups[ctx.gap_pos + i]; + for (int32_t i = 0; i < gap_size; i++) + ctx.groups[ctx.gap_pos + i] = 0; + } + else if (ctx.group_count != 8) + { + return TCS_ERROR_INVALID_ARGUMENT; + } + + out_address->family = TCS_AF_IP6; + for (int i = 0; i < 8; i++) + { + out_address->data.ip6.address.bytes[i * 2] = (uint8_t)(ctx.groups[i] >> 8); + out_address->data.ip6.address.bytes[i * 2 + 1] = (uint8_t)(ctx.groups[i] & 0xFF); + } + out_address->data.ip6.port = (uint16_t)ctx.port_val; + out_address->data.ip6.scope_id = (TcsInterfaceId)ctx.scope_id; } else if (is_mac) { @@ -1157,7 +1433,64 @@ TcsResult tcs_address_to_str(const struct TcsAddress* address, char str[70]) } else if (address->family == TCS_AF_IP6) { - return TCS_ERROR_NOT_IMPLEMENTED; + uint16_t groups[8]; + for (int i = 0; i < 8; i++) + groups[i] = (uint16_t)((unsigned int)address->data.ip6.address.bytes[i * 2] << 8 | + address->data.ip6.address.bytes[i * 2 + 1]); + + // Find longest run of consecutive zero groups for :: compression (RFC 5952) + int best_start = -1; + int best_len = 0; + int cur_start = -1; + int cur_len = 0; + for (int i = 0; i < 8; i++) + { + if (groups[i] == 0) + { + if (cur_start < 0) + cur_start = i; + cur_len++; + if (cur_len > best_len) + { + best_start = cur_start; + best_len = cur_len; + } + } + else + { + cur_start = -1; + cur_len = 0; + } + } + if (best_len < 2) + best_start = -1; + + char addr_str[46]; + int pos = 0; + for (int i = 0; i < 8; i++) + { + if (i == best_start) + { + pos += snprintf(addr_str + pos, sizeof addr_str - (size_t)pos, "::"); + i += best_len - 1; + continue; + } + if (i > 0 && (best_start < 0 || i != best_start + best_len)) + addr_str[pos++] = ':'; + pos += snprintf(addr_str + pos, sizeof addr_str - (size_t)pos, "%x", (unsigned int)groups[i]); + } + addr_str[pos] = '\0'; + + uint16_t p = address->data.ip6.port; + TcsInterfaceId sc = address->data.ip6.scope_id; + if (p != 0 && sc != 0) + snprintf(str, 70, "[%s%%%u]:%u", addr_str, (unsigned int)sc, (unsigned int)p); + else if (p != 0) + snprintf(str, 70, "[%s]:%u", addr_str, (unsigned int)p); + else if (sc != 0) + snprintf(str, 70, "%s%%%u", addr_str, (unsigned int)sc); + else + snprintf(str, 70, "%s", addr_str); } else if (address->family == TCS_AF_PACKET) { @@ -1195,7 +1528,8 @@ bool tcs_address_is_equal(const struct TcsAddress* l, const struct TcsAddress* r case TCS_AF_IP4: return l->data.ip4.address == r->data.ip4.address && l->data.ip4.port == r->data.ip4.port; case TCS_AF_IP6: - return memcmp(l->data.ip6.address, r->data.ip6.address, 16) == 0 && l->data.ip6.port == r->data.ip6.port; + return memcmp(l->data.ip6.address.bytes, r->data.ip6.address.bytes, 16) == 0 && + l->data.ip6.port == r->data.ip6.port; case TCS_AF_PACKET: return memcmp(l->data.packet.mac, r->data.packet.mac, 6) == 0 && l->data.packet.protocol == r->data.packet.protocol && @@ -1215,7 +1549,7 @@ bool tcs_address_is_any(const struct TcsAddress* addr) return addr->data.ip4.address == TCS_ADDRESS_ANY_IP4; case TCS_AF_IP6: { static const uint8_t any6[16] = {0}; - return memcmp(addr->data.ip6.address, any6, 16) == 0; + return memcmp(addr->data.ip6.address.bytes, any6, 16) == 0; } default: return false; @@ -1231,7 +1565,8 @@ bool tcs_address_is_local(const struct TcsAddress* addr) case TCS_AF_IP4: return (addr->data.ip4.address >> 16) == 0xA9FE; // 169.254.0.0/16 case TCS_AF_IP6: - return addr->data.ip6.address[0] == 0xFE && (addr->data.ip6.address[1] & 0xC0) == 0x80; // fe80::/10 + return addr->data.ip6.address.bytes[0] == 0xFE && + (addr->data.ip6.address.bytes[1] & 0xC0) == 0x80; // fe80::/10 default: return false; } @@ -1246,13 +1581,14 @@ bool tcs_address_is_loopback(const struct TcsAddress* addr) case TCS_AF_IP4: return addr->data.ip4.address == TCS_ADDRESS_LOOPBACK_IP4; case TCS_AF_IP6: - return addr->data.ip6.address[0] == 0 && addr->data.ip6.address[1] == 0 && addr->data.ip6.address[2] == 0 && - addr->data.ip6.address[3] == 0 && addr->data.ip6.address[4] == 0 && addr->data.ip6.address[5] == 0 && - addr->data.ip6.address[6] == 0 && addr->data.ip6.address[7] == 0 && addr->data.ip6.address[8] == 0 && - addr->data.ip6.address[9] == 0 && addr->data.ip6.address[10] == 0 && - addr->data.ip6.address[11] == 0 && addr->data.ip6.address[12] == 0 && - addr->data.ip6.address[13] == 0 && addr->data.ip6.address[14] == 0 && - addr->data.ip6.address[15] == 1; + return addr->data.ip6.address.bytes[0] == 0 && addr->data.ip6.address.bytes[1] == 0 && + addr->data.ip6.address.bytes[2] == 0 && addr->data.ip6.address.bytes[3] == 0 && + addr->data.ip6.address.bytes[4] == 0 && addr->data.ip6.address.bytes[5] == 0 && + addr->data.ip6.address.bytes[6] == 0 && addr->data.ip6.address.bytes[7] == 0 && + addr->data.ip6.address.bytes[8] == 0 && addr->data.ip6.address.bytes[9] == 0 && + addr->data.ip6.address.bytes[10] == 0 && addr->data.ip6.address.bytes[11] == 0 && + addr->data.ip6.address.bytes[12] == 0 && addr->data.ip6.address.bytes[13] == 0 && + addr->data.ip6.address.bytes[14] == 0 && addr->data.ip6.address.bytes[15] == 1; case TCS_AF_PACKET: { static const uint8_t zero_mac[6] = {0}; return memcmp(addr->data.packet.mac, zero_mac, 6) == 0; @@ -1271,7 +1607,7 @@ bool tcs_address_is_multicast(const struct TcsAddress* addr) case TCS_AF_IP4: return (addr->data.ip4.address >> 24) >= 224 && (addr->data.ip4.address >> 24) <= 239; case TCS_AF_IP6: - return addr->data.ip6.address[0] == 0xFF; + return addr->data.ip6.address.bytes[0] == 0xFF; case TCS_AF_PACKET: return (addr->data.packet.mac[0] & 0x01) != 0; default: diff --git a/src/tinycsocket_internal.h b/src/tinycsocket_internal.h index cb0ca86..d618279 100644 --- a/src/tinycsocket_internal.h +++ b/src/tinycsocket_internal.h @@ -27,6 +27,8 @@ #include #include +#define tcs_static_assert(name, expr) typedef char tcs_sa_##name[(expr) ? 1 : -1] + static const char* const TCS_VERSION_TXT = "v0.4-dev"; static const char* const TCS_LICENSE_TXT = "Copyright 2018 Markus Lindelöw\n" @@ -205,6 +207,14 @@ typedef enum TCS_AF_PACKET, /**< Layer 2 interface */ } TcsAddressFamily; +/** + * @brief IPv6 address (16 bytes), analogous to POSIX struct in6_addr. + */ +struct TcsIp6Address +{ + uint8_t bytes[16]; +}; + /** * @brief Network Address */ @@ -213,6 +223,7 @@ struct TcsAddress TcsAddressFamily family; union { + char _storage[24]; /**< Ensures full zero-initialization when copied from TCS_ADDRESS_NONE */ struct { uint32_t address; /**< Same byte order as the host */ @@ -221,7 +232,7 @@ struct TcsAddress } ip4; struct { - uint8_t address[16]; /**< Same byte order as the host */ + struct TcsIp6Address address; /**< Network byte order */ TcsInterfaceId scope_id; /**< Native type. Only valid for local link addresses. See ::tcs_interface_list(). */ uint16_t port; /**< Same byte order as the host */ @@ -251,12 +262,14 @@ struct TcsInterfaceAddress struct TcsAddress address; }; +tcs_static_assert(address_storage_size, sizeof(((struct TcsAddress*)0)->data) <= 24); + // gcc may trigger bug #53119 #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-braces" #endif -static const struct TcsAddress TCS_ADDRESS_NONE = {TCS_AF_ANY, {0, 0}}; +static const struct TcsAddress TCS_ADDRESS_NONE = {(TcsAddressFamily)0, {{0}}}; #ifdef __GNUC__ #pragma GCC diagnostic pop #endif @@ -266,6 +279,9 @@ extern const uint32_t TCS_ADDRESS_LOOPBACK_IP4; extern const uint32_t TCS_ADDRESS_BROADCAST_IP4; extern const uint32_t TCS_ADDRESS_NONE_IP4; +extern const struct TcsIp6Address TCS_ADDRESS_ANY_IP6; +extern const struct TcsIp6Address TCS_ADDRESS_LOOPBACK_IP6; + /** * @brief Used when sending/receiving an array of buffers. * @@ -2189,17 +2205,24 @@ TcsResult tcs_address_socket_remote(TcsSocket socket_ctx, struct TcsAddress* rem TcsResult tcs_address_socket_family(TcsSocket socket_ctx, TcsAddressFamily* out_family); /** - * @brief Get an address from a string. + * @brief Parse a network address from a string. + * + * Supports IPv4, IPv6 (RFC 4291 all three forms), MAC, and bracket/port notation (RFC 3986). + * IPv6 zone IDs are limited to numeric values per the minimum requirement of RFC 4007. + * String-based zone IDs (e.g. "%%eth0") are not supported. * - * For example: + * Examples: * - "192.168.0.1:1212" - * - "localhost:80" - * - "[::1]:443". + * - "::1" + * - "[::1]:443" + * - "fe80::1%%3" + * - "::ffff:192.168.1.1" + * - "91:E0:F0:00:FE:00" * * Note that this function will not perform DNS resolution. Use ::tcs_address_resolve() for that. * - * @param str - * @param out_address + * @param str The string to parse. + * @param out_address The parsed address. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_address_parse(const char str[], struct TcsAddress* out_address); diff --git a/src/tinycsocket_posix.c b/src/tinycsocket_posix.c index 9a40c0a..6d4fec3 100644 --- a/src/tinycsocket_posix.c +++ b/src/tinycsocket_posix.c @@ -108,6 +108,9 @@ const uint32_t TCS_ADDRESS_LOOPBACK_IP4 = INADDR_LOOPBACK; const uint32_t TCS_ADDRESS_BROADCAST_IP4 = INADDR_BROADCAST; const uint32_t TCS_ADDRESS_NONE_IP4 = INADDR_NONE; +const struct TcsIp6Address TCS_ADDRESS_ANY_IP6 = {{0}}; +const struct TcsIp6Address TCS_ADDRESS_LOOPBACK_IP6 = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}; + // Type const int TCS_SOCK_STREAM = SOCK_STREAM; const int TCS_SOCK_DGRAM = SOCK_DGRAM; @@ -251,7 +254,14 @@ static TcsResult sockaddr2native(const struct TcsAddress* tcs_address, } else if (tcs_address->family == TCS_AF_IP6) { - return TCS_ERROR_NOT_IMPLEMENTED; + struct sockaddr_in6* addr = (struct sockaddr_in6*)out_address; + addr->sin6_family = (sa_family_t)AF_INET6; + addr->sin6_port = (in_port_t)htons(tcs_address->data.ip6.port); + memcpy(&addr->sin6_addr, tcs_address->data.ip6.address.bytes, 16); + addr->sin6_scope_id = (uint32_t)tcs_address->data.ip6.scope_id; + if (out_address_size != NULL) + *out_address_size = sizeof(struct sockaddr_in6); + return TCS_SUCCESS; } else if (tcs_address->family == TCS_AF_PACKET) { @@ -314,7 +324,11 @@ static TcsResult native2sockaddr(const struct sockaddr* in_addr, struct TcsAddre } else if (in_addr->sa_family == AF_INET6) { - return TCS_ERROR_NOT_IMPLEMENTED; + struct sockaddr_in6 const* addr = (struct sockaddr_in6 const*)(const void*)in_addr; + out_addr->family = TCS_AF_IP6; + out_addr->data.ip6.port = ntohs((uint16_t)addr->sin6_port); + memcpy(out_addr->data.ip6.address.bytes, &addr->sin6_addr, 16); + out_addr->data.ip6.scope_id = (TcsInterfaceId)addr->sin6_scope_id; } #if TCS_AVAILABLE_AF_PACKET else if (in_addr->sa_family == AF_PACKET) @@ -1210,7 +1224,15 @@ TcsResult tcs_opt_membership_add_to(TcsSocket socket_ctx, } else if (multicast_address->family == TCS_AF_IP6) { - return TCS_ERROR_NOT_IMPLEMENTED; + struct ipv6_mreq mreq6; + memset(&mreq6, 0, sizeof mreq6); + memcpy(&mreq6.ipv6mr_multiaddr, multicast_address->data.ip6.address.bytes, 16); + mreq6.ipv6mr_interface = (unsigned int)local_address->data.ip6.scope_id; + + TcsResult sts_opt = tcs_opt_set(socket_ctx, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6, sizeof(mreq6)); + if (sts_opt != TCS_SUCCESS) + return sts_opt; + return TCS_SUCCESS; } else if (multicast_address->family == TCS_AF_PACKET) { @@ -1303,7 +1325,15 @@ TcsResult tcs_opt_membership_drop_from(TcsSocket socket_ctx, } else if (multicast_address->family == TCS_AF_IP6) { - return TCS_ERROR_NOT_IMPLEMENTED; + struct ipv6_mreq mreq6; + memset(&mreq6, 0, sizeof mreq6); + memcpy(&mreq6.ipv6mr_multiaddr, multicast_address->data.ip6.address.bytes, 16); + mreq6.ipv6mr_interface = (unsigned int)local_address->data.ip6.scope_id; + + TcsResult sts_opt = tcs_opt_set(socket_ctx, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &mreq6, sizeof(mreq6)); + if (sts_opt != TCS_SUCCESS) + return sts_opt; + return TCS_SUCCESS; } else if (multicast_address->family == TCS_AF_PACKET) { diff --git a/src/tinycsocket_win32.c b/src/tinycsocket_win32.c index 1a9b703..b91cba3 100644 --- a/src/tinycsocket_win32.c +++ b/src/tinycsocket_win32.c @@ -108,6 +108,9 @@ const uint32_t TCS_ADDRESS_LOOPBACK_IP4 = INADDR_LOOPBACK; const uint32_t TCS_ADDRESS_BROADCAST_IP4 = INADDR_BROADCAST; const uint32_t TCS_ADDRESS_NONE_IP4 = INADDR_NONE; +const struct TcsIp6Address TCS_ADDRESS_ANY_IP6 = {{0}}; +const struct TcsIp6Address TCS_ADDRESS_LOOPBACK_IP6 = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}; + // Type const int TCS_SOCK_STREAM = SOCK_STREAM; const int TCS_SOCK_DGRAM = SOCK_DGRAM; @@ -234,7 +237,14 @@ static TcsResult sockaddr2native(const struct TcsAddress* in_addr, PSOCKADDR out } else if (in_addr->family == TCS_AF_IP6) { - return TCS_ERROR_NOT_IMPLEMENTED; + PSOCKADDR_IN6 addr = (PSOCKADDR_IN6)out_addr; + addr->sin6_family = (ADDRESS_FAMILY)AF_INET6; + addr->sin6_port = htons((USHORT)in_addr->data.ip6.port); + memcpy(&addr->sin6_addr, in_addr->data.ip6.address.bytes, 16); + addr->sin6_scope_id = (ULONG)in_addr->data.ip6.scope_id; + if (out_addrlen != NULL) + *out_addrlen = sizeof(SOCKADDR_IN6); + return TCS_SUCCESS; } else if (in_addr->family == TCS_AF_PACKET) { @@ -278,7 +288,11 @@ static TcsResult native2sockaddr(const PSOCKADDR in_addr, struct TcsAddress* out } else if (in_addr->sa_family == AF_INET6) { - return TCS_ERROR_NOT_IMPLEMENTED; + PSOCKADDR_IN6 addr = (PSOCKADDR_IN6)in_addr; + out_addr->family = TCS_AF_IP6; + out_addr->data.ip6.port = ntohs((uint16_t)addr->sin6_port); + memcpy(out_addr->data.ip6.address.bytes, &addr->sin6_addr, 16); + out_addr->data.ip6.scope_id = (TcsInterfaceId)addr->sin6_scope_id; } else if (in_addr->sa_family == AF_UNSPEC) { @@ -1279,17 +1293,25 @@ TcsResult tcs_opt_membership_add_to(TcsSocket socket_ctx, if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; - // TODO(markusl): Add ipv6 support - if (multicast_address->family != TCS_AF_IP4) - return TCS_ERROR_NOT_IMPLEMENTED; - - struct ip_mreq imr; - memset(&imr, 0, sizeof imr); - imr.imr_multiaddr.s_addr = htonl(multicast_address->data.ip4.address); - if (local_address != NULL) - imr.imr_interface.s_addr = htonl(local_address->data.ip4.address); - - return tcs_opt_set(socket_ctx, TCS_SOL_IP, TCS_SO_IP_MEMBERSHIP_ADD, &imr, sizeof(imr)); + if (multicast_address->family == TCS_AF_IP4) + { + struct ip_mreq imr; + memset(&imr, 0, sizeof imr); + imr.imr_multiaddr.s_addr = htonl(multicast_address->data.ip4.address); + if (local_address != NULL) + imr.imr_interface.s_addr = htonl(local_address->data.ip4.address); + return tcs_opt_set(socket_ctx, TCS_SOL_IP, TCS_SO_IP_MEMBERSHIP_ADD, &imr, sizeof(imr)); + } + else if (multicast_address->family == TCS_AF_IP6) + { + struct ipv6_mreq imr6; + memset(&imr6, 0, sizeof imr6); + memcpy(&imr6.ipv6mr_multiaddr, multicast_address->data.ip6.address.bytes, 16); + if (local_address != NULL) + imr6.ipv6mr_interface = (unsigned long)local_address->data.ip6.scope_id; + return tcs_opt_set(socket_ctx, IPPROTO_IPV6, IPV6_JOIN_GROUP, &imr6, sizeof(imr6)); + } + return TCS_ERROR_NOT_IMPLEMENTED; } TcsResult tcs_opt_membership_drop(TcsSocket socket_ctx, const struct TcsAddress* multicast_address) @@ -1326,17 +1348,25 @@ TcsResult tcs_opt_membership_drop_from(TcsSocket socket_ctx, if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; - // TODO(markusl): Add ipv6 support - if (multicast_address->family != TCS_AF_IP4) - return TCS_ERROR_NOT_IMPLEMENTED; - - struct ip_mreq imr; - memset(&imr, 0, sizeof imr); - imr.imr_multiaddr.s_addr = htonl(multicast_address->data.ip4.address); - if (local_address != NULL) - imr.imr_interface.s_addr = htonl(local_address->data.ip4.address); - - return tcs_opt_set(socket_ctx, TCS_SOL_IP, TCS_SO_IP_MEMBERSHIP_DROP, &imr, sizeof(imr)); + if (multicast_address->family == TCS_AF_IP4) + { + struct ip_mreq imr; + memset(&imr, 0, sizeof imr); + imr.imr_multiaddr.s_addr = htonl(multicast_address->data.ip4.address); + if (local_address != NULL) + imr.imr_interface.s_addr = htonl(local_address->data.ip4.address); + return tcs_opt_set(socket_ctx, TCS_SOL_IP, TCS_SO_IP_MEMBERSHIP_DROP, &imr, sizeof(imr)); + } + else if (multicast_address->family == TCS_AF_IP6) + { + struct ipv6_mreq imr6; + memset(&imr6, 0, sizeof imr6); + memcpy(&imr6.ipv6mr_multiaddr, multicast_address->data.ip6.address.bytes, 16); + if (local_address != NULL) + imr6.ipv6mr_interface = (unsigned long)local_address->data.ip6.scope_id; + return tcs_opt_set(socket_ctx, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &imr6, sizeof(imr6)); + } + return TCS_ERROR_NOT_IMPLEMENTED; } // ######## Address and Interface Utilities ######## diff --git a/tests/tests.cpp b/tests/tests.cpp index 6f0a741..26cec8e 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -1933,3 +1933,405 @@ TEST_CASE("tcs_address_socket_local and remote with invalid args") CHECK(tcs_close(&socket) == TCS_SUCCESS); REQUIRE(tcs_lib_free() == TCS_SUCCESS); } + +// ######## IPv6 Tests ######## + +TEST_CASE("IPv6 socket preset TCP") +{ + REQUIRE(tcs_lib_init() == TCS_SUCCESS); + + TcsSocket socket = TCS_SOCKET_INVALID; + CHECK(tcs_socket_preset(&socket, TCS_PRESET_TCP_IP6) == TCS_SUCCESS); + CHECK(socket != TCS_SOCKET_INVALID); + + CHECK(tcs_close(&socket) == TCS_SUCCESS); + REQUIRE(tcs_lib_free() == TCS_SUCCESS); +} + +TEST_CASE("IPv6 socket preset UDP") +{ + REQUIRE(tcs_lib_init() == TCS_SUCCESS); + + TcsSocket socket = TCS_SOCKET_INVALID; + CHECK(tcs_socket_preset(&socket, TCS_PRESET_UDP_IP6) == TCS_SUCCESS); + CHECK(socket != TCS_SOCKET_INVALID); + + CHECK(tcs_close(&socket) == TCS_SUCCESS); + REQUIRE(tcs_lib_free() == TCS_SUCCESS); +} + +TEST_CASE("IPv6 Simple TCP Test") +{ + REQUIRE(tcs_lib_init() == TCS_SUCCESS); + + TcsSocket listen_socket = TCS_SOCKET_INVALID; + TcsSocket accept_socket = TCS_SOCKET_INVALID; + TcsSocket client_socket = TCS_SOCKET_INVALID; + + REQUIRE(tcs_socket_preset(&listen_socket, TCS_PRESET_TCP_IP6) == TCS_SUCCESS); + REQUIRE(tcs_socket_preset(&client_socket, TCS_PRESET_TCP_IP6) == TCS_SUCCESS); + + CHECK(tcs_opt_reuse_address_set(listen_socket, true) == TCS_SUCCESS); + struct TcsAddress local_address = TCS_ADDRESS_NONE; + local_address.family = TCS_AF_IP6; + local_address.data.ip6.address = TCS_ADDRESS_LOOPBACK_IP6; + local_address.data.ip6.port = 1214; + CHECK(tcs_bind(listen_socket, &local_address) == TCS_SUCCESS); + REQUIRE(tcs_listen(listen_socket, TCS_BACKLOG_MAX) == TCS_SUCCESS); + + struct TcsAddress connect_address = TCS_ADDRESS_NONE; + connect_address.family = TCS_AF_IP6; + connect_address.data.ip6.address = TCS_ADDRESS_LOOPBACK_IP6; + connect_address.data.ip6.port = 1214; + REQUIRE(tcs_connect(client_socket, &connect_address) == TCS_SUCCESS); + + CHECK(tcs_accept(listen_socket, &accept_socket, NULL) == TCS_SUCCESS); + CHECK(tcs_close(&listen_socket) == TCS_SUCCESS); + + uint8_t recv_buffer[8] = {0}; + const uint8_t* send_buffer = (const uint8_t*)"IPv6test"; + CHECK(tcs_send(client_socket, send_buffer, 8, TCS_MSG_SENDALL, NULL) == TCS_SUCCESS); + CHECK(tcs_receive(accept_socket, recv_buffer, 8, TCS_MSG_WAITALL, NULL) == TCS_SUCCESS); + + CHECK(memcmp(recv_buffer, send_buffer, 8) == 0); + + CHECK(tcs_close(&client_socket) == TCS_SUCCESS); + CHECK(tcs_close(&accept_socket) == TCS_SUCCESS); + REQUIRE(tcs_lib_free() == TCS_SUCCESS); +} + +TEST_CASE("IPv6 UDP Test") +{ + REQUIRE(tcs_lib_init() == TCS_SUCCESS); + + TcsSocket socket_recv = TCS_SOCKET_INVALID; + TcsSocket socket_send = TCS_SOCKET_INVALID; + CHECK(tcs_socket(&socket_recv, TCS_AF_IP6, TCS_SOCK_DGRAM, TCS_PROTOCOL_IP_UDP) == TCS_SUCCESS); + CHECK(tcs_socket(&socket_send, TCS_AF_IP6, TCS_SOCK_DGRAM, TCS_PROTOCOL_IP_UDP) == TCS_SUCCESS); + CHECK(tcs_opt_receive_timeout_set(socket_recv, 5000) == TCS_SUCCESS); + CHECK(tcs_opt_reuse_address_set(socket_recv, true) == TCS_SUCCESS); + + struct TcsAddress local_address = TCS_ADDRESS_NONE; + local_address.family = TCS_AF_IP6; + local_address.data.ip6.address = TCS_ADDRESS_ANY_IP6; + local_address.data.ip6.port = 1433; + CHECK(tcs_bind(socket_recv, &local_address) == TCS_SUCCESS); + + uint8_t msg[] = "hello ipv6\n"; + size_t sent = 0; + uint8_t recv_buffer[1024] = {0}; + size_t bytes_received = 0; + + struct TcsAddress dest = TCS_ADDRESS_NONE; + dest.family = TCS_AF_IP6; + dest.data.ip6.address = TCS_ADDRESS_LOOPBACK_IP6; + dest.data.ip6.port = 1433; + + CHECK(tcs_send_to(socket_send, msg, sizeof(msg), TCS_FLAG_NONE, &dest, &sent) == TCS_SUCCESS); + CHECK(tcs_receive(socket_recv, recv_buffer, sizeof(recv_buffer), TCS_FLAG_NONE, &bytes_received) == TCS_SUCCESS); + recv_buffer[bytes_received] = '\0'; + + CHECK(sent > 0); + CHECK(strcmp((const char*)recv_buffer, (const char*)msg) == 0); + + CHECK(tcs_close(&socket_recv) == TCS_SUCCESS); + CHECK(tcs_close(&socket_send) == TCS_SUCCESS); + REQUIRE(tcs_lib_free() == TCS_SUCCESS); +} + +TEST_CASE("IPv6 address parse loopback") +{ + TcsAddress addr; + CHECK(tcs_address_parse("::1", &addr) == TCS_SUCCESS); + CHECK(addr.family == TCS_AF_IP6); + CHECK(addr.data.ip6.port == 0); + CHECK(tcs_address_is_loopback(&addr)); +} + +TEST_CASE("IPv6 address parse all-zeros") +{ + TcsAddress addr; + CHECK(tcs_address_parse("::", &addr) == TCS_SUCCESS); + CHECK(addr.family == TCS_AF_IP6); + CHECK(tcs_address_is_any(&addr)); +} + +TEST_CASE("IPv6 address parse full form") +{ + TcsAddress addr; + // RFC 4291 Form 1: x:x:x:x:x:x:x:x + CHECK(tcs_address_parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334", &addr) == TCS_SUCCESS); + CHECK(addr.family == TCS_AF_IP6); + CHECK(addr.data.ip6.address.bytes[0] == 0x20); + CHECK(addr.data.ip6.address.bytes[1] == 0x01); + CHECK(addr.data.ip6.address.bytes[2] == 0x0d); + CHECK(addr.data.ip6.address.bytes[3] == 0xb8); + CHECK(addr.data.ip6.address.bytes[14] == 0x73); + CHECK(addr.data.ip6.address.bytes[15] == 0x34); + + CHECK(tcs_address_parse("1:2:3:4:5:6:7:8", &addr) == TCS_SUCCESS); + CHECK(addr.data.ip6.address.bytes[1] == 0x01); + CHECK(addr.data.ip6.address.bytes[15] == 0x08); + + CHECK(tcs_address_parse("0:0:0:0:0:0:0:0", &addr) == TCS_SUCCESS); + CHECK(tcs_address_is_any(&addr)); +} + +TEST_CASE("IPv6 address parse compressed") +{ + TcsAddress addr; + CHECK(tcs_address_parse("fe80::1", &addr) == TCS_SUCCESS); + CHECK(addr.family == TCS_AF_IP6); + CHECK(addr.data.ip6.address.bytes[0] == 0xFE); + CHECK(addr.data.ip6.address.bytes[1] == 0x80); + CHECK(addr.data.ip6.address.bytes[15] == 0x01); + CHECK(tcs_address_is_local(&addr)); + + CHECK(tcs_address_parse("2001:db8::1", &addr) == TCS_SUCCESS); + CHECK(addr.data.ip6.address.bytes[0] == 0x20); + CHECK(addr.data.ip6.address.bytes[1] == 0x01); + CHECK(addr.data.ip6.address.bytes[15] == 0x01); + + CHECK(tcs_address_parse("1::", &addr) == TCS_SUCCESS); + CHECK(addr.data.ip6.address.bytes[0] == 0x00); + CHECK(addr.data.ip6.address.bytes[1] == 0x01); + CHECK(addr.data.ip6.address.bytes[15] == 0x00); + + CHECK(tcs_address_parse("1::2", &addr) == TCS_SUCCESS); + CHECK(addr.data.ip6.address.bytes[1] == 0x01); + CHECK(addr.data.ip6.address.bytes[15] == 0x02); + + CHECK(tcs_address_parse("FFFF::1", &addr) == TCS_SUCCESS); + CHECK(addr.data.ip6.address.bytes[0] == 0xFF); + CHECK(addr.data.ip6.address.bytes[1] == 0xFF); +} + +TEST_CASE("IPv6 address parse mixed IPv4 notation") +{ + // RFC 4291 Form 3: x:x:x:x:x:x:d.d.d.d + TcsAddress addr; + CHECK(tcs_address_parse("::ffff:192.168.1.1", &addr) == TCS_SUCCESS); + CHECK(addr.family == TCS_AF_IP6); + CHECK(addr.data.ip6.address.bytes[10] == 0xFF); + CHECK(addr.data.ip6.address.bytes[11] == 0xFF); + CHECK(addr.data.ip6.address.bytes[12] == 192); + CHECK(addr.data.ip6.address.bytes[13] == 168); + CHECK(addr.data.ip6.address.bytes[14] == 1); + CHECK(addr.data.ip6.address.bytes[15] == 1); + + CHECK(tcs_address_parse("::13.1.68.3", &addr) == TCS_SUCCESS); + CHECK(addr.data.ip6.address.bytes[12] == 13); + CHECK(addr.data.ip6.address.bytes[13] == 1); + CHECK(addr.data.ip6.address.bytes[14] == 68); + CHECK(addr.data.ip6.address.bytes[15] == 3); + + CHECK(tcs_address_parse("0:0:0:0:0:ffff:129.144.52.38", &addr) == TCS_SUCCESS); + CHECK(addr.data.ip6.address.bytes[10] == 0xFF); + CHECK(addr.data.ip6.address.bytes[11] == 0xFF); + CHECK(addr.data.ip6.address.bytes[12] == 129); + CHECK(addr.data.ip6.address.bytes[13] == 144); + CHECK(addr.data.ip6.address.bytes[14] == 52); + CHECK(addr.data.ip6.address.bytes[15] == 38); + + CHECK(tcs_address_parse("64:ff9b::192.168.1.1", &addr) == TCS_SUCCESS); + CHECK(addr.data.ip6.address.bytes[0] == 0x00); + CHECK(addr.data.ip6.address.bytes[1] == 0x64); + CHECK(addr.data.ip6.address.bytes[2] == 0xFF); + CHECK(addr.data.ip6.address.bytes[3] == 0x9B); + CHECK(addr.data.ip6.address.bytes[12] == 192); + CHECK(addr.data.ip6.address.bytes[15] == 1); + + // Invalid mixed notation + CHECK(tcs_address_parse("::1.2.3.256", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("::1.2.3", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("::1.2.3.4.5", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("0:0:0:0:0:0:0:192.168.1.1", &addr) == TCS_ERROR_INVALID_ARGUMENT); +} + +TEST_CASE("IPv6 address parse with port") +{ + TcsAddress addr; + CHECK(tcs_address_parse("[::1]:8080", &addr) == TCS_SUCCESS); + CHECK(addr.family == TCS_AF_IP6); + CHECK(addr.data.ip6.port == 8080); + CHECK(tcs_address_is_loopback(&addr)); + + CHECK(tcs_address_parse("[::1]", &addr) == TCS_SUCCESS); + CHECK(addr.data.ip6.port == 0); + CHECK(tcs_address_is_loopback(&addr)); + + CHECK(tcs_address_parse("[fe80::1%3]:8080", &addr) == TCS_SUCCESS); + CHECK(addr.data.ip6.port == 8080); + CHECK(addr.data.ip6.scope_id == 3); + CHECK(tcs_address_is_local(&addr)); + + CHECK(tcs_address_parse("[::1]:65535", &addr) == TCS_SUCCESS); + CHECK(addr.data.ip6.port == 65535); + + CHECK(tcs_address_parse("[::1]:65536", &addr) == TCS_ERROR_INVALID_ARGUMENT); +} + +TEST_CASE("IPv6 address parse with scope id") +{ + TcsAddress addr; + CHECK(tcs_address_parse("fe80::1%3", &addr) == TCS_SUCCESS); + CHECK(addr.family == TCS_AF_IP6); + CHECK(addr.data.ip6.scope_id == 3); + CHECK(tcs_address_is_local(&addr)); +} + +TEST_CASE("IPv6 address parse invalid") +{ + TcsAddress addr; + CHECK(tcs_address_parse(":::", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("1:2:3:4:5:6:7:8:9", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("1::2::3", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("::1:2:3:4:5:6:7:8", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("1:2:3:4:5:6:7::8", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("::1:", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("fe80::1:", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("[::1]:", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("[::1]:99999", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("[::1]:8080abc", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("[::1]:999999", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("fe80::1%3abc", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("fe80::1%99999999999", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("[::1", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("[]", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("[::1]garbage", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("fe80::1%", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("10000::1", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("g::1", &addr) == TCS_ERROR_INVALID_ARGUMENT); + // RFC 3986: port = *DIGIT, strictly decimal + CHECK(tcs_address_parse("[::1]:0x50", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("[::1]:+80", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("[::1]:-1", &addr) == TCS_ERROR_INVALID_ARGUMENT); + CHECK(tcs_address_parse("[::1]: 80", &addr) == TCS_ERROR_INVALID_ARGUMENT); +} + +TEST_CASE("IPv6 address to string loopback") +{ + TcsAddress addr = TCS_ADDRESS_NONE; + addr.family = TCS_AF_IP6; + addr.data.ip6.address = TCS_ADDRESS_LOOPBACK_IP6; + char str[70]; + CHECK(tcs_address_to_str(&addr, str) == TCS_SUCCESS); + CHECK_EQ(str, "::1"); +} + +TEST_CASE("IPv6 address to string all-zeros") +{ + TcsAddress addr = TCS_ADDRESS_NONE; + addr.family = TCS_AF_IP6; + addr.data.ip6.address = TCS_ADDRESS_ANY_IP6; + char str[70]; + CHECK(tcs_address_to_str(&addr, str) == TCS_SUCCESS); + CHECK_EQ(str, "::"); +} + +TEST_CASE("IPv6 address to string with port") +{ + TcsAddress addr = TCS_ADDRESS_NONE; + addr.family = TCS_AF_IP6; + addr.data.ip6.address = TCS_ADDRESS_LOOPBACK_IP6; + addr.data.ip6.port = 8080; + char str[70]; + CHECK(tcs_address_to_str(&addr, str) == TCS_SUCCESS); + CHECK_EQ(str, "[::1]:8080"); +} + +TEST_CASE("IPv6 address to string RFC 5952 compliance") +{ + TcsAddress addr; + char str[70]; + + // Leading zeros suppressed + CHECK(tcs_address_parse("2001:0db8::0001", &addr) == TCS_SUCCESS); + CHECK(tcs_address_to_str(&addr, str) == TCS_SUCCESS); + CHECK_EQ(str, "2001:db8::1"); + + // Single zero group NOT compressed to :: + CHECK(tcs_address_parse("2001:db8:0:1:1:1:1:1", &addr) == TCS_SUCCESS); + CHECK(tcs_address_to_str(&addr, str) == TCS_SUCCESS); + CHECK_EQ(str, "2001:db8:0:1:1:1:1:1"); + + // Longest zero run compressed, first run wins on tie + CHECK(tcs_address_parse("1:0:0:2:0:0:0:3", &addr) == TCS_SUCCESS); + CHECK(tcs_address_to_str(&addr, str) == TCS_SUCCESS); + CHECK_EQ(str, "1:0:0:2::3"); + + // Equal length runs: first wins + CHECK(tcs_address_parse("1:0:0:2:3:0:0:4", &addr) == TCS_SUCCESS); + CHECK(tcs_address_to_str(&addr, str) == TCS_SUCCESS); + CHECK_EQ(str, "1::2:3:0:0:4"); + + // Lowercase hex + CHECK(tcs_address_parse("ABCD:EF01::1", &addr) == TCS_SUCCESS); + CHECK(tcs_address_to_str(&addr, str) == TCS_SUCCESS); + CHECK_EQ(str, "abcd:ef01::1"); +} + +TEST_CASE("IPv6 address roundtrip") +{ + const char* inputs[] = {"::1", "fe80::1", "2001:db8::1", "::", "ff02::1", "1:2:3:4:5:6:7:8", "1::2"}; + for (size_t i = 0; i < sizeof(inputs) / sizeof(inputs[0]); i++) + { + TcsAddress addr; + CHECK(tcs_address_parse(inputs[i], &addr) == TCS_SUCCESS); + char str[70]; + CHECK(tcs_address_to_str(&addr, str) == TCS_SUCCESS); + CHECK_EQ(str, inputs[i]); + } +} + +TEST_CASE("IPv6 address utility functions") +{ + TcsAddress loopback = TCS_ADDRESS_NONE; + loopback.family = TCS_AF_IP6; + loopback.data.ip6.address = TCS_ADDRESS_LOOPBACK_IP6; + CHECK(tcs_address_is_loopback(&loopback)); + CHECK_FALSE(tcs_address_is_any(&loopback)); + CHECK_FALSE(tcs_address_is_multicast(&loopback)); + + TcsAddress any = TCS_ADDRESS_NONE; + any.family = TCS_AF_IP6; + any.data.ip6.address = TCS_ADDRESS_ANY_IP6; + CHECK(tcs_address_is_any(&any)); + CHECK_FALSE(tcs_address_is_loopback(&any)); + + TcsAddress multicast; + tcs_address_parse("ff02::1", &multicast); + CHECK(tcs_address_is_multicast(&multicast)); + + TcsAddress link_local; + tcs_address_parse("fe80::1", &link_local); + CHECK(tcs_address_is_local(&link_local)); +} + +TEST_CASE("IPv6 address resolve loopback") +{ + REQUIRE(tcs_lib_init() == TCS_SUCCESS); + + struct TcsAddress addresses[4]; + size_t count = 0; + CHECK(tcs_address_resolve("::1", TCS_AF_IP6, addresses, 4, &count) == TCS_SUCCESS); + CHECK(count >= 1); + CHECK(addresses[0].family == TCS_AF_IP6); + CHECK(tcs_address_is_loopback(&addresses[0])); + + REQUIRE(tcs_lib_free() == TCS_SUCCESS); +} + +TEST_CASE("IPv6 address is_equal") +{ + TcsAddress a = TCS_ADDRESS_NONE; + a.family = TCS_AF_IP6; + a.data.ip6.address = TCS_ADDRESS_LOOPBACK_IP6; + TcsAddress b = TCS_ADDRESS_NONE; + b.family = TCS_AF_IP6; + b.data.ip6.address = TCS_ADDRESS_LOOPBACK_IP6; + CHECK(tcs_address_is_equal(&a, &b)); + + b.data.ip6.port = 80; + CHECK_FALSE(tcs_address_is_equal(&a, &b)); +}