Implement ATEN AST2100 (0x59) support

Tweak heuristic used to identify iKVM servers; it now correctly detects all
of the SuperMicro hardware that we've tested it against.

Modify test case for TightVNC to avoid triggering iKVM heuristic.
This commit is contained in:
Kevin Kelley 2017-01-11 23:43:50 -08:00
parent 9ab402d95b
commit 0d048acaca
14 changed files with 1976 additions and 28 deletions

View File

@ -141,7 +141,8 @@ WebSockets to TCP socket proxy. There is a python proxy included
* Original Logo : Michael Sersen * Original Logo : Michael Sersen
* tight encoding : Michael Tinglof (Mercuri.ca) * tight encoding : Michael Tinglof (Mercuri.ca)
* pixel format conversion : [Alexander Clouter](http://www.digriz.org.uk/) * pixel format conversion : [Alexander Clouter](http://www.digriz.org.uk/)
* ATEN iKVM support : [Alexander Clouter](http://www.digriz.org.uk/) * ATEN iKVM "HARMON" (0x59) support : [Alexander Clouter](http://www.digriz.org.uk/)
* ATEN iKVM "AST2100" (0x57) support : [Kevin Kelley](https://github.com/kelleyk)
* Included libraries: * Included libraries:
* as3crypto : Henri Torgemane (code.google.com/p/as3crypto) * as3crypto : Henri Torgemane (code.google.com/p/as3crypto)

View File

@ -70,7 +70,9 @@ var UI;
WebUtil.load_scripts( WebUtil.load_scripts(
{'core': ["base64.js", "websock.js", "des.js", "input/keysymdef.js", {'core': ["base64.js", "websock.js", "des.js", "input/keysymdef.js",
"input/xtscancodes.js", "input/util.js", "input/devices.js", "input/xtscancodes.js", "input/util.js", "input/devices.js",
"display.js", "inflator.js", "rfb.js", "input/keysym.js"]}); "display.js", "inflator.js", "rfb.js", "input/keysym.js",
"ast2100/ast2100const.js", "ast2100/ast2100util.js",
"ast2100/ast2100idct.js", "ast2100/ast2100.js"]});
window.onscriptsload = function () { UI.load(); }; window.onscriptsload = function () { UI.load(); };
/* [end skip-as-module] */ /* [end skip-as-module] */

49
core/ast2100/README.md Normal file
View File

@ -0,0 +1,49 @@
# AST2100 (0x57) encoding support
These are notes to accompany the ATEN iKVM "AST2100" (0x59) encoding implementation that this branch contains.
This implementation is the product of clean-room reverse engineering (that is, I am not and have never been subject to
nondisclosure agreements, nor have I had access to proprietary information, related to the subject matter of this
project).
(c) Copyright 2015-2017 Kevin Kelley <kelleyk@kelleyk.net>
### Current problems / limitations (aka "TODOs")
- Especially on lower quality settings, you will notice that the picture is not as clear as what the ATEN iKVM client
will show you (using the same settings). I'm aware of this issue and intend to fix it. Disabling chroma
subsampling will allow the encoder to use VQ when there are few colors (e.g. when you are looking at a terminal); VQ
data doesn't have the same quality issue.
- The code could stand to be much better-tested.
- The JavaScript files related to the AST2100 decoder are loaded even when noVNC does not use the decoder. It would
be nice to lazy-load them only when they are necessary.
- Lots of globals (functions, constants, etc.) are exposed. Some quick refactoring could tuck the majority of them
away to avoid cluttering the namespace.
### Profiling
- For some reason, when I use blitImageData() (with the noVNC render queue disabled), that function shows up as the
"heaviest" function in Chrome's CPU profiler, even though the function is doing nothing other than evaluating a
branch condition or two and then calling _rgbxImageData(). When I call _rgbxImageData() directly, then
putImageData() (the Canvas method that's actually doing the heavy lifting) is correctly shown as the "heaviest"
function.
- Profiler oddness aside, putImageData() is overwhelmingly the dominant cost; it seems to occupy 75-85% of the CPU
time that noVNC uses. There are plenty of places that we could get small performance improvements in Ast2100Decoder
(and elsewhere in noVNC) but they seem unlikely to have a worthwhile impact, given that fact.
### About the implementation
- One large, remaining inefficiency is the several times that image data is copied around before being blitted. The
Ast2100Decoder class generates as output 256-element arrays (representing 64 pixels as (R,G,B,A) 4-tuples). This is
exactly what winds up in the ImageData object that is eventually passed to putImageData(); we could just have the
decoder write its output directly into those arrays if we wanted.
### Performance questions
- Is it faster to call putImageData() fewer times with larger buffers? We could collect groups of blocks (or even an
entire frame) and then call putImageData() once. (Of course, this would require redrawing unchanged regions every
frame, too.)

482
core/ast2100/ast2100.js Normal file
View File

@ -0,0 +1,482 @@
"use strict";
/*global DCTSIZE, DCTSIZE2, JpegHuffmanTable, BitStream, AST2100IDCT, AAN_IDCT_SCALING_FACTORS, ZIGZAG_ORDER */
/*global ATEN_QT_LUMA, ATEN_QT_CHROMA, TABLE_CLASS_AC, TABLE_CLASS_DC */
/*global BITS_AC_LUMA, BITS_AC_CHROMA, BITS_DC_LUMA, BITS_DC_CHROMA, HUFFVAL_AC_LUMA, HUFFVAL_AC_CHROMA, HUFFVAL_DC_LUMA, HUFFVAL_DC_CHROMA */
/*global inRangeIncl, isEmpty */
/*global fmt_u8a, fmt_u8, fmt_u16 */
/*
(c) Copyright 2015-2017 Kevin Kelley <kelleyk@kelleyk.net>.
This implementation is the product of clean-room reverse engineering (that is, I
am not and have never been subject to enondisclosure agreements, nor have I had
access to proprietary information, related to the subject matter of this
project).
*/
var verboseDebug = false;
var verboseMcuCount = false;
var traceUpdates = false;
var verboseStats = false;
var verboseVideoSettings = true;
var Ast2100Decoder;
(function () {
"use strict";
Ast2100Decoder = function (defaults) {
this._blitCallback = defaults.blitCallback;
this._frame_width = defaults.width;
this._frame_height = defaults.height;
if (!this._frame_width || !this._frame_height)
throw 'Missing required parameter: width, height';
// Either 444u or 422u (though the "422" mode is really 4:2:0, where
// chroma is subsampled by a factor of two in each direction).. Applies
// only to JPEG-ish blocks, not to VQ blocks; but VQ blocks seem to only
// appear in 4:4:4 color mode.
this.subsamplingMode = -1;
this.quantTables = [new Int32Array(64), new Int32Array(64)];
this._loadedQuantTables = [-1, -1];
this.huffTables = [
[new JpegHuffmanTable({bits: BITS_DC_LUMA, huffval: HUFFVAL_DC_LUMA}),
new JpegHuffmanTable({bits: BITS_DC_CHROMA, huffval: HUFFVAL_DC_CHROMA})],
[new JpegHuffmanTable({bits: BITS_AC_LUMA, huffval: HUFFVAL_AC_LUMA}),
new JpegHuffmanTable({bits: BITS_AC_CHROMA, huffval: HUFFVAL_AC_CHROMA})],
];
this._scan_components = [
{huffTableSelectorDC: 0, huffTableSelectorAC: 0}, // Y
{huffTableSelectorDC: 1, huffTableSelectorAC: 1}, // Cb
{huffTableSelectorDC: 1, huffTableSelectorAC: 1} // Cr
];
this._scan_prev_dc = [0, 0, 0];
this._mcuPosX = 0;
this._mcuPosY = 0;
this._initializeVq();
// Allocate memory here once and then re-use it. These buffers store
// data after entropy decoding (Huffman, zigzag) and before we dequant
// and apply the IDCT.
this._tmpBufY = [
new Int16Array(DCTSIZE2),
new Int16Array(DCTSIZE2),
new Int16Array(DCTSIZE2),
new Int16Array(DCTSIZE2)];
this._tmpBufCb = new Int16Array(DCTSIZE2);
this._tmpBufCr = new Int16Array(DCTSIZE2);
// These buffers store IDCT output.
this._componentBufY = [
new Uint8Array(DCTSIZE2),
new Uint8Array(DCTSIZE2),
new Uint8Array(DCTSIZE2),
new Uint8Array(DCTSIZE2)];
this._componentBufCb = new Uint8Array(DCTSIZE2);
this._componentBufCr = new Uint8Array(DCTSIZE2);
// The final RGB output goes here; noVNC expects each group of four
// elements to represent one RGBA pixel. this._outputBuf = new
// Uint8Array(DCTSIZE2 * 4);
this._outputBuf = new Uint8Array(DCTSIZE2 * 4);
};
Ast2100Decoder.prototype = {
// TODO: Could/should remove this indirection.
_idct: function (quant_table, data_unit, dstBuf) {
if (!quant_table)
throw 'Required argument missing: quant_table';
if (!data_unit)
throw 'Required argument missing: data_unit';
if (!dstBuf)
throw 'Required argument missing: dstBuf';
return AST2100IDCT.idct_fixed_aan(quant_table, data_unit, dstBuf);
},
_getMcuSize: function () {
return {444: 8, 422: 16}[this.subsamplingMode];
},
// Bakes in C(u)*C(v) and the cosine terms (from the IDCT formula).
_loadQuantTable: function (slot, srcTable) {
for (var y = 0; y < 8; ++y) {
for (var x = 0; x < 8; ++x) {
this.quantTables[slot][y*8+x] = ~~(srcTable[y*8+x] * AAN_IDCT_SCALING_FACTORS[x] * AAN_IDCT_SCALING_FACTORS[y] * 65536.0);
}
}
},
_initializeVq: function () {
// These colors are in YCbCr, so they're just black, white, and two
// shades of grey.
this._vqCodewordLookup = [0, 1, 2, 3];
this._vqCodebook = [
[0x00, 0x80, 0x80],
[0xFF, 0x80, 0x80],
[0x80, 0x80, 0x80],
[0xC0, 0x80, 0x80]
];
},
// Update our position variables to point at the next MCU.
_advancePosition: function () {
var mcuSize = this._getMcuSize();
var widthInMcus = ~~(this._frame_width / mcuSize);
if (this._frame_width % mcuSize != 0)
widthInMcus += 1;
var heightInMcus = ~~(this._frame_height / mcuSize);
if (this._frame_height % mcuSize != 0)
heightInMcus += 1;
this._mcuPosX += 1;
if (this._mcuPosX >= widthInMcus) {
this._mcuPosX = 0;
this._mcuPosY += 1;
}
if (this._mcuPosY >= heightInMcus) {
this._mcuPosY = 0;
}
},
// Change the frame's size.
setSize: function (width, height) {
if (this._frame_width != width || this._frame_height != height)
Util.Debug('Ast2100Decoder: frame height changed to '+width+'x'+height);
this._frame_width = width;
this._frame_height = height;
},
decode: function (data) {
var mcuIdx = 0;
if (verboseStats) {
var blockTypeCounter = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // length 16
}
// Reset state that must be reset between frames.
this._scan_prev_dc = [0, 0, 0];
this._mcuPosX = 0;
this._mcuPosY = 0;
// First four bytes.
var quantTableSelectorLuma = data[0]; // 0 <= x <= 0xB
var quantTableSelectorChroma = data[1]; // 0 <= x <= 0xB
var subsamplingMode = (data[2] << 8) | data[3]; // 422u or 444u
if (this.subsamplingMode != subsamplingMode) {
if (verboseVideoSettings)
console.log('decode(): new subsampling mode: '+subsamplingMode);
this.subsamplingMode = subsamplingMode;
}
// The remainder of the stream is byte-swapped in four-byte chunks.
// BitStream takes care of this.
this._stream = new BitStream({data: data});
this._stream.skip(16);
this._stream.skip(16); // do this in two parts because bits must be < 32; thanks JavaScript!
if (quantTableSelectorLuma != this._loadedQuantTables[0]) {
if (!inRangeIncl(quantTableSelectorLuma, 0, 0xB))
throw 'Out-of-range selector for luma quant table: ' + quantTableSelectorLuma.toString(16);
if (verboseVideoSettings)
console.log('decode(): loading new luma quant table: '+fmt_u8(quantTableSelectorLuma));
this._loadQuantTable(0, ATEN_QT_LUMA[quantTableSelectorLuma]);
this._loadedQuantTables[0] = quantTableSelectorLuma;
}
if (quantTableSelectorChroma != this._loadedQuantTables[1]) {
if (!inRangeIncl(quantTableSelectorChroma, 0, 0xB))
throw 'Out-of-range selector for chroma quant table: ' + quantTableSelectorChroma.toString(16);
if (verboseVideoSettings)
console.log('decode(): loading new chroma quant table: '+fmt_u8(quantTableSelectorChroma));
this._loadQuantTable(1, ATEN_QT_CHROMA[quantTableSelectorChroma]);
this._loadedQuantTables[1] = quantTableSelectorChroma;
}
if (this.subsamplingMode != 422 && this.subsamplingMode != 444)
throw 'Unexpected value for subsamplingMode: 0x' + fmt_u16(this.subsamplingMode);
// The remainder of the stream is byte-swapped in four-byte chunks. BitStream takes care of this.
this._stream = new BitStream({data: data});
this._stream.skip(16);
this._stream.skip(16); // do this in two parts because bits must be < 32; thanks JavaScript!
while (true) {
var controlFlag = this._stream.read(4); // uint4
if (verboseStats)
++blockTypeCounter[controlFlag];
if (verboseMcuCount) {
console.log('MCU #' + mcuIdx + '- Control flag: ' + controlFlag.toString(16));
console.log(' stream pos = ' + this._stream.getPos());
}
if (controlFlag == 0 || controlFlag == 4 || controlFlag == 8 || controlFlag == 0xC) {
// JPEG-ish (DCT-compressed) data.
if (controlFlag == 8 || controlFlag == 0xC) {
this._mcuPosX = this._stream.read(8); // uint8
this._mcuPosY = this._stream.read(8); // uint8
if (traceUpdates)
console.log("decode(): read new MCU pos: (0x"+fmt_u8(this._mcuPosX)+",0x"+fmt_u8(this._mcuPosY)+")");
}
if (controlFlag == 4 || controlFlag == 0xC) {
// Haven't seen traffic where this feature is used yet.
throw 'Unexpected control flag: alternate quant table';
}
// Since we always have 4:2:2 chroma subsampling on, we'll read 6 blocks (4 Y, 1 Cr, 1 Cb) and
// produce a block of 16x16 pixels.
this._parseMcu();
} else if (inRangeIncl(controlFlag, 5, 7) || inRangeIncl(controlFlag, 0xD, 0xF)) {
// VQ-compressed data.
if (controlFlag >= 0xD) {
this._mcuPosX = this._stream.read(8); // uint8
this._mcuPosY = this._stream.read(8); // uint8
if (traceUpdates)
console.log("decode(): read new MCU pos: (0x"+fmt_u8(this._mcuPosX)+",0x"+fmt_u8(this._mcuPosY)+")");
}
var codewordSize = (controlFlag & 7) - 5; // 0 <= codewordSize <= 2
this._parseVqBlock(codewordSize);
} else if (controlFlag == 9) {
// Done with frame!
break;
} else {
throw 'Unexpected control flag: unknown value 0x'+fmt_u8(controlFlag);
}
++mcuIdx;
}
// The 0x9 "end-of-frame" block is included in this count.
if (traceUpdates)
console.log("decode(): finished after "+mcuIdx+" blocks");
if (verboseStats) {
var counts = {};
for (var i = 0; i < 16; ++i)
if (i != 9 && blockTypeCounter[i] > 0)
counts[i] = blockTypeCounter[i];
if (!isEmpty(counts))
console.log(counts);
}
},
// - Always 8x8, since chroma subsampling does not apply to
// VQ-compressed data. (As a consequence, VQ blocks only seem to
// appear when DCT chroma subsampling is disabled; that is, in "444"
// mode.)
// - Reads 64 * codewordSize bits from the input stream.
_parseVqBlock: function (codewordSize) {
var mcuSize = this._getMcuSize();
if (mcuSize != 8)
throw 'Unexpected MCU size for VQ block!';
if (!inRangeIncl(codewordSize, 0, 2))
throw 'Out-of-range codewordSize!';
var i;
var y_buf = this._componentBufY[0];
var cb_buf = this._componentBufCb;
var cr_buf = this._componentBufCr;
var that = this;
var setColor = function (j, codeword) {
var color = that._vqCodebook[that._vqCodewordLookup[codeword]];
y_buf[j] = color[0];
cb_buf[j] = color[1];
cr_buf[j] = color[2];
};
// Read new codebook data. (That is: new colors for each of our
// codewords (the values we'll read from the input data) to map to.)
for (i = 0; i < (1 << codewordSize); ++i) {
// Read 1b flag and 2b codebook slot number. if flag is set,
// read 24b RGB value and set colors[slot #]. Regardless (?),
// set the ith codeowrd to map to this slot.
var hasNewColor = this._stream.read(1);
var codebookSlotIdx = this._stream.read(2);
if (hasNewColor) {
var color = [this._stream.read(8), this._stream.read(8), this._stream.read(8)]; // Y, Cb, Cr
this._vqCodebook[codebookSlotIdx] = color;
}
this._vqCodewordLookup[i] = codebookSlotIdx;
}
// Read a block of image data.
if (codewordSize == 0) {
// Act as though we've got a single-entry codebook.
for (i = 0; i < 64; ++i)
setColor(i, 0);
} else {
for (i = 0; i < 64; ++i)
setColor(i, this._stream.read(codewordSize));
}
// Perform colorspace conversion and copy into destination image buffer.
for (var j = 0; j < 64; ++j)
this._ycbcrToRgb(this._outputBuf, j, this._componentBufY[0][j], this._componentBufCb[j], this._componentBufCr[j]);
this._blitCallback(8 * this._mcuPosX, 8 * this._mcuPosY, 8, 8, this._outputBuf);
this._advancePosition();
},
_parseMcu: function () {
var qtLuma = this.quantTables[0];
var qtChroma = this.quantTables[1];
this._parseDataUnit(0, this._tmpBufY[0]);
this._idct(qtLuma, this._tmpBufY[0], this._componentBufY[0]);
if (this.subsamplingMode != 444) {
this._parseDataUnit(0, this._tmpBufY[1]);
this._idct(qtLuma, this._tmpBufY[1], this._componentBufY[1]);
this._parseDataUnit(0, this._tmpBufY[2]);
this._idct(qtLuma, this._tmpBufY[2], this._componentBufY[2]);
this._parseDataUnit(0, this._tmpBufY[3]);
this._idct(qtLuma, this._tmpBufY[3], this._componentBufY[3]);
}
this._parseDataUnit(1, this._tmpBufCb);
this._idct(qtChroma, this._tmpBufCb, this._componentBufCb);
this._parseDataUnit(2, this._tmpBufCr);
this._idct(qtChroma, this._tmpBufCr, this._componentBufCr);
if (this.subsamplingMode != 444) {
// 4:2:0 subsampling (x2 in each direction), or what ATEN calls "422" (even though it's not).
for (var dy = 0; dy < 2; ++ dy) {
for (var dx = 0; dx < 2; ++dx) {
// for each of the four blocks in this MCU
var componentBufY = this._componentBufY[dx*2+dy];
for (var y = 0; y < 8; ++y) {
for (var x = 0; x < 8; ++x) {
var hy = ~~((8*dx+y)/2);
var hx = ~~((8*dy+x)/2);
this._ycbcrToRgb(this._outputBuf, y*8+x, componentBufY[y*8+x], this._componentBufCb[hy*8+hx], this._componentBufCr[hy*8+hx]);
}
}
this._blitCallback(16 * this._mcuPosX + 8 * dy, 16 * this._mcuPosY + 8 * dx, 8, 8, this._outputBuf);
}
}
} else {
// No subsampling.
for (var j = 0; j < 64; ++j)
this._ycbcrToRgb(this._outputBuf, j, this._componentBufY[0][j], this._componentBufCb[j], this._componentBufCr[j]);
this._blitCallback(8 * this._mcuPosX, 8 * this._mcuPosY, 8, 8, this._outputBuf);
}
this._advancePosition();
},
_parseDataUnit: function (componentIdx, buf) {
var scanComponent = this._scan_components[componentIdx];
var dc_hufftable = this.huffTables[TABLE_CLASS_DC][scanComponent.huffTableSelectorDC];
var ac_hufftable = this.huffTables[TABLE_CLASS_AC][scanComponent.huffTableSelectorAC];
var setValue = function (i, val) {
buf[ZIGZAG_ORDER[i]] = val;
};
// First element is the DC component, followed by 63 AC components.
// The DC component is encoded slightly differently than the AC
// components: it is stored as the delta between the value and the
// last DC component from the same component.
var dc_delta = this._readEncodedValueDC(dc_hufftable);
this._scan_prev_dc[componentIdx] += dc_delta;
buf[0] = this._scan_prev_dc[componentIdx];
// Read the AC components.
for (var i = 1; i < 64;) {
var x = ac_hufftable.readCode(this._stream);
// N.B.(kelleyk): Renamed r, s to runlen, size.
// See ITU T.81 p89 (e.g. Fig F.1).
// r is runlength of zeroes; if s==0, r==0 means EOB and r==15
// means ZRL. s is number of bits required to represent the
// amplitude that follows the codeword.
var runlen = x >>> 4;
var size = x & 0x0F;
if (size == 0) {
if (runlen == 0) { // special EOB (end-of-block) codeword; fill remainder with zeroes
while (i < 64) {
setValue(i, 0);
++i;
}
break;
} else if (runlen == 0xF) { // special ZLE (zero-length-encode) codeword; emit sixteen zeroes
for (var j = 0; j < 16; ++j)
setValue(i + j, 0);
i += 16;
continue;
}
}
// Emit runlen zero entries.
for (var j = 0; j < runlen; ++j)
setValue(i + j, 0);
i += runlen;
setValue(i, this._readEncodedValueAC(size)); // category=size
i += 1;
}
return buf;
},
_readEncodedValueDC: function (huffTable) {
var category = huffTable.readCode(this._stream);
return this._readEncodedValueAC(category);
},
_readEncodedValueAC: function (category) {
if (category == 0)
return 0;
var value; // sint32
var val_sign = this._stream.read(1);
if (val_sign == 0) {
// Negative (unlike the more common two's-complement
// representation).
value = -(1 << category) + 1;
} else {
value = (1 << category - 1);
}
if (category > 1) {
var more_bits = this._stream.read(category - 1); // uint
value += more_bits;
}
return value;
},
_ycbcrToRgb: function (outputBuf, outputOffset, y, cb, cr) {
outputOffset *= 4;
outputBuf[outputOffset + 0] = clamp(YUVTORGB_Y_TABLE[y] + YUVTORGB_CR_R_TABLE[cr]);
outputBuf[outputOffset + 1] = clamp(YUVTORGB_Y_TABLE[y] + YUVTORGB_CR_G_TABLE[cr] + YUVTORGB_CB_G_TABLE[cb]);
outputBuf[outputOffset + 2] = clamp(YUVTORGB_Y_TABLE[y] + YUVTORGB_CB_B_TABLE[cb]);
outputBuf[outputOffset + 3] = 0xFF; // noVNC expects alpha
}
};
})();

View File

@ -0,0 +1,337 @@
/* (c) Copyright 2015-2017 Kevin Kelley <kelleyk@kelleyk.net>. */
// JPEG zigzag tables
var ZIGZAG_ORDER = [
0, 1, 8, 16, 9, 2, 3, 10,
17, 24, 32, 25, 18, 11, 4, 5,
12, 19, 26, 33, 40, 48, 41, 34,
27, 20, 13, 6, 7, 14, 21, 28,
35, 42, 49, 56, 57, 50, 43, 36,
29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46,
53, 60, 61, 54, 47, 55, 62, 63,
];
var DEZIGZAG_ORDER = [
0, 1, 5, 6, 14, 15, 27, 28,
2, 4, 7, 13, 16, 26, 29, 42,
3, 8, 12, 17, 25, 30, 41, 43,
9, 11, 18, 24, 31, 40, 44, 53,
10, 19, 23, 32, 39, 45, 52, 54,
20, 22, 33, 38, 46, 51, 55, 60,
21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63,
];
// Huffman tables
var BITS_DC_LUMA = [0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0];
var HUFFVAL_DC_LUMA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
var BITS_DC_CHROMA = [0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0];
var HUFFVAL_DC_CHROMA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
var BITS_AC_LUMA = [0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d];
var HUFFVAL_AC_LUMA = [
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa
];
var BITS_AC_CHROMA = [0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77];
var HUFFVAL_AC_CHROMA = [
0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34,
0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38,
0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2,
0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa
];
// Quant tables
var ATEN_QT_LUMA = [
[
0x14, 0x0D, 0x0C, 0x14, 0x1E, 0x32, 0x3F, 0x4C,
0x0F, 0x0F, 0x11, 0x17, 0x20, 0x48, 0x4B, 0x44,
0x11, 0x10, 0x14, 0x1E, 0x32, 0x47, 0x56, 0x46,
0x11, 0x15, 0x1B, 0x24, 0x3F, 0x6C, 0x64, 0x4D,
0x16, 0x1B, 0x2E, 0x46, 0x55, 0x88, 0x80, 0x60,
0x1E, 0x2B, 0x44, 0x50, 0x65, 0x82, 0x8D, 0x73,
0x3D, 0x50, 0x61, 0x6C, 0x80, 0x97, 0x96, 0x7E,
0x5A, 0x73, 0x76, 0x7A, 0x8C, 0x7D, 0x80, 0x7B,
], [
0x11, 0x0C, 0x0A, 0x11, 0x1A, 0x2B, 0x37, 0x42,
0x0D, 0x0D, 0x0F, 0x14, 0x1C, 0x3F, 0x41, 0x3C,
0x0F, 0x0E, 0x11, 0x1A, 0x2B, 0x3E, 0x4B, 0x3D,
0x0F, 0x12, 0x18, 0x1F, 0x37, 0x5F, 0x57, 0x43,
0x13, 0x18, 0x28, 0x3D, 0x4A, 0x77, 0x70, 0x54,
0x1A, 0x26, 0x3C, 0x46, 0x58, 0x71, 0x7B, 0x64,
0x35, 0x46, 0x55, 0x5F, 0x70, 0x84, 0x83, 0x6E,
0x4E, 0x64, 0x67, 0x6B, 0x7A, 0x6D, 0x70, 0x6C,
], [
0x0E, 9, 9, 0x0E, 0x15, 0x24, 0x2E, 0x37,
0x0A, 0x0A, 0x0C, 0x11, 0x17, 0x34, 0x36, 0x31,
0x0C, 0x0B, 0x0E, 0x15, 0x24, 0x33, 0x3E, 0x32,
0x0C, 0x0F, 0x13, 0x1A, 0x2E, 0x4E, 0x48, 0x38,
0x10, 0x13, 0x21, 0x32, 0x3D, 0x62, 0x5D, 0x45,
0x15, 0x1F, 0x31, 0x3A, 0x49, 0x5E, 0x66, 0x53,
0x2C, 0x3A, 0x46, 0x4E, 0x5D, 0x6D, 0x6C, 0x5B,
0x41, 0x53, 0x56, 0x58, 0x65, 0x5A, 0x5D, 0x59,
], [
0x0B, 7, 7, 0x0B, 0x11, 0x1C, 0x24, 0x2B,
8, 8, 0x0A, 0x0D, 0x12, 0x29, 0x2B, 0x27,
0x0A, 9, 0x0B, 0x11, 0x1C, 0x28, 0x31, 0x28,
0x0A, 0x0C, 0x0F, 0x14, 0x24, 0x3E, 0x39, 0x2C,
0x0C, 0x0F, 0x1A, 0x28, 0x30, 0x4E, 0x4A, 0x37,
0x11, 0x19, 0x27, 0x2E, 0x3A, 0x4A, 0x51, 0x42,
0x23, 0x2E, 0x38, 0x3E, 0x4A, 0x56, 0x56, 0x48,
0x33, 0x42, 0x44, 0x46, 0x50, 0x47, 0x4A, 0x47,
], [
9, 6, 5, 9, 0x0D, 0x16, 0x1C, 0x22,
6, 6, 7, 0x0A, 0x0E, 0x20, 0x21, 0x1E,
7, 7, 9, 0x0D, 0x16, 0x20, 0x26, 0x1F,
7, 9, 0x0C, 0x10, 0x1C, 0x30, 0x2D, 0x22,
0x0A, 0x0C, 0x14, 0x1F, 0x26, 0x3D, 0x39, 0x2B,
0x0D, 0x13, 0x1E, 0x24, 0x2D, 0x3A, 0x3F, 0x33,
0x1B, 0x24, 0x2B, 0x30, 0x39, 0x44, 0x43, 0x38,
0x28, 0x33, 0x35, 0x37, 0x3F, 0x38, 0x39, 0x37,
], [
6, 4, 3, 6, 9, 0x0F, 0x13, 0x16,
4, 4, 5, 7, 9, 0x15, 0x16, 0x14,
5, 4, 6, 9, 0x0F, 0x15, 0x19, 0x15,
5, 6, 8, 0x0A, 0x13, 0x20, 0x1E, 0x17,
6, 8, 0x0D, 0x15, 0x19, 0x28, 0x26, 0x1C,
9, 0x0D, 0x14, 0x18, 0x1E, 0x27, 0x2A, 0x22,
0x12, 0x18, 0x1D, 0x20, 0x26, 0x2D, 0x2D, 0x25,
0x1B, 0x22, 0x23, 0x24, 0x2A, 0x25, 0x26, 0x25,
], [
3, 2, 1, 3, 4, 7, 9, 0x0B,
2, 2, 2, 3, 4, 0x0A, 0x0B, 0x0A,
2, 2, 3, 4, 7, 0x0A, 0x0C, 0x0A,
2, 3, 4, 5, 9, 0x10, 0x0F, 0x0B,
3, 4, 6, 0x0A, 0x0C, 0x14, 0x13, 0x0E,
4, 6, 0x0A, 0x0C, 0x0F, 0x13, 0x15, 0x11,
9, 0x0C, 0x0E, 0x10, 0x13, 0x16, 0x16, 0x12,
0x0D, 0x11, 0x11, 0x12, 0x15, 0x12, 0x13, 0x12,
], [
2, 1, 1, 2, 3, 5, 6, 7,
1, 1, 1, 2, 3, 7, 7, 6,
1, 1, 2, 3, 5, 7, 8, 7,
1, 2, 2, 3, 6, 0x0A, 0x0A, 7,
2, 2, 4, 7, 8, 0x0D, 0x0C, 9,
3, 4, 6, 8, 0x0A, 0x0D, 0x0E, 0x0B,
6, 8, 9, 0x0A, 0x0C, 0x0F, 0x0F, 0x0C,
9, 0x0B, 0x0B, 0x0C, 0x0E, 0x0C, 0x0C, 0x0C,
], [
2, 1, 1, 2, 3, 5, 6, 7,
1, 1, 1, 2, 3, 7, 7, 6,
1, 1, 2, 3, 5, 7, 8, 7,
1, 2, 2, 3, 6, 0x0A, 0x0A, 7,
2, 2, 4, 7, 8, 0x0D, 0x0C, 9,
3, 4, 6, 8, 0x0A, 0x0D, 0x0E, 0x0B,
6, 8, 9, 0x0A, 0x0C, 0x0F, 0x0F, 0x0C,
9, 0x0B, 0x0B, 0x0C, 0x0E, 0x0C, 0x0C, 0x0C,
], [
1, 1, 1, 1, 2, 3, 4, 5,
1, 1, 1, 1, 2, 5, 5, 5,
1, 1, 1, 2, 3, 5, 6, 5,
1, 1, 2, 2, 4, 8, 7, 5,
1, 2, 3, 5, 6, 0x0A, 9, 7,
2, 3, 5, 6, 7, 9, 0x0A, 8,
4, 6, 7, 8, 9, 0x0B, 0x0B, 9,
6, 8, 8, 9, 0x0A, 9, 9, 9,
], [
1, 1, 1, 1, 1, 2, 3, 3,
1, 1, 1, 1, 1, 3, 3, 3,
1, 1, 1, 1, 2, 3, 4, 3,
1, 1, 1, 1, 3, 5, 5, 3,
1, 1, 2, 3, 4, 6, 6, 4,
1, 2, 3, 4, 5, 6, 7, 5,
3, 4, 4, 5, 6, 7, 7, 6,
4, 5, 5, 6, 7, 6, 6, 6,
], [
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 2, 1,
1, 1, 1, 1, 1, 2, 2, 1,
1, 1, 1, 1, 2, 3, 3, 2,
1, 1, 1, 2, 2, 3, 3, 2,
1, 2, 2, 2, 3, 3, 3, 3,
2, 2, 2, 3, 3, 3, 3, 3,
]
];
var ATEN_QT_CHROMA = [
[
0x1F, 0x21, 0x2D, 0x58, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
0x21, 0x27, 0x30, 0x7B, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
0x2D, 0x30, 0x69, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
0x58, 0x7B, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
], [
0x1B, 0x1D, 0x27, 0x4C, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
0x1D, 0x22, 0x2A, 0x6B, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
0x27, 0x2A, 0x5B, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
0x4C, 0x6B, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
], [
0x16, 0x18, 0x20, 0x3F, 0x85, 0x85, 0x85, 0x85,
0x18, 0x1C, 0x22, 0x58, 0x85, 0x85, 0x85, 0x85,
0x20, 0x22, 0x4B, 0x85, 0x85, 0x85, 0x85, 0x85,
0x3F, 0x58, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
], [
0x12, 0x13, 0x1A, 0x33, 0x6C, 0x6C, 0x6C, 0x6C,
0x13, 0x16, 0x1C, 0x48, 0x6C, 0x6C, 0x6C, 0x6C,
0x1A, 0x1C, 0x3D, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
0x33, 0x48, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
], [
0x0D, 0x0E, 0x13, 0x26, 0x50, 0x50, 0x50, 0x50,
0x0E, 0x11, 0x15, 0x35, 0x50, 0x50, 0x50, 0x50,
0x13, 0x15, 0x2D, 0x50, 0x50, 0x50, 0x50, 0x50,
0x26, 0x35, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
], [
9, 0x0A, 0x0D, 0x1A, 0x37, 0x37, 0x37, 0x37,
0x0A, 0x0B, 0x0E, 0x25, 0x37, 0x37, 0x37, 0x37,
0x0D, 0x0E, 0x1F, 0x37, 0x37, 0x37, 0x37, 0x37,
0x1A, 0x25, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
], [
4, 5, 6, 0x0D, 0x1B, 0x1B, 0x1B, 0x1B,
5, 5, 7, 0x12, 0x1B, 0x1B, 0x1B, 0x1B,
6, 7, 0x0F, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
0x0D, 0x12, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
], [
3, 3, 4, 8, 0x12, 0x12, 0x12, 0x12,
3, 3, 4, 0x0C, 0x12, 0x12, 0x12, 0x12,
4, 4, 0x0A, 0x12, 0x12, 0x12, 0x12, 0x12,
8, 0x0C, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
], [
2, 2, 3, 7, 0x0F, 0x0F, 0x0F, 0x0F,
2, 3, 4, 0x0A, 0x0F, 0x0F, 0x0F, 0x0F,
3, 4, 8, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
7, 0x0A, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
], [
2, 2, 3, 5, 0x0C, 0x0C, 0x0C, 0x0C,
2, 2, 3, 8, 0x0C, 0x0C, 0x0C, 0x0C,
3, 3, 7, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
5, 8, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
], [
1, 1, 2, 4, 9, 9, 9, 9,
1, 1, 2, 6, 9, 9, 9, 9,
2, 2, 5, 9, 9, 9, 9, 9,
4, 6, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9,
], [
1, 1, 1, 2, 6, 6, 6, 6,
1, 1, 1, 4, 6, 6, 6, 6,
1, 1, 3, 6, 6, 6, 6, 6,
2, 4, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
]
];
/*
Ref.: libjpeg's jddctmgr.c:
"For float AA&N IDCT method, multipliers are equal to quantization
coefficients scaled by scalefactor[row]*scalefactor[col], where
scalefactor[0] = 1
scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7
*/
var AAN_IDCT_SCALING_FACTORS = [
1.0, 1.387039845, 1.306562965, 1.175875602,
1.0, 0.785694958, 0.541196100, 0.275899379,
];
var YUVTORGB_CR_G_TABLE = new Int32Array(256);
var YUVTORGB_CR_R_TABLE = new Int32Array(256);
var YUVTORGB_CB_G_TABLE = new Int32Array(256);
var YUVTORGB_CB_B_TABLE = new Int32Array(256);
var YUVTORGB_Y_TABLE = new Int32Array(256);
for (var i = 0; i < 256; ++i) {
YUVTORGB_CR_G_TABLE[i] = (0x688000 - i * 0xD000) >> 16;
YUVTORGB_CR_R_TABLE[i] = (0xFF340000 + i * 0x19900) >> 16;
YUVTORGB_CB_G_TABLE[i] = (0x328000 - i * 0x6400) >> 16;
YUVTORGB_CB_B_TABLE[i] = (0xFEFE8000 + i * 0x20400) >> 16;
YUVTORGB_Y_TABLE[i] = (0xFFEDE040 + i * 0x129FC) >> 16;
}
var TABLE_CLASS_DC = 0;
var TABLE_CLASS_AC = 1;
var DCTSIZE = 8;
var DCTSIZE2 = DCTSIZE * DCTSIZE;

214
core/ast2100/ast2100idct.js Normal file
View File

@ -0,0 +1,214 @@
/* (c) Copyright 2015-2017 Kevin Kelley <kelleyk@kelleyk.net>. */
var AST2100IDCT;
// N.B.: These values assume CONST_BITS == 8
var FIX_1_082392200 = 277; // fix(1.082392200)
var FIX_1_414213562 = 362; // fix(1.414213562)
var FIX_1_847759065 = 473; // fix(1.847759065)
var FIX_2_613125930 = 669; // fix(2.613125930)
var MAXJSAMPLE = 255; // largest value of a sample; 8-bit, so 2**8-1
var CONST_BITS = 8;
var PASS1_BITS = 0;
// This is like clamp() except that it also adds the MAXJSAMPLE/2 offset.
var range_limit = function (x) {
// XXX(kelleyk): This offset is baked into the range limit table in the ATEN
// stuff and in libjpeg; see commpent above prepare_range_limit_table() in
// jdmaster.c.
x += 128;
return Math.max(0, Math.min(255, x));
};
// Convert float to fixed-point.
var fix = function (x) {
return ~~(x * (1 << CONST_BITS) + 0.5);
};
var fixed_dequant = function (scaled_quant_table, buf, i) {
// N.B.: The data in buf is unscaled; the data in scaled_quant_table has
// been scaled by 1<<16.
return fixed_mul(scaled_quant_table[i], buf[i]);
};
var descale = function (x, n) {
// console.log([x, x>>n, (x>>n)+128]);
return x >> n;
};
// if not accurate rounding mode...
var idescale = descale;
// else use better implementation
// N.B.(kelleyk): This isn't a libjpeg parameter; it just means that ATEN
// downshifts all the way back to "normal ints" at the end of pass 1.
var END_PASS1_DESCALE_BITS = CONST_BITS;
var fixed_mul = function (a, b) {
return descale(a * b, CONST_BITS);
};
(function () {
"use strict";
AST2100IDCT = {
// Uses the 16/16 integer representation that ATEN and libjpeg's
// jidctfst.c ("fast, not-so-accurate integer IDCT") do. Note that, for
// performance, this routine *also* incorporates the dequantization
// step, which is why it takes scaled_quant_table as an argument. (This
// argument does not actually contain "just" a scaled quant table; some
// constants have been pre-multiplied into it. See the function that
// loads quant tables for more details.)
idct_fixed_aan: function (scaled_quant_table, buf, dstBuf) {
// ATEN rounds things off early, at a cost to precision: the int32
// values in 'workspace' are not scaled at all.
var workspace = new Int32Array(64);
for (var x = 0; x < 8; ++x)
AST2100IDCT._aan_idct_col(scaled_quant_table, buf, workspace, x);
for (var y = 0; y < 8; ++y)
AST2100IDCT._aan_idct_row(scaled_quant_table, dstBuf, workspace, y);
return dstBuf;
},
// Columns; aka "Pass 1".
_aan_idct_col: function(scaled_quant_table, buf, workspace, x) {
var dequant = function (idx) { return fixed_dequant(scaled_quant_table, buf, idx); };
var mul = fixed_mul;
var y;
var all_ac_zero = true;
for (y = 1; y < 8; ++y) {
if (buf[8 * y + x] != 0) {
all_ac_zero = false;
break;
}
}
if (all_ac_zero) {
var raw_dcval = buf[8 * 0 + x];
var quant_val = scaled_quant_table[8 * 0 + x];
var dcval = idescale(dequant(8 * 0 + x), END_PASS1_DESCALE_BITS); // in total, >> 16
for (y = 0; y < 8; ++y)
workspace[8 * y + x] = dcval;
return;
}
// Even part.
var tmp0 = dequant(8 * 0 + x);
var tmp1 = dequant(8 * 2 + x);
var tmp2 = dequant(8 * 4 + x);
var tmp3 = dequant(8 * 6 + x);
var tmp10 = tmp0 + tmp2; // Phase 3
var tmp11 = tmp0 - tmp2;
var tmp13 = tmp1 + tmp3; // Phases 5-3
var tmp12 = mul((tmp1 - tmp3), FIX_1_414213562) - tmp13; // 2 * c4
tmp0 = tmp10 + tmp13;
tmp3 = tmp10 - tmp13;
tmp1 = tmp11 + tmp12;
tmp2 = tmp11 - tmp12;
// Odd part.
var tmp4 = dequant(8 * 1 + x);
var tmp5 = dequant(8 * 3 + x);
var tmp6 = dequant(8 * 5 + x);
var tmp7 = dequant(8 * 7 + x);
var z13 = tmp6 + tmp5; // Phase 6
var z10 = tmp6 - tmp5;
var z11 = tmp4 + tmp7;
var z12 = tmp4 - tmp7;
tmp7 = z11 + z13; // Phase 5
tmp11 = mul((z11 - z13), FIX_1_414213562); // 2 * c4
var z5 = mul((z10 + z12), FIX_1_847759065); // 2 * c2
tmp10 = mul(FIX_1_082392200, z12) - z5; // 2 * (c2-c6)
tmp12 = mul(-FIX_2_613125930, z10) + z5;
tmp6 = tmp12 - tmp7;
tmp5 = tmp11 - tmp6;
tmp4 = tmp10 + tmp5;
workspace[x + 8 * 0] = idescale(tmp0 + tmp7, END_PASS1_DESCALE_BITS);
workspace[x + 8 * 7] = idescale(tmp0 - tmp7, END_PASS1_DESCALE_BITS);
workspace[x + 8 * 1] = idescale(tmp1 + tmp6, END_PASS1_DESCALE_BITS);
workspace[x + 8 * 6] = idescale(tmp1 - tmp6, END_PASS1_DESCALE_BITS);
workspace[x + 8 * 2] = idescale(tmp2 + tmp5, END_PASS1_DESCALE_BITS);
workspace[x + 8 * 5] = idescale(tmp2 - tmp5, END_PASS1_DESCALE_BITS);
workspace[x + 8 * 4] = idescale(tmp3 + tmp4, END_PASS1_DESCALE_BITS);
workspace[x + 8 * 3] = idescale(tmp3 - tmp4, END_PASS1_DESCALE_BITS);
},
// Rows; aka "Pass 2".
_aan_idct_row: function(scaled_quant_table, buf, workspace, y) {
var wsptr = function (x) { return workspace[8 * y + x]; };
var mul = fixed_mul;
// Even part.
var tmp10 = wsptr(0) + wsptr(4);
var tmp11 = wsptr(0) - wsptr(4);
var tmp13 = wsptr(2) + wsptr(6);
var tmp12 = mul((wsptr(2) - wsptr(6)), FIX_1_414213562) - tmp13;
var tmp0 = tmp10 + tmp13;
var tmp3 = tmp10 - tmp13;
var tmp1 = tmp11 + tmp12;
var tmp2 = tmp11 - tmp12;
// Odd part.
var z13 = wsptr(5) + wsptr(3);
var z10 = wsptr(5) - wsptr(3);
var z11 = wsptr(1) + wsptr(7);
var z12 = wsptr(1) - wsptr(7);
var tmp7 = z11 + z13;
tmp11 = mul((z11 - z13), FIX_1_414213562);
var z5 = mul((z10 + z12), FIX_1_847759065); // 2 * c2
tmp10 = mul(FIX_1_082392200, z12) - z5; // 2 * (c2-c6)
tmp12 = mul(-FIX_2_613125930, z10) + z5;
var tmp6 = tmp12 - tmp7;
var tmp5 = tmp11 - tmp6;
var tmp4 = tmp10 + tmp5;
var set_out = function (x, val) {
// Shift right by PASS1_BITS bits to convert back to a normal
// int, and then by another 3 to divide by 8.
val = idescale(val, PASS1_BITS + 3);
val = range_limit(val); // This also applies the +128 offset.
buf[y * 8 + x] = val;
};
// Final output stage: scale down by a factor of 8 and range-limit
set_out(0, tmp0 + tmp7);
set_out(7, tmp0 - tmp7);
set_out(1, tmp1 + tmp6);
set_out(6, tmp0 - tmp6);
set_out(2, tmp2 + tmp5);
set_out(5, tmp2 - tmp5);
set_out(4, tmp3 + tmp4);
set_out(3, tmp3 - tmp4);
}
};
})();

284
core/ast2100/ast2100util.js Normal file
View File

@ -0,0 +1,284 @@
/* (c) Copyright 2015-2017 Kevin Kelley <kelleyk@kelleyk.net>. */
function fmt_rgb_buf (n, buf) {
var s = "";
for (var y = 0; y < n; ++y) {
for (var x = 0; x < n; ++x) {
var offset = ((y*n)+x)*4;
s += fmt_u8(buf[offset+0]) + fmt_u8(buf[offset+1]) + fmt_u8(buf[offset+2]) + fmt_u8(buf[offset+3]) + ' ';
}
s += '\n';
}
return s;
}
var fmt_u8 = function (x) {
var pad = '00';
var s = x.toString(16);
return pad.substring(0, pad.length - s.length) + s;
};
var fmt_u16 = function (x) {
var pad = '0000';
var s = x.toString(16);
return pad.substring(0, pad.length - s.length) + s;
};
var fmt_u32 = function (x) { // cheesy way to ger around the sign bit
return fmt_u16(x >>> 16) + fmt_u16(x & 0xFFFF);
};
var fmt_s8 = function (x) {
if (x < 0)
x += (1 << 8);
return fmt_u8(x);
};
var fmt_s16 = function (x) {
if (x < 0)
x += (1 << 16);
return fmt_u16(x);
};
var fmt_s32 = function (x) {
if (x < 0)
x += (1 << 32);
return fmt_u32(x);
};
var fmt_array = function(f, xx) {
xx = Array.from(xx); // if xx is a typed array, the strings fmt_u8 returns will be silently coerced back to numbers
return '[' + xx.map(f).join(', ') + ']';
};
var fmt_u8a = function (xx) { return fmt_array(fmt_u8, xx); };
var fmt_u16a = function (xx) { return fmt_array(fmt_u16, xx); };
var fmt_u32a = function (xx) { return fmt_array(fmt_u32, xx); };
var fmt_s8a = function (xx) { return fmt_array(fmt_s8, xx); };
var fmt_s16a = function (xx) { return fmt_array(fmt_s16, xx); };
var fmt_s32a = function (xx) { return fmt_array(fmt_s32, xx); };
var inRangeIncl = function (x, a, b) {
return (x >= a && x <= b);
};
var inRange = function (x, a, b) {
return (x >= a && x < b);
};
var swap32 = function (val) {
return ((val & 0xFF) << 24)
| ((val & 0xFF00) << 8)
| ((val >> 8) & 0xFF00)
| ((val >> 24) & 0xFF);
};
var arrayEq = function (a, b) {
if (a.length != b.length)
return false;
for (var i = 0; i < a.length; ++i)
if (a[i] != b[i])
return false;
return true;
};
var isEmpty = function (obj) {
for(var prop in obj)
if(obj.hasOwnProperty(prop))
return false;
return true;
};
var clamp = function (x) {
x = ~~x;
if (x <= 0)
return 0;
if (x >= 255)
return 255;
return x;
};
var BitStream;
var JpegHuffmanTable;
(function () {
"use strict";
BitStream = function (defaults) {
// ATEN scan data does not treat 0xFF bytes specially.
this._interpretMarkers = false;
// True iff we've encountered "end-of-image".
this._eoi = false;
this._data = defaults.data; // uint8[]
if (this._data === undefined)
throw 'BitStream constructor requires argument "data"!';
this._nextDword = 0; // uint8
// Fill the readbuf and reservoir with the first two dwords in the data
// buffer.
this._reservoir = 0;
this._bitsInReservoir = 0;
this._refill();
this._readbuf = this._reservoir;
this._reservoir = 0;
this._bitsInReservoir = 0;
this._refill();
};
BitStream.prototype = {
getPos: function () {
return (this._nextDword * 32) - (32 + this._bitsInReservoir);
},
skip: function (bits) {
while (bits > 0) {
var n = Math.min(32, bits);
this.read(n);
bits -= n;
}
},
read: function (bits) {
// JavaScript's bitwise operators have this limitation.
if (bits >= 32)
throw 'Number of bits must be less than 32.';
// this.checkInvariants();
var that = this;
var moveFromReservoir = function (n) {
that._readbuf = (that._readbuf << n) | (that._reservoir >>> (32 - n));
that._reservoir <<= n;
that._bitsInReservoir -= n;
};
var retval = this._readbuf >>> (32 - bits); // same as this.peek(bits)
if (bits > this._bitsInReservoir) {
bits -= this._bitsInReservoir;
moveFromReservoir(this._bitsInReservoir);
this._refill();
}
moveFromReservoir(bits);
return retval;
},
_refill: function () {
// this.checkInvariants();
// N.B.: Must use >>> for unsigned shift; >> is sra.
if (this._bitsInReservoir != 0)
throw 'Oops: in _refill(), bitsInReservoir='+this._bitsInReservoir;
for (var i = 0; i < 4; ++i) {
var x = this._data[(4 * this._nextDword) + i];
if (x === undefined)
throw 'BitStream overran available data!';
// TODO: if interpretMarkers ...
this._reservoir = (this._reservoir << 8) | x;
this._bitsInReservoir += 8;
}
this._nextDword += 1;
this._reservoir = swap32(this._reservoir);
// this.checkInvariants();
},
peek: function (bits) {
if (bits >= 32) // due to the fact that the bitwise operators have this limitation
throw 'Number of bits must be less than 32.';
// this.checkInvariants();
return this._readbuf >>> (32 - bits);
},
showDebug: function () {
console.log('stream readbuf='+fmt_u32(this._readbuf)+' reservoir='+fmt_u32(this._reservoir)+' bitsLeft='+this._bitsInReservoir+'d nextDword='+fmt_u16(this._nextDword));
},
checkInvariants: function () {
var err = null;
if (this._bitsInReservoir < 0 || this._bitsInReservoir > 32)
err = 'Oops: BitStream invariant violated: bitsInReservoir='+this._bitsInReservoir;
if (err) {
console.log(err);
this.showDebug();
throw err;
}
}
};
JpegHuffmanTable = function (defaults) {
if (!defaults)
defaults = {};
if (!defaults.bits || !defaults.huffval)
throw 'Arguments bits, huffval are required.';
this._huffsize = new Uint8Array(1<<16);
this._huffval_lookup = new Uint8Array(1<<16);
// 1 to 16, plus a useless 0th element to make indexing nicer.
this._bits = new Uint8Array(17);
this._buildTables(defaults.bits, defaults.huffval);
};
JpegHuffmanTable.prototype = {
_buildTables: function (bits, huffval) {
// First, copy BITS.
for (var i = 0; i < 17; ++i) {
this._bits[i] = bits[i];
// This follows from the fact that the all-ones codeword of each
// length is reserved as a prefix for longer codewords, which
// implies that there may not be more than (2**i)-1 codewords of
// length i bits.
if ((1 << i) <= bits[i])
throw 'Oops: bad BITS.';
}
// Calculate Huffman codewords. The best explanation for this is a
// graphical one. Refer to 'Binary Tree Expansin of DHT' figure
// here:
// http://www.impulseadventure.com/photo/jpeg-huffman-coding.html.
var next_codeword = 0;
var codeword_qty = 0;
var codeword_idx = 0; // index into HUFFVAL
for (var code_len = 1; code_len < 17; ++code_len) {
for (var i = 0; i < this._bits[code_len]; ++i) {
for (var j = 0; j < (1 << (16 - code_len)); ++j) {
this._huffsize[next_codeword + j] = code_len;
this._huffval_lookup[next_codeword + j] = huffval[codeword_idx];
}
next_codeword += (1 << (16 - code_len));
codeword_idx += 1;
}
}
if (codeword_idx != huffval.length)
throw 'Oops: all codewords should be used!';
},
// Reads the next code from the stream. Consumes up to 16 bits.
// Returns the corresponding codeword (the value that the code
// represents, which is always exactly one byte).
readCode: function (stream) {
// Some number of bits, 16 < x <= 0, on the least-significant end of
// this 16b value are not relevant, depoending on how long the code
// actually is. However, we've built our lookup tables so that,
// regardless of what value the irrelevant bits have, we will get
// the same result.
var fixedlenCode = stream.peek(16); // uint16
var codeLen = this._huffsize[fixedlenCode];
stream.skip(codeLen);
var codeword = this._huffval_lookup[fixedlenCode];
return codeword;
}
};
})();

View File

@ -467,6 +467,7 @@
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
// this probably isn't getting called *nearly* as much // this probably isn't getting called *nearly* as much
var new_arr = new Uint8Array(width * height * 4); var new_arr = new Uint8Array(width * height * 4);
// XXX(kelleyk): Why are we allocating *two* new arrays here?
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length)); new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
this._renderQ_push({ this._renderQ_push({
'type': 'blit', 'type': 'blit',

View File

@ -21,9 +21,11 @@
* import XK2HID from "./input/keysym"; * import XK2HID from "./input/keysym";
* import XtScancode from "./input/xtscancodes"; * import XtScancode from "./input/xtscancodes";
* import Inflator from "./inflator.mod"; * import Inflator from "./inflator.mod";
* import Ast2100Decoder from "./ast2100/ast2100";
* import arrayEq from "./ast2100/ast2100util";
*/ */
/*jslint white: false, browser: true */ /*jslint white: false, browser: true */
/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode */ /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode, Ast2100Decoder, arrayEq */
/* [module] export default */ function RFB(defaults) { /* [module] export default */ function RFB(defaults) {
"use strict"; "use strict";
@ -44,6 +46,7 @@
this._rfb_disconnect_reason = ""; this._rfb_disconnect_reason = "";
this._rfb_tightvnc = false; this._rfb_tightvnc = false;
this._rfb_atenikvm = false;
this._rfb_xvp_ver = 0; this._rfb_xvp_ver = 0;
// In preference order // In preference order
@ -54,7 +57,13 @@
['HEXTILE', 0x05 ], ['HEXTILE', 0x05 ],
['RRE', 0x02 ], ['RRE', 0x02 ],
['RAW', 0x00 ], ['RAW', 0x00 ],
['ATEN', 0x59 ],
// ATEN iKVM encodings
['ATEN_AST2100', 0x57 ],
['ATEN_ASTJPEG', 0x58 ],
['ATEN_HERMON', 0x59 ],
['ATEN_YARKON', 0x60 ],
['ATEN_PILOT3', 0x61 ],
// Psuedo-encoding settings // Psuedo-encoding settings
@ -369,6 +378,13 @@
} }
}, },
// Tell the ATEN iKVM server to change the quantization tables and/or
// type of subsampling that it uses to encode video.
atenChangeVideoSettings: function (lumaQt, chromaQt, subsamplingMode) {
RFB.messages.atenChangeVideoSettings(this._sock, lumaQt, chromaQt, subsamplingMode);
this._sock.flush();
},
// Private methods // Private methods
@ -803,6 +819,8 @@
this._rfb_auth_scheme = 0; this._rfb_auth_scheme = 0;
var types = this._sock.rQshiftBytes(num_types); var types = this._sock.rQshiftBytes(num_types);
Util.Debug("Server security types: " + types); Util.Debug("Server security types: " + types);
// N.B.(kelleyk): Deliberately copy-constructed, since the underlying array is reused.
this._rfb_server_supported_security_types = Array.from(types);
for (var i = 0; i < types.length; i++) { for (var i = 0; i < types.length; i++) {
switch (types[i]) { switch (types[i]) {
case 1: // None case 1: // None
@ -841,8 +859,9 @@
var aten_sep = this._aten_password_sep; var aten_sep = this._aten_password_sep;
var aten_auth = this._rfb_password.split(aten_sep); var aten_auth = this._rfb_password.split(aten_sep);
if (aten_auth.length < 2) { if (aten_auth.length < 2) {
this._onPasswordRequired(this, 'ATEN iKVM credentials required (user' + aten_sep + this._onPasswordRequired(
'password) -- got only ' + this._rfb_password); this,
'ATEN iKVM credentials required (user' + aten_sep + 'password)');
return false; return false;
} }
@ -850,6 +869,7 @@
this._convert_color = true; this._convert_color = true;
if (this._rfb_tightvnc) { if (this._rfb_tightvnc) {
// N.B.(kelleyk): We've already "skipped" the four bytes that we read into numTunnels.
this._rfb_tightvnc = false; this._rfb_tightvnc = false;
} else { } else {
this._sock.rQskipBytes(4); this._sock.rQskipBytes(4);
@ -904,6 +924,8 @@
}, },
_negotiate_tight_tunnels: function (numTunnels) { _negotiate_tight_tunnels: function (numTunnels) {
// N.B.(kelleyk): For a full of known tunnel types, see:
// https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#tight-security-type
var clientSupportedTunnelTypes = { var clientSupportedTunnelTypes = {
0: { vendor: 'TGHT', signature: 'NOTUNNEL' } 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
}; };
@ -934,16 +956,31 @@
}, },
_negotiate_tight_auth: function () { _negotiate_tight_auth: function () {
var numTunnels; // NB(directxman12): this is only in scope within the following block, var numTunnels = 0; // NB(directxman12): this is only in scope within the following block,
// or if equal to zero (necessary for ATEN iKVM support) // or if equal to zero (necessary for ATEN iKVM support)
if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
if (this._sock.rQwait("num tunnels", 4)) { return false; } if (this._sock.rQwait("num tunnels", 4)) { return false; }
numTunnels = this._sock.rQshift32(); numTunnels = this._sock.rQshift32();
if (this._rfb_version === 3.8 && (numTunnels & 0xffff0ff0) >>> 0 === 0xaff90fb0) { return this._negotiate_aten_auth(); }
if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
this._rfb_tightvnc = true; this._rfb_tightvnc = true;
// N.B.(kelleyk): I can only find about a half-dozen known tunnel
// types, so TightVNC should be sending a relatively small number
// here, whereas the ATEN servers send four bytes that, when
// interpreted as a u32, represent a very large number. (The
// condition I've chosen below just checks that the first byte
// is nonzero.) Further, TightVNC servers seem to be support
// both 0x02 and 0x10 security types, whereas ATEN iKVM servers
// advertise only support for 0x10. (Are there *other* VNC
// servers that only support 0x10?)
if (this._rfb_version === 3.8 &&
arrayEq(this._rfb_server_supported_security_types, [0x10]) &&
(numTunnels <= 0 || numTunnels > 0x1000000)) {
Util.Info('Detected ATEN iKVM server (using heuristic #0 -- ' +
'older Winbond/Nuvoton or Renesas BMC?).');
return this._negotiate_aten_auth();
}
if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
if (numTunnels > 0) { if (numTunnels > 0) {
this._negotiate_tight_tunnels(numTunnels); this._negotiate_tight_tunnels(numTunnels);
return false; // wait until we receive the sub auth to continue return false; // wait until we receive the sub auth to continue
@ -960,9 +997,14 @@
if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; } if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
// Newer X10 Supermicro motherboards get here // Newer X10 Supermicro motherboards get here.
if (this._rfb_version === 3.8 && numTunnels === 0 && subAuthCount === 0) { // N.B.(kelleyk): If we had trouble with this heuristic matching
Util.Warn("Newer ATEN iKVM detected, you may get an 'unsupported encoding 87'"); // non-ATEN servers, we could also add the "only security type 0x10
// is supported" condition from above.
if (this._rfb_version === 3.8 &&
numTunnels === 0 &&
(subAuthCount === 0 || (subAuthCount & 0xFFFF) == 0x0100)) {
Util.Info('Detected ATEN iKVM server (using heuristic #1 -- newer AST2400 BMC?).');
return this._negotiate_aten_auth(); return this._negotiate_aten_auth();
} }
@ -1156,7 +1198,7 @@
// - X9DRL-3F/X9DRL-6F // - X9DRL-3F/X9DRL-6F
// - X10SLD // - X10SLD
// //
// Not supported (uses encoding 87): // Supported using the ATEN "AST2100" encoding (0x57 / 87):
// - X10SL7-F // - X10SL7-F
// - X10SLD-F // - X10SLD-F
// - X10SLM-F // - X10SLM-F
@ -1177,7 +1219,10 @@
this._fb_width = 10000; this._fb_width = 10000;
this._fb_height = 10000; this._fb_height = 10000;
// lies about what it supports // TODO(kelleyk): This message (and this block of code) is part
// of the original ATEN "HERMON" (0x59) support. The "AST2100"
// (0x57) encoding delivers RGB888 color. I suppose that we should
// update this somehow? What effect does it have?
Util.Warn("ATEN iKVM lies and only does 15 bit depth with RGB555"); Util.Warn("ATEN iKVM lies and only does 15 bit depth with RGB555");
this._convert_color = true; this._convert_color = true;
this._pixelFormat.bpp = 16; this._pixelFormat.bpp = 16;
@ -1560,6 +1605,9 @@
return false; return false;
} }
// TODO(kelleyk): We should probably modify this condition so
// that this can only happen when we are using the ATEN_HERMON
// encoding, and not other ATEN encodings. --
// ATEN uses 0x00 even when it is meant to be 0x59 // ATEN uses 0x00 even when it is meant to be 0x59
if (this._rfb_atenikvm && this._FBU.encoding === 0x00) { if (this._rfb_atenikvm && this._FBU.encoding === 0x00) {
this._FBU.encoding = 0x59; this._FBU.encoding = 0x59;
@ -1877,6 +1925,26 @@
sock._sQlen += 18; sock._sQlen += 18;
}, },
atenChangeVideoSettings: function (sock, lumaQt, chromaQt, subsamplingMode) {
if (!inRangeIncl(lumaQt, 0, 0xB))
throw 'Bad value: must have 0 <= lumaQt <= 0xB';
if (!inRangeIncl(chromaQt, 0, 0xB))
throw 'Bad value: must have 0 <= chromaQt <= 0xB';
if (subsamplingMode != 422 && subsamplingMode != 444)
throw 'Bad value: subsamplingMode must be one of 444, 422';
var buf = sock._sQ;
var offset = sock._sQlen;
buf[offset] = 0x32;
buf[offset + 1] = lumaQt;
buf[offset + 2] = chromaQt;
buf[offset + 3] = subsamplingMode >>> 8;
buf[offset + 4] = subsamplingMode;
sock._sQlen += 5;
},
pointerEvent: function (sock, x, y, mask) { pointerEvent: function (sock, x, y, mask) {
var buff = sock._sQ; var buff = sock._sQ;
var offset = sock._sQlen; var offset = sock._sQlen;
@ -2766,12 +2834,12 @@
Util.Error("Server sent compress level pseudo-encoding"); Util.Error("Server sent compress level pseudo-encoding");
}, },
ATEN: function () { ATEN_HERMON: function () {
if (this._FBU.aten_len === -1) { if (this._FBU.aten_len === -1) {
this._FBU.bytes = 8; this._FBU.bytes = 8;
if (this._sock.rQwait("ATEN", this._FBU.bytes)) { return false; } if (this._sock.rQwait("ATEN_HERMON", this._FBU.bytes)) { return false; }
this._FBU.bytes = 0; this._FBU.bytes = 0;
this._sock.rQskipBytes(4); this._sock.rQskipBytes(4); // N.B.(kelleyk): This is the "mysteryFlag".
this._FBU.aten_len = this._sock.rQshift32(); this._FBU.aten_len = this._sock.rQshift32();
if (this._FBU.width === 64896 && this._FBU.height === 65056) { if (this._FBU.width === 64896 && this._FBU.height === 65056) {
@ -2784,25 +2852,25 @@
return true; return true;
} }
if (this._fb_width !== this._FBU.width && this._fb_height !== this._FBU.height) { if (this._fb_width !== this._FBU.width && this._fb_height !== this._FBU.height) {
Util.Debug(">> ATEN resize desktop"); Util.Debug(">> ATEN_HERMON resize desktop");
this._fb_width = this._FBU.width; this._fb_width = this._FBU.width;
this._fb_height = this._FBU.height; this._fb_height = this._FBU.height;
this._onFBResize(this, this._fb_width, this._fb_height); this._onFBResize(this, this._fb_width, this._fb_height);
this._display.resize(this._fb_width, this._fb_height); this._display.resize(this._fb_width, this._fb_height);
Util.Debug("<< ATEN resize desktop"); Util.Debug("<< ATEN_HERMON resize desktop");
} }
} }
if (this._FBU.aten_type === -1) { if (this._FBU.aten_type === -1) {
this._FBU.bytes = 10; this._FBU.bytes = 10;
if (this._sock.rQwait("ATEN", this._FBU.bytes)) { return false; } if (this._sock.rQwait("ATEN_HERMON", this._FBU.bytes)) { return false; }
this._FBU.bytes = 0; this._FBU.bytes = 0;
this._FBU.aten_type = this._sock.rQshift8(); this._FBU.aten_type = this._sock.rQshift8();
this._sock.rQskip8(); this._sock.rQskip8();
this._sock.rQskipBytes(4); // number of subrects this._sock.rQskipBytes(4); // number of subrects
if (this._FBU.aten_len !== this._sock.rQshift32()) { if (this._FBU.aten_len !== this._sock.rQshift32()) {
return this._fail('ATEN RAW len mis-match'); return this._fail('ATEN_HERMON RAW len mis-match');
} }
this._FBU.aten_len -= 10; this._FBU.aten_len -= 10;
} }
@ -2811,7 +2879,7 @@
switch (this._FBU.aten_type) { switch (this._FBU.aten_type) {
case 0: // Subrects case 0: // Subrects
this._FBU.bytes = 6 + (16 * 16 * this._pixelFormat.Bpp); // at least a subrect this._FBU.bytes = 6 + (16 * 16 * this._pixelFormat.Bpp); // at least a subrect
if (this._sock.rQwait("ATEN", this._FBU.bytes)) { return false; } if (this._sock.rQwait("ATEN_HERMON", this._FBU.bytes)) { return false; }
var a = this._sock.rQshift16(); var a = this._sock.rQshift16();
var b = this._sock.rQshift16(); var b = this._sock.rQshift16();
var y = this._sock.rQshift8(); var y = this._sock.rQshift8();
@ -2828,7 +2896,7 @@
if (this._FBU.bytes > 0) return false; if (this._FBU.bytes > 0) return false;
break; break;
default: default:
return this._fail('unknown ATEN type: '+this._FBU.aten_type); return this._fail('unknown ATEN_HERMON type: '+this._FBU.aten_type);
} }
} }
@ -2843,6 +2911,97 @@
this._FBU.aten_len = -1; this._FBU.aten_len = -1;
this._FBU.aten_type = -1; this._FBU.aten_type = -1;
return true;
},
ATEN_AST2100: function () {
if (this._FBU.aten_len === -1) {
this._FBU.bytes = 8;
if (this._sock.rQwait("ATEN_AST2100", this._FBU.bytes)) { return false; }
// N.B.(kelleyk): I think that the mysteryFlag is 0 when in
// "text mode" (at the BIOS, without X started, etc.) and 1 when
// running X. Perhaps it's something to do with what mode the
// "video card" is being used in?
var mysteryFlag = this._sock.rQshift32();
// if (mysteryFlag != 0)
// console.log('Nonzero mysteryFlag (='+mysteryFlag+')! When does this occur?');
this._FBU.aten_len = this._sock.rQshift32();
}
// Actually read the data.
if (this._FBU.aten_len !== 0) {
this._FBU.bytes = this._FBU.aten_len;
if (this._sock.rQwait("ATEN_AST2100", this._FBU.bytes)) { return false; }
var data = this._sock.rQshiftBytes(this._FBU.aten_len);
}
// Without this, the code in _framebufferUpdate() will keep looping
// instead of realizing that it's finished and sending out a
// FramebufferUpdateRequest. It's important to make sure this code
// is called as soon as we have read any data we are going to read:
// in particular, it must be called BEFORE we might return true
// ("we're done"). Otherwise... infinite loop!
this._FBU.rects -= 1;
if (this._FBU.rects != 0)
throw 'Unexpected number of rects in FramebufferUpdate message; should always be 1!';
// N.B.(kelleyk): It's also very important for the way that this
// function works that aten_len wind up -1 before we ever return
// true; otherwise we'll fail to parse the two extra, ATEN-specific
// header fields (see above) the next time we get a FramebufferUpdate
// message.
this._FBU.aten_len = -1;
// These are -640 and -480 (as int16s), as in the other ATEN encodings.
if (this._FBU.width === 64896 && this._FBU.height === 65056) {
Util.Debug('Ast2100Decoder: screen is off.');
if (this._FBU.aten_len !== 0)
Util.Warn('Ast2100Decoder: warning: framebuffer dimensions ' +
'indicate that screen is off but data length is nonzero.');
return true;
} else if (this._FBU.aten_len === 0) {
// This seems to happen when the display is off (e.g. when you
// tell the machine to restart).
// TODO(kelleyk): Is there a way to tell noVNC that the display
// is "off"? Should we show a black screen, or otherwise indicate
// to the user that this is what is going on?
Util.Warn('Ast2100Decoder: warning: data length is zero, but ' +
'framebuffer dimensions are not -640x-480 (which is ' +
'typically given to indicate that the screen is off).');
return true;
}
if (!this._aten_ast2100_dec) {
var display = this._display;
this._aten_ast2100_dec = new Ast2100Decoder({
width: this._FBU.width,
height: this._FBU.height,
blitCallback: function (x, y, width, height, buf) {
// Last arguments here are offset, from_queue. 'from_queue'
// means 'should this block be rendered from the queue?', not
// 'is this block being rendered from the queue?'. It causes
// the block to be enqueued instead of being blitted right
// away via a call to _rgbxImageData().
display.blitRgbxImage(x, y, width, height, buf, 0, true);
}
});
}
// N.B.(kelleyk): Copied this block from ATEN_HERMON above.
if (this._fb_width !== this._FBU.width && this._fb_height !== this._FBU.height) {
Util.Debug(">> ATEN_AST2100 resize desktop");
this._fb_width = this._FBU.width;
this._fb_height = this._FBU.height;
this._onFBResize(this, this._fb_width, this._fb_height);
this._display.resize(this._fb_width, this._fb_height);
Util.Debug("<< ATEN_AST2100 resize desktop");
this._aten_ast2100_dec.setSize(this._FBU.width, this._FBU.height);
}
this._aten_ast2100_dec.decode(data);
return true; return true;
} }
}; };

View File

@ -123,6 +123,10 @@ module.exports = function(config) {
'core/des.js', 'core/des.js',
'core/display.js', 'core/display.js',
'core/inflator.js', 'core/inflator.js',
'core/ast2100/ast2100const.js',
'core/ast2100/ast2100util.js',
'core/ast2100/ast2100idct.js',
'core/ast2100/ast2100.js',
'tests/test.*.js' 'tests/test.*.js'
], ],

1
tests/frame4.hex Normal file

File diff suppressed because one or more lines are too long

384
tests/test.ast2100.js Normal file
View File

@ -0,0 +1,384 @@
// requires local modules: ast2100, ast2100idct, ast2100const, ast2100util
/* jshint expr: true */
var assert = chai.assert;
var expect = chai.expect;
// Convert a hex string, optionally containing spaces, into an array of integers representing bytes.
var parseHex = function (s) {
s = s.replace(/\s/g, '');
if (s.length % 2 != 0)
throw 'Hex data has uneven length!';
for (var bytes = [], i = 0; i < s.length; i += 2)
bytes.push(parseInt(s.substr(i, 2), 16));
return bytes;
};
function make_decoder () {
return new Ast2100Decoder({
width: 0x400,
height: 0x300,
blitCallback: function () {}
});
}
// Swap u32 byte order. Mutates input!
function buf_swap32 (data) {
for (var i = 0; i < data.length; i += 4) {
var tmp;
tmp = data[i+0];
data[i+0] = data[i+3];
data[i+3] = tmp;
tmp = data[i+1];
data[i+1] = data[i+2];
data[i+2] = tmp;
}
return data;
}
describe('ATEN_AST2100 video encoding', function() {
"use strict";
var dec;
beforeEach(function () {
dec = make_decoder();
dec._mcuLimit = 10;
});
describe('BitStream', function() {
var stream, stream2, stream3;
var data = buf_swap32(parseHex('f0f0 cccc cccc cccc cccc cccc cccc cccc'));
var data2 = parseHex('F0F1F2F3 F4F5F6F7 F8F9FAFB FCFDFEFF');
var data3 = parseHex('0b0b01bc45fbc5e020040101ffff3f20c0ffffff0000240000000000000000005028140a0000000000000000');
beforeEach(function () {
stream = new BitStream({data: data});
stream2 = new BitStream({data: data2});
stream3 = new BitStream({data: data3});
});
it('should pass simple sanity checks', function () {
expect(stream.read(4)).to.equal(0xF);
expect(stream.read(4)).to.equal(0x0);
expect(stream.read(4)).to.equal(0xF);
expect(stream.read(4)).to.equal(0x0);
});
it('should pass simple sanity checks (part II)', function () {
var i;
for (i = 0; i < 4; ++i)
expect(stream.read(1)).to.equal(1);
for (i = 0; i < 4; ++i)
expect(stream.read(1)).to.equal(0);
});
it('should be able to properly refill the buffer', function () {
expect(stream.read(4)).to.equal(0xF);
expect(stream.read(4)).to.equal(0x0);
expect(stream.read(4)).to.equal(0xF);
expect(stream.read(8)).to.equal(0x0C);
});
it('should properly swap byte order', function () {
expect(stream2.read(8)).to.equal(0xF3);
expect(stream2.read(8)).to.equal(0xF2);
expect(stream2.read(8)).to.equal(0xF1);
expect(stream2.read(8)).to.equal(0xF0);
expect(stream2.read(8)).to.equal(0xF7);
});
it('should properly handle skipping the full 32-bit read-buffer (data3)', function () {
// Must do this in two parts so that bits < 32 each time.
stream3.skip(16);
stream3.skip(16);
expect(stream3.read(4)).to.equal(0xE);
});
// it('issue repro', function () {
// var data = parseHex('f0f0 cccc cccc cccc cccc cccc cccc cccc');
// var stream = new BitStream({data: data});
// expect(stream.read(31)).to.equal(0xF0F0CCCC >>> 1);
// expect(stream.read(8)).to.equal(0x66);
// });
});
describe('quant tables', function() {
it('quant tables are properly loaded and scaled', function () {
// This is luma quant table #4.
var expected = Int32Array.from([
0x00090000, 0x0008527e, 0x00068866, 0x000a9537, 0x000d0000, 0x00114908, 0x000f274b, 0x0009616d,
0x0008527e, 0x000b8b14, 0x000caf8f, 0x00104f53, 0x00136b26, 0x0022df8f, 0x0018c594, 0x000b7b02,
0x0009255c, 0x000caf8f, 0x000f5d2c, 0x0013f8fd, 0x001cbe90, 0x0020d994, 0x001adebc, 0x000b2cc4,
0x00083b2b, 0x000eadca, 0x00126faf, 0x00161f78, 0x0020ecad, 0x002c58a1, 0x001ca316, 0x000b07c7,
0x000a0000, 0x0010a4fc, 0x001a219a, 0x002473bf, 0x00260000, 0x002fed69, 0x001ed922, 0x000bdd19,
0x000a36ca, 0x0014b4bd, 0x001ecbfa, 0x00214279, 0x00235b34, 0x0023cdea, 0x001ac9de, 0x000b0e2f,
0x000e9cbf, 0x001b0616, 0x001e67d4, 0x001e8bd4, 0x001ed922, 0x001cea24, 0x00139fb4, 0x00085c96,
0x000b0935, 0x00138450, 0x00131afd, 0x0011d7e1, 0x001161b4, 0x000c23a7, 0x000882d0, 0x00042fc6
]);
dec._loadQuantTable(0, ATEN_QT_LUMA[4]);
var luma_quant_table = dec.quantTables[0];
expect(luma_quant_table).to.deep.equal(expected);
});
});
describe('IDCT', function () {
var outputBuf;
beforeEach(function () {
outputBuf = new Uint8Array(DCTSIZE2); // equivalent to one of the componentBufs in Ast2100Decoder.
});
// TODO: Fix typing: variables should be typed arrays.
it('test case 0 - DC value only', function () {
// this is the output of the VLC / entropy coding process
// XXX: @KK: Why is this 16-bit?
var dataUnit = [ // new Int16Array(
0xFF9C,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
var expected_idct_output = Uint8Array.from([ // new Uint8Array(
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF]);
dec._loadQuantTable(0, ATEN_QT_LUMA[4]);
var luma_quant_table = dec.quantTables[0];
AST2100IDCT.idct_fixed_aan(luma_quant_table, dataUnit, outputBuf);
// console.log(result);
console.log('expected:');
console.log(fmt_u8a(expected_idct_output));
console.log('result:');
console.log(fmt_u8a(outputBuf));
expect(outputBuf).to.deep.equal(expected_idct_output);
});
it('test case 1', function () {
// this is the output of the VLC / entropy coding process
// XXX: @KK: Why is this 16-bit?
var dataUnit = Int16Array.from([
0xFFBD,0,0,0,0,0,0,0,
0xFFC3,0,0,0,0,0,0,0,
0x26,0,0,0,0,0,0,0,
0xFFED,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,
6,0,0,0,0,0,0,0,
0xFFFC,0,0,0,0,0,0,0,
2,0,0,0,0,0,0,0]);
var expected = Uint8Array.from([
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
0xE,0xE,0xE,0xE,0xE,0xE,0xE,0xE,
0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,
0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1]);
dec._loadQuantTable(0, ATEN_QT_LUMA[4]);
var luma_quant_table = dec.quantTables[0];
AST2100IDCT.idct_fixed_aan(luma_quant_table, dataUnit, outputBuf);
/*
console.log('expected:');
console.log(fmt_u8a(expected));
console.log('result:');
console.log(fmt_u8a(outputBuf));
*/
// XXX: TEMPORARY -- figure out this rounding issue
// expect(result).to.deep.equal(expected);
var maxErr = 0;
for (i = 0; i < 64; ++i)
maxErr = Math.max(maxErr, Math.abs(outputBuf[i] - expected[i]));
if (maxErr > 1)
throw 'Error too high!';
});
it('test case 2', function () {
// this is the output of the VLC / entropy coding process
// XXX: @KK: Why is this 16-bit?
var dataUnit = Int16Array.from([
0xFF9B, 0, 0, 0, 0, 0, 0, 0, 0xFFA4, 0, 0, 0, 0, 0, 0, 0, 0x35, 0, 0, 0, 0, 0, 0, 0,
0xFFE6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0xFFFA,
0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0]);
var expected = [
0xE, 0xE, 0xE, 0xE, 0xE, 0xE, 0xE, 0xE, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1];
// Between IDCT passes, the workspace should look like this:
// [-903, 0, 0, 0, 0, 0, 0, 0, -874, 0, 0, 0, 0, 0, 0, 0, -914, 0, 0, 0, 0, 0, 0, 0, -874, 0, 0, 0, 0, 0, 0, 0,
// -915, 0, 0, 0, 0, 0, 0, 0, -868, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 266, 0, 0, 0, 0, 0, 0, 0]
dec._loadQuantTable(0, ATEN_QT_LUMA[5]);
var luma_quant_table = dec.quantTables[0];
AST2100IDCT.idct_fixed_aan(luma_quant_table, dataUnit, outputBuf);
// console.log(result);
console.log('expected:');
console.log(fmt_u8a(expected));
console.log('result:');
console.log(fmt_u8a(outputBuf));
// XXX: TEMPORARY -- figure out this rounding issue
// expect(result).to.deep.equal(expected);
var maxErr = 0;
for (i = 0; i < 64; ++i)
maxErr = Math.max(maxErr, Math.abs(outputBuf[i] - expected[i]));
if (maxErr > 1)
throw 'Error too high!';
});
});
describe('VQ', function () {
it('should successfully load a codebook (colors)', function () {
var data = buf_swap32(parseHex('bc010b0b e0c5fb45 01010420 203fffff ffffffc0 00240000 00000000 00000000 0a142850'));
data = data.slice(4);
var stream = new BitStream({data: data}); // Strip off quant table selectors and subsampling mode.
var controlFlag = stream.read(4);
expect(controlFlag).to.equal(0xE);
var xMcuPos = stream.read(8), yMcuPos = stream.read(8);
expect(xMcuPos).to.equal(0xC);
expect(yMcuPos).to.equal(0x5F);
dec.subsamplingMode = 444; // required or an assertion in the VQ code will fail
dec._stream = stream;
dec._parseVqBlock(1); // codewordSize=1
expect(dec._vqCodewordLookup).to.deep.equal([1, 0, 2, 3]);
expect(dec._vqCodebook).to.deep.equal([
[0x10, 0x80, 0x80],
[0xA2, 0x80, 0x80],
[0x80, 0x80, 0x80],
[0xC0, 0x80, 0x80]
]);
});
it('should correctly handle multiple data blocks', function () {
// This data is from a memory dump. It should contain two VQ (0xE-type) blocks and then an 0x9 (end-frame).
var data = buf_swap32(parseHex('bc010b0b e0c5fb45 01010420 203fffff ffffffc0 00240000 00000000 00000000 0a142850' + '00000000 00000000'));
console.log('VQ multiple block data:');
console.log(fmt_u8a(data));
dec.decode(data);
// TODO: improve asserts/etc. beyond just 'finished without error'
});
});
describe('Full JPEG subsampled MCU example 0', function () {
// Corresponds to whole-mcu-example / main_mcu_test_2()
var data = parseHex('040701a61bff6280f81fbaa2ff4dbc408dfccf405c15ff1f004800f500000000000000002dd4a462237ced2c9c25dca4cef7e6667d6626f3308063c3c7a1b7335badc24aaf0b5e9daf69590fcb96206b59e6f2ccb2bdd386b17dbb72182080d6288a2a1f2f0608a0fe87ae287f132f1023ff33d057c5ff470012403d0000000000000000ec1fb06cdacd2bdf9d79f3cde7f9f1362b7ce914ba521c79524a5bdc212a5ed47c3cbc6c7e072ae3e3c18384785f6ca6d4e22fb1af0c96449d1847a9c7037e966fa3ac8d4990830339463d277f25fc6f30ffe5f4f5cfec9f4ffff8bf00a0f5d358a2ab6e4e6778d929f686bd58ca9c26b8f7338f15105065dd39c718e219a78e');
it('should decode properly', function () {
// DOES NOT APPEAR TO correspond to my notes in 'whole-mcu-example.txt'.
dec._blitCallback = function (x, y, width, height, buf) {
console.log({x:x, y:y, width:width, height:height, buf:buf});
console.log(fmt_rgb_buf(width, buf));
};
dec.decode(data);
});
});
describe('Full JPEG subsampled MCU example 1', function () {
/* TODO: this looks VERRRRRY SIMILAR to the above example */
var data = parseHex('040701a61bff6280f81fbaa2ff4dbc408dfccf405c15ff1f004800f500000000000000002dd4a462237ced2c9c25dca4cef7e6667d6626f3308063c3c7a1b7335badc24aaf0b5e9daf69590fcb96206b59e6f2ccb2bdd386b17dbb72182080d6288a2a1f2f0608a0fe87ae287f132f1023ff33d057c5ff470012403d0000000000000000ec1fb06cdacd2bdf9d79f3cde7f9f1362b7ce914ba521c79524a5bdc212a5ed47c3cbc6c7e072ae3e3c18384785f6ca6d4e22fb1af0c96449d1847a9c7037e966fa3ac8d4990830339463d277f25fc6f30ffe5f4f5cfec9f4ffff8bf00a0f5d358a2ab6e4e6778d929f686bd58ca9c26b8f7338f15105065dd39c718');
/*
it('should load quant tables and set subsamplingMode', function () {
dec.decode(data);
expect(dec._loadedQuantTables[0]).to.equal(4);
expect(dec._loadedQuantTables[1]).to.equal(7);
expect(dec.subsamplingMode).to.equal(422);
});
*/
it('should decode properly', function () {
// DOES NOT APPEAR TO correspond to my notes in 'whole-mcu-example.txt'.
dec._blitCallback = function (x, y, width, height, buf) {
console.log({x:x, y:y, width:width, height:height, buf:buf});
console.log(fmt_rgb_buf(width, buf));
};
dec.decode(data);
});
});
// TODO: On the 'full-frame decode' and 'frame udpate decode' tests, we are NOT actually asserting anything about the code's output yet.
/*
describe('Full frame decode test', function () {
it('should decode properly', function () {
throw "Won't work without jQuery (or some other way of loading data).";
dec._blitCallback = function (x, y, width, height, buf) {
if (x == 0 && y == 0) {
console.log({x:x, y:y, width:width, height:height, buf:buf});
console.log(fmt_rgb_buf(width, buf));
}
};
// TODO: This path won't work for other people, and the test case needs to be made to understand that this
// is async; plus, I had to manually add jQuery to the generated HTML.
$.get("/novnc-tests/tests/frame4.hex", function (data) {
data = parseHex(data);
dec.decode(data);
});
});
});
*/
describe('Frame update decode test', function () {
it('should decode properly', function () {
var data = parseHex('050501a69a3f6080008aa2a8000000900000000000000000');
console.log(data);
// this data should NOT be solid-white!
dec._blitCallback = function (x, y, width, height, buf) {
console.log({x:x, y:y, width:width, height:height, buf:buf});
console.log(fmt_rgb_buf(width, buf));
};
dec.decode(data);
});
});
});

View File

@ -759,8 +759,17 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._rfb_init_state = 'Security'; client._rfb_init_state = 'Security';
}); });
function send_security(type, cl) { // N.B.(kelleyk): 'types' may be either a single number or an array of numbers.
cl._sock._websocket._receive_data(new Uint8Array([1, type])); function send_security(types, cl) {
if (types.constructor !== Array)
types = [types];
var data = new Uint8Array(1 + types.length);
data[0] = types.length;
for (var i = 0; i < types.length; ++i)
data[i+1] = types[i];
cl._sock._websocket._receive_data(data);
} }
it('should fail on auth scheme 0 (pre 3.7) with the given message', function () { it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
@ -906,7 +915,13 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._sock._websocket._open(); client._sock._websocket._open();
client._rfb_init_state = 'Security'; client._rfb_init_state = 'Security';
client._rfb_version = 3.8; client._rfb_version = 3.8;
send_security(16, client); // N.B.(kelleyk): Actual TightVNC servers support more than
// just 16 ("tight"), whereas ATEN iKVM servers advertise only
// 16. This is a large part of how we detect that we are
// speaking to an iKVM server, so if we want to test the
// client's interaction with a TightVNC server, we need to
// send more than just the one.
send_security([1, 16], client);
client._sock._websocket._get_sent_data(); // skip the security reply client._sock._websocket._get_sent_data(); // skip the security reply
}); });
@ -995,6 +1010,8 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._sock._websocket._open(); client._sock._websocket._open();
client._rfb_init_state = 'Security'; client._rfb_init_state = 'Security';
client._rfb_version = 3.8; client._rfb_version = 3.8;
// N.B.(kelleyk): ATEN iKVM server advertise *only* 16 ("tight"),
// which is how we detect them.
send_security(16, client); send_security(16, client);
client._sock._websocket._get_sent_data(); // skip the security reply client._sock._websocket._get_sent_data(); // skip the security reply
client._rfb_password = 'test1:test2'; client._rfb_password = 'test1:test2';
@ -1018,6 +1035,17 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._rfb_atenikvm).to.be.true; expect(client._rfb_atenikvm).to.be.true;
expect(client._sock).to.have.sent(auth); expect(client._sock).to.have.sent(auth);
}); });
it('via new style method', function () {
client._sock._websocket._receive_data(new Uint8Array([
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0]));
expect(client._rfb_tightvnc).to.be.false;
expect(client._rfb_atenikvm).to.be.true;
expect(client._sock).to.have.sent(auth);
});
}); });
}); });

View File

@ -81,7 +81,9 @@
WebUtil.load_scripts({ WebUtil.load_scripts({
'core': ["base64.js", "websock.js", "des.js", "input/keysymdef.js", 'core': ["base64.js", "websock.js", "des.js", "input/keysymdef.js",
"input/xtscancodes.js", "input/util.js", "input/devices.js", "input/xtscancodes.js", "input/util.js", "input/devices.js",
"display.js", "inflator.js", "rfb.js", "input/keysym.js"]}); "ast2100/ast2100.js", "ast2100/ast2100idct.js", "ast2100/ast2100util.js", "ast2100/ast2100const.js",
"display.js", "inflator.js", "rfb.js", "input/keysym.js"],
'app': [WebUtil.getLanguageFileLocation(), "webutil.js"]});
var rfb; var rfb;
var resizeTimeout; var resizeTimeout;