summaryrefslogtreecommitdiff
path: root/src/javax/jmdns/impl/DNSOutgoing.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/javax/jmdns/impl/DNSOutgoing.java')
-rw-r--r--src/javax/jmdns/impl/DNSOutgoing.java451
1 files changed, 451 insertions, 0 deletions
diff --git a/src/javax/jmdns/impl/DNSOutgoing.java b/src/javax/jmdns/impl/DNSOutgoing.java
new file mode 100644
index 0000000..c3ba24e
--- /dev/null
+++ b/src/javax/jmdns/impl/DNSOutgoing.java
@@ -0,0 +1,451 @@
+// Copyright 2003-2005 Arthur van Hoff, Rick Blair
+// Licensed under Apache License version 2.0
+// Original license LGPL
+
+package javax.jmdns.impl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jmdns.impl.constants.DNSConstants;
+import javax.jmdns.impl.constants.DNSRecordClass;
+
+/**
+ * An outgoing DNS message.
+ *
+ * @author Arthur van Hoff, Rick Blair, Werner Randelshofer
+ */
+public final class DNSOutgoing extends DNSMessage {
+
+ public static class MessageOutputStream extends ByteArrayOutputStream {
+ private final DNSOutgoing _out;
+
+ private final int _offset;
+
+ /**
+ * Creates a new message stream, with a buffer capacity of the specified size, in bytes.
+ *
+ * @param size
+ * the initial size.
+ * @exception IllegalArgumentException
+ * if size is negative.
+ */
+ MessageOutputStream(int size, DNSOutgoing out) {
+ this(size, out, 0);
+ }
+
+ MessageOutputStream(int size, DNSOutgoing out, int offset) {
+ super(size);
+ _out = out;
+ _offset = offset;
+ }
+
+ void writeByte(int value) {
+ this.write(value & 0xFF);
+ }
+
+ void writeBytes(String str, int off, int len) {
+ for (int i = 0; i < len; i++) {
+ writeByte(str.charAt(off + i));
+ }
+ }
+
+ void writeBytes(byte data[]) {
+ if (data != null) {
+ writeBytes(data, 0, data.length);
+ }
+ }
+
+ void writeBytes(byte data[], int off, int len) {
+ for (int i = 0; i < len; i++) {
+ writeByte(data[off + i]);
+ }
+ }
+
+ void writeShort(int value) {
+ writeByte(value >> 8);
+ writeByte(value);
+ }
+
+ void writeInt(int value) {
+ writeShort(value >> 16);
+ writeShort(value);
+ }
+
+ void writeUTF(String str, int off, int len) {
+ // compute utf length
+ int utflen = 0;
+ for (int i = 0; i < len; i++) {
+ int ch = str.charAt(off + i);
+ if ((ch >= 0x0001) && (ch <= 0x007F)) {
+ utflen += 1;
+ } else {
+ if (ch > 0x07FF) {
+ utflen += 3;
+ } else {
+ utflen += 2;
+ }
+ }
+ }
+ // write utf length
+ writeByte(utflen);
+ // write utf data
+ for (int i = 0; i < len; i++) {
+ int ch = str.charAt(off + i);
+ if ((ch >= 0x0001) && (ch <= 0x007F)) {
+ writeByte(ch);
+ } else {
+ if (ch > 0x07FF) {
+ writeByte(0xE0 | ((ch >> 12) & 0x0F));
+ writeByte(0x80 | ((ch >> 6) & 0x3F));
+ writeByte(0x80 | ((ch >> 0) & 0x3F));
+ } else {
+ writeByte(0xC0 | ((ch >> 6) & 0x1F));
+ writeByte(0x80 | ((ch >> 0) & 0x3F));
+ }
+ }
+ }
+ }
+
+ void writeName(String name) {
+ writeName(name, true);
+ }
+
+ void writeName(String name, boolean useCompression) {
+ String aName = name;
+ while (true) {
+ int n = aName.indexOf('.');
+ if (n < 0) {
+ n = aName.length();
+ }
+ if (n <= 0) {
+ writeByte(0);
+ return;
+ }
+ String label = aName.substring(0, n);
+ if (useCompression && USE_DOMAIN_NAME_COMPRESSION) {
+ Integer offset = _out._names.get(aName);
+ if (offset != null) {
+ int val = offset.intValue();
+ writeByte((val >> 8) | 0xC0);
+ writeByte(val & 0xFF);
+ return;
+ }
+ _out._names.put(aName, Integer.valueOf(this.size() + _offset));
+ writeUTF(label, 0, label.length());
+ } else {
+ writeUTF(label, 0, label.length());
+ }
+ aName = aName.substring(n);
+ if (aName.startsWith(".")) {
+ aName = aName.substring(1);
+ }
+ }
+ }
+
+ void writeQuestion(DNSQuestion question) {
+ writeName(question.getName());
+ writeShort(question.getRecordType().indexValue());
+ writeShort(question.getRecordClass().indexValue());
+ }
+
+ void writeRecord(DNSRecord rec, long now) {
+ writeName(rec.getName());
+ writeShort(rec.getRecordType().indexValue());
+ writeShort(rec.getRecordClass().indexValue() | ((rec.isUnique() && _out.isMulticast()) ? DNSRecordClass.CLASS_UNIQUE : 0));
+ writeInt((now == 0) ? rec.getTTL() : rec.getRemainingTTL(now));
+
+ // We need to take into account the 2 size bytes
+ MessageOutputStream record = new MessageOutputStream(512, _out, _offset + this.size() + 2);
+ rec.write(record);
+ byte[] byteArray = record.toByteArray();
+
+ writeShort(byteArray.length);
+ write(byteArray, 0, byteArray.length);
+ }
+
+ }
+
+ /**
+ * This can be used to turn off domain name compression. This was helpful for tracking problems interacting with other mdns implementations.
+ */
+ public static boolean USE_DOMAIN_NAME_COMPRESSION = true;
+
+ Map<String, Integer> _names;
+
+ private int _maxUDPPayload;
+
+ private final MessageOutputStream _questionsBytes;
+
+ private final MessageOutputStream _answersBytes;
+
+ private final MessageOutputStream _authoritativeAnswersBytes;
+
+ private final MessageOutputStream _additionalsAnswersBytes;
+
+ private final static int HEADER_SIZE = 12;
+
+ /**
+ * Create an outgoing multicast query or response.
+ *
+ * @param flags
+ */
+ public DNSOutgoing(int flags) {
+ this(flags, true, DNSConstants.MAX_MSG_TYPICAL);
+ }
+
+ /**
+ * Create an outgoing query or response.
+ *
+ * @param flags
+ * @param multicast
+ */
+ public DNSOutgoing(int flags, boolean multicast) {
+ this(flags, multicast, DNSConstants.MAX_MSG_TYPICAL);
+ }
+
+ /**
+ * Create an outgoing query or response.
+ *
+ * @param flags
+ * @param multicast
+ * @param senderUDPPayload
+ * The sender's UDP payload size is the number of bytes of the largest UDP payload that can be reassembled and delivered in the sender's network stack.
+ */
+ public DNSOutgoing(int flags, boolean multicast, int senderUDPPayload) {
+ super(flags, 0, multicast);
+ _names = new HashMap<String, Integer>();
+ _maxUDPPayload = (senderUDPPayload > 0 ? senderUDPPayload : DNSConstants.MAX_MSG_TYPICAL);
+ _questionsBytes = new MessageOutputStream(senderUDPPayload, this);
+ _answersBytes = new MessageOutputStream(senderUDPPayload, this);
+ _authoritativeAnswersBytes = new MessageOutputStream(senderUDPPayload, this);
+ _additionalsAnswersBytes = new MessageOutputStream(senderUDPPayload, this);
+ }
+
+ /**
+ * Return the number of byte available in the message.
+ *
+ * @return available space
+ */
+ public int availableSpace() {
+ return _maxUDPPayload - HEADER_SIZE - _questionsBytes.size() - _answersBytes.size() - _authoritativeAnswersBytes.size() - _additionalsAnswersBytes.size();
+ }
+
+ /**
+ * Add a question to the message.
+ *
+ * @param rec
+ * @exception IOException
+ */
+ public void addQuestion(DNSQuestion rec) throws IOException {
+ MessageOutputStream record = new MessageOutputStream(512, this);
+ record.writeQuestion(rec);
+ byte[] byteArray = record.toByteArray();
+ if (byteArray.length < this.availableSpace()) {
+ _questions.add(rec);
+ _questionsBytes.write(byteArray, 0, byteArray.length);
+ } else {
+ throw new IOException("message full");
+ }
+ }
+
+ /**
+ * Add an answer if it is not suppressed.
+ *
+ * @param in
+ * @param rec
+ * @exception IOException
+ */
+ public void addAnswer(DNSIncoming in, DNSRecord rec) throws IOException {
+ if ((in == null) || !rec.suppressedBy(in)) {
+ this.addAnswer(rec, 0);
+ }
+ }
+
+ /**
+ * Add an answer to the message.
+ *
+ * @param rec
+ * @param now
+ * @exception IOException
+ */
+ public void addAnswer(DNSRecord rec, long now) throws IOException {
+ if (rec != null) {
+ if ((now == 0) || !rec.isExpired(now)) {
+ MessageOutputStream record = new MessageOutputStream(512, this);
+ record.writeRecord(rec, now);
+ byte[] byteArray = record.toByteArray();
+ if (byteArray.length < this.availableSpace()) {
+ _answers.add(rec);
+ _answersBytes.write(byteArray, 0, byteArray.length);
+ } else {
+ throw new IOException("message full");
+ }
+ }
+ }
+ }
+
+ /**
+ * Add an authoritative answer to the message.
+ *
+ * @param rec
+ * @exception IOException
+ */
+ public void addAuthorativeAnswer(DNSRecord rec) throws IOException {
+ MessageOutputStream record = new MessageOutputStream(512, this);
+ record.writeRecord(rec, 0);
+ byte[] byteArray = record.toByteArray();
+ if (byteArray.length < this.availableSpace()) {
+ _authoritativeAnswers.add(rec);
+ _authoritativeAnswersBytes.write(byteArray, 0, byteArray.length);
+ } else {
+ throw new IOException("message full");
+ }
+ }
+
+ /**
+ * Add an additional answer to the record. Omit if there is no room.
+ *
+ * @param in
+ * @param rec
+ * @exception IOException
+ */
+ public void addAdditionalAnswer(DNSIncoming in, DNSRecord rec) throws IOException {
+ MessageOutputStream record = new MessageOutputStream(512, this);
+ record.writeRecord(rec, 0);
+ byte[] byteArray = record.toByteArray();
+ if (byteArray.length < this.availableSpace()) {
+ _additionals.add(rec);
+ _additionalsAnswersBytes.write(byteArray, 0, byteArray.length);
+ } else {
+ throw new IOException("message full");
+ }
+ }
+
+ /**
+ * Builds the final message buffer to be send and returns it.
+ *
+ * @return bytes to send.
+ */
+ public byte[] data() {
+ long now = System.currentTimeMillis(); // System.currentTimeMillis()
+ _names.clear();
+
+ MessageOutputStream message = new MessageOutputStream(_maxUDPPayload, this);
+ message.writeShort(_multicast ? 0 : this.getId());
+ message.writeShort(this.getFlags());
+ message.writeShort(this.getNumberOfQuestions());
+ message.writeShort(this.getNumberOfAnswers());
+ message.writeShort(this.getNumberOfAuthorities());
+ message.writeShort(this.getNumberOfAdditionals());
+ for (DNSQuestion question : _questions) {
+ message.writeQuestion(question);
+ }
+ for (DNSRecord record : _answers) {
+ message.writeRecord(record, now);
+ }
+ for (DNSRecord record : _authoritativeAnswers) {
+ message.writeRecord(record, now);
+ }
+ for (DNSRecord record : _additionals) {
+ message.writeRecord(record, now);
+ }
+ return message.toByteArray();
+ }
+
+ @Override
+ public boolean isQuery() {
+ return (this.getFlags() & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_QUERY;
+ }
+
+ /**
+ * Debugging.
+ */
+ String print(boolean dump) {
+ StringBuilder buf = new StringBuilder();
+ buf.append(this.print());
+ if (dump) {
+ buf.append(this.print(this.data()));
+ }
+ return buf.toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ buf.append(isQuery() ? "dns[query:" : "dns[response:");
+ buf.append(" id=0x");
+ buf.append(Integer.toHexString(this.getId()));
+ if (this.getFlags() != 0) {
+ buf.append(", flags=0x");
+ buf.append(Integer.toHexString(this.getFlags()));
+ if ((this.getFlags() & DNSConstants.FLAGS_QR_RESPONSE) != 0) {
+ buf.append(":r");
+ }
+ if ((this.getFlags() & DNSConstants.FLAGS_AA) != 0) {
+ buf.append(":aa");
+ }
+ if ((this.getFlags() & DNSConstants.FLAGS_TC) != 0) {
+ buf.append(":tc");
+ }
+ }
+ if (this.getNumberOfQuestions() > 0) {
+ buf.append(", questions=");
+ buf.append(this.getNumberOfQuestions());
+ }
+ if (this.getNumberOfAnswers() > 0) {
+ buf.append(", answers=");
+ buf.append(this.getNumberOfAnswers());
+ }
+ if (this.getNumberOfAuthorities() > 0) {
+ buf.append(", authorities=");
+ buf.append(this.getNumberOfAuthorities());
+ }
+ if (this.getNumberOfAdditionals() > 0) {
+ buf.append(", additionals=");
+ buf.append(this.getNumberOfAdditionals());
+ }
+ if (this.getNumberOfQuestions() > 0) {
+ buf.append("\nquestions:");
+ for (DNSQuestion question : _questions) {
+ buf.append("\n\t");
+ buf.append(question);
+ }
+ }
+ if (this.getNumberOfAnswers() > 0) {
+ buf.append("\nanswers:");
+ for (DNSRecord record : _answers) {
+ buf.append("\n\t");
+ buf.append(record);
+ }
+ }
+ if (this.getNumberOfAuthorities() > 0) {
+ buf.append("\nauthorities:");
+ for (DNSRecord record : _authoritativeAnswers) {
+ buf.append("\n\t");
+ buf.append(record);
+ }
+ }
+ if (this.getNumberOfAdditionals() > 0) {
+ buf.append("\nadditionals:");
+ for (DNSRecord record : _additionals) {
+ buf.append("\n\t");
+ buf.append(record);
+ }
+ }
+ buf.append("\nnames=");
+ buf.append(_names);
+ buf.append("]");
+ return buf.toString();
+ }
+
+ /**
+ * @return the maxUDPPayload
+ */
+ public int getMaxUDPPayload() {
+ return this._maxUDPPayload;
+ }
+
+}