/
etc/
lib/
src/Abilities/
src/Abilities/Skills/
src/Abilities/Spells/
src/Abilities/Spells/Enums/
src/Affects/
src/ArtheaConsole/
src/ArtheaConsole/Properties/
src/ArtheaGUI/Properties/
src/Clans/Enums/
src/Commands/Communication/
src/Commands/ItemCommands/
src/Connections/
src/Connections/Colors/
src/Connections/Enums/
src/Connections/Players/
src/Connections/Players/Enums/
src/Continents/
src/Continents/Areas/
src/Continents/Areas/Characters/
src/Continents/Areas/Characters/Enums/
src/Continents/Areas/Items/
src/Continents/Areas/Items/Enums/
src/Continents/Areas/Rooms/
src/Continents/Areas/Rooms/Enums/
src/Continents/Areas/Rooms/Exits/
src/Creation/
src/Creation/Attributes/
src/Creation/Interfaces/
src/Database/
src/Database/Interfaces/
src/Environment/
src/Properties/
src/Scripts/Enums/
src/Scripts/Interfaces/
#region Arthea License

/***********************************************************************
*  Arthea MUD by R. Jennings (2007)      http://arthea.googlecode.com/ *
*  By using this code you comply with the Artistic and GPLv2 Licenses. *
***********************************************************************/

#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Arthea.Environment;

namespace Arthea.Connections
{
    /// <summary>
    /// Telnet options
    /// </summary>
    public struct Telopt
    {
        /// <summary>
        /// backspace
        /// </summary>
        public const byte Backspace = 8;

        /// <summary>
        /// MCCP v1
        /// </summary>
        public const byte Compress = 85;

        /// <summary>
        /// MCCP v2
        /// </summary>
        public const byte Compress2 = 86;

        /// <summary>
        /// server does
        /// </summary>
        public const byte Do = 253;

        /// <summary>
        /// server doesn't
        /// </summary>
        public const byte Dont = 254;

        /// <summary>
        /// echo
        /// </summary>
        public const byte Echo = 1;

        /// <summary>
        /// iac
        /// </summary>
        public const byte IAC = 255;

        /// <summary>
        /// negotiate about window size
        /// </summary>
        public const byte NAWS = 31;

        /// <summary>
        /// new-environment
        /// </summary>
        public const byte NE = 39;

        /// <summary>
        /// no operation
        /// </summary>
        public const byte NOP = 241;

        /// <summary>
        /// sub negotiations
        /// </summary>
        public const byte SB = 250;

        /// <summary>
        /// signal end of sub negotiations
        /// </summary>
        public const byte SE = 240;

        /// <summary>
        /// send option
        /// </summary>
        public const byte Send = 1;

        /// <summary>
        /// Suppress Go Ahead
        /// </summary>
        public const byte SGA = 3;

        /// <summary>
        /// Terminal speed
        /// </summary>
        public const byte TSPEED = 32;

        /// <summary>
        /// terminal type
        /// </summary>
        public const byte TTYPE = 24;

        /// <summary>
        /// Value for NE.
        /// </summary>
        public const byte Value = 1;

        /// <summary>
        /// Variable for NE.
        /// </summary>
        public const byte Var = 0;

        /// <summary>
        /// client will
        /// </summary>
        public const byte Will = 251;

        /// <summary>
        /// client wont
        /// </summary>
        public const byte Wont = 252;
    }

    /// <summary>
    /// Implementation of telnet.
    /// </summary>
    public struct Telnet
    {
        #region Fields (6)

        /// <summary>
        /// Turn on MCCP v2
        /// </summary>
        public static readonly byte[] Compress2Start =
            new byte[] {Telopt.IAC, Telopt.SB, Telopt.Compress2, Telopt.IAC, Telopt.SE, 0};

        /// <summary>
        /// Turn on MCCP v1
        /// </summary>
        public static readonly byte[] CompressStart =
            new byte[] {Telopt.IAC, Telopt.SB, Telopt.Compress, Telopt.Will, Telopt.SE, 0};

        /// <summary>
        /// Turns echo off
        /// </summary>
        public static readonly byte[] EchoOff = new byte[] {Telopt.IAC, Telopt.Will, Telopt.Echo, 0};

        /// <summary>
        /// Turns echo on
        /// </summary>
        public static readonly byte[] EchoOn = new byte[] {Telopt.IAC, Telopt.Wont, Telopt.Echo, 0};

        /// <summary>
        /// Announces naws capability
        /// </summary>
        public static readonly byte[] NawsDo = new byte[] {Telopt.IAC, Telopt.Do, Telopt.NAWS, 0};

        private static readonly byte[] RequestTType =
            new byte[] {Telopt.IAC, Telopt.SB, Telopt.TTYPE, Telopt.Send, Telopt.IAC, Telopt.SE};

        #endregion

        #region Methods (3)

        private static byte[] RequestEnvVars()
        {
            List<byte> bytes = new List<byte>();

            bytes.Add(Telopt.IAC);
            bytes.Add(Telopt.SB);
            bytes.Add(Telopt.NE);
            bytes.Add(Telopt.Send);
            bytes.Add(Telopt.Var);

            foreach (byte c in Globals.Encoding.GetBytes("\"SYSTEMTYPE\""))
                bytes.Add(c);

            bytes.Add(Telopt.IAC);
            bytes.Add(Telopt.SE);

            return bytes.ToArray();
        }

