Skip to content
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

Rewrite IPAddress without union #225

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 104 additions & 79 deletions api/IPAddress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,53 +19,87 @@

#include "IPAddress.h"
#include "Print.h"
#include <cstdint>
#include <new>

using namespace arduino;

IPAddress::IPAddress() : IPAddress(IPv4) {}
IPAddress::IPAddress() = default;

IPAddress::IPAddress(IPType ip_type)
{
_type = ip_type;
memset(_address.bytes, 0, sizeof(_address.bytes));
}
IPAddress::IPAddress(IPType ip_type) : _type(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));
_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;
_address[IPADDRESS_V4_BYTES_INDEX] = first_octet;
_address[IPADDRESS_V4_BYTES_INDEX + 1] = second_octet;
_address[IPADDRESS_V4_BYTES_INDEX + 2] = third_octet;
_address[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(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) : _address{o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14, o15, o16}, _type(IPv6) {}

IPAddress::IPAddress(uint32_t address)
// IPv4 only
IPAddress::IPAddress(uint32_t address)
{
// IPv4 only
_type = IPv4;
memset(_address.bytes, 0, sizeof(_address.bytes));
_address.dword[IPADDRESS_V4_DWORD_INDEX] = address;
// memcpy(raw_address(), &address, 4); // This method guarantees a defined behavior.
// But lifetime started when:
// [basic.life#2] https://eel.is/c++draft/basic.life#2
//(2.1) -- storage with the proper alignment and size for type T is obtained, and
//(2.2) -- its initialization (if any) is complete (including vacuous initialization) ([dcl.init]),
//
// The statement: {#Any pointer conversions to write to ADDRESS storage (as a multibyte integer)
// are undefined behavior when the lifetime of the multibyte type has not previously started.#}
// only apply to c++17 and earlier. Since C++20 array of bytes implicitly creates the inner objects.

// C++20: https://timsong-cpp.github.io/cppwp/n4861/intro.object#13
// 13 An operation that begins the lifetime of an array of char, unsigned char, or std::byte implicitly creates objects within
// the region of storage occupied by the array. [ Note: The array object provides storage for these objects. — end note ]

// C++23: https://timsong-cpp.github.io/cppwp/n4950/intro.object#13
// 13 An operation that begins the lifetime of an array of unsigned char or std::byte implicitly creates objects within the
// region of storage occupied by the array.
// [Note 5: The array object provides storage for these objects. — end note]

// Current draft: https://eel.is/c++draft/intro.object#14
// 14 Except during constant evaluation, an operation that begins the lifetime of an array of unsigned char or std::byte implicitly
// creates objects within the region of storage occupied by the array.
// [Note 5: The array object provides storage for these objects. — end note]

// *reinterpret_cast<uint32_t*>(_address[IPADDRESS_V4_BYTES_INDEX]) = address; // This valid initialization in the `_address` storage since C++20.
// now the pointer `_address[IPADDRESS_V4_BYTES_INDEX]` points to a multibyte int.

new (&_address[IPADDRESS_V4_BYTES_INDEX]) uint32_t (address); // But the new-expression is better for understanding and looks nicer (for trivial types, the
// new expression only begins its lifetime and does not perform any other actions).
// NOTE: https://en.cppreference.com/w/cpp/language/new#Notes

// NOTE: new-expression and reinterpret_cast require alignment of the storage, but memcpy does not.

// C++ standard draft [basic.life#7](https://eel.is/c++draft/basic.life#7)
// Before the lifetime of an object has started but after the storage which the object
// will occupy has been allocated or, after the lifetime of an object has ended and
// before the storage which the object occupied is reused or released, any pointer that
// represents the address of the storage location where the object will be or was
// located may be used but only in limited ways. For an object under construction or
// destruction, see [class.cdtor]. Otherwise, such a pointer refers to allocated storage
// ([basic.stc.dynamic.allocation]), and using the pointer as if the pointer were of
// type void* is well-defined. Indirection through such a pointer is permitted but the
// resulting lvalue may only be used in limited ways, as described below.
// The program has undefined behavior if
// --the pointer is used as the operand of a delete-expression,
// --the pointer is used as the operand of a static_cast ([expr.static.cast]), except
// when the conversion is to pointer to cv void, or to pointer to cv void and subsequently
// to pointer to cv char, cv unsigned char, or cv std::byte ([cstddef.syn]), or

// C++ standard draft [basic.life#8](https://eel.is/c++draft/basic.life#8)
// Similarly, before the lifetime of an object has started but after the storage which
// the object will occupy has been allocated or, after the lifetime of an object has
// ended and before the storage which the object occupied is reused or released, any
// glvalue that refers to the original object may be used but only in limited ways.
// For an object under construction or destruction, see [class.cdtor]. Otherwise, such
// a glvalue refers to allocated storage ([basic.stc.dynamic.allocation]), and using
// the properties of the glvalue that do not depend on its value is well-defined.
// The program has undefined behavior if
// -- the glvalue is used to access the object, or

// NOTE on conversion/comparison and uint32_t:
// These conversions are host platform dependent.
Expand All @@ -78,15 +112,10 @@ IPAddress::IPAddress(uint32_t address)

IPAddress::IPAddress(const uint8_t *address) : IPAddress(IPv4, address) {}

IPAddress::IPAddress(IPType ip_type, const uint8_t *address)
IPAddress::IPAddress(IPType ip_type, const uint8_t *address) : _type(ip_type)
{
_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));
}
const size_t copy_size = (ip_type == IPv4) ? sizeof(uint32_t) : sizeof(_address);
memcpy(raw_address(), address, copy_size);
}

