Initial checkin

This commit is contained in:
Paul Philion
2012-05-04 17:44:49 -07:00
parent b782e3eb21
commit fe31169282
107 changed files with 7759 additions and 0 deletions

51
pom.xml Normal file
View 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>

View 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()
{}
}

View 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>();
}
}

View 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);
}
}

View 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);
}
}

View 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());
}};
}
}

View 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);
}
}

View File

@@ -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);
}
}
}

View 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));
}
}

View 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;
}
}

View 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);
}
}

View File

@@ -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;
}
}
}

View 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);
}
}

View 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

View 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));
}
}

View 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();
}
}

View 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);
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View 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();
}

View 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");
}
}
}

View 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));
}
}

View 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));
}
}
}

View 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();
}
}

View File

@@ -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);
}
}

View 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());
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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));
}
}

View 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));
}
}

View 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);
}
}

View 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;
}
}

View 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()
);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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");
}
}

View 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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View 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");
}
}
}

View 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)
);
}
}

View 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)
);
}
}

View 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();
}

View 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);
}
}

View 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());
}};
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View 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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View 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()
{}
}

View 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View 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);
}
}

View 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));
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}
}

View File

@@ -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));
}
}

View 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;
}

View 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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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));
}
}

View File

@@ -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");
}
}

View File

@@ -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));
}
}

View 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 })));
}
}

View 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,
})));
}
}

View File

@@ -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})));
}
}

View 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));
}
}

View 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})));
}
}

View 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