        /// <summary>
        /// Advertises telnet options to the specified connection.
        /// </summary>
        /// <param name="conn">The connection.</param>
        public static void Advertise(Connection conn)
        {
            conn.WriteToSocket(new byte[] {Telopt.IAC, Telopt.Will, Telopt.Compress2, 0});
            conn.WriteToSocket(new byte[] {Telopt.IAC, Telopt.Will, Telopt.Compress, 0});
            conn.WriteToSocket(new byte[] {Telopt.IAC, Telopt.Will, Telopt.NAWS, 0});
            conn.WriteToSocket(new byte[] {Telopt.IAC, Telopt.Will, Telopt.NE, 0});
            conn.WriteToSocket(new byte[] {Telopt.IAC, Telopt.Will, Telopt.TSPEED, 0});
            conn.WriteToSocket(new byte[] {Telopt.IAC, Telopt.Will, Telopt.TTYPE, 0});
        }

        /// <summary>
        /// Processes any telnet options.
        /// </summary>
        /// <param name="bytes">The bytes.</param>
        /// <param name="index">The index.</param>
        /// <param name="con">The connection.</param>
        public static void Process(byte[] bytes, ref int index, Connection con)
        {
            Debug.Assert(bytes[index] == Telopt.IAC);

            index++;

            switch (bytes[index])
            {
                case Telopt.Will:
                    switch (bytes[++index])
                    {
                        case Telopt.TSPEED:
                            break;
                        case Telopt.TTYPE:
                            con.WriteToSocket(RequestTType);
                            break;
                        case Telopt.NE:
                            con.WriteToSocket(RequestEnvVars());
                            break;
                        case Telopt.NAWS:
                            con.WriteToSocket(NawsDo);
                            break;
                        case Telopt.SGA:
                            break;
                        default:
                            Log.Error("unknown telopt will {0}", bytes[index]);
                            break;
                    }
                    break;
                case Telopt.Wont:
                    index++;
                    Log.Error("unknown telopt won't {0}", bytes[index]);
                    break;
                case Telopt.Do:
                    switch (bytes[++index])
                    {
                        case Telopt.Echo:
                            break;
                        case Telopt.SGA:
                            break;
                        case Telopt.Compress:
                            if (con.Flags.Has(ConnectionFlags.MCCP))
                            {
                                con.WriteToSocket(new byte[] {Telopt.IAC, Telopt.Wont, Telopt.Compress, 0});
                            }
                            else
                            {
                                con.WriteToSocket(CompressStart);
                                con.StartCompression();
                            }
                            break;
                        case Telopt.Compress2:
                            if (con.Flags.Has(ConnectionFlags.MCCP))
                            {
                                con.WriteToSocket(new byte[] {Telopt.IAC, Telopt.Wont, Telopt.Compress2, 0});
                            }
                            else
                            {
                                con.WriteToSocket(Compress2Start);
                                con.StartCompression();
                            }
                            break;
                        default:
                            Log.Error("unknown telopt do {0}", bytes[index]);
                            break;
                    }
                    break;
                case Telopt.Dont:
                    switch (bytes[++index])
                    {
                        case Telopt.Echo:
                            break;
                        case Telopt.Compress:
                        case Telopt.Compress2:
                            break;
                        default:
                            Log.Error("unknown telopt don't {0}", bytes[index]);
                            break;
                    }
                    break;
                case Telopt.SE:
                    break;
                case Telopt.SB:
                    switch (bytes[++index])
                    {
                        case Telopt.NAWS:
                            con.ScreenWidth = Convert.ToInt16(bytes[++index])*255;
                            con.ScreenWidth += Convert.ToInt16(bytes[++index]);
                            con.ScreenHeight = Convert.ToInt16(bytes[++index])*255;
                            con.ScreenHeight += Convert.ToInt16(bytes[++index]);
                            con.Flags.Set(ConnectionFlags.NAWS);
                            break;
                        case Telopt.TTYPE:
                            StringBuilder buf = new StringBuilder();

                            index += 2;
                            while (bytes[index] != Telopt.IAC)
                            {
                                buf.Append(Convert.ToChar(bytes[index++]));
                            }
                            con.TerminalType = buf.ToString();
                            con.Flags.Set(ConnectionFlags.TType);
                            ++index;
                            break;
                        case Telopt.NE:
                            index += 2;
                            // This could be handy... I can set environment variables
                            // in my windows putty client and have them read here.
                            // Haven't tried linux yet.
                            //  - RJ Aug'07
                            while (bytes[index] != Telopt.IAC)
                            {
                                StringBuilder var = new StringBuilder();

                                ++index;

                                while (bytes[index] != Telopt.Value && bytes[index] != Telopt.IAC)
                                {
                                    var.Append(Convert.ToChar(bytes[index++]));
                                }

                                if (bytes[index] == Telopt.IAC)
                                    break;

                                ++index;

                                StringBuilder value = new StringBuilder();

                                while (bytes[index] != Telopt.Var && bytes[index] != Telopt.IAC)
                                {
                                    value.Append(Convert.ToChar(bytes[index++]));
                                }

                                if (var.ToString().ToLower() == "systemtype")
                                    con.SystemType = value.ToString();
                            }
                            con.Flags.Set(ConnectionFlags.NE);
                            ++index; // SE
                            break;
                        default:
                            Log.Bug("unknown telnet sub negotiation {0}.", bytes[index]);
                            break;
                    }
                    break;
                default:
                    Log.Bug("Unknown telnet option IAC {0} {1}", bytes[index++], bytes[index]);
                    break;
            }
        }

        #endregion
    }
}