Skip to content

Instantly share code, notes, and snippets.

@sirisian
Created October 4, 2014 04:41
Show Gist options
  • Save sirisian/7f4cb0faedee4d17d65b to your computer and use it in GitHub Desktop.
Save sirisian/7f4cb0faedee4d17d65b to your computer and use it in GitHub Desktop.
/// <var>A binary bit-based writer and reader used for packets.</var>
var Packet =
{
Create: function()
{
/// <signature>
/// <summary>Constructor.</summary>
/// <returns type="Packet" />
/// </signature>
/// <signature>
/// <summary>Constructor.</summary>
/// <param name="codecState" type="CodecState">The codec state to use for coding.</param>
/// <returns type="Packet" />
/// </signature>
/// <signature>
/// <summary>Constructor.</summary>
/// <param name="buffer" type="ArrayBuffer">The buffer to read from.</param>
/// <param name="error" type="function">Error callback.</param>
/// <returns type="Packet" />
/// </signature>
/// <signature>
/// <summary>Constructor.</summary>
/// <param name="buffer" type="ArrayBuffer">The buffer to read from.</param>
/// <param name="codecState" type="CodecState">The codec state to use for coding.</param>
/// <param name="error" type="function">Error callback.</param>
/// <returns type="Packet" />
/// </signature>
var packet = Object.create(this,
{
internalBuffer:
{
value: null,
writable: true
},
buffer:
{
value: null,
writable: true
},
/// <field type="UInt8Array">The byte buffer for the packet.</field>
Buffer:
{
get: function()
{
return new Uint8Array(this.internalBuffer, 0, (this.bitIndex + 7) / 8 >> 0);
}
},
bitIndex:
{
value: 0,
writable: true
},
/// <field type="UInt">The current bit index of the writer and reader.</field>
BitIndex:
{
get: function()
{
return this.bitIndex;
},
set: function(value)
{
this.bitIndex = value;
}
},
maximumBitIndex:
{
value: 0,
writable: true
},
/// <field type="UInt">The maximum bit index of the writer and reader.</field>
MaximumBitIndex:
{
get: function()
{
return this.maximumBitIndex;
},
set: function(value)
{
this.maximumBitIndex = value;
}
},
headerSize:
{
get: function()
{
return 16;
}
},
error:
{
value: null,
writable: true
},
Error:
{
get: function()
{
return this.error == null;
}
},
CodecState:
{
value: null,
writable: true
}
});
if (arguments.length == 0)
{
// 1500 bytes - Websocket Header - TCP Header = Default Packet Size
packet.internalBuffer = new ArrayBuffer(1400);
packet.buffer = new Uint32Array(packet.internalBuffer);
}
else if (arguments.length == 1)
{
// 1500 bytes - Websocket Header - TCP Header = Default Packet Size
packet.internalBuffer = new ArrayBuffer(1400);
packet.buffer = new Uint32Array(packet.internalBuffer);
packet.CodecState = arguments[0];
}
else if (arguments.length == 2)
{
packet.internalBuffer = arguments[0];
packet.buffer = new Uint32Array(packet.internalBuffer);
var callback = arguments[1];
packet.error = function(message)
{
callback(message);
this.error = null;
};
packet.ReadHeader();
}
else
{
packet.internalBuffer = arguments[0];
packet.buffer = new Uint32Array(packet.internalBuffer);
packet.CodecState = arguments[1];
var callback = arguments[2];
packet.error = function(message)
{
callback(message);
this.error = null;
};
packet.ReadHeader();
}
return packet;
},
Log2: function(value)
{
/// <signature>
/// <summary>Computes the log2 of a value.</summary>
/// <param name="value" type="UInt">The value to compute the log2 of.</param>
/// <returns type="UInt" />
/// </signature>
if (value == 0)
{
return 0;
}
else
{
var left = 0;
var right = 32;
for (var i = 0; i < 5; ++i)
{
if (value >> (32 - (left + right) / 2) != 0)
{
// Left
right = (left + right) / 2;
}
else
{
// Right
left = (left + right) / 2;
}
}
return 32 - left;
}
},
ResetBitIndex: function()
{
/// <signature>
/// <summary>Moves the bit index after the header.</summary>
/// </signature>
this.bitIndex = this.headerSize;
},
Trace: function()
{
/// <signature>
/// <summary>Returns a string of 0s and 1s representing the bits in the this.buffer.</summary>
/// <returns type="string" />
/// </signature>
var s = "";
for (var copyBits = 0; copyBits < Math.max(this.bitIndex, this.maximumBitIndex); ++copyBits)
{
s += (this.buffer[copyBits / 32 >> 0] << copyBits % 32 >>> 31) == 0 ? "0" : "1";
}
return s;
},
ReadHeader: function()
{
/// <signature>
/// <summary>Reads the length of the packet stored in the header.</summary>
/// </signature>
this.maximumBitIndex = this.buffer[0] >>> 32 - this.headerSize;
this.bitIndex += this.headerSize;
},
Begin: function()
{
/// <signature>
/// <summary>Writes a 16 bit header.</summary>
/// </signature>
this.bitIndex += this.headerSize;
},
End: function()
{
/// <signature>
/// <summary>Writes maxBitIndex to the packet header created with BeginPacket.</summary>
/// </signature>
this.maximumBitIndex = this.bitIndex;
this.buffer[0] |= this.maximumBitIndex << 32 - this.headerSize;
},
WriteEvent: function(value)
{
/// <signature>
/// <summary>Writes an event to the packet.</summary>
/// <param name="value" type="UInt8">The event value.</param>
/// </signature>
this.WriteUInt8(value);
},
WriteBool: function(value)
{
/// <signature>
/// <summary>Writes a bool to the packet.</summary>
/// <param name="value" type="bool">The bool value.</param>
/// </signature>
if (value)
{
this.buffer[this.bitIndex / 32 >> 0] |= 1 << 31 - this.bitIndex % 32;
}
this.bitIndex++;
},
WriteUInt8: function(value)
{
/// <signature>
/// <summary>Writes an 8-bit unsigned integer to the packet.</summary>
/// <param name="value" type="UInt8">The 8 bit unsigned integer value.</param>
/// </signature>
var offset = this.bitIndex % 32;
this.buffer[this.bitIndex / 32 >> 0] |= value << 24 >>> offset;
if (offset > 24)
{
this.buffer[(this.bitIndex / 32 >> 0) + 1] |= value << 56 - offset;
}
this.bitIndex += 8;
},
WriteInt8: function(value)
{
/// <signature>
/// <summary>Writes an 8-bit signed integer to the packet.</summary>
/// <param name="value" type="Int8">The 8 bit signed integer value.</param>
/// </signature>
this.WriteUInt8(value);
},
WriteUInt16: function(value)
{
/// <signature>
/// <summary>Writes an 16-bit unsigned integer to the packet.</summary>
/// <param name="value" type="UInt16">The 16 bit unsigned integer value.</param>
/// </signature>
var offset = this.bitIndex % 32;
this.buffer[this.bitIndex / 32 >> 0] |= value << 16 >>> offset;
if (offset > 16)
{
this.buffer[(this.bitIndex / 32 >> 0) + 1] |= value << 48 - offset;
}
this.bitIndex += 16;
},
WriteInt16: function(value)
{
/// <signature>
/// <summary>Writes an 16-bit signed integer to the packet.</summary>
/// <param name="value" type="Int16">The 16 bit signed integer value.</param>
/// </signature>
this.WriteUInt16(value);
},
WriteUInt32: function(value)
{
/// <signature>
/// <summary>Writes an 32-bit unsigned integer to the packet.</summary>
/// <param name="value" type="UInt32">The 32 bit unsigned integer value.</param>
/// </signature>
var offset = this.bitIndex % 32;
this.buffer[this.bitIndex / 32 >> 0] |= value >>> offset;
if (offset > 0)
{
this.buffer[(this.bitIndex / 32 >> 0) + 1] |= value << 32 - offset;
}
this.bitIndex += 32;
},
WriteInt32: function(value)
{
/// <signature>
/// <summary>Writes an 32-bit signed integer to the packet.</summary>
/// <param name="value" type="Int32">The 32 bit signed integer value.</param>
/// </signature>
this.WriteUInt32(value);
},
WriteUIntN: function(value, bits)
{
/// <signature>
/// <summary>Writes an n-bit unsigned integer to the packet.</summary>
/// <param name="value" type="UInt">The unsigned integer value.</param>
/// <param name="bits" type="UInt">The number of bits to use.</param>
/// </signature>
var offset = this.bitIndex % 32;
this.buffer[this.bitIndex / 32 >> 0] |= value << 32 - bits >>> offset;
if (offset > 32 - bits)
{
this.buffer[(this.bitIndex / 32 >> 0) + 1] |= value << 64 - bits - offset;
}
this.bitIndex += bits;
},
WriteIntN: function(value, bits)
{
/// <signature>
/// <summary>Writes an n-bit unsigned integer to the packet.</summary>
/// <param name="value" type="Int">The signed integer value.</param>
/// <param name="bits" type="UInt">The number of bits to use.</param>
/// </signature>
this.WriteUIntN(value, bits);
},
WriteUInt: function(value)
{
/// <signature>
/// <summary>Writes an unsigned integer to the packet.</summary>
/// <param name="value" type="UInt">The unsigned integer value.</param>
/// <param name="maximum" type="UInt">The inclusive unsigned integer maximum.</param>
/// </signature>
/// <signature>
/// <summary>Writes an unsigned integer to the packet.</summary>
/// <param name="value" type="UInt">The unsigned integer value.</param>
/// <param name="minimum" type="UInt">The inclusive unsigned integer minimum.</param>
/// <param name="maximum" type="UInt">The inclusive unsigned integer maximum.</param>
/// </signature>
var minimum, maximum;
if (arguments.length == 2)
{
minimum = 0;
maximum = arguments[1];
}
else
{
minimum = arguments[1];
maximum = arguments[2];
}
var bits = this.Log2(maximum - minimum);
value -= minimum;
this.WriteUIntN(value, bits);
},
WriteInt: function()
{
/// <signature>
/// <summary>Writes a signed integer to the packet.</summary>
/// <param name="value" type="Int">The signed integer value.</param>
/// <param name="maximum" type="Int">The inclusive signed integer maximum.</param>
/// </signature>
/// <signature>
/// <summary>Writes a signed integer to the packet.</summary>
/// <param name="value" type="Int">The signed integer value.</param>
/// <param name="minimum" type="Int">The inclusive signed integer minimum.</param>
/// <param name="maximum" type="Int">The inclusive signed integer maximum.</param>
/// </signature>
this.WriteUInt.apply(this, arguments);
},
WriteVariableWidthUInt: function(value, bits)
{
/// <signature>
/// <summary>Writes an unsigned integer to the packet using a variable width encoding.</summary>
/// <param name="value" type="UInt">The unsigned integer value.</param>
/// <param name="bits" type="UInt">The number of bits to use for the sequence. Choose a bits value that represents the number of bits to hold the average value.</param>
/// </signature>
var shift = bits;
// Stop when our value can fit inside
for (; shift < 32 && value >= (0x1 << shift); shift += bits)
{
this.WriteBool(true); // Write a 1 for a continuation bit signifying one more interval is needed
}
if (shift < 32)
{
this.WriteBool(false); // Write a 0 for a continuation bit signifying the end
}
this.WriteUIntN(value, shift > 32 ? 32 : shift);
},
WriteVariableWidthInt: function(value, bits)
{
/// <signature>
/// <summary>Writes an signed integer to the packet using a variable width encoding.</summary>
/// <param name="value" type="Int">The signed integer value.</param>
/// <param name="bits" type="UInt">The number of bits to use for the sequence. Choose a bits value that represents the number of bits to hold the average value.</param>
/// </signature>
var shift = bits;
// Stop when our value can fit inside
for (; shift < 32 && (value < -(0x1 << (shift - 1)) || value >= 0x1 << (shift - 1)); shift += bits)
{
this.WriteBool(true); // Write a 1 for a continuation bit signifying one more interval is needed
}
if (shift < 32)
{
this.WriteBool(false); // Write a 0 for a continuation bit signifying the end
}
this.WriteIntN(value, shift > 32 ? 32 : shift);
},
WriteFloat32: function(value)
{
/// <signature>
/// <summary>Writes a 32 bit float to the packet.</summary>
/// <param name="value" type="Float32">The floating point value.</param>
/// </signature>
/// <signature>
/// <summary>Writes a 32 bit float to the packet.</summary>
/// <param name="value" type="Float32">The floating point value.</param>
/// <param name="maximum" type="Float32">The maximum value for the range starting at 0.</param>
/// <param name="bits" type="UInt">The number of bits to use.</param>
/// </signature>
/// <signature>
/// <summary>Writes a 32 bit float to the packet.</summary>
/// <param name="value" type="Float32">The floating point value.</param>
/// <param name="minimum" type="Float32">The inclusive floating point minimum.</param>
/// <param name="maximum" type="Float32">The inclusive floating point maximum.</param>
/// <param name="bits" type="UInt">The number of bits to use.</param>
/// </signature>
if (arguments.length == 1)
{
var arrayBuffer = new ArrayBuffer(4);
var float32 = new Float32Array(arrayBuffer);
var float32UInt32 = new Uint32Array(arrayBuffer);
float32[0] = value;
this.WriteUInt32(float32UInt32[0]);
}
else
{
var minimum, maximum, bits;
if (arguments.length == 3)
{
minimum = 0;
maximum = arguments[1];
bits = arguments[2];
}
else
{
minimum = arguments[1];
maximum = arguments[2];
bits = arguments[3];
}
var uValue;
if (minimum < 0 && maximum > 0)
{
uValue = value == 0 ? 0 : Math.round((value - minimum) / (maximum - minimum) * ((0x1 << bits) - 2)) + 1;
}
else
{
uValue = Math.round((value - minimum) / (maximum - minimum) * ((0x1 << bits) - 1));
}
this.WriteUIntN(uValue, bits);
}
},
WriteFloat64: function(value)
{
/// <signature>
/// <summary>Writes a 64 bit float to the packet.</summary>
/// <param name="value" type="Float64">The floating point value.</param>
/// </signature>
/// <signature>
/// <summary>Writes a 64 bit float to the packet.</summary>
/// <param name="value" type="Float64">The floating point value.</param>
/// <param name="maximum" type="Float64">The maximum value for the range starting at 0.</param>
/// <param name="bits" type="UInt">The number of bits to use.</param>
/// </signature>
/// <signature>
/// <summary>Writes a 64 bit float to the packet.</summary>
/// <param name="value" type="Float64">The floating point value.</param>
/// <param name="minimum" type="Float64">The inclusive floating point minimum.</param>
/// <param name="maximum" type="Float64">The inclusive floating point maximum.</param>
/// <param name="bits" type="UInt">The number of bits to use.</param>
/// </signature>
if (arguments.length == 1)
{
var arrayBuffer = new ArrayBuffer(8);
var float64 = new Float64Array(arrayBuffer);
var float64UInt32 = new Uint32Array(arrayBuffer);
float64[0] = value;
this.WriteUInt32(float64UInt32[0]);
this.WriteUInt32(float64UInt32[1]);
}
else
{
var minimum, maximum, bits;
if (arguments.length == 3)
{
minimum = 0;
maximum = arguments[1];
bits = arguments[2];
}
else
{
minimum = arguments[1];
maximum = arguments[2];
bits = arguments[3];
}
var uValue;
if (minimum < 0 && maximum > 0)
{
uValue = value == 0 ? 0 : Math.round((value - minimum) / (maximum - minimum) * ((0x1 << bits) - 2)) + 1;
}
else
{
uValue = Math.round((value - minimum) / (maximum - minimum) * ((0x1 << bits) - 1));
}
this.WriteUIntN(uValue, bits);
}
},
WriteString: function(value)
{
/// <signature>
/// <summary>Writes a string to the packet.</summary>
/// <param name="value" type="string">The string value.</param>
/// </signature>
/// <signature>
/// <summary>Writes a string to the packet using a codec to encode the string.</summary>
/// <param name="value" type="string">The string value.</param>
/// <param name="codec" type="UInt">The codec identifier for the binded codec state.</param>
/// </signature>
var codec = null;
if (arguments.length == 2)
{
codec = arguments[1];
}
this.WriteUInt16(value.length);
if (this.CodecState != undefined && codec != null)
{
var codecToUse = this.CodecState.Get(codec);
for (var index = 0; index < value.length; ++index)
{
codecToUse.Encode(this, value.charAt(index));
}
}
else
{
for (var index = 0; index < value.length; ++index)
{
this.WriteUIntN(value.charCodeAt(index), 7);
}
}
},
WritePacket: function(value)
{
/// <signature>
/// <summary>Writes a packet to the packet.</summary>
/// <param name="value" type="Packet">The packet.</param>
/// </signature>
value.BitIndex = 0;
for (var copyBits = 0; copyBits < value.MaximumBitIndex; copyBits += 32)
{
var bits = value.MaximumBitIndex - copyBits > 32 ? 32 : value.MaximumBitIndex - copyBits;
// Read n-bits from value.
var valueUIntN = 0;
var offset = value.bitIndex % 32;
valueUIntN |= value.buffer[value.bitIndex / 32 >> 0] << offset >>> 32 - bits;
if (offset > 32 - bits)
{
valueUIntN |= value.buffer[(value.bitIndex / 32 >> 0) + 1] >>> 64 - bits - offset;
}
value.bitIndex += bits;
valueUIntN >>>= 0;
// Write n-bits to the buffer.
offset = this.bitIndex % 32;
this.buffer[this.bitIndex / 32 >> 0] |= valueUIntN << 32 - bits >>> offset;
if (offset > 32 - bits)
{
this.buffer[(this.bitIndex / 32 >> 0) + 1] |= valueUIntN << 64 - bits - offset;
}
this.bitIndex += bits;
}
},
ReadEvent: function(callback)
{
/// <signature>
/// <summary>Reads an event from the packet.</summary>
/// <param name="callback" type="function(UInt8, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
if (this.bitIndex + 8 > this.maximumBitIndex)
{
this.error("Event expected");
}
this.ReadUInt8(callback);
},
ReadBool: function(callback)
{
/// <signature>
/// <summary>Reads a bool from the packet.</summary>
/// <param name="callback" type="function(bool, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
var value = false;
if (this.bitIndex + 1 > this.maximumBitIndex)
{
this.error("Bool expected");
}
else
{
value = (this.buffer[this.bitIndex / 32 >> 0] << this.bitIndex % 32 >>> 31) == 1;
this.bitIndex++;
callback(value, this);
}
},
ReadUInt8: function(callback)
{
/// <signature>
/// <summary>Reads an 8-bit unsigned integer from the packet.</summary>
/// <param name="callback" type="function(UInt8, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
if (this.bitIndex + 8 > this.maximumBitIndex)
{
this.error("UInt8 expected.");
}
else
{
var value = 0;
var offset = this.bitIndex % 32;
value |= this.buffer[this.bitIndex / 32 >> 0] << offset >>> 24;
if (offset > 24)
{
value |= this.buffer[(this.bitIndex / 32 >> 0) + 1] >>> 56 - offset;
}
this.bitIndex += 8;
callback(value);
}
},
ReadInt8: function(callback)
{
/// <signature>
/// <summary>Reads an 8-bit signed integer from the packet.</summary>
/// <param name="callback" type="function(Int8, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
if (this.bitIndex + 8 > this.maximumBitIndex)
{
this.error("Int8 expected.");
}
else
{
var value = 0;
var offset = this.bitIndex % 32;
value |= this.buffer[this.bitIndex / 32 >> 0] << offset >> 24;
if (offset > 24)
{
value |= this.buffer[(this.bitIndex / 32 >> 0) + 1] >>> 56 - offset;
}
this.bitIndex += 8;
callback(value, this);
}
},
ReadUInt16: function(callback)
{
/// <signature>
/// <summary>Reads a 16-bit unsigned integer from the packet.</summary>
/// <param name="callback" type="function(UInt16, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
if (this.bitIndex + 16 > this.maximumBitIndex)
{
this.error("UInt16 expected.");
}
else
{
var value = 0;
var offset = this.bitIndex % 32;
value |= this.buffer[this.bitIndex / 32 >> 0] << offset >>> 16;
if (offset > 16)
{
value |= this.buffer[(this.bitIndex / 32 >> 0) + 1] >>> 48 - offset;
}
this.bitIndex += 16;
callback(value, this);
}
},
ReadInt16: function(callback)
{
/// <signature>
/// <summary>Reads a 16-bit signed integer from the packet.</summary>
/// <param name="callback" type="function(Int16, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
if (this.bitIndex + 16 > this.maximumBitIndex)
{
this.error("Int16 expected.");
}
else
{
var value = 0;
var offset = this.bitIndex % 32;
value |= this.buffer[this.bitIndex / 32 >> 0] << offset >> 16;
if (offset > 16)
{
value |= this.buffer[(this.bitIndex / 32 >> 0) + 1] >>> 48 - offset;
}
this.bitIndex += 16;
callback(value, this);
}
},
ReadUInt32: function(callback)
{
/// <signature>
/// <summary>Reads a 32-bit unsigned integer from the packet.</summary>
/// <param name="callback" type="function(UInt32, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
if (this.bitIndex + 32 > this.maximumBitIndex)
{
this.error("UInt32 expected.");
}
else
{
var value = 0;
var offset = this.bitIndex % 32;
value |= this.buffer[this.bitIndex / 32 >> 0] << offset;
if (offset > 0)
{
value |= this.buffer[(this.bitIndex / 32 >> 0) + 1] >>> 32 - offset;
}
this.bitIndex += 32;
callback(value >>> 0, this);
}
},
ReadInt32: function(callback)
{
/// <signature>
/// <summary>Reads a 32-bit signed integer from the packet.</summary>
/// <param name="callback" type="function(Int32, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
if (this.bitIndex + 32 > this.maximumBitIndex)
{
this.error("Int32 expected.");
}
else
{
var value = 0;
var offset = this.bitIndex % 32;
value |= this.buffer[this.bitIndex / 32 >> 0] << offset;
if (offset > 0)
{
value |= this.buffer[(this.bitIndex / 32 >> 0) + 1] >>> 32 - offset;
}
this.bitIndex += 32;
callback(value, this);
}
},
ReadUIntN: function(bits, callback)
{
/// <signature>
/// <summary>Reads an n-bit unsigned integer from the packet.</summary>
/// <param name="bits" type="UInt">The number of bits used.</param>
/// <param name="callback" type="function(UInt, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
if (this.bitIndex + bits > this.maximumBitIndex)
{
this.error("UInt" + bits + " expected.");
}
else
{
var value = 0;
var offset = this.bitIndex % 32;
value |= this.buffer[this.bitIndex / 32 >> 0] << offset >>> 32 - bits;
if (offset > 32 - bits)
{
value |= this.buffer[(this.bitIndex / 32 >> 0) + 1] >>> 64 - bits - offset;
}
this.bitIndex += bits;
callback(value >>> 0, this);
}
},
ReadIntN: function(bits, callback)
{
/// <signature>
/// <summary>Reads an n-bit signed integer from the packet.</summary>
/// <param name="bits" type="UInt">The number of bits used.</param>
/// <param name="callback" type="function(Int, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
if (this.bitIndex + bits > this.maximumBitIndex)
{
this.error("Int" + bits + " expected.");
}
else
{
var value = 0;
var offset = this.bitIndex % 32;
value |= this.buffer[this.bitIndex / 32 >> 0] << offset >> 32 - bits;
if (offset > 32 - bits)
{
value |= this.buffer[(this.bitIndex / 32 >> 0) + 1] >>> 64 - bits - offset;
}
this.bitIndex += bits;
callback(value >> 0, this);
}
},
ReadUInt: function()
{
/// <signature>
/// <summary>Reads an unsigned integer from the packet.</summary>
/// <param name="maximum" type="UInt">The inclusive unsigned integer maximum used.</param>
/// <param name="callback" type="function(UInt, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
/// <signature>
/// <summary>Reads an unsigned integer from the packet.</summary>
/// <param name="minimum" type="UInt">The inclusive unsigned integer minimum used.</param>
/// <param name="maximum" type="UInt">The inclusive unsigned integer maximum used.</param>
/// <param name="callback" type="function(UInt, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
var minimum, maximum, callback;
if (arguments.length == 2)
{
minimum = 0;
maximum = arguments[0];
callback = arguments[1];
}
else
{
minimum = arguments[0];
maximum = arguments[1];
callback = arguments[2];
}
var bits = this.Log2(maximum - minimum);
if (this.bitIndex + bits > this.maximumBitIndex)
{
this.error("UInt" + bits + " with range [" + minimum + ", " + maximum + "] expected.");
}
else
{
this.ReadUIntN(bits, function(value)
{
callback(value + minimum, this);
});
}
},
ReadInt: function(minimum, maximum, callback)
{
/// <signature>
/// <summary>Reads a signed integer from the packet.</summary>
/// <param name="maximum" type="Int">The inclusive signed integer maximum.</param>
/// <param name="callback" type="function(Int, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
/// <signature>
/// <summary>Reads a signed integer from the packet.</summary>
/// <param name="minimum" type="Int">The inclusive signed integer minimum.</param>
/// <param name="maximum" type="Int">The inclusive signed integer maximum.</param>
/// <param name="callback" type="function(Int, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
var minimum, maximum, callback;
if (arguments.length == 2)
{
minimum = 0;
maximum = arguments[0];
callback = arguments[1];
}
else
{
minimum = arguments[0];
maximum = arguments[1];
callback = arguments[2];
}
var bits = this.Log2(maximum - minimum);
if (this.bitIndex + bits > this.maximumBitIndex)
{
this.error("Int" + bits + " with range [" + minimum + ", " + maximum + "] expected.");
}
else
{
this.ReadUIntN(bits, function(value)
{
callback(value + minimum, this);
});
}
},
ReadVariableWidthUInt: function(bits, callback)
{
/// <signature>
/// <summary>Reads an unsigned integer to the packet using a variable width encoding.</summary>
/// <param name="value" type="UInt">The unsigned integer value.</param>
/// <param name="bits" type="UInt">The number of bits to use for the sequence. Choose a bits value that represents the number of bits to hold the average value.</param>
/// <param name="callback" type="function(UInt, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
var bitCount = 0;
var continuationBitValue;
do
{
continuationBitValue = false;
this.ReadBool(function(continuationBit)
{
continuationBitValue = continuationBit;
bitCount += bits;
});
}
while (continuationBitValue && bitCount < 32);
if (bitCount > 32)
{
this.error("VariableWidthUInt Expected.");
}
else
{
this.ReadUIntN(bitCount, callback);
}
},
ReadVariableWidthInt: function(bits, callback)
{
/// <signature>
/// <summary>Read a signed integer to the packet using a variable width encoding.</summary>
/// <param name="value" type="Int">The signed integer value.</param>
/// <param name="bits" type="UInt">The number of bits to use for the sequence. Choose a bits value that represents the number of bits to hold the average value.</param>
/// <param name="callback" type="function(Int, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
var bitCount = 0;
var continuationBitValue;
do
{
continuationBitValue = false;
this.ReadBool(function(continuationBit)
{
continuationBitValue = continuationBit;
bitCount += bits;
});
}
while (continuationBitValue && bitCount < 32);
if (bitCount > 32)
{
this.error("VariableWidthInt Expected.");
}
else
{
this.ReadIntN(bitCount, callback);
}
},
ReadFloat32: function(callback)
{
/// <signature>
/// <summary>Reads a 32 floating point from the packet.</summary>
/// <param name="callback" type="function(Float32, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
/// <signature>
/// <summary>Reads a 32 floating point from the packet.</summary>
/// <param name="maximum" type="Float32">The inclusive floating point maximum.</param>
/// <param name="bits" type="UInt">The number of bits used.</param>
/// <param name="callback" type="function(Float32, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
/// <signature>
/// <summary>Writes a 32 bit float to the packet.</summary>
/// <param name="minimum" type="Float32">The inclusive floating point minimum.</param>
/// <param name="maximum" type="Float32">The inclusive floating point maximum.</param>
/// <param name="bits" type="UInt">The number of bits used.</param>
/// <param name="callback" type="function(Float32, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
if (arguments.length == 1)
{
if (this.bitIndex + 32 > this.maximumBitIndex)
{
this.error("Float32 expected.");
}
else
{
var arrayBuffer = new ArrayBuffer(4);
var float32 = new Float32Array(arrayBuffer);
var float32UInt32 = new Uint32Array(arrayBuffer);
this.ReadUInt32(function(value)
{
float32UInt32[0] = value;
});
callback(float32[0], this);
}
}
else
{
var minimum, maximum, bits, callback;
if (arguments.length == 3)
{
minimum = 0;
maximum = arguments[0];
bits = arguments[1];
callback = arguments[2];
}
else
{
minimum = arguments[0];
maximum = arguments[1];
bits = arguments[2];
callback = arguments[3];
}
if (this.bitIndex + bits > this.maximumBitIndex)
{
this.error("Float32 expected.");
}
else
{
this.ReadUIntN(bits, function(value)
{
if (minimum < 0 && maximum > 0)
{
callback(value == 0 ? 0 : (value - 1) / ((0x1 << bits) - 2) * (maximum - minimum) + minimum, this);
}
else
{
callback(value / ((0x1 << bits) - 1) * (maximum - minimum) + minimum, this);
}
});
}
}
},
ReadFloat64: function(callback)
{
/// <signature>
/// <summary>Reads a 64 floating point from the packet.</summary>
/// <param name="callback" type="function(Float64, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
/// <signature>
/// <summary>Reads a 64 floating point from the packet.</summary>
/// <param name="maximum" type="Float64">The inclusive floating point maximum.</param>
/// <param name="bits" type="UInt">The number of bits to use.</param>
/// <param name="callback" type="function(Float64, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
/// <signature>
/// <summary>Writes a 64 bit float to the packet.</summary>
/// <param name="minimum" type="Float64">The inclusive floating point minimum.</param>
/// <param name="maximum" type="Float64">The inclusive floating point maximum.</param>
/// <param name="bits" type="UInt">The number of bits to use.</param>
/// <param name="callback" type="function(Float64, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
if (arguments.length == 1)
{
if (this.bitIndex + 64 > this.maximumBitIndex)
{
this.error("Float64 expected.");
}
else
{
var arrayBuffer = new ArrayBuffer(8);
var float64 = new Float64Array(arrayBuffer);
var float64UInt32 = new Uint32Array(arrayBuffer);
this.ReadUInt32(function(value)
{
float64UInt32[0] = value;
});
this.ReadUInt32(function(value)
{
float64UInt32[1] = value;
});
callback(float64[0], this);
}
}
else
{
var minimum, maximum, bits, callback;
if (arguments.length == 3)
{
minimum = 0;
maximum = arguments[0];
bits = arguments[1];
callback = arguments[2];
}
else
{
minimum = arguments[0];
maximum = arguments[1];
bits = arguments[2];
callback = arguments[3];
}
if (this.bitIndex + bits > this.maximumBitIndex)
{
this.error("Float64 expected.");
}
else
{
this.ReadUIntN(bits, function(value)
{
if (minimum < 0 && maximum > 0)
{
callback(value == 0 ? 0 : (value - 1) / ((0x1 << bits) - 2) * (maximum - minimum) + minimum, this);
}
else
{
callback(value / ((0x1 << bits) - 1) * (maximum - minimum) + minimum, this);
}
});
}
}
},
ReadString: function()
{
/// <signature>
/// <summary>Reads a string from the packet.</summary>
/// <param name="callback" type="function(String, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
/// <signature>
/// <summary>Reads a string from the packet.</summary>
/// <param name="codec" type="UInt">The codec identifier for the binded codec state.</param>
/// <param name="callback" type="function(String, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
var codec = null;
var callback;
if (arguments.length == 1)
{
callback = arguments[0];
}
else
{
codec = arguments[0];
callback = arguments[1];
}
var value = "";
var packet = this;
this.ReadUInt16(function(length)
{
if (packet.CodecState != undefined && codec != null)
{
var codecToUse = packet.CodecState.Get(codec);
for (var index = 0; index < length; ++index)
{
codecToUse.Decode(packet, function(character)
{
value += character;
}, packet.error);
}
}
else
{
for (var index = 0; index < length; ++index)
{
packet.ReadUIntN(7, function(charCode)
{
value += string.fromCharCode(charCode);
});
}
}
callback(value, this);
});
},
ReadPacket: function(callback)
{
/// <signature>
/// <summary>Reads a packet from the packet.</summary>
/// <param name="callback" type="function(Packet, [Packet])">A callback with the result. Only executes if the read succeeds.</param>
/// </signature>
if (this.error == null) return;
this.ReadUIntN(this.headerSize, function(maximumBitIndex, packet)
{
if (packet.bitIndex + maximumBitIndex - packet.headerSize > packet.maximumBitIndex)
{
packet.error("Packet expected.");
}
else
{
var bitIndex = packet.BitIndex;
var value = Packet.Create(packet.Buffer.buffer, packet.error);
if (packet.codecState != null)
{
value.CodecState = packet.codecState;
}
value.BitIndex = packet.bitIndex;
value.MaximumBitIndex = packet.bitIndex + maximumBitIndex - packet.headerSize;
packet.bitIndex += maximumBitIndex - packet.headerSize;
callback(value, packet);
}
});
},
// Read methods
Read: function()
{
/// <signature>
/// <summary>Reads a block of data with one callback.</summary>
/// <param name="readOperations" type="&quot;datatype&quot;, [&quot;datatype&quot;, arguments...]...">A datatype string or array starting with a datatype string followed by arguments. As an example to use ReadUInt8 you would use the datatype string "UInt8".</param>
/// <param name="callback" type="function(datatype..., [Packet])">A callback with the results. Only executes if the read succeeds.</param>
/// </signature>
var values = [];
for (var i = 0; i < arguments.length - 1; ++i)
{
if (typeof arguments[i] == "string")
{
this["Read" + arguments[i]](function(value)
{
values.push(value);
});
}
else
{
arguments[i].push(function(value)
{
values.push(value);
});
this["Read" + arguments[i].shift()].apply(this, arguments[i]);
}
}
values.push(this);
arguments[arguments.length - 1].apply(this, values);
},
ReadConditional: function()
{
/// <signature>
/// <summary>Reads a boolean and executes read operations on true or false.</summary>
/// <param name="trueReadOperations" type="&quot;datatype&quot;, [&quot;datatype&quot;, arguments...]...">A datatype string or array starting with a datatype string followed by arguments. As an example to use ReadUInt8 you would use the datatype string "UInt8".</param>
/// <param name="trueCallback" type="function(datatype..., [Packet])">A callback with the results. Only executes if the read succeeds.</param>
/// </signature>
/// <signature>
/// <summary>Reads a boolean and executes read operations on true or false.</summary>
/// <param name="trueReadOperations" type="&quot;datatype&quot;, [&quot;datatype&quot;, arguments...]...">A datatype string or array starting with a datatype string followed by arguments. As an example to use ReadUInt8 you would use the datatype string "UInt8".</param>
/// <param name="trueCallback" type="function(datatype..., [Packet])">A callback with the results. Only executes if the read succeeds.</param>
/// <param name="falseReadOperations" type="&quot;datatype&quot;, [&quot;datatype&quot;, arguments...]...">A datatype string or array starting with a datatype string followed by arguments. As an example to use ReadUInt8 you would use the datatype string "UInt8".</param>
/// <param name="falseCallback" type="function(datatype..., [Packet])">A callback with the results. Only executes if the read succeeds.</param>
/// </signature>
var conditionalArguments = arguments;
this.ReadBool(function(conditional, packet)
{
if (conditional)
{
var values = [];
for (var i = 0;; ++i)
{
if (typeof conditionalArguments[i] == "string")
{
packet["Read" + conditionalArguments[i]](function(value)
{
values.push(value);
});
}
else if (Array.isArray(conditionalArguments[i]))
{
conditionalArguments[i].push(function(value)
{
values.push(value);
});
packet["Read" + conditionalArguments[i].shift()].apply(packet, conditionalArguments[i]);
}
else
{
values.push(packet);
conditionalArguments[i].apply(packet, values);
break;
}
}
}
else
{
var i = 0;
for (;; ++i)
{
if (typeof conditionalArguments[i] == "function")
{
i++;
break;
}
}
var values = [];
for (; i < conditionalArguments.length - 1; ++i)
{
if (typeof conditionalArguments[i] == "string")
{
packet["Read" + conditionalArguments[i]](function(value)
{
values.push(value);
});
}
else if (Array.isArray(conditionalArguments[i]))
{
conditionalArguments[i].push(function(value)
{
values.push(value);
});
packet["Read" + conditionalArguments[i].shift()].apply(packet, conditionalArguments[i]);
}
}
values.push(packet);
conditionalArguments[conditionalArguments.length - 1].apply(packet, values);
}
});
}
}
/// <var>A state manager for compression codecs. Each session requires one if codec compression is to be used.</var>
var CodecState =
{
Create: function()
{
/// <signature>
/// <summary>Constructor.</summary>
/// <returns type="CodecState" />
/// </signature>
var codecState = Object.create(this,
{
codecs:
{
value: [],
writable: true
}
});
return codecState;
},
Add: function(identifier, codec)
{
/// <signature>
/// <summary>Registers an identifier to a new codec.</summary>
/// <param name="identifier" type="UInt">An unsigned integer enumeration that signifies what the codec is used for. For example Codecs.Chat.</param>
/// <param name="codec" type="Codec">A codec that supports Encode and Decode operations for strings. AdaptiveCodec is an example.</param>
/// </signature>
this.codecs[identifier] = codec;
},
Get: function(identifier)
{
return this.codecs[identifier];
}
}
/// <var>An implementation of the adaptive huffman algorithm for string compression. Supports ASCII only.</var>
var AdaptiveCodec =
{
Create: function()
{
var adaptiveCodec = Object.create(this,
{
characterToNode:
{
value: [],
writable: true
},
root:
{
value: null,
writable: true
},
newCharacterNode:
{
value: null,
writable: true
},
tail:
{
value: null,
writable: true
},
dummy:
{
value:
{
character: 0,
weight: 0,
code: -1,
parent: null,
left: null,
right: null,
previous: null,
next: null
},
writable: true
}
});
adaptiveCodec.root =
{
character: 0,
weight: 0,
code: -1,
parent: null,
left: adaptiveCodec.dummy,
right: adaptiveCodec.dummy,
previous: null,
next: null
}
adaptiveCodec.newCharacterNode = adaptiveCodec.tail = adaptiveCodec.root;
for (var i = 32; i <= 126 + 1; ++i)
{
adaptiveCodec.characterToNode.push(null);
}
return adaptiveCodec;
},
Encode: function(packet, character)
{
// CharCode is between 32 and 126
var currentParent;
var characterIndex = character.charCodeAt(0) - 31;
var newCharacter = this.characterToNode[characterIndex] == null;
if (newCharacter)
{
// New character in stream
this.GetCode(this.newCharacterNode, function(codePart)
{
packet.WriteBool(codePart);
});
packet.WriteUIntN(characterIndex, 7);
this.AddNewCharacter(characterIndex);
}
else
{
// Existing character in stream
this.GetCode(this.characterToNode[characterIndex], function(codePart)
{
packet.WriteBool(codePart);
});
this.AddExistingCharacter(characterIndex);
}
},
Decode: function(packet, callback, error)
{
// CharCode is between 32 and 126
var characterIndex = 0;
var newCharacter = false;
var node = this.root;
while (!packet.Error)
{
if (node == null)
{
error();
return;
}
newCharacter = node === this.newCharacterNode;
if (newCharacter)
{
// New character in stream
characterIndex = 0;
packet.ReadUIntN(7, function(value)
{
characterIndex = value;
});
callback(String.fromCharCode(characterIndex + 31));
this.AddNewCharacter(characterIndex);
break;
}
else if (node.character != 0)
{
// Existing character in stream
characterIndex = node.character;
callback(String.fromCharCode(characterIndex + 31));
this.AddExistingCharacter(characterIndex);
break;
}
else
{
// Branch node
packet.ReadBool(function(value)
{
if (value)
{
// Right
node = node.right;
}
else
{
// Left
node = node.left;
}
});
}
}
},
AddNewCharacter: function(characterIndex)
{
var newLeftNode =
{
character: 0,
weight: 0,
code: 0,
parent: this.newCharacterNode,
left: this.dummy,
right: this.dummy,
previous: null,
next: null
};
var newRightNode =
{
character: characterIndex,
weight: 1,
code: 1,
parent: this.newCharacterNode,
left: this.dummy,
right: this.dummy,
previous: this.tail,
next: newLeftNode
};
this.tail.next = newLeftNode.previous = newRightNode;
this.tail = newLeftNode;
this.characterToNode[characterIndex] = newRightNode;
this.newCharacterNode.left = newLeftNode;
this.newCharacterNode.right = newRightNode;
var currentParent = this.newCharacterNode;
this.newCharacterNode = newLeftNode;
while (currentParent != null)
{
currentParent.weight++;
var swap = null;
for (var node = currentParent.previous; node != null && node.weight == currentParent.weight; node = node.previous)
{
if (node !== currentParent.parent)
{
swap = node;
}
}
if (swap != null)
{
// Swap the character
var tempCharacter = swap.character;
swap.character = currentParent.character;
currentParent.character = tempCharacter;
// Swap character to node references
this.characterToNode[currentParent.character] = currentParent;
this.characterToNode[swap.character] = swap;
// Swap left child
var tempLeftChild = swap.left;
swap.left = currentParent.left;
currentParent.left = tempLeftChild;
// Swap right child
var tempRightChild = swap.right;
swap.right = currentParent.right;
currentParent.right = tempRightChild;
// Update the left and right parents to point to their new parent node
currentParent.left.parent = currentParent;
currentParent.right.parent = currentParent;
swap.left.parent = swap;
swap.right.parent = swap;
}
for (var node = currentParent; node.previous != null && node.weight > node.previous.weight; node = node.previous)
{
if (node.previous.previous != null) node.previous.previous.next = node;
var tempPrevious = node.previous.previous;
node.previous.previous = node;
node.previous.next = node.next;
if (node.next != null) node.next.previous = node.previous;
node.next = node.previous;
node.previous = tempPrevious;
}
currentParent = currentParent.parent;
}
},
AddExistingCharacter: function(characterIndex)
{
var currentParent = this.characterToNode[characterIndex];
currentParent.weight--;
var lastCurrentParent = currentParent;
while (currentParent != null)
{
currentParent.weight++;
var swap = null;
for (var node = currentParent.previous; node != null && node.weight == currentParent.weight; node = node.previous)
{
if (node !== currentParent.parent)
{
swap = node;
}
}
if (swap != null)
{
// Swap the character
var tempCharacter = swap.character;
swap.character = currentParent.character;
currentParent.character = tempCharacter;
// Swap character to node references
this.characterToNode[currentParent.character] = currentParent;
this.characterToNode[swap.character] = swap;
// Swap left child
var tempLeftChild = swap.left;
swap.left = currentParent.left;
currentParent.left = tempLeftChild;
// Swap right child
var tempRightChild = swap.right;
swap.right = currentParent.right;
currentParent.right = tempRightChild;
// Update the left and right parents to point to their new parent node
currentParent.left.parent = currentParent;
currentParent.right.parent = currentParent;
swap.left.parent = swap;
swap.right.parent = swap;
for (var node = swap; node.previous != null && node.weight > node.previous.weight; node = node.previous)
{
if (node.previous.previous != null) node.previous.previous.next = node;
var tempPrevious = node.previous.previous;
node.previous.previous = node;
node.previous.next = node.next;
if (node.next != null) node.next.previous = node.previous;
node.next = node.previous;
node.previous = tempPrevious;
}
lastCurrentParent = currentParent;
currentParent = swap.parent;
}
else
{
for (var node = currentParent; node.previous != null && node.weight > node.previous.weight; node = node.previous)
{
if (node.previous.previous != null) node.previous.previous.next = node;
var tempPrevious = node.previous.previous;
node.previous.previous = node;
node.previous.next = node.next;
if (node.next != null) node.next.previous = node.previous;
node.next = node.previous;
node.previous = tempPrevious;
}
lastCurrentParent = currentParent;
currentParent = currentParent.parent;
}
lastCurrentParent.weight++;
}
},
GetCode: function(node, appendCodePart)
{
if (node.parent != null)
{
this.GetCode(node.parent, appendCodePart);
appendCodePart(node.code);
}
}
}
module.exports = { Packet: Packet, CodecState: CodecState, AdaptiveCodec: AdaptiveCodec };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment