// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
package rdpclient.rdp;

import streamer.ByteBuffer;
import streamer.Element;
import streamer.Link;
import streamer.OneTimeSwitch;
import streamer.Pipeline;
import streamer.PipelineImpl;
import streamer.debug.MockSink;
import streamer.debug.MockSource;

/**
 * @see http://msdn.microsoft.com/en-us/library/cc240475.aspx
 */
public class ClientInfoPDU extends OneTimeSwitch {

    public static final int INFO_MOUSE = 0x1;
    public static final int INFO_DISABLECTRLALTDEL = 0x2;
    public static final int INFO_UNICODE = 0x10;

    public static final int INFO_MAXIMIZESHELL = 0x20;
    public static final int INFO_LOGONNOTIFY = 0x40;
    public static final int INFO_ENABLEWINDOWSKEY = 0x100;
    public static final int INFO_MOUSE_HAS_WHEEL = 0x00020000;
    public static final int INFO_NOAUDIOPLAYBACK = 0x00080000;

    public static final int PERF_DISABLE_WALLPAPER = 0x1;
    public static final int PERF_DISABLE_FULLWINDOWDRAG = 0x2;
    public static final int PERF_DISABLE_MENUANIMATIONS = 0x4;

    protected byte[] userName = "".getBytes(RdpConstants.CHARSET_16);
    protected byte[] password = "".getBytes(RdpConstants.CHARSET_16); // No effect
    protected byte[] alternateShell = "".getBytes(RdpConstants.CHARSET_16);
    protected byte[] domain = "".getBytes(RdpConstants.CHARSET_16);
    protected byte[] workingDir = "".getBytes(RdpConstants.CHARSET_16);
    protected byte[] clientAddress = "192.168.0.100".getBytes(RdpConstants.CHARSET_16);
    protected byte[] clientDir = "C:\\Windows\\System32\\mstscax.dll".getBytes(RdpConstants.CHARSET_16);

    protected String standardTimeZoneName = "EET, Standard Time";
    protected String daylightTimeZoneName = "EET, Summer Time";
    protected int standardTimeZoneBias = 0; /* in minutes */
    protected int daylightTimeZoneBias = 60; /* in minutes */

    public ClientInfoPDU(String id, String userName) {
        super(id);
        this.userName = userName.getBytes(RdpConstants.CHARSET_16);
    }

    @Override
    protected void handleOneTimeData(ByteBuffer buf, Link link) {
        if (buf == null)
            return;

        throw new RuntimeException("Unexpected packet: " + buf + ".");
    }

