XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

LumaTunnel

【WinUI】LumaTunnel 是一个面向个人多设备的 Windows 代理系统。客户端在本机提供 HTTP/HTTPS CONNECT 与 SOCKS5 TCP 代理,并通过一个受信任 TLS 证书保护的 WSS 会话,将多个 TCP 流复用到自建 Windows Server 节点。

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Threading.Channels;
using LumaTunnel.Client.Core.Abstractions;
using LumaTunnel.Shared.Protocol;

namespace LumaTunnel.Client.Core.Tunnel;

internal sealed class TunnelClientStream(TunnelService owner, ClientTunnelState state) : Stream, IHalfCloseable
{
    private readonly TunnelService _owner = owner;
    private readonly ClientTunnelState _state = state;
    private byte[]? _current;
    private int _currentOffset;
    private int _disposed;
    private int _writeClosed;

    public override bool CanRead => Volatile.Read(ref _disposed) == 0;
    public override bool CanSeek => false;
    public override bool CanWrite => Volatile.Read(ref _disposed) == 0 && Volatile.Read(ref _writeClosed) == 0;
    public override long Length => throw new NotSupportedException();
    public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
    public override void Flush() { }
    public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
    public override int Read(byte[] buffer, int offset, int count) => ReadAsync(buffer.AsMemory(offset, count)).AsTask().GetAwaiter().GetResult();

    public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
    {
        ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
        if (buffer.IsEmpty)
            return 0;

        while (_current is null || _currentOffset == _current.Length)
        {
            _current = null;
            _currentOffset = 0;
            try
            {
                if (!await _state.Incoming.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false))
                    return 0;
                if (!_state.Incoming.Reader.TryRead(out _current))
                    continue;
            }
            catch (ChannelClosedException exception) when (exception.InnerException is not null)
            {
                throw new IOException("The tunnel stream failed.", exception.InnerException);
            }
        }

        var count = Math.Min(buffer.Length, _current.Length - _currentOffset);
        _current.AsMemory(_currentOffset, count).CopyTo(buffer);
        _currentOffset += count;
        return count;
    }

    public override void Write(byte[] buffer, int offset, int count) => WriteAsync(buffer.AsMemory(offset, count)).AsTask().GetAwaiter().GetResult();

    public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
    {
        ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
        if (Volatile.Read(ref _writeClosed) != 0)
            throw new IOException("The tunnel stream write side is closed.");

        while (!buffer.IsEmpty)
        {
            var count = Math.Min(buffer.Length, TunnelProtocol.MaxPayloadLength);
            await _owner.SendDataAsync(_state.StreamId, buffer[..count], cancellationToken).ConfigureAwait(false);
            buffer = buffer[count..];
        }
    }

    public async ValueTask HalfCloseWriteAsync(CancellationToken cancellationToken = default)
    {
        if (Interlocked.Exchange(ref _writeClosed, 1) == 0 && Volatile.Read(ref _disposed) == 0)
            await _owner.SendHalfCloseAsync(_state.StreamId, cancellationToken).ConfigureAwait(false);
    }

    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)
            _owner.CloseStreamAsync(_state.StreamId).AsTask().GetAwaiter().GetResult();
        base.Dispose(disposing);
    }

    public override async ValueTask DisposeAsync()
    {
        if (Interlocked.Exchange(ref _disposed, 1) == 0)
            await _owner.CloseStreamAsync(_state.StreamId).ConfigureAwait(false);
        await base.DisposeAsync().ConfigureAwait(false);
        GC.SuppressFinalize(this);
    }
}