Repository navigation
Expand file tree
/
Copy pathChatConnection.cs
More file actions
193 lines (167 loc) · 7.84 KB
/
Copy pathChatConnection.cs
File metadata and controls
193 lines (167 loc) · 7.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace TCPTunnel
{
internal enum ChatTransport { Tcp, Bluetooth, Local }
internal interface IChatConnection : IDisposable
{
Stream Stream { get; }
IPAddress RemoteIpAddress { get; }
ChatTransport Transport => ChatTransport.Tcp;
}
internal sealed class TcpChatConnection : IChatConnection
{
private int disposed;
internal TcpClient Client { get; }
public Stream Stream { get; }
public IPAddress RemoteIpAddress { get; }
public ChatTransport Transport => ChatTransport.Tcp;
internal TcpChatConnection(TcpClient client)
{
Client = client ?? throw new ArgumentNullException(nameof(client));
Client.NoDelay = true;
Stream = Client.GetStream();
RemoteIpAddress = (Client.Client.RemoteEndPoint as IPEndPoint)?.Address;
}
public void Dispose()
{
if (Interlocked.Exchange(ref disposed, 1) == 0) Client.Dispose();
}
}
internal sealed class LocalChatConnection : IChatConnection
{
private readonly MemoryDuplexStream stream;
public Stream Stream => stream;
public IPAddress RemoteIpAddress => null;
public ChatTransport Transport => ChatTransport.Local;
private LocalChatConnection(MemoryDuplexStream stream) => this.stream = stream;
internal static (LocalChatConnection HubSide, LocalChatConnection ClientSide) CreatePair()
{
var (hub, client) = MemoryDuplexStream.CreatePair();
return (new LocalChatConnection(hub), new LocalChatConnection(client));
}
public void Dispose() => stream.Dispose();
}
internal sealed class DuplexStream : Stream
{
private readonly Stream input;
private readonly Stream output;
private int disposed;
internal DuplexStream(Stream input, Stream output)
{
this.input = input ?? throw new ArgumentNullException(nameof(input));
this.output = output ?? throw new ArgumentNullException(nameof(output));
}
public override bool CanRead => true;
public override bool CanWrite => true;
public override bool CanSeek => false;
public override long Length => throw new NotSupportedException();
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
public override int Read(byte[] buffer, int offset, int count) => input.Read(buffer, offset, count);
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken token) =>
input.ReadAsync(buffer, offset, count, token);
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken token = default) =>
input.ReadAsync(buffer, token);
public override void Write(byte[] buffer, int offset, int count) => output.Write(buffer, offset, count);
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken token) =>
output.WriteAsync(buffer, offset, count, token);
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken token = default) =>
output.WriteAsync(buffer, token);
public override void Flush() => output.Flush();
public override Task FlushAsync(CancellationToken token) => output.FlushAsync(token);
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
protected override void Dispose(bool disposing)
{
if (disposing && Interlocked.Exchange(ref disposed, 1) == 0)
{
try { input.Dispose(); } catch { }
try { output.Dispose(); } catch { }
}
base.Dispose(disposing);
}
}
internal sealed class MemoryDuplexStream : Stream
{
private const int ChunkCapacity = 1024;
private readonly Channel<byte[]> incoming;
private readonly Channel<byte[]> outgoing;
private byte[] current;
private int currentOffset;
private MemoryDuplexStream(Channel<byte[]> incoming, Channel<byte[]> outgoing)
{
this.incoming = incoming;
this.outgoing = outgoing;
}
internal static (MemoryDuplexStream First, MemoryDuplexStream Second) CreatePair()
{
var options = new BoundedChannelOptions(ChunkCapacity) { SingleReader = true, FullMode = BoundedChannelFullMode.Wait };
Channel<byte[]> toFirst = Channel.CreateBounded<byte[]>(options);
Channel<byte[]> toSecond = Channel.CreateBounded<byte[]>(options);
return (new MemoryDuplexStream(toFirst, toSecond), new MemoryDuplexStream(toSecond, toFirst));
}
public override bool CanRead => true;
public override bool CanWrite => true;
public override bool CanSeek => false;
public override long Length => throw new NotSupportedException();
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken token = default)
{
if (buffer.Length == 0)
return 0;
while (current == null || currentOffset >= current.Length)
{
if (!await incoming.Reader.WaitToReadAsync(token).ConfigureAwait(false))
return 0;
if (incoming.Reader.TryRead(out byte[] next))
{
current = next;
currentOffset = 0;
}
}
int count = Math.Min(buffer.Length, current.Length - currentOffset);
current.AsMemory(currentOffset, count).CopyTo(buffer);
currentOffset += count;
return count;
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken token) =>
ReadAsync(buffer.AsMemory(offset, count), token).AsTask();
public override int Read(byte[] buffer, int offset, int count) =>
ReadAsync(buffer.AsMemory(offset, count)).AsTask().GetAwaiter().GetResult();
public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken token = default)
{
if (buffer.Length == 0)
return;
try
{
await outgoing.Writer.WriteAsync(buffer.ToArray(), token).ConfigureAwait(false);
}
catch (ChannelClosedException)
{
throw new IOException("The local connection is closed.");
}
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken token) =>
WriteAsync(buffer.AsMemory(offset, count), token).AsTask();
public override void Write(byte[] buffer, int offset, int count) =>
WriteAsync(buffer.AsMemory(offset, count)).AsTask().GetAwaiter().GetResult();
public override void Flush() { }
public override Task FlushAsync(CancellationToken token) => Task.CompletedTask;
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
protected override void Dispose(bool disposing)
{
if (disposing)
{
outgoing.Writer.TryComplete();
incoming.Writer.TryComplete();
}
base.Dispose(disposing);
}
}
}