IPAddress::IPAddress(const char *address)
Expand All @@ -97,18 +126,18 @@ IPAddress::IPAddress(const char *address)
String IPAddress::toString4() const
{
char szRet[16];
snprintf(szRet, sizeof(szRet), "%u.%u.%u.%u", _address.bytes[IPADDRESS_V4_BYTES_INDEX], _address.bytes[IPADDRESS_V4_BYTES_INDEX + 1], _address.bytes[IPADDRESS_V4_BYTES_INDEX + 2], _address.bytes[IPADDRESS_V4_BYTES_INDEX + 3]);
snprintf(szRet, sizeof(szRet), "%u.%u.%u.%u", _address[IPADDRESS_V4_BYTES_INDEX], _address[IPADDRESS_V4_BYTES_INDEX + 1], _address[IPADDRESS_V4_BYTES_INDEX + 2], _address[IPADDRESS_V4_BYTES_INDEX + 3]);
return String(szRet);
}

String IPAddress::toString6() const
{
char szRet[40];
snprintf(szRet, sizeof(szRet), "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
_address.bytes[0], _address.bytes[1], _address.bytes[2], _address.bytes[3],
_address.bytes[4], _address.bytes[5], _address.bytes[6], _address.bytes[7],
_address.bytes[8], _address.bytes[9], _address.bytes[10], _address.bytes[11],
_address.bytes[12], _address.bytes[13], _address.bytes[14], _address.bytes[15]);
_address[0], _address[1], _address[2], _address[3],
_address[4], _address[5], _address[6], _address[7],
_address[8], _address[9], _address[10], _address[11],
_address[12], _address[13], _address[14], _address[15]);
return String(szRet);
}

Expand All @@ -135,7 +164,7 @@ bool IPAddress::fromString4(const char *address)
int16_t acc = -1; // Accumulator
uint8_t dots = 0;

memset(_address.bytes, 0, sizeof(_address.bytes));
memset(_address, 0, sizeof(_address));
while (*address)
{
char c = *address++;
Expand All @@ -157,7 +186,7 @@ bool IPAddress::fromString4(const char *address)
/* No value between dots, e.g. '1..' */
return false;
}
_address.bytes[IPADDRESS_V4_BYTES_INDEX + dots++] = acc;
_address[IPADDRESS_V4_BYTES_INDEX + dots++] = acc;
acc = -1;
}
else
Expand All @@ -175,7 +204,7 @@ bool IPAddress::fromString4(const char *address)
/* No value between dots, e.g. '1..' */
return false;
}
_address.bytes[IPADDRESS_V4_BYTES_INDEX + 3] = acc;
_address[IPADDRESS_V4_BYTES_INDEX + 3] = acc;
_type = IPv4;
return true;
}
Expand Down Expand Up @@ -215,8 +244,8 @@ bool IPAddress::fromString6(const char *address) {
if (colons == 7)
// too many separators
return false;
_address.bytes[colons * 2] = acc >> 8;
_address.bytes[colons * 2 + 1] = acc & 0xff;
_address[colons * 2] = acc >> 8;
_address[colons * 2 + 1] = acc & 0xff;
colons++;
acc = 0;
}
Expand All @@ -233,15 +262,15 @@ bool IPAddress::fromString6(const char *address) {
// 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;
_address[colons * 2] = acc >> 8;
_address[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];
_address[16 - colons * 2 + double_colons * 2 + i] = _address[double_colons * 2 + i];
for (int i = double_colons * 2; i < 16 - colons * 2 + double_colons * 2; i++)
_address.bytes[i] = 0;
_address[i] = 0;
}

_type = IPv6;
Expand All @@ -252,8 +281,10 @@ IPAddress& IPAddress::operator=(const uint8_t *address)
{
// 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));

memset(_address, 0, sizeof(_address));
memcpy(raw_address(), address, sizeof(uint32_t));

return *this;
}

