using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Diagnostics;

namespace WinnyProtocolAnalyzer
{
    static class Network
    {
        /// <summary>
        /// ネットワークからデータを読み込みます。
        /// </summary>
        /// <param name="stream">接続</param>
        /// <param name="readRC4">読み込み用RC4</param>
        /// <returns>読み込んだデータ</returns>
        static public byte[] Read(NetworkStream stream, RC4 readRC4)
        {
            // Length
            byte[] aobReadBufferLength = new byte[4];
            int iReadLength = 0;
            while (iReadLength < aobReadBufferLength.Length)
            {
                int iRead = stream.Read(aobReadBufferLength, iReadLength, aobReadBufferLength.Length - iReadLength);
                if (iRead == 0)
                {
                    throw new Exception("転送接続からデータを読み取れません: 既存の接続はリモート ホストに強制的に切断されました。");
                }
                iReadLength += iRead;
            }
            readRC4.Cipher(aobReadBufferLength, aobReadBufferLength.Length);

            // Body
            int iReadBufferLength = BitConverter.ToInt32(aobReadBufferLength, 0);
            if (iReadBufferLength < 0 || iReadBufferLength > 1024 * 1024 * 10)
            {
                return null;
            }
            byte[] aobReadBuffer = new byte[iReadBufferLength];
            iReadLength = 0;
            while (iReadLength < iReadBufferLength)
            {
                int iRead = stream.Read(aobReadBuffer, iReadLength, iReadBufferLength - iReadLength);
                if (iRead == 0)
                {
                    throw new Exception("転送接続からデータを読み取れません: 既存の接続はリモート ホストに強制的に切断されました。");
                }
                iReadLength += iRead;
            }
            readRC4.Cipher(aobReadBuffer, aobReadBuffer.Length);

            return aobReadBuffer;
        }

        /// <summary>
        /// ネットワークにデータを書き込みます。
        /// </summary>
        /// <param name="aobWriteBuffer">書き込むデータ</param>
        /// <param name="stream">接続</param>
        /// <param name="writeRC4">書き込み用RC4</param>
        static public void Write(byte[] aobWriteBuffer, NetworkStream stream, RC4 writeRC4)
        {
            // Length
            byte[] aobWriteBufferLength = BitConverter.GetBytes(aobWriteBuffer.Length);
            writeRC4.Cipher(aobWriteBufferLength, aobWriteBufferLength.Length);
            stream.Write(aobWriteBufferLength, 0, aobWriteBufferLength.Length);

            // Body
            writeRC4.Cipher(aobWriteBuffer, aobWriteBuffer.Length);
            stream.Write(aobWriteBuffer, 0, aobWriteBuffer.Length);
        }

        /// <summary>
        /// 共通鍵を交換します。
        /// </summary>
        /// <param name="stream">ネットワーク接続</param>
        /// <param name="readRC4">読み込み用RC4</param>
        /// <param name="writeRC4">書き込み用RC4</param>
        static public void KeyExchange(NetworkStream stream, out RC4 readRC4, out RC4 writeRC4)
        {
            stream.ReadTimeout = Property.NetworkTimeout;
            stream.WriteTimeout = Property.NetworkTimeout;

            #region Write
            byte[] aobWriteKey = new byte[Property.KeyLength];
            new Random().NextBytes(aobWriteKey);
            writeRC4 = new RC4(aobWriteKey);

            byte[] aobWriteBuffer = new byte[Property.DummyLength + Property.KeyLength];
            new Random().NextBytes(aobWriteBuffer);
            Buffer.BlockCopy(aobWriteKey, 0, aobWriteBuffer, 2, aobWriteKey.Length);

            stream.Write(aobWriteBuffer, 0, aobWriteBuffer.Length);
            #endregion

            #region Read
            byte[] aobReadBuffer = new byte[Property.DummyLength + Property.KeyLength];
            int iReadLength = 0;
            while (iReadLength < aobReadBuffer.Length)
            {
                int iRead = stream.Read(aobReadBuffer, iReadLength, aobReadBuffer.Length - iReadLength);
                if (iRead == 0)
                {
                    throw new Exception("転送接続からデータを読み取れません: 既存の接続はリモート ホストに強制的に切断されました。");
                }
                iReadLength += iRead;
            }

            byte[] aobReadKey = new byte[Property.KeyLength];
            Buffer.BlockCopy(aobReadBuffer, 2, aobReadKey, 0, aobReadKey.Length);
            readRC4 = new RC4(aobReadKey);
            #endregion
        }
    }
}
