diff --git a/src/main/java/org/xbill/DNS/Record.java b/src/main/java/org/xbill/DNS/Record.java index 7f942a4d..7ea6d21d 100644 --- a/src/main/java/org/xbill/DNS/Record.java +++ b/src/main/java/org/xbill/DNS/Record.java @@ -5,9 +5,14 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectStreamException; +import java.io.Serializable; import java.text.DecimalFormat; import java.util.Arrays; import java.util.function.Supplier; +import lombok.extern.slf4j.Slf4j; import org.xbill.DNS.utils.base16; /** @@ -16,9 +21,11 @@ * * @author Brian Wellington */ -public abstract class Record implements Cloneable, Comparable { +@Slf4j +public abstract class Record implements Cloneable, Comparable, Serializable { protected Name name; - protected int type, dclass; + protected int type; + protected int dclass; protected long ttl; private static final DecimalFormat byteFormat = new DecimalFormat(); @@ -27,6 +34,25 @@ public abstract class Record implements Cloneable, Comparable { byteFormat.setMinimumIntegerDigits(3); } + private static class RecordSerializationProxy implements Serializable { + private static final long serialVersionUID = 1434159920070152561L; + private final byte[] wireData; + private final boolean isEmpty; + + RecordSerializationProxy(Record r) { + this.isEmpty = r instanceof EmptyRecord; + wireData = r.toWire(isEmpty ? Section.QUESTION : Section.ANSWER); + } + + protected Object readResolve() throws ObjectStreamException { + try { + return Record.fromWire(wireData, isEmpty ? Section.QUESTION : Section.ANSWER); + } catch (IOException e) { + throw new InvalidObjectException(e.getMessage()); + } + } + } + protected Record() {} /** @since 3.1 */ @@ -43,6 +69,15 @@ protected Record(Name name, int type, int dclass, long ttl) { this.ttl = ttl; } + Object writeReplace() { + log.trace("Creating proxy object for serialization"); + return new RecordSerializationProxy(this); + } + + private void readObject(ObjectInputStream ois) throws InvalidObjectException { + throw new InvalidObjectException("Use RecordSerializationProxy"); + } + private static Record getEmptyRecord(Name name, int type, int dclass, long ttl, boolean hasData) { Record rec; if (hasData) { @@ -169,7 +204,8 @@ public static Record newRecord(Name name, int type, int dclass) { } static Record fromWire(DNSInput in, int section, boolean isUpdate) throws IOException { - int type, dclass; + int type; + int dclass; long ttl; int length; Name name; @@ -404,12 +440,7 @@ protected static String byteArrayToString(byte[] array, boolean quote) { /** Converts a byte array into the unknown RR format. */ protected static String unknownToString(byte[] data) { - StringBuilder sb = new StringBuilder(); - sb.append("\\# "); - sb.append(data.length); - sb.append(" "); - sb.append(base16.toString(data)); - return sb.toString(); + return "\\# " + data.length + " " + base16.toString(data); } /** @@ -453,8 +484,7 @@ public static Record fromString( rec.rdataFromString(st, origin); t = st.get(); if (t.type != Tokenizer.EOL && t.type != Tokenizer.EOF) { - throw st.exception( - "unexpected tokens at end of record (wanted EOL/EOF, got " + t.toString() + ")"); + throw st.exception("unexpected tokens at end of record (wanted EOL/EOF, got " + t + ")"); } return rec; } diff --git a/src/test/java/org/xbill/DNS/ARecordTest.java b/src/test/java/org/xbill/DNS/ARecordTest.java index 646e9d2d..c0df4323 100644 --- a/src/test/java/org/xbill/DNS/ARecordTest.java +++ b/src/test/java/org/xbill/DNS/ARecordTest.java @@ -40,7 +40,11 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.net.InetAddress; import java.net.UnknownHostException; import org.junit.jupiter.api.BeforeEach; @@ -135,4 +139,19 @@ void rrToWire() { ar.rrToWire(dout, null, false); assertArrayEquals(m_addr_bytes, dout.toByteArray()); } + + @Test + void testSerializable() throws IOException, ClassNotFoundException { + try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { + try (ObjectOutputStream oos = new ObjectOutputStream(bos)) { + ARecord expected = new ARecord(Name.root, DClass.IN, 60, m_addr); + oos.writeObject(expected); + try (ObjectInputStream ois = + new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()))) { + Record actual = (Record) ois.readObject(); + assertEquals(expected, actual); + } + } + } + } } diff --git a/src/test/java/org/xbill/DNS/RecordTest.java b/src/test/java/org/xbill/DNS/RecordTest.java index 7dee4e67..7903fb26 100644 --- a/src/test/java/org/xbill/DNS/RecordTest.java +++ b/src/test/java/org/xbill/DNS/RecordTest.java @@ -46,7 +46,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.net.InetAddress; import java.net.UnknownHostException; import java.time.Instant; @@ -552,7 +556,6 @@ void fromString_invalid() throws IOException { int t = Type.A; int d = DClass.IN; int ttl = 0xABE99; - InetAddress addr = InetAddress.getByName("191.234.43.10"); assertThrows( RelativeNameException.class, @@ -863,14 +866,36 @@ void testAllTypesHaveNoArgConstructor() { assertNotNull(proto.get()); } catch (Exception e) { fail( - "Record type " - + Type.string(i) - + " (" - + i - + ", " - + proto.getClass().getSimpleName() - + ")" - + " seems to have no or invalid 0arg ctor"); + String.format( + "Record type %s, (%d, %s) seems to have no or invalid 0arg ctor", + Type.string(i), i, proto.getClass().getSimpleName())); + } + } + } + } + + @Test + void testSerializable() throws IOException { + for (int i = 1; i < 65535; i++) { + try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { + try (ObjectOutputStream oos = new ObjectOutputStream(bos)) { + if (Type.getFactory(i) != null) { + Record expected = Record.newRecord(Name.root, i, DClass.IN); + try { + oos.writeObject(expected); + try (ObjectInputStream ois = + new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()))) { + Record actual = (Record) ois.readObject(); + assertEquals(expected, actual); + } + } catch (Exception e) { + fail( + String.format( + "Record type %s (%d, %s) failed to (de)serialize", + Type.string(i), i, expected.getClass().getSimpleName()), + e); + } + } } } }