From 6a0b8dc22fc86a5de37278231f3f8418afc9b836 Mon Sep 17 00:00:00 2001 From: Ben Harris Date: Tue, 28 Apr 2020 11:08:01 -0400 Subject: Fix stateful decoder i think --- IrcSharp.sln | 2 +- IrcTokens/Extensions.cs | 59 +++++++++++++++++++++++++++++++++ IrcTokens/Line.cs | 4 +-- IrcTokens/StatefulDecoder.cs | 37 ++------------------- Sample/Client.cs | 71 ---------------------------------------- Sample/Program.cs | 26 --------------- Sample/TokensSample.csproj | 12 ------- TokensSample/Client.cs | 71 ++++++++++++++++++++++++++++++++++++++++ TokensSample/Program.cs | 26 +++++++++++++++ TokensSample/TokensSample.csproj | 12 +++++++ 10 files changed, 174 insertions(+), 146 deletions(-) create mode 100644 IrcTokens/Extensions.cs delete mode 100644 Sample/Client.cs delete mode 100644 Sample/Program.cs delete mode 100644 Sample/TokensSample.csproj create mode 100644 TokensSample/Client.cs create mode 100644 TokensSample/Program.cs create mode 100644 TokensSample/TokensSample.csproj diff --git a/IrcSharp.sln b/IrcSharp.sln index 030a2af..0573020 100644 --- a/IrcSharp.sln +++ b/IrcSharp.sln @@ -5,7 +5,7 @@ VisualStudioVersion = 16.0.30011.22 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IrcTokens", "IrcTokens\IrcTokens.csproj", "{9E812F45-B2CD-42D2-8378-EBEBF8697905}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TokensSample", "Sample\TokensSample.csproj", "{A45DA39B-6B47-4713-8049-3B36E0235B67}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TokensSample", "TokensSample\TokensSample.csproj", "{A45DA39B-6B47-4713-8049-3B36E0235B67}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IrcStates", "IrcStates\IrcStates.csproj", "{233E3CB4-61F1-4368-9139-7E9F4A58ED2D}" EndProject diff --git a/IrcTokens/Extensions.cs b/IrcTokens/Extensions.cs new file mode 100644 index 0000000..2eee7dc --- /dev/null +++ b/IrcTokens/Extensions.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace IrcTokens +{ + public static class Extensions + { + public static IEnumerable Split(this byte[] bytes, byte separator) + { + if (bytes == null || bytes.Length == 0) return new List(); + + var newLineIndices = bytes.Select((b, i) => b == separator ? i : -1).Where(i => i != -1).ToArray(); + var lines = new byte[newLineIndices.Length + 1][]; + var currentIndex = 0; + var arrIndex = 0; + + for (var i = 0; i < newLineIndices.Length && currentIndex < bytes.Length; ++i) + { + var n = new byte[newLineIndices[i] - currentIndex]; + Array.Copy(bytes, currentIndex, n, 0, newLineIndices[i] - currentIndex); + currentIndex = newLineIndices[i] + 1; + lines[arrIndex++] = n; + } + + // Handle the last string at the end of the array if there is one. + if (currentIndex < bytes.Length) + lines[arrIndex] = bytes.Skip(currentIndex).ToArray(); + else if (arrIndex == newLineIndices.Length) + // We had a separator character at the end of a string. Rather than just allowing + // a null character, we'll replace the last element in the array with an empty string. + lines[arrIndex] = Array.Empty(); + + return lines.ToArray(); + } + + public static byte[] Trim(this IEnumerable bytes, byte separator) + { + if (bytes == null || !bytes.Any()) return Array.Empty(); + var byteList = new List(bytes); + var i = 0; + + while (byteList[i] == separator) + { + byteList.RemoveAt(i); + i++; + } + + i = byteList.Count - 1; + while (byteList[i] == separator) + { + byteList.RemoveAt(i); + i--; + } + + return byteList.ToArray(); + } + } +} diff --git a/IrcTokens/Line.cs b/IrcTokens/Line.cs index f574e4e..d1a8286 100644 --- a/IrcTokens/Line.cs +++ b/IrcTokens/Line.cs @@ -104,7 +104,7 @@ namespace IrcTokens /// /// escaped string /// unescaped string - public static string UnescapeTag(string val) + private static string UnescapeTag(string val) { var unescaped = new StringBuilder(); @@ -141,7 +141,7 @@ namespace IrcTokens /// /// string to escape /// escaped string - public static string EscapeTag(string val) + private static string EscapeTag(string val) { for (var i = 0; i < TagUnescaped.Length; ++i) val = val?.Replace(TagUnescaped[i], TagEscaped[i], StringComparison.Ordinal); diff --git a/IrcTokens/StatefulDecoder.cs b/IrcTokens/StatefulDecoder.cs index 2db32f2..62d1703 100644 --- a/IrcTokens/StatefulDecoder.cs +++ b/IrcTokens/StatefulDecoder.cs @@ -56,41 +56,10 @@ namespace IrcTokens { if (data == null || data.Length == 0) return null; - _buffer = _buffer.Concat(data).ToArray(); + _buffer = _buffer == null ? Array.Empty() : _buffer.Concat(data).ToArray(); - // simulate string.Split('\n') before decoding - var newLineIndices = _buffer.Select((b, i) => b == '\n' ? i : -1).Where(i => i != -1).ToArray(); - var lines = new List(); - - for (int i = 0, currentIndex = 0; i < newLineIndices.Length; ++i) - { - var n = new byte[newLineIndices[i] - currentIndex]; - Array.Copy(_buffer, currentIndex, n, 0, newLineIndices[i] - currentIndex); - currentIndex = newLineIndices[i] + 1; - lines.Add(n); - } - - var listLines = lines.Select(l => l.ToList()).ToList(); - - // simulate string.Trim('\r') before decoding - foreach (var line in listLines) - { - var i = 0; - while (line[i] == '\r') - { - line.RemoveAt(i); - i++; - } - - i = line.Count - 1; - while (line[i] == '\r') - { - line.RemoveAt(i); - i--; - } - } - - _buffer = listLines[^1].ToArray(); + var listLines = _buffer.Split((byte) '\n').Select(l => l.Trim((byte) '\r')).ToList(); + _buffer = listLines.Last(); var decodeLines = new List(); foreach (var line in listLines.SkipLast(1).Select(l => l.ToArray())) diff --git a/Sample/Client.cs b/Sample/Client.cs deleted file mode 100644 index 933cab7..0000000 --- a/Sample/Client.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Sockets; -using IrcTokens; - -namespace TokensSample -{ - public class Client - { - private readonly byte[] _bytes; - private readonly StatefulDecoder _decoder; - private readonly StatefulEncoder _encoder; - private readonly Socket _socket; - - public Client() - { - _decoder = new StatefulDecoder(); - _encoder = new StatefulEncoder(); - _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP); - _bytes = new byte[1024]; - } - - public void Start() - { - _socket.Connect("127.0.0.1", 6667); - - Send(new Line {Command = "NICK", Params = new List {"tokensbot"}}); - Send(new Line {Command = "USER", Params = new List {"tokensbot", "0", "*", "real name"}}); - - while (true) - { - var bytesReceived = _socket.Receive(_bytes); - - if (bytesReceived == 0) - { - Console.WriteLine("! disconnected"); - _socket.Shutdown(SocketShutdown.Both); - break; - } - - var lines = _decoder.Push(_bytes); - - foreach (var line in lines) - { - Console.WriteLine($"< {line.Format()}"); - - switch (line.Command) - { - case "PING": - Send(new Line {Command = "PONG", Params = line.Params}); - break; - case "001": - Send(new Line {Command = "JOIN", Params = new List {"#test"}}); - break; - case "PRIVMSG": - Send(new Line {Command = "PRIVMSG", Params = new List {line.Params[0], "hello there"}}); - break; - } - } - } - } - - private void Send(Line line) - { - Console.WriteLine($"> {line.Format()}"); - _encoder.Push(line); - while (_encoder.PendingBytes.Length > 0) - _encoder.Pop(_socket.Send(_encoder.PendingBytes)); - } - } -} diff --git a/Sample/Program.cs b/Sample/Program.cs deleted file mode 100644 index c3a0885..0000000 --- a/Sample/Program.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using IrcTokens; - -namespace TokensSample -{ - public class Program - { - public static void Main(string[] args) - { - // tokenization - var line = new Line("@id=123 :ben!~ben@hostname PRIVMSG #channel :hello there!"); - Console.WriteLine(line); - Console.WriteLine(line.Format()); - - // formatting - var line2 = new Line {Command = "USER", Params = new List {"user", "0", "*", "real name"}}; - Console.WriteLine(line2); - Console.WriteLine(line2.Format()); - - // stateful example - var client = new Client(); - client.Start(); - } - } -} diff --git a/Sample/TokensSample.csproj b/Sample/TokensSample.csproj deleted file mode 100644 index 7c66734..0000000 --- a/Sample/TokensSample.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Exe - netcoreapp3.1 - - - - - - - diff --git a/TokensSample/Client.cs b/TokensSample/Client.cs new file mode 100644 index 0000000..933cab7 --- /dev/null +++ b/TokensSample/Client.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Net.Sockets; +using IrcTokens; + +namespace TokensSample +{ + public class Client + { + private readonly byte[] _bytes; + private readonly StatefulDecoder _decoder; + private readonly StatefulEncoder _encoder; + private readonly Socket _socket; + + public Client() + { + _decoder = new StatefulDecoder(); + _encoder = new StatefulEncoder(); + _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP); + _bytes = new byte[1024]; + } + + public void Start() + { + _socket.Connect("127.0.0.1", 6667); + + Send(new Line {Command = "NICK", Params = new List {"tokensbot"}}); + Send(new Line {Command = "USER", Params = new List {"tokensbot", "0", "*", "real name"}}); + + while (true) + { + var bytesReceived = _socket.Receive(_bytes); + + if (bytesReceived == 0) + { + Console.WriteLine("! disconnected"); + _socket.Shutdown(SocketShutdown.Both); + break; + } + + var lines = _decoder.Push(_bytes); + + foreach (var line in lines) + { + Console.WriteLine($"< {line.Format()}"); + + switch (line.Command) + { + case "PING": + Send(new Line {Command = "PONG", Params = line.Params}); + break; + case "001": + Send(new Line {Command = "JOIN", Params = new List {"#test"}}); + break; + case "PRIVMSG": + Send(new Line {Command = "PRIVMSG", Params = new List {line.Params[0], "hello there"}}); + break; + } + } + } + } + + private void Send(Line line) + { + Console.WriteLine($"> {line.Format()}"); + _encoder.Push(line); + while (_encoder.PendingBytes.Length > 0) + _encoder.Pop(_socket.Send(_encoder.PendingBytes)); + } + } +} diff --git a/TokensSample/Program.cs b/TokensSample/Program.cs new file mode 100644 index 0000000..c3a0885 --- /dev/null +++ b/TokensSample/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using IrcTokens; + +namespace TokensSample +{ + public class Program + { + public static void Main(string[] args) + { + // tokenization + var line = new Line("@id=123 :ben!~ben@hostname PRIVMSG #channel :hello there!"); + Console.WriteLine(line); + Console.WriteLine(line.Format()); + + // formatting + var line2 = new Line {Command = "USER", Params = new List {"user", "0", "*", "real name"}}; + Console.WriteLine(line2); + Console.WriteLine(line2.Format()); + + // stateful example + var client = new Client(); + client.Start(); + } + } +} diff --git a/TokensSample/TokensSample.csproj b/TokensSample/TokensSample.csproj new file mode 100644 index 0000000..7c66734 --- /dev/null +++ b/TokensSample/TokensSample.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp3.1 + + + + + + + -- cgit 1.4.1