Implement IPv6 parser, fix #49
This commit is contained in:
parent
94c7fd2231
commit
7f080831c5
@ -17,9 +17,11 @@ module tanya.net.ip;
|
|||||||
import tanya.algorithm.mutation;
|
import tanya.algorithm.mutation;
|
||||||
import tanya.container.string;
|
import tanya.container.string;
|
||||||
import tanya.conv;
|
import tanya.conv;
|
||||||
|
import tanya.encoding.ascii;
|
||||||
import tanya.format;
|
import tanya.format;
|
||||||
import tanya.meta.trait;
|
import tanya.meta.trait;
|
||||||
import tanya.meta.transform;
|
import tanya.meta.transform;
|
||||||
|
import tanya.net.iface;
|
||||||
import tanya.net.inet;
|
import tanya.net.inet;
|
||||||
import tanya.range;
|
import tanya.range;
|
||||||
import tanya.typecons;
|
import tanya.typecons;
|
||||||
@ -158,7 +160,7 @@ struct Address4
|
|||||||
* Returns: $(D_KEYWORD true) if this is a multicast address,
|
* Returns: $(D_KEYWORD true) if this is a multicast address,
|
||||||
* $(D_KEYWORD false) otherwise.
|
* $(D_KEYWORD false) otherwise.
|
||||||
*
|
*
|
||||||
* See_Also: $(D_PSYMBOL isMulticast).
|
* See_Also: $(D_PSYMBOL isUnicast).
|
||||||
*/
|
*/
|
||||||
bool isMulticast() const @nogc nothrow pure @safe
|
bool isMulticast() const @nogc nothrow pure @safe
|
||||||
{
|
{
|
||||||
@ -274,7 +276,7 @@ struct Address4
|
|||||||
* successful, or nothing otherwise.
|
* successful, or nothing otherwise.
|
||||||
*/
|
*/
|
||||||
Option!Address4 address4(R)(R range)
|
Option!Address4 address4(R)(R range)
|
||||||
if (isInputRange!R && isSomeChar!(ElementType!R))
|
if (isForwardRange!R && is(Unqual!(ElementType!R) == char) && hasLength!R)
|
||||||
{
|
{
|
||||||
Address4 result;
|
Address4 result;
|
||||||
version (LittleEndian)
|
version (LittleEndian)
|
||||||
@ -394,16 +396,237 @@ struct Address6
|
|||||||
// Raw bytes
|
// Raw bytes
|
||||||
private ubyte[16] address;
|
private ubyte[16] address;
|
||||||
|
|
||||||
|
/// Scope ID.
|
||||||
|
uint scopeID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an $(D_PSYMBOL Address6) from an unsigned integer in host
|
* Constructs an $(D_PSYMBOL Address6) from an array containing raw bytes
|
||||||
* byte order.
|
* in network byte order and scope ID.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* address = The address as an unsigned integer in host byte order.
|
* address = The address as an unsigned integer in host byte order.
|
||||||
|
* scopeID = Scope ID.
|
||||||
*/
|
*/
|
||||||
this(ulong address)
|
this(ubyte[16] address, uint scopeID = 0) @nogc nothrow pure @safe
|
||||||
{
|
{
|
||||||
copy(NetworkOrder!8(address), this.address[]);
|
copy(address[], this.address[]);
|
||||||
|
this.scopeID = scopeID;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@nogc nothrow pure @safe unittest
|
||||||
|
{
|
||||||
|
const ubyte[16] expected = [ 0, 1, 0, 2, 0, 3, 0, 4,
|
||||||
|
0, 5, 0, 6, 0, 7, 0, 8 ];
|
||||||
|
auto actual = Address6(expected, 1);
|
||||||
|
assert(actual.toBytes() == expected);
|
||||||
|
assert(actual.scopeID == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns object that represents ::.
|
||||||
|
*
|
||||||
|
* Returns: Object that represents any address.
|
||||||
|
*/
|
||||||
|
static Address6 any() @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
return Address6();
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@nogc nothrow pure @safe unittest
|
||||||
|
{
|
||||||
|
assert(Address6.any().isAny());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns object that represents ::1.
|
||||||
|
*
|
||||||
|
* Returns: Object that represents the Loopback address.
|
||||||
|
*/
|
||||||
|
static Address6 loopback() @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
typeof(return) address;
|
||||||
|
address.address[$ - 1] = 1;
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@nogc nothrow pure @safe unittest
|
||||||
|
{
|
||||||
|
assert(Address6.loopback().isLoopback());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* :: can represent any address. This function checks whether this
|
||||||
|
* address is ::.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if this is an unspecified address,
|
||||||
|
* $(D_KEYWORD false) otherwise.
|
||||||
|
*/
|
||||||
|
bool isAny() const @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
return this.address == any.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@nogc nothrow @safe unittest
|
||||||
|
{
|
||||||
|
assert(address6("::").isAny());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loopback address is ::1.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if this is a loopback address,
|
||||||
|
* $(D_KEYWORD false) otherwise.
|
||||||
|
*/
|
||||||
|
bool isLoopback() const @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
return this.address == loopback.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@nogc nothrow @safe unittest
|
||||||
|
{
|
||||||
|
assert(address6("::1").isLoopback());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether this address' destination is a group of endpoints.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if this is a multicast address,
|
||||||
|
* $(D_KEYWORD false) otherwise.
|
||||||
|
*
|
||||||
|
* See_Also: $(D_PSYMBOL isUnicast).
|
||||||
|
*/
|
||||||
|
bool isMulticast() const @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
return this.address[0] == 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@nogc nothrow @safe unittest
|
||||||
|
{
|
||||||
|
assert(address6("ff00::").isMulticast());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether this address' destination is a single endpoint.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if this is a multicast address,
|
||||||
|
* $(D_KEYWORD false) otherwise.
|
||||||
|
*
|
||||||
|
* See_Also: $(D_PSYMBOL isMulticast).
|
||||||
|
*/
|
||||||
|
bool isUnicast() const @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
return !isMulticast();
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@nogc nothrow @safe unittest
|
||||||
|
{
|
||||||
|
assert(address6("::1").isUnicast());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether this address is a link-local unicast address.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if this is a link-local address,
|
||||||
|
* $(D_KEYWORD false) otherwise.
|
||||||
|
*/
|
||||||
|
bool isLinkLocal() const @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
return this.address[0] == 0xfe && (this.address[1] & 0xc0) == 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@nogc nothrow @safe unittest
|
||||||
|
{
|
||||||
|
assert(address6("fe80::1").isLinkLocal());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether this address is an Unique Local Address (ULA).
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if this is an Unique Local Address,
|
||||||
|
* $(D_KEYWORD false) otherwise.
|
||||||
|
*/
|
||||||
|
bool isUniqueLocal() const @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
return this.address[0] == 0xfc || this.address[0] == 0xfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@nogc nothrow @safe unittest
|
||||||
|
{
|
||||||
|
assert(address6("fd80:124e:34f3::1").isUniqueLocal());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns text representation of this address.
|
||||||
|
*
|
||||||
|
* Returns: text representation of this address.
|
||||||
|
*/
|
||||||
|
String stringify() const @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
String output;
|
||||||
|
foreach (i, b; this.address)
|
||||||
|
{
|
||||||
|
ubyte low = b & 0xf;
|
||||||
|
ubyte high = b >> 4;
|
||||||
|
|
||||||
|
if (high < 10)
|
||||||
|
{
|
||||||
|
output.insertBack(cast(char) (high + '0'));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
output.insertBack(cast(char) (high - 10 + 'a'));
|
||||||
|
}
|
||||||
|
if (low < 10)
|
||||||
|
{
|
||||||
|
output.insertBack(cast(char) (low + '0'));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
output.insertBack(cast(char) (low - 10 + 'a'));
|
||||||
|
}
|
||||||
|
if (i % 2 != 0 && i != (this.address.length - 1))
|
||||||
|
{
|
||||||
|
output.insertBack(':');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@nogc nothrow @safe unittest
|
||||||
|
{
|
||||||
|
import tanya.algorithm.comparison : equal;
|
||||||
|
|
||||||
|
assert(equal(address6("1:2:3:4:5:6:7:8").stringify()[],
|
||||||
|
"0001:0002:0003:0004:0005:0006:0007:0008"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces a byte array containing this address in network byte order.
|
||||||
|
*
|
||||||
|
* Returns: This address as raw bytes in network byte order.
|
||||||
|
*/
|
||||||
|
ubyte[16] toBytes() const @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
return this.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@nogc nothrow @safe unittest
|
||||||
|
{
|
||||||
|
auto actual = address6("1:2:3:4:5:6:7:8");
|
||||||
|
ubyte[16] expected = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8];
|
||||||
|
assert(actual.toBytes() == expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,6 +640,25 @@ private void write2Bytes(R)(ref R range, ubyte[] address)
|
|||||||
/**
|
/**
|
||||||
* Parses a string containing an IPv6 address.
|
* Parses a string containing an IPv6 address.
|
||||||
*
|
*
|
||||||
|
* This function isn't pure since an IPv6 address can contain interface name
|
||||||
|
* or interface ID (separated from the address by `%`). If an interface name
|
||||||
|
* is specified (i.e. first character after `%` is not a digit), the parser
|
||||||
|
* tries to convert it to the ID of that interface. If the interface with the
|
||||||
|
* given name can't be found, the parser doesn't fail, but just ignores the
|
||||||
|
* invalid interface name.
|
||||||
|
*
|
||||||
|
* If an ID is given (i.e. first character after `%` is a digit),
|
||||||
|
* $(D_PSYMBOL address6) just stores it in $(D_PSYMBOL Address6.scopeID) without
|
||||||
|
* checking whether an interface with this ID really exists. If the ID is
|
||||||
|
* invalid (if it is too long or contains non decimal characters), parsing
|
||||||
|
* and nothing is returned.
|
||||||
|
*
|
||||||
|
* If neither an ID nor a name is given, $(D_PSYMBOL Address6.scopeID) is set
|
||||||
|
* to `0`.
|
||||||
|
*
|
||||||
|
* The parser doesn't support notation with an embedded IPv4 address (e.g.
|
||||||
|
* ::1.2.3.4).
|
||||||
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* R = Input range type.
|
* R = Input range type.
|
||||||
* range = Stringish range containing the address.
|
* range = Stringish range containing the address.
|
||||||
@ -425,15 +667,19 @@ private void write2Bytes(R)(ref R range, ubyte[] address)
|
|||||||
* successful, or nothing otherwise.
|
* successful, or nothing otherwise.
|
||||||
*/
|
*/
|
||||||
Option!Address6 address6(R)(R range)
|
Option!Address6 address6(R)(R range)
|
||||||
if (isInputRange!R && isSomeChar!(ElementType!R))
|
if (isForwardRange!R && is(Unqual!(ElementType!R) == char) && hasLength!R)
|
||||||
{
|
{
|
||||||
if (range.empty)
|
if (range.empty)
|
||||||
{
|
{
|
||||||
return typeof(return)();
|
return typeof(return)();
|
||||||
}
|
}
|
||||||
Address6 result;
|
Address6 result;
|
||||||
|
ubyte[12] tail;
|
||||||
size_t i;
|
size_t i;
|
||||||
|
size_t j;
|
||||||
|
|
||||||
|
// An address begins with a number, not ':'. But there is a special case
|
||||||
|
// if the address begins with '::'.
|
||||||
if (range.front == ':')
|
if (range.front == ':')
|
||||||
{
|
{
|
||||||
range.popFront();
|
range.popFront();
|
||||||
@ -445,6 +691,8 @@ if (isInputRange!R && isSomeChar!(ElementType!R))
|
|||||||
goto ParseTail;
|
goto ParseTail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse the address before '::'.
|
||||||
|
// This loop parses the whole address if it doesn't contain '::'.
|
||||||
for (; i < 13; i += 2)
|
for (; i < 13; i += 2)
|
||||||
{
|
{
|
||||||
write2Bytes(range, result.address[i .. $]);
|
write2Bytes(range, result.address[i .. $]);
|
||||||
@ -465,15 +713,50 @@ if (isInputRange!R && isSomeChar!(ElementType!R))
|
|||||||
}
|
}
|
||||||
write2Bytes(range, result.address[14 .. $]);
|
write2Bytes(range, result.address[14 .. $]);
|
||||||
|
|
||||||
return range.empty ? typeof(return)(result) : typeof(return)();
|
if (range.empty)
|
||||||
|
|
||||||
ParseTail:
|
|
||||||
ubyte[12] tail;
|
|
||||||
size_t j;
|
|
||||||
|
|
||||||
for (; !range.empty; i += 2, j += 2, range.popFront())
|
|
||||||
{
|
{
|
||||||
if (i > 11 || range.front == ':')
|
return typeof(return)(result);
|
||||||
|
}
|
||||||
|
else if (range.front == '%')
|
||||||
|
{
|
||||||
|
goto ParseIface;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return typeof(return)();
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseTail: // after ::
|
||||||
|
// Normally the address can't end with ':', but a special case is if the
|
||||||
|
// address ends with '::'. So the first iteration of the loop below is
|
||||||
|
// unrolled to check whether the address contains something after '::' at
|
||||||
|
// all.
|
||||||
|
if (range.empty)
|
||||||
|
{
|
||||||
|
return typeof(return)(result); // ends with ::
|
||||||
|
}
|
||||||
|
if (range.front == ':')
|
||||||
|
{
|
||||||
|
return typeof(return)();
|
||||||
|
}
|
||||||
|
write2Bytes(range, tail[j .. $]);
|
||||||
|
if (range.empty)
|
||||||
|
{
|
||||||
|
goto CopyTail;
|
||||||
|
}
|
||||||
|
else if (range.front == '%')
|
||||||
|
{
|
||||||
|
goto ParseIface;
|
||||||
|
}
|
||||||
|
else if (range.front != ':')
|
||||||
|
{
|
||||||
|
return typeof(return)();
|
||||||
|
}
|
||||||
|
range.popFront();
|
||||||
|
|
||||||
|
for (i = 2, j = 2; i <= 11; i += 2, j += 2, range.popFront())
|
||||||
|
{
|
||||||
|
if (range.empty || range.front == ':')
|
||||||
{
|
{
|
||||||
return typeof(return)();
|
return typeof(return)();
|
||||||
}
|
}
|
||||||
@ -481,19 +764,47 @@ ParseTail:
|
|||||||
|
|
||||||
if (range.empty)
|
if (range.empty)
|
||||||
{
|
{
|
||||||
break;
|
goto CopyTail;
|
||||||
}
|
}
|
||||||
if (range.front != ':')
|
else if (range.front == '%')
|
||||||
|
{
|
||||||
|
goto ParseIface;
|
||||||
|
}
|
||||||
|
else if (range.front != ':')
|
||||||
{
|
{
|
||||||
return typeof(return)();
|
return typeof(return)();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
copy(tail[0 .. j + 2], result.address[$ - j - 2 .. $]);
|
|
||||||
|
|
||||||
|
ParseIface: // Scope name or ID
|
||||||
|
range.popFront();
|
||||||
|
if (range.empty)
|
||||||
|
{
|
||||||
|
return typeof(return)();
|
||||||
|
}
|
||||||
|
else if (isDigit(range.front))
|
||||||
|
{
|
||||||
|
const scopeID = readIntegral!uint(range);
|
||||||
|
if (range.empty)
|
||||||
|
{
|
||||||
|
result.scopeID = scopeID;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return typeof(return)();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.scopeID = nameToIndex(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyTail:
|
||||||
|
copy(tail[0 .. j + 2], result.address[$ - j - 2 .. $]);
|
||||||
return typeof(return)(result);
|
return typeof(return)(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@nogc nothrow pure @safe unittest
|
@nogc nothrow @safe unittest
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
ubyte[16] expected = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8];
|
ubyte[16] expected = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8];
|
||||||
@ -518,10 +829,62 @@ ParseTail:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rejects malformed addresses
|
// Rejects malformed addresses
|
||||||
@nogc nothrow pure @safe unittest
|
@nogc nothrow @safe unittest
|
||||||
{
|
{
|
||||||
assert(address6("").isNothing);
|
assert(address6("").isNothing);
|
||||||
assert(address6(":").isNothing);
|
assert(address6(":").isNothing);
|
||||||
assert(address6(":a").isNothing);
|
assert(address6(":a").isNothing);
|
||||||
assert(address6("a:").isNothing);
|
assert(address6("a:").isNothing);
|
||||||
|
assert(address6("1:2:3:4::6:").isNothing);
|
||||||
|
assert(address6("1:2:3:4::6:7:8%").isNothing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an $(D_PSYMBOL Address6) from raw bytes in network byte order and
|
||||||
|
* the scope ID.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* R = Input range type.
|
||||||
|
* range = $(D_KEYWORD ubyte) range containing the address.
|
||||||
|
* scopeID = Scope ID.
|
||||||
|
*
|
||||||
|
* Returns: $(D_PSYMBOL Option) containing the address if the $(D_PARAM range)
|
||||||
|
* contains exactly 16 bytes, or nothing otherwise.
|
||||||
|
*/
|
||||||
|
Option!Address6 address6(R)(R range, uint scopeID = 0)
|
||||||
|
if (isInputRange!R && is(Unqual!(ElementType!R) == ubyte))
|
||||||
|
{
|
||||||
|
Address6 result;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (; i < 16 && !range.empty; ++i, range.popFront())
|
||||||
|
{
|
||||||
|
result.address[i] = range.front;
|
||||||
|
}
|
||||||
|
result.scopeID = scopeID;
|
||||||
|
|
||||||
|
return range.empty && i == 16 ? typeof(return)(result) : typeof(return)();
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@nogc nothrow pure @safe unittest
|
||||||
|
{
|
||||||
|
{
|
||||||
|
ubyte[16] actual = [ 1, 2, 3, 4, 5, 6, 7, 8,
|
||||||
|
9, 10, 11, 12, 13, 14, 15, 16 ];
|
||||||
|
assert(!address6(actual[]).isNothing);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ubyte[15] actual = [ 1, 2, 3, 4, 5, 6, 7, 8,
|
||||||
|
9, 10, 11, 12, 13, 14, 15 ];
|
||||||
|
assert(address6(actual[]).isNothing);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ubyte[17] actual = [ 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||||
|
10, 11, 12, 13, 14, 15, 16, 17 ];
|
||||||
|
assert(address6(actual[]).isNothing);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assert(address6(cast(ubyte[]) []).isNothing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user