Skip to content

Commit

Permalink
CIDR datatype support in pgclient
Browse files Browse the repository at this point in the history
  • Loading branch information
Ayan Koche1 authored and vietj committed May 10, 2024
1 parent d63fc11 commit 7424c4b
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 2 deletions.
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 @@ -362,6 +362,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 @@ -499,6 +502,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 @@ -639,6 +644,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 @@ -1710,4 +1717,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);
}));
}));
}

}

0 comments on commit 7424c4b

Please sign in to comment.