Skip to content
Merged
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
1 change: 1 addition & 0 deletions dub.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
{
"dpq2": { "version": "*", "dflags-dmd": ["-preview=in"] },
"vibe-serialization": { "version": "*", "dflags-dmd": ["-preview=in"] },
"vibe-core": { "version": "*", "dflags-dmd": ["-preview=in"] },
"gfm:math": "~>8.0.6"
},
"configurations": [
Expand Down
1 change: 1 addition & 0 deletions src/dpq2/conv/from_d_types.d
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module dpq2.conv.from_d_types;

public import dpq2.conv.arrays : isArrayType, toValue, isStaticArrayString;
public import dpq2.conv.geometric : isGeometricType, toValue;
public import dpq2.conv.inet: toValue, vibe2pg;
import dpq2.conv.time : POSTGRES_EPOCH_DATE, TimeStamp, TimeStampUTC, TimeOfDayWithTZ, Interval;
import dpq2.oids : detectOidTypeFromNative, oidConvTo, OidType;
import dpq2.value : Value, ValueFormat;
Expand Down
293 changes: 293 additions & 0 deletions src/dpq2/conv/inet.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
module dpq2.conv.inet;

import dpq2.conv.to_d_types;
import dpq2.oids: OidType;
import dpq2.value;

import std.bitmanip: bigEndianToNative, nativeToBigEndian;
import std.conv: to;
import std.socket;

@safe:

enum PgFamily : ubyte {
PGSQL_AF_INET = AddressFamily.INET,
PGSQL_AF_INET6,
}

alias InetAddress = TInetAddress!false; /// Represents inet PG value
alias CidrAddress = TInetAddress!true; /// Represents cidr PG value

///
package struct TInetAddress (bool isCIDR)
{
PgFamily family;
ubyte netmask;
AddrValue addr;
alias addr this;

///
this(in InternetAddress ia, ubyte mask = 32)
{
addr4 = ia.addr;
family = PgFamily.PGSQL_AF_INET;
netmask = mask;
}

///
this(in Internet6Address ia, ubyte mask = 128)
{
addr6 = ia.addr;
family = PgFamily.PGSQL_AF_INET6;
netmask = mask;
}

///
Address createStdAddr(ushort port = InternetAddress.PORT_ANY) const
{
switch(family)
{
case PgFamily.PGSQL_AF_INET:
return new InternetAddress(addr4, port);

case PgFamily.PGSQL_AF_INET6:
return new Internet6Address(addr6, port);

default:
assert(0, family.unsup);
}
}

///
auto toString() const
{
import std.format: format;

switch (family)
{
case PgFamily.PGSQL_AF_INET:
case PgFamily.PGSQL_AF_INET6:
return format("%s/%d", createStdAddr.toAddrString, this.netmask);

default:
return family.unsup;
}
}
}

unittest
{
auto std_addr = new InternetAddress("127.0.0.1", 123);
auto pg_addr = InetAddress(std_addr);

assert(pg_addr.createStdAddr.toAddrString == "127.0.0.1");

auto v = pg_addr.toValue;
assert(v.binaryValueAs!InetAddress.toString == "127.0.0.1/32");
assert(v.binaryValueAs!InetAddress.createStdAddr.toAddrString == "127.0.0.1");
assert(v.binaryValueAs!InetAddress == pg_addr);
}

unittest
{
auto std_addr = new Internet6Address("::1", 123);
auto pg_addr = InetAddress(std_addr);

assert(pg_addr.createStdAddr.toAddrString == "::1");

auto v = pg_addr.toValue;
assert(v.binaryValueAs!InetAddress.toString == "::1/128");
assert(v.binaryValueAs!InetAddress.createStdAddr.toAddrString == "::1");
assert(v.binaryValueAs!InetAddress == pg_addr);
}

///
InetAddress vibe2pg(VibeNetworkAddress)(VibeNetworkAddress a)
{
InetAddress r;

switch(a.family)
{
case AddressFamily.INET:
r.family = PgFamily.PGSQL_AF_INET;
r.netmask = 32;
r.addr4 = a.sockAddrInet4.sin_addr.s_addr.representAsBytes.bigEndianToNative!uint;
break;

case AddressFamily.INET6:
r.family = PgFamily.PGSQL_AF_INET6;
r.netmask = 128;
r.addr6 = AddrValue(a.sockAddrInet6.sin6_addr.s6_addr).swapEndiannesForBigEndianSystems;
break;

default:
throwUnsup(a.family);
}

return r;
}

private ref ubyte[T.sizeof] representAsBytes(T)(const ref return T s) @trusted
{
return *cast(ubyte[T.sizeof]*) &s;
}

private union Hdr
{
ubyte[4] bytes;

struct
{
PgFamily family;
ubyte netmask;
ubyte always_zero;
ubyte addr_len;
}
}

/// Constructs Value from InetAddress or from CidrAddress
Value toValue(T)(T v)
if(is(T == InetAddress) || is(T == CidrAddress))
{
Hdr hdr;
hdr.family = v.family;

ubyte[] addr_net_byte_order;

switch(v.family)
{
case PgFamily.PGSQL_AF_INET:
addr_net_byte_order ~= v.addr4.nativeToBigEndian;
break;

case PgFamily.PGSQL_AF_INET6:
addr_net_byte_order ~= v.addr.swapEndiannesForBigEndianSystems;
break;

default:
throwUnsup(v.family);
}

hdr.addr_len = addr_net_byte_order.length.to!ubyte;
hdr.netmask = v.netmask;

immutable r = (hdr.bytes ~ addr_net_byte_order).idup;
return Value(r, OidType.HostAddress);
}

package:

/// Convert Value to network address type
T binaryValueAs(T)(in Value v)
if(is(T == InetAddress) || is(T == CidrAddress))
{
enum oidType = is(T == InetAddress) ? OidType.HostAddress : OidType.NetworkAddress;
enum typeName = is(T == InetAddress) ? "inet" : "cidr";

if(v.oidType != oidType)
throwTypeComplaint(v.oidType, typeName);

Hdr hdr;
enum headerLen = hdr.sizeof;
enum ipv4_addr_len = 4;

if(v.data.length < hdr.sizeof + ipv4_addr_len)
throw new ValueConvException(ConvExceptionType.SIZE_MISMATCH, "unexpected data ending");

hdr.bytes = v.data[0 .. hdr.bytes.length];

ubyte lenMustBe;
switch(hdr.family)
{
case PgFamily.PGSQL_AF_INET: lenMustBe = ipv4_addr_len; break;
case PgFamily.PGSQL_AF_INET6: lenMustBe = 16; break;
default: throwUnsup(hdr.family);
}

if(hdr.addr_len != lenMustBe && hdr.always_zero == 0)
throw new ValueConvException(
ConvExceptionType.SIZE_MISMATCH,
"Wrong address length, must be "~lenMustBe.to!string
);

if(headerLen + hdr.addr_len != v.data.length)
throw new ValueConvException(
ConvExceptionType.SIZE_MISMATCH,
"Address length not matches to Value data length"
);

import std.bitmanip: bigEndianToNative;

T r;
r.family = hdr.family;
r.netmask = hdr.netmask;

switch(hdr.family)
{
case PgFamily.PGSQL_AF_INET:
const ubyte[4] b = v.data[headerLen..$];
r.addr4 = b.bigEndianToNative!uint;
break;

case PgFamily.PGSQL_AF_INET6:
AddrValue av;
av.addr6 = v.data[headerLen..$];
r.addr6 = av.swapEndiannesForBigEndianSystems;
break;

default: assert(0);
}

return r;
}

private:

//TODO: noreturn?
void throwUnsup(T)(T family)
{
throw new ValueConvException(ConvExceptionType.NOT_IMPLEMENTED, family.unsup);
}

string unsup(T)(in T family)
{
return "Unsupported address family: "~family.to!string;
}

private union AddrValue
{
ubyte[16] addr6; // IPv6 address in native byte order
short[8] addr6_parts; // for endiannes swap purpose

struct
{
ubyte[12] __unused;
uint addr4; // IPv4 address in native byte order
}
}

import std.system: Endian, endian;

static if(endian == Endian.littleEndian)
auto swapEndiannesForBigEndianSystems(in AddrValue s)
{
// do nothing for little endian
return s.addr6;
}
else
{

ubyte[16] swapEndiannesForBigEndianSystems(in AddrValue s)
{
import std.bitmanip: swapEndian;

AddrValue r;
enum len = AddrValue.addr6_parts.length;

foreach(ubyte i; 0 .. len)
r.addr6_parts[i] = s.addr6_parts[i].swapEndian;

return r.addr6;
}

}
26 changes: 26 additions & 0 deletions src/dpq2/conv/native_tests.d
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ module dpq2.conv.native_tests;
import dpq2;
import dpq2.conv.arrays : isArrayType;
import dpq2.conv.geometric: Line;
import dpq2.conv.inet: InetAddress, CidrAddress;
import std.bitmanip : BitArray;
import std.datetime;
import std.socket: InternetAddress, Internet6Address;
import std.typecons: Nullable;
import std.uuid: UUID;
import std.variant: Variant;
Expand Down Expand Up @@ -40,6 +42,7 @@ public void _integration_test( string connParam ) @system
{
import std.format: format;
import dpq2.connection: createTestConn;
import vibe.core.net: VibeNetworkAddress = NetworkAddress;

auto conn = createTestConn(connParam);

Expand Down Expand Up @@ -258,6 +261,28 @@ public void _integration_test( string connParam ) @system
C!SysTime(SysTime(DateTime(1997, 12, 17, 7, 37, 16), dur!"usecs"(12), testTZ), "timestamptz", "'1997-12-17 07:37:16.000012+02'");
C!(Nullable!SysTime)(Nullable!SysTime(SysTime(DateTime(1997, 12, 17, 7, 37, 16), dur!"usecs"(12), testTZ)), "timestamptz", "'1997-12-17 07:37:16.000012+02'");

// inet
const testInetAddr1 = InetAddress(new InternetAddress("127.0.0.1", InternetAddress.PORT_ANY), 9);
C!InetAddress(testInetAddr1, "inet", `'127.0.0.1/9'`);
const testInetAddr2 = InetAddress(new InternetAddress("127.0.0.1", InternetAddress.PORT_ANY));
C!InetAddress(testInetAddr2, "inet", `'127.0.0.1/32'`);
const testInetAddr3 = VibeNetworkAddress(new InternetAddress("127.0.0.1", InternetAddress.PORT_ANY)).vibe2pg;
C!InetAddress(testInetAddr3, "inet", `'127.0.0.1/32'`);

// inet6
const testInet6Addr1 = InetAddress(new Internet6Address("2::1", InternetAddress.PORT_ANY));
C!InetAddress(testInet6Addr1, "inet", `'2::1/128'`);
const testInet6Addr2 = InetAddress(new Internet6Address("2001:0:130F::9C0:876A:130B", InternetAddress.PORT_ANY),24);
C!InetAddress(testInet6Addr2, "inet", `'2001:0:130f::9c0:876a:130b/24'`);
const testInet6Addr3 = VibeNetworkAddress(new Internet6Address("2001:0:130F::9C0:876A:130B", InternetAddress.PORT_ANY)).vibe2pg;
C!InetAddress(testInet6Addr3, "inet", `'2001:0:130f::9c0:876a:130b/128'`);

// cidr
const testCidrAddr1 = CidrAddress(new InternetAddress("192.168.0.0", InternetAddress.PORT_ANY), 25);
C!CidrAddress(testCidrAddr1, "cidr", `'192.168.0.0/25'`);
const testCidrAddr2 = CidrAddress(new Internet6Address("::", InternetAddress.PORT_ANY), 64);
C!CidrAddress(testCidrAddr2, "cidr", `'::/64'`);

// json
C!PGjson(Json(["float_value": Json(123.456), "text_str": Json("text string")]), "json", `'{"float_value": 123.456,"text_str": "text string"}'`);
C!(Nullable!PGjson)(Nullable!Json(Json(["foo": Json("bar")])), "json", `'{"foo":"bar"}'`);
Expand Down Expand Up @@ -289,6 +314,7 @@ public void _integration_test( string connParam ) @system
C!(PGuuid[])([UUID("8b9ab33a-96e9-499b-9c36-aad1fe86d640")], "uuid[]", "'{8b9ab33a-96e9-499b-9c36-aad1fe86d640}'");
C!(PGline[])([Line(1,2,3), Line(4,5,6)], "line[]", `'{"{1,2,3}","{4,5,6}"}'`);
C!(PGtimestamp[])([PGtimestamp(DateTime(1997, 12, 17, 7, 37, 16), dur!"usecs"(12))], "timestamp[]", `'{"1997-12-17 07:37:16.000012"}'`);
C!(InetAddress[])([testInetAddr1, testInet6Addr2], "inet[]", `'{127.0.0.1/9,2001:0:130f::9c0:876a:130b/24}'`);
C!(Nullable!(int[]))(Nullable!(int[]).init, "int[]", "NULL");
C!(Nullable!(int[]))(Nullable!(int[])([1,2,3]), "int[]", "'{1,2,3}'");
}
Expand Down
1 change: 1 addition & 0 deletions src/dpq2/conv/to_d_types.d
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import dpq2.conv.from_d_types;
import dpq2.conv.numeric: rawValueToNumeric;
import dpq2.conv.time: binaryValueAs, TimeStamp, TimeStampUTC, TimeOfDayWithTZ, Interval;
import dpq2.conv.geometric: binaryValueAs, Line;
import dpq2.conv.inet: binaryValueAs, InetAddress, CidrAddress;
import dpq2.conv.arrays : binaryValueAs;

import vibe.data.json: Json, parseJsonString;
Expand Down
7 changes: 7 additions & 0 deletions src/dpq2/conv/to_variant.d
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module dpq2.conv.to_variant;
import dpq2.value;
import dpq2.oids: OidType;
import dpq2.result: ArrayProperties;
import dpq2.conv.inet: InetAddress, CidrAddress;
import dpq2.conv.to_d_types;
import dpq2.conv.numeric: rawValueToNumeric;
import dpq2.conv.time: TimeStampUTC;
Expand Down Expand Up @@ -98,6 +99,12 @@ Variant toVariant(bool isNullablePayload = true)(in Value v) @safe
case Date: return retVariant!PGdate;
case DateArray: return retArray__!PGdate;

case HostAddress: return retVariant!InetAddress;
case HostAddressArray: return retArray__!InetAddress;

case NetworkAddress: return retVariant!CidrAddress;
case NetworkAddressArray: return retArray__!CidrAddress;

case Time: return retVariant!PGtime_without_time_zone;
case TimeArray: return retArray__!PGtime_without_time_zone;

Expand Down
Loading
Loading