Commit 3ce1f87b authored by Brickner_cp's avatar Brickner_cp

HTTP

parent cc0fd15f
using System;
using System.Collections.Generic;
namespace PcapDotNet.Base
......@@ -18,8 +19,27 @@ namespace PcapDotNet.Base
if (!dictionary2.TryGetValue(pair.Key, out otherValue))
return false;
if (!valueComparer.Equals(pair.Value, otherValue))
{
if (otherValue is string)
{
string otherString = otherValue as string;
string thisString = pair.Value as string;
for (int i = 0; i != otherString.Length; ++i)
{
if (!thisString[i].Equals(otherString[i]))
{
Console.WriteLine("a");
}
else
{
Console.WriteLine("b");
}
}
}
return false;
}
}
return true;
}
......
......@@ -98,6 +98,7 @@
<Compile Include="UInt24.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UInt48.cs" />
<Compile Include="UIntExtensions.cs" />
</ItemGroup>
<ItemGroup>
<None Include="..\PcapDotNet.snk" />
......
using System;
namespace PcapDotNet.Base
{
public static class UIntExtensions
{
public static int NumDigits(this uint value, double digitsBase)
{
return (int)(Math.Floor(Math.Log(value, digitsBase)) + 1);
}
}
}
\ No newline at end of file
......@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PcapDotNet.Base;
using PcapDotNet.Packets.Ethernet;
......@@ -46,6 +47,44 @@ namespace PcapDotNet.Packets.Test
//
#endregion
[TestMethod]
public void RandomHttpTest()
{
Random random = new Random(1);
for (int i = 0; i != 1000; ++i)
{
EthernetLayer ethernetLayer = random.NextEthernetLayer(EthernetType.None);
IpV4Layer ipV4Layer = random.NextIpV4Layer(null);
ipV4Layer.HeaderChecksum = null;
TcpLayer tcpLayer = random.NextTcpLayer();
tcpLayer.Checksum = null;
HttpLayer httpLayer = random.NextHttpLayer();
Packet packet = new PacketBuilder(ethernetLayer, ipV4Layer, tcpLayer, httpLayer).Build(DateTime.Now);
Assert.IsTrue(packet.IsValid, "IsValid");
HttpDatagram httpDatagram = packet.Ethernet.IpV4.Tcp.Http;
Assert.AreEqual(httpLayer.Version, httpDatagram.Version);
if (httpLayer is HttpRequestLayer)
{
Assert.IsTrue(httpDatagram.IsRequest);
Assert.IsFalse(httpDatagram.IsResponse);
HttpRequestLayer httpRequestLayer = (HttpRequestLayer)httpLayer;
HttpRequestDatagram httpRequestDatagram = (HttpRequestDatagram)httpDatagram;
Assert.AreEqual(httpRequestLayer.Method, httpRequestDatagram.Method);
Assert.AreEqual(httpRequestLayer.Uri, httpRequestDatagram.Uri);
}
Assert.AreEqual(httpLayer.Header, httpDatagram.Header);
Assert.AreEqual(httpLayer.Body, httpDatagram.Body);
Assert.AreEqual(httpLayer, httpDatagram.ExtractLayer(), "HTTP Layer");
Assert.AreEqual(httpLayer.Length, httpDatagram.Length);
// Ethernet
}
}
[TestMethod]
public void HttpParsingTest()
{
......
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using PcapDotNet.Base;
using PcapDotNet.Packets.Ethernet;
using PcapDotNet.Packets.Http;
using PcapDotNet.Packets.IpV4;
namespace PcapDotNet.Packets
......@@ -551,6 +553,11 @@ namespace PcapDotNet.Packets
offset += UInt48.SizeOf;
}
public static void Write(this byte[] buffer, ref int offset, string value, Encoding encoding)
{
buffer.Write(ref offset, encoding.GetBytes(value));
}
/// <summary>
/// Writes the given value to the buffer.
/// </summary>
......@@ -652,6 +659,23 @@ namespace PcapDotNet.Packets
buffer.Write(ref offset, value.MillisecondsSinceMidnightUniversalTime, endianity);
}
public static void WriteCarriageReturnLineFeed(this byte[] buffer, int offset)
{
buffer.Write(ref offset, AsciiBytes.CarriageReturn);
buffer.Write(offset, AsciiBytes.LineFeed);
}
public static void WriteCarriageReturnLineFeed(this byte[] buffer, ref int offset)
{
buffer.Write(ref offset, AsciiBytes.CarriageReturn);
buffer.Write(ref offset, AsciiBytes.LineFeed);
}
public static void WriteDecimal(this byte[] buffer, ref int offset, uint value)
{
buffer.Write(ref offset, value.ToString(), Encoding.ASCII);
}
private static bool IsWrongEndianity(Endianity endianity)
{
return (BitConverter.IsLittleEndian == (endianity == Endianity.Big));
......
......@@ -42,7 +42,14 @@ namespace PcapDotNet.Packets.Http
string fieldValueString = HttpRegex.GetString(fieldValue);
Match match = _regex.Match(fieldValueString);
if (!match.Success)
{
while (!match.Success && fieldValueString.Length > 0)
{
fieldValueString = fieldValueString.Substring(0, fieldValueString.Length - 1);
match = _regex.Match(fieldValueString);
}
return;
}
MediaType = match.Groups[MediaTypeGroupName].Captures.Cast<Capture>().First().Value;
MediaSubType = match.Groups[MediaSubTypeGroupName].Captures.Cast<Capture>().First().Value;
......@@ -51,7 +58,7 @@ namespace PcapDotNet.Packets.Http
}
private const string MediaTypeGroupName = "MediaType";
private const string MediaSubTypeGroupName = "MediaSubtType";
private const string MediaSubTypeGroupName = "MediaSubType";
private static readonly Regex _regex =
HttpRegex.MatchEntire(HttpRegex.Concat(HttpRegex.Capture(HttpRegex.Token, MediaTypeGroupName),
......
......@@ -198,6 +198,9 @@ namespace PcapDotNet.Packets.Http
/// </summary>
public abstract class HttpDatagram : Datagram
{
private const string FieldNameGroupName = "FieldNameGroupName";
private const string FieldValueGroupName = "FieldValueGroupName";
internal class ParseInfoBase
{
public int Length { get; set; }
......@@ -238,9 +241,10 @@ namespace PcapDotNet.Packets.Http
return Empty;
HttpTransferEncodingField transferEncodingField = header.TransferEncoding;
if (transferEncodingField != null)
if (transferEncodingField != null &&
transferEncodingField.TransferCodings != null &&
transferEncodingField.TransferCodings.Any(coding => coding != "identity"))
{
if (transferEncodingField.TransferCodings.Any(coding => coding != "identity"))
return ParseChunkedBody(buffer, offset, length);
}
......@@ -284,12 +288,13 @@ namespace PcapDotNet.Packets.Http
if (chunkSizeValue == 0)
{
int? endOffset;
HttpHeader trailerHeader = new HttpHeader(GetHeaderFields(out endOffset, buffer, parser.Offset, buffer.Length - parser.Offset));
parser.CarriageReturnLineFeed();
HttpHeader trailerHeader = new HttpHeader(GetHeaderFields(out endOffset, buffer, parser.Offset, offset + length - parser.Offset));
if (endOffset != null)
parser.Skip(endOffset.Value - parser.Offset);
break;
}
int actualChunkSize = (int)Math.Min(chunkSizeValue, buffer.Length - parser.Offset);
int actualChunkSize = (int)Math.Min(chunkSizeValue, offset + length - parser.Offset);
contentData.Add(new Datagram(buffer, parser.Offset, actualChunkSize));
parser.Skip(actualChunkSize);
parser.CarriageReturnLineFeed();
......@@ -330,51 +335,5 @@ namespace PcapDotNet.Packets.Http
}
private static readonly byte[] _httpSlash = Encoding.ASCII.GetBytes("HTTP/");
private bool _isParsedFirstLine;
private bool _isParsedHeader;
private int? _headerOffset;
private int? _bodyOffset;
private HttpVersion _version;
private HttpHeader _header;
private Datagram _body;
}
// internal static class IEnumerableExtensions
// {
// public static int CountLinearWhiteSpaces(this IEnumerable<byte> sequence)
// {
// int count = 0;
// while (true)
// {
// byte first = sequence.FirstOrDefault();
// if (first == AsciiBytes.CarriageReturn) // CR
// {
// IEnumerable<byte> skippedSequence = sequence.Skip(1);
// if (skippedSequence.FirstOrDefault() == AsciiBytes.LineFeed) // CRLF
// {
// skippedSequence = skippedSequence.Skip(1);
// if (skippedSequence.FirstOrDefault().IsSpaceOrHorizontalTab()) // CRLF ( SP | HT )
// {
// sequence = skippedSequence.Skip(1);
// count += 3;
// }
// else // CRLF without ( SP | HT )
// return count;
// }
// else // CR without LF
// return count;
// }
// else if (first.IsSpaceOrHorizontalTab()) // ( SP | HT )
// {
// ++count;
// sequence = sequence.Skip(1);
// }
// else // Doesn't start with ( CR | SP | HT )
// return count;
// }
// }
// }
}
\ No newline at end of file
......@@ -31,10 +31,46 @@ namespace PcapDotNet.Packets.Http
}
public HttpField(string name, string value, Encoding encoding)
: this(name, encoding.GetBytes(value))
: this(name, encoding.GetBytes(NormalizeValue(value)))
{
}
private static string NormalizeValue(string value)
{
StringBuilder stringBuilder = new StringBuilder(value.Length);
int offset = 0;
while (offset != value.Length)
{
if (value[offset] == '"')
{
int start = offset;
++offset;
while (offset != value.Length && value[offset] != '"')
{
if (value[offset] == '\\' && offset != value.Length - 1)
++offset;
++offset;
}
if (value[offset] == '"')
++offset;
stringBuilder.Append(value.Substring(start, offset - start));
}
else if (value[offset] == '\t' || value[offset] == ' ' || value[offset] == '\r' || value[offset] == '\n')
{
stringBuilder.Append(' ');
++offset;
while (offset != value.Length && (value[offset] == '\t' || value[offset] == ' ' || value[offset] == '\r' || value[offset] == '\n'))
++offset;
}
else
{
stringBuilder.Append(value[offset]);
++offset;
}
}
return stringBuilder.ToString();
}
public HttpField(string name, IEnumerable<byte> value)
: this(name, value.ToArray())
{
......@@ -81,6 +117,14 @@ namespace PcapDotNet.Packets.Http
}
}
public int Length
{
get
{
return Name.Length + 2 + Value.Count + 2;
}
}
public virtual bool Equals(HttpField other)
{
return other != null && Name.Equals(other.Name, StringComparison.InvariantCultureIgnoreCase) && Value.SequenceEqual(other.Value);
......@@ -96,6 +140,15 @@ namespace PcapDotNet.Packets.Http
return string.Format("{0}: {1}", Name, ValueString);
}
internal void Write(byte[] buffer, ref int offset)
{
buffer.Write(ref offset, Name, Encoding.ASCII);
buffer.Write(ref offset, AsciiBytes.Colon);
buffer.Write(ref offset, AsciiBytes.Space);
buffer.Write(ref offset, Value);
buffer.WriteCarriageReturnLineFeed(ref offset);
}
private static readonly Encoding _defaultEncoding = Encoding.GetEncoding(28591);
}
}
\ No newline at end of file
......@@ -71,7 +71,7 @@ namespace PcapDotNet.Packets.Http
return string.Empty;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(" ");
// stringBuilder.Append(" ");
foreach (var parameter in this)
{
stringBuilder.Append(";");
......
......@@ -20,6 +20,14 @@ namespace PcapDotNet.Packets.Http
{
}
public int BytesLength
{
get
{
return this.Sum(field => field.Length) + 2;
}
}
public HttpField this[string fieldName]
{
get { return GetField<HttpField>(fieldName); }
......@@ -94,6 +102,18 @@ namespace PcapDotNet.Packets.Http
_fields = mergedFields.ToDictionary(field => field.Key, field => HttpField.CreateField(field.Key, field.Value.ToArray()), StringComparer.InvariantCultureIgnoreCase);
}
public void Write(byte[] buffer, int offset)
{
Write(buffer, ref offset);
}
public void Write(byte[] buffer, ref int offset)
{
foreach (HttpField field in this)
field.Write(buffer, ref offset);
buffer.WriteCarriageReturnLineFeed(ref offset);
}
private T GetField<T>(string fieldName) where T : HttpField
{
HttpField field;
......
using System;
namespace PcapDotNet.Packets.Http
{
public abstract class HttpLayer : SimpleLayer, IEquatable<HttpLayer>
{
public HttpVersion Version { get; set; }
public HttpHeader Header { get; set; }
public Datagram Body { get; set; }
public override int Length
{
get
{
return FirstLineLength +
(Header == null ? 0 : Header.BytesLength) +
(Body == null ? 0 : Body.Length);
}
}
public override bool Equals(Layer other)
{
return Equals(other as HttpLayer);
}
public virtual bool Equals(HttpLayer other)
{
return other != null &&
(ReferenceEquals(Version, other.Version) || Version.Equals(other.Version)) &&
(ReferenceEquals(Header, other.Header) || Header.Equals(other.Header)) &&
(ReferenceEquals(Body, other.Body) || Body.Equals(other.Body));
}
protected override void Write(byte[] buffer, int offset)
{
WriteFirstLine(buffer, ref offset);
if (Header != null)
Header.Write(buffer, ref offset);
if (Body != null)
buffer.Write(offset, Body);
}
protected abstract int FirstLineLength { get; }
protected abstract void WriteFirstLine(byte[] buffer, ref int offset);
}
}
\ No newline at end of file
......@@ -103,8 +103,29 @@ namespace PcapDotNet.Packets.Http
public HttpParser FieldContent(out IEnumerable<byte> fieldContent)
{
fieldContent = Range.TakeWhile(value => !value.IsSpaceOrHorizontalTab() && value != AsciiBytes.CarriageReturn);
_offset += fieldContent.Count();
int originalOffset = Offset;
fieldContent = null;
while (Success)
{
fieldContent = new Datagram(_buffer, originalOffset, Offset - originalOffset);
if (IsNext(AsciiBytes.Space) || IsNext(AsciiBytes.CarriageReturn) || IsNext(AsciiBytes.HorizontalTab) || IsNext(AsciiBytes.LineFeed))
break;
if (IsNext(AsciiBytes.DoubleQuotationMark))
{
Datagram quotedString;
QuotedString(out quotedString);
}
else
{
var text = Range.TakeWhile(value => value > 0x20 && value != AsciiBytes.DoubleQuotationMark);
if (!text.Any())
return Fail();
_offset += text.Count();
}
}
return this;
}
......@@ -319,7 +340,7 @@ namespace PcapDotNet.Packets.Http
public HttpParser SkipChunkExtensions()
{
while (IsNext(AsciiBytes.Semicolon))
while (Success && IsNext(AsciiBytes.Semicolon))
{
Bytes(AsciiBytes.Semicolon);
......
......@@ -9,6 +9,11 @@ namespace PcapDotNet.Packets.Http
public const string ParameterNameGroupName = "ParameterName";
public const string ParameterValueGroupName = "ParameterValue";
public static Regex CarriageReturnLineFeed
{
get { return _carriageReturnLineFeed; }
}
public static Regex LinearWhiteSpace
{
get { return _linearWhiteSpaceRegex; }
......@@ -29,9 +34,19 @@ namespace PcapDotNet.Packets.Http
get { return _optionalParametersRegex; }
}
public static string GetString(byte[] buffer, int offset, int count)
{
return _encoding.GetString(buffer, offset, count);
}
public static string GetString(byte[] buffer)
{
return _encoding.GetString(buffer);
return GetString(buffer, 0, buffer.Length);
}
public static byte[] GetBytes(string pattern)
{
return _encoding.GetBytes(pattern);
}
public static Regex Build(string pattern)
......@@ -91,6 +106,11 @@ namespace PcapDotNet.Packets.Http
return Build(string.Format("(?<{0}>{1})", captureName, regex));
}
public static Regex MatchStart(Regex regex)
{
return Build(string.Format("^{0}", regex));
}
public static Regex MatchEntire(Regex regex)
{
return Build(string.Format("^{0}$", regex));
......@@ -101,10 +121,11 @@ namespace PcapDotNet.Packets.Http
return string.Format("(?:{0})", pattern);
}
private static readonly Regex _charRegex = Build(@"[\x00-\x127]");
private static readonly Regex _carriageReturnLineFeed = Build(@"\r\n");
private static readonly Regex _charRegex = Build(@"[\x00-\x7F]");
private static readonly Regex _quotedPairRegex = Concat(Build(@"\\"), _charRegex);
private static readonly Regex _linearWhiteSpaceRegex = Concat(Optional(Build(@"\r\n")), AtLeastOne(Build(@"[ \t]")));
private static readonly Regex _qdtextRegex = Or(_linearWhiteSpaceRegex, Build(@"[^\x00-\x31\x127\""]"));
private static readonly Regex _linearWhiteSpaceRegex = Concat(Optional(CarriageReturnLineFeed), AtLeastOne(Build(@"[ \t]")));
private static readonly Regex _qdtextRegex = Or(_linearWhiteSpaceRegex, Build(@"[^\x00-\x1F\x7F""]"));
private static readonly Regex _quotedStringRegex = Concat(Build('"'), Any(Or(_qdtextRegex, _quotedPairRegex)), Build('"'));
private static readonly Regex _tokenRegex = AtLeastOne(Build(@"[\x21\x23-\x27\x2A\x2B\x2D\x2E0-9A-Z\x5E-\x7A\x7C\x7E-\xFE]"));
private static readonly Regex _valueRegex = Or(Token, QuotedString);
......
......@@ -18,6 +18,18 @@ namespace PcapDotNet.Packets.Http
public string Method { get; private set; }
public string Uri { get; private set; }
public override ILayer ExtractLayer()
{
return new HttpRequestLayer
{
Version = Version,
Method = Method,
Uri = Uri,
Header = Header,
Body = Body,
};
}
internal HttpRequestDatagram(byte[] buffer, int offset, int length)
: this(buffer, offset, Parse(buffer, offset, length))
{
......
using System;
using System.Text;
namespace PcapDotNet.Packets.Http
{
public class HttpRequestLayer : HttpLayer, IEquatable<HttpRequestLayer>
{
public string Method { get; set; }
public string Uri { get; set; }
public override bool Equals(HttpLayer other)
{
return Equals(other as HttpRequestLayer);
}
public bool Equals(HttpRequestLayer other)
{
return base.Equals(other) &&
(ReferenceEquals(Method, other.Method) || Method.Equals(other.Method)) &&
(ReferenceEquals(Uri, other.Uri) || Uri.Equals(other.Uri));
}
protected override int FirstLineLength
{
get
{
int length = 0;
if (Method == null)
return length;
length += Method.Length + 1;
if (Uri == null)
return length;
length += Uri.Length + 1;
if (Version == null)
return length;
return length + Version.Length + 2;
}
}
protected override void WriteFirstLine(byte[] buffer, ref int offset)
{
if (Method == null)
return;
buffer.Write(ref offset, Method, Encoding.ASCII);
buffer.Write(ref offset, AsciiBytes.Space);
if (Uri == null)
return;
buffer.Write(ref offset, Uri, Encoding.ASCII);
buffer.Write(ref offset, AsciiBytes.Space);
if (Version == null)
return;
Version.Write(buffer, ref offset);
buffer.WriteCarriageReturnLineFeed(ref offset);
}
}
}
\ No newline at end of file
......@@ -20,6 +20,18 @@ namespace PcapDotNet.Packets.Http
public Datagram ReasonPhrase { get; private set;}
public override ILayer ExtractLayer()
{
return new HttpResponseLayer
{
Version = Version,
StatusCode = StatusCode,
ReasonPhrase = ReasonPhrase,
Header = Header,
Body = Body,
};
}
internal HttpResponseDatagram(byte[] buffer, int offset, int length)
: this(buffer, offset, Parse(buffer, offset, length))
{
......
using System;
using PcapDotNet.Base;
namespace PcapDotNet.Packets.Http
{
public class HttpResponseLayer : HttpLayer, IEquatable<HttpResponseLayer>
{
public uint? StatusCode { get; set; }
public Datagram ReasonPhrase { get; set; }
public override bool Equals(HttpLayer other)
{
return Equals(other as HttpResponseLayer);
}
public bool Equals(HttpResponseLayer other)
{
return base.Equals(other) &&
StatusCode == other.StatusCode &&
ReasonPhrase == other.ReasonPhrase;
}
protected override int FirstLineLength
{
get
{
int length = 0;
if (Version == null)
return length;
length += Version.Length + 1;
if (StatusCode == null)
return length;
length += StatusCode.Value.NumDigits(10) + 1;
if (ReasonPhrase == null)
return length;
return length + ReasonPhrase.Length + 2;
}
}
protected override void WriteFirstLine(byte[] buffer, ref int offset)
{
if (Version == null)
return;
Version.Write(buffer, ref offset);
buffer.Write(ref offset, AsciiBytes.Space);
if (StatusCode == null)
return;
buffer.WriteDecimal(ref offset, StatusCode.Value);
buffer.Write(ref offset, AsciiBytes.Space);
if (ReasonPhrase == null)
return;
buffer.Write(ref offset, ReasonPhrase);
buffer.WriteCarriageReturnLineFeed(ref offset);
}
}
}
\ No newline at end of file
......@@ -51,10 +51,22 @@ namespace PcapDotNet.Packets.Http
internal HttpTransferEncodingField(byte[] fieldValue)
: base(Name, fieldValue)
{
// string str = "\"h2ÇõX{âDv¼¯Ñ•)ËX?´ÈÔ\"";
// string str = "\"h2ÇõX{âDv¼¯Ñ)\"";
// Match tmpMatch = HttpRegex.QuotedString.Match(str);
// Console.WriteLine(tmpMatch.Success);
string fieldValueString = HttpRegex.GetString(fieldValue);
Match match = _regex.Match(fieldValueString);
if (!match.Success)
{
while (!match.Success && fieldValueString.Length > 0)
{
fieldValueString = fieldValueString.Substring(0, fieldValueString.Length - 1);
match = _regex.Match(fieldValueString);
}
return;
}
SetTransferCodings(match.GroupCapturesValues(RegexTransferCodingGroupName).ToArray());
}
......
using System;
using System.Text;
using PcapDotNet.Base;
namespace PcapDotNet.Packets.Http
{
......@@ -16,6 +18,11 @@ namespace PcapDotNet.Packets.Http
public uint Major { get; private set; }
public uint Minor { get; private set; }
public int Length
{
get { return _httpSlashBytes.Length + Major.NumDigits(10) + 1 + Minor.NumDigits(10); }
}
public override string ToString()
{
return string.Format("HTTP/{0}.{1}", Major, Minor);
......@@ -33,7 +40,16 @@ namespace PcapDotNet.Packets.Http
return Equals(obj as HttpVersion);
}
internal void Write(byte[] buffer, ref int offset)
{
buffer.Write(ref offset, _httpSlashBytes);
buffer.WriteDecimal(ref offset, Major);
buffer.Write(ref offset, AsciiBytes.Dot);
buffer.WriteDecimal(ref offset, Minor);
}
private static readonly HttpVersion _version10 = new HttpVersion(1,0);
private static readonly HttpVersion _version11 = new HttpVersion(1,1);
private static readonly byte[] _httpSlashBytes = Encoding.ASCII.GetBytes("HTTP/");
}
}
\ No newline at end of file
......@@ -117,10 +117,13 @@
<Compile Include="Http\HttpField.cs" />
<Compile Include="Http\HttpFieldParameters.cs" />
<Compile Include="Http\HttpHeader.cs" />
<Compile Include="Http\HttpLayer.cs" />
<Compile Include="Http\HttpParser.cs" />
<Compile Include="Http\HttpRegex.cs" />
<Compile Include="Http\HttpRequestDatagram.cs" />
<Compile Include="Http\HttpRequestLayer.cs" />
<Compile Include="Http\HttpResponseDatagram.cs" />
<Compile Include="Http\HttpResponseLayer.cs" />
<Compile Include="Http\HttpTransferEncodingField.cs" />
<Compile Include="Http\HttpVersion.cs" />
<Compile Include="Icmp\IcmpAddressMaskReplyDatagram.cs" />
......
......@@ -34,6 +34,11 @@ namespace PcapDotNet.TestUtils
return bytes;
}
public static char NextChar(this Random random, char minValue, char maxValue)
{
return (char)random.Next(minValue, maxValue);
}
public static ushort NextUShort(this Random random, int maxValue)
{
return (ushort)random.Next(maxValue);
......
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