Add Cache to minidns

Florian Schmaus created

Change summary

build.gradle                                     | 12 ++
src/main/java/de/measite/minidns/Client.java     | 43 +++++++---
src/main/java/de/measite/minidns/DNSMessage.java |  3 
src/main/java/de/measite/minidns/Question.java   | 73 +++++++++++------
4 files changed, 88 insertions(+), 43 deletions(-)

Detailed changes

build.gradle 🔗

@@ -24,6 +24,18 @@ if (isSnapshot) {
 	version += '-SNAPSHOT'
 }
 
+repositories {
+	mavenLocal()
+	mavenCentral()
+	maven {
+		url 'https://oss.sonatype.org/content/repositories/snapshots/'
+	}
+}
+
+dependencies {
+	compile 'org.igniterealtime.jxmpp:jxmpp-util-cache:0.1.0-alpha1-SNAPSHOT'
+}
+
 jar {
 	manifest {
 		instruction 'Implementation-GitRevision:', project.ext.gitCommit

src/main/java/de/measite/minidns/Client.java 🔗

@@ -17,6 +17,8 @@ import java.util.Random;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import org.jxmpp.util.cache.ExpirationCache;
+
 import de.measite.minidns.Record.CLASS;
 import de.measite.minidns.Record.TYPE;
 
@@ -28,6 +30,9 @@ public class Client {
 
     private static final Logger LOGGER = Logger.getLogger(Client.class.getName());
 
+    protected static final ExpirationCache<Question, DNSMessage> cache = new ExpirationCache<Question, DNSMessage>(
+            10, 1000 * 60 * 60 * 24);
+
     /**
      * The internal random class for sequence generation.
      */
@@ -67,10 +72,7 @@ public class Client {
     public DNSMessage query(String name, TYPE type, CLASS clazz, String host, int port)
         throws IOException
     {
-        Question q = new Question();
-        q.setClazz(clazz);
-        q.setType(type);
-        q.setName(name);
+        Question q = new Question(name, type, clazz);
         return query(q, host, port);
     }
 
@@ -86,10 +88,7 @@ public class Client {
     public DNSMessage query(String name, TYPE type, CLASS clazz, String host)
         throws IOException
     {
-        Question q = new Question();
-        q.setClazz(clazz);
-        q.setType(type);
-        q.setName(name);
+        Question q = new Question(name, type, clazz);
         return query(q, host);
     }
 
@@ -102,10 +101,7 @@ public class Client {
      */
     public DNSMessage query(String name, TYPE type, CLASS clazz)
     {
-        Question q = new Question();
-        q.setClazz(clazz);
-        q.setType(type);
-        q.setName(name);
+        Question q = new Question(name, type, clazz);
         return query(q);
     }
 
@@ -127,6 +123,10 @@ public class Client {
      * @throws IOException On IOErrors.
      */
     public DNSMessage query(Question q, String host, int port) throws IOException {
+        DNSMessage dnsMessage = cache.get(q);
+        if (dnsMessage != null) {
+            return dnsMessage;
+        }
         DNSMessage message = new DNSMessage();
         message.setQuestions(new Question[]{q});
         message.setRecursionDesired(true);
@@ -139,10 +139,16 @@ public class Client {
             socket.send(packet);
             packet = new DatagramPacket(new byte[bufferSize], bufferSize);
             socket.receive(packet);
-            DNSMessage dnsMessage = DNSMessage.parse(packet.getData());
+            dnsMessage = DNSMessage.parse(packet.getData());
             if (dnsMessage.getId() != message.getId()) {
                 return null;
             }
+            for (Record record : dnsMessage.getAnswers()) {
+                if (record.isAnswer(q)) {
+                    cache.put(q, dnsMessage, record.ttl);
+                    break;
+                }
+            }
             return dnsMessage;
         }
     }
@@ -152,10 +158,19 @@ public class Client {
      * @param q The question section of the DNS query.
      */
     public DNSMessage query(Question q) {
+        // While this query method does in fact re-use query(Question, String)
+        // we still do a cache lookup here in order to avoid unnecessary
+        // findDNS()calls, which are expensive on Android. Note that we do not
+        // put the results back into the Cache, as this is already done by
+        // query(Question, String).
+        DNSMessage message = cache.get(q);
+        if (message != null) {
+            return message;
+        }
         String dnsServer[] = findDNS();
         for (String dns : dnsServer) {
             try {
-                DNSMessage message = query(q, dns);
+                message = query(q, dns);
                 if (message == null) {
                     continue;
                 }

src/main/java/de/measite/minidns/DNSMessage.java 🔗

@@ -416,8 +416,7 @@ public class DNSMessage {
         int additionalResourceRecordCount = dis.readUnsignedShort();
         message.questions = new Question[questionCount];
         while (questionCount-- > 0) {
-            Question q = new Question();
-            q.parse(dis, data);
+            Question q = Question.parse(dis, data);
             message.questions[questionCount] = q;
         }
         message.answers = new Record[answerCount];

src/main/java/de/measite/minidns/Question.java 🔗

@@ -11,52 +11,71 @@ import de.measite.minidns.util.NameUtil;
 
 public class Question {
 
-    private String name;
+    private final String name;
 
-    private TYPE type;
+    private final TYPE type;
 
-    private CLASS clazz = CLASS.IN;
+    private final CLASS clazz;
 
-    public TYPE getType() {
-        return type;
-    }
+    private byte[] byteArray;
 
-    public void setType(TYPE type) {
+    public Question(String name, TYPE type, CLASS clazz) {
+        this.name = name;
         this.type = type;
+        this.clazz = clazz;
     }
 
-    public CLASS getClazz() {
-        return clazz;
+    public TYPE getType() {
+        return type;
     }
 
-    public void setClazz(CLASS clazz) {
-        this.clazz = clazz;
+    public CLASS getClazz() {
+        return clazz;
     }
 
     public String getName() {
         return name;
     }
 
-    public void setName(String name) {
-        this.name = name;
+    public static Question parse(DataInputStream dis, byte[] data) throws IOException {
+        String name = NameUtil.parse(dis, data);
+        TYPE type = TYPE.getType(dis.readUnsignedShort());
+        CLASS clazz = CLASS.getClass(dis.readUnsignedShort());
+        return new Question (name, type, clazz);
     }
 
-    public void parse(DataInputStream dis, byte[] data) throws IOException {
-        this.name = NameUtil.parse(dis, data);
-        this.type = TYPE.getType(dis.readUnsignedShort());
-        this.clazz = CLASS.getClass(dis.readUnsignedShort());
+    public byte[] toByteArray() {
+        if (byteArray == null) {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
+            DataOutputStream dos = new DataOutputStream(baos);
+
+            try {
+                dos.write(NameUtil.toByteArray(this.name));
+                dos.writeShort(type.getValue());
+                dos.writeShort(clazz.getValue());
+                dos.flush();
+            } catch (IOException e) {
+                // Should never happen
+                throw new IllegalStateException(e);
+            }
+            byteArray = baos.toByteArray();
+        }
+        return byteArray;
     }
 
-    public byte[] toByteArray() throws IOException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
-        DataOutputStream dos = new DataOutputStream(baos);
-
-        dos.write(NameUtil.toByteArray(this.name));
-        dos.writeShort(type.getValue());
-        dos.writeShort(clazz.getValue());
-
-        dos.flush();
-        return baos.toByteArray();
+    @Override
+    public int hashCode() {
+        return toByteArray().hashCode();
     }
 
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (!(other instanceof Question)) {
+            return false;
+        }
+        return this.hashCode() == other.hashCode();
+    }
 }