    @Override
    protected void onStart() {
        super.onStart();

        // Length of packet
        ByteBuffer buf = new ByteBuffer(1024, true);

        // MCS Send Data Request PDU
        buf.writeByte(0x64);

        // Initiator: 0x03 + 1001 = 1004
        buf.writeShort(3);

        // Channel ID: 1003
        buf.writeShort(1003);

        // Data priority: high, segmentation: begin | end (0x40 | 0x20 | 0x10 = 0x70)
        buf.writeByte(0x70);

        // User data length: (variable length field)
        int length = 224 + userName.length + password.length + alternateShell.length + domain.length + workingDir.length + clientAddress.length + clientDir.length;
        buf.writeShort(length | 0x8000);

        // Flags: SEC_INFO_PKT (0x4000)
        buf.writeShort(0x4000);

        // TS_SECURITY_HEADER::flagsHi - ignored
        buf.writeShort(0x0000);

        // Codepage: 0 (UNKNOWN, LE) (use  0x04090409  (1033,1033) for EN_US)
        buf.writeIntLE(0x0000);

        // Flags
        buf.writeIntLE(INFO_MOUSE | INFO_DISABLECTRLALTDEL | INFO_UNICODE |
                INFO_MAXIMIZESHELL | INFO_LOGONNOTIFY | INFO_ENABLEWINDOWSKEY |
                INFO_MOUSE_HAS_WHEEL | INFO_NOAUDIOPLAYBACK);

        //
        // Lengths
        //

        // cbDomain length: 0 bytes (LE) (NOT including size of mandatory NULL terminator)
        buf.writeShortLE(domain.length);

        // cbUserName length: 16 bytes (0x10, LE) (NOT including size of mandatory NULL terminator)
        buf.writeShortLE(userName.length);

        // cbPassword length: (LE) (NOT including size of mandatory NULL terminator)
        buf.writeShortLE(password.length);

        // cbAlternateShell:  (LE) (NOT including size of mandatory NULL terminator)
        buf.writeShortLE(alternateShell.length);

        // cbWorkingDir: (LE) (NOT including size of mandatory NULL terminator)
        buf.writeShortLE(workingDir.length);

        //
        // Values
        //

        // Domain: (UCS2), see cbDomain
        buf.writeBytes(domain);
        buf.writeShort(0);

        // User name: (UCS2), see cbUserName
        buf.writeBytes(userName);
        buf.writeShort(0);

        // Password: (UCS2), see cbPassword
        buf.writeBytes(password);
        buf.writeShort(0);

        // Alternate shell: (UCS2), see cbAlternateShell
        buf.writeBytes(alternateShell);
        buf.writeShort(0);

        // Working directory: (UCS2), see cbWorkingDir
        buf.writeBytes(workingDir);
        buf.writeShort(0);

        // Client address family: 2 (AF_INET, LE)
        buf.writeShortLE(2);

        // cbClientAddress: ( LE) (including the size of the mandatory NULL terminator)
        buf.writeShortLE(clientAddress.length + 2);

        // Client address: (UCS2)
        buf.writeBytes(clientAddress);
        buf.writeShort(0);

        // cbClientDir: 64 bytes (0x40, LE) (including the size of the mandatory NULL terminator)
        buf.writeShortLE(clientDir.length + 2);

        // Client directory: (UCS2)
        buf.writeBytes(clientDir);
        buf.writeShort(0);

        //
        // Client time zone:
        //

        // Bias: 0 minutes (LE)
        buf.writeIntLE(0);

        // Standard name: "EET, Standard Time" (fixed string: 64 bytes, UCS2)
        buf.writeFixedString(62, standardTimeZoneName, RdpConstants.CHARSET_16);
        buf.writeShort(0);

        // Standard date
        buf.writeBytes(new byte[] {
                // wYear: 0 (LE)
                (byte)0x00, (byte)0x00,
                // wMonth: unknown (LE)
                (byte)0x00, (byte)0x00,
                // wDayOfWeek: Sunday (LE)
                (byte)0x00, (byte)0x00,
                // wDay: unknown (LE)
                (byte)0x00, (byte)0x00,
                // wHour: 0 (LE)
                (byte)0x00, (byte)0x00,
                // wMinute: 0 (LE)
                (byte)0x00, (byte)0x00,
                // wSecond: 0 (LE)
                (byte)0x00, (byte)0x00,
                // wMilliseconds: 0
                (byte)0x00, (byte)0x00,

        });

        // StandardBias: 0 minutes (LE)
        buf.writeIntLE(standardTimeZoneBias);

        // Daylight name: "EET, Summer Time" (fixed string: 64 bytes, UCS2)
        buf.writeFixedString(62, daylightTimeZoneName, RdpConstants.CHARSET_16);
        buf.writeShort(0);

        // Daylight date
        buf.writeBytes(new byte[] {
                // wYear: 0 (LE)
                (byte)0x00, (byte)0x00,
                // wMonth: unknown (LE)
                (byte)0x00, (byte)0x00,
                // wDayOfWeek: Sunday (LE)
                (byte)0x00, (byte)0x00,
                // wDay: unknown (LE)
                (byte)0x00, (byte)0x00,
                // wHour: 0 (LE)
                (byte)0x00, (byte)0x00,
                // wMinute: 0 (LE)
                (byte)0x00, (byte)0x00,
                // wSecond: 0 (LE)
                (byte)0x00, (byte)0x00,
                // wMilliseconds: 0
                (byte)0x00, (byte)0x00,

        });

        // Daylight bias: 60 minutes (LE)
        buf.writeIntLE(daylightTimeZoneBias);

        // Client session ID: 0x00000000 (LE)
        buf.writeIntLE(0);

        // Performance flags: 0x7 (LE) = PERF_DISABLE_WALLPAPER (0x1), PERF_DISABLE_FULLWINDOWDRAG (0x2), PERF_DISABLE_MENUANIMATIONS (0x4)
        buf.writeIntLE(PERF_DISABLE_WALLPAPER | PERF_DISABLE_FULLWINDOWDRAG | PERF_DISABLE_MENUANIMATIONS);

        // cbAutoReconnectCookie: 0 bytes (LE)
        buf.writeShortLE(0);

        // Trim buffer to actual length of data written
        buf.trimAtCursor();

        pushDataToOTOut(buf);

        switchOff();
    }

