about summary refs log tree commit diff
diff options
context:
space:
mode:
authorBen Harris <ben@tilde.team>2020-04-20 17:24:59 -0400
committerBen Harris <ben@tilde.team>2020-04-20 17:24:59 -0400
commit6a3bfd314d87bed96611d24440b0961a06bf7e91 (patch)
tree5652d9ab48d850b7c9bd905b49b6d2709ae849c8
parent616abc70303990fbf8096fc6ada5fac100a6c66a (diff)
stateful in progress
-rw-r--r--IrcTokens/Hostmask.cs16
-rw-r--r--IrcTokens/Line.cs24
-rw-r--r--IrcTokens/StatefulDecoder.cs33
-rw-r--r--IrcTokens/StatefulEncoder.cs35
-rw-r--r--IrcTokens/Tests/StatefulDecoderTests.cs97
-rw-r--r--IrcTokens/Tests/StatefulEncoderTests.cs72
6 files changed, 266 insertions, 11 deletions
diff --git a/IrcTokens/Hostmask.cs b/IrcTokens/Hostmask.cs
index 05470ef..9f935b0 100644
--- a/IrcTokens/Hostmask.cs
+++ b/IrcTokens/Hostmask.cs
@@ -11,8 +11,18 @@
 
         public override string ToString() => _source;
 
+        public override int GetHashCode() => _source.GetHashCode();
+
+        public override bool Equals(object obj)
+        {
+            if (obj == null || GetType() != obj.GetType())
+                return false;
+
+            return _source == ((Hostmask) obj)._source;
+        }
+
         private readonly string _source;
-        
+
         public Hostmask(string source)
         {
             if (source == null) return;
@@ -30,7 +40,7 @@
             {
                 NickName = source;
             }
-            
+
             if (NickName.Contains('!'))
             {
                 var userSplit = NickName.Split('!');
@@ -39,4 +49,4 @@
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/IrcTokens/Line.cs b/IrcTokens/Line.cs
index 7592376..6198c04 100644
--- a/IrcTokens/Line.cs
+++ b/IrcTokens/Line.cs
@@ -15,10 +15,20 @@ namespace IrcTokens
         public List<string> Params { get; set; }
 
         private Hostmask _hostmask;
-        private string _rawLine;
+        private readonly string _rawLine;
 
-        public override string ToString() => 
-            $"Line(tags={string.Join(";", Tags.Select(kvp => $"{kvp.Key}={kvp.Value}"))}, params={string.Join(",", Params)})";
+        public override string ToString() =>
+            $"Line(source={Source}, command={Command}, tags={string.Join(";", Tags.Select(kvp => $"{kvp.Key}={kvp.Value}"))}, params={string.Join(",", Params)})";
+
+        public override int GetHashCode() => Format().GetHashCode();
+
+        public override bool Equals(object obj)
+        {
+            if (obj == null || GetType() != obj.GetType())
+                return false;
+
+            return Format() == ((Line) obj).Format();
+        }
 
         public Hostmask Hostmask =>
             _hostmask ??= new Hostmask(Source);
@@ -108,9 +118,7 @@ namespace IrcTokens
             }
 
             if (Source != null)
-            {
                 outs.Add($":{Source}");
-            }
 
             outs.Add(Command);
 
@@ -122,13 +130,13 @@ namespace IrcTokens
                 foreach (var p in Params)
                 {
                     if (p.Contains(' '))
-                        throw new ArgumentException("non-last parameters cannot have spaces");
+                        throw new ArgumentException("non-last parameters cannot have spaces", p);
                     if (p.StartsWith(':'))
-                        throw new ArgumentException("non-last parameters cannot start with colon");
+                        throw new ArgumentException("non-last parameters cannot start with colon", p);
                 }
                 outs.AddRange(Params);
 
-                if (last == null || string.IsNullOrWhiteSpace(last) || last.Contains(' ') || last.StartsWith(':'))
+                if (string.IsNullOrWhiteSpace(last) || last.Contains(' ') || last.StartsWith(':'))
                     last = $":{last}";
                 outs.Add(last);
             }
