2013-04-08 14:11:57 +08:00
|
|
|
#region License
|
2013-01-11 19:32:38 +08:00
|
|
|
/*
|
2014-06-12 18:59:27 +08:00
|
|
|
* WebSocketFrame.cs
|
2012-07-31 09:36:52 +08:00
|
|
|
*
|
|
|
|
* The MIT License
|
|
|
|
*
|
2015-05-18 14:37:02 +08:00
|
|
|
* Copyright (c) 2012-2015 sta.blockhead
|
2014-06-11 15:46:07 +08:00
|
|
|
*
|
2012-07-31 09:36:52 +08:00
|
|
|
* 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.
|
2014-06-11 15:46:07 +08:00
|
|
|
*
|
2012-07-31 09:36:52 +08:00
|
|
|
* 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
|
|
|
|
|
2015-09-18 20:06:17 +08:00
|
|
|
#region Contributors
|
|
|
|
/*
|
|
|
|
* Contributors:
|
|
|
|
* - Chris Swiedler
|
|
|
|
*/
|
|
|
|
#endregion
|
|
|
|
|
2012-07-31 09:36:52 +08:00
|
|
|
using System;
|
2012-10-04 14:04:21 +08:00
|
|
|
using System.Collections;
|
2012-07-31 09:36:52 +08:00
|
|
|
using System.Collections.Generic;
|
2014-06-11 15:46:07 +08:00
|
|
|
using System.IO;
|
2012-07-31 09:36:52 +08:00
|
|
|
using System.Text;
|
|
|
|
|
2013-10-01 13:52:39 +08:00
|
|
|
namespace WebSocketSharp
|
|
|
|
{
|
2014-06-12 18:59:27 +08:00
|
|
|
internal class WebSocketFrame : IEnumerable<byte>
|
2012-07-31 09:36:52 +08:00
|
|
|
{
|
2014-06-11 15:46:07 +08:00
|
|
|
#region Private Fields
|
|
|
|
|
2015-05-20 16:47:41 +08:00
|
|
|
private byte[] _extPayloadLength;
|
|
|
|
private Fin _fin;
|
|
|
|
private Mask _mask;
|
|
|
|
private byte[] _maskingKey;
|
|
|
|
private Opcode _opcode;
|
|
|
|
private PayloadData _payloadData;
|
|
|
|
private byte _payloadLength;
|
|
|
|
private Rsv _rsv1;
|
|
|
|
private Rsv _rsv2;
|
|
|
|
private Rsv _rsv3;
|
2014-06-11 15:46:07 +08:00
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
2014-07-26 21:01:13 +08:00
|
|
|
#region Internal Fields
|
2013-10-10 15:38:41 +08:00
|
|
|
|
2014-09-24 13:37:11 +08:00
|
|
|
internal static readonly byte[] EmptyUnmaskPingBytes;
|
2013-10-10 15:38:41 +08:00
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Static Constructor
|
|
|
|
|
2014-06-12 18:59:27 +08:00
|
|
|
static WebSocketFrame ()
|
2013-10-10 15:38:41 +08:00
|
|
|
{
|
2014-09-24 13:37:11 +08:00
|
|
|
EmptyUnmaskPingBytes = CreatePingFrame (false).ToByteArray ();
|
2013-10-10 15:38:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
2013-04-08 14:11:57 +08:00
|
|
|
#region Private Constructors
|
2012-07-31 09:36:52 +08:00
|
|
|
|
2014-06-12 18:59:27 +08:00
|
|
|
private WebSocketFrame ()
|
2012-07-31 09:36:52 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
2014-07-26 21:01:13 +08:00
|
|
|
#region Internal Constructors
|
2012-07-31 09:36:52 +08:00
|
|
|
|
2014-09-24 13:37:11 +08:00
|
|
|
internal WebSocketFrame (Opcode opcode, PayloadData payloadData, bool mask)
|
|
|
|
: this (Fin.Final, opcode, payloadData, false, mask)
|
2012-07-31 09:36:52 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2014-09-24 13:37:11 +08:00
|
|
|
internal WebSocketFrame (Fin fin, Opcode opcode, byte[] data, bool compressed, bool mask)
|
|
|
|
: this (fin, opcode, new PayloadData (data), compressed, mask)
|
2013-04-23 17:08:42 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2014-07-26 21:01:13 +08:00
|
|
|
internal WebSocketFrame (
|
2014-09-24 13:37:11 +08:00
|
|
|
Fin fin, Opcode opcode, PayloadData payloadData, bool compressed, bool mask)
|
2012-07-31 09:36:52 +08:00
|
|
|
{
|
2014-06-11 15:46:07 +08:00
|
|
|
_fin = fin;
|
|
|
|
_rsv1 = isData (opcode) && compressed ? Rsv.On : Rsv.Off;
|
|
|
|
_rsv2 = Rsv.Off;
|
|
|
|
_rsv3 = Rsv.Off;
|
|
|
|
_opcode = opcode;
|
2013-05-17 22:02:11 +08:00
|
|
|
|
2014-09-24 13:37:11 +08:00
|
|
|
var len = payloadData.Length;
|
2014-06-12 18:59:27 +08:00
|
|
|
if (len < 126) {
|
|
|
|
_payloadLength = (byte) len;
|
2015-08-24 15:03:50 +08:00
|
|
|
_extPayloadLength = WebSocket.EmptyBytes;
|
2014-06-12 18:59:27 +08:00
|
|
|
}
|
|
|
|
else if (len < 0x010000) {
|
|
|
|
_payloadLength = (byte) 126;
|
2014-08-19 13:14:59 +08:00
|
|
|
_extPayloadLength = ((ushort) len).InternalToByteArray (ByteOrder.Big);
|
2014-06-12 18:59:27 +08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
_payloadLength = (byte) 127;
|
2014-08-19 13:14:59 +08:00
|
|
|
_extPayloadLength = len.InternalToByteArray (ByteOrder.Big);
|
2014-06-12 18:59:27 +08:00
|
|
|
}
|
2013-05-17 22:02:11 +08:00
|
|
|
|
2014-09-24 13:37:11 +08:00
|
|
|
if (mask) {
|
|
|
|
_mask = Mask.Mask;
|
2014-06-11 15:46:07 +08:00
|
|
|
_maskingKey = createMaskingKey ();
|
2014-09-24 13:37:11 +08:00
|
|
|
payloadData.Mask (_maskingKey);
|
2014-06-11 15:46:07 +08:00
|
|
|
}
|
|
|
|
else {
|
2014-09-24 13:37:11 +08:00
|
|
|
_mask = Mask.Unmask;
|
2015-08-24 15:03:50 +08:00
|
|
|
_maskingKey = WebSocket.EmptyBytes;
|
2014-06-11 15:46:07 +08:00
|
|
|
}
|
2013-05-17 22:02:11 +08:00
|
|
|
|
2014-09-24 13:37:11 +08:00
|
|
|
_payloadData = payloadData;
|
2012-07-31 09:36:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
#region Public Properties
|
2013-04-12 13:42:25 +08:00
|
|
|
|
2014-07-26 21:01:13 +08:00
|
|
|
public byte[] ExtendedPayloadLength {
|
2013-04-23 17:08:42 +08:00
|
|
|
get {
|
2014-06-11 15:46:07 +08:00
|
|
|
return _extPayloadLength;
|
2013-04-23 17:08:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public Fin Fin {
|
2013-04-23 17:08:42 +08:00
|
|
|
get {
|
2014-06-11 15:46:07 +08:00
|
|
|
return _fin;
|
2013-04-23 17:08:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public bool IsBinary {
|
2013-04-23 17:08:42 +08:00
|
|
|
get {
|
2014-06-11 15:46:07 +08:00
|
|
|
return _opcode == Opcode.Binary;
|
2013-04-23 17:08:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public bool IsClose {
|
2013-04-23 17:08:42 +08:00
|
|
|
get {
|
2014-06-11 15:46:07 +08:00
|
|
|
return _opcode == Opcode.Close;
|
2013-04-23 17:08:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public bool IsCompressed {
|
2013-04-12 13:42:25 +08:00
|
|
|
get {
|
2014-06-11 15:46:07 +08:00
|
|
|
return _rsv1 == Rsv.On;
|
2013-04-12 13:42:25 +08:00
|
|
|
}
|
|
|
|
}
|
2012-10-09 11:14:55 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public bool IsContinuation {
|
2013-04-12 13:42:25 +08:00
|
|
|
get {
|
2014-06-11 15:46:07 +08:00
|
|
|
return _opcode == Opcode.Cont;
|
2013-04-12 13:42:25 +08:00
|
|
|
}
|
|
|
|
}
|
2012-10-09 11:14:55 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public bool IsControl {
|
2013-03-25 14:17:31 +08:00
|
|
|
get {
|
2014-06-11 15:46:07 +08:00
|
|
|
return _opcode == Opcode.Close || _opcode == Opcode.Ping || _opcode == Opcode.Pong;
|
2013-03-25 14:17:31 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public bool IsData {
|
2013-04-23 17:08:42 +08:00
|
|
|
get {
|
2014-06-11 15:46:07 +08:00
|
|
|
return _opcode == Opcode.Binary || _opcode == Opcode.Text;
|
2013-04-23 17:08:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public bool IsFinal {
|
2012-11-13 15:38:48 +08:00
|
|
|
get {
|
2014-06-11 15:46:07 +08:00
|
|
|
return _fin == Fin.Final;
|
2013-05-19 00:16:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public bool IsFragmented {
|
2013-05-19 00:16:27 +08:00
|
|
|
get {
|
2014-06-11 15:46:07 +08:00
|
|
|
return _fin == Fin.More || _opcode == Opcode.Cont;
|
2012-10-09 11:14:55 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public bool IsMasked {
|
2013-04-23 17:08:42 +08:00
|
|
|
get {
|
2014-06-11 15:46:07 +08:00
|
|
|
return _mask == Mask.Mask;
|
2013-04-23 17:08:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public bool IsPerMessageCompressed {
|
2013-04-23 17:08:42 +08:00
|
|
|
get {
|
2014-06-11 15:46:07 +08:00
|
|
|
return (_opcode == Opcode.Binary || _opcode == Opcode.Text) && _rsv1 == Rsv.On;
|
2013-04-23 17:08:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public bool IsPing {
|
2013-04-23 17:08:42 +08:00
|
|
|
get {
|
2014-06-11 15:46:07 +08:00
|
|
|
return _opcode == Opcode.Ping;
|
2013-04-23 17:08:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public bool IsPong {
|
2012-11-13 15:38:48 +08:00
|
|
|
get {
|
2014-06-11 15:46:07 +08:00
|
|
|
return _opcode == Opcode.Pong;
|
2012-10-09 11:14:55 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public bool IsText {
|
|
|
|
get {
|
|
|
|
return _opcode == Opcode.Text;
|
|
|
|
}
|
|
|
|
}
|
2013-04-12 13:42:25 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public ulong Length {
|
|
|
|
get {
|
|
|
|
return 2 + (ulong) (_extPayloadLength.Length + _maskingKey.Length) + _payloadData.Length;
|
|
|
|
}
|
|
|
|
}
|
2013-04-12 13:42:25 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public Mask Mask {
|
|
|
|
get {
|
|
|
|
return _mask;
|
|
|
|
}
|
|
|
|
}
|
2013-04-12 13:42:25 +08:00
|
|
|
|
2014-07-26 21:01:13 +08:00
|
|
|
public byte[] MaskingKey {
|
2014-06-11 15:46:07 +08:00
|
|
|
get {
|
|
|
|
return _maskingKey;
|
|
|
|
}
|
|
|
|
}
|
2013-04-12 13:42:25 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public Opcode Opcode {
|
|
|
|
get {
|
|
|
|
return _opcode;
|
|
|
|
}
|
|
|
|
}
|
2013-04-12 13:42:25 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public PayloadData PayloadData {
|
|
|
|
get {
|
|
|
|
return _payloadData;
|
|
|
|
}
|
|
|
|
}
|
2013-04-12 13:42:25 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public byte PayloadLength {
|
|
|
|
get {
|
|
|
|
return _payloadLength;
|
|
|
|
}
|
|
|
|
}
|
2013-04-12 13:42:25 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public Rsv Rsv1 {
|
|
|
|
get {
|
|
|
|
return _rsv1;
|
|
|
|
}
|
|
|
|
}
|
2013-04-12 13:42:25 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public Rsv Rsv2 {
|
|
|
|
get {
|
|
|
|
return _rsv2;
|
|
|
|
}
|
|
|
|
}
|
2013-04-12 13:42:25 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
public Rsv Rsv3 {
|
|
|
|
get {
|
|
|
|
return _rsv3;
|
|
|
|
}
|
|
|
|
}
|
2013-04-12 13:42:25 +08:00
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
2012-10-04 14:04:21 +08:00
|
|
|
#region Private Methods
|
2012-07-31 09:36:52 +08:00
|
|
|
|
2014-07-26 21:01:13 +08:00
|
|
|
private static byte[] createMaskingKey ()
|
2013-05-17 22:02:11 +08:00
|
|
|
{
|
2014-07-26 21:01:13 +08:00
|
|
|
var key = new byte[4];
|
2015-08-04 14:50:36 +08:00
|
|
|
WebSocket.RandomNumber.GetBytes (key);
|
2013-05-17 22:02:11 +08:00
|
|
|
|
|
|
|
return key;
|
|
|
|
}
|
|
|
|
|
2014-06-12 18:59:27 +08:00
|
|
|
private static string dump (WebSocketFrame frame)
|
2012-07-31 09:36:52 +08:00
|
|
|
{
|
2013-04-12 13:42:25 +08:00
|
|
|
var len = frame.Length;
|
2014-06-11 15:46:07 +08:00
|
|
|
var cnt = (long) (len / 4);
|
2013-11-05 20:42:59 +08:00
|
|
|
var rem = (int) (len % 4);
|
2013-04-12 13:42:25 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
int cntDigit;
|
|
|
|
string cntFmt;
|
|
|
|
if (cnt < 10000) {
|
|
|
|
cntDigit = 4;
|
|
|
|
cntFmt = "{0,4}";
|
2013-04-12 13:42:25 +08:00
|
|
|
}
|
2014-06-11 15:46:07 +08:00
|
|
|
else if (cnt < 0x010000) {
|
|
|
|
cntDigit = 4;
|
|
|
|
cntFmt = "{0,4:X}";
|
2013-04-12 13:42:25 +08:00
|
|
|
}
|
2014-06-11 15:46:07 +08:00
|
|
|
else if (cnt < 0x0100000000) {
|
|
|
|
cntDigit = 8;
|
|
|
|
cntFmt = "{0,8:X}";
|
2013-04-12 13:42:25 +08:00
|
|
|
}
|
2014-06-11 15:46:07 +08:00
|
|
|
else {
|
|
|
|
cntDigit = 16;
|
|
|
|
cntFmt = "{0,16:X}";
|
2013-04-12 13:42:25 +08:00
|
|
|
}
|
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
var spFmt = String.Format ("{{0,{0}}}", cntDigit);
|
2014-09-10 12:50:28 +08:00
|
|
|
var headerFmt = String.Format (@"
|
|
|
|
{0} 01234567 89ABCDEF 01234567 89ABCDEF
|
2013-11-05 20:42:59 +08:00
|
|
|
{0}+--------+--------+--------+--------+\n", spFmt);
|
2014-06-12 18:59:27 +08:00
|
|
|
var lineFmt = String.Format ("{0}|{{1,8}} {{2,8}} {{3,8}} {{4,8}}|\n", cntFmt);
|
2013-11-05 20:42:59 +08:00
|
|
|
var footerFmt = String.Format ("{0}+--------+--------+--------+--------+", spFmt);
|
2013-04-12 13:42:25 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
var output = new StringBuilder (64);
|
|
|
|
Func<Action<string, string, string, string>> linePrinter = () => {
|
|
|
|
long lineCnt = 0;
|
2013-04-12 13:42:25 +08:00
|
|
|
return (arg1, arg2, arg3, arg4) =>
|
2014-06-11 15:46:07 +08:00
|
|
|
output.AppendFormat (lineFmt, ++lineCnt, arg1, arg2, arg3, arg4);
|
2013-04-12 13:42:25 +08:00
|
|
|
};
|
2015-05-16 16:08:45 +08:00
|
|
|
var printLine = linePrinter ();
|
2013-04-12 13:42:25 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
output.AppendFormat (headerFmt, String.Empty);
|
2013-04-12 13:42:25 +08:00
|
|
|
|
2014-09-10 12:50:28 +08:00
|
|
|
var bytes = frame.ToByteArray ();
|
2014-06-12 18:59:27 +08:00
|
|
|
for (long i = 0; i <= cnt; i++) {
|
2014-06-11 15:46:07 +08:00
|
|
|
var j = i * 4;
|
2015-05-16 16:08:45 +08:00
|
|
|
if (i < cnt) {
|
2013-10-01 13:52:39 +08:00
|
|
|
printLine (
|
2015-05-16 16:08:45 +08:00
|
|
|
Convert.ToString (bytes[j], 2).PadLeft (8, '0'),
|
2014-09-10 12:50:28 +08:00
|
|
|
Convert.ToString (bytes[j + 1], 2).PadLeft (8, '0'),
|
|
|
|
Convert.ToString (bytes[j + 2], 2).PadLeft (8, '0'),
|
|
|
|
Convert.ToString (bytes[j + 3], 2).PadLeft (8, '0'));
|
2015-05-16 16:08:45 +08:00
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rem > 0)
|
2013-10-01 13:52:39 +08:00
|
|
|
printLine (
|
2014-09-10 12:50:28 +08:00
|
|
|
Convert.ToString (bytes[j], 2).PadLeft (8, '0'),
|
|
|
|
rem >= 2 ? Convert.ToString (bytes[j + 1], 2).PadLeft (8, '0') : String.Empty,
|
|
|
|
rem == 3 ? Convert.ToString (bytes[j + 2], 2).PadLeft (8, '0') : String.Empty,
|
2013-04-12 13:42:25 +08:00
|
|
|
String.Empty);
|
|
|
|
}
|
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
output.AppendFormat (footerFmt, String.Empty);
|
|
|
|
return output.ToString ();
|
2013-04-23 17:08:42 +08:00
|
|
|
}
|
|
|
|
|
2013-10-01 13:52:39 +08:00
|
|
|
private static bool isControl (Opcode opcode)
|
2013-04-12 13:42:25 +08:00
|
|
|
{
|
2014-03-06 16:07:30 +08:00
|
|
|
return opcode == Opcode.Close || opcode == Opcode.Ping || opcode == Opcode.Pong;
|
2013-04-12 13:42:25 +08:00
|
|
|
}
|
|
|
|
|
2013-10-01 13:52:39 +08:00
|
|
|
private static bool isData (Opcode opcode)
|
2013-04-12 13:42:25 +08:00
|
|
|
{
|
2014-03-06 16:07:30 +08:00
|
|
|
return opcode == Opcode.Text || opcode == Opcode.Binary;
|
2013-04-12 13:42:25 +08:00
|
|
|
}
|
|
|
|
|
2014-07-26 21:01:13 +08:00
|
|
|
private static string print (WebSocketFrame frame)
|
|
|
|
{
|
|
|
|
/* Opcode */
|
|
|
|
|
|
|
|
var opcode = frame._opcode.ToString ();
|
|
|
|
|
|
|
|
/* Payload Length */
|
|
|
|
|
|
|
|
var payloadLen = frame._payloadLength;
|
|
|
|
|
|
|
|
/* Extended Payload Length */
|
|
|
|
|
2014-09-10 12:50:28 +08:00
|
|
|
var extPayloadLen = payloadLen < 126
|
|
|
|
? String.Empty
|
|
|
|
: payloadLen == 126
|
|
|
|
? frame._extPayloadLength.ToUInt16 (ByteOrder.Big).ToString ()
|
|
|
|
: frame._extPayloadLength.ToUInt64 (ByteOrder.Big).ToString ();
|
2014-07-26 21:01:13 +08:00
|
|
|
|
|
|
|
/* Masking Key */
|
|
|
|
|
|
|
|
var masked = frame.IsMasked;
|
|
|
|
var maskingKey = masked ? BitConverter.ToString (frame._maskingKey) : String.Empty;
|
|
|
|
|
|
|
|
/* Payload Data */
|
|
|
|
|
|
|
|
var payload = payloadLen == 0
|
|
|
|
? String.Empty
|
2014-09-10 12:50:28 +08:00
|
|
|
: payloadLen > 125
|
2014-07-26 21:01:13 +08:00
|
|
|
? String.Format ("A {0} frame.", opcode.ToLower ())
|
2014-09-17 13:50:22 +08:00
|
|
|
: !masked && !frame.IsFragmented && !frame.IsCompressed && frame.IsText
|
2014-07-26 21:01:13 +08:00
|
|
|
? Encoding.UTF8.GetString (frame._payloadData.ApplicationData)
|
|
|
|
: frame._payloadData.ToString ();
|
|
|
|
|
2014-09-10 12:50:28 +08:00
|
|
|
var fmt = @"
|
|
|
|
FIN: {0}
|
2014-07-26 21:01:13 +08:00
|
|
|
RSV1: {1}
|
|
|
|
RSV2: {2}
|
|
|
|
RSV3: {3}
|
|
|
|
Opcode: {4}
|
|
|
|
MASK: {5}
|
|
|
|
Payload Length: {6}
|
|
|
|
Extended Payload Length: {7}
|
|
|
|
Masking Key: {8}
|
|
|
|
Payload Data: {9}";
|
|
|
|
|
|
|
|
return String.Format (
|
|
|
|
fmt,
|
|
|
|
frame._fin,
|
|
|
|
frame._rsv1,
|
|
|
|
frame._rsv2,
|
|
|
|
frame._rsv3,
|
|
|
|
opcode,
|
|
|
|
frame._mask,
|
|
|
|
payloadLen,
|
|
|
|
extPayloadLen,
|
|
|
|
maskingKey,
|
|
|
|
payload);
|
|
|
|
}
|
|
|
|
|
2015-09-19 14:35:15 +08:00
|
|
|
private static WebSocketFrame processHeader (byte[] header)
|
|
|
|
{
|
|
|
|
if (header.Length != 2)
|
|
|
|
throw new WebSocketException (
|
|
|
|
"The header part of a frame cannot be read from the data source.");
|
|
|
|
|
|
|
|
// FIN
|
|
|
|
var fin = (header[0] & 0x80) == 0x80 ? Fin.Final : Fin.More;
|
|
|
|
|
|
|
|
// RSV1
|
|
|
|
var rsv1 = (header[0] & 0x40) == 0x40 ? Rsv.On : Rsv.Off;
|
|
|
|
|
|
|
|
// RSV2
|
|
|
|
var rsv2 = (header[0] & 0x20) == 0x20 ? Rsv.On : Rsv.Off;
|
|
|
|
|
|
|
|
// RSV3
|
|
|
|
var rsv3 = (header[0] & 0x10) == 0x10 ? Rsv.On : Rsv.Off;
|
|
|
|
|
|
|
|
// Opcode
|
|
|
|
var opcode = (Opcode) (header[0] & 0x0f);
|
|
|
|
|
|
|
|
// MASK
|
|
|
|
var mask = (header[1] & 0x80) == 0x80 ? Mask.Mask : Mask.Unmask;
|
|
|
|
|
|
|
|
// Payload Length
|
|
|
|
var payloadLen = (byte) (header[1] & 0x7f);
|
|
|
|
|
|
|
|
// Check if valid header.
|
|
|
|
var err = isControl (opcode) && payloadLen > 125
|
|
|
|
? "A control frame has payload data which is greater than the allowable max length."
|
|
|
|
: isControl (opcode) && fin == Fin.More
|
|
|
|
? "A control frame is fragmented."
|
|
|
|
: !isData (opcode) && rsv1 == Rsv.On
|
|
|
|
? "A non data frame is compressed."
|
|
|
|
: null;
|
|
|
|
|
|
|
|
if (err != null)
|
|
|
|
throw new WebSocketException (CloseStatusCode.ProtocolError, err);
|
|
|
|
|
|
|
|
var frame = new WebSocketFrame ();
|
|
|
|
frame._fin = fin;
|
|
|
|
frame._rsv1 = rsv1;
|
|
|
|
frame._rsv2 = rsv2;
|
|
|
|
frame._rsv3 = rsv3;
|
|
|
|
frame._opcode = opcode;
|
|
|
|
frame._mask = mask;
|
|
|
|
frame._payloadLength = payloadLen;
|
|
|
|
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
2015-09-20 15:39:19 +08:00
|
|
|
private static WebSocketFrame readExtendedPayloadLength (Stream stream, WebSocketFrame frame)
|
|
|
|
{
|
|
|
|
var len = frame._payloadLength < 126 ? 0 : (frame._payloadLength == 126 ? 2 : 8);
|
|
|
|
if (len == 0) {
|
|
|
|
frame._extPayloadLength = WebSocket.EmptyBytes;
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
|
|
|
var bytes = stream.ReadBytes (len);
|
|
|
|
if (bytes.Length != len)
|
|
|
|
throw new WebSocketException (
|
|
|
|
"The 'Extended Payload Length' of a frame cannot be read from the data source.");
|
|
|
|
|
|
|
|
frame._extPayloadLength = bytes;
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
2015-09-18 20:06:17 +08:00
|
|
|
private static void readExtendedPayloadLengthAsync (
|
|
|
|
Stream stream,
|
|
|
|
WebSocketFrame frame,
|
|
|
|
Action<WebSocketFrame> completed,
|
|
|
|
Action<Exception> error)
|
|
|
|
{
|
2015-09-22 11:23:43 +08:00
|
|
|
var len = frame._payloadLength < 126 ? 0 : (frame._payloadLength == 126 ? 2 : 8);
|
|
|
|
if (len == 0) {
|
2015-09-18 20:06:17 +08:00
|
|
|
frame._extPayloadLength = WebSocket.EmptyBytes;
|
|
|
|
completed (frame);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
stream.ReadBytesAsync (
|
2015-09-22 11:23:43 +08:00
|
|
|
len,
|
|
|
|
bytes => {
|
|
|
|
if (bytes.Length != len)
|
2015-09-18 20:06:17 +08:00
|
|
|
throw new WebSocketException (
|
|
|
|
"The 'Extended Payload Length' of a frame cannot be read from the data source.");
|
|
|
|
|
2015-09-22 11:23:43 +08:00
|
|
|
frame._extPayloadLength = bytes;
|
2015-09-18 20:06:17 +08:00
|
|
|
completed (frame);
|
|
|
|
},
|
|
|
|
error);
|
|
|
|
}
|
|
|
|
|
2015-09-20 15:39:19 +08:00
|
|
|
private static WebSocketFrame readHeader (Stream stream)
|
|
|
|
{
|
|
|
|
return processHeader (stream.ReadBytes (2));
|
|
|
|
}
|
|
|
|
|
2015-09-18 20:06:17 +08:00
|
|
|
private static void readHeaderAsync (
|
|
|
|
Stream stream, Action<WebSocketFrame> completed, Action<Exception> error)
|
|
|
|
{
|
2015-09-22 11:23:43 +08:00
|
|
|
stream.ReadBytesAsync (2, bytes => completed (processHeader (bytes)), error);
|
2015-09-18 20:06:17 +08:00
|
|
|
}
|
|
|
|
|
2015-09-20 15:39:19 +08:00
|
|
|
private static WebSocketFrame readMaskingKey (Stream stream, WebSocketFrame frame)
|
|
|
|
{
|
|
|
|
var len = frame.IsMasked ? 4 : 0;
|
|
|
|
if (len == 0) {
|
|
|
|
frame._maskingKey = WebSocket.EmptyBytes;
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
|
|
|
var bytes = stream.ReadBytes (len);
|
|
|
|
if (bytes.Length != len)
|
|
|
|
throw new WebSocketException (
|
|
|
|
"The 'Masking Key' of a frame cannot be read from the data source.");
|
|
|
|
|
|
|
|
frame._maskingKey = bytes;
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
2015-09-18 20:06:17 +08:00
|
|
|
private static void readMaskingKeyAsync (
|
|
|
|
Stream stream,
|
|
|
|
WebSocketFrame frame,
|
|
|
|
Action<WebSocketFrame> completed,
|
|
|
|
Action<Exception> error)
|
|
|
|
{
|
2015-09-22 11:23:43 +08:00
|
|
|
var len = frame.IsMasked ? 4 : 0;
|
|
|
|
if (len == 0) {
|
2015-09-18 20:06:17 +08:00
|
|
|
frame._maskingKey = WebSocket.EmptyBytes;
|
|
|
|
completed (frame);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
stream.ReadBytesAsync (
|
2015-09-22 11:23:43 +08:00
|
|
|
len,
|
|
|
|
bytes => {
|
|
|
|
if (bytes.Length != len)
|
2015-09-18 20:06:17 +08:00
|
|
|
throw new WebSocketException (
|
|
|
|
"The 'Masking Key' of a frame cannot be read from the data source.");
|
|
|
|
|
2015-09-22 11:23:43 +08:00
|
|
|
frame._maskingKey = bytes;
|
2015-09-18 20:06:17 +08:00
|
|
|
completed (frame);
|
|
|
|
},
|
|
|
|
error);
|
|
|
|
}
|
|
|
|
|
2015-09-20 15:39:19 +08:00
|
|
|
private static WebSocketFrame readPayloadData (Stream stream, WebSocketFrame frame)
|
|
|
|
{
|
|
|
|
ulong len = frame._payloadLength < 126
|
|
|
|
? frame._payloadLength
|
|
|
|
: frame._payloadLength == 126
|
|
|
|
? frame._extPayloadLength.ToUInt16 (ByteOrder.Big)
|
|
|
|
: frame._extPayloadLength.ToUInt64 (ByteOrder.Big);
|
|
|
|
|
|
|
|
if (len == 0) {
|
|
|
|
frame._payloadData = new PayloadData (WebSocket.EmptyBytes, frame.IsMasked);
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if allowable length.
|
|
|
|
if (len > PayloadData.MaxLength)
|
|
|
|
throw new WebSocketException (
|
|
|
|
CloseStatusCode.TooBig,
|
|
|
|
"The length of 'Payload Data' of a frame is greater than the allowable max length.");
|
|
|
|
|
|
|
|
var bytes = frame._payloadLength < 127
|
|
|
|
? stream.ReadBytes ((int) len)
|
|
|
|
: stream.ReadBytes ((long) len, 1024);
|
|
|
|
|
|
|
|
if (bytes.LongLength != (long) len)
|
|
|
|
throw new WebSocketException (
|
|
|
|
"The 'Payload Data' of a frame cannot be read from the data source.");
|
|
|
|
|
|
|
|
frame._payloadData = new PayloadData (bytes, frame.IsMasked);
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
2015-09-18 20:06:17 +08:00
|
|
|
private static void readPayloadDataAsync (
|
|
|
|
Stream stream,
|
|
|
|
WebSocketFrame frame,
|
|
|
|
Action<WebSocketFrame> completed,
|
|
|
|
Action<Exception> error)
|
|
|
|
{
|
2015-09-22 11:23:43 +08:00
|
|
|
ulong len = frame._payloadLength < 126
|
|
|
|
? frame._payloadLength
|
|
|
|
: frame._payloadLength == 126
|
|
|
|
? frame._extPayloadLength.ToUInt16 (ByteOrder.Big)
|
|
|
|
: frame._extPayloadLength.ToUInt64 (ByteOrder.Big);
|
2015-09-18 20:06:17 +08:00
|
|
|
|
2015-09-22 11:23:43 +08:00
|
|
|
if (len == 0) {
|
|
|
|
frame._payloadData = new PayloadData (WebSocket.EmptyBytes, frame.IsMasked);
|
2015-09-18 20:06:17 +08:00
|
|
|
completed (frame);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-09-22 11:23:43 +08:00
|
|
|
// Check if allowable length.
|
|
|
|
if (len > PayloadData.MaxLength)
|
|
|
|
throw new WebSocketException (
|
|
|
|
CloseStatusCode.TooBig,
|
|
|
|
"The length of 'Payload Data' of a frame is greater than the allowable max length.");
|
|
|
|
|
2015-09-18 20:06:17 +08:00
|
|
|
if (frame._payloadLength < 127) {
|
2015-09-22 11:23:43 +08:00
|
|
|
var ilen = (int) len;
|
2015-09-18 20:06:17 +08:00
|
|
|
stream.ReadBytesAsync (
|
2015-09-22 11:23:43 +08:00
|
|
|
ilen,
|
|
|
|
bytes => {
|
|
|
|
if (bytes.Length != ilen)
|
2015-09-18 20:06:17 +08:00
|
|
|
throw new WebSocketException (
|
|
|
|
"The 'Payload Data' of a frame cannot be read from the data source.");
|
|
|
|
|
2015-09-22 11:23:43 +08:00
|
|
|
frame._payloadData = new PayloadData (bytes, frame.IsMasked);
|
2015-09-18 20:06:17 +08:00
|
|
|
completed (frame);
|
|
|
|
},
|
|
|
|
error);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-09-22 11:23:43 +08:00
|
|
|
var llen = (long) len;
|
2015-09-18 20:06:17 +08:00
|
|
|
stream.ReadBytesAsync (
|
|
|
|
llen,
|
|
|
|
1024,
|
2015-09-22 11:23:43 +08:00
|
|
|
bytes => {
|
|
|
|
if (bytes.LongLength != llen)
|
2015-09-18 20:06:17 +08:00
|
|
|
throw new WebSocketException (
|
|
|
|
"The 'Payload Data' of a frame cannot be read from the data source.");
|
|
|
|
|
2015-09-22 11:23:43 +08:00
|
|
|
frame._payloadData = new PayloadData (bytes, frame.IsMasked);
|
2015-09-18 20:06:17 +08:00
|
|
|
completed (frame);
|
|
|
|
},
|
|
|
|
error);
|
|
|
|
}
|
|
|
|
|
2013-04-23 17:08:42 +08:00
|
|
|
#endregion
|
|
|
|
|
2013-10-01 13:52:39 +08:00
|
|
|
#region Internal Methods
|
|
|
|
|
2014-09-23 14:45:40 +08:00
|
|
|
internal static WebSocketFrame CreateCloseFrame (PayloadData payloadData, bool mask)
|
2013-10-01 13:52:39 +08:00
|
|
|
{
|
2014-09-24 13:37:11 +08:00
|
|
|
return new WebSocketFrame (Fin.Final, Opcode.Close, payloadData, false, mask);
|
2013-10-01 13:52:39 +08:00
|
|
|
}
|
|
|
|
|
2014-09-23 14:45:40 +08:00
|
|
|
internal static WebSocketFrame CreatePingFrame (bool mask)
|
2013-10-01 13:52:39 +08:00
|
|
|
{
|
2014-09-24 13:37:11 +08:00
|
|
|
return new WebSocketFrame (Fin.Final, Opcode.Ping, new PayloadData (), false, mask);
|
2013-10-01 13:52:39 +08:00
|
|
|
}
|
|
|
|
|
2014-09-23 14:45:40 +08:00
|
|
|
internal static WebSocketFrame CreatePingFrame (byte[] data, bool mask)
|
2013-10-01 13:52:39 +08:00
|
|
|
{
|
2014-09-24 13:37:11 +08:00
|
|
|
return new WebSocketFrame (Fin.Final, Opcode.Ping, new PayloadData (data), false, mask);
|
2012-07-31 09:36:52 +08:00
|
|
|
}
|
|
|
|
|
2014-07-26 21:01:13 +08:00
|
|
|
internal static WebSocketFrame Read (Stream stream)
|
2012-07-31 09:36:52 +08:00
|
|
|
{
|
2014-07-26 21:01:13 +08:00
|
|
|
return Read (stream, true);
|
2012-10-04 14:04:21 +08:00
|
|
|
}
|
|
|
|
|
2014-07-26 21:01:13 +08:00
|
|
|
internal static WebSocketFrame Read (Stream stream, bool unmask)
|
2012-10-04 14:04:21 +08:00
|
|
|
{
|
2015-09-20 15:39:19 +08:00
|
|
|
var frame = readHeader (stream);
|
|
|
|
readExtendedPayloadLength (stream, frame);
|
|
|
|
readMaskingKey (stream, frame);
|
|
|
|
readPayloadData (stream, frame);
|
2013-04-12 13:42:25 +08:00
|
|
|
|
2015-09-20 15:39:19 +08:00
|
|
|
if (unmask && frame.IsMasked)
|
|
|
|
frame.Unmask ();
|
|
|
|
|
|
|
|
return frame;
|
2012-11-13 15:38:48 +08:00
|
|
|
}
|
2012-10-04 14:04:21 +08:00
|
|
|
|
2014-07-26 21:01:13 +08:00
|
|
|
internal static void ReadAsync (
|
2014-06-12 18:59:27 +08:00
|
|
|
Stream stream, Action<WebSocketFrame> completed, Action<Exception> error)
|
2012-11-13 15:38:48 +08:00
|
|
|
{
|
2014-07-26 21:01:13 +08:00
|
|
|
ReadAsync (stream, true, completed, error);
|
2013-04-07 16:05:48 +08:00
|
|
|
}
|
2012-11-13 15:38:48 +08:00
|
|
|
|
2014-07-26 21:01:13 +08:00
|
|
|
internal static void ReadAsync (
|
2014-06-12 18:59:27 +08:00
|
|
|
Stream stream, bool unmask, Action<WebSocketFrame> completed, Action<Exception> error)
|
2013-04-07 16:05:48 +08:00
|
|
|
{
|
2015-09-21 14:11:31 +08:00
|
|
|
readHeaderAsync (
|
|
|
|
stream,
|
|
|
|
frame => {
|
|
|
|
readExtendedPayloadLength (stream, frame);
|
|
|
|
readMaskingKey (stream, frame);
|
|
|
|
readPayloadData (stream, frame);
|
2013-10-18 22:07:43 +08:00
|
|
|
|
2015-09-21 14:11:31 +08:00
|
|
|
if (unmask && frame.IsMasked)
|
|
|
|
frame.Unmask ();
|
|
|
|
|
|
|
|
completed (frame);
|
2013-10-18 22:07:43 +08:00
|
|
|
},
|
2013-10-20 23:17:51 +08:00
|
|
|
error);
|
2012-07-31 09:36:52 +08:00
|
|
|
}
|
|
|
|
|
2015-09-18 20:06:17 +08:00
|
|
|
internal static void ReadAsync2 (
|
|
|
|
Stream stream, bool unmask, Action<WebSocketFrame> completed, Action<Exception> error)
|
|
|
|
{
|
|
|
|
readHeaderAsync (
|
|
|
|
stream,
|
2015-09-22 11:23:43 +08:00
|
|
|
frame =>
|
2015-09-18 20:06:17 +08:00
|
|
|
readExtendedPayloadLengthAsync (
|
|
|
|
stream,
|
|
|
|
frame,
|
2015-09-22 11:23:43 +08:00
|
|
|
frame1 =>
|
2015-09-18 20:06:17 +08:00
|
|
|
readMaskingKeyAsync (
|
|
|
|
stream,
|
|
|
|
frame1,
|
2015-09-22 11:23:43 +08:00
|
|
|
frame2 =>
|
2015-09-18 20:06:17 +08:00
|
|
|
readPayloadDataAsync (
|
|
|
|
stream,
|
|
|
|
frame2,
|
|
|
|
frame3 => {
|
2015-09-22 11:23:43 +08:00
|
|
|
if (unmask && frame3.IsMasked)
|
2015-09-18 20:06:17 +08:00
|
|
|
frame3.Unmask ();
|
|
|
|
|
|
|
|
completed (frame3);
|
|
|
|
},
|
2015-09-22 11:23:43 +08:00
|
|
|
error),
|
|
|
|
error),
|
|
|
|
error),
|
2015-09-18 20:06:17 +08:00
|
|
|
error);
|
|
|
|
}
|
|
|
|
|
2014-09-16 13:53:26 +08:00
|
|
|
internal void Unmask ()
|
|
|
|
{
|
|
|
|
if (_mask == Mask.Unmask)
|
|
|
|
return;
|
|
|
|
|
2014-09-17 13:50:22 +08:00
|
|
|
_mask = Mask.Unmask;
|
2014-09-16 13:53:26 +08:00
|
|
|
_payloadData.Mask (_maskingKey);
|
2015-08-24 15:03:50 +08:00
|
|
|
_maskingKey = WebSocket.EmptyBytes;
|
2014-09-16 13:53:26 +08:00
|
|
|
}
|
|
|
|
|
2014-07-26 21:01:13 +08:00
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Public Methods
|
|
|
|
|
|
|
|
public IEnumerator<byte> GetEnumerator ()
|
|
|
|
{
|
|
|
|
foreach (var b in ToByteArray ())
|
|
|
|
yield return b;
|
|
|
|
}
|
|
|
|
|
2013-10-01 13:52:39 +08:00
|
|
|
public void Print (bool dumped)
|
2012-07-31 09:36:52 +08:00
|
|
|
{
|
2013-11-05 20:42:59 +08:00
|
|
|
Console.WriteLine (dumped ? dump (this) : print (this));
|
|
|
|
}
|
|
|
|
|
|
|
|
public string PrintToString (bool dumped)
|
|
|
|
{
|
2015-05-16 16:08:45 +08:00
|
|
|
return dumped ? dump (this) : print (this);
|
2012-07-31 09:36:52 +08:00
|
|
|
}
|
|
|
|
|
2014-07-26 21:01:13 +08:00
|
|
|
public byte[] ToByteArray ()
|
2012-07-31 09:36:52 +08:00
|
|
|
{
|
2014-06-11 15:46:07 +08:00
|
|
|
using (var buff = new MemoryStream ()) {
|
|
|
|
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) _mask;
|
|
|
|
header = (header << 7) + (int) _payloadLength;
|
2014-08-19 13:14:59 +08:00
|
|
|
buff.Write (((ushort) header).InternalToByteArray (ByteOrder.Big), 0, 2);
|
2013-05-17 22:02:11 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
if (_payloadLength > 125)
|
2015-05-17 16:49:21 +08:00
|
|
|
buff.Write (_extPayloadLength, 0, _payloadLength == 126 ? 2 : 8);
|
2013-05-17 22:02:11 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
if (_mask == Mask.Mask)
|
2015-05-17 16:49:21 +08:00
|
|
|
buff.Write (_maskingKey, 0, 4);
|
2013-05-17 22:02:11 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
if (_payloadLength > 0) {
|
2015-05-17 16:49:21 +08:00
|
|
|
var bytes = _payloadData.ToByteArray ();
|
2014-06-11 15:46:07 +08:00
|
|
|
if (_payloadLength < 127)
|
2015-05-17 16:49:21 +08:00
|
|
|
buff.Write (bytes, 0, bytes.Length);
|
2013-05-17 22:02:11 +08:00
|
|
|
else
|
2015-05-17 16:49:21 +08:00
|
|
|
buff.WriteBytes (bytes);
|
2013-05-17 22:02:11 +08:00
|
|
|
}
|
2012-07-31 09:36:52 +08:00
|
|
|
|
2014-06-11 15:46:07 +08:00
|
|
|
buff.Close ();
|
|
|
|
return buff.ToArray ();
|
2013-05-17 22:02:11 +08:00
|
|
|
}
|
2012-07-31 09:36:52 +08:00
|
|
|
}
|
|
|
|
|
2013-10-01 13:52:39 +08:00
|
|
|
public override string ToString ()
|
2012-07-31 09:36:52 +08:00
|
|
|
{
|
2013-10-01 13:52:39 +08:00
|
|
|
return BitConverter.ToString (ToByteArray ());
|
2013-04-12 13:42:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
2014-09-13 14:55:29 +08:00
|
|
|
#region Explicit Interface Implementations
|
2013-04-12 13:42:25 +08:00
|
|
|
|
2013-10-01 13:52:39 +08:00
|
|
|
IEnumerator IEnumerable.GetEnumerator ()
|
2013-04-12 13:42:25 +08:00
|
|
|
{
|
2013-10-01 13:52:39 +08:00
|
|
|
return GetEnumerator ();
|
2012-07-31 09:36:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|