summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugen Wissner <belka@caraus.de>2018-09-08 07:20:23 +0200
committerEugen Wissner <belka@caraus.de>2018-09-08 07:20:23 +0200
commit7f080831c5646b08e38568d0e0e96b83d59491fa (patch)
tree36a97b9cae61cc83c97657e890293ea7f634db39
parent94c7fd2231e6c1338e7ee0bf240e854dffaaacac (diff)
downloadtanya-7f080831c5646b08e38568d0e0e96b83d59491fa.tar.gz
Implement IPv6 parser, fix #49v0.11.2
-rw-r--r--source/tanya/net/ip.d399
1 files changed, 381 insertions, 18 deletions
diff --git a/source/tanya/net/ip.d b/source/tanya/net/ip.d
index f0a7e86..a53f3c4 100644
--- a/source/tanya/net/ip.d
+++ b/source/tanya/net/ip.d
@@ -17,9 +17,11 @@ module tanya.net.ip;
import tanya.algorithm.mutation;
import tanya.container.string;
import tanya.conv;
+import tanya.encoding.ascii;
import tanya.format;
import tanya.meta.trait;
import tanya.meta.transform;
+import tanya.net.iface;
import tanya.net.inet;
import tanya.range;
import tanya.typecons;
@@ -158,7 +160,7 @@ struct Address4
* Returns: $(D_KEYWORD true) if this is a multicast address,
* $(D_KEYWORD false) otherwise.
*
- * See_Also: $(D_PSYMBOL isMulticast).
+ * See_Also: $(D_PSYMBOL isUnicast).
*/
bool isMulticast() const @nogc nothrow pure @safe
{
@@ -274,7 +276,7 @@ struct Address4
* successful, or nothing otherwise.
*/
Option!Address4 address4(R)(R range)
-if (isInputRange!R && isSomeChar!(ElementType!R))
+if (isForwardRange!R && is(Unqual!(ElementType!R) == char) && hasLength!R)
{
Address4 result;
version (LittleEndian)
@@ -394,16 +396,237 @@ struct Address6
// Raw bytes
private ubyte[16] address;
+ /// Scope ID.
+ uint scopeID;
+
/**
- * Constructs an $(D_PSYMBOL Address6) from an unsigned integer in host
- * byte order.
+ * Constructs an $(D_PSYMBOL Address6) from an array containing raw bytes
+ * in network byte order and scope ID.
*
* Params:
* address = The address as an unsigned integer in host byte order.
+ * scopeID = Scope ID.
+ */
+ this(ubyte[16] address, uint scopeID = 0) @nogc nothrow pure @safe
+ {
+ 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.
*/
- this(ulong address)
+ 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
{
- copy(NetworkOrder!8(address), this.address[]);
+ 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.
*
+ * 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:
* R = Input range type.
* range = Stringish range containing the address.
@@ -425,15 +667,19 @@ private void write2Bytes(R)(ref R range, ubyte[] address)
* successful, or nothing otherwise.
*/
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)
{
return typeof(return)();
}
Address6 result;
+ ubyte[12] tail;
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 == ':')
{
range.popFront();
@@ -445,6 +691,8 @@ if (isInputRange!R && isSomeChar!(ElementType!R))
goto ParseTail;
}
+ // Parse the address before '::'.
+ // This loop parses the whole address if it doesn't contain '::'.
for (; i < 13; i += 2)
{
write2Bytes(range, result.address[i .. $]);
@@ -465,15 +713,50 @@ if (isInputRange!R && isSomeChar!(ElementType!R))
}
write2Bytes(range, result.address[14 .. $]);
- return range.empty ? typeof(return)(result) : typeof(return)();
+ if (range.empty)
+ {
+ return typeof(return)(result);
+ }
+ else if (range.front == '%')
+ {
+ goto ParseIface;
+ }
+ else
+ {
+ return typeof(return)();
+ }
-ParseTail:
- ubyte[12] tail;
- size_t j;
+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 (; !range.empty; i += 2, j += 2, range.popFront())
+ for (i = 2, j = 2; i <= 11; i += 2, j += 2, range.popFront())
{
- if (i > 11 || range.front == ':')
+ if (range.empty || range.front == ':')
{
return typeof(return)();
}
@@ -481,19 +764,47 @@ ParseTail:
if (range.empty)
{
- break;
+ goto CopyTail;
+ }
+ else if (range.front == '%')
+ {
+ goto ParseIface;
}
- if (range.front != ':')
+ else if (range.front != ':')
{
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);
}
-@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];
@@ -518,10 +829,62 @@ ParseTail:
}
// Rejects malformed addresses
-@nogc nothrow pure @safe unittest
+@nogc nothrow @safe unittest
{
assert(address6("").isNothing);
assert(address6(":").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);
+ }
}