// 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/cc240470.aspx
 * @see http://msdn.microsoft.com/en-us/library/cc240663.aspx
 */
public class ClientX224ConnectionRequestPDU extends OneTimeSwitch {

    public static final int X224_TPDU_CONNECTION_REQUEST = 0xe0;
    public static final int X224_TPDU_CONNECTION_CONFIRM = 0xd0;
    public static final int X224_TPDU_DISCONNECTION_REQUEST = 0x80;
    public static final int X224_TPDU_DISCONNECTION_CONFIRM = 0xc0;
    public static final int X224_TPDU_EXPEDITED_DATA = 0x10;
    public static final int X224_TPDU_DATA_ACKNOWLEDGE = 0x61;
    public static final int X224_TPDU_EXPEDITET_ACKNOWLEDGE = 0x40;
    public static final int X224_TPDU_REJECT = 0x51;
    public static final int X224_TPDU_ERROR = 0x70;
    public static final int X224_TPDU_PROTOCOL_IDENTIFIER = 0x01;

    /**
     * Reconnection cookie.
     */
    protected String userName;

    /**
     * Protocol to use:  RDP_NEG_REQ_PROTOCOL_SSL or RDP_NEG_REQ_PROTOCOL_HYBRID.
     */
    protected int protocol;

    public ClientX224ConnectionRequestPDU(String id, String userName, int protocol) {
        super(id);
        this.userName = userName;
        this.protocol = protocol;
    }

    @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 without length field
        int length = 33 + userName.length();
        ByteBuffer buf = new ByteBuffer(length, true);

        // Type (high nibble) = 0xe = CR TPDU; credit (low nibble) = 0
        buf.writeByte(X224_TPDU_CONNECTION_REQUEST);

        buf.writeShort(0); // Destination reference = 0
        buf.writeShort(0); // Source reference = 0
        buf.writeByte(0); // Class and options = 0
        buf.writeString("Cookie: mstshash=" + userName + "\r\n", RdpConstants.CHARSET_8); // Cookie

        // RDP_NEG_REQ::type
        buf.writeByte(RdpConstants.RDP_NEG_REQ_TYPE_NEG_REQ);
        // RDP_NEG_REQ::flags (0)
        buf.writeByte(RdpConstants.RDP_NEG_REQ_FLAGS);
        // RDP_NEG_REQ::length (constant: 8) short int in LE format
        buf.writeByte(0x08);
        buf.writeByte(0x00);

        // RDP_NEG_REQ: Requested protocols: PROTOCOL_SSL
        buf.writeIntLE(protocol);

        // Calculate length of packet and prepend it to buffer
        ByteBuffer data = new ByteBuffer(5);

        // Write length
        data.writeVariableIntLE(buf.length);

        // Reset length of buffer to actual length of data written
        data.length = data.cursor;

        buf.prepend(data);
        data.unref();

        pushDataToOTOut(buf);

        switchOff();
    }

    /**
     * Example.
     *
     * @see http://msdn.microsoft.com/en-us/library/cc240842.aspx
     * @see http://msdn.microsoft.com/en-us/library/cc240500.aspx
     */
    public static void main(String args[]) {
        // System.setProperty("streamer.Link.debug", "true");
        System.setProperty("streamer.Element.debug", "true");
        // System.setProperty("streamer.Pipeline.debug", "true");

        String cookie = "eltons";

        byte[] packet = new byte[] {

                0x03, // TPKT Header: version = 3
                0x00, // TPKT Header: Reserved = 0
                0x00, // TPKT Header: Packet length - high part
                0x2c, // TPKT Header: Packet length - low part (total = 44 bytes)
                0x27, // X.224: Length indicator (39 bytes)
                (byte)0xe0, // X.224: Type (high nibble) = 0xe = CR TPDU;
                // credit (low nibble) = 0
                0x00, 0x00, // X.224: Destination reference = 0
                0x00, 0x00, // X.224: Source reference = 0
                0x00, // X.224: Class and options = 0

                'C', 'o', 'o', 'k', 'i', 'e', ':', ' ', 'm', 's', 't', 's', 'h', 'a', 's', 'h', '=', 'e', 'l', 't', 'o', 'n', 's', // "Cookie: mstshash=eltons"
                '\r', '\n', // -Cookie terminator sequence

                0x01, // RDP_NEG_REQ::type (TYPE_RDP_NEG_REQ)
                0x00, // RDP_NEG_REQ::flags (0)
                0x08, 0x00, // RDP_NEG_REQ::length (8 bytes)
                0x01, 0x00, 0x00, 0x00 // RDP_NEG_REQ: Requested protocols
                // (PROTOCOL_SSL in little endian format)
        };

        MockSource source = new MockSource("source", ByteBuffer.convertByteArraysToByteBuffers(new byte[] {1, 2, 3}));
        Element cr = new ClientX224ConnectionRequestPDU("cr", cookie, RdpConstants.RDP_NEG_REQ_PROTOCOL_SSL);
        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, cr, tpkt, sink, mainSink);
        pipeline.link("source", "cr", "mainSink");
        pipeline.link("cr >" + OTOUT, "tpkt", "sink");
        pipeline.runMainLoop("source", STDOUT, false, false);
    }

}