Expand All @@ -268,35 +299,29 @@ IPAddress& IPAddress::operator=(uint32_t 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;
memset(_address, 0, sizeof(_address));
new (raw_address()) uint32_t (address); // See the comments in corresponding constructor.
return *this;
}

bool IPAddress::operator==(const IPAddress& addr) const {
return (addr._type == _type)
&& (memcmp(addr._address.bytes, _address.bytes, sizeof(_address.bytes)) == 0);
&& (memcmp(addr._address, _address, sizeof(_address)) == 0);
}

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[IPADDRESS_V4_BYTES_INDEX], sizeof(uint32_t)) == 0;
return _type == IPv4 && memcmp(addr, raw_address(), 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];
return *(raw_address() + index);
}

uint8_t& IPAddress::operator[](int index) {
if (_type == IPv4) {
return _address.bytes[IPADDRESS_V4_BYTES_INDEX + index];
}
return _address.bytes[index];
return *(raw_address() + index);
}

size_t IPAddress::printTo(Print& p) const
Expand All @@ -310,7 +335,7 @@ size_t IPAddress::printTo(Print& p) const
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 (_address[f * 2] == 0 && _address[f * 2 + 1] == 0) {
if (current_start == -1) {
current_start = f;
current_length = 1;
Expand All @@ -327,10 +352,10 @@ 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] >> 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;
uint8_t c1 = _address[f * 2] >> 4;
uint8_t c2 = _address[f * 2] & 0xf;
uint8_t c3 = _address[f * 2 + 1] >> 4;
uint8_t c4 = _address[f * 2 + 1] & 0xf;
if (c1 > 0) {
n += p.print((char)(c1 < 10 ? '0' + c1 : 'a' + c1 - 10));
}
Expand All @@ -357,10 +382,10 @@ size_t IPAddress::printTo(Print& p) const
// IPv4
for (int i =0; i < 3; i++)
{
n += p.print(_address.bytes[IPADDRESS_V4_BYTES_INDEX + i], DEC);
n += p.print(_address[IPADDRESS_V4_BYTES_INDEX + i], DEC);
n += p.print('.');
}
n += p.print(_address.bytes[IPADDRESS_V4_BYTES_INDEX + 3], DEC);
n += p.print(_address[IPADDRESS_V4_BYTES_INDEX + 3], DEC);
return n;
}

Expand Down
31 changes: 22 additions & 9 deletions api/IPAddress.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@

#pragma once

#include <string.h>
#include <stdint.h>
#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;
Expand All @@ -42,17 +42,20 @@ enum IPType {

class IPAddress : public Printable {
private:
union {
uint8_t bytes[16];
uint32_t dword[4];
} _address;
IPType _type;
alignas(alignof(uint32_t)) uint8_t _address[16]{}; // If the implementation does not require
// storage as a multibyte integer, you can
// remove the storage field alignment.
// Address (as uint32) is accessed by copying.
IPType _type{IPv4};

// 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 _type == IPv4 ? &_address.bytes[IPADDRESS_V4_BYTES_INDEX] : _address.bytes; }
const uint8_t* raw_address() const { return _type == IPv4 ? &_address[IPADDRESS_V4_BYTES_INDEX] : _address; }
uint8_t* raw_address() { return _type == IPv4 ? &_address[IPADDRESS_V4_BYTES_INDEX] : _address; }
// NOTE: If IPADDRESS_V4_BYTES_INDEX region can be initialized with a multibyte int, then a cast to unsigned char is required.


public:
// Constructors
Expand All @@ -63,6 +66,7 @@ class IPAddress : public Printable {
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
// NOTE: address MUST BE BigEndian.
IPAddress(uint32_t address);
// Default IPv4
IPAddress(const uint8_t *address);
Expand All @@ -75,7 +79,16 @@ 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[IPADDRESS_V4_DWORD_INDEX] : 0; };
// NOTE: Data of the returned integer in the native endianness, but relevant ordering is a BigEndian.
// The user is responsible for ensuring that the value is converted to BigEndian.
operator uint32_t() const {
uint32_t ret;
memcpy(&ret, raw_address(), 4);
// NOTE: maybe use the placement-new (or standard initialisation of uint32_t since C++20)
// for starting of the integer type lifetime in the storage when constructing an IPAddress?
// FIXME: need endianness checking? how do this with the arduino-api?
return _type == IPv4 ? ret : 0;
};

bool operator==(const IPAddress& addr) const;
bool operator!=(const IPAddress& addr) const { return !(*this == addr); };
Expand Down Expand Up @@ -119,4 +132,4 @@ extern const IPAddress IN6ADDR_ANY;
extern const IPAddress INADDR_NONE;
}

using arduino::IPAddress;
using arduino::IPAddress;
Loading