Add net.ip. Fix #48

This commit is contained in:
Eugen Wissner 2018-08-07 22:27:09 +02:00
parent abd286064b
commit aa4ccddf47

374
source/tanya/net/ip.d Normal file
View File

@ -0,0 +1,374 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Internet Protocol implementation.
*
* Copyright: Eugene Wissner 2018.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/net/ip.d,
* tanya/net/ip.d)
*/
module tanya.net.ip;
import tanya.algorithm.mutation;
import tanya.container.string;
import tanya.conv;
import tanya.format;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.net.inet;
import tanya.range;
import tanya.typecons;
/**
* IPv4 internet address.
*/
struct Address4
{
// In network byte order.
private uint address;
version (LittleEndian)
{
private enum uint loopback_ = 0x0100007fU;
enum byte step = 8;
}
else
{
private enum uint loopback_ = 0x7f000001U;
enum byte step = -8;
}
private enum uint any_ = 0U;
private enum uint broadcast = uint.max;
/**
* Constructs an $(D_PSYMBOL Address4) from an unsigned integer in host
* byte order.
*
* Params:
* address = The address as an unsigned integer in host byte order.
*/
this(uint address) @nogc nothrow pure @safe
{
copy(NetworkOrder!4(address),
(() @trusted => (cast(ubyte*) &this.address)[0 .. 4])());
}
///
@nogc nothrow pure @safe unittest
{
assert(Address4(0x00202000U).toUInt() == 0x00202000U);
}
/**
* Returns object that represents 127.0.0.1.
*
* Returns: Object that represents the Loopback address.
*/
static Address4 loopback() @nogc nothrow pure @safe
{
typeof(return) address;
address.address = Address4.loopback_;
return address;
}
///
@nogc nothrow pure @safe unittest
{
assert(Address4.loopback().isLoopback());
}
/**
* Returns object that represents 0.0.0.0.
*
* Returns: Object that represents any address.
*/
static Address4 any() @nogc nothrow pure @safe
{
typeof(return) address;
address.address = Address4.any_;
return address;
}
///
@nogc nothrow pure @safe unittest
{
assert(Address4.any().isAny());
}
/**
* Loopback address is 127.0.0.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_;
}
///
@nogc nothrow pure @safe unittest
{
assert(address4("127.0.0.1").isLoopback());
}
/**
* 0.0.0.0 can represent any address. This function checks whether this
* address is 0.0.0.0.
*
* 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_;
}
///
@nogc nothrow pure @safe unittest
{
assert(address4("0.0.0.0").isAny());
}
/**
* Broadcast address is 255.255.255.255.
*
* Returns: $(D_KEYWORD true) if this is a broadcast address,
* $(D_KEYWORD false) otherwise.
*/
bool isBroadcast() const @nogc nothrow pure @safe
{
return this.address == broadcast;
}
///
@nogc nothrow pure @safe unittest
{
assert(address4("255.255.255.255").isBroadcast());
}
/**
* 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 isMulticast).
*/
bool isMulticast() const @nogc nothrow pure @safe
{
version (LittleEndian)
{
enum uint mask = 0xe0;
}
else
{
enum uint mask = 0xe0000000U;
}
return (this.address & mask) == mask;
}
///
@nogc nothrow pure @safe unittest
{
assert(address4("224.0.0.3").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 pure @safe unittest
{
assert(address4("192.168.0.1").isUnicast());
}
/**
* Produces a string containing an IPv4 address in dotted-decimal notation.
*
* Returns: This address in dotted-decimal notation.
*/
String stringify() const @nogc nothrow pure @safe
{
const octets = (() @trusted => (cast(ubyte*) &this.address)[0 .. 4])();
enum string fmt = "{}.{}.{}.{}";
version (LittleEndian)
{
return format!fmt(octets[0], octets[1], octets[2], octets[3]);
}
else
{
return format!fmt(octets[3], octets[2], octets[1], octets[0]);
}
}
///
@nogc nothrow pure @safe unittest
{
const dottedDecimal = "192.168.0.1";
const address = address4(dottedDecimal);
assert(address.get.stringify() == dottedDecimal);
}
/**
* Produces a byte array containing this address in network byte order.
*
* Returns: This address as raw bytes in network byte order.
*/
ubyte[4] toBytes() const @nogc nothrow pure @safe
{
ubyte[4] bytes;
copy((() @trusted => (cast(ubyte*) &this.address)[0 .. 4])(), bytes[]);
return bytes;
}
///
@nogc nothrow pure @safe unittest
{
const actual = address4("192.168.0.1");
const ubyte[4] expected = [192, 168, 0, 1];
assert(actual.toBytes() == expected);
}
/**
* Converts this address to an unsigned integer in host byte order.
*
* Returns: This address as an unsigned integer in host byte order.
*/
uint toUInt() const @nogc nothrow pure @safe
{
alias slice = () @trusted => (cast(ubyte*) &this.address)[0 .. 4];
return toHostOrder!uint(slice());
}
///
@nogc nothrow pure @safe unittest
{
assert(address4("127.0.0.1").toUInt() == 0x7f000001U);
}
}
/**
* Parses a string containing an IPv4 address in dotted-decimal notation.
*
* Params:
* R = Input range type.
* range = Stringish range containing the address.
*
* Returns: $(D_PSYMBOL Option) containing the address if the parsing was
* successful, or nothing otherwise.
*/
Option!Address4 address4(R)(R range)
if (isInputRange!R && isSomeChar!(ElementType!R))
{
Address4 result;
version (LittleEndian)
{
ubyte shift;
enum ubyte cond = 24;
}
else
{
ubyte shift = 24;
enum ubyte cond = 0;
}
for (; shift != cond; shift += Address4.step, range.popFront())
{
result.address |= readIntegral!ubyte(range) << shift;
if (range.empty || range.front != '.')
{
return typeof(return)();
}
}
result.address |= readIntegral!ubyte(range) << shift;
return range.empty ? typeof(return)(result) : typeof(return)();
}
///
@nogc nothrow pure @safe unittest
{
assert(address4("256.0.0.1").isNothing);
}
/**
* Constructs an $(D_PSYMBOL Address4) from raw bytes in network byte order.
*
* Params:
* R = Input range type.
* range = $(D_KEYWORD ubyte) range containing the address.
*
* Returns: $(D_PSYMBOL Option) containing the address if the $(D_PARAM range)
* contains exactly 4 bytes, or nothing otherwise.
*/
Option!Address4 address4(R)(R range)
if (isInputRange!R && is(Unqual!(ElementType!R) == ubyte))
{
Address4 result;
version (LittleEndian)
{
ubyte shift;
}
else
{
ubyte shift = 24;
}
for (; shift <= 24; shift += Address4.step, range.popFront())
{
if (range.empty)
{
return typeof(return)();
}
result.address |= range.front << shift;
}
return range.empty ? typeof(return)(result) : typeof(return)();
}
///
@nogc nothrow pure @safe unittest
{
{
ubyte[4] actual = [127, 0, 0, 1];
assert(address4(actual[]).isLoopback());
}
{
ubyte[3] actual = [127, 0, 0];
assert(address4(actual[]).isNothing);
}
{
ubyte[5] actual = [127, 0, 0, 0, 1];
assert(address4(actual[]).isNothing);
}
}
@nogc nothrow pure @safe unittest
{
assert(address4(cast(ubyte[]) []).isNothing);
}
// Assignment and comparison works
@nogc nothrow pure @safe unittest
{
auto address1 = Address4.loopback();
auto address2 = Address4.any();
address1 = address2;
assert(address1 == address2);
}