diff --git a/IrcTokens/StatefulDecoder.cs b/IrcTokens/StatefulDecoder.cs
new file mode 100644
index 0000000..65dd3de
--- /dev/null
+++ b/IrcTokens/StatefulDecoder.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace IrcTokens
+{
+    public class StatefulDecoder
+    {
+        private string _buffer;
+        public EncodingInfo Encoding { get; set; }
+        public EncodingInfo Fallback { get; set; }
+
+        public string Pending => _buffer;
+
+        public void Clear()
+        {
+            _buffer = "";
+        }
+
+        public List<Line> Push(string data)
+        {
+            if (string.IsNullOrEmpty(data))
+                return null;
+
+            _buffer += data;
+            return _buffer
+                .Split('\n')
+                .Select(l => l.TrimEnd('\r'))
+                .Select(l => new Line(l))
+                .ToList();
+        }
+    }
+}
diff --git a/IrcTokens/StatefulEncoder.cs b/IrcTokens/StatefulEncoder.cs
new file mode 100644
index 0000000..0c8b5f9
--- /dev/null
+++ b/IrcTokens/StatefulEncoder.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace IrcTokens
+{
+    public class StatefulEncoder
+    {
+        private string _buffer;
+        public EncodingInfo Encoding { get; set; }
+        private List<Line> _bufferedLines;
+
+        public string Pending => _buffer;
+
+        public void Clear()
+        {
+            _buffer = "";
+            _bufferedLines.Clear();
+        }
+
+        public void Push(Line line)
+        {
+            _buffer += $"{line.Format()}\r\n";
+            _bufferedLines.Add(line);
+        }
+
+        public List<Line> Pop(int byteCount)
+        {
+            var sent = _buffer.Substring(byteCount).Count(c => c == '\n');
+            _buffer = _buffer.Substring(byteCount);
+            _bufferedLines = _bufferedLines.Skip(sent).ToList();
+            return _bufferedLines.Take(sent).ToList();
+        }
+    }
+}
diff --git a/IrcTokens/Tests/StatefulDecoderTests.cs b/IrcTokens/Tests/StatefulDecoderTests.cs
new file mode 100644
index 0000000..e0c2143
--- /dev/null
+++ b/IrcTokens/Tests/StatefulDecoderTests.cs
@@ -0,0 +1,97 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IrcTokens.Tests
+{
+    [TestClass]
+    public class StatefulDecoderTests
+    {
+        private StatefulDecoder _decoder;
+
+        [TestInitialize]
+        public void TestInitialize()
+        {
+            _decoder = new StatefulDecoder();
+        }
+
+        [TestMethod]
+        public void TestPartial()
+        {
+            var lines = _decoder.Push("PRIVMSG ");
+            Assert.AreEqual(new List<string>(), lines);
+
+            lines = _decoder.Push("#channel hello\r\n");
+            Assert.AreEqual(1, lines.Count);
+
+            var line = new Line("PRIVMSG #channel hello");
+            CollectionAssert.AreEqual(new List<Line> {line}, lines);
+        }
+
+        [TestMethod]
+        public void TestMultiple()
+        {
+            _decoder.Push("PRIVMSG #channel1 hello\r\n");
+            var lines = _decoder.Push("PRIVMSG #channel2 hello\r\n");
+            Assert.AreEqual(2, lines.Count);
+
+            var line1 = new Line("PRIVMSG #channel1 hello");
+            var line2 = new Line("PRIVMSG #channel2 hello");
+            Assert.AreEqual(line1, lines[0]);
+            Assert.AreEqual(line2, lines[1]);
+        }
+
+        [TestMethod]
+        public void TestEncoding()
+        {
+            var iso8859 = Encoding.GetEncodings().Single(ei => ei.Name == "iso-8859-1");
+            _decoder = new StatefulDecoder {Encoding = iso8859};
+            var lines = _decoder.Push("PRIVMSG #channel :hello Č\r\n");
+            var line = new Line("PRIVMSG #channel :hello Č");
+            Assert.AreEqual(line, lines[0]);
+        }
+
+        [TestMethod]
+        public void TestEncodingFallback()
+        {
+            var latin1 = Encoding.GetEncodings().Single(ei => ei.Name == "latin-1");
+            _decoder = new StatefulDecoder {Fallback = latin1};
+            var lines = _decoder.Push("PRIVMSG #channel hélló\r\n");
+            Assert.AreEqual(1, lines.Count);
+            Assert.AreEqual(new Line("PRIVMSG #channel hélló"), lines[0]);
+        }
+
+        [TestMethod]
+        public void TestEmpty()
+        {
+            var lines = _decoder.Push(string.Empty);
+            Assert.IsNull(lines);
+        }
+
+        [TestMethod]
+        public void TestBufferUnfinished()
+        {
+            _decoder.Push("PRIVMSG #channel hello");
+            var lines = _decoder.Push(string.Empty);
+            Assert.IsNull(lines);
+        }
+
+        [TestMethod]
+        public void TestClear()
+        {
+            _decoder.Push("PRIVMSG ");
+            _decoder.Clear();
+            Assert.AreEqual(string.Empty, _decoder.Pending);
+        }
+
+        [TestMethod]
+        public void TestTagEncodingMismatch()
+        {
+            _decoder.Push("@asd=á ");
+            var lines = _decoder.Push("PRIVMSG #chan :á\r\n");
+            Assert.AreEqual("á", lines[0].Params[0]);
+            Assert.AreEqual("á", lines[0].Tags["asd"]);
+        }
+    }
+}
diff --git a/IrcTokens/Tests/StatefulEncoderTests.cs b/IrcTokens/Tests/StatefulEncoderTests.cs
new file mode 100644
index 0000000..4732573
--- /dev/null
+++ b/IrcTokens/Tests/StatefulEncoderTests.cs
@@ -0,0 +1,72 @@
+using System.Linq;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IrcTokens.Tests
+{
+    [TestClass]
+    public class StatefulEncoderTests
+    {
+        private StatefulEncoder _encoder;
+
+        [TestInitialize]
+        public void TestInitialize()
+        {
+            _encoder = new StatefulEncoder();
+        }
+
+        [TestMethod]
+        public void TestPush()
+        {
+            var line = new Line("PRIVMSG #channel hello");
+            _encoder.Push(line);
+            Assert.AreEqual("PRIVMSG #channel hello\r\n", _encoder.Pending);
+        }
+
+        [TestMethod]
+        public void TestPopPartial()
+        {
+            var line = new Line("PRIVMSG #channel hello");
+            _encoder.Push(line);
+            _encoder.Pop("PRIVMSG #channel hello".Length);
+            Assert.AreEqual("\r\n", _encoder.Pending);
+        }
+
+        [TestMethod]
+        public void TestPopReturned()
+        {
+            var line = new Line("PRIVMSG #channel hello");
+            _encoder.Push(line);
+            _encoder.Push(line);
+            var lines = _encoder.Pop("PRIVMSG #channel hello\r\n".Length);
+            Assert.AreEqual(1, lines.Count);
+            Assert.AreEqual(line, lines[0]);
+        }
+
+        [TestMethod]
+        public void TestPopNoneReturned()
+        {
+            var line = new Line("PRIVMSG #channel hello");
+            _encoder.Push(line);
+            var lines = _encoder.Pop(1);
+            Assert.AreEqual(0, lines.Count);
+        }
+
+        [TestMethod]
+        public void TestClear()
+        {
+            _encoder.Push(new Line("PRIVMSG #channel hello"));
+            _encoder.Clear();
+            Assert.AreEqual(string.Empty, _encoder.Pending);
+        }
+
+        [TestMethod]
+        public void TestEncoding()
+        {
+            var iso88592 = Encoding.GetEncodings().Single(ei => ei.Name == "iso-8859-2");
+            _encoder = new StatefulEncoder {Encoding = iso88592};
+            _encoder.Push(new Line("PRIVMSG #channel :hello Č"));
+            Assert.AreEqual("PRIVMSG #channel :hello Č\r\n", _encoder.Pending);
+        }
+    }
+}