using System; using System.Diagnostics; using System.IO; using System.Threading; namespace Lskj.AgentBridge { /// /// Length-prefixed bridge framing with one total deadline for the header /// and body. A stalled asynchronous operation closes the owning stream so /// a local client cannot retain a server worker indefinitely. /// internal static class DeadlineFrameCodec { internal const int MaximumMessageBytes = 1024 * 1024; internal static byte[] ReadFrame(Stream stream, TimeSpan timeout) { if (stream == null) throw new ArgumentNullException("stream"); ValidateTimeout(timeout); Stopwatch stopwatch = Stopwatch.StartNew(); byte[] header = ReadExactly(stream, 4, timeout, stopwatch); int length = header[0] | (header[1] << 8) | (header[2] << 16) | (header[3] << 24); if (length <= 0 || length > MaximumMessageBytes) throw new InvalidDataException("请求长度无效或超过 1 MB 限制。"); return ReadExactly(stream, length, timeout, stopwatch); } internal static void WriteFrame(Stream stream, byte[] body, TimeSpan timeout) { if (stream == null) throw new ArgumentNullException("stream"); ValidateTimeout(timeout); if (body == null || body.Length == 0 || body.Length > MaximumMessageBytes) throw new InvalidDataException("响应长度无效或超过 1 MB 限制。"); Stopwatch stopwatch = Stopwatch.StartNew(); int length = body.Length; byte[] header = new[] { (byte)(length & 0xff), (byte)((length >> 8) & 0xff), (byte)((length >> 16) & 0xff), (byte)((length >> 24) & 0xff) }; WriteWithDeadline(stream, header, 0, header.Length, timeout, stopwatch); WriteWithDeadline(stream, body, 0, body.Length, timeout, stopwatch); stream.Flush(); } private static byte[] ReadExactly( Stream stream, int length, TimeSpan timeout, Stopwatch stopwatch) { byte[] buffer = new byte[length]; int offset = 0; while (offset < length) { int read = ReadWithDeadline( stream, buffer, offset, length - offset, timeout, stopwatch); if (read <= 0) throw new EndOfStreamException("客户端在完整消息到达前断开连接。"); offset += read; } return buffer; } private static int ReadWithDeadline( Stream stream, byte[] buffer, int offset, int count, TimeSpan timeout, Stopwatch stopwatch) { RemainingMilliseconds(timeout, stopwatch); IAsyncResult pending = stream.BeginRead(buffer, offset, count, null, null); Func complete = delegate { return stream.EndRead(pending); }; int remaining; try { remaining = RemainingMilliseconds(timeout, stopwatch); } catch (TimeoutException) { AbortPending(stream, pending, complete); throw; } return CompleteWithDeadline(stream, pending, remaining, complete); } private static void WriteWithDeadline( Stream stream, byte[] buffer, int offset, int count, TimeSpan timeout, Stopwatch stopwatch) { RemainingMilliseconds(timeout, stopwatch); IAsyncResult pending = stream.BeginWrite(buffer, offset, count, null, null); Func complete = delegate { stream.EndWrite(pending); return 0; }; int remaining; try { remaining = RemainingMilliseconds(timeout, stopwatch); } catch (TimeoutException) { AbortPending(stream, pending, complete); throw; } CompleteWithDeadline(stream, pending, remaining, complete); } private static int CompleteWithDeadline( Stream stream, IAsyncResult pending, int remaining, Func complete) { WaitHandle waitHandle = pending.AsyncWaitHandle; if (!waitHandle.WaitOne(remaining, false)) { AbortPending(stream, waitHandle, complete); throw new TimeoutException("命名管道帧传输超时。"); } try { return complete(); } finally { waitHandle.Close(); } } private static void AbortPending( Stream stream, IAsyncResult pending, Func complete) { AbortPending(stream, pending.AsyncWaitHandle, complete); } private static void AbortPending( Stream stream, WaitHandle waitHandle, Func complete) { try { stream.Dispose(); } finally { try { complete(); } catch { } waitHandle.Close(); } } private static int RemainingMilliseconds(TimeSpan timeout, Stopwatch stopwatch) { double value = (timeout - stopwatch.Elapsed).TotalMilliseconds; if (value <= 0) throw new TimeoutException("命名管道帧传输超时。"); return Math.Max(1, (int)Math.Min(int.MaxValue, Math.Ceiling(value))); } private static void ValidateTimeout(TimeSpan timeout) { if (timeout <= TimeSpan.Zero || timeout > TimeSpan.FromMinutes(1)) throw new ArgumentOutOfRangeException( "timeout", "命名管道帧超时必须在 1 分钟以内。"); } } }