Telnet.java

/*
 * 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 org.apache.commons.net.telnet;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.time.Duration;
import java.util.Arrays;

import org.apache.commons.net.SocketClient;

class Telnet extends SocketClient {
    static final boolean debug = /* true; */ false;

    static final boolean debugoptions = /* true; */ false;

    static final byte[] COMMAND_DO = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO };

    static final byte[] COMMAND_DONT = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.DONT };

    static final byte[] COMMAND_WILL = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL };

    static final byte[] COMMAND_WONT = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT };

    static final byte[] COMMAND_SB = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.SB };

    static final byte[] COMMAND_SE = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.SE };

    static final int WILL_MASK = 0x01;
    static final int DO_MASK = 0x02;
    static final int REQUESTED_WILL_MASK = 0x04;
    static final int REQUESTED_DO_MASK = 0x08;

    /* public */
    static final int DEFAULT_PORT = 23;

    /* TERMINAL-TYPE option (start) */
    /**
     * Terminal type option
     */
    protected static final int TERMINAL_TYPE = 24;
    /**
     * Send (for subnegotiation)
     */
    protected static final int TERMINAL_TYPE_SEND = 1;
    /**
     * Is (for subnegotiation)
     */
    protected static final int TERMINAL_TYPE_IS = 0;

    /**
     * Is sequence (for subnegotiation)
     */
    static final byte[] COMMAND_IS = { (byte) TERMINAL_TYPE, (byte) TERMINAL_TYPE_IS };

    /* Code Section added for supporting AYT (start) */
    /**
     * AYT sequence
     */
    static final byte[] COMMAND_AYT = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.AYT };

    private final int[] doResponse;

    private final int[] willResponse;

    private final int[] options;

    /**
     * Terminal type
     */
    private String terminalType;
    /* TERMINAL-TYPE option (end) */

    /* open TelnetOptionHandler functionality (end) */

    /* open TelnetOptionHandler functionality (start) */
    /**
     * Array of option handlers
     */
    private final TelnetOptionHandler[] optionHandlers;

    /**
     * monitor to wait for AYT
     */
    private final Object aytMonitor = new Object();

    /**
     * flag for AYT
     */
    private volatile boolean aytFlag = true;
    /* Code Section added for supporting AYT (end) */

    /**
     * The stream on which to spy
     */
    private volatile OutputStream spyStream;

    /**
     * The notification handler
     */
    private TelnetNotificationHandler notifhand;

    /**
     * Empty Constructor
     */
    Telnet() {
        setDefaultPort(DEFAULT_PORT);
        doResponse = new int[TelnetOption.MAX_OPTION_VALUE + 1];
        willResponse = new int[TelnetOption.MAX_OPTION_VALUE + 1];
        options = new int[TelnetOption.MAX_OPTION_VALUE + 1];
        optionHandlers = new TelnetOptionHandler[TelnetOption.MAX_OPTION_VALUE + 1];
    }

    /* TERMINAL-TYPE option (start) */
    /**
     * This constructor lets you specify the terminal type.
     *
     * @param termtype - terminal type to be negotiated (ej. VT100)
     */
    Telnet(final String termtype) {
        setDefaultPort(DEFAULT_PORT);
        doResponse = new int[TelnetOption.MAX_OPTION_VALUE + 1];
        willResponse = new int[TelnetOption.MAX_OPTION_VALUE + 1];
        options = new int[TelnetOption.MAX_OPTION_VALUE + 1];
        terminalType = termtype;
        optionHandlers = new TelnetOptionHandler[TelnetOption.MAX_OPTION_VALUE + 1];
    }
    /* TERMINAL-TYPE option (end) */

    /**
     * Called upon connection.
     *
     * @throws IOException - Exception in I/O.
     */
    @Override
    protected void _connectAction_() throws IOException {
        /* (start). BUGFIX: clean the option info for each connection */
        for (int ii = 0; ii < TelnetOption.MAX_OPTION_VALUE + 1; ii++) {
            doResponse[ii] = 0;
            willResponse[ii] = 0;
            options[ii] = 0;
            if (optionHandlers[ii] != null) {
                optionHandlers[ii].setDo(false);
                optionHandlers[ii].setWill(false);
            }
        }
        /* (end). BUGFIX: clean the option info for each connection */

        super._connectAction_();
        _input_ = new BufferedInputStream(_input_);
        _output_ = new BufferedOutputStream(_output_);

        /* open TelnetOptionHandler functionality (start) */
        for (int ii = 0; ii < TelnetOption.MAX_OPTION_VALUE + 1; ii++) {
            if (optionHandlers[ii] != null) {
                if (optionHandlers[ii].getInitLocal()) {
                    requestWill(optionHandlers[ii].getOptionCode());
                }

                if (optionHandlers[ii].getInitRemote()) {
                    requestDo(optionHandlers[ii].getOptionCode());
                }
            }
        }
        /* open TelnetOptionHandler functionality (end) */
    }

    /* Code Section added for supporting spystreams (start) */
    /**
     * Registers an OutputStream for spying what's going on in the Telnet session.
     *
     * @param spystream - OutputStream on which session activity will be echoed.
     */
    void _registerSpyStream(final OutputStream spystream) {
        spyStream = spystream;
    }

    /* Code Section added for supporting AYT (start) */
    /**
     * Sends an {@code Are You There (AYT)} sequence and waits for the result.
     *
     * @param timeout - Time to wait for a response.
     * @throws IOException              - Exception in I/O.
     * @throws IllegalArgumentException - Illegal argument
     * @throws InterruptedException     - Interrupted during wait.
     * @return true if AYT received a response, false otherwise
     **/
    final boolean _sendAYT(final Duration timeout) throws IOException, IllegalArgumentException, InterruptedException {
        boolean retValue = false;
        synchronized (aytMonitor) {
            synchronized (this) {
                aytFlag = false;
                _output_.write(COMMAND_AYT);
                _output_.flush();
            }
            aytMonitor.wait(timeout.toMillis());
            if (!aytFlag) {
                aytFlag = true;
            } else {
                retValue = true;
            }
        }

        return retValue;
    }
    /* Code Section added for supporting AYT (end) */

    /**
     * Sends a command, automatically adds IAC prefix and flushes the output.
     *
     * @param cmd - command data to be sent
     * @throws IOException - Exception in I/O.
     * @since 3.0
     */
    final synchronized void _sendCommand(final byte cmd) throws IOException {
        _output_.write(TelnetCommand.IAC);
        _output_.write(cmd);
        _output_.flush();
    }

    /* open TelnetOptionHandler functionality (start) */
    /**
     * Manages subnegotiation for Terminal Type.
     *
     * @param subn - subnegotiation data to be sent
     * @throws IOException - Exception in I/O.
     **/
    final synchronized void _sendSubnegotiation(final int[] subn) throws IOException {
        if (debug) {
            System.err.println("SEND SUBNEGOTIATION: ");
            if (subn != null) {
                System.err.println(Arrays.toString(subn));
            }
        }
        if (subn != null) {
            _output_.write(COMMAND_SB);
            // Note _output_ is buffered, so might as well simplify by writing single bytes
            for (final int element : subn) {
                final byte b = (byte) element;
                if (b == (byte) TelnetCommand.IAC) { // cast is necessary because IAC is outside the signed byte range
                    _output_.write(b); // double any IAC bytes
                }
                _output_.write(b);
            }
            _output_.write(COMMAND_SE);

            /* Code Section added for sending the negotiation ASAP (start) */
            _output_.flush();
            /* Code Section added for sending the negotiation ASAP (end) */
        }
    }
    /* open TelnetOptionHandler functionality (end) */

    /**
     * Stops spying this Telnet.
     *
     */
    void _stopSpyStream() {
        spyStream = null;
    }

    /**
     * Registers a new TelnetOptionHandler for this telnet to use.
     *
     * @param opthand - option handler to be registered.
     * @throws InvalidTelnetOptionException - The option code is invalid.
     * @throws IOException                  on error
     **/
    void addOptionHandler(final TelnetOptionHandler opthand) throws InvalidTelnetOptionException, IOException {
        final int optcode = opthand.getOptionCode();
        if (!TelnetOption.isValidOption(optcode)) {
            throw new InvalidTelnetOptionException("Invalid Option Code", optcode);
        }
        if (optionHandlers[optcode] != null) {
            throw new InvalidTelnetOptionException("Already registered option", optcode);
        }
        optionHandlers[optcode] = opthand;
        if (isConnected()) {
            if (opthand.getInitLocal()) {
                requestWill(optcode);
            }

            if (opthand.getInitRemote()) {
                requestDo(optcode);
            }
        }
    }

    /**
     * Unregisters a TelnetOptionHandler.
     *
     * @param optcode - Code of the option to be unregistered.
     * @throws InvalidTelnetOptionException - The option code is invalid.
     * @throws IOException                  on error
     **/
    void deleteOptionHandler(final int optcode) throws InvalidTelnetOptionException, IOException {
        if (!TelnetOption.isValidOption(optcode)) {
            throw new InvalidTelnetOptionException("Invalid Option Code", optcode);
        }
        if (optionHandlers[optcode] == null) {
            throw new InvalidTelnetOptionException("Unregistered option", optcode);
        }
        final TelnetOptionHandler opthand = optionHandlers[optcode];
        optionHandlers[optcode] = null;

        if (opthand.getWill()) {
            requestWont(optcode);
        }

        if (opthand.getDo()) {
            requestDont(optcode);
        }
    }
    /* open TelnetOptionHandler functionality (end) */

    /* Code Section added for supporting AYT (start) */
    /**
     * Processes the response of an AYT
     */
    final synchronized void processAYTResponse() {
        if (!aytFlag) {
            synchronized (aytMonitor) {
                aytFlag = true;
                aytMonitor.notifyAll();
            }
        }
    }
    /* Code Section added for supporting AYT (end) */

    /**
     * Processes a COMMAND.
     *
     * @param command - option code to be set.
     **/
    void processCommand(final int command) {
        if (debugoptions) {
            System.err.println("RECEIVED COMMAND: " + command);
        }

        if (notifhand != null) {
            notifhand.receivedNegotiation(TelnetNotificationHandler.RECEIVED_COMMAND, command);
        }
    }

    /**
     * Processes a {@code DO} request.
     *
     * @param option - option code to be set.
     * @throws IOException - Exception in I/O.
     **/
    void processDo(final int option) throws IOException {
        if (debugoptions) {
            System.err.println("RECEIVED DO: " + TelnetOption.getOption(option));
        }

        if (notifhand != null) {
            notifhand.receivedNegotiation(TelnetNotificationHandler.RECEIVED_DO, option);
        }

        boolean acceptNewState = false;

        /* open TelnetOptionHandler functionality (start) */
        if (optionHandlers[option] != null) {
            acceptNewState = optionHandlers[option].getAcceptLocal();
        } else if (option == TERMINAL_TYPE && terminalType != null && !terminalType.isEmpty()) {
            acceptNewState = true;
        }
        /* TERMINAL-TYPE option (end) */
        /* open TelnetOptionHandler functionality (start) */

        if (willResponse[option] > 0) {
            --willResponse[option];
            if (willResponse[option] > 0 && stateIsWill(option)) {
                --willResponse[option];
            }
        }

        if (willResponse[option] == 0) {
            if (requestedWont(option)) {

                switch (option) {

                default:
                    break;

                }

                if (acceptNewState) {
                    setWantWill(option);
                    sendWill(option);
                } else {
                    ++willResponse[option];
                    sendWont(option);
                }
            } else {
                // Other end has acknowledged option.

                switch (option) {

                default:
                    break;

                }

            }
        }

        setWill(option);
    }

    /**
     * Processes a {@code DONT} request.
     *
     * @param option - option code to be set.
     * @throws IOException - Exception in I/O.
     **/
    void processDont(final int option) throws IOException {
        if (debugoptions) {
            System.err.println("RECEIVED DONT: " + TelnetOption.getOption(option));
        }
        if (notifhand != null) {
            notifhand.receivedNegotiation(TelnetNotificationHandler.RECEIVED_DONT, option);
        }
        if (willResponse[option] > 0) {
            --willResponse[option];
            if (willResponse[option] > 0 && stateIsWont(option)) {
                --willResponse[option];
            }
        }

        if (willResponse[option] == 0 && requestedWill(option)) {

            switch (option) {

            default:
                break;

            }

            /* FIX for a BUG in the negotiation (start) */
            if (stateIsWill(option) || requestedWill(option)) {
                sendWont(option);
            }

            setWantWont(option);
            /* FIX for a BUG in the negotiation (end) */
        }

        setWont(option);
    }

    /* TERMINAL-TYPE option (start) */
    /**
     * Processes a suboption negotiation.
     *
     * @param suboption       - subnegotiation data received
     * @param suboptionLength - length of data received
     * @throws IOException - Exception in I/O.
     **/
    void processSuboption(final int[] suboption, final int suboptionLength) throws IOException {
        if (debug) {
            System.err.println("PROCESS SUBOPTION.");
        }

        /* open TelnetOptionHandler functionality (start) */
        if (suboptionLength > 0) {
            if (optionHandlers[suboption[0]] != null) {
                final int[] responseSuboption = optionHandlers[suboption[0]].answerSubnegotiation(suboption, suboptionLength);
                _sendSubnegotiation(responseSuboption);
            } else if (suboptionLength > 1) {
                if (debug) {
                    for (int ii = 0; ii < suboptionLength; ii++) {
                        System.err.println("SUB[" + ii + "]: " + suboption[ii]);
                    }
                }
                if (suboption[0] == TERMINAL_TYPE && suboption[1] == TERMINAL_TYPE_SEND) {
                    sendTerminalType();
                }
            }
        }
        /* open TelnetOptionHandler functionality (end) */
    }

    /**
     * Processes a {@code WILL} request.
     *
     * @param option - option code to be set.
     * @throws IOException - Exception in I/O.
     **/
    void processWill(final int option) throws IOException {
        if (debugoptions) {
            System.err.println("RECEIVED WILL: " + TelnetOption.getOption(option));
        }

        if (notifhand != null) {
            notifhand.receivedNegotiation(TelnetNotificationHandler.RECEIVED_WILL, option);
        }

        boolean acceptNewState = false;

        /* open TelnetOptionHandler functionality (start) */
        if (optionHandlers[option] != null) {
            acceptNewState = optionHandlers[option].getAcceptRemote();
        }
        /* open TelnetOptionHandler functionality (end) */

        if (doResponse[option] > 0) {
            --doResponse[option];
            if (doResponse[option] > 0 && stateIsDo(option)) {
                --doResponse[option];
            }
        }

        if (doResponse[option] == 0 && requestedDont(option)) {

            switch (option) {

            default:
                break;

            }

            if (acceptNewState) {
                setWantDo(option);
                sendDo(option);
            } else {
                ++doResponse[option];
                sendDont(option);
            }
        }

        setDo(option);
    }

    /**
     * Processes a {@code WONT} request.
     *
     * @param option - option code to be set.
     * @throws IOException - Exception in I/O.
     **/
    void processWont(final int option) throws IOException {
        if (debugoptions) {
            System.err.println("RECEIVED WONT: " + TelnetOption.getOption(option));
        }

        if (notifhand != null) {
            notifhand.receivedNegotiation(TelnetNotificationHandler.RECEIVED_WONT, option);
        }

        if (doResponse[option] > 0) {
            --doResponse[option];
            if (doResponse[option] > 0 && stateIsDont(option)) {
                --doResponse[option];
            }
        }

        if (doResponse[option] == 0 && requestedDo(option)) {

            switch (option) {

            default:
                break;

            }

            /* FIX for a BUG in the negotiation (start) */
            if (stateIsDo(option) || requestedDo(option)) {
                sendDont(option);
            }

            setWantDont(option);
            /* FIX for a BUG in the negotiation (end) */
        }

        setDont(option);
    }

    /**
     * Registers a notification handler to which will be sent notifications of received telnet option negotiation commands.
     *
     * @param notifhand - TelnetNotificationHandler to be registered
     */
    public void registerNotifHandler(final TelnetNotificationHandler notifhand) {
        this.notifhand = notifhand;
    }

    /**
     * Requests a DO.
     *
     * @param option - Option code.
     * @throws IOException - Exception in I/O.
     **/
    final synchronized void requestDo(final int option) throws IOException {
        if (doResponse[option] == 0 && stateIsDo(option) || requestedDo(option)) {
            return;
        }
        setWantDo(option);
        ++doResponse[option];
        sendDo(option);
    }

    /**
     * Requests a {@code DONT}.
     *
     * @param option - Option code.
     * @throws IOException - Exception in I/O.
     **/
    final synchronized void requestDont(final int option) throws IOException {
        if (doResponse[option] == 0 && stateIsDont(option) || requestedDont(option)) {
            return;
        }
        setWantDont(option);
        ++doResponse[option];
        sendDont(option);
    }

    /**
     * Looks for the state of the option.
     *
     * @return returns true if a {@code DO} has been requested.
     *
     * @param option - option code to be looked up.
     */
    boolean requestedDo(final int option) {
        return (options[option] & REQUESTED_DO_MASK) != 0;
    }

    /**
     * Looks for the state of the option.
     *
     * @return returns true if a {@code DONT} has been requested
     *
     * @param option - option code to be looked up.
     */
    boolean requestedDont(final int option) {
        return !requestedDo(option);
    }

    /**
     * Looks for the state of the option.
     *
     * @return returns true if a {@code WILL} has been requested
     *
     * @param option - option code to be looked up.
     */
    boolean requestedWill(final int option) {
        return (options[option] & REQUESTED_WILL_MASK) != 0;
    }

    /**
     * Looks for the state of the option.
     *
     * @return returns true if a {@code WONT} has been requested
     *
     * @param option - option code to be looked up.
     */
    boolean requestedWont(final int option) {
        return !requestedWill(option);
    }

    /**
     * Requests a {@code WILL}.
     *
     * @param option - Option code.
     * @throws IOException - Exception in I/O.
     **/
    final synchronized void requestWill(final int option) throws IOException {
        if (willResponse[option] == 0 && stateIsWill(option) || requestedWill(option)) {
            return;
        }
        setWantWill(option);
        ++doResponse[option];
        sendWill(option);
    }

    /* TERMINAL-TYPE option (end) */

    /**
     * Requests a {@code WONT}.
     *
     * @param option - Option code.
     * @throws IOException - Exception in I/O.
     **/
    final synchronized void requestWont(final int option) throws IOException {
        if (willResponse[option] == 0 && stateIsWont(option) || requestedWont(option)) {
            return;
        }
        setWantWont(option);
        ++doResponse[option];
        sendWont(option);
    }

    /**
     * Sends a byte.
     *
     * @param b - byte to send
     * @throws IOException - Exception in I/O.
     **/
    final synchronized void sendByte(final int b) throws IOException {
        _output_.write(b);

        /* Code Section added for supporting spystreams (start) */
        spyWrite(b);
        /* Code Section added for supporting spystreams (end) */

    }

    /**
     * Sends a {@code DO}.
     *
     * @param option - Option code.
     * @throws IOException - Exception in I/O.
     **/
    final synchronized void sendDo(final int option) throws IOException {
        if (debug || debugoptions) {
            System.err.println("DO: " + TelnetOption.getOption(option));
        }
        _output_.write(COMMAND_DO);
        _output_.write(option);

        /* Code Section added for sending the negotiation ASAP (start) */
        _output_.flush();
        /* Code Section added for sending the negotiation ASAP (end) */
    }

    /**
     * Sends a {@code DONT}.
     *
     * @param option - Option code.
     * @throws IOException - Exception in I/O.
     **/
    final synchronized void sendDont(final int option) throws IOException {
        if (debug || debugoptions) {
            System.err.println("DONT: " + TelnetOption.getOption(option));
        }
        _output_.write(COMMAND_DONT);
        _output_.write(option);

        /* Code Section added for sending the negotiation ASAP (start) */
        _output_.flush();
        /* Code Section added for sending the negotiation ASAP (end) */
    }

    /**
     * Sends terminal type information.
     *
     * @throws IOException - Exception in I/O.
     */
    final synchronized void sendTerminalType() throws IOException {
        if (debug) {
            System.err.println("SEND TERMINAL-TYPE: " + terminalType);
        }
        if (terminalType != null) {
            _output_.write(COMMAND_SB);
            _output_.write(COMMAND_IS);
            _output_.write(terminalType.getBytes(getCharset()));
            _output_.write(COMMAND_SE);
            _output_.flush();
        }
    }

    /**
     * Sends a {@code WILL}.
     *
     * @param option - Option code.
     * @throws IOException - Exception in I/O.
     **/
    final synchronized void sendWill(final int option) throws IOException {
        if (debug || debugoptions) {
            System.err.println("WILL: " + TelnetOption.getOption(option));
        }
        _output_.write(COMMAND_WILL);
        _output_.write(option);

        /* Code Section added for sending the negotiation ASAP (start) */
        _output_.flush();
        /* Code Section added for sending the negotiation ASAP (end) */
    }

    /**
     * Sends a {@code WONT}.
     *
     * @param option - Option code.
     * @throws IOException - Exception in I/O.
     **/
    final synchronized void sendWont(final int option) throws IOException {
        if (debug || debugoptions) {
            System.err.println("WONT: " + TelnetOption.getOption(option));
        }
        _output_.write(COMMAND_WONT);
        _output_.write(option);

        /* Code Section added for sending the negotiation ASAP (start) */
        _output_.flush();
        /* Code Section added for sending the negotiation ASAP (end) */
    }

    /**
     * Sets the state of the option.
     *
     * @param option - option code to be set.
     * @throws IOException
     */
    void setDo(final int option) throws IOException {
        options[option] |= DO_MASK;

        /* open TelnetOptionHandler functionality (start) */
        if (requestedDo(option) && optionHandlers[option] != null) {
            optionHandlers[option].setDo(true);

            final int[] subneg = optionHandlers[option].startSubnegotiationRemote();

            if (subneg != null) {
                _sendSubnegotiation(subneg);
            }
        }
        /* open TelnetOptionHandler functionality (end) */
    }

    /**
     * Sets the state of the option.
     *
     * @param option - option code to be set.
     */
    void setDont(final int option) {
        options[option] &= ~DO_MASK;

        /* open TelnetOptionHandler functionality (start) */
        if (optionHandlers[option] != null) {
            optionHandlers[option].setDo(false);
        }
        /* open TelnetOptionHandler functionality (end) */
    }

    /**
     * Sets the state of the option.
     *
     * @param option - option code to be set.
     */
    void setWantDo(final int option) {
        options[option] |= REQUESTED_DO_MASK;
    }

    /**
     * Sets the state of the option.
     *
     * @param option - option code to be set.
     */
    void setWantDont(final int option) {
        options[option] &= ~REQUESTED_DO_MASK;
    }

    /**
     * Sets the state of the option.
     *
     * @param option - option code to be set.
     */
    void setWantWill(final int option) {
        options[option] |= REQUESTED_WILL_MASK;
    }

    /**
     * Sets the state of the option.
     *
     * @param option - option code to be set.
     */
    void setWantWont(final int option) {
        options[option] &= ~REQUESTED_WILL_MASK;
    }

    /**
     * Sets the state of the option.
     *
     * @param option - option code to be set.
     * @throws IOException
     */
    void setWill(final int option) throws IOException {
        options[option] |= WILL_MASK;

        /* open TelnetOptionHandler functionality (start) */
        if (requestedWill(option) && optionHandlers[option] != null) {
            optionHandlers[option].setWill(true);

            final int[] subneg = optionHandlers[option].startSubnegotiationLocal();

            if (subneg != null) {
                _sendSubnegotiation(subneg);
            }
        }
        /* open TelnetOptionHandler functionality (end) */
    }

    /* open TelnetOptionHandler functionality (start) */

    /**
     * Sets the state of the option.
     *
     * @param option - option code to be set.
     */
    void setWont(final int option) {
        options[option] &= ~WILL_MASK;

        /* open TelnetOptionHandler functionality (start) */
        if (optionHandlers[option] != null) {
            optionHandlers[option].setWill(false);
        }
        /* open TelnetOptionHandler functionality (end) */
    }

    /**
     * Sends a read char on the spy stream.
     *
     * @param ch - character read from the session
     */
    void spyRead(final int ch) {
        final OutputStream spy = spyStream;
        if (spy != null) {
            try {
                if (ch != '\r') // never write '\r' on its own
                {
                    if (ch == '\n') {
                        spy.write('\r'); // add '\r' before '\n'
                    }
                    spy.write(ch); // write original character
                    spy.flush();
                }
            } catch (final IOException e) {
                spyStream = null;
            }
        }
    }

    /**
     * Sends a written char on the spy stream.
     *
     * @param ch - character written to the session
     */
    void spyWrite(final int ch) {
        if (!(stateIsDo(TelnetOption.ECHO) && requestedDo(TelnetOption.ECHO))) {
            final OutputStream spy = spyStream;
            if (spy != null) {
                try {
                    spy.write(ch);
                    spy.flush();
                } catch (final IOException e) {
                    spyStream = null;
                }
            }
        }
    }
    /* Code Section added for supporting spystreams (end) */

    /**
     * Looks for the state of the option.
     *
     * @return returns true if a {@code DO} has been acknowledged.
     *
     * @param option - option code to be looked up.
     */
    boolean stateIsDo(final int option) {
        return (options[option] & DO_MASK) != 0;
    }

    /**
     * Looks for the state of the option.
     *
     * @return returns true if a {@code DONT} has been acknowledged
     *
     * @param option - option code to be looked up.
     */
    boolean stateIsDont(final int option) {
        return !stateIsDo(option);
    }

    /**
     * Looks for the state of the option.
     *
     * @return returns true if a {@code WILL} has been acknowledged
     *
     * @param option - option code to be looked up.
     */
    boolean stateIsWill(final int option) {
        return (options[option] & WILL_MASK) != 0;
    }

    /**
     * Looks for the state of the option.
     *
     * @return returns true if a {@code WONT} has been acknowledged
     *
     * @param option - option code to be looked up.
     */
    boolean stateIsWont(final int option) {
        return !stateIsWill(option);
    }

    /**
     * Unregisters the current notification handler.
     *
     */
    public void unregisterNotifHandler() {
        this.notifhand = null;
    }
}