0001: ///////////////////////////////////////////////////////////////////////////////
0002: //
0003: // This program is free software; you can redistribute it and/or modify
0004: // it under the terms of the GNU General Public License and GNU Library
0005: // General Public License as published by the Free Software Foundation;
0006: // either version 2, or (at your option) any later version.
0007: //
0008: // This program is distributed in the hope that it will be useful,
0009: // but WITHOUT ANY WARRANTY; without even the implied warranty of
0010: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0011: // GNU General Public License and GNU Library General Public License
0012: // for more details.
0013: //
0014: // You should have received a copy of the GNU General Public License
0015: // and GNU Library General Public License along with this program; if
0016: // not, write to the Free Software Foundation, 675 Mass Ave, Cambridge,
0017: // MA 02139, USA.
0018: //
0019: ///////////////////////////////////////////////////////////////////////////////
0020:
0021: package org.rdesktop.server.rdp;
0022:
0023: import java.io.*;
0024: import java.net.*;
0025:
0026: import java.awt.*;
0027: import java.awt.image.*;
0028:
0029: import org.rdesktop.server.rdp.crypto.*;
0030: import org.rdesktop.server.rdp.rdp5.VChannels;
0031:
0032: public class RdpProto {
0033: public static final int RDP5_DISABLE_NOTHING = 0x00;
0034: public static final int RDP5_NO_WALLPAPER = 0x01;
0035: public static final int RDP5_NO_FULLWINDOWDRAG = 0x02;
0036: public static final int RDP5_NO_MENUANIMATIONS = 0x04;
0037: public static final int RDP5_NO_THEMING = 0x08;
0038: public static final int RDP5_NO_CURSOR_SHADOW = 0x20;
0039: public static final int RDP5_NO_CURSORSETTINGS = 0x40;
0040:
0041: public static final int RDP_LOGON_NORMAL = 0x33;
0042: public static final int RDP_LOGON_AUTO = 0x8;
0043: public static final int RDP_LOGON_BLOB = 0x100;
0044:
0045: public static final int RDP_PDU_DEMAND_ACTIVE = 1;
0046: public static final int RDP_PDU_CONFIRM_ACTIVE = 3;
0047: public static final int RDP_PDU_DEACTIVATE = 6;
0048: public static final int RDP_PDU_DATA = 7;
0049:
0050: public static final int RDP_DATA_PDU_UPDATE = 2;
0051: public static final int RDP_DATA_PDU_CONTROL = 20;
0052: public static final int RDP_DATA_PDU_POINTER = 27;
0053: public static final int RDP_DATA_PDU_INPUT = 28;
0054: public static final int RDP_DATA_PDU_SYNCHRONISE = 31;
0055: public static final int RDP_DATA_PDU_BELL = 34;
0056: public static final int RDP_DATA_PDU_LOGON = 38;
0057: public static final int RDP_DATA_PDU_FONT2 = 39;
0058: public static final int RDP_DATA_PDU_DISCONNECT = 47;
0059:
0060: public static final int RDP_CTL_REQUEST_CONTROL = 1;
0061: public static final int RDP_CTL_GRANT_CONTROL = 2;
0062: public static final int RDP_CTL_DETACH = 3;
0063: public static final int RDP_CTL_COOPERATE = 4;
0064:
0065: public static final int RDP_UPDATE_ORDERS = 0;
0066: public static final int RDP_UPDATE_BITMAP = 1;
0067: public static final int RDP_UPDATE_PALETTE = 2;
0068: public static final int RDP_UPDATE_SYNCHRONIZE = 3;
0069:
0070: public static final int RDP_POINTER_SYSTEM = 1;
0071: public static final int RDP_POINTER_MOVE = 3;
0072: public static final int RDP_POINTER_COLOR = 6;
0073: public static final int RDP_POINTER_CACHED = 7;
0074:
0075: public static final int RDP_NULL_POINTER = 0;
0076: public static final int RDP_DEFAULT_POINTER = 0x7F00;
0077:
0078: public static final int RDP_INPUT_SYNCHRONIZE = 0;
0079: public static final int RDP_INPUT_CODEPOINT = 1;
0080: public static final int RDP_INPUT_VIRTKEY = 2;
0081: public static final int RDP_INPUT_SCANCODE = 4;
0082: public static final int RDP_INPUT_MOUSE = 0x8001;
0083:
0084: public static final int RDP_CAPSET_GENERAL = 1;
0085: public static final int RDP_CAPLEN_GENERAL = 0x18;
0086:
0087: public static final int OS_MAJOR_TYPE_UNIX = 4;
0088: public static final int OS_MINOR_TYPE_XSERVER = 7;
0089:
0090: public static final int ORDER_CAP_NEGOTIATE = 2;
0091: public static final int ORDER_CAP_NOSUPPORT = 4;
0092:
0093: public static final int RDP_CAPSET_BITMAP = 2;
0094: public static final int RDP_CAPLEN_BITMAP = 0x1C;
0095: public static final int RDP_CAPSET_ORDER = 3;
0096: public static final int RDP_CAPLEN_ORDER = 0x58;
0097: public static final int RDP_CAPSET_BMPCACHE = 4;
0098: public static final int RDP_CAPLEN_BMPCACHE = 0x28;
0099: public static final int RDP_CAPSET_CONTROL = 5;
0100: public static final int RDP_CAPLEN_CONTROL = 0x0C;
0101: public static final int RDP_CAPSET_ACTIVATE = 7;
0102: public static final int RDP_CAPLEN_ACTIVATE = 0x0C;
0103: public static final int RDP_CAPSET_POINTER = 8;
0104: public static final int RDP_CAPLEN_POINTER = 0x08;
0105: public static final int RDP_CAPSET_SHARE = 9;
0106: public static final int RDP_CAPLEN_SHARE = 0x08;
0107: public static final int RDP_CAPSET_COLCACHE = 10;
0108: public static final int RDP_CAPLEN_COLCACHE = 0x08;
0109: public static final int RDP_CAPSET_UNKNOWN = 13;
0110: public static final int RDP_CAPLEN_UNKNOWN = 0x9C;
0111: public static final int RDP_CAPSET_BMPCACHE2 = 19;
0112: public static final int RDP_CAPLEN_BMPCACHE2 = 0x28;
0113:
0114: public static final int BMPCACHE2_FLAG_PERSIST = (1 << 31);
0115: public static final int BMPCACHE2_C0_CELLS = 0x78;
0116: public static final int BMPCACHE2_C1_CELLS = 0x78;
0117: public static final int BMPCACHE2_C2_CELLS = 0x150;
0118: public static final int BMPCACHE2_NUM_PSTCELLS = 0x9f6;
0119:
0120: public static final int RDP5_FLAG = 0x0030;
0121:
0122: public static final byte[] RDP_SOURCE = { (byte) 0x4D, (byte) 0x53,
0123: (byte) 0x54, (byte) 0x53, (byte) 0x43, (byte) 0x00 };
0124:
0125: private final byte[] canned_caps = { 0x01, 0x00, 0x00, 0x00, 0x09,
0126: 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0127: 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0128: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0129: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0130: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0131: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0132: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0133: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C,
0134: 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x08,
0135: 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x34, 0x00,
0136: (byte) 0xfe, 0x00, 0x04, 0x00, (byte) 0xfe, 0x00, 0x04,
0137: 0x00, (byte) 0xFE, 0x00, 0x08, 0x00, (byte) 0xFE, 0x00,
0138: 0x08, 0x00, (byte) 0xFE, 0x00, 0x10, 0x00, (byte) 0xFE,
0139: 0x00, 0x20, 0x00, (byte) 0xFE, 0x00, 0x40, 0x00,
0140: (byte) 0xFE, 0x00, (byte) 0x80, 0x00, (byte) 0xFE, 0x00,
0141: 0x00, 0x01, 0x40, 0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0x01,
0142: 0x02, 0x00, 0x00, 0x00 };
0143:
0144: static byte caps_0x10[] = { (byte) 0xFE, 0x00, 0x04, 0x00,
0145: (byte) 0xFE, 0x00, 0x04, 0x00, (byte) 0xFE, 0x00, 0x08,
0146: 0x00, (byte) 0xFE, 0x00, 0x08, 0x00, (byte) 0xFE, 0x00,
0147: 0x10, 0x00, (byte) 0xFE, 0x00, 0x20, 0x00, (byte) 0xFE,
0148: 0x00, 0x40, 0x00, (byte) 0xFE, 0x00, (byte) 0x80, 0x00,
0149: (byte) 0xFE, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x08,
0150: 0x00, 0x01, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00 };
0151:
0152: static byte caps_0x0c[] = { 0x01, 0x00, 0x00, 0x00 };
0153:
0154: static byte caps_0x0d[] = { 0x01, 0x00, 0x00, 0x00, 0x09, 0x04,
0155: 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0156: 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0157: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0158: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0159: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0160: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0161: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0162: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
0163:
0164: static byte caps_0x0e[] = { 0x01, 0x00, 0x00, 0x00 };
0165:
0166: protected String m_host;
0167: protected int m_port;
0168: protected String m_username;
0169: protected int m_width;
0170: protected int m_height;
0171:
0172: protected int m_rdp_shareid = 0;
0173: protected RdpCache m_cache = null;
0174: protected RdpOrders m_orders = null;
0175: protected RdpPacket m_stream = null;
0176: protected RdpSecure m_secureLayer = null;
0177: protected RdpDesktopFrame m_frame = null;
0178: protected RdpAbstractDesktopCanvas m_surface = null;
0179: protected boolean m_connected = false;
0180:
0181: public int m_next_packet = 0;
0182:
0183: protected static void processGeneralCaps(RdpProto rdp,
0184: RdpPacket data) {
0185: int pad2octetsB;
0186:
0187: data.incrementPosition(10);
0188: pad2octetsB = data.getLittleEndian16();
0189:
0190: if (pad2octetsB != 0) {
0191: RdpOptions.use_rdp5 = false;
0192: //rdp.use_rdp5 = false;
0193: }
0194: }
0195:
0196: protected static void processBitmapCaps(RdpProto rdp, RdpPacket data) {
0197: int width, height, bpp;
0198:
0199: bpp = data.getLittleEndian16();
0200: data.incrementPosition(6);
0201:
0202: width = data.getLittleEndian16();
0203: height = data.getLittleEndian16();
0204:
0205: if (RdpOptions.server_bpp != bpp) {
0206: RdpOptions.server_bpp = bpp;
0207: }
0208:
0209: if (rdp.m_width != width || rdp.m_height != height) {
0210: rdp.m_width = width;
0211: rdp.m_height = height;
0212: }
0213: }
0214:
0215: public RdpProto() {
0216: m_orders = new RdpOrders();
0217: m_cache = new RdpCache();
0218: m_orders.registerCache(m_cache);
0219: }
0220:
0221: public RdpProto(VChannels channels) {
0222: m_secureLayer = new RdpSecure(channels, this );
0223: m_orders = new RdpOrders();
0224: m_cache = new RdpCache();
0225: m_orders.registerCache(m_cache);
0226: }
0227:
0228: private RdpPacket initData(int size) throws RdpDesktopException {
0229: RdpPacket buffer = null;
0230:
0231: buffer = m_secureLayer.init(
0232: RdpOptions.encryption ? RdpSecure.SEC_ENCRYPT : 0,
0233: size + 18);
0234: buffer.pushLayer(RdpPacket.RDP_HEADER, 18);
0235: return buffer;
0236: }
0237:
0238: private void sendGeneralCaps(RdpPacket data) {
0239: data.setLittleEndian16(RDP_CAPSET_GENERAL);
0240: data.setLittleEndian16(RDP_CAPLEN_GENERAL);
0241:
0242: data.setLittleEndian16(1);
0243: data.setLittleEndian16(3);
0244: data.setLittleEndian16(0x200);
0245: data.setLittleEndian16(RdpOptions.use_rdp5 ? 0x40d : 0);
0246: data.setLittleEndian16(0);
0247: data.setLittleEndian16(0);
0248: data.setLittleEndian16(0);
0249: data.setLittleEndian16(0);
0250: data.setLittleEndian16(0);
0251: data.setLittleEndian16(0);
0252: }
0253:
0254: private void sendBitmapCaps(RdpPacket data) {
0255: data.setLittleEndian16(RDP_CAPSET_BITMAP);
0256: data.setLittleEndian16(RDP_CAPLEN_BITMAP);
0257:
0258: data.setLittleEndian16(RdpOptions.server_bpp);
0259: data.setLittleEndian16(1);
0260: data.setLittleEndian16(1);
0261: data.setLittleEndian16(1);
0262: data.setLittleEndian16(m_width);
0263: data.setLittleEndian16(m_height);
0264: data.setLittleEndian16(0);
0265: data.setLittleEndian16(1);
0266: data.setLittleEndian16(RdpOptions.bitmap_compression ? 1 : 0);
0267: data.setLittleEndian16(0);
0268: data.setLittleEndian16(1);
0269: data.setLittleEndian16(0);
0270: }
0271:
0272: private void sendOrderCaps(RdpPacket data) {
0273: byte[] order_caps = new byte[32];
0274: order_caps[0] = 1;
0275: order_caps[1] = 1;
0276: order_caps[2] = 1;
0277: order_caps[3] = (byte) (RdpOptions.bitmap_caching ? 1 : 0);
0278: order_caps[4] = 0;
0279: order_caps[8] = 1;
0280: order_caps[9] = 1;
0281: order_caps[10] = 1;
0282: order_caps[11] = (byte) (RdpOptions.desktop_save ? 1 : 0);
0283: order_caps[13] = 1;
0284: order_caps[20] = (byte) (RdpOptions.polygon_ellipse_orders ? 1
0285: : 0);
0286: order_caps[21] = (byte) (RdpOptions.polygon_ellipse_orders ? 1
0287: : 0);
0288: order_caps[22] = 1;
0289: order_caps[25] = (byte) (RdpOptions.polygon_ellipse_orders ? 1
0290: : 0);
0291: order_caps[26] = (byte) (RdpOptions.polygon_ellipse_orders ? 1
0292: : 0);
0293: order_caps[27] = 1;
0294: data.setLittleEndian16(RDP_CAPSET_ORDER);
0295: data.setLittleEndian16(RDP_CAPLEN_ORDER);
0296:
0297: data.incrementPosition(20);
0298: data.setLittleEndian16(1);
0299: data.setLittleEndian16(20);
0300: data.setLittleEndian16(0);
0301: data.setLittleEndian16(1);
0302: data.setLittleEndian16(0x147);
0303: data.setLittleEndian16(0x2a);
0304: data.copyFromByteArray(order_caps, 0, data.getPosition(), 32);
0305: data.incrementPosition(32);
0306: data.setLittleEndian16(0x6a1);
0307: data.incrementPosition(6);
0308: data.setLittleEndian32(RdpOptions.desktop_save ? 0x38400 : 0);
0309: data.setLittleEndian32(0);
0310: data.setLittleEndian32(0x4e4);
0311: }
0312:
0313: private void sendBitmapcacheCaps(RdpPacket data) {
0314: data.setLittleEndian16(RDP_CAPSET_BMPCACHE);
0315: data.setLittleEndian16(RDP_CAPLEN_BMPCACHE);
0316:
0317: data.incrementPosition(24);
0318: data.setLittleEndian16(0x258);
0319: data.setLittleEndian16(0x100);
0320: data.setLittleEndian16(0x12c);
0321: data.setLittleEndian16(0x400);
0322: data.setLittleEndian16(0x106);
0323: data.setLittleEndian16(0x1000);
0324: }
0325:
0326: private void sendBitmapcache2Caps(RdpPacket data) {
0327: data.setLittleEndian16(RDP_CAPSET_BMPCACHE2);
0328: data.setLittleEndian16(RDP_CAPLEN_BMPCACHE2);
0329:
0330: data.setLittleEndian16(RdpOptions.persistent_bitmap_caching ? 2
0331: : 0);
0332: data.setBigEndian16(3);
0333:
0334: data.setLittleEndian32(BMPCACHE2_C0_CELLS);
0335: data.setLittleEndian32(BMPCACHE2_C1_CELLS);
0336: data.setLittleEndian32(BMPCACHE2_C2_CELLS);
0337:
0338: data.incrementPosition(20);
0339: }
0340:
0341: private void sendColorcacheCaps(RdpPacket data) {
0342: data.setLittleEndian16(RDP_CAPSET_COLCACHE);
0343: data.setLittleEndian16(RDP_CAPLEN_COLCACHE);
0344:
0345: data.setLittleEndian16(6);
0346: data.setLittleEndian16(0);
0347: }
0348:
0349: private void sendActivateCaps(RdpPacket data) {
0350: data.setLittleEndian16(RDP_CAPSET_ACTIVATE);
0351: data.setLittleEndian16(RDP_CAPLEN_ACTIVATE);
0352:
0353: data.setLittleEndian16(0);
0354: data.setLittleEndian16(0);
0355: data.setLittleEndian16(0);
0356: data.setLittleEndian16(0);
0357: }
0358:
0359: private void sendControlCaps(RdpPacket data) {
0360: data.setLittleEndian16(RDP_CAPSET_CONTROL);
0361: data.setLittleEndian16(RDP_CAPLEN_CONTROL);
0362:
0363: data.setLittleEndian16(0);
0364: data.setLittleEndian16(0);
0365: data.setLittleEndian16(2);
0366: data.setLittleEndian16(2);
0367: }
0368:
0369: private void sendPointerCaps(RdpPacket data) {
0370: data.setLittleEndian16(RDP_CAPSET_POINTER);
0371: data.setLittleEndian16(RDP_CAPLEN_POINTER);
0372:
0373: data.setLittleEndian16(0);
0374: data.setLittleEndian16(20);
0375: }
0376:
0377: private void sendShareCaps(RdpPacket data) {
0378: data.setLittleEndian16(RDP_CAPSET_SHARE);
0379: data.setLittleEndian16(RDP_CAPLEN_SHARE);
0380:
0381: data.setLittleEndian16(0);
0382: data.setLittleEndian16(0);
0383: }
0384:
0385: private void sendUnknownCaps(RdpPacket data, int id, int length,
0386: byte[] caps) {
0387: data.setLittleEndian16(id);
0388: data.setLittleEndian16(length);
0389:
0390: data.copyFromByteArray(caps, 0, data.getPosition(), length - 4);
0391: data.incrementPosition(length - 4);
0392: }
0393:
0394: private void processDemandActive(RdpPacket data)
0395: throws RdpDesktopException, IOException, CryptoException,
0396: RdpOrderException {
0397: int type[] = new int[1];
0398: m_rdp_shareid = data.getLittleEndian32();
0399: sendConfirmActive(m_rdp_shareid);
0400:
0401: sendSynchronize();
0402: sendControl(RdpProto.RDP_CTL_COOPERATE);
0403: sendControl(RdpProto.RDP_CTL_REQUEST_CONTROL);
0404:
0405: receive(type);
0406: receive(type);
0407: receive(type);
0408:
0409: sendInput(0, RdpProto.RDP_INPUT_SYNCHRONIZE, 0, 0, 0);
0410: sendFonts(1);
0411: sendFonts(2);
0412:
0413: receive(type);
0414:
0415: m_orders.resetOrderState();
0416: }
0417:
0418: private boolean processData(RdpPacket data, int[] disconnectReason)
0419: throws RdpDesktopException, RdpOrderException {
0420: int data_type, ctype, clen, len, roff, rlen;
0421: data_type = 0;
0422:
0423: data.incrementPosition(6);
0424: len = data.getLittleEndian16();
0425: data_type = data.get8();
0426: ctype = data.get8();
0427: clen = data.getLittleEndian16();
0428: clen -= 18;
0429:
0430: switch (data_type) {
0431: case (RdpProto.RDP_DATA_PDU_UPDATE): {
0432: processUpdate(data);
0433: break;
0434: }
0435: case RdpProto.RDP_DATA_PDU_CONTROL: {
0436: break;
0437: }
0438: case RdpProto.RDP_DATA_PDU_SYNCHRONISE: {
0439: break;
0440: }
0441: case (RdpProto.RDP_DATA_PDU_POINTER): {
0442: processPointer(data);
0443: break;
0444: }
0445: case (RdpProto.RDP_DATA_PDU_BELL): {
0446: Toolkit tx = Toolkit.getDefaultToolkit();
0447: tx.beep();
0448: break;
0449: }
0450: case (RdpProto.RDP_DATA_PDU_LOGON): {
0451: break;
0452: }
0453: case RdpProto.RDP_DATA_PDU_DISCONNECT: {
0454: disconnectReason[0] = processDisconnectPdu(data);
0455: return true;
0456: }
0457: default: {
0458: break;
0459: }
0460:
0461: }
0462:
0463: return false;
0464: }
0465:
0466: private void processUpdate(RdpPacket data)
0467: throws RdpOrderException, RdpDesktopException {
0468: int update_type = 0;
0469:
0470: update_type = data.getLittleEndian16();
0471:
0472: switch (update_type) {
0473: case (RdpProto.RDP_UPDATE_ORDERS): {
0474: data.incrementPosition(2);
0475: int n_orders = data.getLittleEndian16();
0476: data.incrementPosition(2);
0477: m_orders.processOrders(data, m_next_packet, n_orders);
0478: break;
0479: }
0480: case (RdpProto.RDP_UPDATE_BITMAP): {
0481: processBitmapUpdates(data);
0482: break;
0483: }
0484: case (RdpProto.RDP_UPDATE_PALETTE): {
0485: processPalette(data);
0486: break;
0487: }
0488: case (RdpProto.RDP_UPDATE_SYNCHRONIZE): {
0489: break;
0490: }
0491: default: {
0492: break;
0493: }
0494: }
0495: }
0496:
0497: private void process_system_pointer_pdu(RdpPacket data) {
0498: int system_pointer_type = 0;
0499:
0500: data.getLittleEndian16(system_pointer_type);
0501:
0502: switch (system_pointer_type) {
0503: case RdpProto.RDP_NULL_POINTER: {
0504: m_surface.setCursor(null);
0505: break;
0506: }
0507: default: {
0508: break;
0509: }
0510: }
0511: }
0512:
0513: private void processPointer(RdpPacket data)
0514: throws RdpDesktopException {
0515: int message_type = 0;
0516: int x = 0, y = 0;
0517:
0518: message_type = data.getLittleEndian16();
0519: data.incrementPosition(2);
0520: switch (message_type) {
0521: case (RdpProto.RDP_POINTER_MOVE): {
0522: x = data.getLittleEndian16();
0523: y = data.getLittleEndian16();
0524:
0525: if (data.getPosition() <= data.getEnd()) {
0526: m_surface.movePointer(x, y);
0527: }
0528: break;
0529: }
0530: case (RdpProto.RDP_POINTER_COLOR): {
0531: process_colour_pointer_pdu(data);
0532: break;
0533: }
0534: case (RdpProto.RDP_POINTER_CACHED): {
0535: process_cached_pointer_pdu(data);
0536: break;
0537: }
0538: case RdpProto.RDP_POINTER_SYSTEM: {
0539: process_system_pointer_pdu(data);
0540: break;
0541: }
0542: default: {
0543: break;
0544: }
0545: }
0546: }
0547:
0548: protected void processBitmapUpdates(RdpPacket data)
0549: throws RdpDesktopException {
0550: int n_updates = 0;
0551: byte[] pixel = null;
0552: int minX, minY, maxX, maxY;
0553: int left = 0, top = 0, right = 0, bottom = 0, width = 0, height = 0;
0554: int cx = 0, cy = 0, bitsperpixel = 0, compression = 0, buffersize = 0, size = 0;
0555:
0556: maxX = maxY = 0;
0557: minX = m_surface.getWidth();
0558: minY = m_surface.getHeight();
0559:
0560: n_updates = data.getLittleEndian16();
0561:
0562: for (int i = 0; i < n_updates; i++) {
0563: left = data.getLittleEndian16();
0564: top = data.getLittleEndian16();
0565: right = data.getLittleEndian16();
0566: bottom = data.getLittleEndian16();
0567: width = data.getLittleEndian16();
0568: height = data.getLittleEndian16();
0569: bitsperpixel = data.getLittleEndian16();
0570: int Bpp = (bitsperpixel + 7) / 8;
0571: compression = data.getLittleEndian16();
0572: buffersize = data.getLittleEndian16();
0573:
0574: cx = right - left + 1;
0575: cy = bottom - top + 1;
0576:
0577: if (minX > left) {
0578: minX = left;
0579: }
0580:
0581: if (minY > top) {
0582: minY = top;
0583: }
0584:
0585: if (maxX < right) {
0586: maxX = right;
0587: }
0588:
0589: if (maxY < bottom) {
0590: maxY = bottom;
0591: }
0592:
0593: if (RdpOptions.server_bpp != bitsperpixel) {
0594: RdpOptions.set_bpp(bitsperpixel);
0595: }
0596:
0597: if (compression == 0) {
0598: pixel = new byte[width * height * Bpp];
0599:
0600: for (int y = 0; y < height; y++) {
0601: data.copyToByteArray(pixel, (height - y - 1)
0602: * (width * Bpp), data.getPosition(), width
0603: * Bpp);
0604: data.incrementPosition(width * Bpp);
0605: }
0606:
0607: m_surface.displayImage(RdpBitmap.convertImage(pixel,
0608: Bpp), width, height, left, top, cx, cy);
0609: continue;
0610: }
0611:
0612: if ((compression & 0x400) != 0) {
0613: size = buffersize;
0614: } else {
0615: data.incrementPosition(2);
0616: size = data.getLittleEndian16();
0617: data.incrementPosition(4);
0618:
0619: }
0620:
0621: if (Bpp == 1) {
0622: pixel = RdpBitmap.decompress(width, height, size, data,
0623: Bpp);
0624: if (pixel != null) {
0625: m_surface.displayImage(RdpBitmap.convertImage(
0626: pixel, Bpp), width, height, left, top, cx,
0627: cy);
0628: }
0629: } else {
0630: if (RdpOptions.bitmap_decompression_store == RdpOptions.INTEGER_BITMAP_DECOMPRESSION) {
0631: int[] pixeli = RdpBitmap.decompressInt(width,
0632: height, size, data, Bpp);
0633: if (pixeli != null) {
0634: m_surface.displayImage(pixeli, width, height,
0635: left, top, cx, cy);
0636: }
0637: } else if (RdpOptions.bitmap_decompression_store == RdpOptions.BUFFEREDIMAGE_BITMAP_DECOMPRESSION) {
0638: Image pix = RdpBitmap.decompressImg(width, height,
0639: size, data, Bpp, null);
0640: if (pix != null) {
0641: m_surface.displayImage(pix, left, top);
0642: }
0643: } else {
0644: m_surface.displayCompressed(left, top, width,
0645: height, size, data, Bpp, null);
0646: }
0647: }
0648: }
0649:
0650: m_surface.repaint(minX, minY, maxX - minX + 1, maxY - minY + 1);
0651: }
0652:
0653: protected void processPalette(RdpPacket data) {
0654: int n_colors = 0;
0655: IndexColorModel cm = null;
0656: byte[] palette = null;
0657:
0658: byte[] red = null;
0659: byte[] green = null;
0660: byte[] blue = null;
0661: int j = 0;
0662:
0663: data.incrementPosition(2);
0664: n_colors = data.getLittleEndian16();
0665: data.incrementPosition(2);
0666: palette = new byte[n_colors * 3];
0667: red = new byte[n_colors];
0668: green = new byte[n_colors];
0669: blue = new byte[n_colors];
0670: data.copyToByteArray(palette, 0, data.getPosition(),
0671: palette.length);
0672: data.incrementPosition(palette.length);
0673:
0674: for (int i = 0; i < n_colors; i++) {
0675: red[i] = palette[j];
0676: green[i] = palette[j + 1];
0677: blue[i] = palette[j + 2];
0678: j += 3;
0679: }
0680:
0681: cm = new IndexColorModel(8, n_colors, red, green, blue);
0682: m_surface.registerPalette(cm);
0683: }
0684:
0685: protected void process_null_system_pointer_pdu(RdpPacket s)
0686: throws RdpDesktopException {
0687: m_surface.setCursor(m_cache.getCursor(0));
0688: }
0689:
0690: protected void process_colour_pointer_pdu(RdpPacket data)
0691: throws RdpDesktopException {
0692: Cursor cursor = null;
0693: byte[] mask = null, pixel = null;
0694: int x = 0, y = 0, width = 0, height = 0, cache_idx = 0, masklen = 0, datalen = 0;
0695:
0696: cache_idx = data.getLittleEndian16();
0697: x = data.getLittleEndian16();
0698: y = data.getLittleEndian16();
0699: width = data.getLittleEndian16();
0700: height = data.getLittleEndian16();
0701: masklen = data.getLittleEndian16();
0702: datalen = data.getLittleEndian16();
0703: mask = new byte[masklen];
0704: pixel = new byte[datalen];
0705: data.copyToByteArray(pixel, 0, data.getPosition(), datalen);
0706: data.incrementPosition(datalen);
0707: data.copyToByteArray(mask, 0, data.getPosition(), masklen);
0708: data.incrementPosition(masklen);
0709: cursor = m_surface.createCursor(x, y, width, height, mask,
0710: pixel, cache_idx);
0711: m_surface.setCursor(cursor);
0712: m_cache.putCursor(cache_idx, cursor);
0713: }
0714:
0715: protected void process_cached_pointer_pdu(RdpPacket data)
0716: throws RdpDesktopException {
0717: int cache_idx = data.getLittleEndian16();
0718: m_surface.setCursor(m_cache.getCursor(cache_idx));
0719: }
0720:
0721: protected void processServerCaps(RdpPacket data, int length) {
0722: int n;
0723: int next, start;
0724: int ncapsets, capset_type, capset_length;
0725:
0726: start = data.getPosition();
0727:
0728: ncapsets = data.getLittleEndian16();
0729: data.incrementPosition(2);
0730:
0731: for (n = 0; n < ncapsets; n++) {
0732: if (data.getPosition() > start + length) {
0733: return;
0734: }
0735:
0736: capset_type = data.getLittleEndian16();
0737: capset_length = data.getLittleEndian16();
0738:
0739: next = data.getPosition() + capset_length - 4;
0740:
0741: switch (capset_type) {
0742: case RdpProto.RDP_CAPSET_GENERAL: {
0743: processGeneralCaps(this , data);
0744: break;
0745: }
0746: case RdpProto.RDP_CAPSET_BITMAP: {
0747: processBitmapCaps(this , data);
0748: break;
0749: }
0750: }
0751:
0752: data.setPosition(next);
0753: }
0754: }
0755:
0756: protected int processDisconnectPdu(RdpPacket data) {
0757: return data.getLittleEndian32();
0758: }
0759:
0760: public void sendData(RdpPacket data, int data_pdu_type)
0761: throws RdpDesktopException, IOException, CryptoException {
0762: synchronized (this ) {
0763: int length;
0764:
0765: data.setPosition(data.getHeader(RdpPacket.RDP_HEADER));
0766: length = data.getEnd() - data.getPosition();
0767:
0768: data.setLittleEndian16(length);
0769: data.setLittleEndian16(RDP_PDU_DATA | 0x10);
0770: data.setLittleEndian16(m_secureLayer.getUserID() + 1001);
0771:
0772: data.setLittleEndian32(m_rdp_shareid);
0773: data.set8(0);
0774: data.set8(1);
0775: data.setLittleEndian16(length - 14);
0776: data.set8(data_pdu_type);
0777: data.set8(0);
0778: data.setLittleEndian16(0);
0779:
0780: m_secureLayer.send(data,
0781: RdpOptions.encryption ? RdpSecure.SEC_ENCRYPT : 0);
0782: }
0783: }
0784:
0785: public RdpPacket receive(int[] type) throws IOException,
0786: RdpDesktopException, CryptoException, RdpOrderException {
0787: int length = 0;
0788:
0789: if ((m_stream == null) || (m_next_packet >= m_stream.getEnd())) {
0790: m_stream = m_secureLayer.receive();
0791: if (m_stream == null) {
0792: return null;
0793: }
0794: m_next_packet = m_stream.getPosition();
0795: } else {
0796: m_stream.setPosition(m_next_packet);
0797: }
0798:
0799: length = m_stream.getLittleEndian16();
0800:
0801: if (length == 0x8000) {
0802: m_next_packet += 8;
0803: type[0] = 0;
0804: return m_stream;
0805: }
0806:
0807: type[0] = m_stream.getLittleEndian16() & 0xf;
0808: if (m_stream.getPosition() != m_stream.getEnd()) {
0809: m_stream.incrementPosition(2);
0810: }
0811:
0812: m_next_packet += length;
0813: return m_stream;
0814: }
0815:
0816: public int available() throws java.io.IOException {
0817: return m_secureLayer.available();
0818: }
0819:
0820: public void connect(String host, int port, String username,
0821: String password, int flags, String domain, String command,
0822: String directory, int width, int height, int bitsPerPixel)
0823: throws RdpDesktopException, RdpConnectionException {
0824: m_host = host;
0825: m_port = port;
0826: m_username = username;
0827: m_width = width;
0828: m_height = height;
0829:
0830: try {
0831: m_secureLayer.connect();
0832: m_connected = true;
0833: sendLogonInfo(flags, domain, username, password, command,
0834: directory);
0835: } catch (UnknownHostException e) {
0836: throw new RdpConnectionException(
0837: "Could not resolve host name: " + host);
0838: } catch (ConnectException e) {
0839: throw new RdpConnectionException(
0840: "Connection refused when trying to connect to "
0841: + host + " on port " + port);
0842: } catch (NoRouteToHostException e) {
0843: throw new RdpConnectionException(
0844: "Connection timed out when attempting to connect to "
0845: + host);
0846: } catch (IOException e) {
0847: throw new RdpConnectionException("Connection Failed");
0848: } catch (RdpDesktopException e) {
0849: throw new RdpConnectionException(e.getMessage());
0850: } catch (RdpOrderException e) {
0851: throw new RdpConnectionException(e.getMessage());
0852: } catch (CryptoException e) {
0853: throw new RdpConnectionException(e.getMessage());
0854: }
0855: }
0856:
0857: public void disconnect() {
0858: m_connected = false;
0859: m_secureLayer.disconnect();
0860: }
0861:
0862: public boolean isConnected() {
0863: return m_connected;
0864: }
0865:
0866: public void mainLoop(int[] disconnectReason) throws IOException,
0867: RdpDesktopException, RdpOrderException, CryptoException {
0868: RdpPacket data = null;
0869: int[] type = new int[1];
0870: boolean disconnect = false;
0871:
0872: while (m_connected == true) {
0873: try {
0874: data = receive(type);
0875:
0876: if (data == null) {
0877: return;
0878: }
0879: } catch (EOFException e) {
0880: return;
0881: }
0882:
0883: switch (type[0]) {
0884: case (RdpProto.RDP_PDU_DEMAND_ACTIVE): {
0885: processDemandActive(data);
0886: m_frame.triggerReadyToSend();
0887: break;
0888: }
0889: case (RdpProto.RDP_PDU_DEACTIVATE): {
0890: break;
0891: }
0892: case (RdpProto.RDP_PDU_DATA): {
0893: disconnect = processData(data, disconnectReason);
0894: break;
0895: }
0896: case 0: {
0897: break;
0898: }
0899: default: {
0900: throw new RdpDesktopException(
0901: "Unimplemented type in main loop :" + type[0]);
0902: }
0903: }
0904:
0905: if (disconnect == true) {
0906: return;
0907: }
0908: }
0909: }
0910:
0911: public void sendLogonInfo(int logonflags, String domain,
0912: String username, String password, String command,
0913: String directory) throws RdpDesktopException, IOException,
0914: CryptoException {
0915: int len_ip = 2 * "127.0.0.1".length();
0916: int len_dll = 2 * "C:\\WINNT\\System32\\mstscax.dll".length();
0917: int packetlen = 0;
0918:
0919: int sec_flags = RdpOptions.encryption ? (RdpSecure.SEC_LOGON_INFO | RdpSecure.SEC_ENCRYPT)
0920: : RdpSecure.SEC_LOGON_INFO;
0921: int domainlen = 2 * domain.length();
0922: int userlen = 2 * username.length();
0923: int passlen = 2 * password.length();
0924: int commandlen = 2 * command.length();
0925: int dirlen = 2 * directory.length();
0926:
0927: RdpPacket data;
0928:
0929: if ((RdpOptions.use_rdp5 == false)
0930: || (RdpOptions.server_rdp_version == 1)) {
0931: data = m_secureLayer.init(sec_flags, 18 + domainlen
0932: + userlen + passlen + commandlen + dirlen + 10);
0933:
0934: data.setLittleEndian32(0);
0935: data.setLittleEndian32(logonflags);
0936: data.setLittleEndian16(domainlen);
0937: data.setLittleEndian16(userlen);
0938: data.setLittleEndian16(passlen);
0939: data.setLittleEndian16(commandlen);
0940: data.setLittleEndian16(dirlen);
0941: data.outUnicodeString(domain, domainlen);
0942: data.outUnicodeString(username, userlen);
0943: data.outUnicodeString(password, passlen);
0944: data.outUnicodeString(command, commandlen);
0945: data.outUnicodeString(directory, dirlen);
0946:
0947: } else {
0948: logonflags |= RDP_LOGON_BLOB;
0949: packetlen = 4
0950: + // Unknown uint32
0951: 4
0952: + // logonflags
0953: 2
0954: + // len_domain
0955: 2
0956: + // len_user
0957: ((logonflags & RDP_LOGON_AUTO) != 0 ? 2 : 0)
0958: + // len_password
0959: ((logonflags & RDP_LOGON_BLOB) != 0 ? 2 : 0)
0960: + // Length of BLOB
0961: 2
0962: + // len_program
0963: 2
0964: + // len_directory
0965: (0 < domainlen ? domainlen + 2 : 2)
0966: + // domain
0967: userlen
0968: + ((logonflags & RDP_LOGON_AUTO) != 0 ? passlen : 0)
0969: + 0
0970: + // We have no 512 byte BLOB. Perhaps we must?
0971: ((logonflags & RDP_LOGON_BLOB) != 0
0972: && (logonflags & RDP_LOGON_AUTO) == 0 ? 2
0973: : 0)
0974: + (0 < commandlen ? commandlen + 2 : 2)
0975: + (0 < dirlen ? dirlen + 2 : 2) + 2 + // Unknown (2)
0976: 2 + // Client ip length
0977: len_ip + // Client ip
0978: 2 + // DLL string length
0979: len_dll + // DLL string
0980: 2 + // Unknown
0981: 2 + // Unknown
0982: 64 + // Time zone #0
0983: 20 + // Unknown
0984: 64 + // Time zone #1
0985: 32 + 6; // Unknown
0986:
0987: data = m_secureLayer.init(sec_flags, packetlen);
0988:
0989: data.setLittleEndian32(0);
0990: data.setLittleEndian32(logonflags);
0991: data.setLittleEndian16(domainlen);
0992: data.setLittleEndian16(userlen);
0993:
0994: if ((logonflags & RDP_LOGON_AUTO) != 0) {
0995: data.setLittleEndian16(passlen);
0996: }
0997:
0998: if ((logonflags & RDP_LOGON_BLOB) != 0
0999: && ((logonflags & RDP_LOGON_AUTO) == 0)) {
1000: data.setLittleEndian16(0);
1001: }
1002:
1003: data.setLittleEndian16(commandlen);
1004: data.setLittleEndian16(dirlen);
1005:
1006: if (0 < domainlen) {
1007: data.outUnicodeString(domain, domainlen);
1008: } else {
1009: data.setLittleEndian16(0);
1010: }
1011:
1012: data.outUnicodeString(username, userlen);
1013:
1014: if ((logonflags & RDP_LOGON_AUTO) != 0) {
1015: data.outUnicodeString(password, passlen);
1016: }
1017:
1018: if ((logonflags & RDP_LOGON_BLOB) != 0
1019: && (logonflags & RDP_LOGON_AUTO) == 0) {
1020: data.setLittleEndian16(0);
1021: }
1022:
1023: if (0 < commandlen) {
1024: data.outUnicodeString(command, commandlen);
1025: } else {
1026: data.setLittleEndian16(0);
1027: }
1028:
1029: if (0 < dirlen) {
1030: data.outUnicodeString(directory, dirlen);
1031: } else {
1032: data.setLittleEndian16(0);
1033: }
1034:
1035: data.setLittleEndian16(2);
1036: data.setLittleEndian16(len_ip + 2);
1037: data.outUnicodeString("127.0.0.1", len_ip);
1038: data.setLittleEndian16(len_dll + 2);
1039: data.outUnicodeString("C:\\WINNT\\System32\\mstscax.dll",
1040: len_dll);
1041: data.setLittleEndian16(0xffc4);
1042: data.setLittleEndian16(0xffff);
1043: data.outUnicodeString("GTB, normaltid",
1044: 2 * "GTB, normaltid".length());
1045: data.incrementPosition(62 - 2 * "GTB, normaltid".length());
1046:
1047: data.setLittleEndian32(0x0a0000);
1048: data.setLittleEndian32(0x050000);
1049: data.setLittleEndian32(3);
1050: data.setLittleEndian32(0);
1051: data.setLittleEndian32(0);
1052:
1053: data.outUnicodeString("GTB, sommartid",
1054: 2 * "GTB, sommartid".length());
1055: data.incrementPosition(62 - 2 * "GTB, sommartid".length());
1056:
1057: data.setLittleEndian32(0x30000);
1058: data.setLittleEndian32(0x050000);
1059: data.setLittleEndian32(2);
1060: data.setLittleEndian32(0);
1061: data.setLittleEndian32(0xffffffc4);
1062: data.setLittleEndian32(0xfffffffe);
1063: data.setLittleEndian32(RdpOptions.rdp5_performanceflags);
1064: data.setLittleEndian32(0);
1065: }
1066:
1067: data.markEnd();
1068: byte[] buffer = new byte[data.getEnd()];
1069: data.copyToByteArray(buffer, 0, 0, data.getEnd());
1070: m_secureLayer.send(data, sec_flags);
1071: }
1072:
1073: public void sendConfirmActive(int rdp_shareid)
1074: throws RdpDesktopException, IOException, CryptoException {
1075: int caplen = RDP_CAPLEN_GENERAL + RDP_CAPLEN_BITMAP
1076: + RDP_CAPLEN_ORDER + RDP_CAPLEN_BMPCACHE
1077: + RDP_CAPLEN_COLCACHE + RDP_CAPLEN_ACTIVATE
1078: + RDP_CAPLEN_CONTROL + RDP_CAPLEN_POINTER
1079: + RDP_CAPLEN_SHARE + RDP_CAPLEN_UNKNOWN + 4;
1080:
1081: int sec_flags = RdpOptions.encryption ? (RDP5_FLAG | RdpSecure.SEC_ENCRYPT)
1082: : RDP5_FLAG;
1083: RdpPacket data = m_secureLayer.init(sec_flags, 6 + 14 + caplen
1084: + RDP_SOURCE.length);
1085:
1086: data.setLittleEndian16(2 + 14 + caplen + RDP_SOURCE.length);
1087: data.setLittleEndian16((RDP_PDU_CONFIRM_ACTIVE | 0x10));
1088: data.setLittleEndian16(m_secureLayer.getUserID() + 1001);
1089:
1090: data.setLittleEndian32(rdp_shareid);
1091: data.setLittleEndian16(0x3ea);
1092: data.setLittleEndian16(RDP_SOURCE.length);
1093: data.setLittleEndian16(caplen);
1094:
1095: data.copyFromByteArray(RDP_SOURCE, 0, data.getPosition(),
1096: RDP_SOURCE.length);
1097: data.incrementPosition(RDP_SOURCE.length);
1098: data.setLittleEndian16(0xd);
1099: data.incrementPosition(2);
1100:
1101: sendGeneralCaps(data);
1102: sendBitmapCaps(data);
1103: sendOrderCaps(data);
1104:
1105: if ((RdpOptions.use_rdp5 == true)
1106: && (RdpOptions.persistent_bitmap_caching == true)) {
1107: sendBitmapcache2Caps(data);
1108: } else {
1109: sendBitmapcacheCaps(data);
1110: }
1111:
1112: sendColorcacheCaps(data);
1113: sendActivateCaps(data);
1114: sendControlCaps(data);
1115: sendPointerCaps(data);
1116: sendShareCaps(data);
1117:
1118: sendUnknownCaps(data, 0x0d, 0x58, caps_0x0d);
1119: sendUnknownCaps(data, 0x0c, 0x08, caps_0x0c);
1120: sendUnknownCaps(data, 0x0e, 0x08, caps_0x0e);
1121: sendUnknownCaps(data, 0x10, 0x34, caps_0x10);
1122:
1123: data.markEnd();
1124: m_secureLayer.send(data, sec_flags);
1125: }
1126:
1127: public void sendSynchronize() throws RdpDesktopException,
1128: IOException, CryptoException {
1129: RdpPacket data = initData(4);
1130:
1131: data.setLittleEndian16(1); // type
1132: data.setLittleEndian16(1002);
1133:
1134: data.markEnd();
1135: sendData(data, RDP_DATA_PDU_SYNCHRONISE);
1136: }
1137:
1138: public void sendControl(int action) throws RdpDesktopException,
1139: IOException, CryptoException {
1140:
1141: RdpPacket data = initData(8);
1142:
1143: data.setLittleEndian16(action);
1144: data.setLittleEndian16(0);
1145: data.setLittleEndian32(0);
1146:
1147: data.markEnd();
1148: sendData(data, RDP_DATA_PDU_CONTROL);
1149: }
1150:
1151: public void sendInput(int time, int message_type, int device_flags,
1152: int param1, int param2) {
1153: RdpPacket data = null;
1154:
1155: try {
1156: data = initData(16);
1157: } catch (RdpDesktopException e) {
1158: e.printStackTrace();
1159: disconnect();
1160: return;
1161: }
1162:
1163: data.setLittleEndian16(1);
1164: data.setLittleEndian16(0);
1165:
1166: data.setLittleEndian32(time);
1167: data.setLittleEndian16(message_type);
1168: data.setLittleEndian16(device_flags);
1169: data.setLittleEndian16(param1);
1170: data.setLittleEndian16(param2);
1171:
1172: data.markEnd();
1173:
1174: try {
1175: sendData(data, RDP_DATA_PDU_INPUT);
1176: } catch (RdpDesktopException e) {
1177: e.printStackTrace();
1178: disconnect();
1179: return;
1180: } catch (CryptoException e) {
1181: e.printStackTrace();
1182: disconnect();
1183: return;
1184: } catch (IOException e) {
1185: e.printStackTrace();
1186: disconnect();
1187: return;
1188: }
1189: }
1190:
1191: public void sendFonts(int seq) throws RdpDesktopException,
1192: IOException, CryptoException {
1193: RdpPacket data = initData(8);
1194:
1195: data.setLittleEndian16(0);
1196: data.setLittleEndian16(0x3e);
1197: data.setLittleEndian16(seq);
1198: data.setLittleEndian16(0x32);
1199:
1200: data.markEnd();
1201: sendData(data, RDP_DATA_PDU_FONT2);
1202: }
1203:
1204: public void registerDrawingSurface(RdpDesktopFrame frame) {
1205: m_frame = frame;
1206: m_surface = m_frame.getCanvas();
1207: m_orders.registerDrawingSurface(m_surface);
1208: }
1209: }
|