#region MIT License
|
/**
|
* WsFrame.cs
|
*
|
* The MIT License
|
*
|
* Copyright (c) 2012 sta.blockhead
|
*
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* of this software and associated documentation files (the "Software"), to deal
|
* in the Software without restriction, including without limitation the rights
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
* copies of the Software, and to permit persons to whom the Software is
|
* furnished to do so, subject to the following conditions:
|
*
|
* The above copyright notice and this permission notice shall be included in
|
* all copies or substantial portions of the Software.
|
*
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* THE SOFTWARE.
|
*/
|
#endregion
|
|
using System;
|
using System.IO;
|
using System.Collections;
|
using System.Collections.Generic;
|
using System.Text;
|
|
namespace WebSocketSharp.Frame
|
{
|
public class WsFrame : IEnumerable<byte>
|
{
|
#region Field
|
|
private const int _readBufferLen = 1024;
|
|
#endregion
|
|
#region Private Constructor
|
|
private WsFrame()
|
{
|
Rsv1 = Rsv.OFF;
|
Rsv2 = Rsv.OFF;
|
Rsv3 = Rsv.OFF;
|
ExtPayloadLen = new byte[] { };
|
MaskingKey = new byte[] { };
|
}
|
|
#endregion
|
|
#region Public Constructors
|
|
public WsFrame(Opcode opcode, PayloadData payloadData)
|
: this(Fin.FINAL, opcode, payloadData)
|
{
|
}
|
|
public WsFrame(Fin fin, Opcode opcode, PayloadData payloadData)
|
: this(fin, opcode, Mask.MASK, payloadData)
|
{
|
}
|
|
public WsFrame(Fin fin, Opcode opcode, Mask mask, PayloadData payloadData)
|
: this()
|
{
|
Fin = fin;
|
Opcode = opcode;
|
//Masked = payloadData.Length != 0 ? mask : Mask.UNMASK;
|
//client->server always mask whatever having payload
|
//https://github.com/wsky/websocket-sharp/issues/2
|
Masked = mask;
|
PayloadData = payloadData;
|
|
init();
|
}
|
|
#endregion
|
|
#region Properties
|
|
public Fin Fin { get; private set; }
|
public Rsv Rsv1 { get; private set; }
|
public Rsv Rsv2 { get; private set; }
|
public Rsv Rsv3 { get; private set; }
|
public Opcode Opcode { get; private set; }
|
public Mask Masked { get; private set; }
|
public byte PayloadLen { get; private set; }
|
public byte[] ExtPayloadLen { get; private set; }
|
public byte[] MaskingKey { get; private set; }
|
public PayloadData PayloadData { get; private set; }
|
|
public ulong Length
|
{
|
get
|
{
|
return 2 + (ulong)(ExtPayloadLen.Length + MaskingKey.Length) + PayloadLength;
|
}
|
}
|
|
public ulong PayloadLength
|
{
|
get
|
{
|
return PayloadData.Length;
|
}
|
}
|
|
#endregion
|
|
#region Private Methods
|
|
IEnumerator IEnumerable.GetEnumerator()
|
{
|
return GetEnumerator();
|
}
|
|
private void init()
|
{
|
setPayloadLen(PayloadLength);
|
if (Masked == Mask.MASK)
|
maskPayloadData();
|
}
|
|
private void maskPayloadData()
|
{
|
var key = new byte[4];
|
var rand = new Random();
|
rand.NextBytes(key);
|
|
MaskingKey = key;
|
PayloadData.Mask(key);
|
}
|
|
private static WsFrame parse(Stream stream, bool unmask)
|
{
|
return parse(Ext.ReadBytes(stream, 2), stream, unmask);
|
}
|
|
private static WsFrame parse(byte[] header, Stream stream, bool unmask)
|
{
|
if (header == null || header.Length != 2)
|
return null;
|
|
try
|
{
|
var frame = readHeader(header);
|
readExtPayloadLen(stream, frame);
|
readMaskingKey(stream, frame);
|
readPayloadData(stream, frame, unmask);
|
|
return frame;
|
}
|
catch
|
{
|
return null;
|
}
|
}
|
|
private static void readExtPayloadLen(Stream stream, WsFrame frame)
|
{
|
var length = frame.PayloadLen <= 125
|
? 0
|
: frame.PayloadLen == 126
|
? 2
|
: 8;
|
|
if (length == 0)
|
return;
|
|
var extLen = Ext.ReadBytes(stream, length);
|
if (extLen.Length != length)
|
throw new IOException();
|
|
frame.ExtPayloadLen = extLen;
|
}
|
|
private static WsFrame readHeader(byte[] header)
|
{
|
// FIN
|
Fin fin = (header[0] & 0x80) == 0x80 ? Fin.FINAL : Fin.MORE;
|
// RSV1
|
Rsv rsv1 = (header[0] & 0x40) == 0x40 ? Rsv.ON : Rsv.OFF;
|
// RSV2
|
Rsv rsv2 = (header[0] & 0x20) == 0x20 ? Rsv.ON : Rsv.OFF;
|
// RSV3
|
Rsv rsv3 = (header[0] & 0x10) == 0x10 ? Rsv.ON : Rsv.OFF;
|
// Opcode
|
Opcode opcode = (Opcode)(header[0] & 0x0f);
|
// MASK
|
Mask masked = (header[1] & 0x80) == 0x80 ? Mask.MASK : Mask.UNMASK;
|
// Payload len
|
byte payloadLen = (byte)(header[1] & 0x7f);
|
|
return new WsFrame
|
{
|
Fin = fin,
|
Rsv1 = rsv1,
|
Rsv2 = rsv2,
|
Rsv3 = rsv3,
|
Opcode = opcode,
|
Masked = masked,
|
PayloadLen = payloadLen
|
};
|
}
|
|
private static void readMaskingKey(Stream stream, WsFrame frame)
|
{
|
if (frame.Masked == Mask.UNMASK)
|
return;
|
|
var maskingKey = Ext.ReadBytes(stream, 4);
|
if (maskingKey.Length != 4)
|
throw new IOException();
|
|
frame.MaskingKey = maskingKey;
|
}
|
|
private static void readPayloadData(Stream stream, WsFrame frame, bool unmask)
|
{
|
ulong length = frame.PayloadLen <= 125
|
? frame.PayloadLen
|
: frame.PayloadLen == 126
|
? Ext.To<ushort>(frame.ExtPayloadLen, ByteOrder.BIG)
|
: Ext.To<ushort>(frame.ExtPayloadLen, ByteOrder.BIG);
|
|
if (length == 0)
|
{
|
frame.PayloadData = new PayloadData(new byte[] { });
|
return;
|
}
|
|
if (frame.PayloadLen > 126 && length > PayloadData.MaxLength)
|
throw new WsReceivedTooBigMessageException();
|
|
var buffer = Ext.ReadBytes(stream, (int)length);
|
//var buffer = length <= (ulong)_readBufferLen
|
// ? Ext.ReadBytes(stream, (int)length)
|
// : Ext.ReadBytes(stream, (long)length, _readBufferLen);
|
|
if (buffer.LongLength != (long)length)
|
throw new IOException();
|
|
var payloadData = frame.Masked == Mask.MASK
|
? new PayloadData(buffer, true)
|
: new PayloadData(buffer);
|
|
if (frame.Masked == Mask.MASK && unmask)
|
{
|
payloadData.Mask(frame.MaskingKey);
|
frame.Masked = Mask.UNMASK;
|
frame.MaskingKey = new byte[] { };
|
}
|
|
frame.PayloadData = payloadData;
|
}
|
|
private void setPayloadLen(ulong length)
|
{
|
if (length < 126)
|
{
|
PayloadLen = (byte)length;
|
return;
|
}
|
|
if (length < 0x010000)
|
{
|
PayloadLen = (byte)126;
|
ExtPayloadLen = Ext.ToBytes((ushort)length, ByteOrder.BIG);
|
return;
|
}
|
|
PayloadLen = (byte)127;
|
ExtPayloadLen = Ext.ToBytes(length, ByteOrder.BIG);
|
}
|
|
#endregion
|
|
#region Public Methods
|
|
public IEnumerator<byte> GetEnumerator()
|
{
|
foreach (byte b in ToBytes())
|
yield return b;
|
}
|
|
public static WsFrame Parse(byte[] src)
|
{
|
return Parse(src, true);
|
}
|
|
public static WsFrame Parse(byte[] src, bool unmask)
|
{
|
using (MemoryStream ms = new MemoryStream(src))
|
{
|
return Parse(ms, unmask);
|
}
|
}
|
|
public static WsFrame Parse(Stream stream)
|
{
|
return Parse(stream, true);
|
}
|
|
public static WsFrame Parse(Stream stream, bool unmask)
|
{
|
return parse(stream, unmask);
|
}
|
|
public static void ParseAsync(Stream stream, Action<WsFrame> completed)
|
{
|
ParseAsync(stream, true, completed);
|
}
|
|
public static void ParseAsync(Stream stream, bool unmask, Action<WsFrame> completed)
|
{
|
var headerLen = 2;
|
var header = new byte[headerLen];
|
|
AsyncCallback callback = (ar) =>
|
{
|
WsFrame frame = null;
|
try
|
{
|
var readLen = stream.EndRead(ar);
|
frame = readLen == 2
|
? parse(header, stream, unmask)
|
: null;
|
}
|
catch
|
{
|
frame = null;
|
}
|
finally
|
{
|
if (completed != null)
|
completed(frame);
|
}
|
};
|
|
stream.BeginRead(header, 0, headerLen, callback, null);
|
}
|
|
public void Print()
|
{
|
byte[] buffer;
|
long count, i, j;
|
int countDigit, remainder;
|
string countFmt, extPayloadLen, headerFmt, topLineFmt, bottomLineFmt, payloadData, spFmt;
|
|
switch (ExtPayloadLen.Length)
|
{
|
case 2:
|
extPayloadLen = Ext.To<ushort>(ExtPayloadLen, ByteOrder.BIG).ToString();
|
break;
|
case 8:
|
extPayloadLen = Ext.To<ulong>(ExtPayloadLen, ByteOrder.BIG).ToString();
|
break;
|
default:
|
extPayloadLen = String.Empty;
|
break;
|
}
|
|
if (((Opcode.TEXT | Opcode.PING | Opcode.PONG) & Opcode) == Opcode &&
|
Masked == Mask.UNMASK &&
|
PayloadLength > 0)
|
{
|
payloadData = Encoding.UTF8.GetString(PayloadData.ToBytes());
|
}
|
else
|
{
|
payloadData = BitConverter.ToString(PayloadData.ToBytes());
|
}
|
|
headerFmt = @"
|
WsFrame:
|
|
FIN={0}, RSV1={1}, RSV2={2}, RSV3={3}, Opcode={4},
|
MASK={5}, Payload Len={6}, Extended Payload Len={7},
|
Masking Key ={8},
|
Payload Data={9}";
|
|
buffer = ToBytes();
|
count = (long)(Length / 4);
|
remainder = (int)(Length % 4);
|
|
if (count < 10000)
|
{
|
countDigit = 4;
|
countFmt = "{0,4}";
|
}
|
else if (count < 0x010000)
|
{
|
countDigit = 4;
|
countFmt = "{0,4:X}";
|
}
|
else if (count < 0x0100000000)
|
{
|
countDigit = 8;
|
countFmt = "{0,8:X}";
|
}
|
else
|
{
|
countDigit = 16;
|
countFmt = "{0,16:X}";
|
}
|
|
spFmt = String.Format("{{0,{0}}}", countDigit);
|
|
topLineFmt = String.Format(@"
|
{0} 01234567 89ABCDEF 01234567 89ABCDEF
|
{0}+--------+--------+--------+--------+", spFmt);
|
|
Func<string, Action<string, string, string, string>> func = s =>
|
{
|
long lineCount = 0;
|
string lineFmt = String.Format(" {0}|{{1,8}} {{2,8}} {{3,8}} {{4,8}}|", s);
|
return (arg1, arg2, arg3, arg4) =>
|
{
|
Console.WriteLine(lineFmt, ++lineCount, arg1, arg2, arg3, arg4);
|
};
|
};
|
var printLine = func(countFmt);
|
|
bottomLineFmt = String.Format(" {0}+--------+--------+--------+--------+", spFmt);
|
|
Console.WriteLine(headerFmt,
|
Fin, Rsv1, Rsv2, Rsv3, Opcode,
|
Masked, PayloadLen, extPayloadLen,
|
BitConverter.ToString(MaskingKey),
|
payloadData);
|
|
Console.WriteLine(topLineFmt, String.Empty);
|
|
for (i = 0; i <= count; i++)
|
{
|
j = i * 4;
|
if (i < count)
|
{
|
printLine(
|
Convert.ToString(buffer[j], 2).PadLeft(8, '0'),
|
Convert.ToString(buffer[j + 1], 2).PadLeft(8, '0'),
|
Convert.ToString(buffer[j + 2], 2).PadLeft(8, '0'),
|
Convert.ToString(buffer[j + 3], 2).PadLeft(8, '0'));
|
}
|
else if (i == count && remainder > 0)
|
{
|
printLine(
|
Convert.ToString(buffer[j], 2).PadLeft(8, '0'),
|
remainder >= 2 ? Convert.ToString(buffer[j + 1], 2).PadLeft(8, '0') : String.Empty,
|
remainder == 3 ? Convert.ToString(buffer[j + 2], 2).PadLeft(8, '0') : String.Empty,
|
String.Empty);
|
}
|
}
|
|
Console.WriteLine(bottomLineFmt, String.Empty);
|
}
|
|
public byte[] ToBytes()
|
{
|
var buffer = new List<byte>();
|
|
var header = (int)Fin;
|
header = (header << 1) + (int)Rsv1;
|
header = (header << 1) + (int)Rsv2;
|
header = (header << 1) + (int)Rsv3;
|
header = (header << 4) + (int)Opcode;
|
header = (header << 1) + (int)Masked;
|
header = (header << 7) + (int)PayloadLen;
|
buffer.AddRange(Ext.ToBytes((ushort)header, ByteOrder.BIG));
|
|
if (PayloadLen >= 126)
|
buffer.AddRange(ExtPayloadLen);
|
|
if (Masked == Mask.MASK)
|
buffer.AddRange(MaskingKey);
|
|
if (PayloadLen > 0)
|
buffer.AddRange(PayloadData.ToBytes());
|
|
return buffer.ToArray();
|
}
|
|
public override string ToString()
|
{
|
return BitConverter.ToString(ToBytes());
|
}
|
|
#endregion
|
}
|
}
|