tangled
alpha
login
or
join now
benharri.org
/
ircsharp
1
fork
atom
IRC parsing, tokenization, and state handling in C#
1
fork
atom
overview
issues
pulls
pipelines
Fix example
benharri.org
5 years ago
8403fd8d
6a0b8dc2
+50
-29
7 changed files
expand all
collapse all
unified
split
IrcStates
Server.cs
IrcTokens
Extensions.cs
StatefulDecoder.cs
StatefulEncoder.cs
Tests
StatefulDecoder.cs
README.md
TokensSample
Client.cs
+1
-1
IrcStates/Server.cs
···
57
57
58
58
public List<(Line, Emit)> Recv(byte[] data)
59
59
{
60
60
-
var lines = _decoder.Push(data);
60
60
+
var lines = _decoder.Push(data, data.Length);
61
61
if (lines == null) throw new ServerDisconnectedException();
62
62
63
63
var emits = lines.Select(ParseTokens).ToList();
+4
-1
IrcTokens/Extensions.cs
···
36
36
37
37
public static byte[] Trim(this IEnumerable<byte> bytes, byte separator)
38
38
{
39
39
-
if (bytes == null || !bytes.Any()) return Array.Empty<byte>();
39
39
+
if (bytes == null) return Array.Empty<byte>();
40
40
+
40
41
var byteList = new List<byte>(bytes);
41
42
var i = 0;
43
43
+
44
44
+
if (!byteList.Any()) return byteList.ToArray();
42
45
43
46
while (byteList[i] == separator)
44
47
{
+5
-4
IrcTokens/StatefulDecoder.cs
···
49
49
50
50
public List<Line> Push(string data)
51
51
{
52
52
-
return Push(Encoding.GetBytes(data));
52
52
+
var bytes = Encoding.GetBytes(data);
53
53
+
return Push(bytes, bytes.Length);
53
54
}
54
55
55
55
-
public List<Line> Push(byte[] data)
56
56
+
public List<Line> Push(byte[] data, int bytesReceived)
56
57
{
57
57
-
if (data == null || data.Length == 0) return null;
58
58
+
if (data == null) return null;
58
59
59
59
-
_buffer = _buffer == null ? Array.Empty<byte>() : _buffer.Concat(data).ToArray();
60
60
+
_buffer = _buffer == null ? Array.Empty<byte>() : _buffer.Concat(data.Take(bytesReceived)).ToArray();
60
61
61
62
var listLines = _buffer.Split((byte) '\n').Select(l => l.Trim((byte) '\r')).ToList();
62
63
_buffer = listLines.Last();
+7
-7
IrcTokens/StatefulEncoder.cs
···
7
7
{
8
8
public class StatefulEncoder
9
9
{
10
10
-
private Queue<Line> _bufferedLines;
10
10
+
private List<Line> _bufferedLines;
11
11
private Encoding _encoding;
12
12
13
13
public StatefulEncoder()
···
45
45
public void Clear()
46
46
{
47
47
PendingBytes = Array.Empty<byte>();
48
48
-
_bufferedLines = new Queue<Line>();
48
48
+
_bufferedLines = new List<Line>();
49
49
}
50
50
51
51
public void Push(Line line)
···
53
53
if (line == null) throw new ArgumentNullException(nameof(line));
54
54
55
55
PendingBytes = PendingBytes.Concat(Encoding.GetBytes($"{line.Format()}\r\n")).ToArray();
56
56
-
_bufferedLines.Enqueue(line);
56
56
+
_bufferedLines.Add(line);
57
57
}
58
58
59
59
public List<Line> Pop(int byteCount)
···
61
61
var sent = PendingBytes.Take(byteCount).Count(c => c == '\n');
62
62
63
63
PendingBytes = PendingBytes.Skip(byteCount).ToArray();
64
64
-
_bufferedLines = new Queue<Line>(_bufferedLines.Take(sent));
64
64
+
65
65
+
var sentLines = _bufferedLines.Take(sent).ToList();
66
66
+
_bufferedLines = _bufferedLines.Skip(sent).ToList();
65
67
66
66
-
return Enumerable.Range(0, sent)
67
67
-
.Select(_ => _bufferedLines.Dequeue())
68
68
-
.ToList();
68
68
+
return sentLines;
69
69
}
70
70
}
71
71
}
+4
-2
IrcTokens/Tests/StatefulDecoder.cs
···
45
45
{
46
46
var iso8859 = Encoding.GetEncoding("iso-8859-1");
47
47
_decoder = new IrcTokens.StatefulDecoder {Encoding = iso8859};
48
48
-
var lines = _decoder.Push(iso8859.GetBytes("PRIVMSG #channel :hello Ç\r\n"));
48
48
+
var bytes = iso8859.GetBytes("PRIVMSG #channel :hello Ç\r\n");
49
49
+
var lines = _decoder.Push(bytes, bytes.Length);
49
50
var line = new Line("PRIVMSG #channel :hello Ç");
50
51
Assert.IsTrue(line.Equals(lines[0]));
51
52
}
···
55
56
{
56
57
var latin1 = Encoding.GetEncoding("iso-8859-1");
57
58
_decoder = new IrcTokens.StatefulDecoder {Encoding = null, Fallback = latin1};
58
58
-
var lines = _decoder.Push(latin1.GetBytes("PRIVMSG #channel hélló\r\n"));
59
59
+
var bytes = latin1.GetBytes("PRIVMSG #channel hélló\r\n");
60
60
+
var lines = _decoder.Push(bytes, bytes.Length);
59
61
Assert.AreEqual(1, lines.Count);
60
62
Assert.IsTrue(new Line("PRIVMSG #channel hélló").Equals(lines[0]));
61
63
}
+19
-10
README.md
···
29
29
30
30
public class Client
31
31
{
32
32
-
private readonly Socket _socket;
32
32
+
private readonly byte[] _bytes;
33
33
private readonly StatefulDecoder _decoder;
34
34
private readonly StatefulEncoder _encoder;
35
35
-
private readonly byte[] _bytes;
35
35
+
private readonly Socket _socket;
36
36
37
37
public Client()
38
38
{
39
39
_decoder = new StatefulDecoder();
40
40
_encoder = new StatefulEncoder();
41
41
-
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
42
42
-
_bytes = new byte[1024];
41
41
+
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
42
42
+
_bytes = new byte[1024];
43
43
}
44
44
45
45
public void Start()
46
46
{
47
47
_socket.Connect("127.0.0.1", 6667);
48
48
+
while (!_socket.Connected) Thread.Sleep(1000);
48
49
49
49
-
Send(new Line {Command = "USER", Params = new List<string> {"username", "0", "*", "real name"}});
50
50
-
Send(new Line {Command = "NICK", Params = new List<string> {"statefulbot"}});
50
50
+
Send(new Line {Command = "NICK", Params = new List<string> {"tokensbot"}});
51
51
+
Send(new Line {Command = "USER", Params = new List<string> {"tokensbot", "0", "*", "real name"}});
51
52
52
53
while (true)
53
54
{
54
55
var bytesReceived = _socket.Receive(_bytes);
55
55
-
var lines = _decoder.Push(_bytes);
56
56
57
57
-
if (lines.Count == 0)
57
57
+
if (bytesReceived == 0)
58
58
{
59
59
Console.WriteLine("! disconnected");
60
60
_socket.Shutdown(SocketShutdown.Both);
61
61
+
_socket.Close();
61
62
break;
62
63
}
64
64
+
65
65
+
var lines = _decoder.Push(_bytes, bytesReceived);
63
66
64
67
foreach (var line in lines)
65
68
{
···
71
74
Send(new Line {Command = "PONG", Params = line.Params});
72
75
break;
73
76
case "001":
74
74
-
Send(new Line {Command = "JOIN", Params = new List<string> {"#channel"}});
77
77
+
Send(new Line {Command = "JOIN", Params = new List<string> {"#test"}});
78
78
+
break;
79
79
+
case "PRIVMSG":
80
80
+
Send(new Line
81
81
+
{
82
82
+
Command = "PRIVMSG", Params = new List<string> {line.Params[0], "hello there"}
83
83
+
});
75
84
break;
76
85
}
77
86
}
···
83
92
Console.WriteLine($"> {line.Format()}");
84
93
_encoder.Push(line);
85
94
while (_encoder.PendingBytes.Length > 0)
86
86
-
_encoder.Pop(_socket.Send(_encoder.PendingBytes));
95
95
+
_encoder.Pop(_socket.Send(_encoder.PendingBytes, SocketFlags.None));
87
96
}
88
97
}
89
98
+10
-4
TokensSample/Client.cs
···
1
1
using System;
2
2
using System.Collections.Generic;
3
3
using System.Net.Sockets;
4
4
+
using System.Threading;
4
5
using IrcTokens;
5
6
6
7
namespace TokensSample
···
23
24
public void Start()
24
25
{
25
26
_socket.Connect("127.0.0.1", 6667);
27
27
+
while (!_socket.Connected) Thread.Sleep(1000);
26
28
27
29
Send(new Line {Command = "NICK", Params = new List<string> {"tokensbot"}});
28
30
Send(new Line {Command = "USER", Params = new List<string> {"tokensbot", "0", "*", "real name"}});
···
35
37
{
36
38
Console.WriteLine("! disconnected");
37
39
_socket.Shutdown(SocketShutdown.Both);
40
40
+
_socket.Close();
38
41
break;
39
42
}
40
43
41
41
-
var lines = _decoder.Push(_bytes);
42
42
-
44
44
+
var lines = _decoder.Push(_bytes, bytesReceived);
45
45
+
43
46
foreach (var line in lines)
44
47
{
45
48
Console.WriteLine($"< {line.Format()}");
···
53
56
Send(new Line {Command = "JOIN", Params = new List<string> {"#test"}});
54
57
break;
55
58
case "PRIVMSG":
56
56
-
Send(new Line {Command = "PRIVMSG", Params = new List<string> {line.Params[0], "hello there"}});
59
59
+
Send(new Line
60
60
+
{
61
61
+
Command = "PRIVMSG", Params = new List<string> {line.Params[0], "hello there"}
62
62
+
});
57
63
break;
58
64
}
59
65
}
···
65
71
Console.WriteLine($"> {line.Format()}");
66
72
_encoder.Push(line);
67
73
while (_encoder.PendingBytes.Length > 0)
68
68
-
_encoder.Pop(_socket.Send(_encoder.PendingBytes));
74
74
+
_encoder.Pop(_socket.Send(_encoder.PendingBytes, SocketFlags.None));
69
75
}
70
76
}
71
77
}