Commit 7ea0becf authored by Brickner_cp's avatar Brickner_cp

ICMP wireshark tests

parent 2eef92c2
...@@ -111,6 +111,11 @@ namespace PcapDotNet.Base ...@@ -111,6 +111,11 @@ namespace PcapDotNet.Base
}); });
} }
public static string BytesSequenceToHexadecimalString(this IEnumerable<byte> sequence)
{
return sequence.BytesSequenceToHexadecimalString(string.Empty);
}
/// <summary> /// <summary>
/// Creates a hash code by xoring the hash codes of the elements in the sequence. /// Creates a hash code by xoring the hash codes of the elements in the sequence.
/// </summary> /// </summary>
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
<Compile Include="LivePacketDeviceTests.cs" /> <Compile Include="LivePacketDeviceTests.cs" />
<Compile Include="MoreIpV4Option.cs" /> <Compile Include="MoreIpV4Option.cs" />
<Compile Include="MoreTcpOption.cs" /> <Compile Include="MoreTcpOption.cs" />
<Compile Include="MoreXElement.cs" /> <Compile Include="XElementExtensions.cs" />
<Compile Include="PacketDumpFileTests.cs" /> <Compile Include="PacketDumpFileTests.cs" />
<Compile Include="PacketHandler.cs" /> <Compile Include="PacketHandler.cs" />
<Compile Include="OfflinePacketDeviceTests.cs" /> <Compile Include="OfflinePacketDeviceTests.cs" />
......
...@@ -5,6 +5,7 @@ using System.Globalization; ...@@ -5,6 +5,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Threading; using System.Threading;
using System.Xml.Linq; using System.Xml.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
...@@ -12,6 +13,7 @@ using PcapDotNet.Base; ...@@ -12,6 +13,7 @@ using PcapDotNet.Base;
using PcapDotNet.Packets; using PcapDotNet.Packets;
using PcapDotNet.Packets.Arp; using PcapDotNet.Packets.Arp;
using PcapDotNet.Packets.Ethernet; using PcapDotNet.Packets.Ethernet;
using PcapDotNet.Packets.Icmp;
using PcapDotNet.Packets.Igmp; using PcapDotNet.Packets.Igmp;
using PcapDotNet.Packets.IpV4; using PcapDotNet.Packets.IpV4;
using PcapDotNet.Packets.TestUtils; using PcapDotNet.Packets.TestUtils;
...@@ -108,6 +110,7 @@ namespace PcapDotNet.Core.Test ...@@ -108,6 +110,7 @@ namespace PcapDotNet.Core.Test
Arp, Arp,
IpV4, IpV4,
Igmp, Igmp,
Icmp,
Udp, Udp,
Tcp Tcp
} }
...@@ -138,6 +141,13 @@ namespace PcapDotNet.Core.Test ...@@ -138,6 +141,13 @@ namespace PcapDotNet.Core.Test
ipV4Layer.Protocol = null; ipV4Layer.Protocol = null;
return PacketBuilder.Build(packetTimestamp, ethernetLayer, ipV4Layer, random.NextIgmpLayer()); return PacketBuilder.Build(packetTimestamp, ethernetLayer, ipV4Layer, random.NextIgmpLayer());
case PacketType.Icmp:
ethernetLayer.EtherType = EthernetType.None;
ipV4Layer.Protocol = null;
IcmpLayer icmpLayer = random.NextIcmpLayer();
IEnumerable<ILayer> icmpPayloadLayers = random.NextIcmpPayloadLayers(icmpLayer);
return PacketBuilder.Build(packetTimestamp, new ILayer[]{ethernetLayer, ipV4Layer, icmpLayer}.Concat(icmpPayloadLayers));
case PacketType.Udp: case PacketType.Udp:
ethernetLayer.EtherType = EthernetType.None; ethernetLayer.EtherType = EthernetType.None;
ipV4Layer.Protocol = null; ipV4Layer.Protocol = null;
...@@ -220,7 +230,14 @@ namespace PcapDotNet.Core.Test ...@@ -220,7 +230,14 @@ namespace PcapDotNet.Core.Test
packetEnumerator.MoveNext(); packetEnumerator.MoveNext();
Packet packet = packetEnumerator.Current; Packet packet = packetEnumerator.Current;
ComparePacket(packet, documentPacket); try
{
ComparePacket(packet, documentPacket);
}
catch (Exception e)
{
throw new AssertFailedException("Failed comparing packet " + i + ". " + e.Message, e);
}
++i; ++i;
} }
} }
...@@ -228,7 +245,12 @@ namespace PcapDotNet.Core.Test ...@@ -228,7 +245,12 @@ namespace PcapDotNet.Core.Test
private static void ComparePacket(Packet packet, XElement documentPacket) private static void ComparePacket(Packet packet, XElement documentPacket)
{ {
object currentDatagram = packet; object currentDatagram = packet;
foreach (var layer in documentPacket.Elements("proto")) CompareProtocols(currentDatagram, documentPacket);
}
private static void CompareProtocols(object currentDatagram, XElement layersContainer)
{
foreach (var layer in layersContainer.Protocols())
{ {
switch (layer.Name()) switch (layer.Name())
{ {
...@@ -236,7 +258,7 @@ namespace PcapDotNet.Core.Test ...@@ -236,7 +258,7 @@ namespace PcapDotNet.Core.Test
break; break;
case "frame": case "frame":
CompareFrame(layer, packet); CompareFrame(layer, (Packet)currentDatagram);
break; break;
case "eth": case "eth":
...@@ -271,6 +293,14 @@ namespace PcapDotNet.Core.Test ...@@ -271,6 +293,14 @@ namespace PcapDotNet.Core.Test
CompareIgmp(layer, (IgmpDatagram)currentDatagram); CompareIgmp(layer, (IgmpDatagram)currentDatagram);
break; break;
case "icmp":
PropertyInfo icmpProperty = currentDatagram.GetType().GetProperty("Icmp");
if (icmpProperty == null)
break;
currentDatagram = icmpProperty.GetValue(currentDatagram);
CompareIcmp(layer, (IcmpDatagram)currentDatagram);
break;
case "udp": case "udp":
PropertyInfo udpProperty = currentDatagram.GetType().GetProperty("Udp"); PropertyInfo udpProperty = currentDatagram.GetType().GetProperty("Udp");
if (udpProperty == null) if (udpProperty == null)
...@@ -379,12 +409,12 @@ namespace PcapDotNet.Core.Test ...@@ -379,12 +409,12 @@ namespace PcapDotNet.Core.Test
case "arp.src.hw": case "arp.src.hw":
case "arp.src.hw_mac": case "arp.src.hw_mac":
field.AssertShow(arpDatagram.SenderHardwareAddress.BytesSequenceToHexadecimalString(":")); field.AssertShow(arpDatagram.SenderHardwareAddress);
break; break;
case "arp.src.proto": case "arp.src.proto":
field.AssertShow(arpDatagram.SenderProtocolAddress.BytesSequenceToHexadecimalString(":")); field.AssertShow(arpDatagram.SenderProtocolAddress);
break; break;
case "arp.src.proto_ipv4": case "arp.src.proto_ipv4":
...@@ -393,11 +423,11 @@ namespace PcapDotNet.Core.Test ...@@ -393,11 +423,11 @@ namespace PcapDotNet.Core.Test
case "arp.dst.hw": case "arp.dst.hw":
case "arp.dst.hw_mac": case "arp.dst.hw_mac":
field.AssertShow(arpDatagram.TargetHardwareAddress.BytesSequenceToHexadecimalString(":")); field.AssertShow(arpDatagram.TargetHardwareAddress);
break; break;
case "arp.dst.proto": case "arp.dst.proto":
field.AssertShow(arpDatagram.TargetProtocolAddress.BytesSequenceToHexadecimalString(":")); field.AssertShow(arpDatagram.TargetProtocolAddress);
break; break;
case "arp.dst.proto_ipv4": case "arp.dst.proto_ipv4":
...@@ -607,7 +637,12 @@ namespace PcapDotNet.Core.Test ...@@ -607,7 +637,12 @@ namespace PcapDotNet.Core.Test
break; break;
default: default:
throw new InvalidOperationException("Invalid message type " + igmpDatagram.MessageType); if (typeof(IgmpMessageType).GetEnumValues<IgmpMessageType>().Contains(igmpDatagram.MessageType))
throw new InvalidOperationException("Invalid message type " + igmpDatagram.MessageType);
field.AssertValue(igmpDatagram.Skip(1));
// field.AssertShow(igmpDatagram.Skip(1));
break;
} }
break; break;
...@@ -666,7 +701,7 @@ namespace PcapDotNet.Core.Test ...@@ -666,7 +701,7 @@ namespace PcapDotNet.Core.Test
break; break;
case "igmp.aux_data": case "igmp.aux_data":
field.AssertShow(groupRecordDatagram.AuxiliaryData.BytesSequenceToHexadecimalString(":")); field.AssertShow(groupRecordDatagram.AuxiliaryData);
break; break;
default: default:
...@@ -675,6 +710,147 @@ namespace PcapDotNet.Core.Test ...@@ -675,6 +710,147 @@ namespace PcapDotNet.Core.Test
} }
} }
private static void CompareIcmp(XElement icmp, IcmpDatagram icmpDatagram)
{
int routerIndex = 0;
foreach (var field in icmp.Fields())
{
switch (field.Name())
{
case "icmp.type":
field.AssertShowDecimal((byte)icmpDatagram.MessageType);
break;
case "icmp.code":
field.AssertShowHex(icmpDatagram.Code);
break;
case "icmp.checksum_bad":
field.AssertShowDecimal(!icmpDatagram.IsChecksumCorrect);
break;
case "icmp.checksum":
field.AssertShowHex(icmpDatagram.Checksum);
break;
case "data":
field.AssertValue(((IcmpIpV4HeaderPlus64BitsPayloadDatagram)icmpDatagram).IpV4.Payload);
break;
case "data.data":
field.AssertShow(((IcmpIpV4HeaderPlus64BitsPayloadDatagram)icmpDatagram).IpV4.Payload);
break;
case "data.len":
field.AssertShowDecimal(((IcmpIpV4HeaderPlus64BitsPayloadDatagram)icmpDatagram).IpV4.Payload.Length);
break;
case "":
switch (icmpDatagram.MessageType)
{
case IcmpMessageType.ParameterProblem:
if (field.Show() != "Unknown session type")
field.AssertShow("Pointer: " + ((IcmpParameterProblemDatagram)icmpDatagram).Pointer);
break;
case IcmpMessageType.RouterAdvertisement:
IcmpRouterAdvertisementDatagram routerAdvertisementDatagram = (IcmpRouterAdvertisementDatagram)icmpDatagram;
string fieldName = field.Show().Split(':')[0];
switch (fieldName)
{
case "Number of addresses":
field.AssertShow(fieldName + ": " + routerAdvertisementDatagram.NumAddresses);
break;
case "Address entry size":
field.AssertShow(fieldName + ": " + routerAdvertisementDatagram.AddressEntrySize);
break;
case "Lifetime":
TimeSpan actualLifetime = routerAdvertisementDatagram.Lifetime;
StringBuilder actualLifetimeString = new StringBuilder(fieldName + ": ");
if (actualLifetime.Hours != 0)
{
actualLifetimeString.Append(actualLifetime.Hours + " hour");
if (actualLifetime.Hours != 1)
actualLifetimeString.Append('s');
}
if (actualLifetime.Minutes != 0)
{
if (actualLifetime.Hours != 0)
actualLifetimeString.Append(", ");
actualLifetimeString.Append(actualLifetime.Minutes + " minute");
if (actualLifetime.Minutes != 1)
actualLifetimeString.Append('s');
}
if (actualLifetime.Seconds != 0)
{
if (actualLifetime.Hours != 0 || actualLifetime.Minutes != 0)
actualLifetimeString.Append(", ");
actualLifetimeString.Append(actualLifetime.Seconds + " second");
if (actualLifetime.Seconds != 1)
actualLifetimeString.Append('s');
}
break;
case "Router address":
field.AssertShow(fieldName + ": " + routerAdvertisementDatagram.Entries[routerIndex].RouterAddress);
break;
case "Preference level":
field.AssertShow(fieldName + ": " + routerAdvertisementDatagram.Entries[routerIndex++].RouterAddressPreference);
break;
default:
throw new InvalidOperationException("Invalid icmp " + icmpDatagram.MessageType + " field " + fieldName);
}
break;
}
break;
case "icmp.ident":
field.AssertShowHex(((IcmpIdentifiedDatagram)icmpDatagram).Identifier);
break;
case "icmp.seq":
field.AssertShowDecimal(((IcmpIdentifiedDatagram)icmpDatagram).SequenceNumber);
break;
case "icmp.redir_gw":
field.AssertShow(((IcmpRedirectDatagram)icmpDatagram).GatewayInternetAddress.ToString());
break;
case "icmp.mtu":
field.AssertShowDecimal(((IcmpDestinationUnreachableDatagram)icmpDatagram).NextHopMtu);
break;
default:
if (!field.Name().StartsWith("lt2p."))
throw new InvalidOperationException("Invalid icmp field " + field.Name());
break;
}
}
CompareProtocols(icmpDatagram, icmp);
// object currentDatagram = icmpDatagram;
// foreach (var layer in icmp.Protocols())
// {
// switch (layer.Name())
// {
// case "ip":
// PropertyInfo ipV4Property = currentDatagram.GetType().GetProperty("IpV4");
// if (ipV4Property == null)
// break;
// currentDatagram = ipV4Property.GetValue(currentDatagram);
// CompareIpV4(layer, (IpV4Datagram)currentDatagram);
// break;
//
// default:
// throw new InvalidOperationException("unexpected layer under icmp: " + layer.Name());
// }
// }
}
private static void CompareUdp(XElement udpElement, IpV4Datagram ipV4Datagram) private static void CompareUdp(XElement udpElement, IpV4Datagram ipV4Datagram)
{ {
UdpDatagram udpDatagram = ipV4Datagram.Udp; UdpDatagram udpDatagram = ipV4Datagram.Udp;
...@@ -713,7 +889,10 @@ namespace PcapDotNet.Core.Test ...@@ -713,7 +889,10 @@ namespace PcapDotNet.Core.Test
break; break;
case "udp.checksum_bad": case "udp.checksum_bad":
checksumField.AssertShowDecimal(!ipV4Datagram.IsTransportChecksumCorrect); if (checksumField.Show() == "1")
Assert.IsFalse(ipV4Datagram.IsTransportChecksumCorrect);
else
checksumField.AssertShowDecimal(0);
break; break;
} }
} }
......
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml.Linq; using System.Xml.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using PcapDotNet.Base;
namespace PcapDotNet.Core.Test namespace PcapDotNet.Core.Test
{ {
internal static class MoreXElement internal static class XElementExtensions
{ {
public static IEnumerable<XElement> Fields(this XElement element) public static IEnumerable<XElement> Fields(this XElement element)
{ {
return element.Elements("field"); return element.Elements("field");
} }
public static IEnumerable<XElement> Protocols(this XElement element)
{
return element.Elements("proto");
}
public static string GetAttributeValue(this XElement element, string attributeName)
{
XAttribute attribute = element.Attribute(attributeName);
if (attribute == null)
throw new ArgumentException("element " + element.Name + " doesn't contain attribute " + attributeName, "attributeName");
return attribute.Value;
}
public static string Name(this XElement element) public static string Name(this XElement element)
{ {
return element.Attribute("name").Value; return element.GetAttributeValue("name");
} }
public static string Show(this XElement element) public static string Show(this XElement element)
{ {
return element.Attribute("show").Value; return element.GetAttributeValue("show");
} }
public static string Value(this XElement element) public static string Value(this XElement element)
{ {
return element.Attribute("value").Value; return element.GetAttributeValue("value");
} }
public static void AssertShow(this XElement element, string value) public static void AssertShow(this XElement element, string value)
{ {
Assert.AreEqual(element.Show(), value); Assert.AreEqual(element.Show(), value, element.Name());
}
public static void AssertShow(this XElement element, IEnumerable<byte> value)
{
element.AssertShow(value.BytesSequenceToHexadecimalString(":"));
} }
public static void AssertShowDecimal(this XElement element, bool value) public static void AssertShowDecimal(this XElement element, bool value)
...@@ -38,72 +59,82 @@ namespace PcapDotNet.Core.Test ...@@ -38,72 +59,82 @@ namespace PcapDotNet.Core.Test
public static void AssertShowDecimal(this XElement element, byte value) public static void AssertShowDecimal(this XElement element, byte value)
{ {
Assert.AreEqual(element.Show(), value.ToString()); element.AssertShow(value.ToString());
} }
public static void AssertShowDecimal(this XElement element, short value) public static void AssertShowDecimal(this XElement element, short value)
{ {
Assert.AreEqual(element.Show(), value.ToString()); element.AssertShow(value.ToString());
} }
public static void AssertShowDecimal(this XElement element, ushort value) public static void AssertShowDecimal(this XElement element, ushort value)
{ {
Assert.AreEqual(element.Show(), value.ToString()); element.AssertShow(value.ToString());
} }
public static void AssertShowDecimal(this XElement element, int value) public static void AssertShowDecimal(this XElement element, int value)
{ {
Assert.AreEqual(element.Show(), value.ToString(), element.Name()); element.AssertShow(value.ToString());
} }
public static void AssertShowDecimal(this XElement element, uint value) public static void AssertShowDecimal(this XElement element, uint value)
{ {
Assert.AreEqual(element.Show(), value.ToString()); element.AssertShow(value.ToString());
} }
public static void AssertShowDecimal(this XElement element, long value) public static void AssertShowDecimal(this XElement element, long value)
{ {
Assert.AreEqual(element.Show(), value.ToString()); element.AssertShow(value.ToString());
} }
public static void AssertShowDecimal(this XElement element, ulong value) public static void AssertShowDecimal(this XElement element, ulong value)
{ {
Assert.AreEqual(element.Show(), value.ToString()); element.AssertShow(value.ToString());
} }
public static void AssertShowHex(this XElement element, byte value) public static void AssertShowHex(this XElement element, byte value)
{ {
Assert.AreEqual(element.Show(), "0x" + value.ToString("x" + 2 * sizeof(byte))); element.AssertShow("0x" + value.ToString("x" + 2 * sizeof(byte)));
} }
public static void AssertShowHex(this XElement element, short value) public static void AssertShowHex(this XElement element, short value)
{ {
Assert.AreEqual(element.Show(), "0x" + value.ToString("x" + 2 * sizeof(ushort))); element.AssertShow("0x" + value.ToString("x" + 2 * sizeof(short)));
} }
public static void AssertShowHex(this XElement element, ushort value) public static void AssertShowHex(this XElement element, ushort value)
{ {
Assert.AreEqual(element.Show(), "0x" + value.ToString("x" + 2 * sizeof(ushort))); element.AssertShow("0x" + value.ToString("x" + 2 * sizeof(ushort)));
} }
public static void AssertShowHex(this XElement element, int value) public static void AssertShowHex(this XElement element, int value)
{ {
Assert.AreEqual(element.Show(), "0x" + value.ToString("x" + 2 * sizeof(int))); element.AssertShow("0x" + value.ToString("x" + 2 * sizeof(int)));
} }
public static void AssertShowHex(this XElement element, uint value) public static void AssertShowHex(this XElement element, uint value)
{ {
Assert.AreEqual(element.Show(), "0x" + value.ToString("x" + 2 * sizeof(uint))); element.AssertShow("0x" + value.ToString("x" + 2 * sizeof(uint)));
} }
public static void AssertShowHex(this XElement element, long value) public static void AssertShowHex(this XElement element, long value)
{ {
Assert.AreEqual(element.Show(), "0x" + value.ToString("x" + 2 * sizeof(long))); element.AssertShow("0x" + value.ToString("x" + 2 * sizeof(long)));
} }
public static void AssertShowHex(this XElement element, ulong value) public static void AssertShowHex(this XElement element, ulong value)
{ {
Assert.AreEqual(element.Show(), "0x" + value.ToString("x" + 2 * sizeof(ulong))); element.AssertShow("0x" + value.ToString("x" + 2 * sizeof(ulong)));
}
public static void AssertValue(this XElement element, string value)
{
Assert.AreEqual(element.Value(), value, element.Name());
}
public static void AssertValue(this XElement element, IEnumerable<byte> bytes)
{
element.AssertValue(bytes.BytesSequenceToHexadecimalString());
} }
} }
} }
\ No newline at end of file
...@@ -28,6 +28,7 @@ void PacketCommunicator::DataLink::set(PcapDataLink value) ...@@ -28,6 +28,7 @@ void PacketCommunicator::DataLink::set(PcapDataLink value)
ReadOnlyCollection<PcapDataLink>^ PacketCommunicator::SupportedDataLinks::get() ReadOnlyCollection<PcapDataLink>^ PacketCommunicator::SupportedDataLinks::get()
{ {
throw gcnew NotSupportedException("Supported DataLinks is unsupported to avoid winpcap memory leak"); throw gcnew NotSupportedException("Supported DataLinks is unsupported to avoid winpcap memory leak");
// pcap_free_datalinks(NULL);
/* /*
int* dataLinks; int* dataLinks;
int numDatalinks = pcap_list_datalinks(_pcapDescriptor, &dataLinks); int numDatalinks = pcap_list_datalinks(_pcapDescriptor, &dataLinks);
......
...@@ -83,103 +83,28 @@ namespace PcapDotNet.Packets.Test ...@@ -83,103 +83,28 @@ namespace PcapDotNet.Packets.Test
{ {
IcmpLayer icmpLayer = random.NextIcmpLayer(); IcmpLayer icmpLayer = random.NextIcmpLayer();
icmpLayer.Checksum = null; icmpLayer.Checksum = null;
if (icmpLayer.MessageType == IcmpMessageType.DestinationUnreachable &&
IpV4Layer icmpIpV4Layer = null; icmpLayer.MessageTypeAndCode != IcmpMessageTypeAndCode.DestinationUnreachableFragmentationNeededAndDontFragmentSet)
IEnumerable<ILayer> icmpIpV4PayloadLayers = null;
switch (icmpLayer.MessageType)
{ {
case IcmpMessageType.DestinationUnreachable: ((IcmpDestinationUnreachableLayer)icmpLayer).NextHopMtu = 0;
case IcmpMessageType.TimeExceeded:
case IcmpMessageType.ParameterProblem:
case IcmpMessageType.SourceQuench:
case IcmpMessageType.Redirect:
case IcmpMessageType.SecurityFailures:
icmpIpV4Layer = random.NextIpV4Layer();
icmpIpV4PayloadLayers = new[] {random.NextPayloadLayer(IcmpIpV4HeaderPlus64BitsPayloadDatagram.OriginalDatagramPayloadLength)};
break;
case IcmpMessageType.ConversionFailed:
icmpIpV4Layer = random.NextIpV4Layer();
if (icmpLayer.MessageTypeAndCode == IcmpMessageTypeAndCode.ConversionFailedUnsupportedTransportProtocol)
icmpIpV4PayloadLayers = new[]
{
random.NextPayloadLayer(
IcmpConversionFailedDatagram.OriginalDatagramLengthForUnsupportedTransportProtocol -
icmpIpV4Layer.Length)
};
else
{
switch (icmpIpV4Layer.Protocol)
{
case IpV4Protocol.Udp:
icmpIpV4PayloadLayers = new ILayer[]
{
random.NextUdpLayer(),
random.NextPayloadLayer(random.Next(100))
};
break;
case IpV4Protocol.Tcp:
icmpIpV4PayloadLayers = new ILayer[]
{
random.NextTcpLayer(),
random.NextPayloadLayer(random.Next(100))
};
break;
default:
icmpIpV4PayloadLayers = new[]
{
random.NextPayloadLayer(random.Next(200))
};
break;
}
}
break;
case IcmpMessageType.Echo:
case IcmpMessageType.EchoReply:
case IcmpMessageType.Timestamp:
case IcmpMessageType.TimestampReply:
case IcmpMessageType.InformationRequest:
case IcmpMessageType.InformationReply:
case IcmpMessageType.RouterAdvertisement:
case IcmpMessageType.RouterSolicitation:
case IcmpMessageType.AddressMaskRequest:
case IcmpMessageType.AddressMaskReply:
case IcmpMessageType.Traceroute:
case IcmpMessageType.DomainNameRequest:
break;
case IcmpMessageType.DomainNameReply:
default:
throw new InvalidOperationException("Invalid icmpMessageType " + icmpLayer.MessageType);
} }
int icmpPayloadLength = (icmpIpV4Layer != null ? icmpIpV4Layer.Length + icmpIpV4PayloadLayers.Select(layer => layer.Length).Sum() : 0); IEnumerable<ILayer> icmpPayloadLayers = random.NextIcmpPayloadLayers(icmpLayer);
int icmpPayloadLength = icmpPayloadLayers.Select(layer => layer.Length).Sum();
switch (icmpLayer.MessageType) switch (icmpLayer.MessageType)
{ {
// case IcmpMessageType.DestinationUnreachable:
// case IcmpMessageType.TimeExceeded:
case IcmpMessageType.ParameterProblem: case IcmpMessageType.ParameterProblem:
((IcmpParameterProblemLayer)icmpLayer).Pointer %= (byte)icmpPayloadLength; ((IcmpParameterProblemLayer)icmpLayer).Pointer %= (byte)icmpPayloadLength;
break; break;
// case IcmpMessageType.SourceQuench:
// case IcmpMessageType.Redirect:
case IcmpMessageType.SecurityFailures: case IcmpMessageType.SecurityFailures:
((IcmpSecurityFailuresLayer)icmpLayer).Pointer %= (ushort)icmpPayloadLength; ((IcmpSecurityFailuresLayer)icmpLayer).Pointer %= (ushort)icmpPayloadLength;
// icmpIpV4Layer = random.NextIpV4Layer();
// icmpIpV4PayloadLayers = new[] {random.NextPayloadLayer(IcmpIpV4HeaderPlus64BitsPayloadDatagram.OriginalDatagramPayloadLength)};
break; break;
// case IcmpMessageType.ConversionFailed:
} }
PacketBuilder packetBuilder; PacketBuilder packetBuilder = new PacketBuilder(new ILayer[] { ethernetLayer, ipV4Layer, icmpLayer }.Concat(icmpPayloadLayers));
if (icmpIpV4Layer != null)
packetBuilder = new PacketBuilder(new ILayer[] {ethernetLayer, ipV4Layer, icmpLayer, icmpIpV4Layer}.Concat(icmpIpV4PayloadLayers));
else
packetBuilder = new PacketBuilder(ethernetLayer, ipV4Layer, icmpLayer);
Packet packet = packetBuilder.Build(DateTime.Now); Packet packet = packetBuilder.Build(DateTime.Now);
Assert.IsTrue(packet.IsValid, "IsValid"); Assert.IsTrue(packet.IsValid, "IsValid");
...@@ -221,7 +146,8 @@ namespace PcapDotNet.Packets.Test ...@@ -221,7 +146,8 @@ namespace PcapDotNet.Packets.Test
IcmpLayer actualIcmpLayer = (IcmpLayer)actualIcmp.ExtractLayer(); IcmpLayer actualIcmpLayer = (IcmpLayer)actualIcmp.ExtractLayer();
icmpLayer.Checksum = actualIcmpLayer.Checksum; icmpLayer.Checksum = actualIcmpLayer.Checksum;
Assert.AreEqual(icmpLayer, actualIcmpLayer); Assert.AreEqual(icmpLayer, actualIcmpLayer);
Assert.AreNotEqual(random.NextIcmpLayer(), actualIcmpLayer); if (actualIcmpLayer.MessageType != IcmpMessageType.RouterSolicitation)
Assert.AreNotEqual(random.NextIcmpLayer(), actualIcmpLayer);
Assert.IsTrue(actualIcmp.IsChecksumCorrect); Assert.IsTrue(actualIcmp.IsChecksumCorrect);
Assert.AreEqual(icmpLayer.MessageType, actualIcmp.MessageType); Assert.AreEqual(icmpLayer.MessageType, actualIcmp.MessageType);
Assert.AreEqual(icmpLayer.CodeValue, actualIcmp.Code); Assert.AreEqual(icmpLayer.CodeValue, actualIcmp.Code);
...@@ -231,13 +157,13 @@ namespace PcapDotNet.Packets.Test ...@@ -231,13 +157,13 @@ namespace PcapDotNet.Packets.Test
switch (packet.Ethernet.IpV4.Icmp.MessageType) switch (packet.Ethernet.IpV4.Icmp.MessageType)
{ {
case IcmpMessageType.DestinationUnreachable:
case IcmpMessageType.RouterSolicitation: case IcmpMessageType.RouterSolicitation:
case IcmpMessageType.SourceQuench: case IcmpMessageType.SourceQuench:
case IcmpMessageType.TimeExceeded: case IcmpMessageType.TimeExceeded:
Assert.AreEqual<uint>(0, actualIcmp.Variable); Assert.AreEqual<uint>(0, actualIcmp.Variable);
break; break;
case IcmpMessageType.DestinationUnreachable:
case IcmpMessageType.ParameterProblem: case IcmpMessageType.ParameterProblem:
case IcmpMessageType.Redirect: case IcmpMessageType.Redirect:
case IcmpMessageType.ConversionFailed: case IcmpMessageType.ConversionFailed:
...@@ -257,7 +183,6 @@ namespace PcapDotNet.Packets.Test ...@@ -257,7 +183,6 @@ namespace PcapDotNet.Packets.Test
case IcmpMessageType.DomainNameRequest: case IcmpMessageType.DomainNameRequest:
case IcmpMessageType.SecurityFailures: case IcmpMessageType.SecurityFailures:
break; break;
break;
case IcmpMessageType.DomainNameReply: case IcmpMessageType.DomainNameReply:
default: default:
......
...@@ -128,8 +128,8 @@ namespace PcapDotNet.Packets.TestUtils ...@@ -128,8 +128,8 @@ namespace PcapDotNet.Packets.TestUtils
Identification = random.NextUShort(), Identification = random.NextUShort(),
Ttl = random.NextByte(), Ttl = random.NextByte(),
Protocol = protocol, Protocol = protocol,
HeaderChecksum = random.NextUShort(), HeaderChecksum = random.NextBool() ? (ushort?)random.NextUShort() : null,
Fragmentation = random.NextIpV4Fragmentation(), Fragmentation = random.NextBool() ? random.NextIpV4Fragmentation() : IpV4Fragmentation.None,
Source = random.NextIpV4Address(), Source = random.NextIpV4Address(),
Destination = random.NextIpV4Address(), Destination = random.NextIpV4Address(),
Options = random.NextIpV4Options() Options = random.NextIpV4Options()
...@@ -576,6 +576,7 @@ namespace PcapDotNet.Packets.TestUtils ...@@ -576,6 +576,7 @@ namespace PcapDotNet.Packets.TestUtils
public static IcmpLayer NextIcmpLayer(this Random random) public static IcmpLayer NextIcmpLayer(this Random random)
{ {
IcmpMessageType icmpMessageType = random.NextEnum(IcmpMessageType.DomainNameReply); IcmpMessageType icmpMessageType = random.NextEnum(IcmpMessageType.DomainNameReply);
ushort? checksum = random.NextBool() ? (ushort?)random.NextUShort() : null;
switch (icmpMessageType) switch (icmpMessageType)
{ {
...@@ -583,41 +584,42 @@ namespace PcapDotNet.Packets.TestUtils ...@@ -583,41 +584,42 @@ namespace PcapDotNet.Packets.TestUtils
return new IcmpDestinationUnreachableLayer return new IcmpDestinationUnreachableLayer
{ {
Code = random.NextEnum<IcmpCodeDestinationUnrechable>(), Code = random.NextEnum<IcmpCodeDestinationUnrechable>(),
Checksum = random.NextUShort(), Checksum = checksum,
NextHopMtu = random.NextUShort(),
}; };
case IcmpMessageType.TimeExceeded: case IcmpMessageType.TimeExceeded:
return new IcmpTimeExceededLayer return new IcmpTimeExceededLayer
{ {
Code = random.NextEnum<IcmpCodeTimeExceeded>(), Code = random.NextEnum<IcmpCodeTimeExceeded>(),
Checksum = random.NextUShort(), Checksum = checksum,
}; };
case IcmpMessageType.ParameterProblem: case IcmpMessageType.ParameterProblem:
return new IcmpParameterProblemLayer return new IcmpParameterProblemLayer
{ {
Checksum = random.NextUShort(), Checksum = checksum,
Pointer = random.NextByte() Pointer = random.NextByte()
}; };
case IcmpMessageType.SourceQuench: case IcmpMessageType.SourceQuench:
return new IcmpSourceQuenchLayer return new IcmpSourceQuenchLayer
{ {
Checksum = random.NextUShort(), Checksum = checksum
}; };
case IcmpMessageType.Redirect: case IcmpMessageType.Redirect:
return new IcmpRedirectLayer return new IcmpRedirectLayer
{ {
Code = random.NextEnum<IcmpCodeRedirect>(), Code = random.NextEnum<IcmpCodeRedirect>(),
Checksum = random.NextUShort(), Checksum = checksum,
GatewayInternetAddress = random.NextIpV4Address() GatewayInternetAddress = random.NextIpV4Address()
}; };
case IcmpMessageType.Echo: case IcmpMessageType.Echo:
return new IcmpEchoLayer return new IcmpEchoLayer
{ {
Checksum = random.NextUShort(), Checksum = checksum,
Identifier = random.NextUShort(), Identifier = random.NextUShort(),
SequenceNumber = random.NextUShort() SequenceNumber = random.NextUShort()
}; };
...@@ -625,7 +627,7 @@ namespace PcapDotNet.Packets.TestUtils ...@@ -625,7 +627,7 @@ namespace PcapDotNet.Packets.TestUtils
case IcmpMessageType.EchoReply: case IcmpMessageType.EchoReply:
return new IcmpEchoReplyLayer return new IcmpEchoReplyLayer
{ {
Checksum = random.NextUShort(), Checksum = checksum,
Identifier = random.NextUShort(), Identifier = random.NextUShort(),
SequenceNumber = random.NextUShort() SequenceNumber = random.NextUShort()
}; };
...@@ -634,7 +636,7 @@ namespace PcapDotNet.Packets.TestUtils ...@@ -634,7 +636,7 @@ namespace PcapDotNet.Packets.TestUtils
case IcmpMessageType.Timestamp: case IcmpMessageType.Timestamp:
return new IcmpTimestampLayer return new IcmpTimestampLayer
{ {
Checksum = random.NextUShort(), Checksum = checksum,
Identifier = random.NextUShort(), Identifier = random.NextUShort(),
SequenceNumber = random.NextUShort(), SequenceNumber = random.NextUShort(),
OriginateTimestamp = random.NextIpV4TimeOfDay(), OriginateTimestamp = random.NextIpV4TimeOfDay(),
...@@ -645,7 +647,7 @@ namespace PcapDotNet.Packets.TestUtils ...@@ -645,7 +647,7 @@ namespace PcapDotNet.Packets.TestUtils
case IcmpMessageType.TimestampReply: case IcmpMessageType.TimestampReply:
return new IcmpTimestampReplyLayer return new IcmpTimestampReplyLayer
{ {
Checksum = random.NextUShort(), Checksum = checksum,
Identifier = random.NextUShort(), Identifier = random.NextUShort(),
SequenceNumber = random.NextUShort(), SequenceNumber = random.NextUShort(),
OriginateTimestamp = random.NextIpV4TimeOfDay(), OriginateTimestamp = random.NextIpV4TimeOfDay(),
...@@ -656,7 +658,7 @@ namespace PcapDotNet.Packets.TestUtils ...@@ -656,7 +658,7 @@ namespace PcapDotNet.Packets.TestUtils
case IcmpMessageType.InformationRequest: case IcmpMessageType.InformationRequest:
return new IcmpInformationRequestLayer return new IcmpInformationRequestLayer
{ {
Checksum = random.NextUShort(), Checksum = checksum,
Identifier = random.NextUShort(), Identifier = random.NextUShort(),
SequenceNumber = random.NextUShort(), SequenceNumber = random.NextUShort(),
}; };
...@@ -664,7 +666,7 @@ namespace PcapDotNet.Packets.TestUtils ...@@ -664,7 +666,7 @@ namespace PcapDotNet.Packets.TestUtils
case IcmpMessageType.InformationReply: case IcmpMessageType.InformationReply:
return new IcmpInformationReplyLayer return new IcmpInformationReplyLayer
{ {
Checksum = random.NextUShort(), Checksum = checksum,
Identifier = random.NextUShort(), Identifier = random.NextUShort(),
SequenceNumber = random.NextUShort(), SequenceNumber = random.NextUShort(),
}; };
...@@ -673,7 +675,7 @@ namespace PcapDotNet.Packets.TestUtils ...@@ -673,7 +675,7 @@ namespace PcapDotNet.Packets.TestUtils
case IcmpMessageType.RouterAdvertisement: case IcmpMessageType.RouterAdvertisement:
return new IcmpRouterAdvertisementLayer return new IcmpRouterAdvertisementLayer
{ {
Checksum = random.NextUShort(), Checksum = checksum,
Lifetime = random.NextTimeSpan(TimeSpan.Zero, TimeSpan.FromSeconds(ushort.MaxValue)), Lifetime = random.NextTimeSpan(TimeSpan.Zero, TimeSpan.FromSeconds(ushort.MaxValue)),
Entries = random.NextIcmpRouterAdvertisementEntries(random.Next(10)).ToList() Entries = random.NextIcmpRouterAdvertisementEntries(random.Next(10)).ToList()
}; };
...@@ -681,13 +683,13 @@ namespace PcapDotNet.Packets.TestUtils ...@@ -681,13 +683,13 @@ namespace PcapDotNet.Packets.TestUtils
case IcmpMessageType.RouterSolicitation: case IcmpMessageType.RouterSolicitation:
return new IcmpRouterSolicitationLayer return new IcmpRouterSolicitationLayer
{ {
Checksum = random.NextUShort(), Checksum = checksum,
}; };
case IcmpMessageType.AddressMaskRequest: case IcmpMessageType.AddressMaskRequest:
return new IcmpAddressMaskRequestLayer return new IcmpAddressMaskRequestLayer
{ {
Checksum = random.NextUShort(), Checksum = checksum,
Identifier = random.NextUShort(), Identifier = random.NextUShort(),
SequenceNumber = random.NextUShort(), SequenceNumber = random.NextUShort(),
AddressMask = random.NextIpV4Address() AddressMask = random.NextIpV4Address()
...@@ -696,7 +698,7 @@ namespace PcapDotNet.Packets.TestUtils ...@@ -696,7 +698,7 @@ namespace PcapDotNet.Packets.TestUtils
case IcmpMessageType.AddressMaskReply: case IcmpMessageType.AddressMaskReply:
return new IcmpAddressMaskReplyLayer return new IcmpAddressMaskReplyLayer
{ {
Checksum = random.NextUShort(), Checksum = checksum,
Identifier = random.NextUShort(), Identifier = random.NextUShort(),
SequenceNumber = random.NextUShort(), SequenceNumber = random.NextUShort(),
AddressMask = random.NextIpV4Address() AddressMask = random.NextIpV4Address()
...@@ -706,7 +708,7 @@ namespace PcapDotNet.Packets.TestUtils ...@@ -706,7 +708,7 @@ namespace PcapDotNet.Packets.TestUtils
return new IcmpTracerouteLayer return new IcmpTracerouteLayer
{ {
Code = random.NextEnum<IcmpCodeTraceroute>(), Code = random.NextEnum<IcmpCodeTraceroute>(),
Checksum = random.NextUShort(), Checksum = checksum,
Identification = random.NextUShort(), Identification = random.NextUShort(),
OutboundHopCount = random.NextUShort(), OutboundHopCount = random.NextUShort(),
ReturnHopCount = random.NextUShort(), ReturnHopCount = random.NextUShort(),
...@@ -718,14 +720,14 @@ namespace PcapDotNet.Packets.TestUtils ...@@ -718,14 +720,14 @@ namespace PcapDotNet.Packets.TestUtils
return new IcmpConversionFailedLayer return new IcmpConversionFailedLayer
{ {
Code = random.NextEnum<IcmpCodeConversionFailed>(), Code = random.NextEnum<IcmpCodeConversionFailed>(),
Checksum = random.NextUShort(), Checksum = checksum,
Pointer = random.NextUInt(), Pointer = random.NextUInt(),
}; };
case IcmpMessageType.DomainNameRequest: case IcmpMessageType.DomainNameRequest:
return new IcmpDomainNameRequestLayer return new IcmpDomainNameRequestLayer
{ {
Checksum = random.NextUShort(), Checksum = checksum,
Identifier = random.NextUShort(), Identifier = random.NextUShort(),
SequenceNumber = random.NextUShort(), SequenceNumber = random.NextUShort(),
}; };
...@@ -737,7 +739,7 @@ namespace PcapDotNet.Packets.TestUtils ...@@ -737,7 +739,7 @@ namespace PcapDotNet.Packets.TestUtils
return new IcmpSecurityFailuresLayer return new IcmpSecurityFailuresLayer
{ {
Code = random.NextEnum<IcmpCodeSecurityFailures>(), Code = random.NextEnum<IcmpCodeSecurityFailures>(),
Checksum = random.NextUShort(), Checksum = checksum,
Pointer = random.NextUShort() Pointer = random.NextUShort()
}; };
...@@ -747,6 +749,73 @@ namespace PcapDotNet.Packets.TestUtils ...@@ -747,6 +749,73 @@ namespace PcapDotNet.Packets.TestUtils
} }
public static IEnumerable<ILayer> NextIcmpPayloadLayers(this Random random, IcmpLayer icmpLayer)
{
IEnumerable<ILayer> icmpPayloadLayers = new List<ILayer>();
switch (icmpLayer.MessageType)
{
case IcmpMessageType.DestinationUnreachable:
case IcmpMessageType.TimeExceeded:
case IcmpMessageType.ParameterProblem:
case IcmpMessageType.SourceQuench:
case IcmpMessageType.Redirect:
case IcmpMessageType.SecurityFailures:
icmpPayloadLayers = icmpPayloadLayers.Concat(random.NextIpV4Layer(), random.NextPayloadLayer(IcmpIpV4HeaderPlus64BitsPayloadDatagram.OriginalDatagramPayloadLength));
break;
case IcmpMessageType.ConversionFailed:
IpV4Layer icmpIpV4Layer = random.NextIpV4Layer();
icmpPayloadLayers = icmpPayloadLayers.Concat(icmpIpV4Layer);
if (icmpLayer.MessageTypeAndCode == IcmpMessageTypeAndCode.ConversionFailedUnsupportedTransportProtocol)
{
icmpPayloadLayers =
icmpPayloadLayers.Concat(random.NextPayloadLayer(
IcmpConversionFailedDatagram.OriginalDatagramLengthForUnsupportedTransportProtocol -
icmpIpV4Layer.Length));
}
else
{
switch (icmpIpV4Layer.Protocol)
{
case IpV4Protocol.Udp:
icmpPayloadLayers = icmpPayloadLayers.Concat(random.NextUdpLayer(),
random.NextPayloadLayer(random.Next(100)));
break;
case IpV4Protocol.Tcp:
icmpPayloadLayers = icmpPayloadLayers.Concat(random.NextTcpLayer(),
random.NextPayloadLayer(random.Next(100)));
break;
default:
icmpPayloadLayers = icmpPayloadLayers.Concat(random.NextPayloadLayer(random.Next(200)));
break;
}
}
break;
case IcmpMessageType.Echo:
case IcmpMessageType.EchoReply:
case IcmpMessageType.Timestamp:
case IcmpMessageType.TimestampReply:
case IcmpMessageType.InformationRequest:
case IcmpMessageType.InformationReply:
case IcmpMessageType.RouterAdvertisement:
case IcmpMessageType.RouterSolicitation:
case IcmpMessageType.AddressMaskRequest:
case IcmpMessageType.AddressMaskReply:
case IcmpMessageType.Traceroute:
case IcmpMessageType.DomainNameRequest:
break;
case IcmpMessageType.DomainNameReply:
default:
throw new InvalidOperationException("Invalid icmpMessageType " + icmpLayer.MessageType);
}
return icmpPayloadLayers;
}
public static IEnumerable<IcmpRouterAdvertisementEntry> NextIcmpRouterAdvertisementEntries(this Random random, int numEntries) public static IEnumerable<IcmpRouterAdvertisementEntry> NextIcmpRouterAdvertisementEntries(this Random random, int numEntries)
{ {
for (int i = 0; i != numEntries; ++i) for (int i = 0; i != numEntries; ++i)
......
...@@ -4,37 +4,55 @@ using PcapDotNet.Base; ...@@ -4,37 +4,55 @@ using PcapDotNet.Base;
namespace PcapDotNet.Packets.Icmp namespace PcapDotNet.Packets.Icmp
{ {
/// <summary> /// <summary>
/// RFC 792. /// RFC 792 and RFC 1191.
/// <pre> /// <pre>
/// +-----+------+------+-----------+ /// +-----+------+------+--------------+
/// | Bit | 0-7 | 8-15 | 16-31 | /// | Bit | 0-7 | 8-15 | 16-31 |
/// +-----+------+------+-----------+ /// +-----+------+------+--------------+
/// | 0 | Type | Code | Checksum | /// | 0 | Type | Code | Checksum |
/// +-----+------+------+-----------+ /// +-----+------+------+--------------+
/// | 32 | unused | /// | 32 | unused | Next-Hop MTU |
/// +-----+-------------------------+ /// +-----+-------------+--------------+
/// | 64 | Internet Header | /// | 64 | Internet Header |
/// | | + 64 bits of | /// | | + 64 bits of |
/// | | Original Data Datagram | /// | | Original Data Datagram |
/// +-----+-------------------------+ /// +-----+----------------------------+
/// </pre> /// </pre>
/// </summary> /// </summary>
public class IcmpDestinationUnreachableDatagram : IcmpIpV4HeaderPlus64BitsPayloadDatagram public class IcmpDestinationUnreachableDatagram : IcmpIpV4HeaderPlus64BitsPayloadDatagram
{ {
private static class Offset
{
public const int NextHopMtu = 6;
}
public IcmpDestinationUnreachableDatagram(byte[] buffer, int offset, int length) public IcmpDestinationUnreachableDatagram(byte[] buffer, int offset, int length)
: base(buffer, offset, length) : base(buffer, offset, length)
{ {
} }
public ushort NextHopMtu
{
get { return ReadUShort(Offset.NextHopMtu, Endianity.Big); }
}
public override ILayer ExtractLayer() public override ILayer ExtractLayer()
{ {
return new IcmpDestinationUnreachableLayer return new IcmpDestinationUnreachableLayer
{ {
Code = (IcmpCodeDestinationUnrechable)Code, Code = (IcmpCodeDestinationUnrechable)Code,
Checksum = Checksum Checksum = Checksum,
NextHopMtu = NextHopMtu,
}; };
} }
protected override bool CalculateIsValid()
{
return base.CalculateIsValid() &&
(((IcmpCodeDestinationUnrechable)Code == IcmpCodeDestinationUnrechable.FragmentationNeededAndDontFragmentSet) ||
NextHopMtu == 0);
}
protected override byte MinCodeValue protected override byte MinCodeValue
{ {
get { return _minCode; } get { return _minCode; }
......
...@@ -13,5 +13,23 @@ namespace PcapDotNet.Packets.Icmp ...@@ -13,5 +13,23 @@ namespace PcapDotNet.Packets.Icmp
{ {
get { return (byte)Code; } get { return (byte)Code; }
} }
public ushort NextHopMtu { get; set; }
public bool Equals(IcmpDestinationUnreachableLayer other)
{
return other != null &&
NextHopMtu == other.NextHopMtu;
}
public override sealed bool Equals(IcmpLayer other)
{
return base.Equals(other) && Equals(other as IcmpDestinationUnreachableLayer);
}
protected override uint Value
{
get { return NextHopMtu; }
}
} }
} }
\ No newline at end of file
...@@ -22,7 +22,7 @@ namespace PcapDotNet.Packets.Icmp ...@@ -22,7 +22,7 @@ namespace PcapDotNet.Packets.Icmp
{ {
} }
protected IpV4Datagram IpV4 public IpV4Datagram IpV4
{ {
get get
{ {
......
...@@ -28,7 +28,7 @@ namespace PcapDotNet.Packets.Icmp ...@@ -28,7 +28,7 @@ namespace PcapDotNet.Packets.Icmp
/// Codes 2 and 3 may be received from a host. /// Codes 2 and 3 may be received from a host.
/// </para> /// </para>
/// </summary> /// </summary>
DestinationUnreachable = 3, DestinationUnreachable = 0x03,
/// <summary> /// <summary>
/// RFC 792. /// RFC 792.
......
...@@ -11,6 +11,11 @@ namespace PcapDotNet.Packets ...@@ -11,6 +11,11 @@ namespace PcapDotNet.Packets
return new PacketBuilder(layers).Build(timestamp); return new PacketBuilder(layers).Build(timestamp);
} }
public static Packet Build(DateTime timestamp, IEnumerable<ILayer> layers)
{
return new PacketBuilder(layers).Build(timestamp);
}
public PacketBuilder(params ILayer[] layers) public PacketBuilder(params ILayer[] layers)
{ {
if (layers.Length == 0) if (layers.Length == 0)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment