/*
This file is part of PacketDotNet

PacketDotNet is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

PacketDotNet is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with PacketDotNet.  If not, see <http://www.gnu.org/licenses/>.
*/
/*
 * Copyright 2009 Chris Morgan <chmorgan@gmail.com>
 */

using System;
using System.Collections.Generic;
using System.Text;
using MiscUtil.Conversion;
using PacketDotNet.Utils;

namespace PacketDotNet
{
    /// <summary>
    /// IPv4 packet
    /// See http://en.wikipedia.org/wiki/IPv4 for into
    /// </summary>
    public class IPv4Packet : IpPacket
    {
        // NOTE: No need to warn about lack of use, the compiler won't
        //       put any calls to 'log' here but we need 'log' to exist to compile
#pragma warning disable 0169, 0649
        private static readonly ILogInactive log;
#pragma warning restore 0169, 0649

        /// <value>
        /// Number of bytes in the smallest valid ipv4 packet
        /// </value>
        public const int HeaderMinimumLength = 20;

        /// <summary> Type of service code constants for IP. Type of service describes
        /// how a packet should be handled.
        /// <p>
        /// TOS is an 8-bit record in an IP header which contains a 3-bit
        /// precendence field, 4 TOS bit fields and a 0 bit.
        /// </p>
        /// <p>
        /// The following constants are bit masks which can be logically and'ed
        /// with the 8-bit IP TOS field to determine what type of service is set.
        /// </p>
        /// <p>
        /// Taken from TCP/IP Illustrated V1 by Richard Stevens, p34.
        /// </p>
        /// </summary>
        public struct TypesOfService_Fields
        {
#pragma warning disable 1591
            public readonly static int MINIMIZE_DELAY = 0x10;
            public readonly static int MAXIMIZE_THROUGHPUT = 0x08;
            public readonly static int MAXIMIZE_RELIABILITY = 0x04;
            public readonly static int MINIMIZE_MONETARY_COST = 0x02;
            public readonly static int UNUSED = 0x01;
#pragma warning restore 1591
        }

        /// <value>
        /// Version number of the IP protocol being used
        /// </value>
        public static IpVersion ipVersion = IpVersion.IPv4;

        /// <summary> Get the IP version code.</summary>
        public override IpVersion Version
        {
            get
            {
                return (IpVersion)((header.Bytes[header.Offset + IPv4Fields.VersionAndHeaderLengthPosition] >> 4) & 0x0F);
            }

            set
            {
                // read the original value
                var theByte = header.Bytes[header.Offset + IPv4Fields.VersionAndHeaderLengthPosition];

                // mask in the version bits
                theByte = (byte)((theByte & 0x0F) | (((byte)value << 4) & 0xF0));

                // write back the modified value
                header.Bytes[header.Offset + IPv4Fields.VersionAndHeaderLengthPosition] = theByte;
            }
        }

        /// <value>
        /// Forwards compatibility IPv6.PayloadLength property
        /// </value>
        public override ushort PayloadLength
        {
            get
            {
                return (ushort)(TotalLength - (HeaderLength * 4));
            }

            set
            {
                TotalLength = value + (HeaderLength * 4);
            }
        }

        /// <summary>
        /// The IP header length field.  At most, this can be a
        /// four-bit value.  The high order bits beyond the fourth bit
        /// will be ignored.
        /// </summary>
        /// <param name="length">The length of the IP header in 32-bit words.
        /// </param>
        public override int HeaderLength
        {
            get
            {
                return (header.Bytes[header.Offset + IPv4Fields.VersionAndHeaderLengthPosition]) & 0x0F;
            }

            set
            {
                // read the original value
                var theByte = header.Bytes[header.Offset + IPv4Fields.VersionAndHeaderLengthPosition];

                // mask in the header length bits
                theByte = (byte)((theByte & 0xF0) | (((byte)value) & 0x0F));

                // write back the modified value
                header.Bytes[header.Offset + IPv4Fields.VersionAndHeaderLengthPosition] = theByte;
            }
        }

        /// <summary>
        /// The unique ID of this IP datagram. The ID normally
        /// increments by one each time a datagram is sent by a host.
        /// A 16-bit unsigned integer.
        /// </summary>
        virtual public ushort Id
        {
            get
            {
                return EndianBitConverter.Big.ToUInt16(header.Bytes,
                                                       header.Offset + IPv4Fields.IdPosition);
            }

            set
            {
                EndianBitConverter.Big.CopyBytes(value,
                                                 header.Bytes,
                                                 header.Offset + IPv4Fields.IdPosition);
            }
        }

        /// <summary>
        /// Fragmentation offset
        /// The offset specifies a number of octets (i.e., bytes).
        /// A 13-bit unsigned integer.
        /// </summary>
        virtual public int FragmentOffset
        {
            get
            {
                var fragmentOffsetAndFlags = EndianBitConverter.Big.ToInt16(header.Bytes,
                                                                           header.Offset + IPv4Fields.FragmentOffsetAndFlagsPosition);

                // mask off the high flag bits
                return (fragmentOffsetAndFlags & 0x1FFF);
            }

            set
            {
                // retrieve the value
                var fragmentOffsetAndFlags = EndianBitConverter.Big.ToInt16(header.Bytes,
                                                                           header.Offset + IPv4Fields.FragmentOffsetAndFlagsPosition);

                // mask the fragementation offset in
                fragmentOffsetAndFlags = (short)((fragmentOffsetAndFlags & 0xE000) | (value & 0x1FFF));

                EndianBitConverter.Big.CopyBytes(fragmentOffsetAndFlags,
                                                 header.Bytes,
                                                 header.Offset + IPv4Fields.FragmentOffsetAndFlagsPosition);
            }
        }

        /// <summary> Fetch the IP address of the host where the packet originated from.</summary>
        public override System.Net.IPAddress SourceAddress
        {
            get
            {
                return IpPacket.GetIPAddress(System.Net.Sockets.AddressFamily.InterNetwork,
                                             header.Offset + IPv4Fields.SourcePosition, header.Bytes);
            }

            set
            {
                byte[] address = value.GetAddressBytes();
                Array.Copy(address, 0,
                           header.Bytes, header.Offset + IPv4Fields.SourcePosition,
                           address.Length);
            }
        }

        /// <summary> Fetch the IP address of the host where the packet is destined.</summary>
        public override System.Net.IPAddress DestinationAddress
        {
            get
            {
                return IpPacket.GetIPAddress(System.Net.Sockets.AddressFamily.InterNetwork,
                                             header.Offset + IPv4Fields.DestinationPosition, header.Bytes);
            }

            set
            {
                byte[] address = value.GetAddressBytes();
                Array.Copy(address, 0,
                           header.Bytes, header.Offset + IPv4Fields.DestinationPosition,
                           address.Length);
            }
        }

        /// <summary> Fetch the header checksum.</summary>
        virtual public ushort Checksum
        {
            get
            {
                return EndianBitConverter.Big.ToUInt16(header.Bytes,
                                                      header.Offset + IPv4Fields.ChecksumPosition);
            }

            set
            {
                var val = (UInt16)value;
                EndianBitConverter.Big.CopyBytes(val,
                                                 header.Bytes,
                                                 header.Offset + IPv4Fields.ChecksumPosition);
            }
        }

        /// <summary> Check if the IP packet is valid, checksum-wise.</summary>
        virtual public bool ValidChecksum
        {
            get
            {
                return ValidIPChecksum;
            }

        }

        /// <summary>
        /// Check if the IP packet header is valid, checksum-wise.
        /// </summary>
        public bool ValidIPChecksum
        {
            get
            {
                log.Debug("");

                // first validate other information about the packet. if this stuff
                // is not true, the packet (and therefore the checksum) is invalid
                // - ip_hl >= 5 (ip_hl is the length in 4-byte words)
                if (Header.Length < IPv4Fields.HeaderLength)
                {
                    log.DebugFormat("invalid length, returning false");
                    return false;
                }
                else
                {
                    var headerOnesSum = ChecksumUtils.OnesSum(Header);
                    log.DebugFormat(HexPrinter.GetString(Header, 0, Header.Length));
                    const int expectedHeaderOnesSum = 0xffff;
                    var retval = (headerOnesSum == expectedHeaderOnesSum);
                    log.DebugFormat("headerOnesSum: {0}, expectedHeaderOnesSum {1}, returning {2}",
                                    headerOnesSum,
                                    expectedHeaderOnesSum,
                                    retval);
                    log.DebugFormat("Header.Length {0}", Header.Length);
                    return retval;
                }
            }
        }

