using System.Buffers;
using NewLife.Buffers;
using NewLife.Data;
namespace NewLife.Messaging;
/// <summary>WebSocket消æ¯ç±»åž‹</summary>
public enum WebSocketMessageType
{
/// <summary>é™„åŠ æ•°æ®</summary>
Data = 0,
/// <summary>文本数æ®</summary>
Text = 1,
/// <summary>二进制数æ®</summary>
Binary = 2,
/// <summary>连接关é—</summary>
Close = 8,
/// <summary>心跳</summary>
Ping = 9,
/// <summary>心跳å“应</summary>
Pong = 10,
}
/// <summary>WebSocket 消æ¯</summary>
/// <remarks>
/// <para>承载 WebSocket 帧åè®®å—æ®µï¼ˆFIN/opcode/掩ç é”®ï¼‰ï¼Œä½“çš„è¯»å†™ç» <see cref="Message"/> å作é¢ï¼ˆæ•´å¸§ä¸ºå†…å˜è§†å›¾ã€å¤´å…ˆè¡Œä¸ºæµå¼ï¼‰ã€‚</para>
/// <para><b>掩ç </b>:æœåŠ¡ç«¯â†’å®¢æˆ·ç«¯æ–¹å‘ä¸åŠ æŽ©ç ;客户端→æœåŠ¡ç«¯æ–¹å‘必须掩ç ——å‘逿—¶æœªæŒ‡å®š <see cref="MaskKey"/> 则自动生æˆéšæœºå¯†é’¥ï¼ˆå¸§å†…ä¸´æ—¶ï¼Œä¸å†™å›žæ¶ˆæ¯ï¼‰ï¼›æŽ¥æ”¶ä¾§ä¿å˜å®¢æˆ·ç«¯å¸§çš„æŽ©ç 键,由消费方解ç (å‚考 <see cref="WebSocketCodec"/> 的说明)。</para>
/// </remarks>
public class WsMessage : Message
{
#region 属性
/// <summary>æ¶ˆæ¯æ˜¯å¦ç»“æŸï¼ˆFINï¼‰ã€‚æŽ¥æ”¶ä¾§åæ˜ 帧的 FIN ä½ï¼›å‘é€è·¯å¾„当剿’ç½® 1ï¼ˆä¸æ”¯æŒå‘é€åˆ†ç‰‡ï¼‰</summary>
public Boolean Fin { get; set; }
/// <summary>消æ¯ç±»åž‹ï¼ˆopcode)</summary>
public WebSocketMessageType Type { get; set; } = WebSocketMessageType.Binary;
/// <summary>掩ç 键。接收侧为客户端帧的掩ç 键(解ç 用);å‘é€ä¾§æœåŠ¡ç«¯å¿½ç•¥ã€å®¢æˆ·ç«¯æœªæŒ‡å®šæ—¶è‡ªåŠ¨ç”Ÿæˆ</summary>
public Byte[]? MaskKey { get; set; }
/// <summary>å…³é—状æ€ã€‚ä»… Close å¸§ç» <see cref="TryReadCloseStatus"/> è§£æžåŽæœ‰æ•ˆ</summary>
public Int32 CloseStatus { get; set; }
/// <summary>å…³é—çŠ¶æ€æè¿°ã€‚ä»… Close å¸§ç» <see cref="TryReadCloseStatus"/> è§£æžåŽæœ‰æ•ˆ</summary>
public String? StatusDescription { get; set; }
#endregion
#region 方法
/// <summary>è§£æž WebSocket 帧头并填充当å‰å®žä¾‹ã€‚æ•°æ®ä¸è¶³è¿”回 false,失败路径ä¸äº§ç”Ÿå‰¯ä½œç”¨</summary>
/// <remarks>叧嗿®µçš„读写属于消æ¯ç±»ï¼›æŽ©ç è§£ç 由消费方(<see cref="Demask"/>)执行。</remarks>
/// <param name="buffer">帧首窗å£ï¼ˆåªè¯»åºåˆ—,å¯è·¨æ®µï¼‰</param>
/// <param name="bodyLength">è§£æžåˆ°çš„负载长度</param>
/// <param name="headerSize">帧头å—èŠ‚æ•°ï¼ˆå«æŽ©ç 键)</param>
/// <returns>是å¦è§£æžæˆåŠŸ</returns>
public Boolean TryParse(ReadOnlySequence<Byte> buffer, out Int64 bodyLength, out Int32 headerSize) => TryParse(buffer, out bodyLength, out headerSize, out _);
/// <summary>è§£æž WebSocket 帧头并填充当å‰å®žä¾‹</summary>
/// <remarks>
/// <para>叧嗿®µçš„读写属于消æ¯ç±»ï¼›æŽ©ç è§£ç 由消费方(<see cref="Demask"/>)执行。</para>
/// <para>返回 false 时用 <paramref name="invalid"/> 区分“数æ®ä¸è¶³â€ä¸Žâ€œå¸§æŸåâ€ï¼šå‰è€…ç‰å¾…更多数æ®ï¼ŒåŽè€…å¿…é¡»ç«‹å³æŠ¥é”™ï¼Œ
/// å¦åˆ™è¿žæŽ¥ä¼šä¸€ç›´ç‰å¾…一个永远ä¸ä¼šå®Œæ•´çš„帧。</para>
/// </remarks>
/// <param name="buffer">帧首窗å£ï¼ˆåªè¯»åºåˆ—,å¯è·¨æ®µï¼‰</param>
/// <param name="bodyLength">è§£æžåˆ°çš„负载长度</param>
/// <param name="headerSize">帧头å—èŠ‚æ•°ï¼ˆå«æŽ©ç 键)</param>
/// <param name="invalid">是å¦ä¸ºæŸåå¸§ï¼ˆé•¿åº¦éžæ³•)</param>
/// <returns>是å¦è§£æžæˆåŠŸ</returns>
public Boolean TryParse(ReadOnlySequence<Byte> buffer, out Int64 bodyLength, out Int32 headerSize, out Boolean invalid)
{
invalid = false;
bodyLength = 0;
headerSize = 0;
if (buffer.Length < 2) return false;
var reader = new SequenceReader<Byte>(buffer);
if (!reader.TryRead(out var b0) || !reader.TryRead(out var b1)) return false;
// 第1å—节:FIN(1) RSV1-3(3) OPCODE(4);第2å—节:MASK(1) + 长度(7)
// RFC 6455 §5.2:未å商扩展时 RSV1-3 必须为 0ï¼›ä¿ç•™ opcode(3~7 / 11~15)必须导致连接失败
if ((b0 & 0x70) != 0)
{
invalid = true;
return false;
}
var fin = (b0 & 0x80) != 0;
var opcode = (Byte)(b0 & 0x0F);
if (opcode is not (0 or 1 or 2 or 8 or 9 or 10))
{
invalid = true;
return false;
}
var masked = (b1 & 0x80) != 0;
var len = (Int64)(b1 & 0x7F);
// 扩展长度:126 → 2 å—节;127 → 8 å—节(å‡ç½‘络åºï¼‰
var fieldLen = 2;
if (len == 126)
{
if (reader.Remaining < 2) return false;
if (!reader.TryReadBigEndian(out UInt16 len16)) return false;
len = len16;
fieldLen += 2;
}
else if (len == 127)
{
if (reader.Remaining < 8) return false;
if (!reader.TryReadBigEndian(out Int64 len64)) return false;
len = len64;
fieldLen += 8;
}
// 长度最高ä½ä¸º 1(负数):æŸå帧,必须与“数æ®ä¸è¶³â€åŒºåˆ†å¼€
if (len < 0)
{
invalid = true;
return false;
}
// 掩ç 键(4 å—节),éšå¤´éƒ¨ä¸€å¹¶æ¶ˆè´¹
Byte[]? masks = null;
if (masked)
{
if (reader.Remaining < 4) return false;
masks = new Byte[4];
for (var i = 0; i < 4; i++)
{
if (!reader.TryRead(out masks[i])) return false;
}
fieldLen += 4;
}
// å…¨éƒ¨æ ¡éªŒé€šè¿‡åŽæ‰å†™å…¥å®žä¾‹å—段,失败路径ä¸äº§ç”Ÿå‰¯ä½œç”¨
Fin = fin;
Type = (WebSocketMessageType)opcode;
MaskKey = masks;
bodyLength = len;
headerSize = fieldLen;
return true;
}
/// <summary>写入 WebSocket 帧头(FIN/OPCODE/长度/[掩ç é”®],å‡ä¸ºå¤§ç«¯ï¼‰</summary>
/// <param name="header">å¤´éƒ¨ç›®æ ‡è·¨åº¦ï¼ˆè‡³å°‘æ‰€éœ€å—节)</param>
/// <param name="bodyLength">负载长度</param>
/// <param name="maskKey">掩ç 键(4 å—节时写入并置掩ç ä½ï¼›null 䏿ީç )</param>
/// <returns>帧头å—节数</returns>
public Int32 WriteHeader(Span<Byte> header, Int64 bodyLength, Byte[]? maskKey)
{
var masked = maskKey != null && maskKey.Length >= 4;
var size = bodyLength switch
{
< 126 => 1 + 1,
<= 0xFFFF => 1 + 1 + 2,
_ => 1 + 1 + 8,
};
if (masked) size += 4;
var writer = new SpanWriter(header) { IsLittleEndian = false };
// FIN + OPCODE(当å‰å®žçŽ°ä»…æ”¯æŒå•帧消æ¯ï¼Œæ’ FIN=1)
writer.WriteByte((Byte)(0x80 | (Byte)Type));
// 长度:< 126 å•å—节;≤ 0xFFFF 用 2 å—节;å¦åˆ™ç”¨ 8 å—节(å‡ç½‘络åºï¼‰
WriteLength(ref writer, masked ? (Byte)0x80 : (Byte)0, bodyLength);
if (masked) writer.Write(maskKey!);
return size;
}
/// <summary>写入负载长度(网络åºå¤§ç«¯ï¼‰ã€‚<paramref name="maskBit"/> 为 0x80 时置掩ç ä½</summary>
/// <param name="writer">ç›®æ ‡å†™å…¥å™¨ï¼ˆé¡»ä¸ºå¤§ç«¯ï¼›<see cref="SpanWriter"/> 为å¯å˜ ref structï¼Œå¿…é¡»æŒ‰å¼•ç”¨ä¼ é€’ï¼Œå¦åˆ™ä½ç½®æŽ¨è¿›ä¸¢å¤±ï¼‰</param>
/// <param name="maskBit">掩ç ä½ï¼ˆ0 或 0x80)</param>
/// <param name="length">负载长度</param>
private static void WriteLength(ref SpanWriter writer, Byte maskBit, Int64 length)
{
if (length < 126)
{
writer.WriteByte((Byte)((Byte)length | maskBit));
}
else if (length <= 0xFFFF)
{
writer.WriteByte((Byte)(126 | maskBit));
writer.Write((Int16)length);
}
else
{
writer.WriteByte((Byte)(127 | maskBit));
writer.Write(length);
}
}
/// <summary>对消æ¯è´Ÿè½½æŒ‰æŽ©ç 键解ç (原地 XOR,链å¼è´Ÿè½½è·¨æ®µè¿žç»ï¼‰</summary>
/// <remarks>
/// <para><b>整帧(内å˜ä½“)路径</b>:对负载原地解ç (帧内å—èŠ‚ç‹¬äº«ï¼Œå±žç ´åæ€§æ“ä½œï¼‰ï¼›æœªæŒæŽ©ç é”®æˆ–è´Ÿè½½ä¸ºç©ºæ—¶æ— æ“作。</para>
/// <para><b>æµå¼ä½“</b>:本方法ä¸å¤„ç†ï¼ˆæ•°æ®æœªåˆ°é½æ—¶é€çª—è§£ç æ— æ³•ä¿æŒå移连ç»ï¼‰ï¼Œè¯·å…ˆè¯»æ»¡ç‰©åŒ–(<c>ReadAllAsync</c> åŽ <c>SetBody</c>)å†è§£ç 。</para>
/// <para>æœåŠ¡ç«¯æ–¹å‘æŽ¥æ”¶å®¢æˆ·ç«¯å¸§æ—¶è°ƒç”¨ï¼›æœåŠ¡ç«¯è‡ªèº«å‘é€ä¸Žå®¢æˆ·ç«¯æŽ¥æ”¶å‡æ— 掩ç ï¼Œæ— éœ€è°ƒç”¨ã€‚</para>
/// </remarks>
/// <returns>æ˜¯å¦æ‰§è¡Œäº†è§£ç </returns>
public Boolean Demask()
{
var masks = MaskKey;
var body = Payload;
if (masks == null || masks.Length < 4 || body == null || body.Total == 0) return false;
var offset = 0;
for (var node = body; node != null; node = node.Next)
{
var data = node.GetSpan();
for (var i = 0; i < data.Length; i++)
{
data[i] = (Byte)(data[i] ^ masks[(Int32)((offset + i) & 3)]);
}
offset += data.Length;
}
return true;
}
/// <summary>从消æ¯ä½“读å–å…³é—状æ€ç 与æè¿°ï¼ˆRFC 6455:2 å—节网络åºçжæ€ç + UTF8 åŽŸå› ï¼Œå¯ä¸ºç©ºï¼‰</summary>
/// <returns>是å¦è§£æžæˆåŠŸï¼ˆä½“ä¸ºå†…å˜æ¨¡å¼ä¸”至少 2 å—节)</returns>
public Boolean TryReadCloseStatus()
{
var body = Payload;
if (body == null || body.Total < 2) return false;
CloseStatus = (body[0] << 8) | body[1];
StatusDescription = body.Total > 2 ? body.ToStr(null, 2) : null;
return true;
}
#endregion
}
|