    /**
     * Example.
     */
    public static void main(String args[]) {
        // System.setProperty("streamer.Link.debug", "true");
        System.setProperty("streamer.Element.debug", "true");
        // System.setProperty("streamer.Pipeline.debug", "true");

        /* @formatter:off */
        byte[] packet = new byte[] {

                // TPKT
                (byte) 0x03, (byte) 0x00,

                // TPKT length: 343 bytes
                (byte) 0x01, (byte) 0x57,

                // X224 Data PDU
                (byte) 0x02, (byte) 0xf0, (byte) 0x80,


                // MCS Send Data Request PDU
                (byte) 0x64,

                // Initiator: 0x03 + 1001 = 1004
                (byte) 0x00, (byte) 0x03,

                // Channel ID: 1003 (IO Channel)
                (byte) 0x03, (byte) 0xeb,

                // Data priority: high, segmentation: begin | end (0x40 | 0x20 | 0x10 = 0x70)
                (byte) 0x70,

                // User data length: 328  (0x148) bytes, variable length field
                (byte) 0x81, (byte) 0x48,

                // Flags: SEC_INFO_PKT (0x4000)
                (byte) 0x40, (byte) 0x00,

                // TS_SECURITY_HEADER::flagsHi - ignored
                (byte) 0x00, (byte) 0x00,

                // Codepage: 0 (UNKNOWN, LE) (use  0x04090409  (1033,1033) for EN_US)
                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,

                // Flags: 0xa0173 (LE), INFO_MOUSE (0x1), INFO_DISABLECTRLALTDEL (0x2), INFO_UNICODE (0x10),
                // INFO_MAXIMIZESHELL (0x20), INFO_LOGONNOTIFY (0x40), INFO_ENABLEWINDOWSKEY (0x100),
                // INFO_MOUSE_HAS_WHEEL (0x00020000), INFO_NOAUDIOPLAYBACK (0x00080000),
                (byte) 0x73, (byte) 0x01, (byte) 0x0a, (byte) 0x00,

                // Lengths

                // cbDomain length: 0 bytes (LE) (NOT including size of mandatory NULL terminator)
                (byte) 0x00, (byte) 0x00,

                // cbUserName length: 16 bytes (0x10, LE) (NOT including size of mandatory NULL terminator)
                (byte) 0x10, (byte) 0x00,

                // cbPassword length: 0 bytes (LE) (NOT including size of mandatory NULL terminator)
                (byte) 0x00, (byte) 0x00,

                // cbAlternateShell:  0 bytes (LE) (NOT including size of mandatory NULL terminator)
                (byte) 0x00, (byte) 0x00,

                // cbWorkingDir: 0 bytes (LE) (NOT including size of mandatory NULL terminator)
                (byte) 0x00, (byte) 0x00,

                // Values

                // Domain: "" (UCS2), see cbDomain
                (byte) 0x00, (byte) 0x00,

                // User name: "vlisivka" (UCS2), see cbUserName
                (byte) 0x76, (byte) 0x00, (byte) 0x6c, (byte) 0x00, (byte) 0x69, (byte) 0x00, (byte) 0x73, (byte) 0x00,
                (byte) 0x69, (byte) 0x00, (byte) 0x76, (byte) 0x00, (byte) 0x6b, (byte) 0x00, (byte) 0x61, (byte) 0x00,
                (byte) 0x00, (byte) 0x00,

                // Password: "" (UCS2), see cbPassword
                (byte) 0x00, (byte) 0x00,

                // Alternate shell: "" (UCS2), see cbAlternateShell
                (byte) 0x00, (byte) 0x00,

                // Working directory: "" (UCS2), see cbWorkingDir
                (byte) 0x00, (byte) 0x00,

                // Client address family: 2 (AF_INET, LE)
                (byte) 0x02, (byte) 0x00,

                // cbClientAddress = 28 bytes (0x1c, LE) (including the size of the mandatory NULL terminator)
                (byte) 0x1c, (byte) 0x00,

                // Client address: "192.168.0.100" (UCS2)
                (byte) 0x31, (byte) 0x00, (byte) 0x39, (byte) 0x00, (byte) 0x32, (byte) 0x00, (byte) 0x2e, (byte) 0x00,
                (byte) 0x31, (byte) 0x00, (byte) 0x36, (byte) 0x00, (byte) 0x38, (byte) 0x00, (byte) 0x2e, (byte) 0x00,
                (byte) 0x30, (byte) 0x00, (byte) 0x2e, (byte) 0x00, (byte) 0x31, (byte) 0x00, (byte) 0x30, (byte) 0x00,
                (byte) 0x30, (byte) 0x00, (byte) 0x00, (byte) 0x00,

                // cbClientDir: 64 bytes (0x40, LE) (including the size of the mandatory NULL terminator)
                (byte) 0x40, (byte) 0x00,

                // Client directory: "C:\Windows\System32\mstscax.dll" (UCS2)
                (byte) 0x43, (byte) 0x00, (byte) 0x3a, (byte) 0x00, (byte) 0x5c, (byte) 0x00, (byte) 0x57, (byte) 0x00,
                (byte) 0x69, (byte) 0x00, (byte) 0x6e, (byte) 0x00, (byte) 0x64, (byte) 0x00, (byte) 0x6f, (byte) 0x00,
                (byte) 0x77, (byte) 0x00, (byte) 0x73, (byte) 0x00, (byte) 0x5c, (byte) 0x00, (byte) 0x53, (byte) 0x00,
                (byte) 0x79, (byte) 0x00, (byte) 0x73, (byte) 0x00, (byte) 0x74, (byte) 0x00, (byte) 0x65, (byte) 0x00,
                (byte) 0x6d, (byte) 0x00, (byte) 0x33, (byte) 0x00, (byte) 0x32, (byte) 0x00, (byte) 0x5c, (byte) 0x00,
                (byte) 0x6d, (byte) 0x00, (byte) 0x73, (byte) 0x00, (byte) 0x74, (byte) 0x00, (byte) 0x73, (byte) 0x00,
                (byte) 0x63, (byte) 0x00, (byte) 0x61, (byte) 0x00, (byte) 0x78, (byte) 0x00, (byte) 0x2e, (byte) 0x00,
                (byte) 0x64, (byte) 0x00, (byte) 0x6c, (byte) 0x00, (byte) 0x6c, (byte) 0x00, (byte) 0x00, (byte) 0x00,

                //
                // Client time zone:

                // Bias: 0 minutes (LE)
                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,

                // Standard name: "EET, Standard Time" (fixed string: 64 bytes, UCS2)
                (byte) 0x45, (byte) 0x00, (byte) 0x45, (byte) 0x00, (byte) 0x54, (byte) 0x00, (byte) 0x2c, (byte) 0x00,
                (byte) 0x20, (byte) 0x00, (byte) 0x53, (byte) 0x00, (byte) 0x74, (byte) 0x00, (byte) 0x61, (byte) 0x00,
                (byte) 0x6e, (byte) 0x00, (byte) 0x64, (byte) 0x00, (byte) 0x61, (byte) 0x00, (byte) 0x72, (byte) 0x00,
                (byte) 0x64, (byte) 0x00, (byte) 0x20, (byte) 0x00, (byte) 0x54, (byte) 0x00, (byte) 0x69, (byte) 0x00,
                (byte) 0x6d, (byte) 0x00, (byte) 0x65, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,

                //
                // Standard date
                // wYear: 0 (LE)
                (byte) 0x00, (byte) 0x00,
                // wMonth: unknown (LE)
                (byte) 0x00, (byte) 0x00,
                // wDayOfWeek: Sunday (LE)
                (byte) 0x00, (byte) 0x00,
                // wDay: unknown (LE)
                (byte) 0x00, (byte) 0x00,
                // wHour: 0 (LE)
                (byte) 0x00, (byte) 0x00,
                // wMinute: 0 (LE)
                (byte) 0x00, (byte) 0x00,
                // wSecond: 0 (LE)
                (byte) 0x00, (byte) 0x00,
                // wMilliseconds: 0
                (byte) 0x00, (byte) 0x00,

                // StandardBias: 0 minutes (LE)
                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,

                // Daylight name: "EET, Summer Time" (fixed string: 64 bytes, UCS2)
                (byte) 0x45, (byte) 0x00, (byte) 0x45, (byte) 0x00, (byte) 0x54, (byte) 0x00, (byte) 0x2c, (byte) 0x00,
                (byte) 0x20, (byte) 0x00, (byte) 0x53, (byte) 0x00, (byte) 0x75, (byte) 0x00, (byte) 0x6d, (byte) 0x00,
                (byte) 0x6d, (byte) 0x00, (byte) 0x65, (byte) 0x00, (byte) 0x72, (byte) 0x00, (byte) 0x20, (byte) 0x00,
                (byte) 0x54, (byte) 0x00, (byte) 0x69, (byte) 0x00, (byte) 0x6d, (byte) 0x00, (byte) 0x65, (byte) 0x00,
                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,

                // Daylight date
                // wYear: 0 (LE)
                (byte) 0x00, (byte) 0x00,
                // wMonth: unknown (LE)
                (byte) 0x00, (byte) 0x00,
                // wDayOfWeek: Sunday (LE)
                (byte) 0x00, (byte) 0x00,
                // wDay: unknown (LE)
                (byte) 0x00, (byte) 0x00,
                // wHour: 0 (LE)
                (byte) 0x00, (byte) 0x00,
                // wMinute: 0 (LE)
                (byte) 0x00, (byte) 0x00,
                // wSecond: 0 (LE)
                (byte) 0x00, (byte) 0x00,
                // wMilliseconds: 0
                (byte) 0x00, (byte) 0x00,

                // Daylight bias: 60 minutes (LE)
                (byte) 0x3c, (byte) 0x00, (byte) 0x00, (byte) 0x00,


                // Client session ID: 0x00000000 (LE)
                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,

                // Performance flags: 0x7 (LE) = PERF_DISABLE_WALLPAPER (0x1), PERF_DISABLE_FULLWINDOWDRAG (0x2), PERF_DISABLE_MENUANIMATIONS (0x4)
                (byte) 0x07, (byte) 0x00, (byte) 0x00, (byte) 0x00,

                // cbAutoReconnectCookie: 0 bytes (LE)
                (byte) 0x00, (byte) 0x00,
        };
        /* @formatter:on */

        MockSource source = new MockSource("source", ByteBuffer.convertByteArraysToByteBuffers(new byte[] {1, 2, 3}));
        Element client_info = new ClientInfoPDU("client_info", "vlisivka");
        Element x224 = new ClientX224DataPDU("x224");
        Element tpkt = new ClientTpkt("tpkt");
        Element sink = new MockSink("sink", ByteBuffer.convertByteArraysToByteBuffers(packet));
        Element mainSink = new MockSink("mainSink", ByteBuffer.convertByteArraysToByteBuffers(new byte[] {1, 2, 3}));

        Pipeline pipeline = new PipelineImpl("test");
        pipeline.add(source, client_info, x224, tpkt, sink, mainSink);
        pipeline.link("source", "client_info", "mainSink");
        pipeline.link("client_info >" + OTOUT, "x224", "tpkt", "sink");
        pipeline.runMainLoop("source", STDOUT, false, false);
    }

}
