Best JavaScript code snippet using redwood
corba.js
Source:corba.js
...422 return false;423};424handle_message = function() {425 //Util.Debug(">> handle_message ws.rQlen(): " + ws.rQlen());426 //Util.Debug("ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");427 if (ws.rQlen() === 0) {428 Util.Warn("handle_message called on empty receive queue");429 return;430 }431 switch (corba_state) {432 case 'disconnected':433 case 'failed':434 Util.Error("Got data while disconnected");435 break;436 case 'normal':437 if (normal_msg() && ws.rQlen() > 0) {438 // true means we can continue processing439 // Give other events a chance to run440 if (msgTimer === null) {441 Util.Debug("More data to process, creating timer");442 msgTimer = setTimeout(function () {443 msgTimer = null;444 handle_message();445 }, 10);446 } else {447 Util.Debug("More data to process, existing timer");448 }449 }450 break;451 default:452 init_msg();453 break;454 }455};456function genDES(password, challenge) {457 var i, passwd = [];458 for (i=0; i < password.length; i += 1) {459 passwd.push(password.charCodeAt(i));460 }461 return (new DES(passwd)).encrypt(challenge);462}463function flushClient() {464 if (mouse_arr.length > 0) {465 //send(mouse_arr.concat(fbUpdateRequests()));466 ws.send(mouse_arr);467 setTimeout(function() {468 ws.send(fbUpdateRequests());469 }, 50);470 mouse_arr = [];471 return true;472 } else {473 return false;474 }475}476// overridable for testing477checkEvents = function() {478 var now;479 if (corba_state === 'normal' && !viewportDragging) {480 if (! flushClient()) {481 now = new Date().getTime();482 if (now > last_req_time + conf.fbu_req_rate) {483 last_req_time = now;484 ws.send(fbUpdateRequests());485 }486 }487 }488 setTimeout(checkEvents, conf.check_rate);489};490keyPress = function(keysym, down) {491 var arr;492 if (conf.view_only) { return; } // View only, skip keyboard events493 arr = keyEvent(keysym, down);494 arr = arr.concat(fbUpdateRequests());495 ws.send(arr);496};497mouseButton = function(x, y, down, bmask) {498 if (down) {499 mouse_buttonMask |= bmask;500 } else {501 mouse_buttonMask ^= bmask;502 }503 if (conf.viewportDrag) {504 if (down && !viewportDragging) {505 viewportDragging = true;506 viewportDragPos = {'x': x, 'y': y};507 // Skip sending mouse events508 return;509 } else {510 viewportDragging = false;511 ws.send(fbUpdateRequests()); // Force immediate redraw512 }513 }514 if (conf.view_only) { return; } // View only, skip mouse events515 mouse_arr = mouse_arr.concat(516 pointerEvent(display.absX(x), display.absY(y)) );517 flushClient();518};519mouseMove = function(x, y) {520 //Util.Debug('>> mouseMove ' + x + "," + y);521 var deltaX, deltaY;522 if (viewportDragging) {523 //deltaX = x - viewportDragPos.x; // drag viewport524 deltaX = viewportDragPos.x - x; // drag frame buffer525 //deltaY = y - viewportDragPos.y; // drag viewport526 deltaY = viewportDragPos.y - y; // drag frame buffer527 viewportDragPos = {'x': x, 'y': y};528 display.viewportChange(deltaX, deltaY);529 // Skip sending mouse events530 return;531 }532 if (conf.view_only) { return; } // View only, skip mouse events533 mouse_arr = mouse_arr.concat(534 pointerEvent(display.absX(x), display.absY(y)) );535};536//537// Server message handlers538//539// CORBA/mirror4cast initialisation message handler540init_msg = function() {541 //Util.Debug(">> init_msg [corba_state '" + corba_state + "']");542 var strlen, reason, length, sversion, cversion, repeaterID,543 i, types, num_types, challenge, response, bpp, depth,544 big_endian, red_max, green_max, blue_max, red_shift,545 green_shift, blue_shift, true_color, name_length, is_repeater;546 //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0));547 switch (corba_state) {548 case 'ProtocolVersion' :549 if (ws.rQlen() < 12) {550 return fail("Incomplete protocol version");551 }552 sversion = ws.rQshiftStr(12).substr(4,7);553 Util.Info("Server ProtocolVersion: " + sversion);554 is_repeater = 0;555 switch (sversion) {556 case "000.000": is_repeater = 1; break; // Ultramirror4cast repeater557 case "003.003": corba_version = 3.3; break;558 case "003.006": corba_version = 3.3; break; // Ultramirror4cast559 case "003.889": corba_version = 3.3; break; // Apple Remote Desktop560 case "003.007": corba_version = 3.7; break;561 case "003.008": corba_version = 3.8; break;562 case "004.000": corba_version = 3.8; break; // Intel AMT KVM563 case "004.001": corba_version = 3.8; break; // Realmirror4cast 4.6564 default:565 return fail("Invalid server version " + sversion);566 }567 if (is_repeater) { 568 repeaterID = conf.repeaterID;569 while (repeaterID.length < 250) {570 repeaterID += "\0";571 }572 ws.send_string(repeaterID);573 break;574 }575 if (corba_version > corba_max_version) { 576 corba_version = corba_max_version;577 }578 if (! test_mode) {579 sendTimer = setInterval(function() {580 // Send updates either at a rate of one update581 // every 50ms, or whatever slower rate the network582 // can handle.583 ws.flush();584 }, 50);585 }586 cversion = "00" + parseInt(corba_version,10) +587 ".00" + ((corba_version * 10) % 10);588 ws.send_string("RFB " + cversion + "\n");589 updateState('Security', "Sent ProtocolVersion: " + cversion);590 break;591 case 'Security' :592 if (corba_version >= 3.7) {593 // Server sends supported list, client decides 594 num_types = ws.rQshift8();595 if (ws.rQwait("security type", num_types, 1)) { return false; }596 if (num_types === 0) {597 strlen = ws.rQshift32();598 reason = ws.rQshiftStr(strlen);599 return fail("Security failure: " + reason);600 }601 corba_auth_scheme = 0;602 types = ws.rQshiftBytes(num_types);603 Util.Debug("Server security types: " + types);604 for (i=0; i < types.length; i+=1) {605 if ((types[i] > corba_auth_scheme) && (types[i] < 3)) {606 corba_auth_scheme = types[i];607 }608 }609 if (corba_auth_scheme === 0) {610 return fail("Unsupported security types: " + types);611 }612 613 ws.send([corba_auth_scheme]);614 } else {615 // Server decides616 if (ws.rQwait("security scheme", 4)) { return false; }617 corba_auth_scheme = ws.rQshift32();618 }619 updateState('Authentication',620 "Authenticating using scheme: " + corba_auth_scheme);621 init_msg(); // Recursive fallthrough (workaround JSLint complaint)622 break;623 // Triggered by fallthough, not by server message624 case 'Authentication' :625 //Util.Debug("Security auth scheme: " + corba_auth_scheme);626 switch (corba_auth_scheme) {627 case 0: // connection failed628 if (ws.rQwait("auth reason", 4)) { return false; }629 strlen = ws.rQshift32();630 reason = ws.rQshiftStr(strlen);631 return fail("Auth failure: " + reason);632 case 1: // no authentication633 if (corba_version >= 3.8) {634 updateState('SecurityResult');635 return;636 }637 // Fall through to ClientInitialisation638 break;639 case 2: // mirror4cast authentication640 if (corba_password.length === 0) {641 // Notify via both callbacks since it is kind of642 // a CORBA state change and a UI interface issue.643 updateState('password', "Password Required");644 conf.onPasswordRequired(that);645 return;646 }647 if (ws.rQwait("auth challenge", 16)) { return false; }648 challenge = ws.rQshiftBytes(16);649 //Util.Debug("Password: " + corba_password);650 //Util.Debug("Challenge: " + challenge +651 // " (" + challenge.length + ")");652 response = genDES(corba_password, challenge);653 //Util.Debug("Response: " + response +654 // " (" + response.length + ")");655 656 //Util.Debug("Sending DES encrypted auth response");657 ws.send(response);658 updateState('SecurityResult');659 return;660 default:661 fail("Unsupported auth scheme: " + corba_auth_scheme);662 return;663 }664 updateState('ClientInitialisation', "No auth required");665 init_msg(); // Recursive fallthrough (workaround JSLint complaint)666 break;667 case 'SecurityResult' :668 if (ws.rQwait("Mirror4cast auth response ", 4)) { return false; }669 switch (ws.rQshift32()) {670 case 0: // OK671 // Fall through to ClientInitialisation672 break;673 case 1: // failed674 if (corba_version >= 3.8) {675 length = ws.rQshift32();676 if (ws.rQwait("SecurityResult reason", length, 8)) {677 return false;678 }679 reason = ws.rQshiftStr(length);680 fail(reason);681 } else {682 fail("Authentication failed");683 }684 return;685 case 2: // too-many686 return fail("Too many auth attempts");687 }688 updateState('ClientInitialisation', "Authentication OK");689 init_msg(); // Recursive fallthrough (workaround JSLint complaint)690 break;691 // Triggered by fallthough, not by server message692 case 'ClientInitialisation' :693 ws.send([conf.shared ? 1 : 0]); // ClientInitialisation694 updateState('ServerInitialisation', "Authentication OK");695 break;696 case 'ServerInitialisation' :697 if (ws.rQwait("server initialization", 24)) { return false; }698 /* Screen size */699 fb_width = ws.rQshift16();700 fb_height = ws.rQshift16();701 /* PIXEL_FORMAT */702 bpp = ws.rQshift8();703 depth = ws.rQshift8();704 big_endian = ws.rQshift8();705 true_color = ws.rQshift8();706 red_max = ws.rQshift16();707 green_max = ws.rQshift16();708 blue_max = ws.rQshift16();709 red_shift = ws.rQshift8();710 green_shift = ws.rQshift8();711 blue_shift = ws.rQshift8();712 ws.rQshiftStr(3); // padding713 Util.Info("Screen: " + fb_width + "x" + fb_height + 714 ", bpp: " + bpp + ", depth: " + depth +715 ", big_endian: " + big_endian +716 ", true_color: " + true_color +717 ", red_max: " + red_max +718 ", green_max: " + green_max +719 ", blue_max: " + blue_max +720 ", red_shift: " + red_shift +721 ", green_shift: " + green_shift +722 ", blue_shift: " + blue_shift);723 if (big_endian !== 0) {724 Util.Warn("Server native endian is not little endian");725 }726 if (red_shift !== 16) {727 Util.Warn("Server native red-shift is not 16");728 }729 if (blue_shift !== 0) {730 Util.Warn("Server native blue-shift is not 0");731 }732 /* Connection name/title */733 name_length = ws.rQshift32();734 fb_name = ws.rQshiftStr(name_length);735 736 if (conf.true_color && fb_name === "Intel(r) AMT KVM")737 {738 Util.Warn("Intel AMT KVM only support 8/16 bit depths. Disabling true color");739 conf.true_color = false;740 }741 display.set_true_color(conf.true_color);742 display.resize(fb_width, fb_height);743 keyboard.grab();744 mouse.grab();745 if (conf.true_color) {746 fb_Bpp = 4;747 fb_depth = 3;748 } else {749 fb_Bpp = 1;750 fb_depth = 1;751 }752 response = pixelFormat();753 response = response.concat(clientEncodings());754 response = response.concat(fbUpdateRequests());755 timing.fbu_rt_start = (new Date()).getTime();756 timing.pixels = 0;757 ws.send(response);758 759 /* Start pushing/polling */760 setTimeout(checkEvents, conf.check_rate);761 if (conf.encrypt) {762 updateState('normal', "Connected (encrypted) to: " + fb_name);763 } else {764 updateState('normal', "Connected (unencrypted) to: " + fb_name);765 }766 break;767 }768 //Util.Debug("<< init_msg");769};770/* Normal CORBA/mirror4cast server message handler */771normal_msg = function() {772 //Util.Debug(">> normal_msg");773 var ret = true, msg_type, length, text,774 c, first_colour, num_colours, red, green, blue;775 if (FBU.rects > 0) {776 msg_type = 0;777 } else {778 msg_type = ws.rQshift8();779 }780 switch (msg_type) {781 case 0: // FramebufferUpdate782 ret = framebufferUpdate(); // false means need more data783 break;784 case 1: // SetColourMapEntries785 Util.Debug("SetColourMapEntries");786 ws.rQshift8(); // Padding787 first_colour = ws.rQshift16(); // First colour788 num_colours = ws.rQshift16();789 if (ws.rQwait("SetColourMapEntries", num_colours*6, 6)) { return false; }790 791 for (c=0; c < num_colours; c+=1) { 792 red = ws.rQshift16();793 //Util.Debug("red before: " + red);794 red = parseInt(red / 256, 10);795 //Util.Debug("red after: " + red);796 green = parseInt(ws.rQshift16() / 256, 10);797 blue = parseInt(ws.rQshift16() / 256, 10);798 display.set_colourMap([blue, green, red], first_colour + c);799 }800 Util.Debug("colourMap: " + display.get_colourMap());801 Util.Info("Registered " + num_colours + " colourMap entries");802 //Util.Debug("colourMap: " + display.get_colourMap());803 break;804 case 2: // Bell805 Util.Debug("Bell");806 conf.onBell(that);807 break;808 case 3: // ServerCutText809 Util.Debug("ServerCutText");810 if (ws.rQwait("ServerCutText header", 7, 1)) { return false; }811 ws.rQshiftBytes(3); // Padding812 length = ws.rQshift32();813 if (ws.rQwait("ServerCutText", length, 8)) { return false; }814 text = ws.rQshiftStr(length);815 conf.clipboardReceive(that, text); // Obsolete816 conf.onClipboard(that, text);817 break;818 default:819 fail("Disconnected: illegal server message type " + msg_type);820 Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));821 break;822 }823 //Util.Debug("<< normal_msg");824 return ret;825};826framebufferUpdate = function() {827 var now, hdr, fbu_rt_diff, ret = true;828 if (FBU.rects === 0) {829 //Util.Debug("New FBU: ws.rQslice(0,20): " + ws.rQslice(0,20));830 if (ws.rQwait("FBU header", 3)) {831 ws.rQunshift8(0); // FBU msg_type832 return false;833 }834 ws.rQshift8(); // padding835 FBU.rects = ws.rQshift16();836 //Util.Debug("FramebufferUpdate, rects:" + FBU.rects);837 FBU.bytes = 0;838 timing.cur_fbu = 0;839 if (timing.fbu_rt_start > 0) {840 now = (new Date()).getTime();841 Util.Info("First FBU latency: " + (now - timing.fbu_rt_start));842 }843 }844 while (FBU.rects > 0) {845 if (corba_state !== "normal") {846 return false;847 }848 if (ws.rQwait("FBU", FBU.bytes)) { return false; }849 if (FBU.bytes === 0) {850 if (ws.rQwait("rect header", 12)) { return false; }851 /* New FramebufferUpdate */852 hdr = ws.rQshiftBytes(12);853 FBU.x = (hdr[0] << 8) + hdr[1];854 FBU.y = (hdr[2] << 8) + hdr[3];855 FBU.width = (hdr[4] << 8) + hdr[5];856 FBU.height = (hdr[6] << 8) + hdr[7];857 FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +858 (hdr[10] << 8) + hdr[11], 10);859 conf.onFBUReceive(that,860 {'x': FBU.x, 'y': FBU.y,861 'width': FBU.width, 'height': FBU.height,862 'encoding': FBU.encoding,863 'encodingName': encNames[FBU.encoding]});864 if (encNames[FBU.encoding]) {865 // Debug:866 /*867 var msg = "FramebufferUpdate rects:" + FBU.rects;868 msg += " x: " + FBU.x + " y: " + FBU.y;869 msg += " width: " + FBU.width + " height: " + FBU.height;870 msg += " encoding:" + FBU.encoding;871 msg += "(" + encNames[FBU.encoding] + ")";872 msg += ", ws.rQlen(): " + ws.rQlen();873 Util.Debug(msg);874 */875 } else {876 fail("Disconnected: unsupported encoding " +877 FBU.encoding);878 return false;879 }880 }881 timing.last_fbu = (new Date()).getTime();882 ret = encHandlers[FBU.encoding]();883 now = (new Date()).getTime();884 timing.cur_fbu += (now - timing.last_fbu);885 if (ret) {886 encStats[FBU.encoding][0] += 1;887 encStats[FBU.encoding][1] += 1;888 timing.pixels += FBU.width * FBU.height;889 }890 if (timing.pixels >= (fb_width * fb_height)) {891 if (((FBU.width === fb_width) &&892 (FBU.height === fb_height)) ||893 (timing.fbu_rt_start > 0)) {894 timing.full_fbu_total += timing.cur_fbu;895 timing.full_fbu_cnt += 1;896 Util.Info("Timing of full FBU, cur: " +897 timing.cur_fbu + ", total: " +898 timing.full_fbu_total + ", cnt: " +899 timing.full_fbu_cnt + ", avg: " +900 (timing.full_fbu_total /901 timing.full_fbu_cnt));902 }903 if (timing.fbu_rt_start > 0) {904 fbu_rt_diff = now - timing.fbu_rt_start;905 timing.fbu_rt_total += fbu_rt_diff;906 timing.fbu_rt_cnt += 1;907 Util.Info("full FBU round-trip, cur: " +908 fbu_rt_diff + ", total: " +909 timing.fbu_rt_total + ", cnt: " +910 timing.fbu_rt_cnt + ", avg: " +911 (timing.fbu_rt_total /912 timing.fbu_rt_cnt));913 timing.fbu_rt_start = 0;914 }915 }916 if (! ret) {917 return ret; // false ret means need more data918 }919 }920 conf.onFBUComplete(that,921 {'x': FBU.x, 'y': FBU.y,922 'width': FBU.width, 'height': FBU.height,923 'encoding': FBU.encoding,924 'encodingName': encNames[FBU.encoding]});925 return true; // We finished this FBU926};927//928// FramebufferUpdate encodings929//930encHandlers.RAW = function display_raw() {931 //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)");932 var cur_y, cur_height;933 if (FBU.lines === 0) {934 FBU.lines = FBU.height;935 }936 FBU.bytes = FBU.width * fb_Bpp; // At least a line937 if (ws.rQwait("RAW", FBU.bytes)) { return false; }938 cur_y = FBU.y + (FBU.height - FBU.lines);939 cur_height = Math.min(FBU.lines,940 Math.floor(ws.rQlen()/(FBU.width * fb_Bpp)));941 display.blitImage(FBU.x, cur_y, FBU.width, cur_height,942 ws.get_rQ(), ws.get_rQi());943 ws.rQshiftBytes(FBU.width * cur_height * fb_Bpp);944 FBU.lines -= cur_height;945 if (FBU.lines > 0) {946 FBU.bytes = FBU.width * fb_Bpp; // At least another line947 } else {948 FBU.rects -= 1;949 FBU.bytes = 0;950 }951 //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)");952 return true;953};954encHandlers.COPYRECT = function display_copy_rect() {955 //Util.Debug(">> display_copy_rect");956 var old_x, old_y;957 if (ws.rQwait("COPYRECT", 4)) { return false; }958 display.renderQ_push({959 'type': 'copy',960 'old_x': ws.rQshift16(),961 'old_y': ws.rQshift16(),962 'x': FBU.x,963 'y': FBU.y,964 'width': FBU.width,965 'height': FBU.height});966 FBU.rects -= 1;967 FBU.bytes = 0;968 return true;969};970encHandlers.RRE = function display_rre() {971 //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)");972 var color, x, y, width, height, chunk;973 if (FBU.subrects === 0) {974 if (ws.rQwait("RRE", 4+fb_Bpp)) { return false; }975 FBU.subrects = ws.rQshift32();976 color = ws.rQshiftBytes(fb_Bpp); // Background977 display.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);978 }979 while ((FBU.subrects > 0) && (ws.rQlen() >= (fb_Bpp + 8))) {980 color = ws.rQshiftBytes(fb_Bpp);981 x = ws.rQshift16();982 y = ws.rQshift16();983 width = ws.rQshift16();984 height = ws.rQshift16();985 display.fillRect(FBU.x + x, FBU.y + y, width, height, color);986 FBU.subrects -= 1;987 }988 //Util.Debug(" display_rre: rects: " + FBU.rects +989 // ", FBU.subrects: " + FBU.subrects);990 if (FBU.subrects > 0) {991 chunk = Math.min(rre_chunk_sz, FBU.subrects);992 FBU.bytes = (fb_Bpp + 8) * chunk;993 } else {994 FBU.rects -= 1;995 FBU.bytes = 0;996 }997 //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes);998 return true;999};1000encHandlers.HEXTILE = function display_hextile() {1001 //Util.Debug(">> display_hextile");1002 var subencoding, subrects, color, cur_tile,1003 tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh,1004 rQ = ws.get_rQ(), rQi = ws.get_rQi(); 1005 if (FBU.tiles === 0) {1006 FBU.tiles_x = Math.ceil(FBU.width/16);1007 FBU.tiles_y = Math.ceil(FBU.height/16);1008 FBU.total_tiles = FBU.tiles_x * FBU.tiles_y;1009 FBU.tiles = FBU.total_tiles;1010 }1011 /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */1012 while (FBU.tiles > 0) {1013 FBU.bytes = 1;1014 if (ws.rQwait("HEXTILE subencoding", FBU.bytes)) { return false; }1015 subencoding = rQ[rQi]; // Peek1016 if (subencoding > 30) { // Raw1017 fail("Disconnected: illegal hextile subencoding " + subencoding);1018 //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));1019 return false;1020 }1021 subrects = 0;1022 cur_tile = FBU.total_tiles - FBU.tiles;1023 tile_x = cur_tile % FBU.tiles_x;1024 tile_y = Math.floor(cur_tile / FBU.tiles_x);1025 x = FBU.x + tile_x * 16;1026 y = FBU.y + tile_y * 16;1027 w = Math.min(16, (FBU.x + FBU.width) - x);1028 h = Math.min(16, (FBU.y + FBU.height) - y);1029 /* Figure out how much we are expecting */1030 if (subencoding & 0x01) { // Raw1031 //Util.Debug(" Raw subencoding");1032 FBU.bytes += w * h * fb_Bpp;1033 } else {1034 if (subencoding & 0x02) { // Background1035 FBU.bytes += fb_Bpp;1036 }1037 if (subencoding & 0x04) { // Foreground1038 FBU.bytes += fb_Bpp;1039 }1040 if (subencoding & 0x08) { // AnySubrects1041 FBU.bytes += 1; // Since we aren't shifting it off1042 if (ws.rQwait("hextile subrects header", FBU.bytes)) { return false; }1043 subrects = rQ[rQi + FBU.bytes-1]; // Peek1044 if (subencoding & 0x10) { // SubrectsColoured1045 FBU.bytes += subrects * (fb_Bpp + 2);1046 } else {1047 FBU.bytes += subrects * 2;1048 }1049 }1050 }1051 /*1052 Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +1053 " (" + tile_x + "," + tile_y + ")" +1054 " [" + x + "," + y + "]@" + w + "x" + h +1055 ", subenc:" + subencoding +1056 "(last: " + FBU.lastsubencoding + "), subrects:" +1057 subrects +1058 ", ws.rQlen():" + ws.rQlen() + ", FBU.bytes:" + FBU.bytes +1059 " last:" + ws.rQslice(FBU.bytes-10, FBU.bytes) +1060 " next:" + ws.rQslice(FBU.bytes-1, FBU.bytes+10));1061 */1062 if (ws.rQwait("hextile", FBU.bytes)) { return false; }1063 /* We know the encoding and have a whole tile */1064 FBU.subencoding = rQ[rQi];1065 rQi += 1;1066 if (FBU.subencoding === 0) {1067 if (FBU.lastsubencoding & 0x01) {1068 /* Weird: ignore blanks after RAW */1069 Util.Debug(" Ignoring blank after RAW");1070 } else {1071 display.fillRect(x, y, w, h, FBU.background);1072 }1073 } else if (FBU.subencoding & 0x01) { // Raw1074 display.blitImage(x, y, w, h, rQ, rQi);1075 rQi += FBU.bytes - 1;1076 } else {1077 if (FBU.subencoding & 0x02) { // Background1078 FBU.background = rQ.slice(rQi, rQi + fb_Bpp);1079 rQi += fb_Bpp;1080 }1081 if (FBU.subencoding & 0x04) { // Foreground1082 FBU.foreground = rQ.slice(rQi, rQi + fb_Bpp);1083 rQi += fb_Bpp;1084 }1085 display.startTile(x, y, w, h, FBU.background);1086 if (FBU.subencoding & 0x08) { // AnySubrects1087 subrects = rQ[rQi];1088 rQi += 1;1089 for (s = 0; s < subrects; s += 1) {1090 if (FBU.subencoding & 0x10) { // SubrectsColoured1091 color = rQ.slice(rQi, rQi + fb_Bpp);1092 rQi += fb_Bpp;1093 } else {1094 color = FBU.foreground;1095 }1096 xy = rQ[rQi];1097 rQi += 1;1098 sx = (xy >> 4);1099 sy = (xy & 0x0f);1100 wh = rQ[rQi];1101 rQi += 1;1102 sw = (wh >> 4) + 1;1103 sh = (wh & 0x0f) + 1;1104 display.subTile(sx, sy, sw, sh, color);1105 }1106 }1107 display.finishTile();1108 }1109 ws.set_rQi(rQi);1110 FBU.lastsubencoding = FBU.subencoding;1111 FBU.bytes = 0;1112 FBU.tiles -= 1;1113 }1114 if (FBU.tiles === 0) {1115 FBU.rects -= 1;1116 }1117 //Util.Debug("<< display_hextile");1118 return true;1119};1120// Get 'compact length' header and data size1121getTightCLength = function (arr) {1122 var header = 1, data = 0;1123 data += arr[0] & 0x7f;1124 if (arr[0] & 0x80) {1125 header += 1;1126 data += (arr[1] & 0x7f) << 7;1127 if (arr[1] & 0x80) {1128 header += 1;1129 data += arr[2] << 14;1130 }1131 }1132 return [header, data];1133};1134function display_tight(isTightPNG) {1135 //Util.Debug(">> display_tight");1136 if (fb_depth === 1) {1137 fail("Tight protocol handler only implements true color mode");1138 }1139 var ctl, cmode, clength, color, img, data;1140 var filterId = -1, resetStreams = 0, streamId = -1;1141 var rQ = ws.get_rQ(), rQi = ws.get_rQi(); 1142 FBU.bytes = 1; // compression-control byte1143 if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; }1144 var checksum = function(data) {1145 var sum=0, i;1146 for (i=0; i<data.length;i++) {1147 sum += data[i];1148 if (sum > 65536) sum -= 65536;1149 }1150 return sum;1151 }1152 var decompress = function(data) {1153 for (var i=0; i<4; i++) {1154 if ((resetStreams >> i) & 1) {1155 FBU.zlibs[i].reset();1156 Util.Info("Reset zlib stream " + i);1157 }1158 }1159 var uncompressed = FBU.zlibs[streamId].uncompress(data, 0);1160 if (uncompressed.status !== 0) {1161 Util.Error("Invalid data in zlib stream");1162 }1163 //Util.Warn("Decompressed " + data.length + " to " +1164 // uncompressed.data.length + " checksums " +1165 // checksum(data) + ":" + checksum(uncompressed.data));1166 return uncompressed.data;1167 }1168 var handlePalette = function() {1169 var numColors = rQ[rQi + 2] + 1;1170 var paletteSize = numColors * fb_depth; 1171 FBU.bytes += paletteSize;1172 if (ws.rQwait("TIGHT palette " + cmode, FBU.bytes)) { return false; }1173 var bpp = (numColors <= 2) ? 1 : 8;1174 var rowSize = Math.floor((FBU.width * bpp + 7) / 8);1175 var raw = false;1176 if (rowSize * FBU.height < 12) {1177 raw = true;1178 clength = [0, rowSize * FBU.height];1179 } else {1180 clength = getTightCLength(ws.rQslice(3 + paletteSize,1181 3 + paletteSize + 3));1182 }1183 FBU.bytes += clength[0] + clength[1];1184 if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }1185 // Shift ctl, filter id, num colors, palette entries, and clength off1186 ws.rQshiftBytes(3); 1187 var palette = ws.rQshiftBytes(paletteSize);1188 ws.rQshiftBytes(clength[0]);1189 if (raw) {1190 data = ws.rQshiftBytes(clength[1]);1191 } else {1192 data = decompress(ws.rQshiftBytes(clength[1]));1193 }1194 // Convert indexed (palette based) image data to RGB1195 // TODO: reduce number of calculations inside loop1196 var dest = [];1197 var x, y, b, w, w1, dp, sp;1198 if (numColors === 2) {1199 w = Math.floor((FBU.width + 7) / 8);1200 w1 = Math.floor(FBU.width / 8);1201 for (y = 0; y < FBU.height; y++) {1202 for (x = 0; x < w1; x++) {1203 for (b = 7; b >= 0; b--) {1204 dp = (y*FBU.width + x*8 + 7-b) * 3;1205 sp = (data[y*w + x] >> b & 1) * 3;1206 dest[dp ] = palette[sp ];1207 dest[dp+1] = palette[sp+1];1208 dest[dp+2] = palette[sp+2];1209 }1210 }1211 for (b = 7; b >= 8 - FBU.width % 8; b--) {1212 dp = (y*FBU.width + x*8 + 7-b) * 3;1213 sp = (data[y*w + x] >> b & 1) * 3;1214 dest[dp ] = palette[sp ];1215 dest[dp+1] = palette[sp+1];1216 dest[dp+2] = palette[sp+2];1217 }1218 }1219 } else {1220 for (y = 0; y < FBU.height; y++) {1221 for (x = 0; x < FBU.width; x++) {1222 dp = (y*FBU.width + x) * 3;1223 sp = data[y*FBU.width + x] * 3;1224 dest[dp ] = palette[sp ];1225 dest[dp+1] = palette[sp+1];1226 dest[dp+2] = palette[sp+2];1227 }1228 }1229 }1230 display.renderQ_push({1231 'type': 'blitRgb',1232 'data': dest,1233 'x': FBU.x,1234 'y': FBU.y,1235 'width': FBU.width,1236 'height': FBU.height});1237 return true;1238 }1239 var handleCopy = function() {1240 var raw = false;1241 var uncompressedSize = FBU.width * FBU.height * fb_depth;1242 if (uncompressedSize < 12) {1243 raw = true;1244 clength = [0, uncompressedSize];1245 } else {1246 clength = getTightCLength(ws.rQslice(1, 4));1247 }1248 FBU.bytes = 1 + clength[0] + clength[1];1249 if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }1250 // Shift ctl, clength off1251 ws.rQshiftBytes(1 + clength[0]);1252 if (raw) {1253 data = ws.rQshiftBytes(clength[1]);1254 } else {1255 data = decompress(ws.rQshiftBytes(clength[1]));1256 }1257 display.renderQ_push({1258 'type': 'blitRgb',1259 'data': data,1260 'x': FBU.x,1261 'y': FBU.y,1262 'width': FBU.width,1263 'height': FBU.height});1264 return true;1265 }1266 ctl = ws.rQpeek8();1267 // Keep tight reset bits1268 resetStreams = ctl & 0xF;1269 // Figure out filter1270 ctl = ctl >> 4; 1271 streamId = ctl & 0x3;1272 if (ctl === 0x08) cmode = "fill";1273 else if (ctl === 0x09) cmode = "jpeg";1274 else if (ctl === 0x0A) cmode = "png";1275 else if (ctl & 0x04) cmode = "filter";1276 else if (ctl < 0x04) cmode = "copy";1277 else return fail("Illegal tight compression received, ctl: " + ctl);1278 if (isTightPNG && (cmode === "filter" || cmode === "copy")) {1279 return fail("filter/copy received in tightPNG mode");1280 }1281 switch (cmode) {1282 // fill uses fb_depth because TPIXELs drop the padding byte1283 case "fill": FBU.bytes += fb_depth; break; // TPIXEL1284 case "jpeg": FBU.bytes += 3; break; // max clength1285 case "png": FBU.bytes += 3; break; // max clength1286 case "filter": FBU.bytes += 2; break; // filter id + num colors if palette1287 case "copy": break;1288 }1289 if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }1290 //Util.Debug(" ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");1291 //Util.Debug(" cmode: " + cmode);1292 // Determine FBU.bytes1293 switch (cmode) {1294 case "fill":1295 ws.rQshift8(); // shift off ctl1296 color = ws.rQshiftBytes(fb_depth);1297 display.renderQ_push({1298 'type': 'fill',1299 'x': FBU.x,1300 'y': FBU.y,1301 'width': FBU.width,1302 'height': FBU.height,1303 'color': [color[2], color[1], color[0]] });1304 break;1305 case "png":1306 case "jpeg":1307 clength = getTightCLength(ws.rQslice(1, 4));1308 FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data1309 if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }1310 // We have everything, render it1311 //Util.Debug(" jpeg, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " +1312 // clength[0] + ", clength[1]: " + clength[1]);1313 ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length1314 img = new Image();1315 img.src = "data:image/" + cmode +1316 extract_data_uri(ws.rQshiftBytes(clength[1]));1317 display.renderQ_push({1318 'type': 'img',1319 'img': img,1320 'x': FBU.x,1321 'y': FBU.y});1322 img = null;1323 break;1324 case "filter":1325 filterId = rQ[rQi + 1];1326 if (filterId === 1) {1327 if (!handlePalette()) { return false; }1328 } else {1329 // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter1330 // Filter 2, Gradient is valid but not used if jpeg is enabled1331 throw("Unsupported tight subencoding received, filter: " + filterId);1332 }1333 break;1334 case "copy":1335 if (!handleCopy()) { return false; }1336 break;1337 }1338 FBU.bytes = 0;1339 FBU.rects -= 1;1340 //Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");1341 //Util.Debug("<< display_tight_png");1342 return true;1343}1344extract_data_uri = function(arr) {1345 //var i, stra = [];1346 //for (i=0; i< arr.length; i += 1) {1347 // stra.push(String.fromCharCode(arr[i]));1348 //}1349 //return "," + escape(stra.join(''));1350 return ";base64," + Base64.encode(arr);1351};1352encHandlers.TIGHT = function () { return display_tight(false); };1353encHandlers.TIGHT_PNG = function () { return display_tight(true); };1354encHandlers.last_rect = function last_rect() {...
rfb.js
Source:rfb.js
...432 return false;433};434handle_message = function() {435 //Util.Debug(">> handle_message ws.rQlen(): " + ws.rQlen());436 //Util.Debug("ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");437 if (ws.rQlen() === 0) {438 Util.Warn("handle_message called on empty receive queue");439 return;440 }441 switch (rfb_state) {442 case 'disconnected':443 case 'failed':444 Util.Error("Got data while disconnected");445 break;446 case 'normal':447 if (normal_msg() && ws.rQlen() > 0) {448 // true means we can continue processing449 // Give other events a chance to run450 if (msgTimer === null) {451 Util.Debug("More data to process, creating timer");452 msgTimer = setTimeout(function () {453 msgTimer = null;454 handle_message();455 }, 10);456 } else {457 Util.Debug("More data to process, existing timer");458 }459 }460 break;461 default:462 init_msg();463 break;464 }465};466function genDES(password, challenge) {467 var i, passwd = [];468 for (i=0; i < password.length; i += 1) {469 passwd.push(password.charCodeAt(i));470 }471 return (new DES(passwd)).encrypt(challenge);472}473function flushClient() {474 if (mouse_arr.length > 0) {475 //send(mouse_arr.concat(fbUpdateRequests()));476 ws.send(mouse_arr);477 setTimeout(function() {478 ws.send(fbUpdateRequests());479 }, 50);480 mouse_arr = [];481 return true;482 } else {483 return false;484 }485}486// overridable for testing487checkEvents = function() {488 var now;489 if (rfb_state === 'normal' && !viewportDragging) {490 if (! flushClient()) {491 now = new Date().getTime();492 if (now > last_req_time + conf.fbu_req_rate) {493 last_req_time = now;494 ws.send(fbUpdateRequests());495 }496 }497 }498 setTimeout(checkEvents, conf.check_rate);499};500keyPress = function(keysym, down) {501 var arr;502 if (conf.view_only) { return; } // View only, skip keyboard events503 arr = keyEvent(keysym, down);504 arr = arr.concat(fbUpdateRequests());505 ws.send(arr);506};507mouseButton = function(x, y, down, bmask) {508 if (down) {509 mouse_buttonMask |= bmask;510 } else {511 mouse_buttonMask ^= bmask;512 }513 if (conf.viewportDrag) {514 if (down && !viewportDragging) {515 viewportDragging = true;516 viewportDragPos = {'x': x, 'y': y};517 // Skip sending mouse events518 return;519 } else {520 viewportDragging = false;521 ws.send(fbUpdateRequests()); // Force immediate redraw522 }523 }524 if (conf.view_only) { return; } // View only, skip mouse events525 mouse_arr = mouse_arr.concat(526 pointerEvent(display.absX(x), display.absY(y)) );527 flushClient();528};529mouseMove = function(x, y) {530 //Util.Debug('>> mouseMove ' + x + "," + y);531 var deltaX, deltaY;532 if (viewportDragging) {533 //deltaX = x - viewportDragPos.x; // drag viewport534 deltaX = viewportDragPos.x - x; // drag frame buffer535 //deltaY = y - viewportDragPos.y; // drag viewport536 deltaY = viewportDragPos.y - y; // drag frame buffer537 viewportDragPos = {'x': x, 'y': y};538 display.viewportChange(deltaX, deltaY);539 // Skip sending mouse events540 return;541 }542 if (conf.view_only) { return; } // View only, skip mouse events543 mouse_arr = mouse_arr.concat(544 pointerEvent(display.absX(x), display.absY(y)) );545};546//547// Server message handlers548//549// RFB/VNC initialisation message handler550init_msg = function() {551 //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']");552 var strlen, reason, length, sversion, cversion, repeaterID,553 i, types, num_types, challenge, response, bpp, depth,554 big_endian, red_max, green_max, blue_max, red_shift,555 green_shift, blue_shift, true_color, name_length, is_repeater;556 //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0));557 switch (rfb_state) {558 case 'ProtocolVersion' :559 if (ws.rQlen() < 12) {560 return fail("Incomplete protocol version");561 }562 sversion = ws.rQshiftStr(12).substr(4,7);563 Util.Info("Server ProtocolVersion: " + sversion);564 is_repeater = 0;565 switch (sversion) {566 case "000.000": is_repeater = 1; break; // UltraVNC repeater567 case "003.003": rfb_version = 3.3; break;568 case "003.006": rfb_version = 3.3; break; // UltraVNC569 case "003.889": rfb_version = 3.3; break; // Apple Remote Desktop570 case "003.007": rfb_version = 3.7; break;571 case "003.008": rfb_version = 3.8; break;572 case "004.000": rfb_version = 3.8; break; // Intel AMT KVM573 case "004.001": rfb_version = 3.8; break; // RealVNC 4.6574 default:575 return fail("Invalid server version " + sversion);576 }577 if (is_repeater) { 578 repeaterID = conf.repeaterID;579 while (repeaterID.length < 250) {580 repeaterID += "\0";581 }582 ws.send_string(repeaterID);583 break;584 }585 if (rfb_version > rfb_max_version) { 586 rfb_version = rfb_max_version;587 }588 if (! test_mode) {589 sendTimer = setInterval(function() {590 // Send updates either at a rate of one update591 // every 50ms, or whatever slower rate the network592 // can handle.593 ws.flush();594 }, 50);595 }596 cversion = "00" + parseInt(rfb_version,10) +597 ".00" + ((rfb_version * 10) % 10);598 ws.send_string("RFB " + cversion + "\n");599 updateState('Security', "Sent ProtocolVersion: " + cversion);600 break;601 case 'Security' :602 if (rfb_version >= 3.7) {603 // Server sends supported list, client decides 604 num_types = ws.rQshift8();605 if (ws.rQwait("security type", num_types, 1)) { return false; }606 if (num_types === 0) {607 strlen = ws.rQshift32();608 reason = ws.rQshiftStr(strlen);609 return fail("Security failure: " + reason);610 }611 rfb_auth_scheme = 0;612 types = ws.rQshiftBytes(num_types);613 Util.Debug("Server security types: " + types);614 for (i=0; i < types.length; i+=1) {615 if ((types[i] > rfb_auth_scheme) && (types[i] < 3)) {616 rfb_auth_scheme = types[i];617 }618 }619 if (rfb_auth_scheme === 0) {620 return fail("Unsupported security types: " + types);621 }622 623 ws.send([rfb_auth_scheme]);624 } else {625 // Server decides626 if (ws.rQwait("security scheme", 4)) { return false; }627 rfb_auth_scheme = ws.rQshift32();628 }629 updateState('Authentication',630 "Authenticating using scheme: " + rfb_auth_scheme);631 init_msg(); // Recursive fallthrough (workaround JSLint complaint)632 break;633 // Triggered by fallthough, not by server message634 case 'Authentication' :635 //Util.Debug("Security auth scheme: " + rfb_auth_scheme);636 switch (rfb_auth_scheme) {637 case 0: // connection failed638 if (ws.rQwait("auth reason", 4)) { return false; }639 strlen = ws.rQshift32();640 reason = ws.rQshiftStr(strlen);641 return fail("Auth failure: " + reason);642 case 1: // no authentication643 if (rfb_version >= 3.8) {644 updateState('SecurityResult');645 return;646 }647 // Fall through to ClientInitialisation648 break;649 case 2: // VNC authentication650 if (rfb_password.length === 0) {651 // Notify via both callbacks since it is kind of652 // a RFB state change and a UI interface issue.653 updateState('password', "Password Required");654 conf.onPasswordRequired(that);655 return;656 }657 if (ws.rQwait("auth challenge", 16)) { return false; }658 challenge = ws.rQshiftBytes(16);659 //Util.Debug("Password: " + rfb_password);660 //Util.Debug("Challenge: " + challenge +661 // " (" + challenge.length + ")");662 response = genDES(rfb_password, challenge);663 //Util.Debug("Response: " + response +664 // " (" + response.length + ")");665 666 //Util.Debug("Sending DES encrypted auth response");667 ws.send(response);668 updateState('SecurityResult');669 return;670 default:671 fail("Unsupported auth scheme: " + rfb_auth_scheme);672 return;673 }674 updateState('ClientInitialisation', "No auth required");675 init_msg(); // Recursive fallthrough (workaround JSLint complaint)676 break;677 case 'SecurityResult' :678 if (ws.rQwait("VNC auth response ", 4)) { return false; }679 switch (ws.rQshift32()) {680 case 0: // OK681 // Fall through to ClientInitialisation682 break;683 case 1: // failed684 if (rfb_version >= 3.8) {685 length = ws.rQshift32();686 if (ws.rQwait("SecurityResult reason", length, 8)) {687 return false;688 }689 reason = ws.rQshiftStr(length);690 fail(reason);691 } else {692 fail("Authentication failed");693 }694 return;695 case 2: // too-many696 return fail("Too many auth attempts");697 }698 updateState('ClientInitialisation', "Authentication OK");699 init_msg(); // Recursive fallthrough (workaround JSLint complaint)700 break;701 // Triggered by fallthough, not by server message702 case 'ClientInitialisation' :703 ws.send([conf.shared ? 1 : 0]); // ClientInitialisation704 updateState('ServerInitialisation', "Authentication OK");705 break;706 case 'ServerInitialisation' :707 if (ws.rQwait("server initialization", 24)) { return false; }708 /* Screen size */709 fb_width = ws.rQshift16();710 fb_height = ws.rQshift16();711 /* PIXEL_FORMAT */712 bpp = ws.rQshift8();713 depth = ws.rQshift8();714 big_endian = ws.rQshift8();715 true_color = ws.rQshift8();716 red_max = ws.rQshift16();717 green_max = ws.rQshift16();718 blue_max = ws.rQshift16();719 red_shift = ws.rQshift8();720 green_shift = ws.rQshift8();721 blue_shift = ws.rQshift8();722 ws.rQshiftStr(3); // padding723 Util.Info("Screen: " + fb_width + "x" + fb_height + 724 ", bpp: " + bpp + ", depth: " + depth +725 ", big_endian: " + big_endian +726 ", true_color: " + true_color +727 ", red_max: " + red_max +728 ", green_max: " + green_max +729 ", blue_max: " + blue_max +730 ", red_shift: " + red_shift +731 ", green_shift: " + green_shift +732 ", blue_shift: " + blue_shift);733 if (big_endian !== 0) {734 Util.Warn("Server native endian is not little endian");735 }736 if (red_shift !== 16) {737 Util.Warn("Server native red-shift is not 16");738 }739 if (blue_shift !== 0) {740 Util.Warn("Server native blue-shift is not 0");741 }742 /* Connection name/title */743 name_length = ws.rQshift32();744 fb_name = ws.rQshiftStr(name_length);745 746 if (conf.true_color && fb_name === "Intel(r) AMT KVM")747 {748 Util.Warn("Intel AMT KVM only support 8/16 bit depths. Disabling true color");749 conf.true_color = false;750 }751 display.set_true_color(conf.true_color);752 display.resize(fb_width, fb_height);753 keyboard.grab();754 mouse.grab();755 if (conf.true_color) {756 fb_Bpp = 4;757 fb_depth = 3;758 } else {759 fb_Bpp = 1;760 fb_depth = 1;761 }762 response = pixelFormat();763 response = response.concat(clientEncodings());764 response = response.concat(fbUpdateRequests());765 timing.fbu_rt_start = (new Date()).getTime();766 timing.pixels = 0;767 ws.send(response);768 769 /* Start pushing/polling */770 setTimeout(checkEvents, conf.check_rate);771 if (conf.encrypt) {772 updateState('normal', "Connected (encrypted) to: " + fb_name);773 } else {774 updateState('normal', "Connected (unencrypted) to: " + fb_name);775 }776 break;777 }778 //Util.Debug("<< init_msg");779};780/* Normal RFB/VNC server message handler */781normal_msg = function() {782 //Util.Debug(">> normal_msg");783 var ret = true, msg_type, length, text,784 c, first_colour, num_colours, red, green, blue;785 if (FBU.rects > 0) {786 msg_type = 0;787 } else {788 msg_type = ws.rQshift8();789 }790 switch (msg_type) {791 case 0: // FramebufferUpdate792 ret = framebufferUpdate(); // false means need more data793 break;794 case 1: // SetColourMapEntries795 Util.Debug("SetColourMapEntries");796 ws.rQshift8(); // Padding797 first_colour = ws.rQshift16(); // First colour798 num_colours = ws.rQshift16();799 if (ws.rQwait("SetColourMapEntries", num_colours*6, 6)) { return false; }800 801 for (c=0; c < num_colours; c+=1) { 802 red = ws.rQshift16();803 //Util.Debug("red before: " + red);804 red = parseInt(red / 256, 10);805 //Util.Debug("red after: " + red);806 green = parseInt(ws.rQshift16() / 256, 10);807 blue = parseInt(ws.rQshift16() / 256, 10);808 display.set_colourMap([blue, green, red], first_colour + c);809 }810 Util.Debug("colourMap: " + display.get_colourMap());811 Util.Info("Registered " + num_colours + " colourMap entries");812 //Util.Debug("colourMap: " + display.get_colourMap());813 break;814 case 2: // Bell815 Util.Debug("Bell");816 conf.onBell(that);817 break;818 case 3: // ServerCutText819 Util.Debug("ServerCutText");820 if (ws.rQwait("ServerCutText header", 7, 1)) { return false; }821 ws.rQshiftBytes(3); // Padding822 length = ws.rQshift32();823 if (ws.rQwait("ServerCutText", length, 8)) { return false; }824 text = ws.rQshiftStr(length);825 conf.clipboardReceive(that, text); // Obsolete826 conf.onClipboard(that, text);827 break;828 default:829 fail("Disconnected: illegal server message type " + msg_type);830 Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));831 break;832 }833 //Util.Debug("<< normal_msg");834 return ret;835};836framebufferUpdate = function() {837 var now, hdr, fbu_rt_diff, ret = true;838 if (FBU.rects === 0) {839 //Util.Debug("New FBU: ws.rQslice(0,20): " + ws.rQslice(0,20));840 if (ws.rQwait("FBU header", 3)) {841 ws.rQunshift8(0); // FBU msg_type842 return false;843 }844 ws.rQshift8(); // padding845 FBU.rects = ws.rQshift16();846 //Util.Debug("FramebufferUpdate, rects:" + FBU.rects);847 FBU.bytes = 0;848 timing.cur_fbu = 0;849 if (timing.fbu_rt_start > 0) {850 now = (new Date()).getTime();851 Util.Info("First FBU latency: " + (now - timing.fbu_rt_start));852 }853 }854 while (FBU.rects > 0) {855 if (rfb_state !== "normal") {856 return false;857 }858 if (ws.rQwait("FBU", FBU.bytes)) { return false; }859 if (FBU.bytes === 0) {860 if (ws.rQwait("rect header", 12)) { return false; }861 /* New FramebufferUpdate */862 hdr = ws.rQshiftBytes(12);863 FBU.x = (hdr[0] << 8) + hdr[1];864 FBU.y = (hdr[2] << 8) + hdr[3];865 FBU.width = (hdr[4] << 8) + hdr[5];866 FBU.height = (hdr[6] << 8) + hdr[7];867 FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +868 (hdr[10] << 8) + hdr[11], 10);869 conf.onFBUReceive(that,870 {'x': FBU.x, 'y': FBU.y,871 'width': FBU.width, 'height': FBU.height,872 'encoding': FBU.encoding,873 'encodingName': encNames[FBU.encoding]});874 if (encNames[FBU.encoding]) {875 // Debug:876 /*877 var msg = "FramebufferUpdate rects:" + FBU.rects;878 msg += " x: " + FBU.x + " y: " + FBU.y;879 msg += " width: " + FBU.width + " height: " + FBU.height;880 msg += " encoding:" + FBU.encoding;881 msg += "(" + encNames[FBU.encoding] + ")";882 msg += ", ws.rQlen(): " + ws.rQlen();883 Util.Debug(msg);884 */885 } else {886 fail("Disconnected: unsupported encoding " +887 FBU.encoding);888 return false;889 }890 }891 timing.last_fbu = (new Date()).getTime();892 ret = encHandlers[FBU.encoding]();893 now = (new Date()).getTime();894 timing.cur_fbu += (now - timing.last_fbu);895 if (ret) {896 encStats[FBU.encoding][0] += 1;897 encStats[FBU.encoding][1] += 1;898 timing.pixels += FBU.width * FBU.height;899 }900 if (timing.pixels >= (fb_width * fb_height)) {901 if (((FBU.width === fb_width) &&902 (FBU.height === fb_height)) ||903 (timing.fbu_rt_start > 0)) {904 timing.full_fbu_total += timing.cur_fbu;905 timing.full_fbu_cnt += 1;906 Util.Info("Timing of full FBU, cur: " +907 timing.cur_fbu + ", total: " +908 timing.full_fbu_total + ", cnt: " +909 timing.full_fbu_cnt + ", avg: " +910 (timing.full_fbu_total /911 timing.full_fbu_cnt));912 }913 if (timing.fbu_rt_start > 0) {914 fbu_rt_diff = now - timing.fbu_rt_start;915 timing.fbu_rt_total += fbu_rt_diff;916 timing.fbu_rt_cnt += 1;917 Util.Info("full FBU round-trip, cur: " +918 fbu_rt_diff + ", total: " +919 timing.fbu_rt_total + ", cnt: " +920 timing.fbu_rt_cnt + ", avg: " +921 (timing.fbu_rt_total /922 timing.fbu_rt_cnt));923 timing.fbu_rt_start = 0;924 }925 }926 if (! ret) {927 return ret; // false ret means need more data928 }929 }930 conf.onFBUComplete(that,931 {'x': FBU.x, 'y': FBU.y,932 'width': FBU.width, 'height': FBU.height,933 'encoding': FBU.encoding,934 'encodingName': encNames[FBU.encoding]});935 return true; // We finished this FBU936};937//938// FramebufferUpdate encodings939//940encHandlers.RAW = function display_raw() {941 //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)");942 var cur_y, cur_height;943 if (FBU.lines === 0) {944 FBU.lines = FBU.height;945 }946 FBU.bytes = FBU.width * fb_Bpp; // At least a line947 if (ws.rQwait("RAW", FBU.bytes)) { return false; }948 cur_y = FBU.y + (FBU.height - FBU.lines);949 cur_height = Math.min(FBU.lines,950 Math.floor(ws.rQlen()/(FBU.width * fb_Bpp)));951 display.blitImage(FBU.x, cur_y, FBU.width, cur_height,952 ws.get_rQ(), ws.get_rQi());953 ws.rQshiftBytes(FBU.width * cur_height * fb_Bpp);954 FBU.lines -= cur_height;955 if (FBU.lines > 0) {956 FBU.bytes = FBU.width * fb_Bpp; // At least another line957 } else {958 FBU.rects -= 1;959 FBU.bytes = 0;960 }961 //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)");962 return true;963};964encHandlers.COPYRECT = function display_copy_rect() {965 //Util.Debug(">> display_copy_rect");966 var old_x, old_y;967 if (ws.rQwait("COPYRECT", 4)) { return false; }968 display.renderQ_push({969 'type': 'copy',970 'old_x': ws.rQshift16(),971 'old_y': ws.rQshift16(),972 'x': FBU.x,973 'y': FBU.y,974 'width': FBU.width,975 'height': FBU.height});976 FBU.rects -= 1;977 FBU.bytes = 0;978 return true;979};980encHandlers.RRE = function display_rre() {981 //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)");982 var color, x, y, width, height, chunk;983 if (FBU.subrects === 0) {984 if (ws.rQwait("RRE", 4+fb_Bpp)) { return false; }985 FBU.subrects = ws.rQshift32();986 color = ws.rQshiftBytes(fb_Bpp); // Background987 display.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);988 }989 while ((FBU.subrects > 0) && (ws.rQlen() >= (fb_Bpp + 8))) {990 color = ws.rQshiftBytes(fb_Bpp);991 x = ws.rQshift16();992 y = ws.rQshift16();993 width = ws.rQshift16();994 height = ws.rQshift16();995 display.fillRect(FBU.x + x, FBU.y + y, width, height, color);996 FBU.subrects -= 1;997 }998 //Util.Debug(" display_rre: rects: " + FBU.rects +999 // ", FBU.subrects: " + FBU.subrects);1000 if (FBU.subrects > 0) {1001 chunk = Math.min(rre_chunk_sz, FBU.subrects);1002 FBU.bytes = (fb_Bpp + 8) * chunk;1003 } else {1004 FBU.rects -= 1;1005 FBU.bytes = 0;1006 }1007 //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes);1008 return true;1009};1010encHandlers.HEXTILE = function display_hextile() {1011 //Util.Debug(">> display_hextile");1012 var subencoding, subrects, color, cur_tile,1013 tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh,1014 rQ = ws.get_rQ(), rQi = ws.get_rQi(); 1015 if (FBU.tiles === 0) {1016 FBU.tiles_x = Math.ceil(FBU.width/16);1017 FBU.tiles_y = Math.ceil(FBU.height/16);1018 FBU.total_tiles = FBU.tiles_x * FBU.tiles_y;1019 FBU.tiles = FBU.total_tiles;1020 }1021 /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */1022 while (FBU.tiles > 0) {1023 FBU.bytes = 1;1024 if (ws.rQwait("HEXTILE subencoding", FBU.bytes)) { return false; }1025 subencoding = rQ[rQi]; // Peek1026 if (subencoding > 30) { // Raw1027 fail("Disconnected: illegal hextile subencoding " + subencoding);1028 //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));1029 return false;1030 }1031 subrects = 0;1032 cur_tile = FBU.total_tiles - FBU.tiles;1033 tile_x = cur_tile % FBU.tiles_x;1034 tile_y = Math.floor(cur_tile / FBU.tiles_x);1035 x = FBU.x + tile_x * 16;1036 y = FBU.y + tile_y * 16;1037 w = Math.min(16, (FBU.x + FBU.width) - x);1038 h = Math.min(16, (FBU.y + FBU.height) - y);1039 /* Figure out how much we are expecting */1040 if (subencoding & 0x01) { // Raw1041 //Util.Debug(" Raw subencoding");1042 FBU.bytes += w * h * fb_Bpp;1043 } else {1044 if (subencoding & 0x02) { // Background1045 FBU.bytes += fb_Bpp;1046 }1047 if (subencoding & 0x04) { // Foreground1048 FBU.bytes += fb_Bpp;1049 }1050 if (subencoding & 0x08) { // AnySubrects1051 FBU.bytes += 1; // Since we aren't shifting it off1052 if (ws.rQwait("hextile subrects header", FBU.bytes)) { return false; }1053 subrects = rQ[rQi + FBU.bytes-1]; // Peek1054 if (subencoding & 0x10) { // SubrectsColoured1055 FBU.bytes += subrects * (fb_Bpp + 2);1056 } else {1057 FBU.bytes += subrects * 2;1058 }1059 }1060 }1061 /*1062 Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +1063 " (" + tile_x + "," + tile_y + ")" +1064 " [" + x + "," + y + "]@" + w + "x" + h +1065 ", subenc:" + subencoding +1066 "(last: " + FBU.lastsubencoding + "), subrects:" +1067 subrects +1068 ", ws.rQlen():" + ws.rQlen() + ", FBU.bytes:" + FBU.bytes +1069 " last:" + ws.rQslice(FBU.bytes-10, FBU.bytes) +1070 " next:" + ws.rQslice(FBU.bytes-1, FBU.bytes+10));1071 */1072 if (ws.rQwait("hextile", FBU.bytes)) { return false; }1073 /* We know the encoding and have a whole tile */1074 FBU.subencoding = rQ[rQi];1075 rQi += 1;1076 if (FBU.subencoding === 0) {1077 if (FBU.lastsubencoding & 0x01) {1078 /* Weird: ignore blanks after RAW */1079 Util.Debug(" Ignoring blank after RAW");1080 } else {1081 display.fillRect(x, y, w, h, FBU.background);1082 }1083 } else if (FBU.subencoding & 0x01) { // Raw1084 display.blitImage(x, y, w, h, rQ, rQi);1085 rQi += FBU.bytes - 1;1086 } else {1087 if (FBU.subencoding & 0x02) { // Background1088 FBU.background = rQ.slice(rQi, rQi + fb_Bpp);1089 rQi += fb_Bpp;1090 }1091 if (FBU.subencoding & 0x04) { // Foreground1092 FBU.foreground = rQ.slice(rQi, rQi + fb_Bpp);1093 rQi += fb_Bpp;1094 }1095 display.startTile(x, y, w, h, FBU.background);1096 if (FBU.subencoding & 0x08) { // AnySubrects1097 subrects = rQ[rQi];1098 rQi += 1;1099 for (s = 0; s < subrects; s += 1) {1100 if (FBU.subencoding & 0x10) { // SubrectsColoured1101 color = rQ.slice(rQi, rQi + fb_Bpp);1102 rQi += fb_Bpp;1103 } else {1104 color = FBU.foreground;1105 }1106 xy = rQ[rQi];1107 rQi += 1;1108 sx = (xy >> 4);1109 sy = (xy & 0x0f);1110 wh = rQ[rQi];1111 rQi += 1;1112 sw = (wh >> 4) + 1;1113 sh = (wh & 0x0f) + 1;1114 display.subTile(sx, sy, sw, sh, color);1115 }1116 }1117 display.finishTile();1118 }1119 ws.set_rQi(rQi);1120 FBU.lastsubencoding = FBU.subencoding;1121 FBU.bytes = 0;1122 FBU.tiles -= 1;1123 }1124 if (FBU.tiles === 0) {1125 FBU.rects -= 1;1126 }1127 //Util.Debug("<< display_hextile");1128 return true;1129};1130// Get 'compact length' header and data size1131getTightCLength = function (arr) {1132 var header = 1, data = 0;1133 data += arr[0] & 0x7f;1134 if (arr[0] & 0x80) {1135 header += 1;1136 data += (arr[1] & 0x7f) << 7;1137 if (arr[1] & 0x80) {1138 header += 1;1139 data += arr[2] << 14;1140 }1141 }1142 return [header, data];1143};1144function display_tight(isTightPNG) {1145 //Util.Debug(">> display_tight");1146 if (fb_depth === 1) {1147 fail("Tight protocol handler only implements true color mode");1148 }1149 var ctl, cmode, clength, color, img, data;1150 var filterId = -1, resetStreams = 0, streamId = -1;1151 var rQ = ws.get_rQ(), rQi = ws.get_rQi(); 1152 FBU.bytes = 1; // compression-control byte1153 if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; }1154 var checksum = function(data) {1155 var sum=0, i;1156 for (i=0; i<data.length;i++) {1157 sum += data[i];1158 if (sum > 65536) sum -= 65536;1159 }1160 return sum;1161 }1162 var decompress = function(data) {1163 for (var i=0; i<4; i++) {1164 if ((resetStreams >> i) & 1) {1165 FBU.zlibs[i].reset();1166 Util.Info("Reset zlib stream " + i);1167 }1168 }1169 var uncompressed = FBU.zlibs[streamId].uncompress(data, 0);1170 if (uncompressed.status !== 0) {1171 Util.Error("Invalid data in zlib stream");1172 }1173 //Util.Warn("Decompressed " + data.length + " to " +1174 // uncompressed.data.length + " checksums " +1175 // checksum(data) + ":" + checksum(uncompressed.data));1176 return uncompressed.data;1177 }1178 var handlePalette = function() {1179 var numColors = rQ[rQi + 2] + 1;1180 var paletteSize = numColors * fb_depth; 1181 FBU.bytes += paletteSize;1182 if (ws.rQwait("TIGHT palette " + cmode, FBU.bytes)) { return false; }1183 var bpp = (numColors <= 2) ? 1 : 8;1184 var rowSize = Math.floor((FBU.width * bpp + 7) / 8);1185 var raw = false;1186 if (rowSize * FBU.height < 12) {1187 raw = true;1188 clength = [0, rowSize * FBU.height];1189 } else {1190 clength = getTightCLength(ws.rQslice(3 + paletteSize,1191 3 + paletteSize + 3));1192 }1193 FBU.bytes += clength[0] + clength[1];1194 if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }1195 // Shift ctl, filter id, num colors, palette entries, and clength off1196 ws.rQshiftBytes(3); 1197 var palette = ws.rQshiftBytes(paletteSize);1198 ws.rQshiftBytes(clength[0]);1199 if (raw) {1200 data = ws.rQshiftBytes(clength[1]);1201 } else {1202 data = decompress(ws.rQshiftBytes(clength[1]));1203 }1204 // Convert indexed (palette based) image data to RGB1205 // TODO: reduce number of calculations inside loop1206 var dest = [];1207 var x, y, b, w, w1, dp, sp;1208 if (numColors === 2) {1209 w = Math.floor((FBU.width + 7) / 8);1210 w1 = Math.floor(FBU.width / 8);1211 for (y = 0; y < FBU.height; y++) {1212 for (x = 0; x < w1; x++) {1213 for (b = 7; b >= 0; b--) {1214 dp = (y*FBU.width + x*8 + 7-b) * 3;1215 sp = (data[y*w + x] >> b & 1) * 3;1216 dest[dp ] = palette[sp ];1217 dest[dp+1] = palette[sp+1];1218 dest[dp+2] = palette[sp+2];1219 }1220 }1221 for (b = 7; b >= 8 - FBU.width % 8; b--) {1222 dp = (y*FBU.width + x*8 + 7-b) * 3;1223 sp = (data[y*w + x] >> b & 1) * 3;1224 dest[dp ] = palette[sp ];1225 dest[dp+1] = palette[sp+1];1226 dest[dp+2] = palette[sp+2];1227 }1228 }1229 } else {1230 for (y = 0; y < FBU.height; y++) {1231 for (x = 0; x < FBU.width; x++) {1232 dp = (y*FBU.width + x) * 3;1233 sp = data[y*FBU.width + x] * 3;1234 dest[dp ] = palette[sp ];1235 dest[dp+1] = palette[sp+1];1236 dest[dp+2] = palette[sp+2];1237 }1238 }1239 }1240 display.renderQ_push({1241 'type': 'blitRgb',1242 'data': dest,1243 'x': FBU.x,1244 'y': FBU.y,1245 'width': FBU.width,1246 'height': FBU.height});1247 return true;1248 }1249 var handleCopy = function() {1250 var raw = false;1251 var uncompressedSize = FBU.width * FBU.height * fb_depth;1252 if (uncompressedSize < 12) {1253 raw = true;1254 clength = [0, uncompressedSize];1255 } else {1256 clength = getTightCLength(ws.rQslice(1, 4));1257 }1258 FBU.bytes = 1 + clength[0] + clength[1];1259 if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }1260 // Shift ctl, clength off1261 ws.rQshiftBytes(1 + clength[0]);1262 if (raw) {1263 data = ws.rQshiftBytes(clength[1]);1264 } else {1265 data = decompress(ws.rQshiftBytes(clength[1]));1266 }1267 display.renderQ_push({1268 'type': 'blitRgb',1269 'data': data,1270 'x': FBU.x,1271 'y': FBU.y,1272 'width': FBU.width,1273 'height': FBU.height});1274 return true;1275 }1276 ctl = ws.rQpeek8();1277 // Keep tight reset bits1278 resetStreams = ctl & 0xF;1279 // Figure out filter1280 ctl = ctl >> 4; 1281 streamId = ctl & 0x3;1282 if (ctl === 0x08) cmode = "fill";1283 else if (ctl === 0x09) cmode = "jpeg";1284 else if (ctl === 0x0A) cmode = "png";1285 else if (ctl & 0x04) cmode = "filter";1286 else if (ctl < 0x04) cmode = "copy";1287 else return fail("Illegal tight compression received, ctl: " + ctl);1288 if (isTightPNG && (cmode === "filter" || cmode === "copy")) {1289 return fail("filter/copy received in tightPNG mode");1290 }1291 switch (cmode) {1292 // fill uses fb_depth because TPIXELs drop the padding byte1293 case "fill": FBU.bytes += fb_depth; break; // TPIXEL1294 case "jpeg": FBU.bytes += 3; break; // max clength1295 case "png": FBU.bytes += 3; break; // max clength1296 case "filter": FBU.bytes += 2; break; // filter id + num colors if palette1297 case "copy": break;1298 }1299 if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }1300 //Util.Debug(" ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");1301 //Util.Debug(" cmode: " + cmode);1302 // Determine FBU.bytes1303 switch (cmode) {1304 case "fill":1305 ws.rQshift8(); // shift off ctl1306 color = ws.rQshiftBytes(fb_depth);1307 display.renderQ_push({1308 'type': 'fill',1309 'x': FBU.x,1310 'y': FBU.y,1311 'width': FBU.width,1312 'height': FBU.height,1313 'color': [color[2], color[1], color[0]] });1314 break;1315 case "png":1316 case "jpeg":1317 clength = getTightCLength(ws.rQslice(1, 4));1318 FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data1319 if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }1320 // We have everything, render it1321 //Util.Debug(" jpeg, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " +1322 // clength[0] + ", clength[1]: " + clength[1]);1323 ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length1324 img = new Image();1325 img.src = "data:image/" + cmode +1326 extract_data_uri(ws.rQshiftBytes(clength[1]));1327 display.renderQ_push({1328 'type': 'img',1329 'img': img,1330 'x': FBU.x,1331 'y': FBU.y});1332 img = null;1333 break;1334 case "filter":1335 filterId = rQ[rQi + 1];1336 if (filterId === 1) {1337 if (!handlePalette()) { return false; }1338 } else {1339 // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter1340 // Filter 2, Gradient is valid but not used if jpeg is enabled1341 throw("Unsupported tight subencoding received, filter: " + filterId);1342 }1343 break;1344 case "copy":1345 if (!handleCopy()) { return false; }1346 break;1347 }1348 FBU.bytes = 0;1349 FBU.rects -= 1;1350 //Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");1351 //Util.Debug("<< display_tight_png");1352 return true;1353}1354extract_data_uri = function(arr) {1355 //var i, stra = [];1356 //for (i=0; i< arr.length; i += 1) {1357 // stra.push(String.fromCharCode(arr[i]));1358 //}1359 //return "," + escape(stra.join(''));1360 return ";base64," + Base64.encode(arr);1361};1362encHandlers.TIGHT = function () { return display_tight(false); };1363encHandlers.TIGHT_PNG = function () { return display_tight(true); };1364encHandlers.last_rect = function last_rect() {...
Using AI Code Generation
1var redwood = require("./redwood");2var rQ = redwood.rQ;3var rQslice = redwood.rQslice;4var data = [1,2,3,4,5,6,7,8,9,10];5var rq = rQ(data);6var sliced = rQslice(rq, 2, 5);7console.log("rq = " + rq);8console.log("sliced = " + sliced);9var sliced2 = rQslice(rq, 2, 5, 2);10console.log("sliced2 = " + sliced2);11var sliced3 = rQslice(rq, 2, 5, 3);12console.log("sliced3 = " + sliced3);13var sliced4 = rQslice(rq, 2, 5, 4);14console.log("sliced4 = " + sliced4);15var sliced5 = rQslice(rq, 2, 5, 5);16console.log("sliced5 = " + sliced5);17var sliced6 = rQslice(rq, 2, 5, 6);18console.log("sliced6 = " + sliced6);19var sliced7 = rQslice(rq, 2, 5, 7);20console.log("sliced7 = " + sliced7);21var sliced8 = rQslice(rq, 2, 5, 8);22console.log("sliced8 = " + sliced8);23var sliced9 = rQslice(rq, 2, 5, 9);24console.log("sliced9 = " + sliced9);25var sliced10 = rQslice(rq, 2, 5, 10);26console.log("sliced10 = " + sliced10);27var sliced11 = rQslice(rq, 2, 5, 11);28console.log("sliced11 = " + sliced11);29var sliced12 = rQslice(rq, 2, 5, 12);30console.log("sliced12 = " + sliced12);31var sliced13 = rQslice(rq, 2, 5, 13);32console.log("sliced13 = " + sliced13);33var sliced14 = rQslice(rq, 2, 5, 14);34console.log("
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!