        /// <summary> Fetch ascii escape sequence of the color associated with this packet type.</summary>
        override public System.String Color
        {
            get
            {
                return AnsiEscapeSequences.White;
            }
        }

        /// <summary> Fetch the type of service. </summary>
        public int DifferentiatedServices
        {
            get
            {
                return header.Bytes[header.Offset + IPv4Fields.DifferentiatedServicesPosition];
            }

            set
            {
                header.Bytes[header.Offset + IPv4Fields.DifferentiatedServicesPosition] = (byte)value;
            }
        }

        /// <value>
        /// Renamed to DifferentiatedServices in IPv6 but present here
        /// for backwards compatibility
        /// </value>
        public int TypeOfService
        {
            get { return DifferentiatedServices; }
            set { DifferentiatedServices = value; }
        }

        /// <value>
        /// The entire datagram size including header and data
        /// </value>
        public override int TotalLength
        {
            get
            {
                return EndianBitConverter.Big.ToUInt16(header.Bytes,
                                                       header.Offset + IPv4Fields.TotalLengthPosition);
            }

            set
            {
                var theValue = (UInt16)value;
                EndianBitConverter.Big.CopyBytes(theValue,
                                                 header.Bytes,
                                                 header.Offset + IPv4Fields.TotalLengthPosition);
            }
        }

        /// <summary> Fetch fragment flags.</summary>
        /// <param name="flags">A 3-bit unsigned integer.</param>
        public virtual int FragmentFlags
        {
            get
            {
                var fragmentOffsetAndFlags = EndianBitConverter.Big.ToInt16(header.Bytes,
                                                                           header.Offset + IPv4Fields.FragmentOffsetAndFlagsPosition);

                // shift off the fragment offset bits
                return fragmentOffsetAndFlags >> (16 - 3);
            }

            set
            {
                // retrieve the value
                var fragmentOffsetAndFlags = EndianBitConverter.Big.ToInt16(header.Bytes,
                                                                           header.Offset + IPv4Fields.FragmentOffsetAndFlagsPosition);

                // mask the flags in
                fragmentOffsetAndFlags = (short)((fragmentOffsetAndFlags & 0x1FFF) | ((value & 0x07) << (16 - 3)));

                EndianBitConverter.Big.CopyBytes(fragmentOffsetAndFlags,
                                                 header.Bytes,
                                                 header.Offset + IPv4Fields.FragmentOffsetAndFlagsPosition);
            }
        }

        /// <summary> Fetch the time to live. TTL sets the upper limit on the number of
        /// routers through which this IP datagram is allowed to pass.
        /// Originally intended to be the number of seconds the packet lives it is now decremented
        /// by one each time a router passes the packet on
        ///
        /// 8-bit value
        /// </summary>
        public override int TimeToLive
        {
            get
            {
                return header.Bytes[header.Offset + IPv4Fields.TtlPosition];
            }

            set
            {
                header.Bytes[header.Offset + IPv4Fields.TtlPosition] = (byte)value;
            }
        }

        /// <summary> Fetch the code indicating the type of protocol embedded in the IP</summary>
        /// <seealso cref="IPProtocolType">
        /// </seealso>
        public override IPProtocolType Protocol
        {
            get
            {
                return (IPProtocolType)header.Bytes[header.Offset + IPv4Fields.ProtocolPosition];
            }

            set
            {
                header.Bytes[header.Offset + IPv4Fields.ProtocolPosition] = (byte)value;
            }
        }

        /// <summary>
        /// Calculates the IP checksum, optionally updating the IP checksum header.
        /// </summary>
        /// <returns> The calculated IP checksum.
        /// </returns>
        public ushort CalculateIPChecksum()
        {
            //copy the ip header
            var theHeader = Header;
            byte[] ip = new byte[theHeader.Length];
            Array.Copy(theHeader, ip, theHeader.Length);

            //reset the checksum field (checksum is calculated when this field is zeroed)
            var theValue = (UInt16)0;
            EndianBitConverter.Big.CopyBytes(theValue, ip, IPv4Fields.ChecksumPosition);

            //calculate the one's complement sum of the ip header
            int cs = ChecksumUtils.OnesComplementSum(ip, 0, ip.Length);

            return (ushort)cs;
        }

