Skip to content

Commit 05e0f0a

Browse files
committed
Make Record serializable again
Instead of relying on the unstable default Java serialization of each record type, this uses a proxy object. The proxy object only contains the DNS wire data of a record, which is guaranteed to be stable. This avoids all concerns about compatibility when Record classes evolve and with inheritance. Closes #132 See #114
1 parent 4eff203 commit 05e0f0a

File tree

3 files changed

+58
-12
lines changed

3 files changed

+58
-12
lines changed

src/main/java/org/xbill/DNS/Record.java

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55

66
import java.io.ByteArrayOutputStream;
77
import java.io.IOException;
8+
import java.io.InvalidObjectException;
9+
import java.io.ObjectInputStream;
10+
import java.io.ObjectStreamException;
11+
import java.io.Serializable;
812
import java.text.DecimalFormat;
913
import java.util.Arrays;
1014
import java.util.function.Supplier;
15+
import lombok.extern.slf4j.Slf4j;
1116
import org.xbill.DNS.utils.base16;
1217

1318
/**
@@ -16,9 +21,11 @@
1621
*
1722
* @author Brian Wellington
1823
*/
19-
public abstract class Record implements Cloneable, Comparable<Record> {
24+
@Slf4j
25+
public abstract class Record implements Cloneable, Comparable<Record>, Serializable {
2026
protected Name name;
21-
protected int type, dclass;
27+
protected int type;
28+
protected int dclass;
2229
protected long ttl;
2330

2431
private static final DecimalFormat byteFormat = new DecimalFormat();
@@ -27,6 +34,23 @@ public abstract class Record implements Cloneable, Comparable<Record> {
2734
byteFormat.setMinimumIntegerDigits(3);
2835
}
2936

37+
private static class RecordSerializationProxy implements Serializable {
38+
private static final long serialVersionUID = 1434159920070152561L;
39+
private final byte[] wireData;
40+
41+
RecordSerializationProxy(Record r) {
42+
wireData = r.toWire(Section.ANSWER);
43+
}
44+
45+
protected Object readResolve() throws ObjectStreamException {
46+
try {
47+
return Record.fromWire(wireData, Section.ANSWER);
48+
} catch (IOException e) {
49+
throw new InvalidObjectException(e.getMessage());
50+
}
51+
}
52+
}
53+
3054
protected Record() {}
3155

3256
/** @since 3.1 */
@@ -43,6 +67,15 @@ protected Record(Name name, int type, int dclass, long ttl) {
4367
this.ttl = ttl;
4468
}
4569

70+
Object writeReplace() {
71+
log.trace("Creating proxy object for serialization");
72+
return new RecordSerializationProxy(this);
73+
}
74+
75+
private void readObject(ObjectInputStream ois) throws InvalidObjectException {
76+
throw new InvalidObjectException("Use RecordSerializationProxy");
77+
}
78+
4679
private static Record getEmptyRecord(Name name, int type, int dclass, long ttl, boolean hasData) {
4780
Record rec;
4881
if (hasData) {
@@ -169,7 +202,8 @@ public static Record newRecord(Name name, int type, int dclass) {
169202
}
170203

171204
static Record fromWire(DNSInput in, int section, boolean isUpdate) throws IOException {
172-
int type, dclass;
205+
int type;
206+
int dclass;
173207
long ttl;
174208
int length;
175209
Name name;
@@ -404,12 +438,7 @@ protected static String byteArrayToString(byte[] array, boolean quote) {
404438

405439
/** Converts a byte array into the unknown RR format. */
406440
protected static String unknownToString(byte[] data) {
407-
StringBuilder sb = new StringBuilder();
408-
sb.append("\\# ");
409-
sb.append(data.length);
410-
sb.append(" ");
411-
sb.append(base16.toString(data));
412-
return sb.toString();
441+
return "\\# " + data.length + " " + base16.toString(data);
413442
}
414443

415444
/**
@@ -453,8 +482,7 @@ public static Record fromString(
453482
rec.rdataFromString(st, origin);
454483
t = st.get();
455484
if (t.type != Tokenizer.EOL && t.type != Tokenizer.EOF) {
456-
throw st.exception(
457-
"unexpected tokens at end of record (wanted EOL/EOF, got " + t.toString() + ")");
485+
throw st.exception("unexpected tokens at end of record (wanted EOL/EOF, got " + t + ")");
458486
}
459487
return rec;
460488
}

src/test/java/org/xbill/DNS/ARecordTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@
4040
import static org.junit.jupiter.api.Assertions.assertNull;
4141
import static org.junit.jupiter.api.Assertions.assertThrows;
4242

43+
import java.io.ByteArrayInputStream;
44+
import java.io.ByteArrayOutputStream;
4345
import java.io.IOException;
46+
import java.io.ObjectInputStream;
47+
import java.io.ObjectOutputStream;
4448
import java.net.InetAddress;
4549
import java.net.UnknownHostException;
4650
import org.junit.jupiter.api.BeforeEach;
@@ -135,4 +139,19 @@ void rrToWire() {
135139
ar.rrToWire(dout, null, false);
136140
assertArrayEquals(m_addr_bytes, dout.toByteArray());
137141
}
142+
143+
@Test
144+
void testSerializable() throws IOException, ClassNotFoundException {
145+
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
146+
try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
147+
ARecord expected = new ARecord(Name.root, DClass.IN, 60, m_addr);
148+
oos.writeObject(expected);
149+
try (ObjectInputStream ois =
150+
new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
151+
Record actual = (Record) ois.readObject();
152+
assertEquals(expected, actual);
153+
}
154+
}
155+
}
156+
}
138157
}

src/test/java/org/xbill/DNS/RecordTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,6 @@ void fromString_invalid() throws IOException {
552552
int t = Type.A;
553553
int d = DClass.IN;
554554
int ttl = 0xABE99;
555-
InetAddress addr = InetAddress.getByName("191.234.43.10");
556555

557556
assertThrows(
558557
RelativeNameException.class,

0 commit comments

Comments
 (0)