Initial checkin
This commit is contained in:
51
pom.xml
Normal file
51
pom.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.acmerocket.waiter</groupId>
|
||||
<artifactId>waiter</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>waiter</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.10</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.intellij</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
<version>9.0.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<version>1.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>2.0.2</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
17
src/main/java/com/softwarecraftsmen/CanNeverHappenException.java
Executable file
17
src/main/java/com/softwarecraftsmen/CanNeverHappenException.java
Executable file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class CanNeverHappenException extends RuntimeException
|
||||
{
|
||||
public CanNeverHappenException(final @NotNull Exception cause)
|
||||
{
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public CanNeverHappenException()
|
||||
{}
|
||||
}
|
||||
109
src/main/java/com/softwarecraftsmen/Optional.java
Executable file
109
src/main/java/com/softwarecraftsmen/Optional.java
Executable file
@@ -0,0 +1,109 @@
|
||||
package com.softwarecraftsmen;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import static java.util.Collections.emptySet;
|
||||
|
||||
public class Optional<T> implements Set<T>
|
||||
{
|
||||
private Set<T> internalSet;
|
||||
private final T singleValue;
|
||||
|
||||
private Optional()
|
||||
{
|
||||
internalSet = emptySet();
|
||||
singleValue = null;
|
||||
}
|
||||
|
||||
public Optional(final @NotNull T singleValue)
|
||||
{
|
||||
internalSet = new LinkedHashSet<T>(1);
|
||||
this.singleValue = singleValue;
|
||||
internalSet.add(this.singleValue);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public T value()
|
||||
{
|
||||
if (isEmpty())
|
||||
{
|
||||
throw new IllegalStateException("IsEmpty");
|
||||
}
|
||||
return singleValue;
|
||||
}
|
||||
|
||||
public int size()
|
||||
{
|
||||
return internalSet.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return internalSet.isEmpty();
|
||||
}
|
||||
|
||||
public boolean contains(final @NotNull Object o)
|
||||
{
|
||||
return internalSet.contains(o);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Iterator<T> iterator()
|
||||
{
|
||||
return internalSet.iterator();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Object[] toArray()
|
||||
{
|
||||
return internalSet.toArray();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public <T> T[] toArray(final @NotNull T[] a)
|
||||
{
|
||||
return internalSet.toArray(a);
|
||||
}
|
||||
|
||||
public boolean add(final @NotNull T t)
|
||||
{
|
||||
throw new UnsupportedOperationException("add");
|
||||
}
|
||||
|
||||
public boolean remove(final Object o)
|
||||
{
|
||||
throw new UnsupportedOperationException("remove");
|
||||
}
|
||||
|
||||
public boolean containsAll(final @NotNull Collection<?> c)
|
||||
{
|
||||
return internalSet.containsAll(c);
|
||||
}
|
||||
|
||||
public boolean addAll(final @NotNull Collection<? extends T> c)
|
||||
{
|
||||
throw new UnsupportedOperationException("addAll");
|
||||
}
|
||||
|
||||
public boolean retainAll(final Collection<?> c)
|
||||
{
|
||||
throw new UnsupportedOperationException("retainAll");
|
||||
}
|
||||
|
||||
public boolean removeAll(final Collection<?> c)
|
||||
{
|
||||
throw new UnsupportedOperationException("removeAll");
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
throw new UnsupportedOperationException("clear");
|
||||
}
|
||||
|
||||
public static <T> Optional<T> empty()
|
||||
{
|
||||
return new Optional<T>();
|
||||
}
|
||||
}
|
||||
48
src/main/java/com/softwarecraftsmen/Pair.java
Normal file
48
src/main/java/com/softwarecraftsmen/Pair.java
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class Pair<A, B>
|
||||
{
|
||||
private final A name;
|
||||
private final B internetClassType;
|
||||
|
||||
public Pair(final @NotNull A name, final @NotNull B internetClassType)
|
||||
{
|
||||
this.name = name;
|
||||
this.internetClassType = internetClassType;
|
||||
}
|
||||
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final Pair key = (Pair) o;
|
||||
return internetClassType == key.internetClassType && name.equals(key.name);
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
int result;
|
||||
result = name.hashCode();
|
||||
result = 31 * result + internetClassType.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return com.softwarecraftsmen.toString.ToString.string(this, name, internetClassType);
|
||||
}
|
||||
}
|
||||
68
src/main/java/com/softwarecraftsmen/dns/HostInformation.java
Executable file
68
src/main/java/com/softwarecraftsmen/dns/HostInformation.java
Executable file
@@ -0,0 +1,68 @@
|
||||
package com.softwarecraftsmen.dns;
|
||||
|
||||
import static com.softwarecraftsmen.toString.ToString.string;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class HostInformation implements Serializable
|
||||
{
|
||||
public final String cpuType;
|
||||
public final String operatingSystemType;
|
||||
|
||||
public HostInformation(final @NotNull String cpuType, final @NotNull String operatingSystemType)
|
||||
{
|
||||
this.cpuType = cpuType;
|
||||
this.operatingSystemType = operatingSystemType;
|
||||
if (cpuType.length() > 255)
|
||||
{
|
||||
throw new IllegalArgumentException("cpuType is a character strign which DNS restricts to a maximum length of 255 characters");
|
||||
}
|
||||
if (operatingSystemType.length() > 255)
|
||||
{
|
||||
throw new IllegalArgumentException("operatingSystemType is a character strign which DNS restricts to a maximum length of 255 characters");
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return string(this, cpuType, operatingSystemType);
|
||||
}
|
||||
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final HostInformation that = (HostInformation) o;
|
||||
return cpuType.equals(that.cpuType) && operatingSystemType.equals(that.operatingSystemType);
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
int result;
|
||||
result = cpuType.hashCode();
|
||||
result = 31 * result + operatingSystemType.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
writer.writeCharacterString(cpuType);
|
||||
writer.writeCharacterString(operatingSystemType);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static HostInformation hostInformation(final @NotNull String cpuType, final @NotNull String operatingSystemType)
|
||||
{
|
||||
return new HostInformation(cpuType, operatingSystemType);
|
||||
}
|
||||
}
|
||||
81
src/main/java/com/softwarecraftsmen/dns/MailBox.java
Normal file
81
src/main/java/com/softwarecraftsmen/dns/MailBox.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package com.softwarecraftsmen.dns;
|
||||
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import static com.softwarecraftsmen.dns.labels.SimpleLabel.simpleLabel;
|
||||
import com.softwarecraftsmen.dns.labels.Label;
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import com.softwarecraftsmen.dns.names.DomainName;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import static java.util.Locale.UK;
|
||||
|
||||
public class MailBox implements Name
|
||||
{
|
||||
private final String userName;
|
||||
private final DomainName domainName;
|
||||
|
||||
public MailBox(final @NotNull String userName, final @NotNull DomainName domainName)
|
||||
{
|
||||
this.userName = userName;
|
||||
this.domainName = domainName;
|
||||
if (userName.length() > 63)
|
||||
{
|
||||
throw new IllegalArgumentException("An userName must be less than 64 characters in length");
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return format(UK, "%1$s@%2$s", userName, domainName);
|
||||
}
|
||||
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final MailBox mailBox = (MailBox) o;
|
||||
return domainName.equals(mailBox.domainName) && userName.equals(mailBox.userName);
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
int result;
|
||||
result = userName.hashCode();
|
||||
result = 31 * result + domainName.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
writer.writeCharacterString(userName);
|
||||
domainName.serialize(writer);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static MailBox mailBox(final @NotNull String userName, final @NotNull DomainName domainName)
|
||||
{
|
||||
return new MailBox(userName, domainName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Label> toLabels()
|
||||
{
|
||||
return new ArrayList<Label>()
|
||||
{{
|
||||
add(simpleLabel(userName));
|
||||
addAll(domainName.toLabels());
|
||||
}};
|
||||
}
|
||||
}
|
||||
114
src/main/java/com/softwarecraftsmen/dns/MailExchange.java
Executable file
114
src/main/java/com/softwarecraftsmen/dns/MailExchange.java
Executable file
@@ -0,0 +1,114 @@
|
||||
package com.softwarecraftsmen.dns;
|
||||
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import com.softwarecraftsmen.dns.labels.SimpleLabel;
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import static com.softwarecraftsmen.toString.ToString.string;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import static java.util.Collections.reverse;
|
||||
import java.util.List;
|
||||
|
||||
public class MailExchange implements Comparable<MailExchange>, Serializable
|
||||
{
|
||||
private final Unsigned16BitInteger preference;
|
||||
private final HostName hostName;
|
||||
|
||||
public MailExchange(final @NotNull Unsigned16BitInteger preference, final @NotNull HostName hostName)
|
||||
{
|
||||
this.preference = preference;
|
||||
this.hostName = hostName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return string(this, preference, hostName);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"RedundantIfStatement"})
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final MailExchange that = (MailExchange) o;
|
||||
|
||||
if (!hostName.equals(that.hostName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!preference.equals(that.preference))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
int result;
|
||||
result = preference.hashCode();
|
||||
result = 31 * result + hostName.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
public int compareTo(final @NotNull MailExchange that)
|
||||
{
|
||||
final int initialPreference = this.preference.compareTo(that.preference);
|
||||
if (initialPreference != 0)
|
||||
{
|
||||
return initialPreference;
|
||||
}
|
||||
final List<SimpleLabel> thisLabels = reverseLabelsInHostName(this);
|
||||
final List<SimpleLabel> thatLabels = reverseLabelsInHostName(that);
|
||||
|
||||
if (thisLabels.size() < thatLabels.size())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (thisLabels.size() > thatLabels.size())
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
for(int index = 0; index < thisLabels.size(); index++)
|
||||
{
|
||||
final int compareTo = thisLabels.get(index).compareTo(thatLabels.get(index));
|
||||
if (compareTo != 0)
|
||||
{
|
||||
return compareTo;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private List<SimpleLabel> reverseLabelsInHostName(final MailExchange mailExchange)
|
||||
{
|
||||
return new ArrayList<SimpleLabel>(mailExchange.hostName.toLabels())
|
||||
{{
|
||||
reverse(this);
|
||||
}};
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
writer.writeUnsigned16BitInteger(preference);
|
||||
hostName.serialize(writer);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static MailExchange mailExchange(final @NotNull Unsigned16BitInteger preference, final @NotNull HostName hostName)
|
||||
{
|
||||
return new MailExchange(preference, hostName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.softwarecraftsmen.dns;
|
||||
|
||||
import static java.util.Locale.UK;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.Character.isISOControl;
|
||||
|
||||
public final class NonAsciiAndControlCharactersAreNotSupportedInCharacterStringsException extends IllegalArgumentException
|
||||
{
|
||||
public NonAsciiAndControlCharactersAreNotSupportedInCharacterStringsException(final char nonAsciiCharacter)
|
||||
{
|
||||
super(format(UK, "Non ASCII characters, such as %1$s, are not supported in DNS names", nonAsciiCharacter));
|
||||
}
|
||||
|
||||
public static void throwExceptionIfUnsupportedCharacterCode(final char toWrite)
|
||||
{
|
||||
if (isISOControl(toWrite) || toWrite > 255)
|
||||
{
|
||||
throw new NonAsciiAndControlCharactersAreNotSupportedInCharacterStringsException(toWrite);
|
||||
}
|
||||
}
|
||||
}
|
||||
100
src/main/java/com/softwarecraftsmen/dns/Seconds.java
Normal file
100
src/main/java/com/softwarecraftsmen/dns/Seconds.java
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns;
|
||||
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned32BitInteger;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static java.util.Locale.UK;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.currentTimeMillis;
|
||||
|
||||
public class Seconds implements Serializable, Comparable<Seconds>
|
||||
{
|
||||
private final Unsigned32BitInteger value;
|
||||
|
||||
public Seconds(final @NotNull Unsigned32BitInteger value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Seconds seconds(final long value)
|
||||
{
|
||||
return seconds(new Unsigned32BitInteger(value));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Seconds seconds(final @NotNull Unsigned32BitInteger value)
|
||||
{
|
||||
return new Seconds(value);
|
||||
}
|
||||
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final Seconds seconds = (Seconds) o;
|
||||
return value.equals(seconds.value);
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return value.hashCode();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return format(UK, "%1$s second(s)", value);
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
writer.writeUnsigned32BitInteger(value);
|
||||
}
|
||||
|
||||
public int compareTo(final @NotNull Seconds that)
|
||||
{
|
||||
return value.compareTo(that.value);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Seconds chooseSmallestValue(final @NotNull Seconds that)
|
||||
{
|
||||
switch (compareTo(that))
|
||||
{
|
||||
case -1:
|
||||
return this;
|
||||
case 0:
|
||||
return this;
|
||||
case 1:
|
||||
return that;
|
||||
default:
|
||||
return that;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Seconds currentTime()
|
||||
{
|
||||
return seconds(currentTimeMillis() / 1000);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Seconds add(final @NotNull Seconds offset)
|
||||
{
|
||||
return seconds(this.value.add(offset.value));
|
||||
}
|
||||
}
|
||||
188
src/main/java/com/softwarecraftsmen/dns/SerializableInternetProtocolAddress.java
Executable file
188
src/main/java/com/softwarecraftsmen/dns/SerializableInternetProtocolAddress.java
Executable file
@@ -0,0 +1,188 @@
|
||||
package com.softwarecraftsmen.dns;
|
||||
|
||||
import com.softwarecraftsmen.CanNeverHappenException;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import static com.softwarecraftsmen.dns.names.PointerName.pointerName;
|
||||
import com.softwarecraftsmen.dns.names.PointerName;
|
||||
import static com.softwarecraftsmen.toString.ToString.string;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static java.lang.Integer.parseInt;
|
||||
import static java.lang.String.format;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import static java.util.Locale.UK;
|
||||
|
||||
public class SerializableInternetProtocolAddress<A extends InetAddress> implements Serializable
|
||||
{
|
||||
public static final SerializableInternetProtocolAddress<Inet4Address> InternetProtocolVersion4LocalHost = serializableInternetProtocolVersion4Address(127, 0 , 0, 1);
|
||||
public static final SerializableInternetProtocolAddress<Inet4Address> InternetProtocolVersion4UnspecifiedAddress = serializableInternetProtocolVersion4Address(0, 0 , 0, 0);
|
||||
public static final SerializableInternetProtocolAddress<Inet6Address> InternetProtocolVersion6LocalHost = serializableInternetProtocolVersion6Address(0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001);
|
||||
public static final SerializableInternetProtocolAddress<Inet6Address> InternetProtocolVersion6UnspecifiedAddress = serializableInternetProtocolVersion6Address(0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000);
|
||||
|
||||
public static final SerializableInternetProtocolAddress<Inet6Address> InternetProtocolVersion6LocalNetworkAddress = serializableInternetProtocolVersion6Address(0xfe00, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000);
|
||||
public static final SerializableInternetProtocolAddress<Inet6Address> InternetProtocolVersion6MulticastPrefixAddress = serializableInternetProtocolVersion6Address(0xff00, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000);
|
||||
public static final SerializableInternetProtocolAddress<Inet6Address> InternetProtocolVersion6AllNodesAddress = serializableInternetProtocolVersion6Address(0xff02, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001);
|
||||
public static final SerializableInternetProtocolAddress<Inet6Address> InternetProtocolVersion6AllRoutersAddress = serializableInternetProtocolVersion6Address(0xff02, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0002);
|
||||
public static final SerializableInternetProtocolAddress<Inet6Address> InternetProtocolVersion6AllHostsAddress = serializableInternetProtocolVersion6Address(0xff02, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0003);
|
||||
|
||||
public final A address;
|
||||
|
||||
public SerializableInternetProtocolAddress(final @NotNull A address)
|
||||
{
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
writer.writeBytes(address.getAddress());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return string(this, address);
|
||||
}
|
||||
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final SerializableInternetProtocolAddress that = (SerializableInternetProtocolAddress) o;
|
||||
return address.equals(that.address);
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return address.hashCode();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static <A extends InetAddress> SerializableInternetProtocolAddress<A> serializableInternetProtocolAddress(final @NotNull A address)
|
||||
{
|
||||
return new SerializableInternetProtocolAddress<A>(address);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static SerializableInternetProtocolAddress<Inet4Address> serializableInternetProtocolVersion4Address(final @NotNull String dottedString)
|
||||
{
|
||||
final String[] parts = dottedString.split("\\.");
|
||||
if (parts.length != 4)
|
||||
{
|
||||
throw new IllegalArgumentException(format(UK, "%1$s is not a valid Internet Protocol version 4 dotted address string of four parts", dottedString));
|
||||
}
|
||||
return serializableInternetProtocolVersion4Address(parseInteger(parts[0]), parseInteger(parts[1]), parseInteger(parts[2]), parseInteger(parts[3]));
|
||||
}
|
||||
|
||||
private static int parseInteger(final @NotNull String value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return parseInt(value);
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
throw new IllegalArgumentException("%1$s is not a valid unsigned byte between 0 and 255");
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static SerializableInternetProtocolAddress<Inet4Address> serializableInternetProtocolVersion4Address(final int one, final int two, final int three, final int four)
|
||||
{
|
||||
guardArgumentIsUnsignedByte(one);
|
||||
guardArgumentIsUnsignedByte(two);
|
||||
guardArgumentIsUnsignedByte(three);
|
||||
guardArgumentIsUnsignedByte(four);
|
||||
try
|
||||
{
|
||||
final Inet4Address inet4Address = (Inet4Address) Inet4Address.getByAddress(new byte[]{(byte) one, (byte) two, (byte) three, (byte) four});
|
||||
return serializableInternetProtocolAddress(inet4Address);
|
||||
}
|
||||
catch (UnknownHostException e)
|
||||
{
|
||||
throw new CanNeverHappenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void guardArgumentIsUnsignedByte(final int potentialUnsignedByte)
|
||||
{
|
||||
if (potentialUnsignedByte < 0 || potentialUnsignedByte > 255)
|
||||
{
|
||||
throw new IllegalArgumentException(format(UK, "%1$s is not between 0 and 255 inclusive", potentialUnsignedByte));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make this work!
|
||||
// Does not support escaped IP addresses used with ports, eg http://[::1]:8080/
|
||||
/*
|
||||
@NotNull
|
||||
public static SerializableInternetProtocolAddress<Inet6Address> serializableInternetProtocolVersion6Address(final @NotNull String colonString)
|
||||
{
|
||||
final String[] leftAndRight = colonString.split("::");
|
||||
if (leftAndRight.length > 2)
|
||||
{
|
||||
throw new IllegalArgumentException("It is illegal to have more than one :: in a Internet Protocol version 6 colon string");
|
||||
}
|
||||
if (leftAndRight.length == 0)
|
||||
{
|
||||
colonString.split(":");
|
||||
}
|
||||
else
|
||||
{
|
||||
leftAndRight[0].split(":");
|
||||
leftAndRight[1].split(":");
|
||||
}
|
||||
throw new UnsupportedOperationException("To finish");
|
||||
}*/
|
||||
|
||||
@NotNull
|
||||
public static SerializableInternetProtocolAddress<Inet6Address> serializableInternetProtocolVersion6Address(final long one, final long two, final long three, final long four, final long five, final long six, final long seven, final long eight)
|
||||
{
|
||||
try
|
||||
{
|
||||
final Inet6Address inet6Address = (Inet6Address) Inet6Address.getByAddress(toBytes(one, two, three, four, five, six, seven, eight));
|
||||
return serializableInternetProtocolAddress(inet6Address);
|
||||
}
|
||||
catch (UnknownHostException e)
|
||||
{
|
||||
throw new CanNeverHappenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PointerName toInternetProtocolName()
|
||||
{
|
||||
if (address instanceof Inet4Address)
|
||||
{
|
||||
return pointerName((Inet4Address)address);
|
||||
}
|
||||
else if (address instanceof Inet6Address)
|
||||
{
|
||||
return pointerName((Inet6Address)address);
|
||||
}
|
||||
throw new IllegalStateException("We only support instances of InetAddress which are for Internet Protocol Version 4 and Internet Protocol Version 6");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static byte[] toBytes(final long ... values)
|
||||
{
|
||||
final byte[] bytes = new byte[values.length * 2];
|
||||
for(int index = 0; index < values.length; index++)
|
||||
{
|
||||
bytes[index * 2] = (byte) (values[index] & 0xFF00);
|
||||
bytes[index * 2 + 1] = (byte) (values[index] & 0x00FF);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
99
src/main/java/com/softwarecraftsmen/dns/ServiceInformation.java
Executable file
99
src/main/java/com/softwarecraftsmen/dns/ServiceInformation.java
Executable file
@@ -0,0 +1,99 @@
|
||||
package com.softwarecraftsmen.dns;
|
||||
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import static com.softwarecraftsmen.toString.ToString.string;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class ServiceInformation implements Serializable, Comparable<ServiceInformation>
|
||||
{
|
||||
private final Unsigned16BitInteger priority;
|
||||
private final Unsigned16BitInteger weight;
|
||||
private final Unsigned16BitInteger port;
|
||||
private final HostName canonicalTargetHostName;
|
||||
|
||||
// target is "." => service not present; target should be CanonicalName; target's A records should be in Additional records...
|
||||
public ServiceInformation(final @NotNull Unsigned16BitInteger priority, final @NotNull Unsigned16BitInteger weight, final @NotNull Unsigned16BitInteger port, final @NotNull HostName canonicalTargetHostName)
|
||||
{
|
||||
this.priority = priority;
|
||||
this.weight = weight;
|
||||
this.port = port;
|
||||
this.canonicalTargetHostName = canonicalTargetHostName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static ServiceInformation serviceInformation(final @NotNull Unsigned16BitInteger priority, final @NotNull Unsigned16BitInteger weight, final @NotNull Unsigned16BitInteger port, final @NotNull HostName canonicalTargetHostName)
|
||||
{
|
||||
return new ServiceInformation(priority, weight, port, canonicalTargetHostName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return string(this, priority, weight, port, canonicalTargetHostName);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"RedundantIfStatement"})
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final ServiceInformation that = (ServiceInformation) o;
|
||||
|
||||
if (!canonicalTargetHostName.equals(that.canonicalTargetHostName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!port.equals(that.port))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!priority.equals(that.priority))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!weight.equals(that.weight))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
int result;
|
||||
result = priority.hashCode();
|
||||
result = 31 * result + weight.hashCode();
|
||||
result = 31 * result + port.hashCode();
|
||||
result = 31 * result + canonicalTargetHostName.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
writer.writeUnsigned16BitInteger(priority);
|
||||
writer.writeUnsigned16BitInteger(weight);
|
||||
writer.writeUnsigned16BitInteger(port);
|
||||
canonicalTargetHostName.serialize(writer);
|
||||
}
|
||||
|
||||
public int compareTo(final @NotNull ServiceInformation that)
|
||||
{
|
||||
final int i = this.priority.compareTo(that.priority);
|
||||
if (i != 0)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
return this.weight.compareTo(that.weight);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns;
|
||||
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.sort;
|
||||
import static java.util.Collections.unmodifiableList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class ServiceInformationPrioritised implements Iterable<ServiceInformation>
|
||||
{
|
||||
@NotNull
|
||||
public static final WeightRandomNumberGenerator RegularRandomNumberGenerator = new RegularWeightRandomNumberGenerator();
|
||||
|
||||
private final WeightRandomNumberGenerator weightRandomNumberGenerator;
|
||||
private final List<ServiceInformation> serviceInformationPrioritised;
|
||||
|
||||
public ServiceInformationPrioritised(final @NotNull WeightRandomNumberGenerator weightRandomNumberGenerator, final @NotNull List<ServiceInformation> serviceInformationPrioritised)
|
||||
{
|
||||
this.weightRandomNumberGenerator = weightRandomNumberGenerator;
|
||||
this.serviceInformationPrioritised = serviceInformationPrioritised;
|
||||
sort(this.serviceInformationPrioritised);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Iterable<ServiceInformation> prioritise(final @NotNull ServiceInformation ... serviceInformation)
|
||||
{
|
||||
return new ServiceInformationPrioritised(RegularRandomNumberGenerator, asList(serviceInformation));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Iterator<ServiceInformation> iterator()
|
||||
{
|
||||
return unmodifiableList(serviceInformationPrioritised).iterator();
|
||||
}
|
||||
|
||||
public interface WeightRandomNumberGenerator
|
||||
{
|
||||
@NotNull
|
||||
Unsigned16BitInteger generate(final @NotNull Unsigned16BitInteger maximum);
|
||||
}
|
||||
|
||||
private static final class RegularWeightRandomNumberGenerator implements WeightRandomNumberGenerator
|
||||
{
|
||||
@NotNull
|
||||
public Unsigned16BitInteger generate(final @NotNull Unsigned16BitInteger maximum)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
100
src/main/java/com/softwarecraftsmen/dns/StatementOfAuthority.java
Executable file
100
src/main/java/com/softwarecraftsmen/dns/StatementOfAuthority.java
Executable file
@@ -0,0 +1,100 @@
|
||||
package com.softwarecraftsmen.dns;
|
||||
|
||||
import com.softwarecraftsmen.toString.ToString;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned32BitInteger;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import com.softwarecraftsmen.dns.MailBox;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class StatementOfAuthority implements Serializable
|
||||
{
|
||||
private final HostName primaryNameServerHostName;
|
||||
private final MailBox administratorMailbox;
|
||||
private final Unsigned32BitInteger serial;
|
||||
private final Seconds referesh;
|
||||
private final Seconds retry;
|
||||
private final Seconds expire;
|
||||
|
||||
// TODO: Subclass / create MailBox which uses Name
|
||||
public StatementOfAuthority(final @NotNull HostName primaryNameServerHostName, final @NotNull MailBox administratorMailbox, final @NotNull Unsigned32BitInteger serial, final @NotNull Seconds refresh, final @NotNull Seconds retry, final @NotNull Seconds expire)
|
||||
{
|
||||
this.primaryNameServerHostName = primaryNameServerHostName;
|
||||
this.administratorMailbox = administratorMailbox;
|
||||
this.serial = serial;
|
||||
this.referesh = refresh;
|
||||
this.retry = retry;
|
||||
this.expire = expire;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return ToString.string(this, primaryNameServerHostName, administratorMailbox, serial, referesh, retry, expire);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"RedundantIfStatement"})
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final StatementOfAuthority that = (StatementOfAuthority) o;
|
||||
|
||||
if (!administratorMailbox.equals(that.administratorMailbox))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!expire.equals(that.expire))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!primaryNameServerHostName.equals(that.primaryNameServerHostName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!referesh.equals(that.referesh))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!retry.equals(that.retry))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!serial.equals(that.serial))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
int result;
|
||||
result = primaryNameServerHostName.hashCode();
|
||||
result = 31 * result + administratorMailbox.hashCode();
|
||||
result = 31 * result + serial.hashCode();
|
||||
result = 31 * result + referesh.hashCode();
|
||||
result = 31 * result + retry.hashCode();
|
||||
result = 31 * result + expire.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
primaryNameServerHostName.serialize(writer);
|
||||
administratorMailbox.serialize(writer);
|
||||
writer.writeUnsigned32BitInteger(serial);
|
||||
writer.writeUnsignedSeconds(referesh);
|
||||
writer.writeUnsignedSeconds(retry);
|
||||
writer.writeUnsignedSeconds(expire);
|
||||
}
|
||||
}
|
||||
64
src/main/java/com/softwarecraftsmen/dns/TODO
Executable file
64
src/main/java/com/softwarecraftsmen/dns/TODO
Executable file
@@ -0,0 +1,64 @@
|
||||
Multi-thread:-
|
||||
Check cache
|
||||
UDP / TCP Sending thread
|
||||
UDP Receiving thread
|
||||
Thread to notify recepients that queries are done
|
||||
Encache
|
||||
|
||||
Cache - hmmm how...
|
||||
By question -> tie to responses (most queries are either 'A' or 'Any' or reverse look ups (PTR requests)
|
||||
Cache eviction -> queue of records to evict by time-to-live; thread goes and regularly cleans up cache OR removed on first use (easier and neater)
|
||||
Also, consider double querying - make a second query against the name server authority, using A records in additional records?
|
||||
Record response time precisely
|
||||
Cap time-to-live at, say, three hours
|
||||
Do an A question => look in cache, see if exact match(es) [several IP addresses possible].If not, look for CNAME with same owner. Then look in cache for exact match(es)
|
||||
|
||||
Test serialization of all ResourceRecords (including Length of RDATA)
|
||||
|
||||
Higher level API:-
|
||||
Find SOA (default if no records for name)
|
||||
Find All (basis of all caching approaches)
|
||||
Cascaded find (www.google.com, google.com, com, .)
|
||||
Find All IPAddresses given one IPAddress (find PTR, find A or find CNAME)
|
||||
Fina authoritative name server
|
||||
|
||||
Add record types:-
|
||||
NULL (contains any data)
|
||||
WKS
|
||||
RP
|
||||
|
||||
Support internationalised domain names
|
||||
Support EDNS for >512 byte UDP
|
||||
|
||||
Domain Changes
|
||||
Add a class to combine Weight and Priority to implement the rules in RFC2782 for SRV records to order servers; look at MailExchagne for ideas.
|
||||
Consider comparable on all Names... look at mail exchange for ideas.
|
||||
Implement AbstractResourceRecord.serialize
|
||||
Implement Serialize in all Names...
|
||||
Add all common IANA service names as a partial enumerated list to ServiceClassLabel.
|
||||
|
||||
Confirm
|
||||
SOA is domain name or host name owner?
|
||||
Negative caching
|
||||
SRV code works for real (do an acceptance test)!
|
||||
|
||||
BIND Like Support
|
||||
Use /etc/hosts (or Windows equivalent) to speed up searches
|
||||
Use /etc/resolv.conf domain and search lines?
|
||||
Use $LOCALDOMAINNAME (is it that) environment variable
|
||||
Listen to changes to /etc/hosts or /etc/resolv.conf
|
||||
Multiple retries
|
||||
|
||||
ZeroConf
|
||||
Support using UDP on 5353 for Bonjour / ZeroConf
|
||||
Support running as a local broadcast responding DNS server for ZeroConf
|
||||
(need a way of registering broadcast records, and making sure these are re-broadcast; effectively, becomes a mini-DNS server authoritatitive for the current host)
|
||||
Multicast host is 224.0.0.251.
|
||||
Each computer stores its own DNS records for A, MX, PTR, CNAME, etc
|
||||
Listens for requests; if for A record for our hostname/domainname (eg myhost.local.) then responds
|
||||
|
||||
Case Insensitivity
|
||||
Comparision of Name?
|
||||
|
||||
Google Code
|
||||
Wiki examples of the major APIs
|
||||
67
src/main/java/com/softwarecraftsmen/dns/Text.java
Executable file
67
src/main/java/com/softwarecraftsmen/dns/Text.java
Executable file
@@ -0,0 +1,67 @@
|
||||
package com.softwarecraftsmen.dns;
|
||||
|
||||
import static com.softwarecraftsmen.toString.ToString.string;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import java.util.List;
|
||||
|
||||
public class Text implements Serializable
|
||||
{
|
||||
private final List<String> lines;
|
||||
|
||||
public Text(final @NotNull List<String> lines)
|
||||
{
|
||||
this.lines = lines;
|
||||
for (String line : lines)
|
||||
{
|
||||
if (line.length() > 255)
|
||||
{
|
||||
throw new IllegalArgumentException("Maximum length of a character string in DNS is 255 characters");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return string(this, lines);
|
||||
}
|
||||
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final Text text = (Text) o;
|
||||
return lines.equals(text.lines);
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return lines.hashCode();
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
for (String line : lines)
|
||||
{
|
||||
writer.writeCharacterString(line);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Text text(final @NotNull String ... lines)
|
||||
{
|
||||
return new Text(asList(lines));
|
||||
}
|
||||
}
|
||||
127
src/main/java/com/softwarecraftsmen/dns/client/Client.java
Executable file
127
src/main/java/com/softwarecraftsmen/dns/client/Client.java
Executable file
@@ -0,0 +1,127 @@
|
||||
package com.softwarecraftsmen.dns.client;
|
||||
|
||||
import com.softwarecraftsmen.CanNeverHappenException;
|
||||
import com.softwarecraftsmen.Optional;
|
||||
import com.softwarecraftsmen.dns.*;
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import com.softwarecraftsmen.dns.names.DomainName;
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import com.softwarecraftsmen.dns.labels.ServiceLabel;
|
||||
import com.softwarecraftsmen.dns.labels.ServiceProtocolLabel;
|
||||
import com.softwarecraftsmen.dns.client.resourceRecordRepositories.ResourceRecordRepository;
|
||||
import static com.softwarecraftsmen.dns.names.ServiceName.serviceName;
|
||||
import com.softwarecraftsmen.dns.messaging.InternetClassType;
|
||||
import static com.softwarecraftsmen.dns.messaging.InternetClassType.*;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class Client
|
||||
{
|
||||
private final ResourceRecordRepository resourceRecordRepository;
|
||||
|
||||
public Client(final @NotNull ResourceRecordRepository resourceRecordRepository)
|
||||
{
|
||||
this.resourceRecordRepository = resourceRecordRepository;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Optional<HostName> findNameFromInternetProtocolVersion4Address(@NotNull Inet4Address internetProtocolVersion4Address)
|
||||
{
|
||||
return findNameFromInternetProtocolVersion4Address(new SerializableInternetProtocolAddress<Inet4Address>(internetProtocolVersion4Address));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<Inet4Address> findAllInternetProtocolVersion4Addresses(final @NotNull HostName hostName)
|
||||
{
|
||||
final Set<SerializableInternetProtocolAddress<Inet4Address>> set = resourceRecordRepository.findData(hostName, A);
|
||||
final Set<Inet4Address> addresses = new LinkedHashSet<Inet4Address>(set.size());
|
||||
for (SerializableInternetProtocolAddress<Inet4Address> inet4AddressSerializableInternetProtocolAddress : set)
|
||||
{
|
||||
addresses.add(inet4AddressSerializableInternetProtocolAddress.address);
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Optional<HostName> findNameFromInternetProtocolVersion6Address(@NotNull Inet6Address internetProtocolVersion6Address)
|
||||
{
|
||||
return findNameFromInternetProtocolVersion6Address(new SerializableInternetProtocolAddress<Inet6Address>(internetProtocolVersion6Address));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<Inet6Address> findAllInternetProtocolVersion6Addresses(final @NotNull HostName hostName)
|
||||
{
|
||||
final Set<SerializableInternetProtocolAddress<Inet6Address>> set = resourceRecordRepository.findData(hostName, AAAA);
|
||||
final Set<Inet6Address> addresses = new LinkedHashSet<Inet6Address>(set.size());
|
||||
for (SerializableInternetProtocolAddress<Inet6Address> inet4AddressSerializableInternetProtocolAddress : set)
|
||||
{
|
||||
addresses.add(inet4AddressSerializableInternetProtocolAddress.address);
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<MailExchange> findMailServers(final @NotNull DomainName domainName)
|
||||
{
|
||||
return resourceRecordRepository.findData(domainName, MX);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Optional<Text> findText(final @NotNull HostName hostName)
|
||||
{
|
||||
return findOptionalData(hostName, TXT);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Optional<HostInformation> findHostInformation(final @NotNull HostName hostName)
|
||||
{
|
||||
return findOptionalData(hostName, HINFO);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Optional<HostName> findCanonicalName(final @NotNull HostName hostName)
|
||||
{
|
||||
return findOptionalData(hostName, CNAME);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Optional<HostName> findNameFromInternetProtocolVersion4Address(final @NotNull SerializableInternetProtocolAddress<Inet4Address> internetProtocolVersion4Address)
|
||||
{
|
||||
return findOptionalData(internetProtocolVersion4Address.toInternetProtocolName(), PTR);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Optional<HostName> findNameFromInternetProtocolVersion6Address(final @NotNull SerializableInternetProtocolAddress<Inet6Address> internetProtocolVersion6Address)
|
||||
{
|
||||
return findOptionalData(internetProtocolVersion6Address.toInternetProtocolName(), PTR);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<ServiceInformation> findServiceInformation(final @NotNull ServiceLabel serviceLabel, final @NotNull ServiceProtocolLabel serviceProtocolLabel, final @NotNull DomainName domainName)
|
||||
{
|
||||
return resourceRecordRepository.findData(serviceName(serviceLabel, serviceProtocolLabel, domainName), SRV);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"LoopStatementThatDoesntLoop"})
|
||||
private <T extends Serializable> Optional<T> findOptionalData(final Name name, final InternetClassType internetClassType)
|
||||
{
|
||||
final Set<T> set = resourceRecordRepository.findData(name, internetClassType);
|
||||
if (set.isEmpty())
|
||||
{
|
||||
return com.softwarecraftsmen.Optional.empty();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (T serializable : set)
|
||||
{
|
||||
return new Optional<T>(serializable);
|
||||
}
|
||||
}
|
||||
throw new CanNeverHappenException();
|
||||
}
|
||||
}
|
||||
12
src/main/java/com/softwarecraftsmen/dns/client/resolvers/DnsResolver.java
Executable file
12
src/main/java/com/softwarecraftsmen/dns/client/resolvers/DnsResolver.java
Executable file
@@ -0,0 +1,12 @@
|
||||
package com.softwarecraftsmen.dns.client.resolvers;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import com.softwarecraftsmen.dns.messaging.InternetClassType;
|
||||
import com.softwarecraftsmen.dns.messaging.Message;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface DnsResolver
|
||||
{
|
||||
@NotNull
|
||||
Message resolve(final @NotNull Name name, final @NotNull InternetClassType internetClassType);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.client.resolvers;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import com.softwarecraftsmen.dns.client.resolvers.protoolClients.ProtocolClient;
|
||||
import com.softwarecraftsmen.dns.client.resolvers.protoolClients.TcpProtocolClient;
|
||||
import com.softwarecraftsmen.dns.client.resolvers.protoolClients.UdpProtocolClient;
|
||||
import com.softwarecraftsmen.dns.client.serverAddressFinders.ServerAddressFinder;
|
||||
import com.softwarecraftsmen.dns.messaging.InternetClassType;
|
||||
import com.softwarecraftsmen.dns.messaging.Message;
|
||||
import static com.softwarecraftsmen.dns.messaging.Message.emptyReply;
|
||||
import static com.softwarecraftsmen.dns.messaging.Message.query;
|
||||
import static com.softwarecraftsmen.dns.messaging.Question.internetQuestion;
|
||||
import com.softwarecraftsmen.dns.messaging.deserializer.BadlyFormedDnsMessageException;
|
||||
import com.softwarecraftsmen.dns.messaging.deserializer.MessageDeserializer;
|
||||
import com.softwarecraftsmen.dns.messaging.deserializer.TruncatedDnsMessageException;
|
||||
import static com.softwarecraftsmen.dns.messaging.serializer.ByteSerializer.serialize;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SynchronousDnsResolver implements DnsResolver
|
||||
{
|
||||
private final ServerAddressFinder serverAddressFinder;
|
||||
private static final int MaximumNonEDNS0UdpMessageSize = 512;
|
||||
|
||||
public SynchronousDnsResolver(final @NotNull ServerAddressFinder serverAddressFinder)
|
||||
{
|
||||
this.serverAddressFinder = serverAddressFinder;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"EmptyCatchBlock"})
|
||||
@NotNull
|
||||
public Message resolve(final @NotNull Name name, final @NotNull InternetClassType internetClassType)
|
||||
{
|
||||
return resolveAgainstAllServers
|
||||
(
|
||||
query(internetQuestion(name, internetClassType)),
|
||||
new ArrayList<InetSocketAddress>()
|
||||
{{
|
||||
addAll(serverAddressFinder.find());
|
||||
addAll(serverAddressFinder.find());
|
||||
}}
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"EmptyCatchBlock"})
|
||||
private Message resolveAgainstAllServers(final Message request, final List<InetSocketAddress> dnsServers)
|
||||
{
|
||||
for (InetSocketAddress dnsServer : dnsServers)
|
||||
{
|
||||
try
|
||||
{
|
||||
return resolve(request, dnsServer, false);
|
||||
}
|
||||
catch (IOException e)
|
||||
{}
|
||||
catch (BadlyFormedDnsMessageException e)
|
||||
{}
|
||||
}
|
||||
return emptyReply(request);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Message resolve(final @NotNull Message request, final InetSocketAddress remoteSocketAddress, final boolean forceTcpUse) throws IOException, BadlyFormedDnsMessageException
|
||||
{
|
||||
final byte[] bytes = serialize(request);
|
||||
final boolean useTcp = forceTcpUse || bytes.length > MaximumNonEDNS0UdpMessageSize;
|
||||
final ProtocolClient protocolClient = useTcp ? new TcpProtocolClient(null, remoteSocketAddress, 1, 100) : new UdpProtocolClient(null, remoteSocketAddress, 1, 100);
|
||||
|
||||
boolean tryAgainWithTcp = false;
|
||||
try
|
||||
{
|
||||
return new MessageDeserializer(protocolClient.sendAndReceive(bytes)).readMessage();
|
||||
}
|
||||
catch(TruncatedDnsMessageException exception)
|
||||
{
|
||||
if (forceTcpUse)
|
||||
{
|
||||
throw new BadlyFormedDnsMessageException("TCP DNS message was truncated; this should never happen", exception);
|
||||
}
|
||||
tryAgainWithTcp = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
protocolClient.close();
|
||||
}
|
||||
return resolve(request, remoteSocketAddress, tryAgainWithTcp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.softwarecraftsmen.dns.client.resolvers.protoolClients;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface ProtocolClient
|
||||
{
|
||||
public static final byte[] EmptyByteArray = new byte[] {};
|
||||
|
||||
@NotNull
|
||||
byte[] sendAndReceive(@NotNull byte[] sendData) throws IOException;
|
||||
|
||||
void close();
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.softwarecraftsmen.dns.client.resolvers.protoolClients;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SelectionKey;
|
||||
|
||||
public class SelectorKeyHelper
|
||||
{
|
||||
private final SelectionKey key;
|
||||
private final int numberOfRetries;
|
||||
private final int blockInMilliseconds;
|
||||
|
||||
public SelectorKeyHelper(final @NotNull SelectionKey key, final int blockInMilliseconds, final int numberOfRetries)
|
||||
{
|
||||
if (blockInMilliseconds < 0)
|
||||
{
|
||||
throw new IllegalArgumentException("blockInMilliseconds can not be negative");
|
||||
}
|
||||
this.key = key;
|
||||
this.numberOfRetries = numberOfRetries;
|
||||
this.blockInMilliseconds = blockInMilliseconds;
|
||||
}
|
||||
|
||||
public void blockUntilReady(final int operationCode) throws IOException
|
||||
{
|
||||
key.interestOps(operationCode);
|
||||
int retryCount = 0;
|
||||
try
|
||||
{
|
||||
while (!((key.readyOps() & operationCode) != 0) && retryCount < numberOfRetries)
|
||||
{
|
||||
block();
|
||||
retryCount++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
resetKey();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean block() throws IOException
|
||||
{
|
||||
int numberOfKeysSelected = 0;
|
||||
if (blockInMilliseconds > 0)
|
||||
{
|
||||
numberOfKeysSelected = key.selector().select(blockInMilliseconds);
|
||||
}
|
||||
else if (blockInMilliseconds == 0)
|
||||
{
|
||||
numberOfKeysSelected = key.selector().selectNow();
|
||||
}
|
||||
return numberOfKeysSelected == 0;
|
||||
}
|
||||
|
||||
private void resetKey()
|
||||
{
|
||||
if (key.isValid())
|
||||
{
|
||||
key.interestOps(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.client.resolvers.protoolClients;
|
||||
|
||||
import com.softwarecraftsmen.CanNeverHappenException;
|
||||
import static com.softwarecraftsmen.dns.messaging.serializer.ByteSerializer.MaximumDnsMessageSize;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import static java.lang.System.arraycopy;
|
||||
import java.net.SocketAddress;
|
||||
import static java.nio.ByteBuffer.wrap;
|
||||
import java.nio.channels.*;
|
||||
import static java.nio.channels.SelectionKey.OP_READ;
|
||||
import static java.nio.channels.SelectionKey.OP_CONNECT;
|
||||
import static java.nio.channels.SelectionKey.OP_WRITE;
|
||||
import static java.util.Arrays.copyOf;
|
||||
|
||||
public class TcpProtocolClient implements ProtocolClient
|
||||
{
|
||||
private boolean closed;
|
||||
private final SocketChannel channel;
|
||||
private final SelectionKey key;
|
||||
private final SelectorKeyHelper selectorKeyHelper;
|
||||
|
||||
public TcpProtocolClient(@Nullable final SocketAddress localSocketAddress, @NotNull final SocketAddress remoteSocketAddress, final int blockInMilliseconds, final int numberOfRetries)
|
||||
{
|
||||
this.closed = true;
|
||||
this.channel = openChannel();
|
||||
try
|
||||
{
|
||||
channel.configureBlocking(false);
|
||||
}
|
||||
catch(final IOException exception)
|
||||
{
|
||||
closeChannel(true);
|
||||
throw new IllegalStateException(exception);
|
||||
}
|
||||
key = obtainSelectorKey(openSelector());
|
||||
selectorKeyHelper = new SelectorKeyHelper(key, blockInMilliseconds, numberOfRetries);
|
||||
bind(localSocketAddress);
|
||||
connect(remoteSocketAddress);
|
||||
|
||||
this.closed = false;
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
if (closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
try
|
||||
{
|
||||
closeChannel(false);
|
||||
}
|
||||
catch(IllegalStateException exception)
|
||||
{
|
||||
closeSelector(selector(), true);
|
||||
throw new IllegalStateException(exception);
|
||||
}
|
||||
closeSelector(selector(), false);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"EmptyCatchBlock"})
|
||||
protected void finalize() throws Throwable
|
||||
{
|
||||
super.finalize();
|
||||
try
|
||||
{
|
||||
close();
|
||||
}
|
||||
catch (final Exception exception)
|
||||
{}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"ThrowableInstanceNeverThrown"})
|
||||
private IllegalStateException closeDueToError(final IOException e)
|
||||
{
|
||||
closeChannel(true);
|
||||
closeSelector(selector(), true);
|
||||
return new IllegalStateException(e);
|
||||
}
|
||||
|
||||
private Selector selector() {return key.selector();}
|
||||
|
||||
private SelectionKey obtainSelectorKey(final Selector selector)
|
||||
{
|
||||
try
|
||||
{
|
||||
return channel.register(selector, 0);
|
||||
}
|
||||
catch (final ClosedChannelException e)
|
||||
{
|
||||
closeSelector(selector, true);
|
||||
closeChannel(true);
|
||||
throw new CanNeverHappenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeSelector(final Selector selector, final boolean inResponseToAnEarlierException)
|
||||
{
|
||||
try
|
||||
{
|
||||
selector.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (inResponseToAnEarlierException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private SocketChannel openChannel()
|
||||
{
|
||||
try
|
||||
{
|
||||
return SocketChannel.open();
|
||||
}
|
||||
catch (final IOException exception)
|
||||
{
|
||||
throw new CanNeverHappenException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private Selector openSelector()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Selector.open();
|
||||
}
|
||||
catch(IOException exception)
|
||||
{
|
||||
closeChannel(true);
|
||||
throw new CanNeverHappenException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeChannel(final boolean inResponseToAnEarlierException)
|
||||
{
|
||||
try
|
||||
{
|
||||
channel.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (inResponseToAnEarlierException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void connect(final SocketAddress remoteSocketAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
channel.connect(remoteSocketAddress);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw closeDueToError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void bind(final SocketAddress localSocketAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
channel.socket().bind(localSocketAddress);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw closeDueToError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public byte[] sendAndReceive(final @NotNull byte[] sendData) throws IOException
|
||||
{
|
||||
send(sendData);
|
||||
return receive();
|
||||
}
|
||||
|
||||
public void send(final @NotNull byte[] data) throws IOException
|
||||
{
|
||||
int length = data.length;
|
||||
final byte[] dataWithTcpBytes = new byte[length + 2];
|
||||
arraycopy(data, 0, dataWithTcpBytes, 2, length);
|
||||
dataWithTcpBytes[0] = (byte) ((length >>> 8) & 0xFF);
|
||||
dataWithTcpBytes[1] = (byte) (length & 0xFF);
|
||||
|
||||
selectorKeyHelper.blockUntilReady(OP_CONNECT);
|
||||
if (!channel.finishConnect())
|
||||
{
|
||||
throw new IOException("Could not connect to TCP address");
|
||||
}
|
||||
selectorKeyHelper.blockUntilReady(OP_WRITE);
|
||||
channel.write(wrap(dataWithTcpBytes));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private byte[] receive() throws IOException
|
||||
{
|
||||
selectorKeyHelper.blockUntilReady(OP_READ);
|
||||
byte[] buffer = new byte[MaximumDnsMessageSize];
|
||||
long numberOfBytesRead = channel.read(wrap(buffer));
|
||||
if (numberOfBytesRead <= 0)
|
||||
{
|
||||
return EmptyByteArray;
|
||||
}
|
||||
return copyOf(buffer, (int) numberOfBytesRead);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.client.resolvers.protoolClients;
|
||||
|
||||
import com.softwarecraftsmen.CanNeverHappenException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import static java.nio.ByteBuffer.wrap;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import static java.nio.channels.SelectionKey.OP_READ;
|
||||
import static java.nio.channels.SelectionKey.OP_WRITE;
|
||||
import java.nio.channels.Selector;
|
||||
import static java.util.Arrays.copyOf;
|
||||
|
||||
public class UdpProtocolClient implements ProtocolClient
|
||||
{
|
||||
private boolean closed;
|
||||
private final DatagramChannel channel;
|
||||
private final SelectionKey key;
|
||||
private final SelectorKeyHelper selectorKeyHelper;
|
||||
|
||||
public UdpProtocolClient(@Nullable final SocketAddress localSocketAddress, @NotNull final SocketAddress remoteSocketAddress, final int blockInMilliseconds, final int numberOfRetries)
|
||||
{
|
||||
this.closed = true;
|
||||
this.channel = openChannel();
|
||||
try
|
||||
{
|
||||
channel.configureBlocking(false);
|
||||
}
|
||||
catch(final IOException exception)
|
||||
{
|
||||
closeChannel(true);
|
||||
throw new IllegalStateException(exception);
|
||||
}
|
||||
key = obtainSelectorKey(openSelector());
|
||||
bind(localSocketAddress);
|
||||
connect(remoteSocketAddress);
|
||||
|
||||
selectorKeyHelper = new SelectorKeyHelper(key, blockInMilliseconds, numberOfRetries);
|
||||
|
||||
this.closed = false;
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
if (closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
try
|
||||
{
|
||||
closeChannel(false);
|
||||
}
|
||||
catch(IllegalStateException exception)
|
||||
{
|
||||
closeSelector(selector(), true);
|
||||
throw new IllegalStateException(exception);
|
||||
}
|
||||
closeSelector(selector(), false);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"EmptyCatchBlock"})
|
||||
protected void finalize() throws Throwable
|
||||
{
|
||||
super.finalize();
|
||||
try
|
||||
{
|
||||
close();
|
||||
}
|
||||
catch (final Exception exception)
|
||||
{}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"ThrowableInstanceNeverThrown"})
|
||||
private IllegalStateException closeDueToError(final IOException e)
|
||||
{
|
||||
closeChannel(true);
|
||||
closeSelector(selector(), true);
|
||||
return new IllegalStateException(e);
|
||||
}
|
||||
|
||||
private Selector selector() {return key.selector();}
|
||||
|
||||
private SelectionKey obtainSelectorKey(final Selector selector)
|
||||
{
|
||||
try
|
||||
{
|
||||
return channel.register(selector, 0);
|
||||
}
|
||||
catch (final ClosedChannelException e)
|
||||
{
|
||||
closeSelector(selector, true);
|
||||
closeChannel(true);
|
||||
throw new CanNeverHappenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeSelector(final Selector selector, final boolean inResponseToAnEarlierException)
|
||||
{
|
||||
try
|
||||
{
|
||||
selector.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (inResponseToAnEarlierException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private DatagramChannel openChannel()
|
||||
{
|
||||
try
|
||||
{
|
||||
return DatagramChannel.open();
|
||||
}
|
||||
catch (final IOException exception)
|
||||
{
|
||||
throw new CanNeverHappenException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private Selector openSelector()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Selector.open();
|
||||
}
|
||||
catch(IOException exception)
|
||||
{
|
||||
closeChannel(true);
|
||||
throw new CanNeverHappenException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeChannel(final boolean inResponseToAnEarlierException)
|
||||
{
|
||||
try
|
||||
{
|
||||
channel.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (inResponseToAnEarlierException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void connect(final SocketAddress remoteSocketAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
channel.connect(remoteSocketAddress);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw closeDueToError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void bind(final SocketAddress localSocketAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
channel.socket().bind(localSocketAddress);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw closeDueToError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public byte[] sendAndReceive(final @NotNull byte[] sendData) throws IOException
|
||||
{
|
||||
selectorKeyHelper.blockUntilReady(OP_WRITE);
|
||||
send(sendData);
|
||||
return receive();
|
||||
}
|
||||
|
||||
private void send(final @NotNull byte[] data) throws IOException
|
||||
{
|
||||
channel.write(wrap(data));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private byte[] receive() throws IOException
|
||||
{
|
||||
selectorKeyHelper.blockUntilReady(OP_READ);
|
||||
final int maximumMessageSize = 512;
|
||||
byte[] buffer = new byte[maximumMessageSize];
|
||||
long numberOfBytesRead = channel.read(wrap(buffer));
|
||||
if (numberOfBytesRead <= 0)
|
||||
{
|
||||
return EmptyByteArray;
|
||||
}
|
||||
return copyOf(buffer, (int) numberOfBytesRead);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.client.resourceRecordRepositories;
|
||||
|
||||
import com.softwarecraftsmen.Pair;
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import static com.softwarecraftsmen.dns.Seconds.currentTime;
|
||||
import com.softwarecraftsmen.dns.client.resolvers.DnsResolver;
|
||||
import com.softwarecraftsmen.dns.messaging.InternetClassType;
|
||||
import static com.softwarecraftsmen.dns.messaging.Message.allAnswersMatching;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import com.softwarecraftsmen.dns.resourceRecords.ResourceRecord;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class CachingResourceRecordRepository implements ResourceRecordRepository
|
||||
{
|
||||
private final DnsResolver dnsResolver;
|
||||
private final Seconds maximumTimeToLivePermitted;
|
||||
private final Map<Pair<Name, InternetClassType>, Set<ResourceRecord<? extends Name, ? extends Serializable>>> cache;
|
||||
private final SortedMap<Seconds, Set<ResourceRecord<? extends Name, ? extends Serializable>>> bestBeforeTimesForResourceRecords;
|
||||
|
||||
public CachingResourceRecordRepository(final @NotNull DnsResolver dnsResolver, final @NotNull Seconds maximumTimeToLivePermitted)
|
||||
{
|
||||
this.dnsResolver = dnsResolver;
|
||||
this.maximumTimeToLivePermitted = maximumTimeToLivePermitted;
|
||||
cache = new HashMap<Pair<Name, InternetClassType>, Set<ResourceRecord<? extends Name, ? extends Serializable>>>();
|
||||
bestBeforeTimesForResourceRecords = new TreeMap<Seconds, Set<ResourceRecord<? extends Name, ? extends Serializable>>>();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public <T extends Serializable> Set<T> findData(final @NotNull Name name, final @NotNull InternetClassType internetClassType)
|
||||
{
|
||||
removeStaleEntries();
|
||||
|
||||
final Pair<Name, InternetClassType> key = new Pair<Name, InternetClassType>(name, internetClassType);
|
||||
if (!cache.containsKey(key))
|
||||
{
|
||||
cache.put(key, new LinkedHashSet<ResourceRecord<? extends Name, ? extends Serializable>>());
|
||||
}
|
||||
Set<ResourceRecord<? extends Name, ? extends Serializable>> resourceRecords = cache.get(key);
|
||||
if (resourceRecords.isEmpty())
|
||||
{
|
||||
resourceRecords = resolveAndCache(name, internetClassType);
|
||||
}
|
||||
|
||||
return allAnswersMatching(resourceRecords, internetClassType);
|
||||
}
|
||||
|
||||
private void removeStaleEntries()
|
||||
{
|
||||
final SortedMap<Seconds, Set<ResourceRecord<? extends Name, ? extends Serializable>>> map = bestBeforeTimesForResourceRecords.headMap(currentTime());
|
||||
for (Seconds key : map.keySet())
|
||||
{
|
||||
final Set<ResourceRecord<? extends Name, ? extends Serializable>> resourceRecords = map.get(key);
|
||||
for (ResourceRecord<? extends Name, ? extends Serializable> resourceRecord : resourceRecords)
|
||||
{
|
||||
resourceRecord.removeFromCache(cache);
|
||||
}
|
||||
map.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<ResourceRecord<? extends Name, ? extends Serializable>> resolveAndCache(final Name name, final InternetClassType internetClassType)
|
||||
{
|
||||
final Set<ResourceRecord<? extends Name, ? extends Serializable>> resourceRecords = dnsResolver.resolve(name, internetClassType).allResourceRecords();
|
||||
for (ResourceRecord<? extends Name, ? extends Serializable> resourceRecord : resourceRecords)
|
||||
{
|
||||
// TODO: Only code that needs to be clever is Client IpAddress finding code.
|
||||
// BETTER: do a findCanonicalName from insider find Ip address, if result, do query with that else do query with original name
|
||||
resourceRecord.addToCache(maximumTimeToLivePermitted, bestBeforeTimesForResourceRecords, cache);
|
||||
}
|
||||
return resourceRecords;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.client.resourceRecordRepositories;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import com.softwarecraftsmen.dns.messaging.InternetClassType;
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import com.softwarecraftsmen.dns.client.resolvers.DnsResolver;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class NonCachingResourceRecordRepository implements ResourceRecordRepository
|
||||
{
|
||||
private final DnsResolver dnsResolver;
|
||||
|
||||
public NonCachingResourceRecordRepository(final @NotNull DnsResolver dnsResolver)
|
||||
{
|
||||
this.dnsResolver = dnsResolver;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public <T extends Serializable> Set<T> findData(final @NotNull Name name, final @NotNull InternetClassType internetClassType)
|
||||
{
|
||||
return dnsResolver.resolve(name, internetClassType).allAnswersMatching(internetClassType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.client.resourceRecordRepositories;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import com.softwarecraftsmen.dns.messaging.InternetClassType;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface ResourceRecordRepository
|
||||
{
|
||||
@NotNull
|
||||
<T extends Serializable> Set<T> findData(final @NotNull Name name, final @NotNull InternetClassType internetClassType);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.softwarecraftsmen.dns.client.serverAddressFinders;
|
||||
|
||||
import static com.softwarecraftsmen.dns.client.serverAddressFinders.InternetProtocolVersion4LocalHostServerAddressFinder.InternetProtocolVersion4LocalHostServerAddressFinderOnPort53;
|
||||
import static com.softwarecraftsmen.dns.client.serverAddressFinders.PosixServerAddressFinder.CachedPosixServerAddressFinder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
|
||||
public class BindLikeServerAddressFinder implements ServerAddressFinder
|
||||
{
|
||||
public static final BindLikeServerAddressFinder CachedBindLikeServerAddressFinder = new BindLikeServerAddressFinder(CachedPosixServerAddressFinder, InternetProtocolVersion4LocalHostServerAddressFinderOnPort53);
|
||||
|
||||
private final PosixServerAddressFinder posixServerAddressFinder;
|
||||
private final InternetProtocolVersion4LocalHostServerAddressFinder internetProtocolVersion4LocalHostServerAddressFinder;
|
||||
|
||||
public BindLikeServerAddressFinder(final @NotNull PosixServerAddressFinder posixServerAddressFinder, final @NotNull InternetProtocolVersion4LocalHostServerAddressFinder internetProtocolVersion4LocalHostServerAddressFinder)
|
||||
{
|
||||
this.posixServerAddressFinder = posixServerAddressFinder;
|
||||
this.internetProtocolVersion4LocalHostServerAddressFinder = internetProtocolVersion4LocalHostServerAddressFinder;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<InetSocketAddress> find()
|
||||
{
|
||||
final List<InetSocketAddress> addressList = posixServerAddressFinder.find();
|
||||
if (addressList.isEmpty())
|
||||
{
|
||||
return internetProtocolVersion4LocalHostServerAddressFinder.find();
|
||||
}
|
||||
return addressList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.client.serverAddressFinders;
|
||||
|
||||
import static com.softwarecraftsmen.dns.SerializableInternetProtocolAddress.InternetProtocolVersion4LocalHost;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import static java.util.Collections.unmodifiableList;
|
||||
import java.util.List;
|
||||
|
||||
public class InternetProtocolVersion4LocalHostServerAddressFinder implements ServerAddressFinder
|
||||
{
|
||||
public static final InternetProtocolVersion4LocalHostServerAddressFinder InternetProtocolVersion4LocalHostServerAddressFinderOnPort53 = new InternetProtocolVersion4LocalHostServerAddressFinder(StandardUnicastDnsServerPort);
|
||||
private final List<InetSocketAddress> addresses;
|
||||
|
||||
public InternetProtocolVersion4LocalHostServerAddressFinder(final int dnsServerPort)
|
||||
{
|
||||
addresses = unmodifiableList(new ArrayList<InetSocketAddress>()
|
||||
{{
|
||||
add(new InetSocketAddress(InternetProtocolVersion4LocalHost.address, dnsServerPort));
|
||||
}});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<InetSocketAddress> find()
|
||||
{
|
||||
return addresses;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.client.serverAddressFinders;
|
||||
|
||||
import static com.softwarecraftsmen.dns.SerializableInternetProtocolAddress.InternetProtocolVersion6LocalHost;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import static java.util.Collections.unmodifiableList;
|
||||
import java.util.List;
|
||||
|
||||
public class InternetProtocolVersion6LocalHostServerAddressFinder implements ServerAddressFinder
|
||||
{
|
||||
public static final InternetProtocolVersion6LocalHostServerAddressFinder InternetProtocolVersion6LocalHostServerAddressFinderOnPort53 = new InternetProtocolVersion6LocalHostServerAddressFinder(StandardUnicastDnsServerPort);
|
||||
private final List<InetSocketAddress> addresses;
|
||||
|
||||
public InternetProtocolVersion6LocalHostServerAddressFinder(final int dnsServerPort)
|
||||
{
|
||||
addresses = unmodifiableList(new ArrayList<InetSocketAddress>()
|
||||
{{
|
||||
add(new InetSocketAddress(InternetProtocolVersion6LocalHost.address, dnsServerPort));
|
||||
}});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<InetSocketAddress> find()
|
||||
{
|
||||
return addresses;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.softwarecraftsmen.dns.client.serverAddressFinders;
|
||||
|
||||
import com.softwarecraftsmen.dns.SerializableInternetProtocolAddress;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import static java.util.Collections.unmodifiableList;
|
||||
import java.util.List;
|
||||
|
||||
public class KnownServerAddressFinder implements ServerAddressFinder
|
||||
{
|
||||
private List<InetSocketAddress> inetSocketAddresses;
|
||||
|
||||
public KnownServerAddressFinder(final @NotNull SerializableInternetProtocolAddress ... addresses)
|
||||
{
|
||||
final ArrayList<InetSocketAddress> knownAddresses = new ArrayList<InetSocketAddress>();
|
||||
for (SerializableInternetProtocolAddress address : addresses)
|
||||
{
|
||||
knownAddresses.add(new InetSocketAddress(address.address, StandardUnicastDnsServerPort));
|
||||
}
|
||||
inetSocketAddresses = unmodifiableList(knownAddresses);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<InetSocketAddress> find()
|
||||
{
|
||||
return inetSocketAddresses;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.client.serverAddressFinders;
|
||||
|
||||
import com.softwarecraftsmen.CanNeverHappenException;
|
||||
import com.softwarecraftsmen.dns.SerializableInternetProtocolAddress;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.*;
|
||||
import static java.lang.String.format;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import static java.util.Locale.UK;
|
||||
|
||||
public class PosixServerAddressFinder implements ServerAddressFinder
|
||||
{
|
||||
public static final PosixServerAddressFinder CachedPosixServerAddressFinder = new PosixServerAddressFinder();
|
||||
|
||||
@SuppressWarnings({"ThrowFromFinallyBlock"})
|
||||
@NotNull
|
||||
public List<InetSocketAddress> find()
|
||||
{
|
||||
final File resolvConfFile = new File("/etc/resolv.conf");
|
||||
final boolean nameserverDetailsExist = (resolvConfFile.exists() && resolvConfFile.isFile() && resolvConfFile.canRead());
|
||||
if (!nameserverDetailsExist)
|
||||
{
|
||||
return new ArrayList<InetSocketAddress>();
|
||||
}
|
||||
|
||||
final List<InetSocketAddress> domainNameServerIpAddresses;
|
||||
Reader reader = null;
|
||||
boolean thrown = true;
|
||||
try
|
||||
{
|
||||
reader = new FileReader(resolvConfFile);
|
||||
domainNameServerIpAddresses = read(reader);
|
||||
thrown = false;
|
||||
}
|
||||
catch (FileNotFoundException cause)
|
||||
{
|
||||
throw new CanNeverHappenException(cause);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (reader != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
reader.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (!thrown)
|
||||
{
|
||||
throw new CanNeverHappenException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return domainNameServerIpAddresses;
|
||||
}
|
||||
|
||||
private List<InetSocketAddress> read(final Reader reader)
|
||||
{
|
||||
final List<InetSocketAddress> domainNameServerIpAddresses = new ArrayList<InetSocketAddress>();
|
||||
final LineNumberReader lineNumberReader = new LineNumberReader(reader);
|
||||
try
|
||||
{
|
||||
String line;
|
||||
while((line = lineNumberReader.readLine()) != null)
|
||||
{
|
||||
if (!line.startsWith("nameserver"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
domainNameServerIpAddresses.add(parseNameServerLine(line));
|
||||
}
|
||||
return domainNameServerIpAddresses;
|
||||
}
|
||||
catch (final IOException cause)
|
||||
{
|
||||
final class CouldNotReadResolvConfException extends RuntimeException
|
||||
{
|
||||
public CouldNotReadResolvConfException()
|
||||
{
|
||||
super(format(UK, "Could not read line %1$s from /etc/resolv.conf", lineNumberReader.getLineNumber()), cause);
|
||||
}
|
||||
}
|
||||
throw new CouldNotReadResolvConfException();
|
||||
}
|
||||
}
|
||||
|
||||
private InetSocketAddress parseNameServerLine(final String line)
|
||||
{
|
||||
final String domainNameServer = line.substring(10).trim();
|
||||
final Inet4Address address;
|
||||
try
|
||||
{
|
||||
address = SerializableInternetProtocolAddress.serializableInternetProtocolVersion4Address(domainNameServer).address;
|
||||
}
|
||||
catch (final IllegalArgumentException couldNotParseNameServerLine)
|
||||
{
|
||||
final class CouldNotReadNameServerLineInConfException extends RuntimeException
|
||||
{
|
||||
public CouldNotReadNameServerLineInConfException()
|
||||
{
|
||||
super(format(UK, "Could not read line %1$s from /etc/reolv.conf, possibly because we could not parse the name server. Internet Protocol version 6 addresses are not yet supported", line), couldNotParseNameServerLine);
|
||||
}
|
||||
}
|
||||
throw new CouldNotReadNameServerLineInConfException();
|
||||
}
|
||||
return new InetSocketAddress(address, StandardUnicastDnsServerPort);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.client.serverAddressFinders;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
|
||||
public interface ServerAddressFinder
|
||||
{
|
||||
public static final int StandardUnicastDnsServerPort = 53;
|
||||
public static final int StandardMulticastDnsServerPort = 53;
|
||||
|
||||
@NotNull
|
||||
List<InetSocketAddress> find();
|
||||
}
|
||||
17
src/main/java/com/softwarecraftsmen/dns/labels/Label.java
Normal file
17
src/main/java/com/softwarecraftsmen/dns/labels/Label.java
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.labels;
|
||||
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface Label extends Serializable
|
||||
{
|
||||
@NotNull
|
||||
String toStringRepresentation();
|
||||
|
||||
boolean isEmpty();
|
||||
|
||||
int length();
|
||||
}
|
||||
99
src/main/java/com/softwarecraftsmen/dns/labels/ServiceLabel.java
Executable file
99
src/main/java/com/softwarecraftsmen/dns/labels/ServiceLabel.java
Executable file
@@ -0,0 +1,99 @@
|
||||
package com.softwarecraftsmen.dns.labels;
|
||||
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class ServiceLabel implements Label
|
||||
{
|
||||
private final String value;
|
||||
|
||||
public ServiceLabel(final @NotNull String value)
|
||||
{
|
||||
if (value.startsWith("_"))
|
||||
{
|
||||
this.value = value;
|
||||
if (value.length() == 1)
|
||||
{
|
||||
throw new IllegalArgumentException("label must be more than _");
|
||||
}
|
||||
if (value.length() > 15)
|
||||
{
|
||||
throw new ServiceClassLabelMustBeLessThan15CharactersException();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value.length() == 0)
|
||||
{
|
||||
throw new IllegalArgumentException("label must have a substantive value");
|
||||
}
|
||||
if (value.length() > 14)
|
||||
{
|
||||
throw new ServiceClassLabelMustBeLessThan15CharactersException();
|
||||
}
|
||||
this.value = "_" + value;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final ServiceLabel that = (ServiceLabel) o;
|
||||
return value.equals(that.value);
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return value.hashCode();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static ServiceLabel serviceLabel(final @NotNull String label)
|
||||
{
|
||||
return new ServiceLabel(label);
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
writer.writeCharacterString(value);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toStringRepresentation()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public int length()
|
||||
{
|
||||
return value.length();
|
||||
}
|
||||
|
||||
public static class ServiceClassLabelMustBeLessThan15CharactersException extends IllegalArgumentException
|
||||
{
|
||||
public ServiceClassLabelMustBeLessThan15CharactersException()
|
||||
{
|
||||
super("A service class value must be less than 14 characters long");
|
||||
}
|
||||
}
|
||||
}
|
||||
55
src/main/java/com/softwarecraftsmen/dns/labels/ServiceProtocolLabel.java
Executable file
55
src/main/java/com/softwarecraftsmen/dns/labels/ServiceProtocolLabel.java
Executable file
@@ -0,0 +1,55 @@
|
||||
package com.softwarecraftsmen.dns.labels;
|
||||
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Locale.UK;
|
||||
|
||||
public enum ServiceProtocolLabel implements Label
|
||||
{
|
||||
TCP("_tcp"),
|
||||
UDP("_udp");
|
||||
private final String value;
|
||||
|
||||
private ServiceProtocolLabel(final @NotNull String value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
writer.writeCharacterString(value);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toStringRepresentation()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public int length()
|
||||
{
|
||||
return value.length();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static ServiceProtocolLabel toServiceProtocolLabel(final @NotNull String value)
|
||||
{
|
||||
final String searchValue = (value.charAt(0) == '_') ? value : "_" + value;
|
||||
final ServiceProtocolLabel[] serviceProtocolLabels = values();
|
||||
for (ServiceProtocolLabel serviceProtocolLabel : serviceProtocolLabels)
|
||||
{
|
||||
if (serviceProtocolLabel.value.equals(searchValue))
|
||||
{
|
||||
return serviceProtocolLabel;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(format(UK, "The value %1$s is not a valid ServiceProtocolLabel", value));
|
||||
}
|
||||
}
|
||||
158
src/main/java/com/softwarecraftsmen/dns/labels/SimpleLabel.java
Normal file
158
src/main/java/com/softwarecraftsmen/dns/labels/SimpleLabel.java
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.labels;
|
||||
|
||||
import static com.softwarecraftsmen.dns.NonAsciiAndControlCharactersAreNotSupportedInCharacterStringsException.throwExceptionIfUnsupportedCharacterCode;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import static com.softwarecraftsmen.dns.labels.ServiceLabel.serviceLabel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import static java.util.Locale.UK;
|
||||
|
||||
public class SimpleLabel implements Label, Comparable<SimpleLabel>
|
||||
{
|
||||
@NotNull
|
||||
public static final SimpleLabel Empty = new SimpleLabel("");
|
||||
|
||||
private final String value;
|
||||
|
||||
private SimpleLabel(final @NotNull String value)
|
||||
{
|
||||
if (value.length() > 63)
|
||||
{
|
||||
throw new LabelsCanNotBeLongerThan63CharactersException(value);
|
||||
}
|
||||
for (char toWrite : value.toCharArray())
|
||||
{
|
||||
throwExceptionIfUnsupportedCharacterCode(toWrite);
|
||||
if (toWrite == '.')
|
||||
{
|
||||
throw new LabelsCanNotContainPeriodsException(value);
|
||||
}
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static SimpleLabel simpleLabel(final @NotNull String value)
|
||||
{
|
||||
if (value.length() == 0)
|
||||
{
|
||||
return Empty;
|
||||
}
|
||||
return new SimpleLabel(value);
|
||||
}
|
||||
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final SimpleLabel that = (SimpleLabel) o;
|
||||
return value.equals(that.value);
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return value.hashCode();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toStringRepresentation()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<SimpleLabel> labelsFromDottedName(final String dottedName)
|
||||
{
|
||||
final String[] values = dottedName.split("\\.");
|
||||
return new ArrayList<SimpleLabel>()
|
||||
{{
|
||||
for (String value : values)
|
||||
{
|
||||
add(simpleLabel(value));
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return value.length() == 0;
|
||||
}
|
||||
|
||||
public int length()
|
||||
{
|
||||
return value.length();
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
writer.writeCharacterString(value);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ServiceLabel toServiceLabel()
|
||||
{
|
||||
return serviceLabel(value);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ServiceProtocolLabel toServiceProtocolLabel()
|
||||
{
|
||||
try
|
||||
{
|
||||
return ServiceProtocolLabel.toServiceProtocolLabel(value);
|
||||
}
|
||||
catch(IllegalArgumentException e)
|
||||
{
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public int compareTo(final @NotNull SimpleLabel that)
|
||||
{
|
||||
return this.value.compareTo(that.value);
|
||||
}
|
||||
|
||||
public final class LabelsCanNotContainPeriodsException extends IllegalArgumentException
|
||||
{
|
||||
public LabelsCanNotContainPeriodsException(final @NotNull String value)
|
||||
{
|
||||
super(format(UK, "Labels (the strings between dots in a DNS name) can not contain the period character. This label, %1$s, does.", value));
|
||||
}
|
||||
|
||||
public void throwExceptionIfCharacterIsAPeriod(final char toWrite)
|
||||
{
|
||||
if (toWrite == '.')
|
||||
{
|
||||
throw new LabelsCanNotContainPeriodsException(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final class LabelsCanNotBeLongerThan63CharactersException extends IllegalArgumentException
|
||||
{
|
||||
public LabelsCanNotBeLongerThan63CharactersException(final @NotNull String label)
|
||||
{
|
||||
super(format(UK, "Labels (the strings between dots in a DNS name) can not be longer than 63 character. This label, %1$s, is.", label));
|
||||
}
|
||||
}
|
||||
}
|
||||
132
src/main/java/com/softwarecraftsmen/dns/messaging/GenericName.java
Executable file
132
src/main/java/com/softwarecraftsmen/dns/messaging/GenericName.java
Executable file
@@ -0,0 +1,132 @@
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.DomainName;
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import com.softwarecraftsmen.dns.labels.Label;
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import com.softwarecraftsmen.dns.names.PointerName;
|
||||
import com.softwarecraftsmen.dns.labels.ServiceLabel;
|
||||
import com.softwarecraftsmen.dns.names.ServiceName;
|
||||
import com.softwarecraftsmen.dns.labels.ServiceProtocolLabel;
|
||||
import com.softwarecraftsmen.dns.labels.SimpleLabel;
|
||||
import static com.softwarecraftsmen.dns.labels.SimpleLabel.labelsFromDottedName;
|
||||
import com.softwarecraftsmen.dns.messaging.deserializer.BadlyFormedDnsMessageException;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import static com.softwarecraftsmen.toString.ToString.string;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import static java.util.Locale.UK;
|
||||
|
||||
public class GenericName implements Name
|
||||
{
|
||||
private final List<SimpleLabel> labels;
|
||||
|
||||
public GenericName(final @NotNull List<SimpleLabel> labels)
|
||||
{
|
||||
this.labels = labels;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return string(this, labels);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public HostName toHostName()
|
||||
{
|
||||
return new HostName(labels);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DomainName toDomainName()
|
||||
{
|
||||
return new DomainName(labels);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PointerName toPointerName()
|
||||
{
|
||||
return new PointerName(labels);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ServiceName toServiceName() throws BadlyFormedDnsMessageException
|
||||
{
|
||||
final ServiceLabel serviceLabel;
|
||||
try
|
||||
{
|
||||
serviceLabel = labels.get(0).toServiceLabel();
|
||||
}
|
||||
catch(IndexOutOfBoundsException exception)
|
||||
{
|
||||
throw new BadlyFormedDnsMessageException("There must be at least a service class label in a service name", exception);
|
||||
}
|
||||
final ServiceProtocolLabel serviceProtocolLabel;
|
||||
try
|
||||
{
|
||||
serviceProtocolLabel = labels.get(1).toServiceProtocolLabel();
|
||||
}
|
||||
catch(IndexOutOfBoundsException exception)
|
||||
{
|
||||
throw new BadlyFormedDnsMessageException("There must be at least a service protocol label in a service name", exception);
|
||||
}
|
||||
catch(IllegalArgumentException exception)
|
||||
{
|
||||
throw new BadlyFormedDnsMessageException(format(UK, "The service protocol label %1$s was unrecognised", labels.get(1)), exception);
|
||||
}
|
||||
|
||||
final DomainName domainName;
|
||||
try
|
||||
{
|
||||
domainName = new DomainName(labels.subList(2, labels.size()));
|
||||
}
|
||||
catch(IndexOutOfBoundsException exception)
|
||||
{
|
||||
throw new BadlyFormedDnsMessageException("There must be at least one domain label in a service name", exception);
|
||||
}
|
||||
return new ServiceName(serviceLabel, serviceProtocolLabel, domainName);
|
||||
}
|
||||
|
||||
// TODO: Generic name serialization...
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
throw new UnsupportedOperationException("Write some code!");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Label> toLabels()
|
||||
{
|
||||
return new ArrayList<Label>(labels);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static GenericName genericName(final @NotNull String dottedName)
|
||||
{
|
||||
return new GenericName(labelsFromDottedName(dottedName));
|
||||
}
|
||||
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final GenericName that = (GenericName) o;
|
||||
return labels.equals(that.labels);
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return labels.hashCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import static com.softwarecraftsmen.toString.ToString.string;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class GenericResourceRecordData implements Serializable
|
||||
{
|
||||
private final byte[] data;
|
||||
|
||||
public GenericResourceRecordData(final @NotNull byte[] data)
|
||||
{
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return string(this, Arrays.toString(data));
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
writer.writeBytes(data);
|
||||
}
|
||||
}
|
||||
217
src/main/java/com/softwarecraftsmen/dns/messaging/InternetClassType.java
Executable file
217
src/main/java/com/softwarecraftsmen/dns/messaging/InternetClassType.java
Executable file
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.*;
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import com.softwarecraftsmen.dns.messaging.deserializer.AtomicReader;
|
||||
import com.softwarecraftsmen.dns.messaging.deserializer.BadlyFormedDnsMessageException;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.CanonicalNameResourceRecord.canonicalNameResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.GenericResourceRecord.genericResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.HostInformationResourceRecord.hostInformationResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.InternetProtocolVersion4AddressResourceRecord.internetProtocolVersion4AddressResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.InternetProtocolVersion6AddressResourceRecord.internetProtocolVersion6AddressResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.MailExchangeResourceRecord.mailExchangeResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.NameServerResourceRecord.nameServerResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.PointerResourceRecord.pointerResourceRecord;
|
||||
import com.softwarecraftsmen.dns.resourceRecords.ResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.ServiceInformationResourceRecord.serviceInformationResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.StatementOfAuthorityResourceRecord.statementOfAuthorityResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.TextResourceRecord.textResourceRecord;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Locale.UK;
|
||||
|
||||
public enum InternetClassType implements Serializable
|
||||
{
|
||||
A(1, "a host address")
|
||||
{
|
||||
@NotNull
|
||||
public ResourceRecord<? extends Name, ? extends Serializable> createResourceRecord(final @NotNull GenericName owner, final @NotNull QClass qClass, @NotNull final Seconds timeToLive, final @NotNull AtomicReader reader) throws BadlyFormedDnsMessageException
|
||||
{
|
||||
reader.checkLength(Four);
|
||||
return internetProtocolVersion4AddressResourceRecord(owner.toHostName(), timeToLive, reader.readInternetProtocolVersion4Address());
|
||||
}
|
||||
},
|
||||
NS(2, "an authoritative name server")
|
||||
{
|
||||
@NotNull
|
||||
public ResourceRecord<? extends Name, ? extends Serializable> createResourceRecord(final @NotNull GenericName owner, final @NotNull QClass qClass, @NotNull final Seconds timeToLive, final @NotNull AtomicReader reader) throws BadlyFormedDnsMessageException
|
||||
{
|
||||
reader.readLength();
|
||||
return nameServerResourceRecord(owner.toDomainName(), timeToLive, reader.readHostName());
|
||||
}
|
||||
},
|
||||
MD(3, "a mail destination (Obsolete - use MX)"),
|
||||
MF(4, "a mail forwarder (Obsolete - use MX)"),
|
||||
CNAME(5, "the canonical name for an alias")
|
||||
{
|
||||
@NotNull
|
||||
public ResourceRecord<? extends Name, ? extends Serializable> createResourceRecord(final @NotNull GenericName owner, final @NotNull QClass qClass, @NotNull final Seconds timeToLive, final @NotNull AtomicReader reader) throws BadlyFormedDnsMessageException
|
||||
{
|
||||
reader.readLength();
|
||||
return canonicalNameResourceRecord(owner.toHostName(), timeToLive, reader.readHostName());
|
||||
}
|
||||
},
|
||||
SOA(6, "marks the start of a zone of authority")
|
||||
{
|
||||
@NotNull
|
||||
public ResourceRecord<? extends Name, ? extends Serializable> createResourceRecord(final @NotNull GenericName owner, final @NotNull QClass qClass, @NotNull final Seconds timeToLive, final @NotNull AtomicReader reader) throws BadlyFormedDnsMessageException
|
||||
{
|
||||
reader.readLength();
|
||||
return statementOfAuthorityResourceRecord(owner.toDomainName(), timeToLive, reader.readStatementOfAuthority());
|
||||
}
|
||||
},
|
||||
MB(7, "a mailbox domain name (EXPERIMENTAL)"),
|
||||
MG(8, "a mail group member (EXPERIMENTAL)"),
|
||||
MR(9, "a mail rename domain name (EXPERIMENTAL"),
|
||||
NULL(10, "a null RR (EXPERIMENTAL)"),
|
||||
WKS(11, "a well known service description"),
|
||||
PTR(12, "a domain name pointer")
|
||||
{
|
||||
@NotNull
|
||||
public ResourceRecord<? extends Name, ? extends Serializable> createResourceRecord(final @NotNull GenericName owner, final @NotNull QClass qClass, @NotNull final Seconds timeToLive, final @NotNull AtomicReader reader) throws BadlyFormedDnsMessageException
|
||||
{
|
||||
reader.readLength();
|
||||
return pointerResourceRecord(owner.toPointerName(), timeToLive, reader.readHostName());
|
||||
}
|
||||
},
|
||||
HINFO(13, "host information")
|
||||
{
|
||||
@NotNull
|
||||
public ResourceRecord<? extends Name, ? extends Serializable> createResourceRecord(final @NotNull GenericName owner, final @NotNull QClass qClass, @NotNull final Seconds timeToLive, final @NotNull AtomicReader reader) throws BadlyFormedDnsMessageException
|
||||
{
|
||||
return hostInformationResourceRecord(owner.toHostName(), timeToLive, reader.readHostInformation());
|
||||
}
|
||||
},
|
||||
MINFO(14, "mailbox or mail list information"),
|
||||
MX(15, "mail exchange")
|
||||
{
|
||||
@NotNull
|
||||
public ResourceRecord<? extends Name, ? extends Serializable> createResourceRecord(final @NotNull GenericName owner, final @NotNull QClass qClass, @NotNull final Seconds timeToLive, final @NotNull AtomicReader reader) throws BadlyFormedDnsMessageException
|
||||
{
|
||||
reader.readLength();
|
||||
return mailExchangeResourceRecord(owner.toDomainName(), timeToLive, reader.readMailExchange());
|
||||
}
|
||||
},
|
||||
TXT(16, "text strings")
|
||||
{
|
||||
@NotNull
|
||||
public ResourceRecord<? extends Name, ? extends Serializable> createResourceRecord(final @NotNull GenericName owner, final @NotNull QClass qClass, @NotNull final Seconds timeToLive, final @NotNull AtomicReader reader) throws BadlyFormedDnsMessageException
|
||||
{
|
||||
return textResourceRecord(owner.toHostName(), timeToLive, reader.readText());
|
||||
}
|
||||
},
|
||||
RP(17, "for Responsible Person"),
|
||||
AFSDB(18, "for AFS Data Base location"),
|
||||
X25(19, "for X.25 PSDN address"),
|
||||
ISDN(20, "for ISDN address"),
|
||||
RT(21, "for Route Through"),
|
||||
NSAP(22, "for NSAP address, NSAP style A record"),
|
||||
NSAP_PTR(23, "(Unknown)"),
|
||||
SIG(24, "for security signature"),
|
||||
KEY(25, "for security key"),
|
||||
PX(26, "X.400 mail mapping information"),
|
||||
GPOS(27, "Geographical Position"),
|
||||
AAAA(28, "IP6 Address")
|
||||
{
|
||||
@NotNull
|
||||
public ResourceRecord<? extends Name, ? extends Serializable> createResourceRecord(final @NotNull GenericName owner, final @NotNull QClass qClass, @NotNull final Seconds timeToLive, final @NotNull AtomicReader reader) throws BadlyFormedDnsMessageException
|
||||
{
|
||||
reader.checkLength(Sixteen);
|
||||
return internetProtocolVersion6AddressResourceRecord(owner.toHostName(), timeToLive, reader.readInternetProtocolVersion6Address());
|
||||
}
|
||||
},
|
||||
LOC(29, "Location Information"),
|
||||
NXT(30, "Next DomainName - OBSOLETE"),
|
||||
EID(31, "Endpoint Identifier"),
|
||||
NIMLOC(32, "Nimrod Locator"),
|
||||
SRV(33, "Server Selection")
|
||||
{
|
||||
@NotNull
|
||||
public ResourceRecord<? extends Name, ? extends Serializable> createResourceRecord(final @NotNull GenericName owner, final @NotNull QClass qClass, @NotNull final Seconds timeToLive, final @NotNull AtomicReader reader) throws BadlyFormedDnsMessageException
|
||||
{
|
||||
reader.readLength();
|
||||
return serviceInformationResourceRecord(owner.toServiceName(), timeToLive, reader.readServiceInformation());
|
||||
}
|
||||
},
|
||||
ATMA(34, "ATM Address"),
|
||||
NAPTR(35, "Naming Authority Pointer"),
|
||||
KX(36, "Key Exchanger"),
|
||||
CERT(37, "CERT"),
|
||||
A6(38, "A6"),
|
||||
DNAME(39, "DNAME"),
|
||||
SINK(40, "SINK"),
|
||||
OPT(41, "OPT"),
|
||||
APL(42, "APL"),
|
||||
DS(43, "Delegation Signer"),
|
||||
SSHFP(44, "SSH Key Fingerprint"),
|
||||
IPSECKEY(45, "IPSECKEY"),
|
||||
RRSIG(46, "RRSIG"),
|
||||
NSEC(47, "NSEC"),
|
||||
DNSKEY(48, "DNSKEY"),
|
||||
DHCID(49, "DHCID"),
|
||||
NSEC3(50, "NSEC3"),
|
||||
NSEC3PARAM(51, "NSEC3PARAM"),
|
||||
HIP(55, "HostName Identity Protocol"),
|
||||
SPF(99, "(Unknown)"),
|
||||
UINFO(100, "(Unknown)"),
|
||||
UID(101, "(Unknown)"),
|
||||
GID(102, "(Unknown)"),
|
||||
UNSPEC(103, "(Unknown)"),
|
||||
TKEY(249, "Transaction Key"),
|
||||
TSIG(250, "Transaction Signature"),
|
||||
IXFR(251, "incremental transfer"),
|
||||
|
||||
// Stictly speaking, these are QTYPE not TYPE and are only included in the superset
|
||||
AXFR(252, "transfer of an entire zone"),
|
||||
MAILB(253, "mailbox-related RRs (MB, MG or MR)"),
|
||||
MAILA(254, "mail agent RRs (Obsolete - see MX)"),
|
||||
Asterisk(255, "A request for all records"),
|
||||
|
||||
TA(32768, "DNSSEC Trust Authorities"),
|
||||
DLV(32769, "DNSSEC Lookaside Validation");
|
||||
|
||||
private final Unsigned16BitInteger value;
|
||||
private final String description;
|
||||
|
||||
private InternetClassType(final int value, final @NotNull String description)
|
||||
{
|
||||
this.value = unsigned16BitInteger(value);
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
writer.writeUnsigned16BitInteger(value);
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return format(UK, "%1$s (%2$s)", name(), description);
|
||||
}
|
||||
|
||||
public static InternetClassType internetClassType(final @NotNull Unsigned16BitInteger value)
|
||||
{
|
||||
for (InternetClassType internetClassType : values())
|
||||
{
|
||||
if (internetClassType.value.equals(value))
|
||||
{
|
||||
return internetClassType;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(format(UK, "Unrecognised internet class type code %1$s", value));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ResourceRecord<? extends Name, ? extends Serializable> createResourceRecord(final @NotNull GenericName owner, final @NotNull QClass qClass, final @NotNull Seconds timeToLive, final @NotNull AtomicReader reader) throws BadlyFormedDnsMessageException
|
||||
{
|
||||
return genericResourceRecord(owner, this, timeToLive, reader.readData());
|
||||
}
|
||||
}
|
||||
184
src/main/java/com/softwarecraftsmen/dns/messaging/Message.java
Executable file
184
src/main/java/com/softwarecraftsmen/dns/messaging/Message.java
Executable file
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import static com.softwarecraftsmen.dns.messaging.MessageHeader.outboundMessageHeader;
|
||||
import static com.softwarecraftsmen.dns.messaging.MessageId.messageId;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import com.softwarecraftsmen.dns.resourceRecords.ResourceRecord;
|
||||
import static com.softwarecraftsmen.toString.ToString.string;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import java.util.Collection;
|
||||
import static java.util.Collections.emptyList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class Message implements Serializable
|
||||
{
|
||||
private final MessageHeader messageHeader;
|
||||
private final List<Question> questions;
|
||||
private final List<ResourceRecord<? extends Name, ? extends Serializable>> answers;
|
||||
private final List<ResourceRecord<? extends Name, ? extends Serializable>> nameServerAuthorities;
|
||||
private final List<ResourceRecord<? extends Name, ? extends Serializable>> additionalRecords;
|
||||
public static final List<ResourceRecord<? extends Name, ? extends Serializable>> NoResourceRecords = emptyList();
|
||||
|
||||
public Message(final @NotNull MessageHeader messageHeader, final @NotNull List<Question> questions, final List<ResourceRecord<? extends Name, ? extends Serializable>> answers, final List<ResourceRecord<? extends Name, ? extends Serializable>> nameServerAuthorities, final List<ResourceRecord<? extends Name, ? extends Serializable>> additionalRecords)
|
||||
{
|
||||
this.messageHeader = messageHeader;
|
||||
this.questions = questions;
|
||||
this.answers = answers;
|
||||
this.nameServerAuthorities = nameServerAuthorities;
|
||||
this.additionalRecords = additionalRecords;
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
messageHeader.serialize(writer);
|
||||
writeDnsQuestions(writer);
|
||||
writeResourceRecordsWhichAnswerTheQuestion(writer);
|
||||
writeResourceRecordsWhichPointToTheDomainAuthority(writer);
|
||||
writeResourceRecordsWhichMayHoldAdditionalInformation(writer);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return string(this, messageHeader, questions, answers, nameServerAuthorities, additionalRecords);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"RedundantIfStatement"})
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final Message message = (Message) o;
|
||||
|
||||
if (!additionalRecords.equals(message.additionalRecords))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!answers.equals(message.answers))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!messageHeader.equals(message.messageHeader))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!nameServerAuthorities.equals(message.nameServerAuthorities))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!questions.equals(message.questions))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
int result;
|
||||
result = messageHeader.hashCode();
|
||||
result = 31 * result + questions.hashCode();
|
||||
result = 31 * result + answers.hashCode();
|
||||
result = 31 * result + nameServerAuthorities.hashCode();
|
||||
result = 31 * result + additionalRecords.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
private void writeDnsQuestions(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
for(Question question : questions)
|
||||
{
|
||||
question.serialize(writer);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeResourceRecordsWhichAnswerTheQuestion(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
for (ResourceRecord<? extends Name, ? extends Serializable> answer : answers)
|
||||
{
|
||||
answer.serialize(writer);
|
||||
}
|
||||
}
|
||||
|
||||
// These MUST always (on send or receive) be of InternetClassType.NS
|
||||
private void writeResourceRecordsWhichPointToTheDomainAuthority(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
for (ResourceRecord<? extends Name, ? extends Serializable> nameServerAuthority : nameServerAuthorities)
|
||||
{
|
||||
nameServerAuthority.serialize(writer);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeResourceRecordsWhichMayHoldAdditionalInformation(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
for (ResourceRecord<? extends Name, ? extends Serializable> additionalRecord : additionalRecords)
|
||||
{
|
||||
additionalRecord.serialize(writer);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Message query(final @NotNull Question ... questions)
|
||||
{
|
||||
return query(messageId(), questions);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Message query(final @NotNull MessageId messageId, final @NotNull Question ... questions)
|
||||
{
|
||||
final List<Question> questionList = asList(questions);
|
||||
return new Message(outboundMessageHeader(messageId, questionList), questionList, NoResourceRecords, NoResourceRecords, NoResourceRecords);
|
||||
}
|
||||
|
||||
public static Message emptyReply(final @NotNull Message request)
|
||||
{
|
||||
return new Message(MessageHeader.emptyReply(request.messageHeader), request.questions, NoResourceRecords, NoResourceRecords, NoResourceRecords);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<ResourceRecord<? extends Name, ? extends Serializable>> allResourceRecords()
|
||||
{
|
||||
return new LinkedHashSet<ResourceRecord<? extends Name, ? extends Serializable>>()
|
||||
{{
|
||||
addAll(answers);
|
||||
addAll(nameServerAuthorities);
|
||||
addAll(additionalRecords);
|
||||
}};
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@NotNull
|
||||
public <T extends Serializable> Set<T> allAnswersMatching(final @NotNull InternetClassType internetClassType)
|
||||
{
|
||||
return allAnswersMatching(answers, internetClassType);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@NotNull
|
||||
public static <T extends Serializable> Set<T> allAnswersMatching(final @NotNull Collection<ResourceRecord<? extends Name, ? extends Serializable>> resourceRecords, final @NotNull InternetClassType internetClassType)
|
||||
{
|
||||
final Set<T> set = new LinkedHashSet<T>(resourceRecords.size());
|
||||
for (ResourceRecord<? extends Name, ? extends Serializable> answer : resourceRecords)
|
||||
{
|
||||
answer.appendDataIfIs(internetClassType, (Set) set);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
}
|
||||
155
src/main/java/com/softwarecraftsmen/dns/messaging/MessageHeader.java
Executable file
155
src/main/java/com/softwarecraftsmen/dns/messaging/MessageHeader.java
Executable file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import static com.softwarecraftsmen.dns.messaging.MessageHeaderFlags.Query;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import static com.softwarecraftsmen.toString.ToString.string;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MessageHeader implements Serializable
|
||||
{
|
||||
private final MessageId messageId;
|
||||
private final MessageHeaderFlags messageHeaderFlags;
|
||||
private final Unsigned16BitInteger numberOfEntriesInQuestionSection;
|
||||
private final Unsigned16BitInteger numberOfResourceRecordsInAnswerSection;
|
||||
private final Unsigned16BitInteger numberOfNameServerRecordsInAuthoritySection;
|
||||
private final Unsigned16BitInteger numberOfResourceRecordsInAdditionalRecordsAnswerSection;
|
||||
public static final int SizeOfDnsMessageHeader = 12;
|
||||
|
||||
public MessageHeader(final @NotNull MessageId messageId, final @NotNull MessageHeaderFlags messageHeaderFlags, final @NotNull Unsigned16BitInteger numberOfEntriesInQuestionSection, final @NotNull Unsigned16BitInteger numberOfResourceRecordsInAnswerSection, final @NotNull Unsigned16BitInteger numberOfNameServerRecordsInAuthoritySection, final @NotNull Unsigned16BitInteger numberOfResourceRecordsInAdditionalRecordsAnswerSection)
|
||||
{
|
||||
this.messageId = messageId;
|
||||
this.messageHeaderFlags = messageHeaderFlags;
|
||||
this.numberOfEntriesInQuestionSection = numberOfEntriesInQuestionSection;
|
||||
this.numberOfResourceRecordsInAnswerSection = numberOfResourceRecordsInAnswerSection;
|
||||
this.numberOfNameServerRecordsInAuthoritySection = numberOfNameServerRecordsInAuthoritySection;
|
||||
this.numberOfResourceRecordsInAdditionalRecordsAnswerSection = numberOfResourceRecordsInAdditionalRecordsAnswerSection;
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
messageId.serialize(writer);
|
||||
messageHeaderFlags.serialize(writer);
|
||||
writer.writeUnsigned16BitInteger(numberOfEntriesInQuestionSection);
|
||||
writer.writeUnsigned16BitInteger(numberOfResourceRecordsInAnswerSection);
|
||||
writer.writeUnsigned16BitInteger(numberOfNameServerRecordsInAuthoritySection);
|
||||
writer.writeUnsigned16BitInteger(numberOfResourceRecordsInAdditionalRecordsAnswerSection);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger getNumberOfEntriesInQuestionSection()
|
||||
{
|
||||
return numberOfEntriesInQuestionSection;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger getNumberOfResourceRecordsInAnswerSection()
|
||||
{
|
||||
return numberOfResourceRecordsInAnswerSection;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger getNumberOfNameServerRecordsInAuthoritySection()
|
||||
{
|
||||
return numberOfNameServerRecordsInAuthoritySection;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger getNumberOfResourceRecordsInAdditionalRecordsAnswerSection()
|
||||
{
|
||||
return numberOfResourceRecordsInAdditionalRecordsAnswerSection;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static MessageHeader outboundMessageHeader(final @NotNull MessageId messageId, final @NotNull List<Question> questions)
|
||||
{
|
||||
return new MessageHeader(messageId, Query, unsigned16BitInteger(questions.size()), Zero, Zero, Zero);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"SimplifiableIfStatement"})
|
||||
public boolean matchesReply(final @NotNull MessageHeader reply)
|
||||
{
|
||||
if (!messageId.equals(reply.messageId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!messageHeaderFlags.matchesReply(reply.messageHeaderFlags))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return numberOfEntriesInQuestionSection.equals(reply.numberOfEntriesInQuestionSection);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"RedundantIfStatement"})
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final MessageHeader that = (MessageHeader) o;
|
||||
|
||||
if (!messageHeaderFlags.equals(that.messageHeaderFlags))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!messageId.equals(that.messageId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!numberOfEntriesInQuestionSection.equals(that.numberOfEntriesInQuestionSection))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!numberOfNameServerRecordsInAuthoritySection.equals(that.numberOfNameServerRecordsInAuthoritySection))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!numberOfResourceRecordsInAdditionalRecordsAnswerSection.equals(that.numberOfResourceRecordsInAdditionalRecordsAnswerSection))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!numberOfResourceRecordsInAnswerSection.equals(that.numberOfResourceRecordsInAnswerSection))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
int result;
|
||||
result = messageId.hashCode();
|
||||
result = 31 * result + messageHeaderFlags.hashCode();
|
||||
result = 31 * result + numberOfEntriesInQuestionSection.hashCode();
|
||||
result = 31 * result + numberOfResourceRecordsInAnswerSection.hashCode();
|
||||
result = 31 * result + numberOfNameServerRecordsInAuthoritySection.hashCode();
|
||||
result = 31 * result + numberOfResourceRecordsInAdditionalRecordsAnswerSection.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return string(this, messageId, messageHeaderFlags, numberOfEntriesInQuestionSection, numberOfResourceRecordsInAnswerSection, numberOfNameServerRecordsInAuthoritySection, numberOfResourceRecordsInAdditionalRecordsAnswerSection);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static MessageHeader emptyReply(final @NotNull MessageHeader messageHeader)
|
||||
{
|
||||
return new MessageHeader(messageHeader.messageId, MessageHeaderFlags.emptyReply(messageHeader.messageHeaderFlags), One, Zero, Zero, Zero);
|
||||
}
|
||||
}
|
||||
175
src/main/java/com/softwarecraftsmen/dns/messaging/MessageHeaderFlags.java
Executable file
175
src/main/java/com/softwarecraftsmen/dns/messaging/MessageHeaderFlags.java
Executable file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import static com.softwarecraftsmen.dns.messaging.OperationCode.Status;
|
||||
import static com.softwarecraftsmen.dns.messaging.ResponseCode.NoErrorCondition;
|
||||
import static com.softwarecraftsmen.dns.messaging.ResponseCode.ServerFailure;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import static com.softwarecraftsmen.toString.ToString.string;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.Zero;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned3BitInteger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class MessageHeaderFlags implements Serializable
|
||||
{
|
||||
public static final MessageHeaderFlags Query = new MessageHeaderFlags(false, OperationCode.Query, false, false, true, false, Unsigned3BitInteger.Zero, NoErrorCondition);
|
||||
|
||||
private final boolean isResponse;
|
||||
private final OperationCode operationCode;
|
||||
private final boolean authoritativeAnswer;
|
||||
private final boolean truncation;
|
||||
private final boolean recursionDesired;
|
||||
private final boolean recursionAvailable;
|
||||
private final Unsigned3BitInteger z;
|
||||
private final ResponseCode responseCode;
|
||||
|
||||
public MessageHeaderFlags(final boolean isResponse, final OperationCode operationCode, final boolean authoritativeAnswer, final boolean truncation, final boolean recursionDesired, final boolean recursionAvailable, final @NotNull Unsigned3BitInteger z, final @NotNull ResponseCode responseCode)
|
||||
{
|
||||
this.isResponse = isResponse;
|
||||
this.operationCode = operationCode;
|
||||
this.authoritativeAnswer = authoritativeAnswer;
|
||||
this.truncation = truncation;
|
||||
this.recursionDesired = recursionDesired;
|
||||
this.recursionAvailable = recursionAvailable;
|
||||
this.z = z;
|
||||
this.responseCode = responseCode;
|
||||
if (!isResponse)
|
||||
{
|
||||
if (authoritativeAnswer)
|
||||
{
|
||||
throw new IllegalArgumentException("Queries (isReponse=false) can not have authoritativeAnswer=true");
|
||||
}
|
||||
if (truncation)
|
||||
{
|
||||
throw new IllegalArgumentException("Queries (isReponse=false) can not have truncation=true");
|
||||
}
|
||||
if (recursionAvailable)
|
||||
{
|
||||
throw new IllegalArgumentException("Queries (isReponse=false) can not have recursionAvailable=true");
|
||||
}
|
||||
if (responseCode.isError())
|
||||
{
|
||||
throw new IllegalArgumentException("Queries (isReponse=false) can not have responseCode.isError()=true");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
Unsigned16BitInteger unsigned16BitInteger = Zero.setBitIetf(isResponse, 1);
|
||||
unsigned16BitInteger = operationCode.set4Bits(unsigned16BitInteger);
|
||||
unsigned16BitInteger = unsigned16BitInteger.setBitIetf(authoritativeAnswer, 5);
|
||||
unsigned16BitInteger = unsigned16BitInteger.setBitIetf(truncation, 6);
|
||||
unsigned16BitInteger = unsigned16BitInteger.setBitIetf(recursionDesired, 7);
|
||||
unsigned16BitInteger = unsigned16BitInteger.setBitPowerOfTwo(recursionAvailable, 1);
|
||||
unsigned16BitInteger = responseCode.set4Bits(unsigned16BitInteger);
|
||||
writer.writeUnsigned16BitInteger(unsigned16BitInteger);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"RedundantIfStatement"})
|
||||
public boolean matchesReply(final MessageHeaderFlags reply)
|
||||
{
|
||||
if (isResponse)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!reply.isResponse)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!operationCode.equals(reply.operationCode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (recursionDesired != reply.recursionDesired)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"RedundantIfStatement"})
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final MessageHeaderFlags that = (MessageHeaderFlags) o;
|
||||
|
||||
if (authoritativeAnswer != that.authoritativeAnswer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (isResponse != that.isResponse)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (recursionAvailable != that.recursionAvailable)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (recursionDesired != that.recursionDesired)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (truncation != that.truncation)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (operationCode != that.operationCode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!z.equals(that.z))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (responseCode != that.responseCode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
int result;
|
||||
result = (isResponse ? 1 : 0);
|
||||
result = 31 * result + operationCode.hashCode();
|
||||
result = 31 * result + (authoritativeAnswer ? 1 : 0);
|
||||
result = 31 * result + (truncation ? 1 : 0);
|
||||
result = 31 * result + (recursionDesired ? 1 : 0);
|
||||
result = 31 * result + (recursionAvailable ? 1 : 0);
|
||||
result = 31 * result + responseCode.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return string(this, isResponse, operationCode, authoritativeAnswer, truncation, recursionDesired, recursionAvailable, responseCode);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static MessageHeaderFlags emptyReply(final @NotNull MessageHeaderFlags messageHeaderFlags)
|
||||
{
|
||||
return new MessageHeaderFlags(true, Status, false, false, messageHeaderFlags.recursionDesired, false, Unsigned3BitInteger.Zero, ServerFailure);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static MessageHeaderFlags reply(final boolean recursionDesired)
|
||||
{
|
||||
return new MessageHeaderFlags(true, Status, false, false, recursionDesired, false, Unsigned3BitInteger.Zero, NoErrorCondition);
|
||||
}
|
||||
}
|
||||
72
src/main/java/com/softwarecraftsmen/dns/messaging/MessageId.java
Executable file
72
src/main/java/com/softwarecraftsmen/dns/messaging/MessageId.java
Executable file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import static com.softwarecraftsmen.toString.ToString.string;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.MaximumValue;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class MessageId implements Serializable
|
||||
{
|
||||
private final Unsigned16BitInteger messageId;
|
||||
|
||||
public MessageId(final @NotNull Unsigned16BitInteger messageId)
|
||||
{
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
private static final Object lockObject = new Object();
|
||||
|
||||
private static Unsigned16BitInteger lastMessageId = MaximumValue;
|
||||
|
||||
private static Unsigned16BitInteger getMessageId()
|
||||
{
|
||||
synchronized(lockObject)
|
||||
{
|
||||
lastMessageId = lastMessageId.increment();
|
||||
}
|
||||
return lastMessageId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static MessageId messageId()
|
||||
{
|
||||
return new MessageId(getMessageId());
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
writer.writeUnsigned16BitInteger(messageId);
|
||||
}
|
||||
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final MessageId that = (MessageId) o;
|
||||
return messageId.equals(that.messageId);
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return messageId.hashCode();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return string(this, messageId);
|
||||
}
|
||||
}
|
||||
45
src/main/java/com/softwarecraftsmen/dns/messaging/OperationCode.java
Executable file
45
src/main/java/com/softwarecraftsmen/dns/messaging/OperationCode.java
Executable file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned4BitInteger;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned4BitInteger.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Locale.UK;
|
||||
|
||||
public enum OperationCode
|
||||
{
|
||||
Query(Zero),
|
||||
InverseQuery(One),
|
||||
Status(Two);
|
||||
|
||||
private final Unsigned4BitInteger unsigned4BitInteger;
|
||||
|
||||
private OperationCode(final @NotNull Unsigned4BitInteger unsigned4BitInteger)
|
||||
{
|
||||
this.unsigned4BitInteger = unsigned4BitInteger;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger set4Bits(final @NotNull Unsigned16BitInteger unsigned16BitInteger)
|
||||
{
|
||||
return unsigned16BitInteger.set4BitsIetf(unsigned4BitInteger, 1);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static OperationCode operationCode(final @NotNull Unsigned4BitInteger unsigned4BitInteger)
|
||||
{
|
||||
for (OperationCode operationCode : values())
|
||||
{
|
||||
if (operationCode.unsigned4BitInteger.equals(unsigned4BitInteger))
|
||||
{
|
||||
return operationCode;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(format(UK, "No OperationCode known for %1$s", unsigned4BitInteger));
|
||||
}
|
||||
}
|
||||
64
src/main/java/com/softwarecraftsmen/dns/messaging/QClass.java
Executable file
64
src/main/java/com/softwarecraftsmen/dns/messaging/QClass.java
Executable file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.unsigned16BitInteger;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Locale.UK;
|
||||
|
||||
public enum QClass implements Serializable
|
||||
{
|
||||
Reserved0000(unsigned16BitInteger(0x0000), "Reserved", false, true, false),
|
||||
Internet(unsigned16BitInteger(0x0001), "Internet", false, false, false),
|
||||
CSNET(unsigned16BitInteger(0x0002), "CS", true, false, false),
|
||||
Chaos(unsigned16BitInteger(0x0003), "CH", false, true, false),
|
||||
Hesiod(unsigned16BitInteger(0x0004), "HS", false, false, false),
|
||||
Any(unsigned16BitInteger(0x00FF), "Any", false, false, true),
|
||||
ReservedFFFF(unsigned16BitInteger(0x0000), "Reserved", false, true, false),
|
||||
;
|
||||
|
||||
private final Unsigned16BitInteger value;
|
||||
private final String description;
|
||||
private final boolean obsolete;
|
||||
private final boolean reserved;
|
||||
private final boolean isOnlyQClass;
|
||||
|
||||
private QClass(final @NotNull Unsigned16BitInteger value, final @NotNull String description, final boolean obsolete, final boolean reserved, final boolean isOnlyQClass)
|
||||
{
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
this.obsolete = obsolete;
|
||||
this.reserved = reserved;
|
||||
this.isOnlyQClass = isOnlyQClass;
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
writer.writeUnsigned16BitInteger(value);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return format(UK, "%1$s (%2$s) (%3$s) (%4$s) (%5$s)", name(), description, obsolete ? "obsolete" : "current", reserved ? "reserved" : "unreserved", isOnlyQClass ? "is only QCLASS" : "is not only QCLASS");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static QClass qclass(final @NotNull Unsigned16BitInteger value)
|
||||
{
|
||||
for (QClass qClass : values())
|
||||
{
|
||||
if (qClass.value.equals(value))
|
||||
{
|
||||
return qClass;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(format(UK, "Unrecognised class code %1$s", value));
|
||||
}
|
||||
}
|
||||
82
src/main/java/com/softwarecraftsmen/dns/messaging/Question.java
Executable file
82
src/main/java/com/softwarecraftsmen/dns/messaging/Question.java
Executable file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import static com.softwarecraftsmen.toString.ToString.string;
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import static com.softwarecraftsmen.dns.messaging.QClass.Internet;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class Question implements Serializable
|
||||
{
|
||||
private final Name name;
|
||||
private final InternetClassType internetClassType;
|
||||
private final QClass qClass;
|
||||
|
||||
public Question(final @NotNull Name name, final @NotNull InternetClassType internetClassType, final @NotNull QClass qClass)
|
||||
{
|
||||
this.name = name;
|
||||
this.internetClassType = internetClassType;
|
||||
this.qClass = qClass;
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
name.serialize(writer);
|
||||
internetClassType.serialize(writer);
|
||||
qClass.serialize(writer);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Question internetQuestion(final @NotNull Name name, final @NotNull InternetClassType internetClassType)
|
||||
{
|
||||
return new Question(name, internetClassType, Internet);
|
||||
}
|
||||
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final Question question = (Question) o;
|
||||
if (internetClassType != question.internetClassType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!name.equals(question.name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
//noinspection RedundantIfStatement
|
||||
if (qClass != question.qClass)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
int result;
|
||||
result = name.hashCode();
|
||||
result = 31 * result + internetClassType.hashCode();
|
||||
result = 31 * result + qClass.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return string(this, name, internetClassType, qClass);
|
||||
}
|
||||
}
|
||||
60
src/main/java/com/softwarecraftsmen/dns/messaging/ResponseCode.java
Executable file
60
src/main/java/com/softwarecraftsmen/dns/messaging/ResponseCode.java
Executable file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned4BitInteger;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned4BitInteger.Zero;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned4BitInteger.One;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned4BitInteger.Two;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned4BitInteger.Three;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned4BitInteger.Four;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned4BitInteger.Five;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Locale.UK;
|
||||
|
||||
public enum ResponseCode
|
||||
{
|
||||
NoErrorCondition(Zero, false),
|
||||
FormatError(One, true),
|
||||
ServerFailure(Two, true),
|
||||
NameError(Three, true),
|
||||
NotImplemented(Four, true),
|
||||
Refused(Five, true);
|
||||
|
||||
private final Unsigned4BitInteger unsigned4BitInteger;
|
||||
private final boolean isError;
|
||||
|
||||
private ResponseCode(final @NotNull Unsigned4BitInteger unsigned4BitInteger, final boolean isError)
|
||||
{
|
||||
this.unsigned4BitInteger = unsigned4BitInteger;
|
||||
this.isError = isError;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger set4Bits(final @NotNull Unsigned16BitInteger unsigned16BitInteger)
|
||||
{
|
||||
return unsigned16BitInteger.set4BitsIetf(unsigned4BitInteger, 4);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static ResponseCode responseCode(final Unsigned4BitInteger unsigned4BitInteger)
|
||||
{
|
||||
for (ResponseCode responseCode : values())
|
||||
{
|
||||
if (responseCode.unsigned4BitInteger.equals(unsigned4BitInteger))
|
||||
{
|
||||
return responseCode;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(format(UK, "No ResponseCode known for %1$s", unsigned4BitInteger));
|
||||
}
|
||||
|
||||
public boolean isError()
|
||||
{
|
||||
return isError;
|
||||
}
|
||||
}
|
||||
305
src/main/java/com/softwarecraftsmen/dns/messaging/deserializer/AtomicReader.java
Executable file
305
src/main/java/com/softwarecraftsmen/dns/messaging/deserializer/AtomicReader.java
Executable file
@@ -0,0 +1,305 @@
|
||||
package com.softwarecraftsmen.dns.messaging.deserializer;
|
||||
|
||||
import com.softwarecraftsmen.CanNeverHappenException;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.Four;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.Sixteen;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned3BitInteger;
|
||||
import com.softwarecraftsmen.dns.*;
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import com.softwarecraftsmen.dns.names.DomainName;
|
||||
import com.softwarecraftsmen.dns.MailBox;
|
||||
import com.softwarecraftsmen.dns.labels.Label;
|
||||
import com.softwarecraftsmen.dns.labels.SimpleLabel;
|
||||
import static com.softwarecraftsmen.dns.MailBox.mailBox;
|
||||
import static com.softwarecraftsmen.dns.Seconds.seconds;
|
||||
import static com.softwarecraftsmen.dns.SerializableInternetProtocolAddress.serializableInternetProtocolAddress;
|
||||
import com.softwarecraftsmen.dns.messaging.QClass;
|
||||
import com.softwarecraftsmen.dns.messaging.GenericName;
|
||||
import com.softwarecraftsmen.dns.messaging.GenericResourceRecordData;
|
||||
import com.softwarecraftsmen.dns.messaging.InternetClassType;
|
||||
import com.softwarecraftsmen.dns.messaging.MessageHeaderFlags;
|
||||
import com.softwarecraftsmen.dns.messaging.MessageId;
|
||||
import com.softwarecraftsmen.dns.messaging.OperationCode;
|
||||
import static com.softwarecraftsmen.dns.messaging.OperationCode.operationCode;
|
||||
import com.softwarecraftsmen.dns.messaging.ResponseCode;
|
||||
import static com.softwarecraftsmen.dns.messaging.QClass.qclass;
|
||||
import static com.softwarecraftsmen.dns.messaging.InternetClassType.internetClassType;
|
||||
import static com.softwarecraftsmen.dns.messaging.ResponseCode.responseCode;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import static java.util.Locale.UK;
|
||||
|
||||
public class AtomicReader
|
||||
{
|
||||
private final ByteArrayReader reader;
|
||||
|
||||
public AtomicReader(final @NotNull ByteArrayReader reader)
|
||||
{
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public MessageId readMessageId()
|
||||
{
|
||||
reader.moveToOffset(0);
|
||||
return new MessageId(reader.readUnsigned16BitInteger());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public MessageHeaderFlags readMessageHeaderFlags() throws BadlyFormedDnsMessageException, TruncatedDnsMessageException
|
||||
{
|
||||
reader.moveToOffset(2);
|
||||
final Unsigned16BitInteger unsigned16BitInteger = reader.readUnsigned16BitInteger();
|
||||
final boolean isResponse = unsigned16BitInteger.getBitIetf(0);
|
||||
final OperationCode operationCode;
|
||||
try
|
||||
{
|
||||
operationCode = operationCode(unsigned16BitInteger.getUnsigned4BitIntegerIetf(1));
|
||||
}
|
||||
catch (IllegalArgumentException exception)
|
||||
{
|
||||
throw new BadlyFormedDnsMessageException("Could not deserialize header flag operation code", exception);
|
||||
}
|
||||
final boolean authoritativeAnswer = unsigned16BitInteger.getBitIetf(5);
|
||||
final boolean truncation = unsigned16BitInteger.getBitIetf(6);
|
||||
if (isResponse && truncation)
|
||||
{
|
||||
throw new TruncatedDnsMessageException();
|
||||
}
|
||||
final boolean recursionDesired = unsigned16BitInteger.getBitIetf(7);
|
||||
final boolean recursionAvailable = unsigned16BitInteger.getBitIetf(8);
|
||||
final Unsigned3BitInteger z = unsigned16BitInteger.getThreeBitsIetf(9);
|
||||
|
||||
final ResponseCode responseCode;
|
||||
try
|
||||
{
|
||||
responseCode = responseCode(unsigned16BitInteger.getUnsigned4BitIntegerIetf(12));
|
||||
}
|
||||
catch (IllegalArgumentException exception)
|
||||
{
|
||||
throw new BadlyFormedDnsMessageException("Could not deserialize header flag response code", exception);
|
||||
}
|
||||
return new MessageHeaderFlags(isResponse, operationCode, authoritativeAnswer, truncation, recursionDesired, recursionAvailable, z, responseCode);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger readNumberOfEntriesInQuestionSection()
|
||||
{
|
||||
reader.moveToOffset(4);
|
||||
return readLength();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger readNumberOfResourceRecordsInAnswerSection()
|
||||
{
|
||||
reader.moveToOffset(6);
|
||||
return readLength();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger readNumberOfNameServerRecordsInAuthoritySection()
|
||||
{
|
||||
reader.moveToOffset(8);
|
||||
return readLength();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger readNumberOfResourceRecordsInTheAdditionalRecordsAnswerSection()
|
||||
{
|
||||
reader.moveToOffset(10);
|
||||
return readLength();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public InternetClassType readInternetClassType() throws BadlyFormedDnsMessageException
|
||||
{
|
||||
try
|
||||
{
|
||||
return internetClassType(reader.readUnsigned16BitInteger());
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
throw new BadlyFormedDnsMessageException("Could not understand QClass value", e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public QClass readClass() throws BadlyFormedDnsMessageException
|
||||
{
|
||||
try
|
||||
{
|
||||
return qclass(reader.readUnsigned16BitInteger());
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
throw new BadlyFormedDnsMessageException("Could not understand QClass value", e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public GenericName readGenericName()
|
||||
{
|
||||
return new GenericName(readLabels());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DomainName readDomainName()
|
||||
{
|
||||
return new DomainName(readLabels());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public HostName readHostName()
|
||||
{
|
||||
return new HostName(readLabels());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public MailBox readMailBox() throws BadlyFormedDnsMessageException
|
||||
{
|
||||
final List<SimpleLabel> labels = readLabels();
|
||||
if (labels.size() < 2)
|
||||
{
|
||||
throw new BadlyFormedDnsMessageException("A mailbox must have more than one label");
|
||||
}
|
||||
final Label userName = labels.get(0);
|
||||
return mailBox(userName.toStringRepresentation(), new DomainName(labels.subList(1, labels.size() - 1)));
|
||||
}
|
||||
|
||||
private List<SimpleLabel> readLabels()
|
||||
{
|
||||
final LabelsReader labelsReader = new LabelsReader(reader);
|
||||
final List<SimpleLabel> labels = new ArrayList<SimpleLabel>();
|
||||
labelsReader.readLabels(labels);
|
||||
return labels;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public SerializableInternetProtocolAddress<Inet4Address> readInternetProtocolVersion4Address()
|
||||
{
|
||||
final byte[] rawBytes = reader.readRawByteArray(Four);
|
||||
try
|
||||
{
|
||||
return serializableInternetProtocolAddress((Inet4Address) Inet4Address.getByAddress(rawBytes));
|
||||
}
|
||||
catch (UnknownHostException e)
|
||||
{
|
||||
throw new CanNeverHappenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public SerializableInternetProtocolAddress<Inet6Address> readInternetProtocolVersion6Address()
|
||||
{
|
||||
final byte[] rawBytes = reader.readRawByteArray(Sixteen);
|
||||
try
|
||||
{
|
||||
return serializableInternetProtocolAddress((Inet6Address) Inet6Address.getByAddress(rawBytes));
|
||||
}
|
||||
catch (UnknownHostException e)
|
||||
{
|
||||
throw new CanNeverHappenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void moveToOffset(final int offset)
|
||||
{
|
||||
reader.moveToOffset(offset);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Seconds readTimeToLive()
|
||||
{
|
||||
return seconds(reader.readUnsigned32BitInteger());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public GenericResourceRecordData readData()
|
||||
{
|
||||
final Unsigned16BitInteger lengthOfData = readLength();
|
||||
return new GenericResourceRecordData(reader.readRawByteArray(lengthOfData));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public MailExchange readMailExchange()
|
||||
{
|
||||
return new MailExchange
|
||||
(
|
||||
reader.readUnsigned16BitInteger(),
|
||||
readHostName()
|
||||
);
|
||||
}
|
||||
|
||||
public void checkLength(final @NotNull Unsigned16BitInteger expectedLength) throws BadlyFormedDnsMessageException
|
||||
{
|
||||
final Unsigned16BitInteger lengthOfData = readLength();
|
||||
if (!lengthOfData.equals(expectedLength))
|
||||
{
|
||||
throw new BadlyFormedDnsMessageException(format(UK, "Expected length %1$s but was %2$s", expectedLength, lengthOfData));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger readLength()
|
||||
{
|
||||
return reader.readUnsigned16BitInteger();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public HostInformation readHostInformation()
|
||||
{
|
||||
readLength();
|
||||
return new HostInformation
|
||||
(
|
||||
reader.readAsciiString(readLength()),
|
||||
reader.readAsciiString(readLength())
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Text readText()
|
||||
{
|
||||
final List<String> lines = new ArrayList<String>();
|
||||
final Unsigned16BitInteger lengthOfData = readLength();
|
||||
final long finalPosition = reader.currentPosition() + lengthOfData.toLong();
|
||||
while(finalPosition > reader.currentPosition())
|
||||
{
|
||||
lines.add(reader.readAsciiString(readLength()));
|
||||
}
|
||||
return new Text(lines);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public StatementOfAuthority readStatementOfAuthority() throws BadlyFormedDnsMessageException
|
||||
{
|
||||
return new StatementOfAuthority
|
||||
(
|
||||
readHostName(),
|
||||
readMailBox(),
|
||||
reader.readUnsigned32BitInteger(),
|
||||
readTimeToLive(),
|
||||
readTimeToLive(),
|
||||
readTimeToLive()
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ServiceInformation readServiceInformation()
|
||||
{
|
||||
return new ServiceInformation
|
||||
(
|
||||
reader.readUnsigned16BitInteger(),
|
||||
reader.readUnsigned16BitInteger(),
|
||||
reader.readUnsigned16BitInteger(),
|
||||
readHostName()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.softwarecraftsmen.dns.messaging.deserializer;
|
||||
|
||||
public final class BadlyFormedDnsMessageException extends Exception
|
||||
{
|
||||
public BadlyFormedDnsMessageException(final String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
public BadlyFormedDnsMessageException(final String message, final Exception exception)
|
||||
{
|
||||
super(message, exception);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.softwarecraftsmen.dns.messaging.deserializer;
|
||||
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned32BitInteger;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned8BitInteger;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.unsigned16BitInteger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ByteArrayReader
|
||||
{
|
||||
private final byte[] bytes;
|
||||
private int currentPosition;
|
||||
private final int count;
|
||||
|
||||
public ByteArrayReader(final @NotNull byte[] bytes)
|
||||
{
|
||||
this.bytes = bytes;
|
||||
this.currentPosition = 0;
|
||||
this.count = bytes.length;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned8BitInteger readUnsigned8BitInteger()
|
||||
{
|
||||
return new Unsigned8BitInteger(read());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger readUnsigned16BitInteger()
|
||||
{
|
||||
return unsigned16BitInteger((read() << 8) + read());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned32BitInteger readUnsigned32BitInteger()
|
||||
{
|
||||
return readUnsigned16BitInteger().leftShift16().add(readUnsigned16BitInteger());
|
||||
}
|
||||
|
||||
public char readByteAsAsciiCharacter()
|
||||
{
|
||||
return readUnsigned8BitInteger().toAsciiCharacter();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String readAsciiString(final @NotNull Unsigned8BitInteger numberOfCharacters)
|
||||
{
|
||||
return readAsciiString(numberOfCharacters.createCharacterArray());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String readAsciiString(final Unsigned16BitInteger numberOfCharacters)
|
||||
{
|
||||
return readAsciiString(numberOfCharacters.createCharacterArray());
|
||||
}
|
||||
|
||||
private String readAsciiString(final char[] asciiCharacters)
|
||||
{
|
||||
for (int characterIndex = 0; characterIndex < asciiCharacters.length; characterIndex++)
|
||||
{
|
||||
asciiCharacters[characterIndex] = readByteAsAsciiCharacter();
|
||||
}
|
||||
return String.valueOf(asciiCharacters);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public byte[] readRawByteArray(final Unsigned16BitInteger numberOfBytes)
|
||||
{
|
||||
final byte[] byteArray = numberOfBytes.createByteArray();
|
||||
for (int byteIndex = 0; byteIndex < byteArray.length; byteIndex++)
|
||||
{
|
||||
byteArray[byteIndex] = rawRead();
|
||||
}
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
public void moveToOffset(final int offset)
|
||||
{
|
||||
this.currentPosition = offset;
|
||||
}
|
||||
|
||||
private int read()
|
||||
{
|
||||
if (currentPosition >= count)
|
||||
{
|
||||
throw new IllegalStateException("You've read beyond the end of the byte array");
|
||||
}
|
||||
return (rawRead() & 0xFF);
|
||||
}
|
||||
|
||||
private byte rawRead()
|
||||
{
|
||||
return bytes[currentPosition++];
|
||||
}
|
||||
|
||||
public int currentPosition()
|
||||
{
|
||||
return currentPosition;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.softwarecraftsmen.dns.messaging.deserializer;
|
||||
|
||||
import com.softwarecraftsmen.dns.labels.SimpleLabel;
|
||||
import static com.softwarecraftsmen.dns.labels.SimpleLabel.simpleLabel;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned8BitInteger;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned8BitInteger.Zero;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LabelsReader
|
||||
{
|
||||
private final ByteArrayReader reader;
|
||||
private static final Unsigned8BitInteger TerminalLabel = Zero;
|
||||
private static final Unsigned8BitInteger CompressedNameMask = new Unsigned8BitInteger((1 << 7) + (1 << 6));
|
||||
private static final Unsigned8BitInteger InverseCompressedNameMask = CompressedNameMask.not();
|
||||
|
||||
public LabelsReader(final @NotNull ByteArrayReader reader)
|
||||
{
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
public void readLabels(final @NotNull List<SimpleLabel> appendTo)
|
||||
{
|
||||
boolean continueReadingLabels = true;
|
||||
while(continueReadingLabels)
|
||||
{
|
||||
continueReadingLabels = readLabelOrJumpToLabels(appendTo);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean readLabelOrJumpToLabels(final @NotNull List<SimpleLabel> appendTo)
|
||||
{
|
||||
final Unsigned8BitInteger potentialNumberOfCharacters = reader.readUnsigned8BitInteger();
|
||||
if (isCompressedName(potentialNumberOfCharacters))
|
||||
{
|
||||
final Unsigned8BitInteger upperOffset = potentialNumberOfCharacters.and(InverseCompressedNameMask);
|
||||
final Unsigned8BitInteger lowerOffset = reader.readUnsigned8BitInteger();
|
||||
final int offset = upperOffset.shiftToSigned32BitInteger(lowerOffset);
|
||||
final int currentPosition = reader.currentPosition();
|
||||
reader.moveToOffset(offset);
|
||||
readLabels(appendTo);
|
||||
reader.moveToOffset(currentPosition);
|
||||
return false;
|
||||
}
|
||||
else if (potentialNumberOfCharacters.equals(TerminalLabel))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
appendTo.add(readLabel(potentialNumberOfCharacters));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private SimpleLabel readLabel(final Unsigned8BitInteger potentialNumberOfCharacters)
|
||||
{
|
||||
return simpleLabel(reader.readAsciiString(potentialNumberOfCharacters));
|
||||
}
|
||||
|
||||
private boolean isCompressedName(final @NotNull Unsigned8BitInteger numberOfCharacters)
|
||||
{
|
||||
return (numberOfCharacters.and(CompressedNameMask)).equals(CompressedNameMask);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging.deserializer;
|
||||
|
||||
import com.softwarecraftsmen.dns.messaging.Question;
|
||||
import com.softwarecraftsmen.dns.messaging.Message;
|
||||
import com.softwarecraftsmen.dns.messaging.MessageHeader;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import static com.softwarecraftsmen.dns.messaging.MessageHeader.SizeOfDnsMessageHeader;
|
||||
import com.softwarecraftsmen.dns.resourceRecords.ResourceRecord;
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import static java.util.Locale.UK;
|
||||
|
||||
public class MessageDeserializer
|
||||
{
|
||||
private final StructureReader reader;
|
||||
|
||||
public MessageDeserializer(final @NotNull byte[] message) throws BadlyFormedDnsMessageException
|
||||
{
|
||||
if (message.length < SizeOfDnsMessageHeader)
|
||||
{
|
||||
throw new BadlyFormedDnsMessageException("The message is less than the length of the fixed size DNS MessageHeader");
|
||||
}
|
||||
this.reader = new StructureReader(new AtomicReader(new ByteArrayReader(message)));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Message readMessage() throws BadlyFormedDnsMessageException, TruncatedDnsMessageException
|
||||
{
|
||||
final MessageHeader messageHeader = reader.readMessageHeader();
|
||||
final List<Question> questionList = readQuestions(messageHeader.getNumberOfEntriesInQuestionSection());
|
||||
final List<ResourceRecord<? extends Name, ? extends Serializable>> answers = readResourceRecords(messageHeader.getNumberOfResourceRecordsInAnswerSection());
|
||||
final List<ResourceRecord<? extends Name, ? extends Serializable>> nameServerAuthorities = readResourceRecords(messageHeader.getNumberOfNameServerRecordsInAuthoritySection());
|
||||
final List<ResourceRecord<? extends Name, ? extends Serializable>> additionalRecords = readResourceRecords(messageHeader.getNumberOfResourceRecordsInAdditionalRecordsAnswerSection());
|
||||
return new Message
|
||||
(
|
||||
messageHeader,
|
||||
questionList,
|
||||
answers,
|
||||
nameServerAuthorities,
|
||||
additionalRecords
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<Question> readQuestions(final @NotNull Unsigned16BitInteger numberOfQuestions) throws BadlyFormedDnsMessageException
|
||||
{
|
||||
final List<Question> questions = new ArrayList<Question>();
|
||||
final long longNumberOfQuestions = numberOfQuestions.toLong();
|
||||
long index = 0;
|
||||
while(index++ < longNumberOfQuestions)
|
||||
{
|
||||
questions.add(reader.readQuestion());
|
||||
}
|
||||
return questions;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<ResourceRecord<? extends Name, ? extends Serializable>> readResourceRecords(final @NotNull Unsigned16BitInteger numberOfResourceRecords) throws BadlyFormedDnsMessageException
|
||||
{
|
||||
final List<ResourceRecord<? extends Name, ? extends Serializable>> resourceRecords = new ArrayList<ResourceRecord<? extends Name, ? extends Serializable>>();
|
||||
final long longNumberOfResourceRecords = numberOfResourceRecords.toLong();
|
||||
long index = 0;
|
||||
while(index++ < longNumberOfResourceRecords)
|
||||
{
|
||||
try
|
||||
{
|
||||
resourceRecords.add(reader.readResourceRecord());
|
||||
}
|
||||
catch(BadlyFormedDnsMessageException cause)
|
||||
{
|
||||
throw new BadlyFormedDnsMessageException(format(UK, "Failed to read resource record number %1$s of %2$s", index, numberOfResourceRecords), cause);
|
||||
}
|
||||
}
|
||||
return resourceRecords;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.softwarecraftsmen.dns.messaging.deserializer;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import com.softwarecraftsmen.dns.messaging.GenericName;
|
||||
import com.softwarecraftsmen.dns.messaging.InternetClassType;
|
||||
import com.softwarecraftsmen.dns.messaging.MessageHeader;
|
||||
import com.softwarecraftsmen.dns.messaging.Question;
|
||||
import com.softwarecraftsmen.dns.messaging.QClass;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import com.softwarecraftsmen.dns.resourceRecords.ResourceRecord;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class StructureReader
|
||||
{
|
||||
private final AtomicReader reader;
|
||||
|
||||
public StructureReader(final @NotNull AtomicReader reader)
|
||||
{
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public MessageHeader readMessageHeader() throws BadlyFormedDnsMessageException, TruncatedDnsMessageException
|
||||
{
|
||||
return new MessageHeader
|
||||
(
|
||||
reader.readMessageId(),
|
||||
reader.readMessageHeaderFlags(),
|
||||
reader.readNumberOfEntriesInQuestionSection(),
|
||||
reader.readNumberOfResourceRecordsInAnswerSection(),
|
||||
reader.readNumberOfNameServerRecordsInAuthoritySection(),
|
||||
reader.readNumberOfResourceRecordsInTheAdditionalRecordsAnswerSection()
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Question readQuestion() throws BadlyFormedDnsMessageException
|
||||
{
|
||||
return new Question
|
||||
(
|
||||
reader.readGenericName(),
|
||||
reader.readInternetClassType(),
|
||||
reader.readClass()
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ResourceRecord<? extends Name, ? extends Serializable> readResourceRecord() throws BadlyFormedDnsMessageException
|
||||
{
|
||||
final GenericName owner = reader.readGenericName();
|
||||
final InternetClassType internetClassType = reader.readInternetClassType();
|
||||
final QClass qClass = reader.readClass();
|
||||
final Seconds timeToLive = reader.readTimeToLive();
|
||||
try
|
||||
{
|
||||
return internetClassType.createResourceRecord(owner, qClass, timeToLive, reader);
|
||||
}
|
||||
catch(BadlyFormedDnsMessageException cause)
|
||||
{
|
||||
throw new BadlyFormedDnsMessageException("Could not read resource record data", cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging.deserializer;
|
||||
|
||||
public class TruncatedDnsMessageException extends Exception
|
||||
{
|
||||
public TruncatedDnsMessageException()
|
||||
{
|
||||
super("DNS response message is truncated");
|
||||
}
|
||||
}
|
||||
110
src/main/java/com/softwarecraftsmen/dns/messaging/serializer/AtomicWriter.java
Executable file
110
src/main/java/com/softwarecraftsmen/dns/messaging/serializer/AtomicWriter.java
Executable file
@@ -0,0 +1,110 @@
|
||||
package com.softwarecraftsmen.dns.messaging.serializer;
|
||||
|
||||
import com.softwarecraftsmen.CanNeverHappenException;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned32BitInteger;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned8BitInteger;
|
||||
import static com.softwarecraftsmen.dns.NonAsciiAndControlCharactersAreNotSupportedInCharacterStringsException.throwExceptionIfUnsupportedCharacterCode;
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class AtomicWriter
|
||||
{
|
||||
private final ByteArrayOutputStream stream;
|
||||
|
||||
public AtomicWriter(final @NotNull ByteArrayOutputStream stream)
|
||||
{
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public byte[] toByteArray()
|
||||
{
|
||||
return stream.toByteArray();
|
||||
}
|
||||
|
||||
public void writeTimeToLiveInSeconds(final @NotNull Unsigned32BitInteger timeToLiveInSeconds)
|
||||
{
|
||||
writeUnsigned32BitInteger(timeToLiveInSeconds);
|
||||
}
|
||||
|
||||
public void writeCharacterString(final @NotNull String characterString)
|
||||
{
|
||||
writeCharacterString(characterString.toCharArray());
|
||||
}
|
||||
|
||||
public void writeCharacterString(final @NotNull char[] characterString)
|
||||
{
|
||||
if (characterString.length > 255)
|
||||
{
|
||||
throw new IllegalArgumentException("Maximum length of a character string in DNS is 255 characters");
|
||||
}
|
||||
writeUnsigned8BitUnsignedInteger(new Unsigned8BitInteger(characterString.length));
|
||||
for (char toWrite : characterString)
|
||||
{
|
||||
writeCharacter(toWrite);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeCharacter(final char toWrite)
|
||||
{
|
||||
throwExceptionIfUnsupportedCharacterCode(toWrite);
|
||||
stream.write(toWrite & 0xFF);
|
||||
}
|
||||
|
||||
public void writeUnsignedSeconds(final @NotNull Seconds seconds)
|
||||
{
|
||||
seconds.serialize(this);
|
||||
}
|
||||
|
||||
public void writeUnsigned32BitInteger(final @NotNull Unsigned32BitInteger unsigned32BitInteger)
|
||||
{
|
||||
try
|
||||
{
|
||||
unsigned32BitInteger.write(stream);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new CanNeverHappenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeUnsigned16BitInteger(final @NotNull Unsigned16BitInteger unsigned16BitInteger)
|
||||
{
|
||||
try
|
||||
{
|
||||
unsigned16BitInteger.write(stream);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new CanNeverHappenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeUnsigned8BitUnsignedInteger(final @NotNull Unsigned8BitInteger unsigned8BitInteger)
|
||||
{
|
||||
try
|
||||
{
|
||||
unsigned8BitInteger.write(stream);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new CanNeverHappenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeBytes(final @NotNull byte[] bytes)
|
||||
{
|
||||
try
|
||||
{
|
||||
stream.write(bytes);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new CanNeverHappenException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging.serializer;
|
||||
|
||||
import com.softwarecraftsmen.CanNeverHappenException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public final class ByteSerializer
|
||||
{
|
||||
public static final int MaximumDnsMessageSize = 65535;
|
||||
|
||||
private ByteSerializer()
|
||||
{}
|
||||
|
||||
@NotNull
|
||||
public static byte[] serialize(@NotNull final Serializable toBeSerialized)
|
||||
{
|
||||
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
toBeSerialized.serialize(new AtomicWriter(byteArrayOutputStream));
|
||||
try
|
||||
{
|
||||
byteArrayOutputStream.close();
|
||||
}
|
||||
catch (final IOException cause)
|
||||
{
|
||||
throw new CanNeverHappenException(cause);
|
||||
}
|
||||
final byte[] bytes = byteArrayOutputStream.toByteArray();
|
||||
if (bytes.length > MaximumDnsMessageSize)
|
||||
{
|
||||
throw new IllegalStateException("Maximum DNS message size is 65535 bytes");
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging.serializer;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface Serializable
|
||||
{
|
||||
void serialize(final @NotNull AtomicWriter writer);
|
||||
}
|
||||
142
src/main/java/com/softwarecraftsmen/dns/names/AbstractName.java
Executable file
142
src/main/java/com/softwarecraftsmen/dns/names/AbstractName.java
Executable file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.names;
|
||||
|
||||
import static com.softwarecraftsmen.dns.labels.SimpleLabel.Empty;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.labels.Label;
|
||||
import com.softwarecraftsmen.dns.labels.SimpleLabel;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import static java.util.Collections.unmodifiableList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbstractName implements Name<SimpleLabel>
|
||||
{
|
||||
@NonNls
|
||||
private final List<SimpleLabel> labels;
|
||||
|
||||
public AbstractName(final @NotNull List<SimpleLabel> labels)
|
||||
{
|
||||
this(labels.toArray(new SimpleLabel[labels.size()]));
|
||||
}
|
||||
|
||||
public AbstractName(final @NotNull SimpleLabel... labels)
|
||||
{
|
||||
if (labels.length == 0)
|
||||
{
|
||||
throw new IllegalArgumentException("There must be at least one label");
|
||||
}
|
||||
this.labels = new ArrayList<SimpleLabel>();
|
||||
for (int index = 0; index < labels.length; index++)
|
||||
{
|
||||
final SimpleLabel label = labels[index];
|
||||
if (label.isEmpty() && index != labels.length - 1)
|
||||
{
|
||||
throw new EmptyLabelsAreNotAllowedInNamesExceptAtTheEnd();
|
||||
}
|
||||
this.labels.add(label);
|
||||
if (!label.isEmpty() && index == labels.length - 1)
|
||||
{
|
||||
this.labels.add(Empty);
|
||||
}
|
||||
}
|
||||
if (this.labels.size() > 128)
|
||||
{
|
||||
throw new TooManyLabelsException();
|
||||
}
|
||||
throwExceptionIfNameLongerThan255Bytes();
|
||||
}
|
||||
|
||||
private void throwExceptionIfNameLongerThan255Bytes()
|
||||
{
|
||||
int totalLength = 0;
|
||||
for (Label label : labels)
|
||||
{
|
||||
totalLength += label.length();
|
||||
totalLength += 1;
|
||||
}
|
||||
if (totalLength > 255)
|
||||
{
|
||||
throw new NameIncludingPeriodsAndFinalEmptyLabelCanNotBeMoreThan255Characters();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<SimpleLabel> toLabels()
|
||||
{
|
||||
return unmodifiableList(labels);
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
for (Label label : labels)
|
||||
{
|
||||
label.serialize(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean equals(final Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final AbstractName that = (AbstractName) o;
|
||||
return labels.equals(that.labels);
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return labels.hashCode();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
final StringWriter writer = new StringWriter();
|
||||
for (Label label : labels)
|
||||
{
|
||||
if (label.isEmpty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
writer.write(label.toString());
|
||||
writer.write(".");
|
||||
}
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
public final class EmptyLabelsAreNotAllowedInNamesExceptAtTheEnd extends IllegalArgumentException
|
||||
{
|
||||
public EmptyLabelsAreNotAllowedInNamesExceptAtTheEnd()
|
||||
{
|
||||
super("Empty labels are not allowed in names except at the end");
|
||||
}
|
||||
}
|
||||
|
||||
public final class TooManyLabelsException extends IllegalArgumentException
|
||||
{
|
||||
public TooManyLabelsException()
|
||||
{
|
||||
super("More than 128 labels are not allowed in a DNS name");
|
||||
}
|
||||
}
|
||||
|
||||
public final class NameIncludingPeriodsAndFinalEmptyLabelCanNotBeMoreThan255Characters extends IllegalArgumentException
|
||||
{
|
||||
public NameIncludingPeriodsAndFinalEmptyLabelCanNotBeMoreThan255Characters()
|
||||
{
|
||||
super("More than 255 characters including dots (and the final trailing dot) are in this DNS name");
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/main/java/com/softwarecraftsmen/dns/names/DomainName.java
Executable file
36
src/main/java/com/softwarecraftsmen/dns/names/DomainName.java
Executable file
@@ -0,0 +1,36 @@
|
||||
package com.softwarecraftsmen.dns.names;
|
||||
|
||||
import static com.softwarecraftsmen.toString.ToString.string;
|
||||
import static com.softwarecraftsmen.dns.labels.SimpleLabel.labelsFromDottedName;
|
||||
import com.softwarecraftsmen.dns.labels.SimpleLabel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DomainName extends AbstractName
|
||||
{
|
||||
public DomainName(final @NotNull List<SimpleLabel> labels)
|
||||
{
|
||||
super(labels);
|
||||
}
|
||||
|
||||
public DomainName(final @NotNull SimpleLabel... labels)
|
||||
{
|
||||
super(labels);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return string(this, super.toString());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static DomainName domainName(final @NotNull String dottedName)
|
||||
{
|
||||
return new DomainName
|
||||
(
|
||||
labelsFromDottedName(dottedName)
|
||||
);
|
||||
}
|
||||
}
|
||||
36
src/main/java/com/softwarecraftsmen/dns/names/HostName.java
Executable file
36
src/main/java/com/softwarecraftsmen/dns/names/HostName.java
Executable file
@@ -0,0 +1,36 @@
|
||||
package com.softwarecraftsmen.dns.names;
|
||||
|
||||
import static com.softwarecraftsmen.toString.ToString.string;
|
||||
import static com.softwarecraftsmen.dns.labels.SimpleLabel.labelsFromDottedName;
|
||||
import com.softwarecraftsmen.dns.labels.SimpleLabel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class HostName extends AbstractName
|
||||
{
|
||||
public HostName(final @NotNull List<SimpleLabel> labels)
|
||||
{
|
||||
super(labels);
|
||||
}
|
||||
|
||||
public HostName(final @NotNull SimpleLabel... labels)
|
||||
{
|
||||
super(labels);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return string(this, super.toString());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static HostName hostName(final @NotNull String dottedName)
|
||||
{
|
||||
return new HostName
|
||||
(
|
||||
labelsFromDottedName(dottedName)
|
||||
);
|
||||
}
|
||||
}
|
||||
13
src/main/java/com/softwarecraftsmen/dns/names/Name.java
Executable file
13
src/main/java/com/softwarecraftsmen/dns/names/Name.java
Executable file
@@ -0,0 +1,13 @@
|
||||
package com.softwarecraftsmen.dns.names;
|
||||
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import com.softwarecraftsmen.dns.labels.Label;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface Name<L extends Label> extends Serializable
|
||||
{
|
||||
@NotNull
|
||||
List<L> toLabels();
|
||||
}
|
||||
122
src/main/java/com/softwarecraftsmen/dns/names/PointerName.java
Executable file
122
src/main/java/com/softwarecraftsmen/dns/names/PointerName.java
Executable file
@@ -0,0 +1,122 @@
|
||||
package com.softwarecraftsmen.dns.names;
|
||||
|
||||
import static com.softwarecraftsmen.toString.ToString.string;
|
||||
import static com.softwarecraftsmen.dns.labels.SimpleLabel.simpleLabel;
|
||||
import com.softwarecraftsmen.dns.labels.SimpleLabel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import static java.lang.String.valueOf;
|
||||
|
||||
public class PointerName extends AbstractName
|
||||
{
|
||||
private static final SimpleLabel Arpa = simpleLabel("ARPA");
|
||||
private static final SimpleLabel InAddr = simpleLabel("IN-ADDR");
|
||||
private static final SimpleLabel IP6 = simpleLabel("IP6");
|
||||
private static final Map<Integer, String> HexadecimalMap = new LinkedHashMap<Integer, String>()
|
||||
{{
|
||||
put(10, "a");
|
||||
put(11, "b");
|
||||
put(12, "c");
|
||||
put(13, "d");
|
||||
put(14, "e");
|
||||
put(15, "f");
|
||||
}};
|
||||
|
||||
private PointerName(final @NotNull Inet4Address address)
|
||||
{
|
||||
super(toInternetProtocolVersion4Labels(address.getAddress()));
|
||||
}
|
||||
|
||||
private PointerName(final @NotNull Inet6Address address)
|
||||
{
|
||||
super(toInternetProtocolVersion6Labels(address.getAddress()));
|
||||
}
|
||||
|
||||
public PointerName(final @NotNull List<SimpleLabel> labels)
|
||||
{
|
||||
super(labels);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return string(this, super.toString());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<SimpleLabel> toInternetProtocolVersion4Labels(final @NotNull byte[] address)
|
||||
{
|
||||
return new ArrayList<SimpleLabel>()
|
||||
{{
|
||||
add(networkByteToString(address, 3));
|
||||
add(networkByteToString(address, 2));
|
||||
add(networkByteToString(address, 1));
|
||||
add(networkByteToString(address, 0));
|
||||
add(InAddr);
|
||||
add(Arpa);
|
||||
}};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<SimpleLabel> toInternetProtocolVersion6Labels(final @NotNull byte[] address)
|
||||
{
|
||||
// 4321:0:1:2:3:4:567:89ab
|
||||
// is
|
||||
// b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4.IP6.ARPA
|
||||
return new ArrayList<SimpleLabel>()
|
||||
{{
|
||||
for(int index = 0; index < 16; index ++)
|
||||
{
|
||||
final int unsignedValue = address[index] & 0xFF;
|
||||
add(index * 2, simpleLabel(hexValue(unsignedValue & 0x0F)));
|
||||
add(index * 2 + 1, simpleLabel(hexValue(unsignedValue & 0xF0 >> 4)));
|
||||
}
|
||||
add(32, IP6);
|
||||
add(33, Arpa);
|
||||
}};
|
||||
}
|
||||
|
||||
private static String hexValue(final int unsignedNibble)
|
||||
{
|
||||
if (unsignedNibble < 0)
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (unsignedNibble < 10)
|
||||
{
|
||||
return valueOf(unsignedNibble);
|
||||
}
|
||||
return HexadecimalMap.get(unsignedNibble);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static SimpleLabel networkByteToString(final @NotNull byte[] address, int offset)
|
||||
{
|
||||
return simpleLabel(networkByteToString(address[offset]));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String networkByteToString(final byte addressByte)
|
||||
{
|
||||
final int i = addressByte & 0xFF;
|
||||
return valueOf(i);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PointerName pointerName(final @NotNull Inet4Address address)
|
||||
{
|
||||
return new PointerName(address);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PointerName pointerName(final @NotNull Inet6Address address)
|
||||
{
|
||||
return new PointerName(address);
|
||||
}
|
||||
}
|
||||
93
src/main/java/com/softwarecraftsmen/dns/names/ServiceName.java
Executable file
93
src/main/java/com/softwarecraftsmen/dns/names/ServiceName.java
Executable file
@@ -0,0 +1,93 @@
|
||||
package com.softwarecraftsmen.dns.names;
|
||||
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.labels.Label;
|
||||
import com.softwarecraftsmen.dns.labels.ServiceLabel;
|
||||
import com.softwarecraftsmen.dns.labels.ServiceProtocolLabel;
|
||||
import static com.softwarecraftsmen.toString.ToString.string;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ServiceName implements Name
|
||||
{
|
||||
private final ServiceLabel serviceLabel;
|
||||
private final ServiceProtocolLabel serviceProtocolLabel;
|
||||
private final DomainName domainName;
|
||||
|
||||
public ServiceName(final @NotNull ServiceLabel serviceLabel, final @NotNull ServiceProtocolLabel serviceProtocolLabel, final @NotNull DomainName domainName)
|
||||
{
|
||||
this.serviceLabel = serviceLabel;
|
||||
this.serviceProtocolLabel = serviceProtocolLabel;
|
||||
this.domainName = domainName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static ServiceName serviceName(final @NotNull ServiceLabel serviceLabel, final @NotNull ServiceProtocolLabel serviceProtocolLabel, final @NotNull DomainName domainName)
|
||||
{
|
||||
return new ServiceName(serviceLabel, serviceProtocolLabel, domainName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return string(this, serviceLabel, serviceProtocolLabel, domainName);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"RedundantIfStatement"})
|
||||
public boolean equals(final Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final ServiceName that = (ServiceName) o;
|
||||
|
||||
if (!domainName.equals(that.domainName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!serviceLabel.equals(that.serviceLabel))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!serviceProtocolLabel.equals(that.serviceProtocolLabel))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
int result;
|
||||
result = serviceLabel.hashCode();
|
||||
result = 31 * result + serviceProtocolLabel.hashCode();
|
||||
result = 31 * result + domainName.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
serviceLabel.serialize(writer);
|
||||
serviceProtocolLabel.serialize(writer);
|
||||
domainName.serialize(writer);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Label> toLabels()
|
||||
{
|
||||
return new ArrayList<Label>()
|
||||
{{
|
||||
add(serviceLabel);
|
||||
add(serviceProtocolLabel);
|
||||
addAll(domainName.toLabels());
|
||||
}};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.softwarecraftsmen.dns.resourceRecords;
|
||||
|
||||
import com.softwarecraftsmen.Pair;
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import static com.softwarecraftsmen.dns.Seconds.currentTime;
|
||||
import com.softwarecraftsmen.dns.messaging.InternetClassType;
|
||||
import com.softwarecraftsmen.dns.messaging.QClass;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.AtomicWriter;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import java.util.LinkedHashSet;
|
||||
import static java.util.Locale.UK;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
|
||||
public abstract class AbstractResourceRecord<S extends Name, T extends Serializable> implements ResourceRecord<S, T>
|
||||
{
|
||||
private final S owner;
|
||||
private final InternetClassType internetClassType;
|
||||
private final QClass qClass;
|
||||
private final Seconds timeToLive;
|
||||
private final T data;
|
||||
|
||||
// TODO: Create a Seconds time and use it here and for StatementOfAuthority
|
||||
public AbstractResourceRecord(final @NotNull S owner, final @NotNull InternetClassType internetClassType, final @NotNull QClass qClass, final @NotNull Seconds timeToLive, final @NotNull T data)
|
||||
{
|
||||
this.owner = owner;
|
||||
this.internetClassType = internetClassType;
|
||||
this.qClass = qClass;
|
||||
this.timeToLive = timeToLive;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public void serialize(final @NotNull AtomicWriter writer)
|
||||
{
|
||||
owner.serialize(writer);
|
||||
internetClassType.serialize(writer);
|
||||
qClass.serialize(writer);
|
||||
timeToLive.serialize(writer);
|
||||
throw new UnsupportedOperationException("Find a way to serialize length RDLENGTH for RDATA");
|
||||
//writer.writeUnsigned16BitInteger(data.length);
|
||||
//data.serialize(writer);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"RedundantIfStatement"})
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final AbstractResourceRecord that = (AbstractResourceRecord) o;
|
||||
|
||||
if (!data.equals(that.data))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (internetClassType != that.internetClassType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!owner.equals(that.owner))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (qClass != that.qClass)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!timeToLive.equals(that.timeToLive))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
int result;
|
||||
result = owner.hashCode();
|
||||
result = 31 * result + internetClassType.hashCode();
|
||||
result = 31 * result + qClass.hashCode();
|
||||
result = 31 * result + timeToLive.hashCode();
|
||||
result = 31 * result + data.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return format(UK, "%1$s %2$s %3$s %4$s %5$s", owner, timeToLive, qClass, internetClassType, data);
|
||||
}
|
||||
|
||||
public void appendDataIfIs(final @NotNull InternetClassType internetClassType, final @NotNull Set<T> set)
|
||||
{
|
||||
if (isFor(internetClassType))
|
||||
{
|
||||
set.add(data);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Seconds expiresAtSystemTime(final @NotNull Seconds maximumTimeToLivePermitted)
|
||||
{
|
||||
final Seconds actualTimeToLive = timeToLive.chooseSmallestValue(maximumTimeToLivePermitted);
|
||||
return currentTime().add(actualTimeToLive);
|
||||
}
|
||||
|
||||
public void addToCache(final @NotNull Seconds maximumTimeToLivePermitted, final @NotNull SortedMap<Seconds, Set<ResourceRecord<? extends Name, ? extends Serializable>>> bestBeforeTimesForResourceRecords, final @NotNull Map<Pair<Name, InternetClassType>, Set<ResourceRecord<? extends Name, ? extends Serializable>>> cache)
|
||||
{
|
||||
final Seconds expiresAtSystemTime = expiresAtSystemTime(maximumTimeToLivePermitted);
|
||||
if (expiresAtSystemTime.compareTo(currentTime()) == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!bestBeforeTimesForResourceRecords.containsKey(expiresAtSystemTime))
|
||||
{
|
||||
bestBeforeTimesForResourceRecords.put(expiresAtSystemTime, new LinkedHashSet<ResourceRecord<? extends Name, ? extends Serializable>>());
|
||||
}
|
||||
bestBeforeTimesForResourceRecords.get(expiresAtSystemTime).add(this);
|
||||
|
||||
|
||||
final Pair<Name, InternetClassType> key = new Pair<Name, InternetClassType>(owner, internetClassType);
|
||||
if (!cache.containsKey(key))
|
||||
{
|
||||
cache.put(key, new LinkedHashSet<ResourceRecord<? extends Name, ? extends Serializable>>());
|
||||
}
|
||||
final Set<ResourceRecord<? extends Name, ? extends Serializable>> resourceRecordSet = cache.get(key);
|
||||
resourceRecordSet.add(this);
|
||||
}
|
||||
|
||||
public void removeFromCache(final @NotNull Map<Pair<Name, InternetClassType>, Set<ResourceRecord<? extends Name, ? extends Serializable>>> cache)
|
||||
{
|
||||
final Pair<Name, InternetClassType> key = new Pair<Name, InternetClassType>(owner, internetClassType);
|
||||
if (cache.containsKey(key))
|
||||
{
|
||||
cache.get(key).remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFor(final @NotNull InternetClassType internetClassType)
|
||||
{
|
||||
return this.internetClassType.equals(internetClassType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.softwarecraftsmen.dns.resourceRecords;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import static com.softwarecraftsmen.dns.messaging.QClass.Internet;
|
||||
import static com.softwarecraftsmen.dns.messaging.InternetClassType.CNAME;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class CanonicalNameResourceRecord extends AbstractResourceRecord<HostName, HostName>
|
||||
{
|
||||
public CanonicalNameResourceRecord(final @NotNull HostName alias, final @NotNull Seconds timeToLive, final @NotNull HostName canonicalName)
|
||||
{
|
||||
super(alias, CNAME, Internet, timeToLive, canonicalName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static CanonicalNameResourceRecord canonicalNameResourceRecord(final @NotNull HostName alias, final @NotNull Seconds timeToLive, final @NotNull HostName canonicalName)
|
||||
{
|
||||
return new CanonicalNameResourceRecord(alias, timeToLive, canonicalName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.softwarecraftsmen.dns.resourceRecords;
|
||||
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import static com.softwarecraftsmen.dns.messaging.QClass.Internet;
|
||||
import com.softwarecraftsmen.dns.messaging.GenericName;
|
||||
import com.softwarecraftsmen.dns.messaging.GenericResourceRecordData;
|
||||
import com.softwarecraftsmen.dns.messaging.InternetClassType;
|
||||
import com.softwarecraftsmen.dns.messaging.QClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class GenericResourceRecord extends AbstractResourceRecord<GenericName, GenericResourceRecordData>
|
||||
{
|
||||
public GenericResourceRecord(final @NotNull GenericName owner, final @NotNull InternetClassType internetClassType, final @NotNull QClass qClass, final @NotNull Seconds timeToLive, final @NotNull GenericResourceRecordData data)
|
||||
{
|
||||
super(owner, internetClassType, qClass, timeToLive, data);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static GenericResourceRecord genericResourceRecord(final @NotNull GenericName owner, final @NotNull InternetClassType internetClassType, final @NotNull Seconds timeToLive, final @NotNull GenericResourceRecordData data)
|
||||
{
|
||||
return new GenericResourceRecord(owner, internetClassType, Internet, timeToLive, data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.softwarecraftsmen.dns.resourceRecords;
|
||||
|
||||
import com.softwarecraftsmen.dns.HostInformation;
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import static com.softwarecraftsmen.dns.messaging.QClass.Internet;
|
||||
import static com.softwarecraftsmen.dns.messaging.InternetClassType.HINFO;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class HostInformationResourceRecord extends AbstractResourceRecord<HostName, HostInformation>
|
||||
{
|
||||
public HostInformationResourceRecord(final @NotNull HostName owner, final @NotNull Seconds timeToLive, final @NotNull HostInformation hostInformation)
|
||||
{
|
||||
super(owner, HINFO, Internet, timeToLive, hostInformation);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static HostInformationResourceRecord hostInformationResourceRecord(final @NotNull HostName owner, final @NotNull Seconds timeToLive, final @NotNull HostInformation hostInformation)
|
||||
{
|
||||
return new HostInformationResourceRecord(owner, timeToLive, hostInformation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.softwarecraftsmen.dns.resourceRecords;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import com.softwarecraftsmen.dns.SerializableInternetProtocolAddress;
|
||||
import static com.softwarecraftsmen.dns.messaging.QClass.Internet;
|
||||
import static com.softwarecraftsmen.dns.messaging.InternetClassType.A;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
|
||||
public class InternetProtocolVersion4AddressResourceRecord extends AbstractResourceRecord<HostName, SerializableInternetProtocolAddress<Inet4Address>>
|
||||
{
|
||||
public InternetProtocolVersion4AddressResourceRecord(final @NotNull HostName owner, final @NotNull Seconds timeToLive, final @NotNull SerializableInternetProtocolAddress<Inet4Address> internetProtocolVersion4Address)
|
||||
{
|
||||
super(owner, A, Internet, timeToLive, internetProtocolVersion4Address);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static InternetProtocolVersion4AddressResourceRecord internetProtocolVersion4AddressResourceRecord(final @NotNull HostName owner, final @NotNull Seconds timeToLive, final @NotNull SerializableInternetProtocolAddress<Inet4Address> internetProtocolVersion4Address)
|
||||
{
|
||||
return new InternetProtocolVersion4AddressResourceRecord(owner, timeToLive, internetProtocolVersion4Address);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.softwarecraftsmen.dns.resourceRecords;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import com.softwarecraftsmen.dns.SerializableInternetProtocolAddress;
|
||||
import static com.softwarecraftsmen.dns.messaging.QClass.Internet;
|
||||
import static com.softwarecraftsmen.dns.messaging.InternetClassType.AAAA;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.Inet6Address;
|
||||
|
||||
public class InternetProtocolVersion6AddressResourceRecord extends AbstractResourceRecord<HostName, SerializableInternetProtocolAddress<Inet6Address>>
|
||||
{
|
||||
public InternetProtocolVersion6AddressResourceRecord(final @NotNull HostName owner, final @NotNull Seconds timeToLive, final @NotNull SerializableInternetProtocolAddress<Inet6Address> internetProtocolVersion6Address)
|
||||
{
|
||||
super(owner, AAAA, Internet, timeToLive, internetProtocolVersion6Address);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static InternetProtocolVersion6AddressResourceRecord internetProtocolVersion6AddressResourceRecord(final @NotNull HostName owner, final @NotNull Seconds timeToLive, final @NotNull SerializableInternetProtocolAddress<Inet6Address> internetProtocolVersion6Address)
|
||||
{
|
||||
return new InternetProtocolVersion6AddressResourceRecord(owner, timeToLive, internetProtocolVersion6Address);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.softwarecraftsmen.dns.resourceRecords;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.DomainName;
|
||||
import com.softwarecraftsmen.dns.MailExchange;
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import static com.softwarecraftsmen.dns.messaging.QClass.Internet;
|
||||
import static com.softwarecraftsmen.dns.messaging.InternetClassType.MX;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class MailExchangeResourceRecord extends AbstractResourceRecord<DomainName, MailExchange>
|
||||
{
|
||||
public MailExchangeResourceRecord(final @NotNull DomainName owner, final @NotNull Seconds timeToLive, final @NotNull MailExchange mailExchange)
|
||||
{
|
||||
super(owner, MX, Internet, timeToLive, mailExchange);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static MailExchangeResourceRecord mailExchangeResourceRecord(final @NotNull DomainName owner, final @NotNull Seconds timeToLive, final @NotNull MailExchange mailExchange)
|
||||
{
|
||||
return new MailExchangeResourceRecord(owner, timeToLive, mailExchange);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.softwarecraftsmen.dns.resourceRecords;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.DomainName;
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import static com.softwarecraftsmen.dns.messaging.QClass.Internet;
|
||||
import static com.softwarecraftsmen.dns.messaging.InternetClassType.NS;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class NameServerResourceRecord extends AbstractResourceRecord<DomainName, HostName>
|
||||
{
|
||||
public NameServerResourceRecord(final @NotNull DomainName owner, final @NotNull Seconds timeToLive, final @NotNull HostName nameServerHostName)
|
||||
{
|
||||
super(owner, NS, Internet, timeToLive, nameServerHostName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static NameServerResourceRecord nameServerResourceRecord(final @NotNull DomainName owner, final @NotNull Seconds timeToLive, final @NotNull HostName nameServerHostName)
|
||||
{
|
||||
return new NameServerResourceRecord(owner, timeToLive, nameServerHostName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.softwarecraftsmen.dns.resourceRecords;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import com.softwarecraftsmen.dns.names.PointerName;
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import static com.softwarecraftsmen.dns.messaging.QClass.Internet;
|
||||
import static com.softwarecraftsmen.dns.messaging.InternetClassType.PTR;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class PointerResourceRecord extends AbstractResourceRecord<PointerName, HostName>
|
||||
{
|
||||
public PointerResourceRecord(final @NotNull PointerName owner, final @NotNull Seconds timeToLive, final @NotNull HostName hostName)
|
||||
{
|
||||
super(owner, PTR, Internet, timeToLive, hostName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PointerResourceRecord pointerResourceRecord(final @NotNull PointerName owner, final @NotNull Seconds timeToLive, final @NotNull HostName hostName)
|
||||
{
|
||||
return new PointerResourceRecord(owner, timeToLive, hostName);
|
||||
}
|
||||
}
|
||||
24
src/main/java/com/softwarecraftsmen/dns/resourceRecords/ResourceRecord.java
Executable file
24
src/main/java/com/softwarecraftsmen/dns/resourceRecords/ResourceRecord.java
Executable file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.resourceRecords;
|
||||
|
||||
import com.softwarecraftsmen.Pair;
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import com.softwarecraftsmen.dns.messaging.InternetClassType;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
|
||||
public interface ResourceRecord<S extends Name, T extends Serializable> extends Serializable
|
||||
{
|
||||
void appendDataIfIs(final @NotNull InternetClassType internetClassType, final @NotNull Set<T> set);
|
||||
|
||||
void addToCache(final @NotNull Seconds maximumTimeToLivePermitted, final @NotNull SortedMap<Seconds, Set<ResourceRecord<? extends Name, ? extends Serializable>>> bestBeforeTimesForResourceRecords, final @NotNull Map<Pair<Name, InternetClassType>, Set<ResourceRecord<? extends Name, ? extends Serializable>>> cache);
|
||||
|
||||
void removeFromCache(final @NotNull Map<Pair<Name, InternetClassType>, Set<ResourceRecord<? extends Name, ? extends Serializable>>> cache);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.softwarecraftsmen.dns.resourceRecords;
|
||||
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import com.softwarecraftsmen.dns.ServiceInformation;
|
||||
import com.softwarecraftsmen.dns.names.ServiceName;
|
||||
import static com.softwarecraftsmen.dns.messaging.QClass.Internet;
|
||||
import static com.softwarecraftsmen.dns.messaging.InternetClassType.SRV;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ServiceInformationResourceRecord extends AbstractResourceRecord<ServiceName, ServiceInformation>
|
||||
{
|
||||
public ServiceInformationResourceRecord(final @NotNull ServiceName owner, final @NotNull Seconds timeToLive, final @NotNull ServiceInformation serviceInformation)
|
||||
{
|
||||
super(owner, SRV, Internet, timeToLive, serviceInformation);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static ServiceInformationResourceRecord serviceInformationResourceRecord(final @NotNull ServiceName owner, final @NotNull Seconds timeToLive, final @NotNull ServiceInformation serviceInformation)
|
||||
{
|
||||
return new ServiceInformationResourceRecord(owner, timeToLive, serviceInformation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.softwarecraftsmen.dns.resourceRecords;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.DomainName;
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import com.softwarecraftsmen.dns.StatementOfAuthority;
|
||||
import static com.softwarecraftsmen.dns.messaging.QClass.Internet;
|
||||
import static com.softwarecraftsmen.dns.messaging.InternetClassType.SOA;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class StatementOfAuthorityResourceRecord extends AbstractResourceRecord<DomainName, StatementOfAuthority>
|
||||
{
|
||||
public StatementOfAuthorityResourceRecord(final @NotNull DomainName owner, final @NotNull Seconds timeToLive, final @NotNull StatementOfAuthority statementOfAuthority)
|
||||
{
|
||||
super(owner, SOA, Internet, timeToLive, statementOfAuthority);
|
||||
}
|
||||
|
||||
public static StatementOfAuthorityResourceRecord statementOfAuthorityResourceRecord(final @NotNull DomainName owner, final @NotNull Seconds timeToLive, final @NotNull StatementOfAuthority statementOfAuthority)
|
||||
{
|
||||
return new StatementOfAuthorityResourceRecord(owner, timeToLive, statementOfAuthority);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.softwarecraftsmen.dns.resourceRecords;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import com.softwarecraftsmen.dns.Seconds;
|
||||
import com.softwarecraftsmen.dns.Text;
|
||||
import static com.softwarecraftsmen.dns.messaging.QClass.Internet;
|
||||
import static com.softwarecraftsmen.dns.messaging.InternetClassType.TXT;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class TextResourceRecord extends AbstractResourceRecord<HostName, Text>
|
||||
{
|
||||
public TextResourceRecord(final @NotNull HostName owner, final @NotNull Seconds timeToLive, final @NotNull Text text)
|
||||
{
|
||||
super(owner, TXT, Internet, timeToLive, text);
|
||||
}
|
||||
|
||||
public static TextResourceRecord textResourceRecord(final @NotNull HostName owner, final @NotNull Seconds timeToLive, final @NotNull Text text)
|
||||
{
|
||||
return new TextResourceRecord(owner, timeToLive, text);
|
||||
}
|
||||
}
|
||||
80
src/main/java/com/softwarecraftsmen/toString/ToString.java
Executable file
80
src/main/java/com/softwarecraftsmen/toString/ToString.java
Executable file
@@ -0,0 +1,80 @@
|
||||
package com.softwarecraftsmen.toString;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class ToString
|
||||
{
|
||||
@NotNull
|
||||
public static String string(final @NotNull Object object, final @NotNull Object ... fields)
|
||||
{
|
||||
final StringWriter writer = new StringWriter();
|
||||
writer.write(object.getClass().getSimpleName());
|
||||
writer.write("(");
|
||||
boolean afterFirst = false;
|
||||
for (Object field : fields)
|
||||
{
|
||||
if (afterFirst)
|
||||
{
|
||||
writer.write(", ");
|
||||
}
|
||||
if (field.getClass().isArray())
|
||||
{
|
||||
writer.write(arrayToString(field));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.write(field.toString());
|
||||
}
|
||||
afterFirst = true;
|
||||
}
|
||||
writer.write(")");
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
private static String arrayToString(final Object objects)
|
||||
{
|
||||
final Class<?> arrayType = objects.getClass().getComponentType();
|
||||
if (arrayType.equals(byte.class))
|
||||
{
|
||||
return Arrays.toString((byte[])objects);
|
||||
}
|
||||
else if (arrayType.equals(boolean.class))
|
||||
{
|
||||
return Arrays.toString((boolean[])objects);
|
||||
}
|
||||
else if (arrayType.equals(short.class))
|
||||
{
|
||||
return Arrays.toString((short[])objects);
|
||||
}
|
||||
else if (arrayType.equals(char.class))
|
||||
{
|
||||
return Arrays.toString((char[])objects);
|
||||
}
|
||||
else if (arrayType.equals(int.class))
|
||||
{
|
||||
return Arrays.toString((int[])objects);
|
||||
}
|
||||
else if (arrayType.equals(long.class))
|
||||
{
|
||||
return Arrays.toString((long[])objects);
|
||||
}
|
||||
else if (arrayType.equals(float.class))
|
||||
{
|
||||
return Arrays.toString((float[])objects);
|
||||
}
|
||||
else if (arrayType.equals(double.class))
|
||||
{
|
||||
return Arrays.toString((double[])objects);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Arrays.toString((Object[])objects);
|
||||
}
|
||||
}
|
||||
|
||||
private ToString()
|
||||
{}
|
||||
}
|
||||
201
src/main/java/com/softwarecraftsmen/unsignedIntegers/Unsigned16BitInteger.java
Executable file
201
src/main/java/com/softwarecraftsmen/unsignedIntegers/Unsigned16BitInteger.java
Executable file
@@ -0,0 +1,201 @@
|
||||
package com.softwarecraftsmen.unsignedIntegers;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.Character.MAX_VALUE;
|
||||
import static java.util.Locale.UK;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned3BitInteger.unsigned3BitInteger;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned4BitInteger.unsigned4BitInteger;
|
||||
|
||||
public class Unsigned16BitInteger implements Comparable<Unsigned16BitInteger>
|
||||
{
|
||||
public int toSigned32BitInteger()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
private final int value;
|
||||
@NotNull
|
||||
public static final Unsigned16BitInteger Zero = new Unsigned16BitInteger(0);
|
||||
@NotNull
|
||||
public static final Unsigned16BitInteger One = new Unsigned16BitInteger(1);
|
||||
@NotNull
|
||||
public static Unsigned16BitInteger Four = new Unsigned16BitInteger(4);
|
||||
@NotNull
|
||||
public static Unsigned16BitInteger Sixteen = new Unsigned16BitInteger(16);
|
||||
@NotNull
|
||||
public static Unsigned16BitInteger MaximumValue = new Unsigned16BitInteger(MAX_VALUE);
|
||||
|
||||
private static final int TopBit = 15;
|
||||
|
||||
private Unsigned16BitInteger(final int value)
|
||||
{
|
||||
if (value < 0 || value > 65536)
|
||||
{
|
||||
throw new IllegalArgumentException(format(UK, "The value %1$s is not a valid unsigned 16 bit integer", value));
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Unsigned16BitInteger unsigned16BitInteger(final int value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case 0:
|
||||
return Zero;
|
||||
case 1:
|
||||
return One;
|
||||
case 4:
|
||||
return Four;
|
||||
case 16:
|
||||
return Sixteen;
|
||||
default:
|
||||
return new Unsigned16BitInteger(value);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return format(UK, "%1$s", value);
|
||||
}
|
||||
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final Unsigned16BitInteger that = (Unsigned16BitInteger) o;
|
||||
return value == that.value;
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned32BitInteger leftShift16()
|
||||
{
|
||||
return new Unsigned32BitInteger(value << 16);
|
||||
}
|
||||
|
||||
public long toLong()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public char[] createCharacterArray()
|
||||
{
|
||||
return new char[value];
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public byte[] createByteArray()
|
||||
{
|
||||
return new byte[value];
|
||||
}
|
||||
|
||||
public boolean getBitIetf(final int zeroBasedIetfBitNumber)
|
||||
{
|
||||
return getBitPowerOfTwo(TopBit - zeroBasedIetfBitNumber);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned4BitInteger getUnsigned4BitIntegerIetf(final int zeroBasedIetfBitNumberStart)
|
||||
{
|
||||
return unsigned4BitInteger(getBitsIetf(zeroBasedIetfBitNumberStart, 0x0F));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned3BitInteger getThreeBitsIetf(final int zeroBasedIetfBitNumberStart)
|
||||
{
|
||||
return unsigned3BitInteger(getBitsIetf(zeroBasedIetfBitNumberStart, 0x07));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger set4BitsIetf(final Unsigned4BitInteger unsigned4BitInteger, final int zeroBasedIetfBitNumberStart)
|
||||
{
|
||||
return set4BitsPowerOfTwo(unsigned4BitInteger, TopBit - zeroBasedIetfBitNumberStart);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger setBitIetf(final boolean bitOnOrOff, final int zeroBasedIetfBitNumber)
|
||||
{
|
||||
return setBitPowerOfTwo(bitOnOrOff, TopBit - zeroBasedIetfBitNumber);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger setBitPowerOfTwo(final boolean bitOnOrOff, final int zeroBasedPowerOfTwoBitNumber)
|
||||
{
|
||||
return unsigned16BitInteger(value | (bitOnOrOff ? 1 << zeroBasedPowerOfTwoBitNumber : 0));
|
||||
}
|
||||
|
||||
public void write(final @NotNull OutputStream stream) throws IOException
|
||||
{
|
||||
stream.write((value >>> 8) & 0xFF);
|
||||
stream.write(value & 0xFF);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Unsigned16BitInteger set4BitsPowerOfTwo(final Unsigned4BitInteger unsigned4BitInteger, final int zeroBasedPowerOfTwoBitNumber)
|
||||
{
|
||||
return unsigned16BitInteger(set4Bits(unsigned4BitInteger, zeroBasedPowerOfTwoBitNumber));
|
||||
}
|
||||
|
||||
private boolean getBitPowerOfTwo(final int zeroBasedPowerOfTwoBitNumber)
|
||||
{
|
||||
final int mask = 1 << zeroBasedPowerOfTwoBitNumber;
|
||||
return (value & mask) == mask;
|
||||
}
|
||||
|
||||
private int getBitsIetf(final int zeroBasedIetfBitNumberStart, final int mask)
|
||||
{
|
||||
return getBitsPowerOfTwo(TopBit - zeroBasedIetfBitNumberStart, mask);
|
||||
}
|
||||
|
||||
private int getBitsPowerOfTwo(final int zeroBasedPowerOfTwoBitNumberStart, final int mask)
|
||||
{
|
||||
final int mask2 = mask << zeroBasedPowerOfTwoBitNumberStart;
|
||||
final int value2 = value & mask2;
|
||||
return value2 >> zeroBasedPowerOfTwoBitNumberStart;
|
||||
}
|
||||
|
||||
private int set4Bits(final Unsigned4BitInteger unsigned4BitInteger, final int zeroBasedPowerOfTwoBitNumber) {return value | (unsigned4BitInteger.toUnsigned16BitInteger().value << zeroBasedPowerOfTwoBitNumber);}
|
||||
|
||||
public int compareTo(final Unsigned16BitInteger that)
|
||||
{
|
||||
if (this.value < that.value)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (this.value > that.value)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger increment()
|
||||
{
|
||||
if (value == MAX_VALUE)
|
||||
{
|
||||
return Zero;
|
||||
}
|
||||
return unsigned16BitInteger(value + 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.softwarecraftsmen.unsignedIntegers;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Locale.UK;
|
||||
|
||||
public class Unsigned32BitInteger implements Comparable<Unsigned32BitInteger>
|
||||
{
|
||||
@NotNull
|
||||
public static Unsigned32BitInteger Zero = new Unsigned32BitInteger(0);
|
||||
|
||||
public long to()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
private final long value;
|
||||
|
||||
public Unsigned32BitInteger(final long value)
|
||||
{
|
||||
if (value < 0l || value > 4294967296l)
|
||||
{
|
||||
throw new IllegalArgumentException(format(UK, "The value %1$s is not a valid unsigned 32 bit integer", value));
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Unsigned32BitInteger unsigned32BitInteger(final long value)
|
||||
{
|
||||
if (value == 0)
|
||||
{
|
||||
return Zero;
|
||||
}
|
||||
return new Unsigned32BitInteger(value);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return format(UK, "%1$s", value);
|
||||
}
|
||||
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final Unsigned32BitInteger that = (Unsigned32BitInteger) o;
|
||||
return value == that.value;
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return (int) (value ^ (value >>> 32));
|
||||
}
|
||||
|
||||
public Unsigned32BitInteger add(final @NotNull Unsigned16BitInteger unsigned16BitInteger)
|
||||
{
|
||||
return new Unsigned32BitInteger(value + unsigned16BitInteger.toLong());
|
||||
}
|
||||
|
||||
public void write(final @NotNull OutputStream stream) throws IOException
|
||||
{
|
||||
stream.write((int) ((value >>> 24) & 0xFF));
|
||||
stream.write((int) ((value >>> 16) & 0xFF));
|
||||
stream.write((int) ((value >>> 8) & 0xFF));
|
||||
stream.write((int) (value & 0xFF));
|
||||
}
|
||||
|
||||
public int compareTo(final @NotNull Unsigned32BitInteger unsigned32BitInteger)
|
||||
{
|
||||
if (value == unsigned32BitInteger.value)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return value < unsigned32BitInteger.value ? -1 : 1;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned32BitInteger add(final @NotNull Unsigned32BitInteger that)
|
||||
{
|
||||
return unsigned32BitInteger(this.value + that.value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.unsignedIntegers;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Locale.UK;
|
||||
|
||||
public class Unsigned3BitInteger
|
||||
{
|
||||
@NotNull
|
||||
public static Unsigned3BitInteger Zero = new Unsigned3BitInteger(0);
|
||||
private final int value;
|
||||
|
||||
private Unsigned3BitInteger(final int value)
|
||||
{
|
||||
if (value < 0 || value > 7)
|
||||
{
|
||||
throw new IllegalArgumentException(format(UK, "The value %1$s is not a valid unsigned 3 bit integer", value));
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Unsigned3BitInteger unsigned3BitInteger(final int value)
|
||||
{
|
||||
if (value == 0)
|
||||
{
|
||||
return Zero;
|
||||
}
|
||||
return new Unsigned3BitInteger(value);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return format(UK, "%1$s", value);
|
||||
}
|
||||
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final Unsigned3BitInteger that = (Unsigned3BitInteger) o;
|
||||
|
||||
return value == that.value;
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.unsignedIntegers;
|
||||
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.unsigned16BitInteger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Locale.UK;
|
||||
|
||||
public class Unsigned4BitInteger
|
||||
{
|
||||
private final int value;
|
||||
|
||||
@NotNull
|
||||
public static final Unsigned4BitInteger Zero = new Unsigned4BitInteger(0);
|
||||
@NotNull
|
||||
public static final Unsigned4BitInteger One = new Unsigned4BitInteger(1);
|
||||
@NotNull
|
||||
public static final Unsigned4BitInteger Two = new Unsigned4BitInteger(2);
|
||||
@NotNull
|
||||
public static final Unsigned4BitInteger Three = new Unsigned4BitInteger(3);
|
||||
@NotNull
|
||||
public static final Unsigned4BitInteger Four = new Unsigned4BitInteger(4);
|
||||
@NotNull
|
||||
public static final Unsigned4BitInteger Five = new Unsigned4BitInteger(5);
|
||||
|
||||
private Unsigned4BitInteger(final int value)
|
||||
{
|
||||
if (value < 0 || value > 15)
|
||||
{
|
||||
throw new IllegalArgumentException(format(UK, "The value %1$s is not a valid unsigned 4 bit integer (nibble)", value));
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Unsigned4BitInteger unsigned4BitInteger(final int value)
|
||||
{
|
||||
return new Unsigned4BitInteger(value);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return format(UK, "%1$s", value);
|
||||
}
|
||||
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final Unsigned4BitInteger that = (Unsigned4BitInteger) o;
|
||||
|
||||
return value == that.value;
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned16BitInteger toUnsigned16BitInteger()
|
||||
{
|
||||
return unsigned16BitInteger(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.softwarecraftsmen.unsignedIntegers;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Locale.UK;
|
||||
|
||||
public class Unsigned8BitInteger
|
||||
{
|
||||
public static final Unsigned8BitInteger Zero = new Unsigned8BitInteger(0);
|
||||
private final int value;
|
||||
|
||||
public Unsigned8BitInteger(final int value)
|
||||
{
|
||||
if (value < 0 || value > 255)
|
||||
{
|
||||
throw new IllegalArgumentException(format(UK, "The value %1$s is not a valid unsigned 8 bit integer", value));
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Unsigned8BitInteger unsigned8BitInteger(final int value)
|
||||
{
|
||||
return new Unsigned8BitInteger(value);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString()
|
||||
{
|
||||
return format(UK, "%1$s", value);
|
||||
}
|
||||
|
||||
public boolean equals(final @Nullable Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final Unsigned8BitInteger that = (Unsigned8BitInteger) o;
|
||||
return value == that.value;
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
public char toAsciiCharacter()
|
||||
{
|
||||
return (char)value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public char[] createCharacterArray()
|
||||
{
|
||||
return new char[value];
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned8BitInteger and(final Unsigned8BitInteger mask)
|
||||
{
|
||||
return new Unsigned8BitInteger(value & mask.value);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Unsigned8BitInteger not()
|
||||
{
|
||||
return unsigned8BitInteger(~value & 0xFF);
|
||||
}
|
||||
|
||||
public int shiftToSigned32BitInteger(final Unsigned8BitInteger lowerOffset)
|
||||
{
|
||||
return (value << 8) + lowerOffset.value;
|
||||
}
|
||||
|
||||
public void write(final OutputStream stream) throws IOException
|
||||
{
|
||||
stream.write(value & 0xFF);
|
||||
}
|
||||
}
|
||||
28
src/test/java/com/softwarecraftsmen/ConvenientArrayList.java
Executable file
28
src/test/java/com/softwarecraftsmen/ConvenientArrayList.java
Executable file
@@ -0,0 +1,28 @@
|
||||
package com.softwarecraftsmen;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import com.softwarecraftsmen.dns.resourceRecords.ResourceRecord;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ConvenientArrayList<T> extends ArrayList<T>
|
||||
{
|
||||
public ConvenientArrayList(final @NotNull T ... values)
|
||||
{
|
||||
super(java.util.Arrays.asList(values));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static <T> List<T> toList(final @NotNull T ... values)
|
||||
{
|
||||
return new ConvenientArrayList<T>(values);
|
||||
}
|
||||
|
||||
public static List<ResourceRecord<? extends Name, ? extends Serializable>> toResourceRecordList(final @NotNull ResourceRecord<? extends Name, ? extends Serializable>... values)
|
||||
{
|
||||
return new ConvenientArrayList<ResourceRecord<? extends Name, ? extends Serializable>>(values);
|
||||
}
|
||||
}
|
||||
26
src/test/java/com/softwarecraftsmen/dns/MailBoxTest.java
Normal file
26
src/test/java/com/softwarecraftsmen/dns/MailBoxTest.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns;
|
||||
|
||||
import com.softwarecraftsmen.ConvenientArrayList;
|
||||
import static com.softwarecraftsmen.dns.names.DomainName.domainName;
|
||||
import static com.softwarecraftsmen.dns.MailBox.mailBox;
|
||||
import static com.softwarecraftsmen.dns.labels.SimpleLabel.simpleLabel;
|
||||
import static com.softwarecraftsmen.dns.labels.SimpleLabel.Empty;
|
||||
import com.softwarecraftsmen.dns.labels.Label;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MailBoxTest
|
||||
{
|
||||
@Test
|
||||
public void toLabelsMatchesExactStructure()
|
||||
{
|
||||
final List<Label> labels = mailBox("raph", domainName("softwarecraftsmen.com")).toLabels();
|
||||
assertThat(new ConvenientArrayList<Label>(simpleLabel("raph"), simpleLabel("softwarecraftsmen"), simpleLabel("com"), Empty), equalTo(labels));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns;
|
||||
|
||||
import static com.softwarecraftsmen.dns.names.HostName.hostName;
|
||||
import static com.softwarecraftsmen.dns.MailExchange.mailExchange;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.unsigned16BitInteger;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.junit.Test;
|
||||
|
||||
public class MailExchangeTest
|
||||
{
|
||||
@Test
|
||||
public void comparesUsingPreferencesFirstAndLowerPreferenceWins()
|
||||
{
|
||||
final MailExchange lowerPreference = mailExchange(unsigned16BitInteger(10), hostName("mail.google.com"));
|
||||
final MailExchange higherPreference = mailExchange(unsigned16BitInteger(20), hostName("mail.google.com"));
|
||||
assertThat(lowerPreference, lessThan(higherPreference));
|
||||
assertThat(higherPreference, greaterThan(lowerPreference));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void comparesUsingPreferencesFirstThenHostNamesAndEqualHostNamesAreEqual()
|
||||
{
|
||||
final MailExchange identicalPreference1 = mailExchange(unsigned16BitInteger(10), hostName("mail.google.com"));
|
||||
final MailExchange identicalPreference2 = mailExchange(unsigned16BitInteger(10), hostName("mail.google.com"));
|
||||
final int operand = identicalPreference1.compareTo(identicalPreference2);
|
||||
assertThat(0, equalTo(operand));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void comparesUsingPreferencesFirstThenHostNamesAndHostNamesWithLessLabelsAreFirst()
|
||||
{
|
||||
final MailExchange shorterHostName = mailExchange(unsigned16BitInteger(10), hostName("squid.com"));
|
||||
final MailExchange longerHostName = mailExchange(unsigned16BitInteger(10), hostName("mail.google.com"));
|
||||
assertThat(shorterHostName, lessThan(longerHostName));
|
||||
assertThat(longerHostName, greaterThan(shorterHostName));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void comparesUsingPreferencesFirstThenHostNamesAndHostNamesWithSameLabelsAreFirst()
|
||||
{
|
||||
final MailExchange shorterHostName = mailExchange(unsigned16BitInteger(10), hostName("first.google.com"));
|
||||
final MailExchange longerHostName = mailExchange(unsigned16BitInteger(10), hostName("mail.google.com"));
|
||||
assertThat(shorterHostName, lessThan(longerHostName));
|
||||
assertThat(longerHostName, greaterThan(shorterHostName));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void comparesUsingPreferencesFirstThenHostNamesAndHostNamesWithSameLabelsAreFirstCheckReversesCorrectly()
|
||||
{
|
||||
final MailExchange shorterHostName = mailExchange(unsigned16BitInteger(10), hostName("mail.google.com"));
|
||||
final MailExchange longerHostName = mailExchange(unsigned16BitInteger(10), hostName("mail.google.org"));
|
||||
assertThat(shorterHostName, lessThan(longerHostName));
|
||||
assertThat(longerHostName, greaterThan(shorterHostName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns;
|
||||
|
||||
import static com.softwarecraftsmen.dns.ServiceInformation.serviceInformation;
|
||||
import com.softwarecraftsmen.dns.ServiceInformationPrioritised.WeightRandomNumberGenerator;
|
||||
import static com.softwarecraftsmen.dns.ServiceInformationPrioritised.prioritise;
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import static com.softwarecraftsmen.dns.names.HostName.hostName;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.*;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class ServiceInformationPrioritisedTest
|
||||
{
|
||||
private static final Unsigned16BitInteger somePort = One;
|
||||
private static final HostName someHostName = hostName("www.google.com.");
|
||||
|
||||
@Test
|
||||
public void supportsNotHavingAnyRecords()
|
||||
{
|
||||
final Iterator<ServiceInformation> informationIterator = prioritise().iterator();
|
||||
assertFalse(informationIterator.hasNext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsOneRecord()
|
||||
{
|
||||
final ServiceInformation expected = serviceInformation(Zero, Zero, somePort, someHostName);
|
||||
final Iterator<ServiceInformation> informationIterator = prioritise(expected).iterator();
|
||||
assertTrue(informationIterator.hasNext());
|
||||
assertThat(informationIterator.next(), is(expected));
|
||||
assertFalse(informationIterator.hasNext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correctlySortsRecordsOfDifferentPrioritiesWhenAlreadySorted()
|
||||
{
|
||||
final ServiceInformation lower = serviceInformation(Zero, Zero, somePort, someHostName);
|
||||
final ServiceInformation higher = serviceInformation(One, Zero, somePort, someHostName);
|
||||
final Iterator<ServiceInformation> informationIterator = prioritise(lower, higher).iterator();
|
||||
assertThat(informationIterator.next(), is(lower));
|
||||
assertThat(informationIterator.next(), is(higher));
|
||||
assertFalse(informationIterator.hasNext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correctlySortsRecordsOfDifferentPrioritiesWhenNotAlreadySorted()
|
||||
{
|
||||
final ServiceInformation lower = serviceInformation(Zero, Zero, somePort, someHostName);
|
||||
final ServiceInformation higher = serviceInformation(One, Zero, somePort, someHostName);
|
||||
final Iterator<ServiceInformation> informationIterator = prioritise(higher, lower).iterator();
|
||||
assertThat(informationIterator.next(), is(lower));
|
||||
assertThat(informationIterator.next(), is(higher));
|
||||
assertFalse(informationIterator.hasNext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correctlySortsRecordsOfDifferentPrioritiesWhenTwoAreTheSamePriority()
|
||||
{
|
||||
final ServiceInformation lowest = serviceInformation(Zero, Zero, somePort, someHostName);
|
||||
final ServiceInformation middleAndSame1 = serviceInformation(One, Zero, somePort, someHostName);
|
||||
final ServiceInformation middleAndSame2 = serviceInformation(One, Zero, somePort, someHostName);
|
||||
final ServiceInformation highest = serviceInformation(Four, Zero, somePort, someHostName);
|
||||
final Iterator<ServiceInformation> informationIterator = prioritise(middleAndSame1, highest, lowest, middleAndSame2).iterator();
|
||||
assertThat(informationIterator.next(), is(lowest));
|
||||
assertThat(informationIterator.next(), is(middleAndSame1));
|
||||
assertThat(informationIterator.next(), is(middleAndSame2));
|
||||
assertThat(informationIterator.next(), is(highest));
|
||||
assertFalse(informationIterator.hasNext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void randomlyOrdersByWeightThoseRecordsOfTheSamePriority()
|
||||
{
|
||||
final ServiceInformation lowest = serviceInformation(Zero, Zero, somePort, someHostName);
|
||||
final ServiceInformation middleAndSame1 = serviceInformation(Zero, One, somePort, someHostName);
|
||||
final ServiceInformation middleAndSame2 = serviceInformation(Zero, One, somePort, someHostName);
|
||||
final ServiceInformation highest = serviceInformation(Zero, Four, somePort, someHostName);
|
||||
|
||||
final Iterator<ServiceInformation> informationIterator = new ServiceInformationPrioritised(new BentWeightRandomNumberGenerator(), new ArrayList<ServiceInformation>()
|
||||
{{
|
||||
add(highest);
|
||||
add(middleAndSame2);
|
||||
add(lowest);
|
||||
add(middleAndSame1);
|
||||
}}).iterator();
|
||||
|
||||
assertThat(informationIterator.next(), is(highest));
|
||||
assertThat(informationIterator.next(), is(middleAndSame1));
|
||||
assertThat(informationIterator.next(), is(middleAndSame2));
|
||||
assertThat(informationIterator.next(), is(lowest));
|
||||
assertFalse(informationIterator.hasNext());
|
||||
}
|
||||
|
||||
public static final class BentWeightRandomNumberGenerator implements WeightRandomNumberGenerator
|
||||
{
|
||||
@NotNull
|
||||
public Unsigned16BitInteger generate(final @NotNull Unsigned16BitInteger maximum)
|
||||
{
|
||||
return maximum;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns;
|
||||
|
||||
import static com.softwarecraftsmen.dns.ServiceInformation.serviceInformation;
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import static com.softwarecraftsmen.dns.names.HostName.hostName;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ServiceInformationTest
|
||||
{
|
||||
private static final Unsigned16BitInteger somePort = One;
|
||||
private static final HostName someHostName = hostName("www.google.com.");
|
||||
|
||||
@Test
|
||||
public void compareToSortsBasedOnPriorityFirst()
|
||||
{
|
||||
final ServiceInformation lowest = serviceInformation(Zero, Zero, somePort, someHostName);
|
||||
final ServiceInformation middleAndSame1 = serviceInformation(One, Zero, somePort, someHostName);
|
||||
final ServiceInformation middleAndSame2 = serviceInformation(One, Zero, somePort, someHostName);
|
||||
final ServiceInformation highest = serviceInformation(Four, Zero, somePort, someHostName);
|
||||
|
||||
assertThat(lowest, lessThan(middleAndSame1));
|
||||
assertThat(lowest, lessThan(middleAndSame2));
|
||||
assertThat(middleAndSame1, lessThan(highest));
|
||||
assertThat(middleAndSame2, lessThan(highest));
|
||||
|
||||
assertThat(lowest, lessThan(highest));
|
||||
assertThat(highest, greaterThan(middleAndSame1));
|
||||
assertThat(highest, greaterThan(middleAndSame2));
|
||||
assertThat(highest, greaterThan(lowest));
|
||||
|
||||
assertThat(middleAndSame1, lessThanOrEqualTo(middleAndSame2));
|
||||
assertThat(middleAndSame2, lessThanOrEqualTo(middleAndSame1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compareToSortsBasedOnPriorityThenWeight()
|
||||
{
|
||||
final ServiceInformation samePriorityZeroWeight = serviceInformation(One, Zero, somePort, someHostName);
|
||||
final ServiceInformation samePriorityOneWeight = serviceInformation(One, One, somePort, someHostName);
|
||||
|
||||
assertThat(samePriorityZeroWeight, lessThan(samePriorityOneWeight));
|
||||
assertThat(samePriorityOneWeight, greaterThan(samePriorityZeroWeight));
|
||||
}
|
||||
}
|
||||
155
src/test/java/com/softwarecraftsmen/dns/client/ClientAcceptanceTest.java
Executable file
155
src/test/java/com/softwarecraftsmen/dns/client/ClientAcceptanceTest.java
Executable file
@@ -0,0 +1,155 @@
|
||||
package com.softwarecraftsmen.dns.client;
|
||||
|
||||
import com.softwarecraftsmen.Optional;
|
||||
import com.softwarecraftsmen.dns.names.DomainName;
|
||||
import static com.softwarecraftsmen.dns.names.DomainName.domainName;
|
||||
import com.softwarecraftsmen.dns.HostInformation;
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import static com.softwarecraftsmen.dns.names.HostName.hostName;
|
||||
import com.softwarecraftsmen.dns.MailExchange;
|
||||
import static com.softwarecraftsmen.dns.SerializableInternetProtocolAddress.serializableInternetProtocolVersion4Address;
|
||||
import static com.softwarecraftsmen.dns.SerializableInternetProtocolAddress.serializableInternetProtocolVersion6Address;
|
||||
import com.softwarecraftsmen.dns.labels.ServiceLabel;
|
||||
import static com.softwarecraftsmen.dns.labels.ServiceLabel.serviceLabel;
|
||||
import com.softwarecraftsmen.dns.ServiceInformation;
|
||||
import static com.softwarecraftsmen.dns.labels.ServiceProtocolLabel.TCP;
|
||||
import com.softwarecraftsmen.dns.Text;
|
||||
import com.softwarecraftsmen.dns.client.resolvers.SynchronousDnsResolver;
|
||||
import static com.softwarecraftsmen.dns.client.serverAddressFinders.BindLikeServerAddressFinder.CachedBindLikeServerAddressFinder;
|
||||
import com.softwarecraftsmen.dns.client.resourceRecordRepositories.NonCachingResourceRecordRepository;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.number.OrderingComparisons.greaterThan;
|
||||
import org.junit.Assert;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.util.Set;
|
||||
|
||||
public class ClientAcceptanceTest
|
||||
{
|
||||
private static final HostName GoogleAliasHostName = hostName("www.google.com");
|
||||
private static final HostName GoogleCanonicalHostName = hostName("www.l.google.com");
|
||||
private static final DomainName GoogleDomainName = domainName("google.com");
|
||||
private static final HostName GooglePointerName = hostName("nf-in-f104.google.com");
|
||||
|
||||
@Test
|
||||
public void findAllInternetProtocolVersion4AddressesForNonCanonicalName()
|
||||
{
|
||||
final Set<Inet4Address> version4Addresses = client.findAllInternetProtocolVersion4Addresses(GoogleAliasHostName);
|
||||
assertThat(version4Addresses.size(), is(greaterThan(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAllInternetProtocolVersion4AddressesForCanonicalName()
|
||||
{
|
||||
final Set<Inet4Address> version4Addresses = client.findAllInternetProtocolVersion4Addresses(GoogleCanonicalHostName);
|
||||
assertThat(version4Addresses.size(), is(greaterThan(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAllInternetProtocolVersion6AddressesForNonCanonicalName()
|
||||
{
|
||||
final Set<Inet6Address> version6Addresses = client.findAllInternetProtocolVersion6Addresses(GoogleAliasHostName);
|
||||
assertThat(version6Addresses.size(), is(equalTo(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAllInternetProtocolVersion6AddressesForCanonicalName()
|
||||
{
|
||||
final Set<Inet6Address> version6Addresses = client.findAllInternetProtocolVersion6Addresses(GoogleCanonicalHostName);
|
||||
assertThat(version6Addresses.size(), is(equalTo(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findMailServers()
|
||||
{
|
||||
final Set<MailExchange> mailExchanges = client.findMailServers(GoogleDomainName);
|
||||
assertThat(mailExchanges.size(), is(greaterThan(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findNonExistentMailServers()
|
||||
{
|
||||
final Set<MailExchange> mailExchanges = client.findMailServers(domainName("doesnotexist.google.com"));
|
||||
assertThat(mailExchanges.size(), is(equalTo(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findText()
|
||||
{
|
||||
final Optional<Text> texts = client.findText(GoogleAliasHostName);
|
||||
assertThat(texts.size(), is(equalTo(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findHostInformation()
|
||||
{
|
||||
final Optional<HostInformation> hostInformations = client.findHostInformation(GoogleAliasHostName);
|
||||
assertThat(hostInformations.size(), is(equalTo(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findCanonicalName()
|
||||
{
|
||||
final Optional<HostName> canonicalName = client.findCanonicalName(GoogleAliasHostName);
|
||||
assertThat(canonicalName.value(), is(equalTo(GoogleCanonicalHostName)));
|
||||
}
|
||||
|
||||
// Does not have any values returned. Odd.
|
||||
@Test
|
||||
public void findCanonicalNameHasAValueIfNameIsAlsoCanonical()
|
||||
{
|
||||
final Optional<HostName> canonicalName = client.findCanonicalName(GoogleCanonicalHostName);
|
||||
Assert.assertTrue(canonicalName.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findNameFromInternetProtocolVersion4Address()
|
||||
{
|
||||
final Optional<HostName> name = client.findNameFromInternetProtocolVersion4Address(serializableInternetProtocolVersion4Address(64, 233, 183, 104).address);
|
||||
assertThat(name.value(), is(equalTo(GooglePointerName)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findNameFromInternetProtocolVersion4AddressFromSerializable()
|
||||
{
|
||||
final Optional<HostName> name = client.findNameFromInternetProtocolVersion4Address(serializableInternetProtocolVersion4Address(64, 233, 183, 104));
|
||||
assertThat(name.value(), is(equalTo(GooglePointerName)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findNameFromInternetProtocolVersion6Address()
|
||||
{
|
||||
// 4321:0:1:2:3:4:567:89ab
|
||||
final Optional<HostName> resolvedName = client.findNameFromInternetProtocolVersion6Address(serializableInternetProtocolVersion6Address(0x4321, 0x0, 0x1, 0x2, 0x3, 0x4, 0x567, 0x89ab).address);
|
||||
assertThat(resolvedName.size(), is(equalTo(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findNameFromInternetProtocolVersion6AddressFromSerializable()
|
||||
{
|
||||
// 4321:0:1:2:3:4:567:89ab
|
||||
final Optional<HostName> resolvedName = client.findNameFromInternetProtocolVersion6Address(serializableInternetProtocolVersion6Address(0x4321, 0x0, 0x1, 0x2, 0x3, 0x4, 0x567, 0x89ab));
|
||||
assertThat(resolvedName.size(), is(equalTo(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findServiceInformation()
|
||||
{
|
||||
final ServiceLabel serviceLabel = serviceLabel("_ldap");
|
||||
final Set<ServiceInformation> actualServiceInformation = client.findServiceInformation(serviceLabel, TCP, GoogleDomainName);
|
||||
assertThat(actualServiceInformation.size(), is(equalTo(0)));
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before()
|
||||
{
|
||||
client = new Client(new NonCachingResourceRecordRepository(new SynchronousDnsResolver(CachedBindLikeServerAddressFinder)));
|
||||
}
|
||||
|
||||
private Client client;
|
||||
}
|
||||
195
src/test/java/com/softwarecraftsmen/dns/client/ClientTest.java
Executable file
195
src/test/java/com/softwarecraftsmen/dns/client/ClientTest.java
Executable file
@@ -0,0 +1,195 @@
|
||||
package com.softwarecraftsmen.dns.client;
|
||||
|
||||
import com.softwarecraftsmen.Optional;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.unsigned16BitInteger;
|
||||
import com.softwarecraftsmen.dns.*;
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import com.softwarecraftsmen.dns.names.DomainName;
|
||||
import com.softwarecraftsmen.dns.names.PointerName;
|
||||
import com.softwarecraftsmen.dns.names.ServiceName;
|
||||
import com.softwarecraftsmen.dns.client.resourceRecordRepositories.NonCachingResourceRecordRepository;
|
||||
import com.softwarecraftsmen.dns.client.resolvers.MockDnsResolver;
|
||||
import static com.softwarecraftsmen.dns.names.DomainName.domainName;
|
||||
import static com.softwarecraftsmen.dns.HostInformation.hostInformation;
|
||||
import static com.softwarecraftsmen.dns.names.HostName.hostName;
|
||||
import static com.softwarecraftsmen.dns.MailExchange.mailExchange;
|
||||
import static com.softwarecraftsmen.dns.Seconds.seconds;
|
||||
import static com.softwarecraftsmen.dns.SerializableInternetProtocolAddress.serializableInternetProtocolVersion4Address;
|
||||
import static com.softwarecraftsmen.dns.SerializableInternetProtocolAddress.serializableInternetProtocolVersion6Address;
|
||||
import static com.softwarecraftsmen.dns.labels.ServiceLabel.serviceLabel;
|
||||
import com.softwarecraftsmen.dns.labels.ServiceLabel;
|
||||
import static com.softwarecraftsmen.dns.ServiceInformation.serviceInformation;
|
||||
import static com.softwarecraftsmen.dns.labels.ServiceProtocolLabel.TCP;
|
||||
import static com.softwarecraftsmen.dns.Text.text;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.CanonicalNameResourceRecord.canonicalNameResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.HostInformationResourceRecord.hostInformationResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.InternetProtocolVersion4AddressResourceRecord.internetProtocolVersion4AddressResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.InternetProtocolVersion6AddressResourceRecord.internetProtocolVersion6AddressResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.MailExchangeResourceRecord.mailExchangeResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.PointerResourceRecord.pointerResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.ServiceInformationResourceRecord.serviceInformationResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.TextResourceRecord.textResourceRecord;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.number.OrderingComparisons.greaterThan;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.util.Set;
|
||||
|
||||
public class ClientTest
|
||||
{
|
||||
private static final HostName CanonicalName = hostName("www.l.google.com");
|
||||
private static final HostName AliasName = hostName("www.google.com");
|
||||
private static final DomainName SomeDomainName = domainName("google.com");
|
||||
private static final HostName ResolvedReverseLookupHostName = hostName("nf-in-f104.google.com");
|
||||
private static final SerializableInternetProtocolAddress<Inet4Address> ExampleInternetProtocolVersion4Address = serializableInternetProtocolVersion4Address(64, 233, 183, 104);
|
||||
private static final SerializableInternetProtocolAddress<Inet6Address> ExampleInternetProtocolVersion6Address = serializableInternetProtocolVersion6Address(0x4321, 0x0, 0x1, 0x2, 0x3, 0x4, 0x567, 0x89ab);
|
||||
|
||||
@Test
|
||||
public void findAllInternetProtocolVersion4AddressesForNonCanonicalName()
|
||||
{
|
||||
dnsResolver.program(internetProtocolVersion4AddressResourceRecord(CanonicalName, seconds(1000), serializableInternetProtocolVersion4Address(1, 2, 3, 4)));
|
||||
dnsResolver.program(internetProtocolVersion4AddressResourceRecord(CanonicalName, seconds(1000), serializableInternetProtocolVersion4Address(2, 2, 3, 4)));
|
||||
dnsResolver.program(canonicalNameResourceRecord(AliasName, seconds(1000), CanonicalName));
|
||||
final Set<Inet4Address> version4Addresses = client.findAllInternetProtocolVersion4Addresses(AliasName);
|
||||
assertThat(version4Addresses.size(), is(greaterThan(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAllInternetProtocolVersion4AddressesForCanonicalName()
|
||||
{
|
||||
dnsResolver.program(internetProtocolVersion4AddressResourceRecord(CanonicalName, seconds(1000), serializableInternetProtocolVersion4Address(1, 2, 3, 4)));
|
||||
dnsResolver.program(internetProtocolVersion4AddressResourceRecord(CanonicalName, seconds(1000), serializableInternetProtocolVersion4Address(2, 2, 3, 4)));
|
||||
final Set<Inet4Address> version4Addresses = client.findAllInternetProtocolVersion4Addresses(CanonicalName);
|
||||
assertThat(version4Addresses.size(), is(greaterThan(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAllInternetProtocolVersion6AddressesForNonCanonicalName()
|
||||
{
|
||||
dnsResolver.program(internetProtocolVersion6AddressResourceRecord(CanonicalName, seconds(1000), serializableInternetProtocolVersion6Address(2001, 0x0db8, 0x0000, 0x0000, 0x0000, 0x0000, 0x1428, 0x57ab)));
|
||||
dnsResolver.program(internetProtocolVersion6AddressResourceRecord(CanonicalName, seconds(1000), serializableInternetProtocolVersion6Address(2001, 0x0db8, 0x0000, 0x0000, 0x0000, 0x0000, 0x1428, 0x57ac)));
|
||||
dnsResolver.program(canonicalNameResourceRecord(AliasName, seconds(1000), CanonicalName));
|
||||
|
||||
final Set<Inet6Address> version6Addresses = client.findAllInternetProtocolVersion6Addresses(AliasName);
|
||||
assertThat(version6Addresses.size(), is(greaterThan(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAllInternetProtocolVersion6AddressesForCanonicalName()
|
||||
{
|
||||
dnsResolver.program(internetProtocolVersion6AddressResourceRecord(CanonicalName, seconds(1000), serializableInternetProtocolVersion6Address(2001, 0x0db8, 0x0000, 0x0000, 0x0000, 0x0000, 0x1428, 0x57ab)));
|
||||
dnsResolver.program(internetProtocolVersion6AddressResourceRecord(CanonicalName, seconds(1000), serializableInternetProtocolVersion6Address(2001, 0x0db8, 0x0000, 0x0000, 0x0000, 0x0000, 0x1428, 0x57ac)));
|
||||
final Set<Inet6Address> version6Addresses = client.findAllInternetProtocolVersion6Addresses(CanonicalName);
|
||||
assertThat(version6Addresses.size(), is(greaterThan(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findMailServers()
|
||||
{
|
||||
dnsResolver.program(mailExchangeResourceRecord(SomeDomainName, seconds(1000), mailExchange(unsigned16BitInteger(10), hostName("smtp1.google.com"))));
|
||||
dnsResolver.program(mailExchangeResourceRecord(SomeDomainName, seconds(1000), mailExchange(unsigned16BitInteger(10), hostName("smtp2.google.com"))));
|
||||
final Set<MailExchange> mailExchanges = client.findMailServers(SomeDomainName);
|
||||
assertThat(mailExchanges.size(), is(greaterThan(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findNonExistentMailServers()
|
||||
{
|
||||
final Set<MailExchange> mailExchanges = client.findMailServers(domainName("doesnotexist.google.com"));
|
||||
assertThat(mailExchanges.size(), is(equalTo(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findText()
|
||||
{
|
||||
final Text text = text("hello=world");
|
||||
dnsResolver.program(textResourceRecord(CanonicalName, seconds(1000), text));
|
||||
final Optional<Text> texts = client.findText(CanonicalName);
|
||||
assertThat(texts.value(), is(equalTo(text)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findHostInformation()
|
||||
{
|
||||
final HostInformation hostInformation = hostInformation("i386", "Linux");
|
||||
dnsResolver.program(hostInformationResourceRecord(CanonicalName, seconds(1000), hostInformation));
|
||||
final Optional<HostInformation> hostInformations = client.findHostInformation(CanonicalName);
|
||||
assertThat(hostInformations.value(), is(equalTo(hostInformation)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findCanonicalName()
|
||||
{
|
||||
dnsResolver.program(canonicalNameResourceRecord(AliasName, seconds(1000), CanonicalName));
|
||||
final Optional<HostName> canonicalName = client.findCanonicalName(AliasName);
|
||||
assertThat(canonicalName.value(), is(equalTo(CanonicalName)));
|
||||
}
|
||||
|
||||
// Does not have any values returned. Odd.
|
||||
// How do we distinguish a canonical name from no name - ?SOA?
|
||||
@Test
|
||||
public void findCanonicalNameHasAValueIfNameIsAlsoCanonical()
|
||||
{
|
||||
final Optional<HostName> canonicalName = client.findCanonicalName(CanonicalName);
|
||||
assertThat(canonicalName.size(), is(equalTo(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findNameFromInternetProtocolVersion4Address()
|
||||
{
|
||||
dnsResolver.program(pointerResourceRecord(PointerName.pointerName(ExampleInternetProtocolVersion4Address.address), seconds(1000), ResolvedReverseLookupHostName));
|
||||
final Optional<HostName> resolvedName = client.findNameFromInternetProtocolVersion4Address(ExampleInternetProtocolVersion4Address.address);
|
||||
assertThat(resolvedName.value(), is(equalTo(ResolvedReverseLookupHostName)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findNameFromInternetProtocolVersion4AddressFromSerializable()
|
||||
{
|
||||
dnsResolver.program(pointerResourceRecord(PointerName.pointerName(ExampleInternetProtocolVersion4Address.address), seconds(1000), ResolvedReverseLookupHostName));
|
||||
final Optional<HostName> resolvedName = client.findNameFromInternetProtocolVersion4Address(ExampleInternetProtocolVersion4Address);
|
||||
assertThat(resolvedName.value(), is(equalTo(ResolvedReverseLookupHostName)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findNameFromInternetProtocolVersion6Address()
|
||||
{
|
||||
// 4321:0:1:2:3:4:567:89ab
|
||||
dnsResolver.program(pointerResourceRecord(PointerName.pointerName(ExampleInternetProtocolVersion6Address.address), seconds(1000), ResolvedReverseLookupHostName));
|
||||
final Optional<HostName> resolvedName = client.findNameFromInternetProtocolVersion6Address(ExampleInternetProtocolVersion6Address.address);
|
||||
assertThat(resolvedName.value(), is(equalTo(ResolvedReverseLookupHostName)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findNameFromInternetProtocolVersion6AddressFromSerializable()
|
||||
{
|
||||
// 4321:0:1:2:3:4:567:89ab
|
||||
dnsResolver.program(pointerResourceRecord(PointerName.pointerName(ExampleInternetProtocolVersion6Address.address), seconds(1000), ResolvedReverseLookupHostName));
|
||||
final Optional<HostName> resolvedName = client.findNameFromInternetProtocolVersion6Address(ExampleInternetProtocolVersion6Address);
|
||||
assertThat(resolvedName.value(), is(equalTo(ResolvedReverseLookupHostName)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findServiceInformation()
|
||||
{
|
||||
final ServiceLabel serviceLabel = serviceLabel("_ldap");
|
||||
final ServiceInformation expectedServiceInformation = serviceInformation(unsigned16BitInteger(100), unsigned16BitInteger(10), unsigned16BitInteger(8080), AliasName);
|
||||
dnsResolver.program(serviceInformationResourceRecord(ServiceName.serviceName(serviceLabel, TCP, SomeDomainName), seconds(1000), expectedServiceInformation));
|
||||
final Set<ServiceInformation> actualServiceInformation = client.findServiceInformation(serviceLabel, TCP, SomeDomainName);
|
||||
assertThat(actualServiceInformation.size(), is(greaterThan(0)));
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before()
|
||||
{
|
||||
dnsResolver = new MockDnsResolver();
|
||||
client = new Client(new NonCachingResourceRecordRepository(dnsResolver));
|
||||
}
|
||||
|
||||
private MockDnsResolver dnsResolver;
|
||||
private Client client;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.client.resolvers;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.Name;
|
||||
import com.softwarecraftsmen.dns.messaging.InternetClassType;
|
||||
import com.softwarecraftsmen.dns.messaging.Message;
|
||||
import com.softwarecraftsmen.dns.messaging.MessageHeader;
|
||||
import com.softwarecraftsmen.dns.messaging.Question;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import com.softwarecraftsmen.dns.resourceRecords.ResourceRecord;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class MockDnsResolver implements DnsResolver
|
||||
{
|
||||
private List<ResourceRecord<? extends Name, ? extends Serializable>> resourceRecords;
|
||||
private int resolvedCalledCount;
|
||||
|
||||
public MockDnsResolver()
|
||||
{
|
||||
resourceRecords = new ArrayList<ResourceRecord<? extends Name, ? extends Serializable>>();
|
||||
resolvedCalledCount = 0;
|
||||
}
|
||||
|
||||
public void program(final @NotNull ResourceRecord<? extends Name, ? extends Serializable> resourceRecord)
|
||||
{
|
||||
resourceRecords.add(resourceRecord);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<ResourceRecord<? extends Name, ? extends Serializable>> findAllMatchingRecords()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void assertResolveCalledOnceOnly()
|
||||
{
|
||||
assertThat(resolvedCalledCount, is(equalTo(1)));
|
||||
}
|
||||
|
||||
public void assertResolveCalledTwice()
|
||||
{
|
||||
assertThat(resolvedCalledCount, is(equalTo(2)));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Message resolve(final @NotNull Name name, final @NotNull InternetClassType internetClassType)
|
||||
{
|
||||
resolvedCalledCount++;
|
||||
final MessageHeader messageHeader = new MessageHeader(com.softwarecraftsmen.dns.messaging.MessageId.messageId(), com.softwarecraftsmen.dns.messaging.MessageHeaderFlags.reply(true), com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.Zero, com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.unsigned16BitInteger(resourceRecords.size()), com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.Zero, com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.Zero);
|
||||
return new Message(messageHeader, new ArrayList<Question>(), resourceRecords, com.softwarecraftsmen.dns.messaging.Message.NoResourceRecords, com.softwarecraftsmen.dns.messaging.Message.NoResourceRecords);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.client.resourceRecordRepositories;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import static com.softwarecraftsmen.dns.names.HostName.hostName;
|
||||
import static com.softwarecraftsmen.dns.Seconds.seconds;
|
||||
import com.softwarecraftsmen.dns.SerializableInternetProtocolAddress;
|
||||
import static com.softwarecraftsmen.dns.SerializableInternetProtocolAddress.serializableInternetProtocolVersion4Address;
|
||||
import com.softwarecraftsmen.dns.client.resolvers.MockDnsResolver;
|
||||
import static com.softwarecraftsmen.dns.messaging.InternetClassType.A;
|
||||
import static com.softwarecraftsmen.dns.messaging.InternetClassType.CNAME;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.CanonicalNameResourceRecord.canonicalNameResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.InternetProtocolVersion4AddressResourceRecord.internetProtocolVersion4AddressResourceRecord;
|
||||
import org.hamcrest.Matcher;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasItems;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.util.Set;
|
||||
import static java.lang.Thread.sleep;
|
||||
|
||||
public class CachingResourceRecordRepositoryTest
|
||||
{
|
||||
private static final HostName CanonicalName = hostName("www.l.google.com");
|
||||
private static final HostName AliasName = hostName("www.google.com");
|
||||
|
||||
@Test
|
||||
public void canSelectMultipleRecordsOfTheSameTypeFromDifferentTypes()
|
||||
{
|
||||
final SerializableInternetProtocolAddress<Inet4Address> address1 = serializableInternetProtocolVersion4Address(1, 2, 3, 4);
|
||||
final SerializableInternetProtocolAddress<Inet4Address> address2 = serializableInternetProtocolVersion4Address(2, 2, 3, 4);
|
||||
dnsResolver.program(internetProtocolVersion4AddressResourceRecord(CanonicalName, seconds(1000), address1));
|
||||
dnsResolver.program(internetProtocolVersion4AddressResourceRecord(CanonicalName, seconds(1001), address2));
|
||||
dnsResolver.program(canonicalNameResourceRecord(AliasName, seconds(1002), CanonicalName));
|
||||
final Iterable<SerializableInternetProtocolAddress<Inet4Address>> data = cachingResourceRecordRepository.findData(AliasName, A);
|
||||
final Matcher<Iterable<SerializableInternetProtocolAddress<Inet4Address>>> iterableMatcher = hasItems(address1, address2);
|
||||
assertThat(data, iterableMatcher);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void secondSelectionUsesCacheAndNotDnsResolverButLooksAtAliasRecordsInCache()
|
||||
{
|
||||
cachingResourceRecordRepository = new CachingResourceRecordRepository(dnsResolver, seconds(5000));
|
||||
canSelectMultipleRecordsOfTheSameTypeFromDifferentTypes();
|
||||
dnsResolver.assertResolveCalledOnceOnly();
|
||||
|
||||
cachingResourceRecordRepository.findData(AliasName, CNAME);
|
||||
dnsResolver.assertResolveCalledOnceOnly();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void secondSelectionUsesCacheAndNotDnsResolverButLooksAtCanonicalRecordsInCache()
|
||||
{
|
||||
cachingResourceRecordRepository = new CachingResourceRecordRepository(dnsResolver, seconds(5000));
|
||||
canSelectMultipleRecordsOfTheSameTypeFromDifferentTypes();
|
||||
dnsResolver.assertResolveCalledOnceOnly();
|
||||
|
||||
cachingResourceRecordRepository.findData(CanonicalName, A);
|
||||
dnsResolver.assertResolveCalledOnceOnly();
|
||||
}
|
||||
|
||||
// TODO: Ignores SOA responses and negative caching.
|
||||
@Test
|
||||
public void nonExistentResultIsAlwaysRequeried()
|
||||
{
|
||||
final Set<SerializableInternetProtocolAddress<Inet4Address>> firstCall = cachingResourceRecordRepository.findData(AliasName, A);
|
||||
assertThat(firstCall.size(), is(equalTo(0)));
|
||||
dnsResolver.assertResolveCalledOnceOnly();
|
||||
|
||||
final Set<SerializableInternetProtocolAddress<Inet4Address>> secondCall = cachingResourceRecordRepository.findData(AliasName, A);
|
||||
assertThat(secondCall.size(), is(equalTo(0)));
|
||||
dnsResolver.assertResolveCalledTwice();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cacheExpiryAfterMaximumTimeToLiveResultsInASecondCall() throws InterruptedException
|
||||
{
|
||||
canSelectMultipleRecordsOfTheSameTypeFromDifferentTypes();
|
||||
dnsResolver.assertResolveCalledOnceOnly();
|
||||
|
||||
final int OneAndAHalfSeconds = 1500;
|
||||
sleep(OneAndAHalfSeconds);
|
||||
|
||||
final Set<SerializableInternetProtocolAddress<Inet4Address>> secondCall = cachingResourceRecordRepository.findData(AliasName, A);
|
||||
assertThat(secondCall.size(), is(equalTo(2)));
|
||||
dnsResolver.assertResolveCalledTwice();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before()
|
||||
{
|
||||
dnsResolver = new MockDnsResolver();
|
||||
cachingResourceRecordRepository = new CachingResourceRecordRepository(dnsResolver, seconds(1));
|
||||
}
|
||||
|
||||
private MockDnsResolver dnsResolver;
|
||||
private CachingResourceRecordRepository cachingResourceRecordRepository;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.client.resourceRecordRepositories;
|
||||
|
||||
import com.softwarecraftsmen.dns.names.HostName;
|
||||
import static com.softwarecraftsmen.dns.names.HostName.hostName;
|
||||
import static com.softwarecraftsmen.dns.Seconds.seconds;
|
||||
import com.softwarecraftsmen.dns.SerializableInternetProtocolAddress;
|
||||
import static com.softwarecraftsmen.dns.SerializableInternetProtocolAddress.serializableInternetProtocolVersion4Address;
|
||||
import com.softwarecraftsmen.dns.client.resolvers.MockDnsResolver;
|
||||
import com.softwarecraftsmen.dns.messaging.InternetClassType;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.CanonicalNameResourceRecord.canonicalNameResourceRecord;
|
||||
import static com.softwarecraftsmen.dns.resourceRecords.InternetProtocolVersion4AddressResourceRecord.internetProtocolVersion4AddressResourceRecord;
|
||||
import org.hamcrest.Matcher;
|
||||
import static org.hamcrest.Matchers.hasItems;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
|
||||
public class NonCachingResourceRecordRepositoryTest
|
||||
{
|
||||
private static final HostName CanonicalName = hostName("www.l.google.com");
|
||||
private static final HostName AliasName = hostName("www.google.com");
|
||||
|
||||
@Test
|
||||
public void canSelectMultipleRecordsOfTheSameTypeFromDifferentTypes()
|
||||
{
|
||||
final SerializableInternetProtocolAddress<Inet4Address> address1 = serializableInternetProtocolVersion4Address(1, 2, 3, 4);
|
||||
final SerializableInternetProtocolAddress<Inet4Address> address2 = serializableInternetProtocolVersion4Address(2, 2, 3, 4);
|
||||
dnsResolver.program(internetProtocolVersion4AddressResourceRecord(CanonicalName, seconds(1000), address1));
|
||||
dnsResolver.program(internetProtocolVersion4AddressResourceRecord(CanonicalName, seconds(1000), address2));
|
||||
dnsResolver.program(canonicalNameResourceRecord(AliasName, seconds(1000), CanonicalName));
|
||||
final Iterable<SerializableInternetProtocolAddress<Inet4Address>> data = nonCachingResourceRecordRepository.findData(AliasName, InternetClassType.A);
|
||||
final Matcher<Iterable<SerializableInternetProtocolAddress<Inet4Address>>> iterableMatcher = hasItems(address1, address2);
|
||||
assertThat(data, iterableMatcher);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before()
|
||||
{
|
||||
dnsResolver = new MockDnsResolver();
|
||||
nonCachingResourceRecordRepository = new NonCachingResourceRecordRepository(dnsResolver);
|
||||
}
|
||||
|
||||
private MockDnsResolver dnsResolver;
|
||||
private NonCachingResourceRecordRepository nonCachingResourceRecordRepository;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.labels;
|
||||
|
||||
import com.softwarecraftsmen.dns.labels.ServiceLabel.ServiceClassLabelMustBeLessThan15CharactersException;
|
||||
import static com.softwarecraftsmen.dns.labels.ServiceLabel.serviceLabel;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ServiceLabelTest
|
||||
{
|
||||
@Test(expected = ServiceClassLabelMustBeLessThan15CharactersException.class)
|
||||
public void aServiceClassLabelMustBeLessThan14Characters()
|
||||
{
|
||||
serviceLabel("012345678901234");
|
||||
}
|
||||
|
||||
@Test(expected = ServiceClassLabelMustBeLessThan15CharactersException.class)
|
||||
public void aServiceClassLabelMustBeLessThan15CharactersIfItStartsWithAnUnderscore()
|
||||
{
|
||||
serviceLabel("_012345678901234");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void aServiceClassLabelCanNotBeEmpty()
|
||||
{
|
||||
serviceLabel("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEmptyIsAlwaysFalse()
|
||||
{
|
||||
assertFalse(serviceLabel("http").isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringRepresentationHasAnUnderscore()
|
||||
{
|
||||
assertThat(serviceLabel("http").toStringRepresentation(), is("_http"));
|
||||
assertThat(serviceLabel("_http").toStringRepresentation(), is("_http"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void length()
|
||||
{
|
||||
assertThat(serviceLabel("http").length(), is(5));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.labels;
|
||||
|
||||
import static com.softwarecraftsmen.dns.labels.ServiceProtocolLabel.TCP;
|
||||
import static com.softwarecraftsmen.dns.labels.ServiceProtocolLabel.toServiceProtocolLabel;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ServiceProtocolLabelTest
|
||||
{
|
||||
@Test
|
||||
public void isEmptyIsAlwaysFalse()
|
||||
{
|
||||
assertTrue(TCP.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringRepresentationHasAnUnderscore()
|
||||
{
|
||||
assertThat(TCP.toStringRepresentation(), is("_tcp"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void length()
|
||||
{
|
||||
assertThat(TCP.length(), is(4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toServiceProtocolLabelPresent()
|
||||
{
|
||||
assertThat(toServiceProtocolLabel("tcp"), is(TCP));
|
||||
assertThat(toServiceProtocolLabel("_tcp"), is(TCP));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void toServiceProtocolLabelUnrecognised()
|
||||
{
|
||||
toServiceProtocolLabel("notrecognised");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.labels;
|
||||
|
||||
import static com.softwarecraftsmen.dns.labels.ServiceLabel.serviceLabel;
|
||||
import static com.softwarecraftsmen.dns.labels.ServiceProtocolLabel.TCP;
|
||||
import static com.softwarecraftsmen.dns.labels.SimpleLabel.Empty;
|
||||
import com.softwarecraftsmen.dns.labels.SimpleLabel.LabelsCanNotBeLongerThan63CharactersException;
|
||||
import com.softwarecraftsmen.dns.labels.SimpleLabel.LabelsCanNotContainPeriodsException;
|
||||
import com.softwarecraftsmen.dns.labels.SimpleLabel;
|
||||
import static com.softwarecraftsmen.dns.labels.SimpleLabel.simpleLabel;
|
||||
import com.softwarecraftsmen.dns.NonAsciiAndControlCharactersAreNotSupportedInCharacterStringsException;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SimpleLabelTest
|
||||
{
|
||||
@Test(expected = LabelsCanNotContainPeriodsException.class)
|
||||
public void labelsCanNotContainPeriods()
|
||||
{
|
||||
simpleLabel("www.softwarecraftsmen.com");
|
||||
}
|
||||
|
||||
@Test(expected = LabelsCanNotBeLongerThan63CharactersException.class)
|
||||
public void labelsCanNotBeLongerThan63Bytes()
|
||||
{
|
||||
simpleLabel("01234567890123456789012345678901234567890123456789012345678901234");
|
||||
}
|
||||
|
||||
@Test(expected = NonAsciiAndControlCharactersAreNotSupportedInCharacterStringsException.class)
|
||||
public void labelsCanNotContainNonAsciiCharacters()
|
||||
{
|
||||
simpleLabel("\u0100");
|
||||
}
|
||||
|
||||
@Test(expected = NonAsciiAndControlCharactersAreNotSupportedInCharacterStringsException.class)
|
||||
public void labelsCanNotContainControlCharacters()
|
||||
{
|
||||
simpleLabel("\u0000");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyIsEmpty()
|
||||
{
|
||||
assertThat(Empty.isEmpty(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEmptyIsNotEmpty()
|
||||
{
|
||||
assertThat(simpleLabel("www").isEmpty(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringRepresentationHasAnUnderscore()
|
||||
{
|
||||
assertThat(simpleLabel("www").toStringRepresentation(), is("www"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void length()
|
||||
{
|
||||
assertThat(simpleLabel("www").length(), is(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toServiceLabel()
|
||||
{
|
||||
assertThat(simpleLabel("_http").toServiceLabel(), is(serviceLabel("_http")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toServiceProtocolLabelPresent()
|
||||
{
|
||||
assertThat(simpleLabel("tcp").toServiceProtocolLabel(), is(TCP));
|
||||
assertThat(simpleLabel("_tcp").toServiceProtocolLabel(), is(TCP));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void toServiceProtocolLabelUnrecognised()
|
||||
{
|
||||
simpleLabel("notrecognised").toServiceProtocolLabel();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compareToSortsSimpleLabelLast()
|
||||
{
|
||||
final SimpleLabel empty = Empty;
|
||||
final SimpleLabel a = simpleLabel("a");
|
||||
final SimpleLabel b = simpleLabel("b");
|
||||
|
||||
assertThat(empty, lessThanOrEqualTo(empty));
|
||||
assertThat(empty, lessThan(a));
|
||||
assertThat(a, lessThan(b));
|
||||
assertThat(b, greaterThan(a));
|
||||
}
|
||||
}
|
||||
19
src/test/java/com/softwarecraftsmen/dns/messaging/ClassTest.java
Executable file
19
src/test/java/com/softwarecraftsmen/dns/messaging/ClassTest.java
Executable file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import static com.softwarecraftsmen.dns.messaging.serializer.ByteSerializer.serialize;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ClassTest
|
||||
{
|
||||
@Test
|
||||
public void serializesAsTwoOctets()
|
||||
{
|
||||
assertThat(serialize(QClass.Internet), is(equalTo(new byte[] { 0x00, 0x01 })));
|
||||
}
|
||||
}
|
||||
23
src/test/java/com/softwarecraftsmen/dns/messaging/InternetClassTypeTest.java
Executable file
23
src/test/java/com/softwarecraftsmen/dns/messaging/InternetClassTypeTest.java
Executable file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import static com.softwarecraftsmen.dns.messaging.serializer.ByteSerializer.serialize;
|
||||
import com.softwarecraftsmen.dns.messaging.InternetClassType;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.junit.Test;
|
||||
|
||||
public class InternetClassTypeTest
|
||||
{
|
||||
@Test
|
||||
public void serializesAsTwoOctets()
|
||||
{
|
||||
assertThat(serialize(InternetClassType.DLV), is(equalTo(new byte[]{
|
||||
-0x80,
|
||||
0x01,
|
||||
})));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import static com.softwarecraftsmen.dns.messaging.ResponseCode.NoErrorCondition;
|
||||
import static com.softwarecraftsmen.dns.messaging.serializer.ByteSerializer.serialize;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned3BitInteger.Zero;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.junit.Test;
|
||||
|
||||
public class MessageHeaderFlagsTest
|
||||
{
|
||||
@Test
|
||||
public void serializesQueryAsTwoOctets()
|
||||
{
|
||||
assertThat(serialize(MessageHeaderFlags.Query), is(equalTo(new byte[]{0x01, 0x00})));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializesSomethingElseCorrectly()
|
||||
{
|
||||
assertThat(serialize(new MessageHeaderFlags(true, OperationCode.Query, false, false, true, true, Zero, NoErrorCondition)), is(equalTo(new byte[]{65, 0x02})));
|
||||
}
|
||||
}
|
||||
50
src/test/java/com/softwarecraftsmen/dns/messaging/MessageHeaderTest.java
Executable file
50
src/test/java/com/softwarecraftsmen/dns/messaging/MessageHeaderTest.java
Executable file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.unsigned16BitInteger;
|
||||
import com.softwarecraftsmen.unsignedIntegers.Unsigned3BitInteger;
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned3BitInteger.Zero;
|
||||
import static com.softwarecraftsmen.dns.messaging.ResponseCode.NameError;
|
||||
import static com.softwarecraftsmen.dns.messaging.ResponseCode.NoErrorCondition;
|
||||
import static com.softwarecraftsmen.dns.messaging.serializer.ByteSerializer.serialize;
|
||||
import static com.softwarecraftsmen.dns.messaging.OperationCode.InverseQuery;
|
||||
import static com.softwarecraftsmen.dns.messaging.OperationCode.Query;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
public class MessageHeaderTest
|
||||
{
|
||||
@Test
|
||||
public void serializesHeaderAsExpectedForAClientTalkingToAServer()
|
||||
{
|
||||
final MessageHeader header = new MessageHeader(new MessageId(unsigned16BitInteger(0x0F0F)), MessageHeaderFlags.Query, unsigned16BitInteger(1), unsigned16BitInteger(2), unsigned16BitInteger(3), unsigned16BitInteger(4));
|
||||
|
||||
assertThat(serialize(header), is(equalTo(new byte[]
|
||||
{
|
||||
0x0F, 0x0F,
|
||||
0x01, 0x00,
|
||||
0x00, 0x01,
|
||||
0x00, 0x02,
|
||||
0x00, 0x03,
|
||||
0x00, 0x04
|
||||
})));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replyHeaderIsForRequestHeader()
|
||||
{
|
||||
final Unsigned3BitInteger z = Zero;
|
||||
final MessageHeader request = new MessageHeader(new MessageId(unsigned16BitInteger(0x0F0F)), new MessageHeaderFlags(false, InverseQuery, false, false, true, false, z, NoErrorCondition), unsigned16BitInteger(2), unsigned16BitInteger(0), unsigned16BitInteger(0), unsigned16BitInteger(0));
|
||||
final MessageHeader matchingReply = new MessageHeader(new MessageId(unsigned16BitInteger(0x0F0F)), new MessageHeaderFlags(true, InverseQuery, true, false, true, true, z, NameError), unsigned16BitInteger(2), unsigned16BitInteger(0), unsigned16BitInteger(0), unsigned16BitInteger(0));
|
||||
final MessageHeader nonMatchingReply1 = new MessageHeader(new MessageId(unsigned16BitInteger(0x0F00)), new MessageHeaderFlags(true, InverseQuery, true, false, true, true, z, NameError), unsigned16BitInteger(2), unsigned16BitInteger(0), unsigned16BitInteger(0), unsigned16BitInteger(0));
|
||||
final MessageHeader nonMatchingReply2 = new MessageHeader(new MessageId(unsigned16BitInteger(0x0F0F)), new MessageHeaderFlags(true, Query, true, false, true, true, z, NameError), unsigned16BitInteger(2), unsigned16BitInteger(0), unsigned16BitInteger(0), unsigned16BitInteger(0));
|
||||
|
||||
assertTrue(request.matchesReply(matchingReply));
|
||||
assertFalse(request.matchesReply(nonMatchingReply1));
|
||||
assertFalse(request.matchesReply(nonMatchingReply2));
|
||||
}
|
||||
}
|
||||
20
src/test/java/com/softwarecraftsmen/dns/messaging/MessageIdTest.java
Executable file
20
src/test/java/com/softwarecraftsmen/dns/messaging/MessageIdTest.java
Executable file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.One;
|
||||
import static com.softwarecraftsmen.dns.messaging.serializer.ByteSerializer.serialize;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.junit.Test;
|
||||
|
||||
public class MessageIdTest
|
||||
{
|
||||
@Test
|
||||
public void serializesAsTwoOctets()
|
||||
{
|
||||
assertThat(serialize(new MessageId(One)), is(equalTo(new byte[]{0x00, 0x01})));
|
||||
}
|
||||
}
|
||||
65
src/test/java/com/softwarecraftsmen/dns/messaging/MessageTest.java
Executable file
65
src/test/java/com/softwarecraftsmen/dns/messaging/MessageTest.java
Executable file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
|
||||
*/
|
||||
package com.softwarecraftsmen.dns.messaging;
|
||||
|
||||
import static com.softwarecraftsmen.unsignedIntegers.Unsigned16BitInteger.One;
|
||||
import static com.softwarecraftsmen.dns.names.HostName.hostName;
|
||||
import static com.softwarecraftsmen.dns.messaging.InternetClassType.A;
|
||||
import static com.softwarecraftsmen.dns.messaging.Message.NoResourceRecords;
|
||||
import static com.softwarecraftsmen.dns.messaging.MessageHeader.outboundMessageHeader;
|
||||
import static com.softwarecraftsmen.dns.messaging.Question.internetQuestion;
|
||||
import static com.softwarecraftsmen.dns.messaging.serializer.ByteSerializer.serialize;
|
||||
import com.softwarecraftsmen.dns.messaging.serializer.Serializable;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MessageTest
|
||||
{
|
||||
@Test
|
||||
public void serializesQueriesWithTwoQuestionsInCorrectSequence()
|
||||
{
|
||||
final ArrayList<Question> questions = new ArrayList<Question>()
|
||||
{{
|
||||
add(internetQuestion(hostName("www.softwarecraftsmen.com"), A));
|
||||
add(internetQuestion(hostName("www.softwarecraftsmen.co.uk"), A));
|
||||
}};
|
||||
final MessageHeader messageHeader = outboundMessageHeader(new MessageId(One), questions);
|
||||
|
||||
final byte[] expected = new ArrayList<Byte>()
|
||||
{
|
||||
{
|
||||
appendSerializedForm(messageHeader);
|
||||
for (Question question : questions)
|
||||
{
|
||||
appendSerializedForm(question);
|
||||
}
|
||||
}
|
||||
|
||||
private void appendSerializedForm(final Serializable serializable)
|
||||
{
|
||||
for (byte aByte : serialize(serializable))
|
||||
{
|
||||
add(aByte);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] toPrimitiveByteArray()
|
||||
{
|
||||
final byte[] bytes = new byte[size()];
|
||||
for (int index = 0; index < size(); index++)
|
||||
{
|
||||
bytes[index] = get(index);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}.toPrimitiveByteArray();
|
||||
|
||||
final Message message = new Message(messageHeader, questions, NoResourceRecords, NoResourceRecords, NoResourceRecords);
|
||||
assertThat(serialize(message), is(equalTo(expected)));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user