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
6 changes: 5 additions & 1 deletion vertx-pg-client/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ The *Reactive Postgres Client* currently supports the following data types
|`io.vertx.pgclient.data.Money[]`
|✔

|`CIDR`
|`io.vertx.pgclient.data.Cidr`
|✔

|`PATH`
|`i.r.p.data.Path`
|✔
Expand Down Expand Up @@ -270,7 +274,7 @@ Note: PostgreSQL JSON and JSONB types are represented by the following Java type

The following types

_MONEY_, _BIT_, _VARBIT_, _MACADDR_, _CIDR_, _MACADDR8_,
_MONEY_, _BIT_, _VARBIT_, _MACADDR_, _MACADDR8_,
_XML_, _HSTORE_, _OID_,
_VOID_

Expand Down
40 changes: 40 additions & 0 deletions vertx-pg-client/src/main/java/io/vertx/pgclient/data/Cidr.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.vertx.pgclient.data;

import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;

/**
* A PostgreSQL <a href="https://www.postgresql.org/docs/current/datatype-net-types.html#DATATYPE-CIDR">classless internet domain routing</a>.
*/
public class Cidr {
private InetAddress address;
private Integer netmask;

public InetAddress getAddress(){
return address;
}
public Cidr setAddress(InetAddress address) {
if (address instanceof Inet4Address || address instanceof Inet6Address) {
this.address = address;
} else {
throw new IllegalArgumentException("Invalid IP address type");
}
return this;
}

public Integer getNetmask(){
return netmask;
}

public Cidr setNetmask(Integer netmask) {
if (netmask != null && ((getAddress() instanceof Inet4Address && (netmask < 0 || netmask > 32)) ||
(getAddress() instanceof Inet6Address && (netmask < 0 || netmask > 128)))) {
throw new IllegalArgumentException("Invalid netmask: " + netmask);
}
this.netmask = netmask;
return this;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import io.vertx.pgclient.data.Line;
import io.vertx.pgclient.data.LineSegment;
import io.vertx.pgclient.data.Money;
import io.vertx.pgclient.data.Cidr;
import io.vertx.sqlclient.Tuple;
import io.vertx.sqlclient.data.Numeric;
import io.vertx.pgclient.data.Interval;
Expand Down Expand Up @@ -97,7 +98,7 @@ public enum DataType {
MACADDR(829, true, Object.class, JDBCType.OTHER),
INET(869, true, Inet.class, JDBCType.OTHER),
INET_ARRAY(1041, true, Inet[].class, JDBCType.OTHER),
CIDR(650, true, Object.class, JDBCType.OTHER),
CIDR(650, true, Cidr.class, JDBCType.OTHER),
MACADDR8(774, true, Object[].class, JDBCType.OTHER),
UUID(2950, true, UUID.class, JDBCType.OTHER, Tuple::getUUID),
UUID_ARRAY(2951, true, UUID[].class, JDBCType.OTHER, Tuple::getArrayOfUUIDs),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,9 @@ public static void encodeBinary(DataType id, Object value, ByteBuf buff) {
case MONEY_ARRAY:
binaryEncodeArray((Money[]) value, DataType.MONEY, buff);
break;
case CIDR:
binaryEncodeCidr((Cidr) value, buff);
break;
default:
logger.debug("Data type " + id + " does not support binary encoding");
defaultEncodeBinary(value, buff);
Expand Down Expand Up @@ -501,6 +504,8 @@ public static Object decodeBinary(DataType id, int index, int len, ByteBuf buff)
return binaryDecodeMoney(index, len, buff);
case MONEY_ARRAY:
return binaryDecodeArray(MONEY_ARRAY_FACTORY, DataType.MONEY, index, len, buff);
case CIDR:
return binaryDecodeCidr(index, len, buff);
default:
logger.debug("Data type " + id + " does not support binary decoding");
return defaultDecodeBinary(index, len, buff);
Expand Down Expand Up @@ -641,6 +646,8 @@ public static Object decodeText(DataType id, int index, int len, ByteBuf buff) {
return textDecodeMoney(index, len, buff);
case MONEY_ARRAY:
return textDecodeArray(MONEY_ARRAY_FACTORY, DataType.MONEY, index, len, buff);
case CIDR:
return textDecodeCidr(index, len, buff);
default:
return defaultDecodeText(index, len, buff);
}
Expand Down Expand Up @@ -1713,4 +1720,85 @@ private static <T> void textEncodeArray(T[] values, DataType type, ByteBuf buff)
}
buff.writeByte('}');
}

private static Cidr binaryDecodeCidr(int index, int len, ByteBuf buff){
byte family = buff.getByte(index);
byte netmask = buff.getByte(index+1);
Integer val;
int size = buff.getByte(index+3);
byte[] data = new byte[size];
buff.getBytes(index+4,data);
InetAddress address;

switch (family){
case 2:
case 3:
// IPV4 and IPV6
try {
address = InetAddress.getByAddress(data);
}catch (UnknownHostException e){
throw new DecoderException(e);
}
break;
default:
throw new DecoderException("Invalid IP family: " + family);
}
val = Byte.toUnsignedInt(netmask);
return new Cidr().setAddress(address).setNetmask(val);
}

private static void binaryEncodeCidr(Cidr value, ByteBuf buff) {
InetAddress address = value.getAddress();
byte family;
byte[] data;
int netmask;

if (address instanceof Inet6Address) {
family = 3;
Inet6Address inet6Address = (Inet6Address) address;
data = inet6Address.getAddress();
netmask = (value.getNetmask() == null) ? 128 : value.getNetmask();
} else if (address instanceof Inet4Address) {
family = 2;
Inet4Address inet4Address = (Inet4Address) address;
data = inet4Address.getAddress();
netmask = (value.getNetmask() == null) ? 32 : value.getNetmask();
} else {
throw new DecoderException("Invalid inet address");
}

buff.writeByte(family);
buff.writeByte(netmask);
buff.writeByte(0); // INET
buff.writeByte(data.length);
buff.writeBytes(data);
}

private static Cidr textDecodeCidr(int index, int len, ByteBuf buff) {
Cidr cidr = new Cidr();
int sepIdx = buff.indexOf(index, index + len, (byte) '/');
String s;

if (sepIdx == -1) {
s = textdecodeTEXT(index, len, buff);
} else {
s = textdecodeTEXT(index, sepIdx - index, buff);
String t = textdecodeTEXT(sepIdx + 1, len - (sepIdx + 1 - index), buff);
try {
int netmask = Integer.parseInt(t);
cidr.setNetmask(netmask);
} catch (NumberFormatException e) {
throw new DecoderException(e);
}
}

try {
InetAddress v = InetAddress.getByName(s);
cidr.setAddress(v);
} catch (UnknownHostException e) {
throw new DecoderException(e);
}

return cidr;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package io.vertx.pgclient.data;

import io.vertx.ext.unit.TestContext;
import io.vertx.pgclient.PgConnection;
import io.vertx.sqlclient.*;
import org.junit.Test;

import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.function.BiFunction;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;

public class CidrCodecTest extends DataTypeTestBase{

@Test
public void testValidIPv4() throws Exception {
InetAddress address = InetAddress.getByName("192.168.1.1");
Cidr cidr = new Cidr();
cidr.setAddress(address);
cidr.setNetmask(24);
assertEquals(address, cidr.getAddress());
assertEquals(Integer.valueOf(24), cidr.getNetmask());
}

@Test
public void testValidIPv6() throws Exception {
InetAddress address = InetAddress.getByName("fe80::f03c:91ff:feae:e944");
Cidr cidr = new Cidr();
cidr.setAddress(address);
cidr.setNetmask(64);
assertEquals(address, cidr.getAddress());
assertEquals(Integer.valueOf(64), cidr.getNetmask());
}

@Test
public void testInvalidNetmaskIPv4() throws Exception {
InetAddress address = InetAddress.getByName("192.168.1.1");
Cidr cidr = new Cidr();
cidr.setAddress(address);
assertThrows(IllegalArgumentException.class, () -> cidr.setNetmask(33));
}

@Test
public void testInvalidNetmaskIPv6() throws Exception {
InetAddress address = InetAddress.getByName("fe80::f03c:91ff:feae:e944");
Cidr cidr = new Cidr();
cidr.setAddress(address);
assertThrows(IllegalArgumentException.class, () -> cidr.setNetmask(129));
}

@Test
public void testBinaryDecodeCIDR(TestContext ctx) throws Exception {
testDecodeCIDR(ctx, SqlClient::preparedQuery);
}

private void testDecodeCIDR(TestContext ctx, BiFunction<SqlClient, String, Query<RowSet<Row>>> a) throws Exception {
InetAddress addr1 = Inet4Address.getByName("128.0.0.0");
InetAddress addr2 = Inet6Address.getByName("2001:0db8:1234:0000:0000:0000:0000:0000");
PgConnection.connect(vertx, options).onComplete(ctx.asyncAssertSuccess(conn -> {
a.apply(conn, "SELECT " +
"'128.0.0.0'::CIDR," +
"'128.0.0.0/4'::CIDR," +
"'2001:0db8:1234:0000:0000:0000:0000:0000'::CIDR," +
"'2001:0db8:1234:0000:0000:0000:0000:0000/56'::CIDR")
.execute()
.onComplete(ctx.asyncAssertSuccess(rows -> {
ctx.assertEquals(1, rows.size());
Row row = rows.iterator().next();
Cidr v1 = (Cidr) row.getValue(0);
Cidr v2 = (Cidr) row.getValue(1);
Cidr v3 = (Cidr) row.getValue(2);
Cidr v4 = (Cidr) row.getValue(3);
ctx.assertEquals(addr1, v1.getAddress());
ctx.assertEquals(32,v1.getNetmask());
ctx.assertEquals(addr1, v2.getAddress());
ctx.assertEquals(4, v2.getNetmask());
ctx.assertEquals(addr2, v3.getAddress());
ctx.assertEquals(128, v3.getNetmask());
ctx.assertEquals(addr2, v4.getAddress());
ctx.assertEquals(56, v4.getNetmask());
}));
}));
}

@Test
public void testBinaryEncodeCIDR(TestContext ctx) throws Exception {
InetAddress addr1 = Inet4Address.getByName("128.0.0.0");
InetAddress addr2 = Inet6Address.getByName("2001:0db8:1234:0000:0000:0000:0000:0000");
PgConnection.connect(vertx, options).onComplete(ctx.asyncAssertSuccess(conn -> {
conn
.preparedQuery("SELECT ($1::CIDR)::VARCHAR, ($2::CIDR)::VARCHAR, ($3::CIDR)::VARCHAR, ($4::CIDR)::VARCHAR")
.execute(Tuple.of(
new Cidr().setAddress(addr1),
new Cidr().setAddress(addr1).setNetmask(4),
new Cidr().setAddress(addr2),
new Cidr().setAddress(addr2).setNetmask(56)
))
.onComplete(ctx.asyncAssertSuccess(rows -> {
ctx.assertEquals(1, rows.size());
Row row = rows.iterator().next();
String v1 = row.getString(0);
String v2 = row.getString(1);
String v3 = row.getString(2);
String v4 = row.getString(3);
ctx.assertEquals("128.0.0.0/32", v1);
ctx.assertEquals("128.0.0.0/4", v2);
ctx.assertEquals("2001:db8:1234::/128", v3);
ctx.assertEquals("2001:db8:1234::/56", v4);
}));
}));
}

}