Skip to content
Permalink
Browse files

Add method to register private rr types (#93)

* Add method to register private rr types

* Rename proto and add remove method for Type Mmenomic

Closes #94
  • Loading branch information
ibauersachs committed Mar 17, 2020
1 parent ebc20c3 commit 267118fa63df39bb03969d147749c650ceaa81b5
@@ -7,7 +7,7 @@
<groupId>dnsjava</groupId>
<artifactId>dnsjava</artifactId>
<packaging>bundle</packaging>
<version>3.0.2-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
<name>dnsjava</name>
<description>dnsjava is an implementation of DNS in Java. It supports all defined record types (including the DNSSEC
types), and unknown types. It can be used for queries, zone transfers, and dynamic updates. It includes a cache
@@ -100,6 +100,16 @@ public void add(int val, String str) {
values.put(val, str);
}

/**
* Removes both the numeric value and its text representation, including all aliases.
*
* @param val The numeric value
*/
public void remove(int val) {
values.remove(val);
strings.entrySet().removeIf(entry -> entry.getValue() == val);
}

/**
* Defines an additional text representation of a numeric value. This will be used by getValue(),
* but not getText().
@@ -113,6 +123,16 @@ public void addAlias(int val, String str) {
strings.put(str, val);
}

/**
* Removes an additional text representation of a numeric value.
*
* @param str The text string
*/
public void removeAlias(String str) {
str = sanitize(str);
strings.remove(str);
}

/**
* Copies all mnemonics from one table into another.
*
@@ -44,9 +44,9 @@ protected Record() {}
private static Record getEmptyRecord(Name name, int type, int dclass, long ttl, boolean hasData) {
Record rec;
if (hasData) {
Supplier<Record> proto = Type.getProto(type);
if (proto != null) {
rec = proto.get();
Supplier<Record> factory = Type.getFactory(type);
if (factory != null) {
rec = factory.get();
} else {
rec = new UNKRecord();
}
@@ -298,27 +298,42 @@
public static final int DLV = 32769;

private static class TypeMnemonic extends Mnemonic {
private HashMap<Integer, Supplier<Record>> objects;
private HashMap<Integer, Supplier<Record>> factories;

public TypeMnemonic() {
super("Type", CASE_UPPER);
setPrefix("TYPE");
objects = new HashMap<>();
factories = new HashMap<>();
}

public void add(int val, String str, Supplier<Record> proto) {
public void add(int val, String str, Supplier<Record> factory) {
super.add(val, str);
objects.put(val, proto);
factories.put(val, factory);
}

public void replace(int val, String str, Supplier<Record> factory) {
int oldVal = getValue(str);
if (oldVal != -1) {
if (oldVal != val) {
throw new IllegalArgumentException(
"mnemnonic \"" + str + "\" already used by type " + oldVal);
} else {
remove(val);
factories.remove(val);
}
}

add(val, str, factory);
}

@Override
public void check(int val) {
Type.check(val);
}

public Supplier<Record> getProto(int val) {
public Supplier<Record> getFactory(int val) {
check(val);
return objects.get(val);
return factories.get(val);
}
}

@@ -431,6 +446,22 @@ public static void check(int val) {
}
}

/**
* Registers a new record type along with the respective factory. This allows the reimplementation
* of existing types, the implementation of new types not (yet) supported by the library or the
* implementation of "private use" record types. Note that the method is not synchronized and its
* use may interfere with the creation of records in a multi-threaded environment. The method must
* be used with care in order to avoid unexpected behaviour.
*
* @param val the numeric representation of the record type
* @param str the textual representation of the record type
* @param factory the factory; {@code null} may be used if there is no implementation available.
* In this case, records of the type will be represented by the {@link UNKRecord} class
*/
public static void register(int val, String str, Supplier<Record> factory) {
types.replace(val, str, factory);
}

/**
* Converts a numeric Type into a String
*
@@ -466,8 +497,8 @@ public static int value(String s) {
return value(s, false);
}

static Supplier<Record> getProto(int val) {
return types.getProto(val);
static Supplier<Record> getFactory(int val) {
return types.getFactory(val);
}

/** Is this type valid for a record (a non-meta type)? */
@@ -851,7 +851,7 @@ void checkName() throws TextParseException {
@Test
void testAllTypesHaveNoArgConstructor() {
for (int i = 1; i < 65535; i++) {
Supplier<Record> proto = Type.getProto(i);
Supplier<Record> proto = Type.getFactory(i);
if (proto != null) {
try {
assertNotNull(proto.get());
@@ -36,9 +36,13 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.junit.jupiter.api.Test;

class TypeTest {
@@ -78,4 +82,85 @@ void isRR() {
assertTrue(Type.isRR(Type.CNAME));
assertFalse(Type.isRR(Type.IXFR));
}

private static final int MYTXT = 65534;
private static final String MYTXTName = "MYTXT";

private static class MYTXTRecord extends TXTBase {
MYTXTRecord() {}

public MYTXTRecord(Name name, int dclass, long ttl, List<String> strings) {
super(name, MYTXT, dclass, ttl, strings);
}

public MYTXTRecord(Name name, int dclass, long ttl, String string) {
super(name, MYTXT, dclass, ttl, string);
}
}

private static class TXTRecordReplacement extends TXTBase {
TXTRecordReplacement() {}

public TXTRecordReplacement(Name name, int dclass, long ttl, List<String> strings) {
super(name, Type.TXT, dclass, ttl, strings);
}

public TXTRecordReplacement(Name name, int dclass, long ttl, String string) {
super(name, Type.TXT, dclass, ttl, string);
}
}

@Test
void checkCustomRecords() throws Exception {
// test "private use" record

Type.register(MYTXT, MYTXTName, MYTXTRecord::new);
Name testOwner = Name.fromConstantString("example.");
MYTXTRecord testRecord = new MYTXTRecord(testOwner, DClass.IN, 3600, "hello world");

byte[] wireData = testRecord.toWire(Section.ANSWER);
Record record = Record.fromWire(new DNSInput(wireData), Section.ANSWER, false);
assertEquals(MYTXTRecord.class, record.getClass());
assertEquals(MYTXT, record.getType());

byte[] textData = testRecord.toString().getBytes(StandardCharsets.US_ASCII);
Master m = new Master(new ByteArrayInputStream(textData));
record = m.nextRecord();
assertNotNull(record);
assertEquals(MYTXTRecord.class, record.getClass());
assertEquals(MYTXT, record.getType());
m.close();

Type.register(MYTXT, MYTXTName, null);
record = Record.fromWire(new DNSInput(wireData), Section.ANSWER, false);
assertEquals(UNKRecord.class, record.getClass());
assertEquals(MYTXT, record.getType());

// test implementation replacement

try {
assertThrows(
IllegalArgumentException.class,
() -> Type.register(Type.TXT, "SOA", TXTRecordReplacement::new));

Type.register(Type.TXT, "TXT", TXTRecordReplacement::new);
TXTRecord testRecord2 = new TXTRecord(testOwner, DClass.IN, 3600, "howdy");
wireData = testRecord2.toWire(Section.ANSWER);
record = Record.fromWire(new DNSInput(wireData), Section.ANSWER, false);
assertEquals(TXTRecordReplacement.class, record.getClass());
assertEquals(Type.TXT, record.getType());

byte[] textData2 = testRecord2.toString().getBytes(StandardCharsets.US_ASCII);
m = new Master(new ByteArrayInputStream(textData2));
record = m.nextRecord();
assertNotNull(record);
assertEquals(TXTRecordReplacement.class, record.getClass());
assertEquals(Type.TXT, record.getType());
m.close();

} finally {
// restore default implementation as needed by other tests
Type.register(Type.TXT, "TXT", TXTRecord::new);
}
}
}

0 comments on commit 267118f

Please sign in to comment.
You can’t perform that action at this time.