        /// <summary>
        /// Update the checksum value
        /// </summary>
        public void UpdateIPChecksum ()
        {
            this.Checksum = CalculateIPChecksum();
        }

        /// <summary>
        /// Prepend to the given byte[] origHeader the portion of the IPv6 header used for
        /// generating an tcp checksum
        ///
        /// http://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_checksum_using_IPv4
        /// http://tools.ietf.org/html/rfc793
        /// </summary>
        /// <param name="origHeader">
        /// A <see cref="System.Byte"/>
        /// </param>
        /// <returns>
        /// A <see cref="System.Byte"/>
        /// </returns>
        internal override byte[] AttachPseudoIPHeader(byte[] origHeader)
        {
            log.DebugFormat("origHeader.Length {0}",
                            origHeader.Length);

            bool odd = origHeader.Length % 2 != 0;
            int numberOfBytesFromIPHeaderUsedToGenerateChecksum = 12;
            int headerSize = numberOfBytesFromIPHeaderUsedToGenerateChecksum + origHeader.Length;
            if (odd)
                headerSize++;

            byte[] headerForChecksum = new byte[headerSize];
            // 0-7: ip src+dest addr
            Array.Copy(header.Bytes,
                       header.Offset + IPv4Fields.SourcePosition,
                       headerForChecksum,
                       0,
                       IPv4Fields.AddressLength * 2);
            // 8: always zero
            headerForChecksum[8] = 0;
            // 9: ip protocol
            headerForChecksum[9] = (byte)Protocol;
            // 10-11: header+data length
            var length = (Int16)origHeader.Length;
            EndianBitConverter.Big.CopyBytes(length, headerForChecksum,
                                             10);

            // prefix the pseudoHeader to the header+data
            Array.Copy(origHeader, 0,
                       headerForChecksum, numberOfBytesFromIPHeaderUsedToGenerateChecksum,
                       origHeader.Length);

            //if not even length, pad with a zero
            if (odd)
                headerForChecksum[headerForChecksum.Length - 1] = 0;

            return headerForChecksum;
        }

