From 71773a562c4f6d733b4d767e3cb6ab1b35f519e2 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Sat, 13 Aug 2022 20:32:50 +1000 Subject: [PATCH 01/15] Add IPv6 support; all backwards compatible (IPv4) tests pass --- api/IPAddress.cpp | 203 ++++++++++++++++++++++++++++++++++++++++++---- api/IPAddress.h | 53 +++++++++--- 2 files changed, 226 insertions(+), 30 deletions(-) diff --git a/api/IPAddress.cpp b/api/IPAddress.cpp index 5cf62d5e..2ed66688 100644 --- a/api/IPAddress.cpp +++ b/api/IPAddress.cpp @@ -22,30 +22,61 @@ using namespace arduino; -IPAddress::IPAddress() +IPAddress::IPAddress() : IPAddress(IPv4) {} + +IPAddress::IPAddress(IPType ip_type) { - _address.dword = 0; + _type = ip_type; + memset(_address.bytes, 0, sizeof(_address.bytes)); } IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet) { - _address.bytes[0] = first_octet; - _address.bytes[1] = second_octet; - _address.bytes[2] = third_octet; - _address.bytes[3] = fourth_octet; + _type = IPv4; + memset(_address.bytes, 0, sizeof(_address.bytes) - sizeof(uint32_t)); + _address.bytes[12] = first_octet; + _address.bytes[13] = second_octet; + _address.bytes[14] = third_octet; + _address.bytes[15] = fourth_octet; } IPAddress::IPAddress(uint32_t address) { - _address.dword = address; + // IPv4 only + _type = IPv4; + memset(_address.bytes, 0, sizeof(_address.bytes) - sizeof(uint32_t)); + _address.dword[3] = address; + + // NOTE on conversion/comparison and uint32_t: + // These conversions are host platform dependent. + // There is a defined integer representation of IPv4 addresses, + // based on network byte order (will be the value on big endian systems), + // e.g. http://2398766798 is the same as http://142.250.70.206, + // However on little endian systems the octets 0x83, 0xFA, 0x46, 0xCE, + // in that order, will form the integer (uint32_t) 3460758158 . } -IPAddress::IPAddress(const uint8_t *address) +IPAddress::IPAddress(const uint8_t *address) : IPAddress(IPv4, address) {} + +IPAddress::IPAddress(IPType ip_type, const uint8_t *address) { - memcpy(_address.bytes, address, sizeof(_address.bytes)); + _type = ip_type; + if (ip_type == IPv4) { + memset(_address.bytes, 0, sizeof(_address.bytes) - sizeof(uint32_t)); + memcpy(&_address.bytes[12], address, sizeof(uint32_t)); + } else { + memcpy(_address.bytes, address, sizeof(_address.bytes)); + } +} + +bool IPAddress::fromString(const char *address) { + if (!fromString4(address)) { + return fromString6(address); + } + return true; } -bool IPAddress::fromString(const char *address) +bool IPAddress::fromString4(const char *address) { // TODO: add support for "a", "a.b", "a.b.c" formats @@ -73,7 +104,7 @@ bool IPAddress::fromString(const char *address) /* No value between dots, e.g. '1..' */ return false; } - _address.bytes[dots++] = acc; + _address.bytes[12 + dots++] = acc; acc = -1; } else @@ -91,37 +122,175 @@ bool IPAddress::fromString(const char *address) /* No value between dots, e.g. '1..' */ return false; } - _address.bytes[3] = acc; + memset(_address.bytes, 0, sizeof(_address.bytes) - sizeof(uint32_t)); + _address.bytes[15] = acc; + _type = IPv4; + return true; +} + +bool IPAddress::fromString6(const char *address) { + uint32_t acc = 0; // Accumulator + int dots = 0, doubledots = -1; + + while (*address) + { + char c = tolower(*address++); + if (isalnum(c)) { + if (c >= 'a') + c -= 'a' - '0' - 10; + acc = acc * 16 + (c - '0'); + if (acc > 0xffff) + // Value out of range + return false; + } + else if (c == ':') { + if (*address == ':') { + if (doubledots >= 0) + // :: allowed once + return false; + // remember location + doubledots = dots + !!acc; + address++; + } + if (dots == 7) + // too many separators + return false; + _address.bytes[dots] = acc >> 2; + _address.bytes[dots + 1] = acc & 0xff; + dots++; + acc = 0; + } + else + // Invalid char + return false; + } + + if (doubledots == -1 && dots != 7) + // Too few separators + return false; + _address.bytes[dots] = acc >> 2; + _address.bytes[dots + 1] = acc & 0xff; + dots++; + + if (doubledots != -1) { + for (int i = dots * 2 - doubledots * 2 - 1; i >= 0; i--) + _address.bytes[16 - dots * 2 + doubledots * 2 + i] = _address.bytes[doubledots * 2 + i]; + for (int i = doubledots * 2; i < 16 - dots * 2 + doubledots * 2; i++) + _address.bytes[i] = 0; + } + + _type = IPv6; return true; } IPAddress& IPAddress::operator=(const uint8_t *address) { - memcpy(_address.bytes, address, sizeof(_address.bytes)); + // IPv4 only conversion from byte pointer + _type = IPv4; + memset(_address.bytes, 0, sizeof(_address.bytes) - sizeof(uint32_t)); + memcpy(&_address.bytes[12], address, sizeof(uint32_t)); return *this; } IPAddress& IPAddress::operator=(uint32_t address) { - _address.dword = address; + // IPv4 conversion + // See note on conversion/comparison and uint32_t + _type = IPv4; + _address.dword[0] = 0; + _address.dword[1] = 0; + _address.dword[2] = 0; + _address.dword[3] = address; return *this; } +bool IPAddress::operator==(const IPAddress& addr) const { + return (addr._type == _type) + && (memcmp(addr._address.bytes, _address.bytes, sizeof(_address.bytes)) == 0); +}; + bool IPAddress::operator==(const uint8_t* addr) const { - return memcmp(addr, _address.bytes, sizeof(_address.bytes)) == 0; + // IPv4 only comparison to byte pointer + // Can't support IPv6 as we know our type, but not the length of the pointer + return _type == IPv4 && memcmp(addr, &_address.bytes[12], sizeof(uint32_t)) == 0; } +uint8_t IPAddress::operator[](int index) const { + if (_type == IPv4) { + return _address.bytes[index + 12]; + } + return _address.bytes[index]; +}; + +uint8_t& IPAddress::operator[](int index) { + if (_type == IPv4) { + return _address.bytes[index + 12]; + } + return _address.bytes[index]; +}; + size_t IPAddress::printTo(Print& p) const { size_t n = 0; + + if (_type == IPv6) { + // IPv6 IETF canonical format: left-most longest run of all zero fields, lower case + int8_t longest_start = -1; + int8_t longest_length = 0; + int8_t current_start = -1; + int8_t current_length = 0; + for (int8_t f = 0; f < 8; f++) { + if (_address.bytes[f * 2] == 0 && _address.bytes[f * 2 + 1] == 0) { + if (current_start == -1) { + current_start = f; + current_length = 0; + } else { + current_length++; + } + if (current_length > longest_length) { + longest_start = current_start; + longest_length = current_length; + } + } else { + current_start = -1; + } + } + for (int f = 0; f < 8; f++) { + if (f < longest_start || f >= longest_start + longest_length) { + uint8_t c1 = _address.bytes[f * 2] >> 1; + uint8_t c2 = _address.bytes[f * 2] & 0xf; + uint8_t c3 = _address.bytes[f * 2 + 1] >> 1; + uint8_t c4 = _address.bytes[f * 2 + 1] & 0xf; + if (c1 > 0) { + n += p.print(c1 < 10 ? '0' + c1 : 'a' + c1 - 10); + } + if (c1 > 0 || c2 > 0) { + n += p.print(c2 < 10 ? '0' + c2 : 'a' + c2 - 10); + } + if (c1 > 0 || c2 > 0 || c3 > 0) { + n += p.print(c3 < 10 ? '0' + c3 : 'a' + c3 - 10); + } + n += p.print(c4 < 10 ? '0' + c4 : 'a' + c4 - 10); + if (f < 7) { + n += p.print(':'); + } + } else if (f == longest_start) { + n += p.print(':'); + } + } + return n; + } + + // IPv4 for (int i =0; i < 3; i++) { - n += p.print(_address.bytes[i], DEC); + n += p.print(_address.bytes[12 + i], DEC); n += p.print('.'); } - n += p.print(_address.bytes[3], DEC); + n += p.print(_address.bytes[15], DEC); return n; } +const IPAddress arduino::IN6ADDR_ANY(IPv6); const IPAddress arduino::INADDR_NONE(0,0,0,0); diff --git a/api/IPAddress.h b/api/IPAddress.h index d70783ca..c7c73f2d 100644 --- a/api/IPAddress.h +++ b/api/IPAddress.h @@ -32,46 +32,68 @@ namespace arduino { // A class to make it easier to handle and pass around IP addresses +enum IPType { + IPv4, + IPv6 +}; + class IPAddress : public Printable { private: union { - uint8_t bytes[4]; // IPv4 address - uint32_t dword; + uint8_t bytes[16]; + uint32_t dword[4]; } _address; + IPType _type; // Access the raw byte array containing the address. Because this returns a pointer // to the internal structure rather than a copy of the address this function should only // be used when you know that the usage of the returned uint8_t* will be transient and not // stored. - uint8_t* raw_address() { return _address.bytes; }; + // IPv4 only (for friends) + uint8_t* raw_address() { + if (_type == IPv4) { + return &_address.bytes[12]; + } + return nullptr; + }; + uint8_t* raw_bytes() { return _address.bytes; } public: // Constructors - IPAddress(); + IPAddress(); // IPv4 + IPAddress(IPType ip_type); IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet); - IPAddress(uint32_t address); - IPAddress(const uint8_t *address); + IPAddress(uint32_t address); // IPv4 only; see implementation note + IPAddress(const uint8_t *address); // IPv4 + IPAddress(IPType ip_type, const uint8_t *address); bool fromString(const char *address); bool fromString(const String &address) { return fromString(address.c_str()); } - // Overloaded cast operator to allow IPAddress objects to be used where a pointer - // to a four-byte uint8_t array is expected - operator uint32_t() const { return _address.dword; }; - bool operator==(const IPAddress& addr) const { return _address.dword == addr._address.dword; }; - bool operator!=(const IPAddress& addr) const { return _address.dword != addr._address.dword; }; + // Overloaded cast operator to allow IPAddress objects to be used where a uint32_t is expected + // IPv4 only; see implementation note + operator uint32_t() const { return _type == IPv4 ? _address.dword[3] : 0; }; + + bool operator==(const IPAddress& addr) const; + bool operator!=(const IPAddress& addr) const { return !(*this == addr); }; + + // IPv4 only; we don't know the length of the pointer bool operator==(const uint8_t* addr) const; // Overloaded index operator to allow getting and setting individual octets of the address - uint8_t operator[](int index) const { return _address.bytes[index]; }; - uint8_t& operator[](int index) { return _address.bytes[index]; }; + uint8_t operator[](int index) const; + uint8_t& operator[](int index); // Overloaded copy operators to allow initialisation of IPAddress objects from other types + // IPv4 only IPAddress& operator=(const uint8_t *address); + // IPv4 only; see implementation note IPAddress& operator=(uint32_t address); virtual size_t printTo(Print& p) const; + IPType type() { return _type; } + friend class UDP; friend class Client; friend class Server; @@ -79,8 +101,13 @@ class IPAddress : public Printable { friend ::EthernetClass; friend ::DhcpClass; friend ::DNSClient; + +protected: + bool fromString4(const char *address); + bool fromString6(const char *address); }; +extern const IPAddress IN6ADDR_ANY; extern const IPAddress INADDR_NONE; } From d7d6fe72aa9830919344b319df3dd129d22712f5 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Mon, 15 Aug 2022 00:09:52 +1000 Subject: [PATCH 02/15] Add 16 octet constructor --- api/IPAddress.h | 1 + 1 file changed, 1 insertion(+) diff --git a/api/IPAddress.h b/api/IPAddress.h index c7c73f2d..3bffafaf 100644 --- a/api/IPAddress.h +++ b/api/IPAddress.h @@ -63,6 +63,7 @@ class IPAddress : public Printable { IPAddress(); // IPv4 IPAddress(IPType ip_type); IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet); + IPAddress(uint8_t o1, uint8_t o2, uint8_t o3, uint8_t o4, uint8_t o5, uint8_t o6, uint8_t o7, uint8_t o8, uint8_t o9, uint8_t o10, uint8_t o11, uint8_t o12, uint8_t o13, uint8_t o14, uint8_t o15, uint8_t o16); IPAddress(uint32_t address); // IPv4 only; see implementation note IPAddress(const uint8_t *address); // IPv4 IPAddress(IPType ip_type, const uint8_t *address); From 5706879dd16064331ab4bbd560b750ed45fbcb67 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Mon, 15 Aug 2022 00:10:15 +1000 Subject: [PATCH 03/15] Add IPv6 tests, and fix issues --- api/IPAddress.cpp | 68 +++- test/CMakeLists.txt | 3 + test/src/IPAddress/test_IPAddress6.cpp | 85 +++++ test/src/IPAddress/test_fromString6.cpp | 395 ++++++++++++++++++++++++ test/src/IPAddress/test_printTo6.cpp | 137 ++++++++ 5 files changed, 672 insertions(+), 16 deletions(-) create mode 100644 test/src/IPAddress/test_IPAddress6.cpp create mode 100644 test/src/IPAddress/test_fromString6.cpp create mode 100644 test/src/IPAddress/test_printTo6.cpp diff --git a/api/IPAddress.cpp b/api/IPAddress.cpp index 2ed66688..ab14f85a 100644 --- a/api/IPAddress.cpp +++ b/api/IPAddress.cpp @@ -40,6 +40,26 @@ IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_oc _address.bytes[15] = fourth_octet; } +IPAddress::IPAddress(uint8_t o1, uint8_t o2, uint8_t o3, uint8_t o4, uint8_t o5, uint8_t o6, uint8_t o7, uint8_t o8, uint8_t o9, uint8_t o10, uint8_t o11, uint8_t o12, uint8_t o13, uint8_t o14, uint8_t o15, uint8_t o16) { + _type = IPv6; + _address.bytes[0] = o1; + _address.bytes[1] = o2; + _address.bytes[2] = o3; + _address.bytes[3] = o4; + _address.bytes[4] = o5; + _address.bytes[5] = o6; + _address.bytes[6] = o7; + _address.bytes[7] = o8; + _address.bytes[8] = o9; + _address.bytes[9] = o10; + _address.bytes[10] = o11; + _address.bytes[11] = o12; + _address.bytes[12] = o13; + _address.bytes[13] = o14; + _address.bytes[14] = o15; + _address.bytes[15] = o16; +} + IPAddress::IPAddress(uint32_t address) { // IPv4 only @@ -135,7 +155,7 @@ bool IPAddress::fromString6(const char *address) { while (*address) { char c = tolower(*address++); - if (isalnum(c)) { + if (isalnum(c) && c <= 'f') { if (c >= 'a') c -= 'a' - '0' - 10; acc = acc * 16 + (c - '0'); @@ -145,18 +165,26 @@ bool IPAddress::fromString6(const char *address) { } else if (c == ':') { if (*address == ':') { - if (doubledots >= 0) + if (doubledots >= 0) { // :: allowed once return false; + } + if (*address != '\0' && *(address + 1) == ':') { + // ::: not allowed + return false; + } // remember location doubledots = dots + !!acc; address++; + } else if (*address == '\0') { + // can't end with a single colon + return false; } if (dots == 7) // too many separators return false; - _address.bytes[dots] = acc >> 2; - _address.bytes[dots + 1] = acc & 0xff; + _address.bytes[dots * 2] = acc >> 8; + _address.bytes[dots * 2 + 1] = acc & 0xff; dots++; acc = 0; } @@ -165,11 +193,16 @@ bool IPAddress::fromString6(const char *address) { return false; } - if (doubledots == -1 && dots != 7) + if (doubledots == -1 && dots != 7) { // Too few separators return false; - _address.bytes[dots] = acc >> 2; - _address.bytes[dots + 1] = acc & 0xff; + } + if (doubledots > -1 && dots > 6) { + // Too many segments + return false; + } + _address.bytes[dots * 2] = acc >> 8; + _address.bytes[dots * 2 + 1] = acc & 0xff; dots++; if (doubledots != -1) { @@ -235,16 +268,16 @@ size_t IPAddress::printTo(Print& p) const size_t n = 0; if (_type == IPv6) { - // IPv6 IETF canonical format: left-most longest run of all zero fields, lower case + // IPv6 IETF canonical format: compress left-most longest run of two or more zero fields, lower case int8_t longest_start = -1; - int8_t longest_length = 0; + int8_t longest_length = 1; int8_t current_start = -1; int8_t current_length = 0; for (int8_t f = 0; f < 8; f++) { if (_address.bytes[f * 2] == 0 && _address.bytes[f * 2 + 1] == 0) { if (current_start == -1) { current_start = f; - current_length = 0; + current_length = 1; } else { current_length++; } @@ -258,24 +291,27 @@ size_t IPAddress::printTo(Print& p) const } for (int f = 0; f < 8; f++) { if (f < longest_start || f >= longest_start + longest_length) { - uint8_t c1 = _address.bytes[f * 2] >> 1; + uint8_t c1 = _address.bytes[f * 2] >> 4; uint8_t c2 = _address.bytes[f * 2] & 0xf; - uint8_t c3 = _address.bytes[f * 2 + 1] >> 1; + uint8_t c3 = _address.bytes[f * 2 + 1] >> 4; uint8_t c4 = _address.bytes[f * 2 + 1] & 0xf; if (c1 > 0) { - n += p.print(c1 < 10 ? '0' + c1 : 'a' + c1 - 10); + n += p.print((char)(c1 < 10 ? '0' + c1 : 'a' + c1 - 10)); } if (c1 > 0 || c2 > 0) { - n += p.print(c2 < 10 ? '0' + c2 : 'a' + c2 - 10); + n += p.print((char)(c2 < 10 ? '0' + c2 : 'a' + c2 - 10)); } if (c1 > 0 || c2 > 0 || c3 > 0) { - n += p.print(c3 < 10 ? '0' + c3 : 'a' + c3 - 10); + n += p.print((char)(c3 < 10 ? '0' + c3 : 'a' + c3 - 10)); } - n += p.print(c4 < 10 ? '0' + c4 : 'a' + c4 - 10); + n += p.print((char)(c4 < 10 ? '0' + c4 : 'a' + c4 - 10)); if (f < 7) { n += p.print(':'); } } else if (f == longest_start) { + if (longest_start == 0) { + n += p.print(':'); + } n += p.print(':'); } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f731b577..acf0c8cd 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -30,11 +30,14 @@ set(TEST_SRCS src/Common/test_max.cpp src/Common/test_min.cpp src/IPAddress/test_fromString.cpp + src/IPAddress/test_fromString6.cpp src/IPAddress/test_IPAddress.cpp + src/IPAddress/test_IPAddress6.cpp src/IPAddress/test_operator_assignment.cpp src/IPAddress/test_operator_comparison.cpp src/IPAddress/test_operator_parentheses.cpp src/IPAddress/test_printTo.cpp + src/IPAddress/test_printTo6.cpp src/Print/test_clearWriteError.cpp src/Print/test_getWriteError.cpp src/Print/test_print.cpp diff --git a/test/src/IPAddress/test_IPAddress6.cpp b/test/src/IPAddress/test_IPAddress6.cpp new file mode 100644 index 00000000..a6941b71 --- /dev/null +++ b/test/src/IPAddress/test_IPAddress6.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2020 Arduino. All rights reserved. + */ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include + +#include + +/************************************************************************************** + * TEST CODE + **************************************************************************************/ + +TEST_CASE ("Testing IPAddress(type) constructor()", "[IPAddress6-Ctor-01]") +{ + arduino::IPAddress ip (arduino::IPType::IPv6); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0); + REQUIRE(ip[1] == 0); + REQUIRE(ip[2] == 0); + REQUIRE(ip[3] == 0); + REQUIRE(ip[4] == 0); + REQUIRE(ip[5] == 0); + REQUIRE(ip[6] == 0); + REQUIRE(ip[7] == 0); + REQUIRE(ip[8] == 0); + REQUIRE(ip[9] == 0); + REQUIRE(ip[10] == 0); + REQUIRE(ip[11] == 0); + REQUIRE(ip[12] == 0); + REQUIRE(ip[13] == 0); + REQUIRE(ip[14] == 0); + REQUIRE(ip[15] == 0); +} + +TEST_CASE ("Testing IPAddress(o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o) constructor", "[IPAddress-Ctor6-02]") +{ + arduino::IPAddress ip(0x20,0x01, 0xd,0xb8, 1,2, 3,4, 5,6, 7,8, 9,0xa, 0xb,0xc); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0x20); + REQUIRE(ip[1] == 0x01); + REQUIRE(ip[2] == 0xd); + REQUIRE(ip[3] == 0xb8); + REQUIRE(ip[4] == 1); + REQUIRE(ip[5] == 2); + REQUIRE(ip[6] == 3); + REQUIRE(ip[7] == 4); + REQUIRE(ip[8] == 5); + REQUIRE(ip[9] == 6); + REQUIRE(ip[10] == 7); + REQUIRE(ip[11] == 8); + REQUIRE(ip[12] == 9); + REQUIRE(ip[13] == 0xa); + REQUIRE(ip[14] == 0xb); + REQUIRE(ip[15] == 0xc); +} + +TEST_CASE ("Testing IPAddress(type, a *) constructor", "[IPAddress6-Ctor-03]") +{ + uint8_t const ip_addr_array[] = {0x20,0x01, 0xd,0xb8, 1,2, 3,4, 5,6, 7,8, 9,0xa, 0xb,0xc}; + arduino::IPAddress ip(arduino::IPType::IPv6, ip_addr_array); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0x20); + REQUIRE(ip[1] == 0x01); + REQUIRE(ip[2] == 0xd); + REQUIRE(ip[3] == 0xb8); + REQUIRE(ip[4] == 1); + REQUIRE(ip[5] == 2); + REQUIRE(ip[6] == 3); + REQUIRE(ip[7] == 4); + REQUIRE(ip[8] == 5); + REQUIRE(ip[9] == 6); + REQUIRE(ip[10] == 7); + REQUIRE(ip[11] == 8); + REQUIRE(ip[12] == 9); + REQUIRE(ip[13] == 0xa); + REQUIRE(ip[14] == 0xb); + REQUIRE(ip[15] == 0xc); +} diff --git a/test/src/IPAddress/test_fromString6.cpp b/test/src/IPAddress/test_fromString6.cpp new file mode 100644 index 00000000..d2d29124 --- /dev/null +++ b/test/src/IPAddress/test_fromString6.cpp @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2020 Arduino. All rights reserved. + */ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include + +#include +#include + +/************************************************************************************** + * TEST CODE + **************************************************************************************/ + +TEST_CASE ("Extract valid IPv6 address 'fromString(const char *)'", "[IPAddress-fromString-01]") +{ + arduino::IPAddress ip; + + REQUIRE(ip.fromString("2001:db8:102:304:506:708:90a:b0c") == true); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0x20); + REQUIRE(ip[1] == 0x01); + REQUIRE(ip[2] == 0xd); + REQUIRE(ip[3] == 0xb8); + REQUIRE(ip[4] == 1); + REQUIRE(ip[5] == 2); + REQUIRE(ip[6] == 3); + REQUIRE(ip[7] == 4); + REQUIRE(ip[8] == 5); + REQUIRE(ip[9] == 6); + REQUIRE(ip[10] == 7); + REQUIRE(ip[11] == 8); + REQUIRE(ip[12] == 9); + REQUIRE(ip[13] == 0xa); + REQUIRE(ip[14] == 0xb); + REQUIRE(ip[15] == 0xc); +} + +TEST_CASE ("Extract valid IPv6 address 'fromString(const String &)'", "[IPAddress-fromString-02]") +{ + arduino::IPAddress ip; + + arduino::String const ip_addr_str("2001:db8:102:304:506:708:90a:b0c"); + + REQUIRE(ip.fromString(ip_addr_str) == true); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0x20); + REQUIRE(ip[1] == 0x01); + REQUIRE(ip[2] == 0xd); + REQUIRE(ip[3] == 0xb8); + REQUIRE(ip[4] == 1); + REQUIRE(ip[5] == 2); + REQUIRE(ip[6] == 3); + REQUIRE(ip[7] == 4); + REQUIRE(ip[8] == 5); + REQUIRE(ip[9] == 6); + REQUIRE(ip[10] == 7); + REQUIRE(ip[11] == 8); + REQUIRE(ip[12] == 9); + REQUIRE(ip[13] == 0xa); + REQUIRE(ip[14] == 0xb); + REQUIRE(ip[15] == 0xc); +} + +TEST_CASE ("Extract valid IPv6 any address", "[IPAddress-fromString-03]") +{ + arduino::IPAddress ip; + + REQUIRE(ip.fromString("::") == true); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0); + REQUIRE(ip[1] == 0); + REQUIRE(ip[2] == 0); + REQUIRE(ip[3] == 0); + REQUIRE(ip[4] == 0); + REQUIRE(ip[5] == 0); + REQUIRE(ip[6] == 0); + REQUIRE(ip[7] == 0); + REQUIRE(ip[8] == 0); + REQUIRE(ip[9] == 0); + REQUIRE(ip[10] == 0); + REQUIRE(ip[11] == 0); + REQUIRE(ip[12] == 0); + REQUIRE(ip[13] == 0); + REQUIRE(ip[14] == 0); + REQUIRE(ip[15] == 0); +} + +TEST_CASE ("Extract valid IPv6 localhost address", "[IPAddress-fromString-04]") +{ + arduino::IPAddress ip; + + REQUIRE(ip.fromString("::1") == true); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0); + REQUIRE(ip[1] == 0); + REQUIRE(ip[2] == 0); + REQUIRE(ip[3] == 0); + REQUIRE(ip[4] == 0); + REQUIRE(ip[5] == 0); + REQUIRE(ip[6] == 0); + REQUIRE(ip[7] == 0); + REQUIRE(ip[8] == 0); + REQUIRE(ip[9] == 0); + REQUIRE(ip[10] == 0); + REQUIRE(ip[11] == 0); + REQUIRE(ip[12] == 0); + REQUIRE(ip[13] == 0); + REQUIRE(ip[14] == 0); + REQUIRE(ip[15] == 1); +} + +TEST_CASE ("Extract valid IPv6 different length segments", "[IPAddress-fromString-05]") +{ + arduino::IPAddress ip; + + REQUIRE(ip.fromString("abcd:ef1:23:0:4::") == true); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0xab); + REQUIRE(ip[1] == 0xcd); + REQUIRE(ip[2] == 0xe); + REQUIRE(ip[3] == 0xf1); + REQUIRE(ip[4] == 0); + REQUIRE(ip[5] == 0x23); + REQUIRE(ip[6] == 0); + REQUIRE(ip[7] == 0); + REQUIRE(ip[8] == 0); + REQUIRE(ip[9] == 4); + REQUIRE(ip[10] == 0); + REQUIRE(ip[11] == 0); + REQUIRE(ip[12] == 0); + REQUIRE(ip[13] == 0); + REQUIRE(ip[14] == 0); + REQUIRE(ip[15] == 0); +} + + +TEST_CASE ("Extract valid IPv6 zero start", "[IPAddress-fromString-06]") +{ + arduino::IPAddress ip; + + REQUIRE(ip.fromString("0:2:3:4:5:6:7:8") == true); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0); + REQUIRE(ip[1] == 0); + REQUIRE(ip[2] == 0); + REQUIRE(ip[3] == 2); + REQUIRE(ip[4] == 0); + REQUIRE(ip[5] == 3); + REQUIRE(ip[6] == 0); + REQUIRE(ip[7] == 4); + REQUIRE(ip[8] == 0); + REQUIRE(ip[9] == 5); + REQUIRE(ip[10] == 0); + REQUIRE(ip[11] == 6); + REQUIRE(ip[12] == 0); + REQUIRE(ip[13] == 7); + REQUIRE(ip[14] == 0); + REQUIRE(ip[15] == 8); +} + + +TEST_CASE ("Extract valid IPv6 zero end", "[IPAddress-fromString-07]") +{ + arduino::IPAddress ip; + + REQUIRE(ip.fromString("1:2:3:4:5:6:7:0") == true); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0); + REQUIRE(ip[1] == 1); + REQUIRE(ip[2] == 0); + REQUIRE(ip[3] == 2); + REQUIRE(ip[4] == 0); + REQUIRE(ip[5] == 3); + REQUIRE(ip[6] == 0); + REQUIRE(ip[7] == 4); + REQUIRE(ip[8] == 0); + REQUIRE(ip[9] == 5); + REQUIRE(ip[10] == 0); + REQUIRE(ip[11] == 6); + REQUIRE(ip[12] == 0); + REQUIRE(ip[13] == 7); + REQUIRE(ip[14] == 0); + REQUIRE(ip[15] == 0); +} + + +TEST_CASE ("Extract valid IPv6 two zero start", "[IPAddress-fromString-08]") +{ + arduino::IPAddress ip; + + REQUIRE(ip.fromString("::3:4:5:6:7:0") == true); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0); + REQUIRE(ip[1] == 0); + REQUIRE(ip[2] == 0); + REQUIRE(ip[3] == 0); + REQUIRE(ip[4] == 0); + REQUIRE(ip[5] == 3); + REQUIRE(ip[6] == 0); + REQUIRE(ip[7] == 4); + REQUIRE(ip[8] == 0); + REQUIRE(ip[9] == 5); + REQUIRE(ip[10] == 0); + REQUIRE(ip[11] == 6); + REQUIRE(ip[12] == 0); + REQUIRE(ip[13] == 7); + REQUIRE(ip[14] == 0); + REQUIRE(ip[15] == 0); +} + + +TEST_CASE ("Extract valid IPv6 two zero end", "[IPAddress-fromString-9]") +{ + arduino::IPAddress ip; + + REQUIRE(ip.fromString("0:2:3:4:5:6::") == true); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0); + REQUIRE(ip[1] == 0); + REQUIRE(ip[2] == 0); + REQUIRE(ip[3] == 2); + REQUIRE(ip[4] == 0); + REQUIRE(ip[5] == 3); + REQUIRE(ip[6] == 0); + REQUIRE(ip[7] == 4); + REQUIRE(ip[8] == 0); + REQUIRE(ip[9] == 5); + REQUIRE(ip[10] == 0); + REQUIRE(ip[11] == 6); + REQUIRE(ip[12] == 0); + REQUIRE(ip[13] == 0); + REQUIRE(ip[14] == 0); + REQUIRE(ip[15] == 0); +} + +// Non-canonical + +TEST_CASE ("Extract valid IPv6 any full long form", "[IPAddress-fromString-10]") +{ + arduino::IPAddress ip; + + REQUIRE(ip.fromString("0:0:0:0:0:0:0:0") == true); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0); + REQUIRE(ip[1] == 0); + REQUIRE(ip[2] == 0); + REQUIRE(ip[3] == 0); + REQUIRE(ip[4] == 0); + REQUIRE(ip[5] == 0); + REQUIRE(ip[6] == 0); + REQUIRE(ip[7] == 0); + REQUIRE(ip[8] == 0); + REQUIRE(ip[9] == 0); + REQUIRE(ip[10] == 0); + REQUIRE(ip[11] == 0); + REQUIRE(ip[12] == 0); + REQUIRE(ip[13] == 0); + REQUIRE(ip[14] == 0); + REQUIRE(ip[15] == 0); +} + +TEST_CASE ("Extract valid IPv6 upper case", "[IPAddress-fromString-11]") +{ + arduino::IPAddress ip; + + REQUIRE(ip.fromString("2001:DB8:102:304:506:708:90A:B0C") == true); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0x20); + REQUIRE(ip[1] == 0x01); + REQUIRE(ip[2] == 0xd); + REQUIRE(ip[3] == 0xb8); + REQUIRE(ip[4] == 1); + REQUIRE(ip[5] == 2); + REQUIRE(ip[6] == 3); + REQUIRE(ip[7] == 4); + REQUIRE(ip[8] == 5); + REQUIRE(ip[9] == 6); + REQUIRE(ip[10] == 7); + REQUIRE(ip[11] == 8); + REQUIRE(ip[12] == 9); + REQUIRE(ip[13] == 0xa); + REQUIRE(ip[14] == 0xb); + REQUIRE(ip[15] == 0xc); +} + +TEST_CASE ("Extract valid IPv6 explict start zero", "[IPAddress-fromString-10]") +{ + arduino::IPAddress ip; + + REQUIRE(ip.fromString("0::") == true); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0); + REQUIRE(ip[1] == 0); + REQUIRE(ip[2] == 0); + REQUIRE(ip[3] == 0); + REQUIRE(ip[4] == 0); + REQUIRE(ip[5] == 0); + REQUIRE(ip[6] == 0); + REQUIRE(ip[7] == 0); + REQUIRE(ip[8] == 0); + REQUIRE(ip[9] == 0); + REQUIRE(ip[10] == 0); + REQUIRE(ip[11] == 0); + REQUIRE(ip[12] == 0); + REQUIRE(ip[13] == 0); + REQUIRE(ip[14] == 0); + REQUIRE(ip[15] == 0); +} + +TEST_CASE ("Extract valid IPv6 explict end zero", "[IPAddress-fromString-10]") +{ + arduino::IPAddress ip; + + REQUIRE(ip.fromString("::0") == true); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0); + REQUIRE(ip[1] == 0); + REQUIRE(ip[2] == 0); + REQUIRE(ip[3] == 0); + REQUIRE(ip[4] == 0); + REQUIRE(ip[5] == 0); + REQUIRE(ip[6] == 0); + REQUIRE(ip[7] == 0); + REQUIRE(ip[8] == 0); + REQUIRE(ip[9] == 0); + REQUIRE(ip[10] == 0); + REQUIRE(ip[11] == 0); + REQUIRE(ip[12] == 0); + REQUIRE(ip[13] == 0); + REQUIRE(ip[14] == 0); + REQUIRE(ip[15] == 0); +} + +TEST_CASE ("Extract valid IPv6 compression of one group of zero", "[IPAddress-fromString-10]") +{ + arduino::IPAddress ip; + + REQUIRE(ip.fromString("1::3:4:5:6:7:8") == true); + + REQUIRE(ip.type() == arduino::IPType::IPv6); + REQUIRE(ip[0] == 0); + REQUIRE(ip[1] == 1); + REQUIRE(ip[2] == 0); + REQUIRE(ip[3] == 0); + REQUIRE(ip[4] == 0); + REQUIRE(ip[5] == 3); + REQUIRE(ip[6] == 0); + REQUIRE(ip[7] == 4); + REQUIRE(ip[8] == 0); + REQUIRE(ip[9] == 5); + REQUIRE(ip[10] == 0); + REQUIRE(ip[11] == 6); + REQUIRE(ip[12] == 0); + REQUIRE(ip[13] == 7); + REQUIRE(ip[14] == 0); + REQUIRE(ip[15] == 8); +} + +// Invalid cases + +TEST_CASE ("Extract invalid IPv6 address", "[IPAddress-fromString-12]") +{ + arduino::IPAddress ip; + + REQUIRE(ip.fromString(":::") == false); // three colons by self + REQUIRE(ip.fromString("::3:4:5:6::") == false); // two compressions + REQUIRE(ip.fromString("2001:db8:102:10304:506:708:90a:b0c") == false); // 5 character field + REQUIRE(ip.fromString("200x:db8:102:304:506:708:90a:b0c") == false); // invalid character + REQUIRE(ip.fromString("2001:db8:102:304::506:708:90a:b0c") == false); // double colon with 8 other fields (so not a compression) + REQUIRE(ip.fromString("2001:db8:102:304:::708:90a:b0c") == false); // three colons in middle + REQUIRE(ip.fromString("2001:db8:102:304:506:708:90a:b0c:d0e") == false); // 9 fields + REQUIRE(ip.fromString("2001:db8:102:304:506:708:90a:") == false); // missing last group (but has a colon) + REQUIRE(ip.fromString("2001:db8:102:304:506:708:90a") == false); // only seven groups + REQUIRE(ip.fromString("0:0:0:0:0:0:0:0:0") == false); // nine zeros + REQUIRE(ip.fromString("0:0:0:0:0:0:0:0:") == false); // extra colon + REQUIRE(ip.fromString("0:0:0:0:0:0:0:") == false); // missing last group (but has a colon) + REQUIRE(ip.fromString("0:0:0:0:0:0:0") == false); // only seven groups +} diff --git a/test/src/IPAddress/test_printTo6.cpp b/test/src/IPAddress/test_printTo6.cpp new file mode 100644 index 00000000..621008af --- /dev/null +++ b/test/src/IPAddress/test_printTo6.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2020 Arduino. All rights reserved. + */ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include + +#include +#include + +/************************************************************************************** + * TEST CODE + **************************************************************************************/ + +TEST_CASE ("Print IPv6", "[IPAddress-printTo6-01]") +{ + PrintMock mock; + arduino::IPAddress ip(0x20,0x01, 0xd,0xb8, 1,2, 3,4, 5,6, 7,8, 9,0xa, 0xb,0xc); + + mock.print(ip); + + REQUIRE(mock._str == "2001:db8:102:304:506:708:90a:b0c"); +} + +TEST_CASE ("Print IPv6 any", "[IPAddress-printTo6-02]") +{ + PrintMock mock; + arduino::IPAddress const ip(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); + + mock.print(ip); + + REQUIRE(mock._str == "::"); +} + +TEST_CASE ("Print IPv6 localhost", "[IPAddress-printTo6-03]") +{ + PrintMock mock; + arduino::IPAddress const ip(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1); + + mock.print(ip); + + REQUIRE(mock._str == "::1"); +} + +TEST_CASE ("Print IPv6 different length segments", "[IPAddress-printTo6-04]") +{ + PrintMock mock; + arduino::IPAddress const ip(0xab,0xcd, 0x0e,0xf1, 0x00,0x23, 0,0, 0x00,0x04, 0,0, 0,0, 0,0); + + mock.print(ip); + + REQUIRE(mock._str == "abcd:ef1:23:0:4::"); +} + +TEST_CASE ("Print IPv6 zero longest run end", "[IPAddress-printTo6-05]") +{ + PrintMock mock; + arduino::IPAddress const ip(0,0, 0,1, 0,0, 0,0, 0,2, 0,0, 0,0, 0,0); + + mock.print(ip); + + REQUIRE(mock._str == "0:1:0:0:2::"); +} + +TEST_CASE ("Print IPv6 zero longest run mid", "[IPAddress-printTo6-06]") +{ + PrintMock mock; + arduino::IPAddress const ip(0,0, 0,1, 0,0, 0,0, 0,0, 0,2, 0,0, 0,0); + + mock.print(ip); + + REQUIRE(mock._str == "0:1::2:0:0"); +} + +TEST_CASE ("Print IPv6 start zero", "[IPAddress-printTo6-07]") +{ + PrintMock mock; + arduino::IPAddress const ip(0,0, 0,2, 0,3, 0,4, 0,5, 0,6, 0,7, 0,8); + + mock.print(ip); + + REQUIRE(mock._str == "0:2:3:4:5:6:7:8"); +} + +TEST_CASE ("Print IPv6 ending zero", "[IPAddress-printTo6-08]") +{ + PrintMock mock; + arduino::IPAddress const ip(0,1, 0,2, 0,3, 0,4, 0,5, 0,6, 0,7, 0,0); + + mock.print(ip); + + REQUIRE(mock._str == "1:2:3:4:5:6:7:0"); +} + +TEST_CASE ("Print IPv6 start two zero", "[IPAddress-printTo6-09]") +{ + PrintMock mock; + arduino::IPAddress const ip(0,0, 0,0, 0,3, 0,4, 0,5, 0,6, 0,7, 0,0); + + mock.print(ip); + + REQUIRE(mock._str == "::3:4:5:6:7:0"); +} + +TEST_CASE ("Print IPv6 ending two zero", "[IPAddress-printTo6-10]") +{ + PrintMock mock; + arduino::IPAddress const ip(0,0, 0,2, 0,3, 0,4, 0,5, 0,6, 0,0, 0,0); + + mock.print(ip); + + REQUIRE(mock._str == "0:2:3:4:5:6::"); +} + +TEST_CASE ("Print IPv6 first out of same length", "[IPAddress-printTo6-11]") +{ + PrintMock mock; + arduino::IPAddress const ip(0,1, 0,0, 0,0, 0,4, 0,5, 0,0, 0,0, 0,8); + + mock.print(ip); + + REQUIRE(mock._str == "1::4:5:0:0:8"); +} + + +TEST_CASE ("Print IPv6 single zeros not compressed", "[IPAddress-printTo6-12]") +{ + PrintMock mock; + arduino::IPAddress const ip(0,1, 0,0, 0,3, 0,0, 0,5, 0,0, 0,7, 0,8); + + mock.print(ip); + + REQUIRE(mock._str == "1:0:3:0:5:0:7:8"); +} From 81dfceead6ee814b5b72372c9462d2131e98ef41 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Mon, 15 Aug 2022 00:33:16 +1000 Subject: [PATCH 04/15] IPv6 raw address --- api/IPAddress.h | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/api/IPAddress.h b/api/IPAddress.h index 3bffafaf..64a287d5 100644 --- a/api/IPAddress.h +++ b/api/IPAddress.h @@ -49,14 +49,7 @@ class IPAddress : public Printable { // to the internal structure rather than a copy of the address this function should only // be used when you know that the usage of the returned uint8_t* will be transient and not // stored. - // IPv4 only (for friends) - uint8_t* raw_address() { - if (_type == IPv4) { - return &_address.bytes[12]; - } - return nullptr; - }; - uint8_t* raw_bytes() { return _address.bytes; } + uint8_t* raw_address() { return _type == IPv4 ? &_address.bytes[12] : _address.bytes; } public: // Constructors From a395f2055bc4e2ad6286a4fcd29bd2e4327b2e97 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Mon, 15 Aug 2022 00:33:59 +1000 Subject: [PATCH 05/15] IPv6 comparison tests --- api/IPAddress.h | 10 +-- test/CMakeLists.txt | 1 + .../IPAddress/test_operator_comparison6.cpp | 65 +++++++++++++++++++ 3 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 test/src/IPAddress/test_operator_comparison6.cpp diff --git a/api/IPAddress.h b/api/IPAddress.h index 64a287d5..79f05878 100644 --- a/api/IPAddress.h +++ b/api/IPAddress.h @@ -57,7 +57,7 @@ class IPAddress : public Printable { IPAddress(IPType ip_type); IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet); IPAddress(uint8_t o1, uint8_t o2, uint8_t o3, uint8_t o4, uint8_t o5, uint8_t o6, uint8_t o7, uint8_t o8, uint8_t o9, uint8_t o10, uint8_t o11, uint8_t o12, uint8_t o13, uint8_t o14, uint8_t o15, uint8_t o16); - IPAddress(uint32_t address); // IPv4 only; see implementation note + IPAddress(uint32_t address); // IPv4; see implementation note IPAddress(const uint8_t *address); // IPv4 IPAddress(IPType ip_type, const uint8_t *address); @@ -65,13 +65,13 @@ class IPAddress : public Printable { bool fromString(const String &address) { return fromString(address.c_str()); } // Overloaded cast operator to allow IPAddress objects to be used where a uint32_t is expected - // IPv4 only; see implementation note + // NOTE: IPv4 only; see implementation note operator uint32_t() const { return _type == IPv4 ? _address.dword[3] : 0; }; bool operator==(const IPAddress& addr) const; bool operator!=(const IPAddress& addr) const { return !(*this == addr); }; - // IPv4 only; we don't know the length of the pointer + // NOTE: IPv4 only; we don't know the length of the pointer bool operator==(const uint8_t* addr) const; // Overloaded index operator to allow getting and setting individual octets of the address @@ -79,9 +79,9 @@ class IPAddress : public Printable { uint8_t& operator[](int index); // Overloaded copy operators to allow initialisation of IPAddress objects from other types - // IPv4 only + // NOTE: IPv4 only IPAddress& operator=(const uint8_t *address); - // IPv4 only; see implementation note + // NOTE: IPv4 only; see implementation note IPAddress& operator=(uint32_t address); virtual size_t printTo(Print& p) const; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index acf0c8cd..e88fe130 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -35,6 +35,7 @@ set(TEST_SRCS src/IPAddress/test_IPAddress6.cpp src/IPAddress/test_operator_assignment.cpp src/IPAddress/test_operator_comparison.cpp + src/IPAddress/test_operator_comparison6.cpp src/IPAddress/test_operator_parentheses.cpp src/IPAddress/test_printTo.cpp src/IPAddress/test_printTo6.cpp diff --git a/test/src/IPAddress/test_operator_comparison6.cpp b/test/src/IPAddress/test_operator_comparison6.cpp new file mode 100644 index 00000000..36c7efbd --- /dev/null +++ b/test/src/IPAddress/test_operator_comparison6.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020 Arduino. All rights reserved. + */ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include + +#include + +/************************************************************************************** + * TEST CODE + **************************************************************************************/ + +TEST_CASE ("Testing two basic constructs the same", "[IPAddress6-Operator-==-01]") +{ + arduino::IPAddress ip1(0x20,0x01, 0xd,0xb8, 1,2, 3,4, 5,6, 7,8, 9,0xa, 0xb,0xc), ip2(0x20,0x01, 0xd,0xb8, 1,2, 3,4, 5,6, 7,8, 9,0xa, 0xb,0xc); + REQUIRE((ip1 == ip2) == true); +} + +TEST_CASE ("Testing two addresses different", "[IPAddress-Operator-==-02]") +{ + arduino::IPAddress ip1(0x20,0x01, 0xd,0xb8, 1,2, 3,4, 5,6, 7,8, 9,0xa, 0xb,0xc), ip2(0xfd,0x12, 0x34,0x56, 0x78,0x9a, 0,1, 0,0, 0,0, 0,0, 0,1); + REQUIRE((ip1 == ip2) == false); +} + +TEST_CASE ("Testing not equals different address is true", "[IPAddress-Operator-==-03]") +{ + arduino::IPAddress ip1(0x20,0x01, 0xd,0xb8, 1,2, 3,4, 5,6, 7,8, 9,0xa, 0xb,0xc), ip2(0xfd,0x12, 0x34,0x56, 0x78,0x9a, 0,1, 0,0, 0,0, 0,0, 0,1); + REQUIRE((ip1 != ip2) == true); +} + +TEST_CASE ("Testing not equals same address is false", "[IPAddress-Operator-==-04]") +{ + arduino::IPAddress ip1(0x20,0x01, 0xd,0xb8, 1,2, 3,4, 5,6, 7,8, 9,0xa, 0xb,0xc), ip2(0x20,0x01, 0xd,0xb8, 1,2, 3,4, 5,6, 7,8, 9,0xa, 0xb,0xc); + REQUIRE((ip1 != ip2) == false); +} + +// IPv4 and IPv6 differ based on type (irrespective of bytes) + +TEST_CASE ("Testing IPv4 vs IPv6", "[IPAddress6-Operator-==-05]") +{ + arduino::IPAddress ip1(10, 0, 0, 1), ip2(0x20,0x01, 0xd,0xb8, 1,2, 3,4, 5,6, 7,8, 9,0xa, 0xb,0xc); + REQUIRE((ip1 == ip2) == false); +} + +TEST_CASE ("Testing IPv4 vs IPv6 equivalent IPv4-compatible address (deprecated)", "[IPAddress6-Operator-==-05]") +{ + arduino::IPAddress ip1(10, 0, 0, 1), ip2(0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 10,0, 0,1); + REQUIRE((ip1 == ip2) == false); +} + +TEST_CASE ("Testing IPv4 vs IPv6 localhost", "[IPAddress6-Operator-==-05]") +{ + arduino::IPAddress ip1(127, 0, 0, 1), ip2(0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 10,0, 0,1); + REQUIRE((ip1 == ip2) == false); +} + +TEST_CASE ("Testing IPv4 equivalent compatible address vs IPv6 localhost", "[IPAddress6-Operator-==-05]") +{ + arduino::IPAddress ip1(0, 0, 0, 1), ip2(0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,1); + REQUIRE((ip1 == ip2) == false); +} From 7211accf5a27ff58a11618efd35153d37deb5f1f Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Mon, 15 Aug 2022 09:03:58 +1000 Subject: [PATCH 06/15] Spelling typos --- test/src/IPAddress/test_fromString6.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/src/IPAddress/test_fromString6.cpp b/test/src/IPAddress/test_fromString6.cpp index d2d29124..a0f09c73 100644 --- a/test/src/IPAddress/test_fromString6.cpp +++ b/test/src/IPAddress/test_fromString6.cpp @@ -298,7 +298,7 @@ TEST_CASE ("Extract valid IPv6 upper case", "[IPAddress-fromString-11]") REQUIRE(ip[15] == 0xc); } -TEST_CASE ("Extract valid IPv6 explict start zero", "[IPAddress-fromString-10]") +TEST_CASE ("Extract valid IPv6 explicit start zero", "[IPAddress-fromString-10]") { arduino::IPAddress ip; @@ -323,7 +323,7 @@ TEST_CASE ("Extract valid IPv6 explict start zero", "[IPAddress-fromString-10]") REQUIRE(ip[15] == 0); } -TEST_CASE ("Extract valid IPv6 explict end zero", "[IPAddress-fromString-10]") +TEST_CASE ("Extract valid IPv6 explicit end zero", "[IPAddress-fromString-10]") { arduino::IPAddress ip; From 58c8f228ad7775122bffd14ada0df30e9ba99be6 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Mon, 15 Aug 2022 09:06:50 +1000 Subject: [PATCH 07/15] Fix indend --- api/IPAddress.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/IPAddress.h b/api/IPAddress.h index 79f05878..6d669195 100644 --- a/api/IPAddress.h +++ b/api/IPAddress.h @@ -40,8 +40,8 @@ enum IPType { class IPAddress : public Printable { private: union { - uint8_t bytes[16]; - uint32_t dword[4]; + uint8_t bytes[16]; + uint32_t dword[4]; } _address; IPType _type; From ac541e8c001fe90f09aef5575b2921a0ff5078ab Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Mon, 15 Aug 2022 09:09:27 +1000 Subject: [PATCH 08/15] Move comments --- api/IPAddress.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/api/IPAddress.h b/api/IPAddress.h index 6d669195..c9e90985 100644 --- a/api/IPAddress.h +++ b/api/IPAddress.h @@ -53,12 +53,16 @@ class IPAddress : public Printable { public: // Constructors - IPAddress(); // IPv4 + + // Default IPv4 + IPAddress(); IPAddress(IPType ip_type); IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet); IPAddress(uint8_t o1, uint8_t o2, uint8_t o3, uint8_t o4, uint8_t o5, uint8_t o6, uint8_t o7, uint8_t o8, uint8_t o9, uint8_t o10, uint8_t o11, uint8_t o12, uint8_t o13, uint8_t o14, uint8_t o15, uint8_t o16); - IPAddress(uint32_t address); // IPv4; see implementation note - IPAddress(const uint8_t *address); // IPv4 + // IPv4; see implementation note + IPAddress(uint32_t address); + // Default IPv4 + IPAddress(const uint8_t *address); IPAddress(IPType ip_type, const uint8_t *address); bool fromString(const char *address); From fe80caa43b45722b9eff6adc66437e1f3f73edc3 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Mon, 15 Aug 2022 09:20:06 +1000 Subject: [PATCH 09/15] Define constants for the IPv4 index --- api/IPAddress.cpp | 44 +++++++++++++++++++++----------------------- api/IPAddress.h | 7 +++++-- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/api/IPAddress.cpp b/api/IPAddress.cpp index ab14f85a..08d55b08 100644 --- a/api/IPAddress.cpp +++ b/api/IPAddress.cpp @@ -33,11 +33,11 @@ IPAddress::IPAddress(IPType ip_type) IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet) { _type = IPv4; - memset(_address.bytes, 0, sizeof(_address.bytes) - sizeof(uint32_t)); - _address.bytes[12] = first_octet; - _address.bytes[13] = second_octet; - _address.bytes[14] = third_octet; - _address.bytes[15] = fourth_octet; + memset(_address.bytes, 0, sizeof(_address.bytes)); + _address.bytes[IPADDRESS_V4_BYTES_INDEX] = first_octet; + _address.bytes[IPADDRESS_V4_BYTES_INDEX + 1] = second_octet; + _address.bytes[IPADDRESS_V4_BYTES_INDEX + 2] = third_octet; + _address.bytes[IPADDRESS_V4_BYTES_INDEX + 3] = fourth_octet; } IPAddress::IPAddress(uint8_t o1, uint8_t o2, uint8_t o3, uint8_t o4, uint8_t o5, uint8_t o6, uint8_t o7, uint8_t o8, uint8_t o9, uint8_t o10, uint8_t o11, uint8_t o12, uint8_t o13, uint8_t o14, uint8_t o15, uint8_t o16) { @@ -64,8 +64,8 @@ IPAddress::IPAddress(uint32_t address) { // IPv4 only _type = IPv4; - memset(_address.bytes, 0, sizeof(_address.bytes) - sizeof(uint32_t)); - _address.dword[3] = address; + memset(_address.bytes, 0, sizeof(_address.bytes)); + _address.dword[IPADDRESS_V4_DWORD_INDEX] = address; // NOTE on conversion/comparison and uint32_t: // These conversions are host platform dependent. @@ -82,8 +82,8 @@ IPAddress::IPAddress(IPType ip_type, const uint8_t *address) { _type = ip_type; if (ip_type == IPv4) { - memset(_address.bytes, 0, sizeof(_address.bytes) - sizeof(uint32_t)); - memcpy(&_address.bytes[12], address, sizeof(uint32_t)); + memset(_address.bytes, 0, sizeof(_address.bytes)); + memcpy(&_address.bytes[IPADDRESS_V4_BYTES_INDEX], address, sizeof(uint32_t)); } else { memcpy(_address.bytes, address, sizeof(_address.bytes)); } @@ -103,6 +103,7 @@ bool IPAddress::fromString4(const char *address) int16_t acc = -1; // Accumulator uint8_t dots = 0; + memset(_address.bytes, 0, sizeof(_address.bytes)); while (*address) { char c = *address++; @@ -124,7 +125,7 @@ bool IPAddress::fromString4(const char *address) /* No value between dots, e.g. '1..' */ return false; } - _address.bytes[12 + dots++] = acc; + _address.bytes[IPADDRESS_V4_BYTES_INDEX + dots++] = acc; acc = -1; } else @@ -142,8 +143,7 @@ bool IPAddress::fromString4(const char *address) /* No value between dots, e.g. '1..' */ return false; } - memset(_address.bytes, 0, sizeof(_address.bytes) - sizeof(uint32_t)); - _address.bytes[15] = acc; + _address.bytes[IPADDRESS_V4_BYTES_INDEX + 3] = acc; _type = IPv4; return true; } @@ -220,8 +220,8 @@ IPAddress& IPAddress::operator=(const uint8_t *address) { // IPv4 only conversion from byte pointer _type = IPv4; - memset(_address.bytes, 0, sizeof(_address.bytes) - sizeof(uint32_t)); - memcpy(&_address.bytes[12], address, sizeof(uint32_t)); + memset(_address.bytes, 0, sizeof(_address.bytes)); + memcpy(&_address.bytes[IPADDRESS_V4_BYTES_INDEX], address, sizeof(uint32_t)); return *this; } @@ -230,10 +230,8 @@ IPAddress& IPAddress::operator=(uint32_t address) // IPv4 conversion // See note on conversion/comparison and uint32_t _type = IPv4; - _address.dword[0] = 0; - _address.dword[1] = 0; - _address.dword[2] = 0; - _address.dword[3] = address; + memset(_address.bytes, 0, sizeof(_address.bytes)); + _address.dword[IPADDRESS_V4_DWORD_INDEX] = address; return *this; } @@ -246,19 +244,19 @@ bool IPAddress::operator==(const uint8_t* addr) const { // IPv4 only comparison to byte pointer // Can't support IPv6 as we know our type, but not the length of the pointer - return _type == IPv4 && memcmp(addr, &_address.bytes[12], sizeof(uint32_t)) == 0; + return _type == IPv4 && memcmp(addr, &_address.bytes[IPADDRESS_V4_BYTES_INDEX], sizeof(uint32_t)) == 0; } uint8_t IPAddress::operator[](int index) const { if (_type == IPv4) { - return _address.bytes[index + 12]; + return _address.bytes[IPADDRESS_V4_BYTES_INDEX + index]; } return _address.bytes[index]; }; uint8_t& IPAddress::operator[](int index) { if (_type == IPv4) { - return _address.bytes[index + 12]; + return _address.bytes[IPADDRESS_V4_BYTES_INDEX + index]; } return _address.bytes[index]; }; @@ -321,10 +319,10 @@ size_t IPAddress::printTo(Print& p) const // IPv4 for (int i =0; i < 3; i++) { - n += p.print(_address.bytes[12 + i], DEC); + n += p.print(_address.bytes[IPADDRESS_V4_BYTES_INDEX + i], DEC); n += p.print('.'); } - n += p.print(_address.bytes[15], DEC); + n += p.print(_address.bytes[IPADDRESS_V4_BYTES_INDEX + 3], DEC); return n; } diff --git a/api/IPAddress.h b/api/IPAddress.h index c9e90985..03f2e8e2 100644 --- a/api/IPAddress.h +++ b/api/IPAddress.h @@ -23,6 +23,9 @@ #include "Printable.h" #include "String.h" +#define IPADDRESS_V4_BYTES_INDEX 12 +#define IPADDRESS_V4_DWORD_INDEX 3 + // forward declarations of global name space friend classes class EthernetClass; class DhcpClass; @@ -49,7 +52,7 @@ class IPAddress : public Printable { // to the internal structure rather than a copy of the address this function should only // be used when you know that the usage of the returned uint8_t* will be transient and not // stored. - uint8_t* raw_address() { return _type == IPv4 ? &_address.bytes[12] : _address.bytes; } + uint8_t* raw_address() { return _type == IPv4 ? &_address.bytes[IPADDRESS_V4_BYTES_INDEX] : _address.bytes; } public: // Constructors @@ -70,7 +73,7 @@ class IPAddress : public Printable { // Overloaded cast operator to allow IPAddress objects to be used where a uint32_t is expected // NOTE: IPv4 only; see implementation note - operator uint32_t() const { return _type == IPv4 ? _address.dword[3] : 0; }; + operator uint32_t() const { return _type == IPv4 ? _address.dword[IPADDRESS_V4_DWORD_INDEX] : 0; }; bool operator==(const IPAddress& addr) const; bool operator!=(const IPAddress& addr) const { return !(*this == addr); }; From 79cda869c889d79c677e56aab9dd40b1ea5c963a Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Mon, 15 Aug 2022 09:35:11 +1000 Subject: [PATCH 10/15] Fix build warnings / typos --- api/IPAddress.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/IPAddress.cpp b/api/IPAddress.cpp index 08d55b08..f0b13eef 100644 --- a/api/IPAddress.cpp +++ b/api/IPAddress.cpp @@ -238,7 +238,7 @@ IPAddress& IPAddress::operator=(uint32_t address) bool IPAddress::operator==(const IPAddress& addr) const { return (addr._type == _type) && (memcmp(addr._address.bytes, _address.bytes, sizeof(_address.bytes)) == 0); -}; +} bool IPAddress::operator==(const uint8_t* addr) const { @@ -252,14 +252,14 @@ uint8_t IPAddress::operator[](int index) const { return _address.bytes[IPADDRESS_V4_BYTES_INDEX + index]; } return _address.bytes[index]; -}; +} uint8_t& IPAddress::operator[](int index) { if (_type == IPv4) { return _address.bytes[IPADDRESS_V4_BYTES_INDEX + index]; } return _address.bytes[index]; -}; +} size_t IPAddress::printTo(Print& p) const { From 1f6c79b5e3946ba50b6ae4e70d524a8e1e55ce45 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Mon, 15 Aug 2022 10:38:31 +1000 Subject: [PATCH 11/15] IPv6 is colons, not dots --- api/IPAddress.cpp | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/api/IPAddress.cpp b/api/IPAddress.cpp index f0b13eef..ee941f6d 100644 --- a/api/IPAddress.cpp +++ b/api/IPAddress.cpp @@ -150,7 +150,7 @@ bool IPAddress::fromString4(const char *address) bool IPAddress::fromString6(const char *address) { uint32_t acc = 0; // Accumulator - int dots = 0, doubledots = -1; + int colons = 0, double_colons = -1; while (*address) { @@ -165,7 +165,7 @@ bool IPAddress::fromString6(const char *address) { } else if (c == ':') { if (*address == ':') { - if (doubledots >= 0) { + if (double_colons >= 0) { // :: allowed once return false; } @@ -174,18 +174,18 @@ bool IPAddress::fromString6(const char *address) { return false; } // remember location - doubledots = dots + !!acc; + double_colons = colons + !!acc; address++; } else if (*address == '\0') { // can't end with a single colon return false; } - if (dots == 7) + if (colons == 7) // too many separators return false; - _address.bytes[dots * 2] = acc >> 8; - _address.bytes[dots * 2 + 1] = acc & 0xff; - dots++; + _address.bytes[colons * 2] = acc >> 8; + _address.bytes[colons * 2 + 1] = acc & 0xff; + colons++; acc = 0; } else @@ -193,22 +193,22 @@ bool IPAddress::fromString6(const char *address) { return false; } - if (doubledots == -1 && dots != 7) { + if (double_colons == -1 && colons != 7) { // Too few separators return false; } - if (doubledots > -1 && dots > 6) { - // Too many segments + if (double_colons > -1 && colons > 6) { + // Too many segments (double colon must be at least one zero field) return false; } - _address.bytes[dots * 2] = acc >> 8; - _address.bytes[dots * 2 + 1] = acc & 0xff; - dots++; - - if (doubledots != -1) { - for (int i = dots * 2 - doubledots * 2 - 1; i >= 0; i--) - _address.bytes[16 - dots * 2 + doubledots * 2 + i] = _address.bytes[doubledots * 2 + i]; - for (int i = doubledots * 2; i < 16 - dots * 2 + doubledots * 2; i++) + _address.bytes[colons * 2] = acc >> 8; + _address.bytes[colons * 2 + 1] = acc & 0xff; + colons++; + + if (double_colons != -1) { + for (int i = colons * 2 - double_colons * 2 - 1; i >= 0; i--) + _address.bytes[16 - colons * 2 + double_colons * 2 + i] = _address.bytes[double_colons * 2 + i]; + for (int i = double_colons * 2; i < 16 - colons * 2 + double_colons * 2; i++) _address.bytes[i] = 0; } From 3f7f18bafc10cca26d186aaa939b6b7e92b6b575 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Mon, 15 Aug 2022 10:54:26 +1000 Subject: [PATCH 12/15] Increase test coverage --- test/src/IPAddress/test_operator_comparison6.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/src/IPAddress/test_operator_comparison6.cpp b/test/src/IPAddress/test_operator_comparison6.cpp index 36c7efbd..a5e1b87c 100644 --- a/test/src/IPAddress/test_operator_comparison6.cpp +++ b/test/src/IPAddress/test_operator_comparison6.cpp @@ -63,3 +63,10 @@ TEST_CASE ("Testing IPv4 equivalent compatible address vs IPv6 localhost", "[IPA arduino::IPAddress ip1(0, 0, 0, 1), ip2(0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,1); REQUIRE((ip1 == ip2) == false); } + +TEST_CASE ("Testing IPv6 never matches as raw byte sequence assumed to be length 4", "[IPAddress6-Operator-==-06]") +{ + arduino::IPAddress ip1(0x20,0x01, 0xd,0xb8, 1,2, 3,4, 5,6, 7,8, 9,0xa, 0xb,0xc); + uint8_t const ip2[] = {0x20,0x01, 0xd,0xb8, 1,2, 3,4, 5,6, 7,8, 9,0xa, 0xb,0xc}; + REQUIRE((ip1 == ip2) == false); +} From deabbb85f3a858924cc8dcf1990a7c73282f2ba4 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Mon, 15 Aug 2022 10:55:20 +1000 Subject: [PATCH 13/15] Test IPv6 comparison to int32_t (should always not match) --- .../IPAddress/test_operator_parentheses6.cpp | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 test/src/IPAddress/test_operator_parentheses6.cpp diff --git a/test/src/IPAddress/test_operator_parentheses6.cpp b/test/src/IPAddress/test_operator_parentheses6.cpp new file mode 100644 index 00000000..5b4740c8 --- /dev/null +++ b/test/src/IPAddress/test_operator_parentheses6.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Arduino. All rights reserved. + */ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include + +#include + +/************************************************************************************** + * TEST CODE + **************************************************************************************/ + +// These comparisons should always return false, as you can't compare an IPv6 to an int32_t + +TEST_CASE ("Testing implicit cast of IPv6 compatible (little endian) to uint32_t always false", "[IPAddress6-Operator-()-01]") +{ + // On little endian systems, considering only last four octets (ignoring the rest) + arduino::IPAddress ip(0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 129,168, 1,2); + uint32_t const val_expected = ip; + uint32_t const val_actual = (129 | (168 << 8) | (1 << 16) | (2 << 24)); + REQUIRE((val_expected == val_actual) == false); +} + +TEST_CASE ("Testing implicit cast of IPv6 full little endian to uint32_t always false", "[IPAddress6-Operator-()-01]") +{ + // On little endian systems (full value) + arduino::IPAddress ip(129,168, 1,2, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0); + uint32_t const val_expected = ip; + uint32_t const val_actual = (129 | (168 << 8) | (1 << 16) | (2 << 24)); + REQUIRE((val_expected == val_actual) == false); +} + +TEST_CASE ("Testing implicit cast of IPv6 to uint32_t always false", "[IPAddress6-Operator-()-01]") +{ + // Actual value of the 128-bit IPv6 address, which is network byte order + arduino::IPAddress ip(0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 129,168, 1,2); + uint32_t const val_expected = ip; + uint32_t const val_actual = ((129 << 24) | (168 << 16) | (1 << 8) | 2); + REQUIRE((val_expected == val_actual) == false); +} From 606d3a81ea5abc13697e9bedf525dee3d9a1e0a7 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Mon, 15 Aug 2022 10:55:44 +1000 Subject: [PATCH 14/15] Note on IPv4 tests to int32_t --- test/src/IPAddress/test_operator_assignment.cpp | 1 + test/src/IPAddress/test_operator_parentheses.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/test/src/IPAddress/test_operator_assignment.cpp b/test/src/IPAddress/test_operator_assignment.cpp index f7afece2..e9fc8691 100644 --- a/test/src/IPAddress/test_operator_assignment.cpp +++ b/test/src/IPAddress/test_operator_assignment.cpp @@ -29,5 +29,6 @@ TEST_CASE ("Testing IPAddress::operator = (uint32_t a)", "[IPAddress-Operator-=- uint32_t const ip2 = 192 | (168 << 8) | (1 << 16) | (2 << 24); ip1 = ip2; + // NOTE: Only correct on little-endian systems REQUIRE(ip1 == arduino::IPAddress(192,168,1,2)); } diff --git a/test/src/IPAddress/test_operator_parentheses.cpp b/test/src/IPAddress/test_operator_parentheses.cpp index e9f1c0af..27fce3a5 100644 --- a/test/src/IPAddress/test_operator_parentheses.cpp +++ b/test/src/IPAddress/test_operator_parentheses.cpp @@ -19,5 +19,6 @@ TEST_CASE ("Testing IPAddress::operator uint32_t() const", "[IPAddress-Operator- arduino::IPAddress ip(129,168,1,2); uint32_t const val_expected = ip; uint32_t const val_actual = (129 | (168 << 8) | (1 << 16) | (2 << 24)); + // NOTE: Only correct on little-endian systems REQUIRE(val_expected == val_actual); } From bcc5233a9385ed7eb9d0bc23d380e3f9a3b2b65d Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Mon, 15 Aug 2022 10:57:51 +1000 Subject: [PATCH 15/15] Actually include the tests ! --- test/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e88fe130..bd0b6821 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -37,6 +37,7 @@ set(TEST_SRCS src/IPAddress/test_operator_comparison.cpp src/IPAddress/test_operator_comparison6.cpp src/IPAddress/test_operator_parentheses.cpp + src/IPAddress/test_operator_parentheses6.cpp src/IPAddress/test_printTo.cpp src/IPAddress/test_printTo6.cpp src/Print/test_clearWriteError.cpp pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy