diff --git a/vertx-pg-client/README.adoc b/vertx-pg-client/README.adoc
index 2c586afb6..7010b4e27 100644
--- a/vertx-pg-client/README.adoc
+++ b/vertx-pg-client/README.adoc
@@ -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`
|✔
@@ -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_
diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/data/Cidr.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/data/Cidr.java
new file mode 100644
index 000000000..ac85bc0df
--- /dev/null
+++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/data/Cidr.java
@@ -0,0 +1,40 @@
+package io.vertx.pgclient.data;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+
+/**
+ * A PostgreSQL classless internet domain routing.
+ */
+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;
+ }
+
+
+}
diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataType.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataType.java
index 0ac278820..3c755d53c 100644
--- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataType.java
+++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataType.java
@@ -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;
@@ -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),
diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataTypeCodec.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataTypeCodec.java
index 975223562..d12b37f58 100644
--- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataTypeCodec.java
+++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataTypeCodec.java
@@ -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);
@@ -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);
@@ -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);
}
@@ -1713,4 +1720,85 @@ private static 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;
+ }
}
diff --git a/vertx-pg-client/src/test/java/io/vertx/pgclient/data/CidrCodecTest.java b/vertx-pg-client/src/test/java/io/vertx/pgclient/data/CidrCodecTest.java
new file mode 100644
index 000000000..c35c69309
--- /dev/null
+++ b/vertx-pg-client/src/test/java/io/vertx/pgclient/data/CidrCodecTest.java
@@ -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>> 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);
+ }));
+ }));
+ }
+
+}