        /// <summary>
        /// Construct an instance by values
        /// </summary>
        public IPv4Packet(System.Net.IPAddress SourceAddress,
                          System.Net.IPAddress DestinationAddress)
        {
            // allocate memory for this packet
            int offset = 0;
            int length = IPv4Fields.HeaderLength;
            var headerBytes = new byte[length];
            header = new ByteArraySegment(headerBytes, offset, length);

            // set some default values to make this packet valid
            PayloadLength = 0;
            HeaderLength = (HeaderMinimumLength / 4); // NOTE: HeaderLength is the number of 32bit words in the header
            TimeToLive = DefaultTimeToLive;

            // set instance values
            this.SourceAddress = SourceAddress;
            this.DestinationAddress = DestinationAddress;
            this.Version = ipVersion;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="bas">
        /// A <see cref="ByteArraySegment"/>
        /// </param>
        public IPv4Packet(ByteArraySegment bas)
        {
            log.Debug("");

            header = new ByteArraySegment(bas);
            RandomUtils.EnsurePacketLength(this, IPv4Fields.HeaderLength, header.Length);

            // TOS? See http://en.wikipedia.org/wiki/TCP_offload_engine
            if (TotalLength == 0)
            {
                TotalLength = header.Length;
            }

            // update the header length with the correct value
            // NOTE: we take care to convert from 32bit words into bytes
            // NOTE: we do this *after* setting header because we need header to be valid
            //       before we can retrieve the HeaderLength property

            RandomUtils.EnsurePacketLength(this, HeaderLength * 4, header.Length);
            header.Length = HeaderLength * 4;

            log.DebugFormat("IPv4Packet HeaderLength {0}", HeaderLength);
            log.DebugFormat("header {0}", header);

            // parse the payload
            var payload = header.EncapsulatedBytes(PayloadLength);
            payloadPacketOrData = IpPacket.ParseEncapsulatedBytes(payload,
                                                                  NextHeader,
                                                                  this);
        }

        /// <summary cref="Packet.ToString(StringOutputType)" />
        public override string ToString(StringOutputType outputFormat)
        {
            var buffer = new StringBuilder();
            string color = "";
            string colorEscape = "";

            if(outputFormat == StringOutputType.Colored || outputFormat == StringOutputType.VerboseColored)
            {
                color = Color;
                colorEscape = AnsiEscapeSequences.Reset;
            }

            if(outputFormat == StringOutputType.Normal || outputFormat == StringOutputType.Colored)
            {
                // build the output string
                buffer.AppendFormat("{0}[IPv4Packet: SourceAddress={2}, DestinationAddress={3}, HeaderLength={4}, Protocol={5}, TimeToLive={6}]{1}",
                    color,
                    colorEscape,
                    SourceAddress,
                    DestinationAddress,
                    HeaderLength,
                    Protocol,
                    TimeToLive);
            }

            if(outputFormat == StringOutputType.Verbose || outputFormat == StringOutputType.VerboseColored)
            {
                // collect the properties and their value
                Dictionary<string,string> properties = new Dictionary<string,string>();
                properties.Add("version", Version.ToString());
                // FIXME: Header length output is incorrect
                properties.Add("header length", HeaderLength + " bytes");
                string diffServices =  Convert.ToString(DifferentiatedServices, 2).PadLeft(8, '0').Insert(4, " ");
                properties.Add("differentiated services", "0x" + DifferentiatedServices.ToString("x").PadLeft(2, '0'));
                properties.Add("", diffServices.Substring(0, 7) + ".. = [" + (DifferentiatedServices >> 2) + "] code point");
                properties.Add(" ",".... .." + diffServices[6] + ". = [" + diffServices[6] + "] ECN");
                properties.Add("  ",".... ..." + diffServices[7] + " = [" + diffServices[7] + "] ECE");
                properties.Add("total length", TotalLength.ToString());
                properties.Add("identification", "0x" + Id.ToString("x") + " (" + Id + ")");
                string flags = Convert.ToString(FragmentFlags, 2).PadLeft(8, '0').Substring(5, 3);
                properties.Add("flags", "0x" + FragmentFlags.ToString("x").PadLeft(2, '0'));
                properties.Add("   ", flags[0] + ".. = [" +  flags[0] + "] reserved");
                properties.Add("    ", "." + flags[1] + ". = [" + flags[1] + "] don't fragment");
                properties.Add("     ", ".." + flags[2] + " = [" + flags[2] + "] more fragments");
                properties.Add("fragment offset", FragmentOffset.ToString());
                properties.Add("time to live", TimeToLive.ToString());
                properties.Add("protocol", Protocol.ToString() + " (0x" + Protocol.ToString("x") + ")");
                properties.Add("header checksum", "0x" + Checksum.ToString("x") + " [" + (ValidChecksum ? "valid" : "invalid") + "]");
                properties.Add("source", SourceAddress.ToString());
                properties.Add("destination", DestinationAddress.ToString());

                // calculate the padding needed to right-justify the property names
                int padLength = Utils.RandomUtils.LongestStringLength(new List<string>(properties.Keys));

                // build the output string
                buffer.AppendLine("IP:  ******* IPv4 - \"Internet Protocol (Version 4)\" - offset=? length=" + TotalPacketLength);
                buffer.AppendLine("IP:");
                foreach(var property in properties)
                {
                    if(property.Key.Trim() != "")
                    {
                        buffer.AppendLine("IP: " + property.Key.PadLeft(padLength) + " = " + property.Value);
                    }
                    else
                    {
                        buffer.AppendLine("IP: " + property.Key.PadLeft(padLength) + "   " + property.Value);
                    }
                }
                buffer.AppendLine("IP:");
            }

            // append the base class output
            buffer.Append(base.ToString(outputFormat));

            return buffer.ToString();
        }

        /// <summary>
        /// Generate a random packet
        /// </summary>
        /// <returns>
        /// A <see cref="Packet"/>
        /// </returns>
        public static IPv4Packet RandomPacket()
        {
            var srcAddress = RandomUtils.GetIPAddress(ipVersion);
            var dstAddress = RandomUtils.GetIPAddress(ipVersion);
            return new IPv4Packet(srcAddress, dstAddress);
        }

        /// <summary>
        /// Update the length and checksum fields
        /// </summary>
        public override void UpdateCalculatedValues ()
        {

            // update the length field based on the length of this packet header
            // plus the length of all of the packets it contains
            TotalLength = TotalPacketLength;

            UpdateIPChecksum();
        }
    }
}
