Skip to content

Add IPv6 support to IPAddress #169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Aug 22, 2022
237 changes: 220 additions & 17 deletions api/IPAddress.cpp
Original file line number Diff line number Diff line change
@@ -22,36 +22,88 @@

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));
_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) {
_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)
{
_address.dword = address;
// IPv4 only
_type = IPv4;
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.
// 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));
memcpy(&_address.bytes[IPADDRESS_V4_BYTES_INDEX], address, sizeof(uint32_t));
} else {
memcpy(_address.bytes, address, sizeof(_address.bytes));
}
}

bool IPAddress::fromString(const char *address)
bool IPAddress::fromString(const char *address) {
if (!fromString4(address)) {
return fromString6(address);
}
return true;
}

bool IPAddress::fromString4(const char *address)
{
// TODO: add support for "a", "a.b", "a.b.c" formats

int16_t acc = -1; // Accumulator
uint8_t dots = 0;

memset(_address.bytes, 0, sizeof(_address.bytes));
while (*address)
{
char c = *address++;
@@ -73,7 +125,7 @@ bool IPAddress::fromString(const char *address)
/* No value between dots, e.g. '1..' */
return false;
}
_address.bytes[dots++] = acc;
_address.bytes[IPADDRESS_V4_BYTES_INDEX + dots++] = acc;
acc = -1;
}
else
@@ -91,37 +143,188 @@ bool IPAddress::fromString(const char *address)
/* No value between dots, e.g. '1..' */
return false;
}
_address.bytes[3] = acc;
_address.bytes[IPADDRESS_V4_BYTES_INDEX + 3] = acc;
_type = IPv4;
return true;
}

bool IPAddress::fromString6(const char *address) {
uint32_t acc = 0; // Accumulator
int colons = 0, double_colons = -1;

while (*address)
{
char c = tolower(*address++);
if (isalnum(c) && c <= 'f') {
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 (double_colons >= 0) {
// :: allowed once
return false;
}
if (*address != '\0' && *(address + 1) == ':') {
// ::: not allowed
return false;
}
// remember location
double_colons = colons + !!acc;
address++;
} else if (*address == '\0') {
// can't end with a single colon
return false;
}
if (colons == 7)
// too many separators
return false;
_address.bytes[colons * 2] = acc >> 8;
_address.bytes[colons * 2 + 1] = acc & 0xff;
colons++;
acc = 0;
}
else
// Invalid char
return false;
}

if (double_colons == -1 && colons != 7) {
// Too few separators
return false;
}
if (double_colons > -1 && colons > 6) {
// Too many segments (double colon must be at least one zero field)
return false;
}
_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;
}

_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));
memcpy(&_address.bytes[IPADDRESS_V4_BYTES_INDEX], 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;
memset(_address.bytes, 0, sizeof(_address.bytes));
_address.dword[IPADDRESS_V4_DWORD_INDEX] = 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[IPADDRESS_V4_BYTES_INDEX], sizeof(uint32_t)) == 0;
}

uint8_t IPAddress::operator[](int index) const {
if (_type == IPv4) {
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
{
size_t n = 0;

if (_type == IPv6) {
// 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 = 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 = 1;
} 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] >> 4;
uint8_t c2 = _address.bytes[f * 2] & 0xf;
uint8_t c3 = _address.bytes[f * 2 + 1] >> 4;
uint8_t c4 = _address.bytes[f * 2 + 1] & 0xf;
if (c1 > 0) {
n += p.print((char)(c1 < 10 ? '0' + c1 : 'a' + c1 - 10));
}
if (c1 > 0 || c2 > 0) {
n += p.print((char)(c2 < 10 ? '0' + c2 : 'a' + c2 - 10));
}
if (c1 > 0 || c2 > 0 || c3 > 0) {
n += p.print((char)(c3 < 10 ? '0' + c3 : 'a' + c3 - 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(':');
}
}
return n;
}

// IPv4
for (int i =0; i < 3; i++)
{
n += p.print(_address.bytes[i], DEC);
n += p.print(_address.bytes[IPADDRESS_V4_BYTES_INDEX + i], DEC);
n += p.print('.');
}
n += p.print(_address.bytes[3], DEC);
n += p.print(_address.bytes[IPADDRESS_V4_BYTES_INDEX + 3], DEC);
return n;
}

const IPAddress arduino::IN6ADDR_ANY(IPv6);
const IPAddress arduino::INADDR_NONE(0,0,0,0);
48 changes: 38 additions & 10 deletions api/IPAddress.h
Original file line number Diff line number Diff line change
@@ -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;
@@ -32,55 +35,80 @@ 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; };
uint8_t* raw_address() { return _type == IPv4 ? &_address.bytes[IPADDRESS_V4_BYTES_INDEX] : _address.bytes; }

public:
// Constructors

// 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);
// 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);
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
// NOTE: IPv4 only; see implementation note
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); };

// 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
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
// NOTE: IPv4 only
IPAddress& operator=(const uint8_t *address);
// NOTE: 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;

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;
}

5 changes: 5 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -30,11 +30,16 @@ 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_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
src/Print/test_getWriteError.cpp
src/Print/test_print.cpp
85 changes: 85 additions & 0 deletions test/src/IPAddress/test_IPAddress6.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright (c) 2020 Arduino. All rights reserved.
*/

/**************************************************************************************
* INCLUDE
**************************************************************************************/

#include <catch.hpp>

#include <IPAddress.h>

/**************************************************************************************
* 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);
}
395 changes: 395 additions & 0 deletions test/src/IPAddress/test_fromString6.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,395 @@
/*
* Copyright (c) 2020 Arduino. All rights reserved.
*/

/**************************************************************************************
* INCLUDE
**************************************************************************************/

#include <catch.hpp>

#include <String.h>
#include <IPAddress.h>

/**************************************************************************************
* 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 explicit 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 explicit 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
}
1 change: 1 addition & 0 deletions test/src/IPAddress/test_operator_assignment.cpp
Original file line number Diff line number Diff line change
@@ -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));
}
72 changes: 72 additions & 0 deletions test/src/IPAddress/test_operator_comparison6.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2020 Arduino. All rights reserved.
*/

/**************************************************************************************
* INCLUDE
**************************************************************************************/

#include <catch.hpp>

#include <IPAddress.h>

/**************************************************************************************
* 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);
}

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);
}
1 change: 1 addition & 0 deletions test/src/IPAddress/test_operator_parentheses.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
44 changes: 44 additions & 0 deletions test/src/IPAddress/test_operator_parentheses6.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2020 Arduino. All rights reserved.
*/

/**************************************************************************************
* INCLUDE
**************************************************************************************/

#include <catch.hpp>

#include <IPAddress.h>

/**************************************************************************************
* 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);
}
137 changes: 137 additions & 0 deletions test/src/IPAddress/test_printTo6.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* Copyright (c) 2020 Arduino. All rights reserved.
*/

/**************************************************************************************
* INCLUDE
**************************************************************************************/

#include <catch.hpp>

#include <IPAddress.h>
#include <PrintMock.h>

/**************************************************************************************
* 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");
}