// Transport Security Layer (TLS)
// Copyright (c) 2003-2004 Carlos Guzman Alvarez
// Copyright (C) 2006-2007 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;
using System.Collections;
using System.IO;
using System.Threading;

using Mono.Security.Protocol.Tls.Handshake;

namespace Mono.Security.Protocol.Tls
{
	internal abstract class RecordProtocol
	{
		#region Fields

		private static ManualResetEvent record_processing = new ManualResetEvent (true);

		protected Stream	innerStream;
		protected Context	context;

		#endregion

		#region Properties

		public Context Context
		{
			get { return this.context; }
			set { this.context = value; }
		}

		#endregion

		#region Constructors

		public RecordProtocol(Stream innerStream, Context context)
		{
			this.innerStream			= innerStream;
			this.context				= context;
			this.context.RecordProtocol = this;
		}

		#endregion

		#region Abstract Methods

		public virtual void SendRecord(HandshakeType type)
		{

			IAsyncResult ar = this.BeginSendRecord(type, null, null);

			this.EndSendRecord(ar);

		}

		protected abstract void ProcessHandshakeMessage(TlsStream handMsg);

		protected virtual void ProcessChangeCipherSpec ()
		{
			Context ctx = this.Context;

			// Reset sequence numbers
			ctx.ReadSequenceNumber = 0;

			if (ctx is ClientContext) {
				ctx.EndSwitchingSecurityParameters (true);
			} else {
				ctx.StartSwitchingSecurityParameters (false);
			}
		}

		public virtual HandshakeMessage GetMessage(HandshakeType type)
		{
			throw new NotSupportedException();
		}

		#endregion

		#region Receive Record Async Result
		private class ReceiveRecordAsyncResult : IAsyncResult
		{
			private object locker = new object ();
			private AsyncCallback _userCallback;
			private object _userState;
			private Exception _asyncException;
			private ManualResetEvent handle;
			private byte[] _resultingBuffer;
			private Stream _record;
			private bool completed;

			private byte[] _initialBuffer;

			public ReceiveRecordAsyncResult(AsyncCallback userCallback, object userState, byte[] initialBuffer, Stream record)
			{
				_userCallback = userCallback;
				_userState = userState;
				_initialBuffer = initialBuffer;
				_record = record;
			}

			public Stream Record
			{
				get { return _record; }
			}

			public byte[] ResultingBuffer
			{
				get { return _resultingBuffer; }
			}

			public byte[] InitialBuffer
			{
				get { return _initialBuffer; }
			}

			public object AsyncState
			{
				get { return _userState; }
			}

			public Exception AsyncException
			{
				get { return _asyncException; }
			}

			public bool CompletedWithError
			{
				get {
					if (!IsCompleted)
						return false; // Perhaps throw InvalidOperationExcetion?

					return null != _asyncException;
				}
			}

			public WaitHandle AsyncWaitHandle
			{
				get {
					lock (locker) {
						if (handle == null)
							handle = new ManualResetEvent (completed);
					}
					return handle;
				}
				
			}

			public bool CompletedSynchronously
			{
				get { return false; }
			}

			public bool IsCompleted
			{
				get {
					lock (locker) {
						return completed;
					}
				}
			}

			private void SetComplete(Exception ex, byte[] resultingBuffer)
			{
				lock (locker) {
					if (completed)
						return;

					completed = true;
					_asyncException = ex;
					_resultingBuffer = resultingBuffer;
					if (handle != null)
						handle.Set ();

					if (_userCallback != null)
						_userCallback.BeginInvoke (this, null, null);
				}
			}

			public void SetComplete(Exception ex)
			{
				SetComplete(ex, null);
			}

			public void SetComplete(byte[] resultingBuffer)
			{
				SetComplete(null, resultingBuffer);
			}

			public void SetComplete()
			{
				SetComplete(null, null);
			}
		}
		#endregion

		#region Receive Record Async Result
		private class SendRecordAsyncResult : IAsyncResult
		{
			private object locker = new object ();
			private AsyncCallback _userCallback;
			private object _userState;
			private Exception _asyncException;
			private ManualResetEvent handle;
			private HandshakeMessage _message;
			private bool completed;

			public SendRecordAsyncResult(AsyncCallback userCallback, object userState, HandshakeMessage message)
			{
				_userCallback = userCallback;
				_userState = userState;
				_message = message;
			}

			public HandshakeMessage Message
			{
				get { return _message; }
			}

			public object AsyncState
			{
				get { return _userState; }
			}

			public Exception AsyncException
			{
				get { return _asyncException; }
			}

			public bool CompletedWithError
			{
				get {
					if (!IsCompleted)
						return false; // Perhaps throw InvalidOperationExcetion?

					return null != _asyncException;
				}
			}

			public WaitHandle AsyncWaitHandle
			{
				get {
					lock (locker) {
						if (handle == null)
							handle = new ManualResetEvent (completed);
					}
					return handle;
				}
				
			}

			public bool CompletedSynchronously
			{
				get { return false; }
			}

			public bool IsCompleted
			{
				get {
					lock (locker) {
						return completed;
					}
				}
			}

			public void SetComplete(Exception ex)
			{
				lock (locker) {
					if (completed)
						return;

					completed = true;
					if (handle != null)
						handle.Set ();

					if (_userCallback != null)
						_userCallback.BeginInvoke (this, null, null);

					_asyncException = ex;
				}
			}

			public void SetComplete()
			{
				SetComplete(null);
			}
		}
		#endregion

		#region Reveive Record Methods

		public IAsyncResult BeginReceiveRecord(Stream record, AsyncCallback callback, object state)
		{
			if (this.context.ReceivedConnectionEnd)
			{
				throw new TlsException(
					AlertDescription.InternalError,
					"The session is finished and it's no longer valid.");
			}

			record_processing.Reset ();
			byte[] recordTypeBuffer = new byte[1];

			ReceiveRecordAsyncResult internalResult = new ReceiveRecordAsyncResult(callback, state, recordTypeBuffer, record);

			record.BeginRead(internalResult.InitialBuffer, 0, internalResult.InitialBuffer.Length, new AsyncCallback(InternalReceiveRecordCallback), internalResult);

			return internalResult;
		}

		private void InternalReceiveRecordCallback(IAsyncResult asyncResult)
		{
			ReceiveRecordAsyncResult internalResult = asyncResult.AsyncState as ReceiveRecordAsyncResult;
			Stream record = internalResult.Record;

			try
			{
				
				int bytesRead = internalResult.Record.EndRead(asyncResult);

				//We're at the end of the stream. Time to bail.
				if (bytesRead == 0)
				{
					internalResult.SetComplete((byte[])null);
					return;
				}

				// Try to read the Record Content Type
				int type = internalResult.InitialBuffer[0];

				// Set last handshake message received to None
				this.context.LastHandshakeMsg = HandshakeType.ClientHello;

				ContentType	contentType	= (ContentType)type;
				byte[] buffer = this.ReadRecordBuffer(type, record);
				if (buffer == null)
				{
					// record incomplete (at the moment)
					internalResult.SetComplete((byte[])null);
					return;
				}

				// Decrypt message contents if needed
				if (contentType == ContentType.Alert && buffer.Length == 2)
				{
				}
				else if ((this.Context.Read != null) && (this.Context.Read.Cipher != null))
				{
					buffer = this.decryptRecordFragment (contentType, buffer);
					DebugHelper.WriteLine ("Decrypted record data", buffer);
				}

				// Process record
				switch (contentType)
				{
					case ContentType.Alert:
						this.ProcessAlert((AlertLevel)buffer [0], (AlertDescription)buffer [1]);
						if (record.CanSeek) 
						{
							// don't reprocess that memory block
							record.SetLength (0); 
						}
						buffer = null;
						break;

					case ContentType.ChangeCipherSpec:
						this.ProcessChangeCipherSpec();
						break;

					case ContentType.ApplicationData:
						break;

					case ContentType.Handshake:
						TlsStream message = new TlsStream (buffer);
						while (!message.EOF)
						{
							this.ProcessHandshakeMessage(message);
						}
						break;

					case (ContentType)0x80:
						this.context.HandshakeMessages.Write (buffer);
						break;

					default:
						throw new TlsException(
							AlertDescription.UnexpectedMessage,
							"Unknown record received from server.");
				}

				internalResult.SetComplete(buffer);
			}
			catch (Exception ex)
			{
				internalResult.SetComplete(ex);
			}

		}

		public byte[] EndReceiveRecord(IAsyncResult asyncResult)
		{
			ReceiveRecordAsyncResult internalResult = asyncResult as ReceiveRecordAsyncResult;

			if (null == internalResult)
				throw new ArgumentException("Either the provided async result is null or was not created by this RecordProtocol.");

			if (!internalResult.IsCompleted)
				internalResult.AsyncWaitHandle.WaitOne();

			if (internalResult.CompletedWithError)
				throw internalResult.AsyncException;

			byte[] result = internalResult.ResultingBuffer;
			record_processing.Set ();
			return result;
		}

		public byte[] ReceiveRecord(Stream record)
		{

			IAsyncResult ar = this.BeginReceiveRecord(record, null, null);
			return this.EndReceiveRecord(ar);

		}

		private byte[] ReadRecordBuffer (int contentType, Stream record)
		{
			switch (contentType)
			{
				case 0x80:
					return this.ReadClientHelloV2(record);

				default:
					if (!Enum.IsDefined(typeof(ContentType), (ContentType)contentType))
					{
						throw new TlsException(AlertDescription.DecodeError);
					}
					return this.ReadStandardRecordBuffer(record);
			}
		}

		private byte[] ReadClientHelloV2 (Stream record)
		{
			int msgLength = record.ReadByte ();
			// process further only if the whole record is available
			if (record.CanSeek && (msgLength + 1 > record.Length)) 
			{
				return null;
			}

			byte[] message = new byte[msgLength];
			record.Read (message, 0, msgLength);

			int msgType		= message [0];
			if (msgType != 1)
			{
				throw new TlsException(AlertDescription.DecodeError);
			}
			int protocol = (message [1] << 8 | message [2]);
			int cipherSpecLength = (message [3] << 8 | message [4]);
			int sessionIdLength = (message [5] << 8 | message [6]);
			int challengeLength = (message [7] << 8 | message [8]);
			int length = (challengeLength > 32) ? 32 : challengeLength;

			// Read CipherSpecs
			byte[] cipherSpecV2 = new byte[cipherSpecLength];
			Buffer.BlockCopy (message, 9, cipherSpecV2, 0, cipherSpecLength);

			// Read session ID
			byte[] sessionId = new byte[sessionIdLength];
			Buffer.BlockCopy (message, 9 + cipherSpecLength, sessionId, 0, sessionIdLength);

			// Read challenge ID
			byte[] challenge = new byte[challengeLength];
			Buffer.BlockCopy (message, 9 + cipherSpecLength + sessionIdLength, challenge, 0, challengeLength);
		
			if (challengeLength < 16 || cipherSpecLength == 0 || (cipherSpecLength % 3) != 0)
			{
				throw new TlsException(AlertDescription.DecodeError);
			}

			// Updated the Session ID
			if (sessionId.Length > 0)
			{
				this.context.SessionId = sessionId;
			}

			// Update the protocol version
			this.Context.ChangeProtocol((short)protocol);

			// Select the Cipher suite
			this.ProcessCipherSpecV2Buffer(this.Context.SecurityProtocol, cipherSpecV2);

			// Updated the Client Random
			this.context.ClientRandom = new byte [32]; // Always 32
			// 1. if challenge is bigger than 32 bytes only use the last 32 bytes
			// 2. right justify (0) challenge in ClientRandom if less than 32
			Buffer.BlockCopy (challenge, challenge.Length - length, this.context.ClientRandom, 32 - length, length);

			// Set 
			this.context.LastHandshakeMsg = HandshakeType.ClientHello;
			this.context.ProtocolNegotiated = true;

			return message;
		}

		private byte[] ReadStandardRecordBuffer (Stream record)
		{
			byte[] header = new byte[4];
			if (record.Read (header, 0, 4) != 4)
				throw new TlsException ("buffer underrun");
			
			short protocol = (short)((header [0] << 8) | header [1]);
			short length = (short)((header [2] << 8) | header [3]);

			// process further only if the whole record is available
			// note: the first 5 bytes aren't part of the length
			if (record.CanSeek && (length + 5 > record.Length)) 
			{
				return null;
			}
			
			// Read Record data
			int	totalReceived = 0;
			byte[]	buffer		= new byte[length];
			while (totalReceived != length)
			{
				int justReceived = record.Read(buffer, totalReceived, buffer.Length - totalReceived);

				//Make sure we get some data so we don't end up in an infinite loop here before shutdown.
				if (0 == justReceived)
				{
					throw new TlsException(AlertDescription.CloseNotify, "Received 0 bytes from stream. It must be closed.");
				}

				totalReceived += justReceived;
			}

			// Check that the message has a valid protocol version
			if (protocol != this.context.Protocol && this.context.ProtocolNegotiated)
			{
				throw new TlsException(
					AlertDescription.ProtocolVersion, "Invalid protocol version on message received");
			}

			DebugHelper.WriteLine("Record data", buffer);

			return buffer;
		}

		private void ProcessAlert(AlertLevel alertLevel, AlertDescription alertDesc)
		{
			switch (alertLevel)
			{
				case AlertLevel.Fatal:
					throw new TlsException(alertLevel, alertDesc);

				case AlertLevel.Warning:
				default:
				switch (alertDesc)
				{
					case AlertDescription.CloseNotify:
						this.context.ReceivedConnectionEnd = true;
						break;
				}
				break;
			}
		}

		#endregion

		#region Send Alert Methods

		public void SendAlert(AlertDescription description)
		{
			this.SendAlert(new Alert(description));
		}

		public void SendAlert(
			AlertLevel			level, 
			AlertDescription	description)
		{
			this.SendAlert(new Alert(level, description));
		}

		public void SendAlert(Alert alert)
		{
			AlertLevel level;
			AlertDescription description;
			bool close;

			if (alert == null) {
				DebugHelper.WriteLine(">>>> Write Alert NULL");
				level = AlertLevel.Fatal;
				description = AlertDescription.InternalError;
				close = true;
			} else {
				DebugHelper.WriteLine(">>>> Write Alert ({0}|{1})", alert.Description, alert.Message);
				level = alert.Level;
				description = alert.Description;
				close = alert.IsCloseNotify;
			}

			// Write record
			this.SendRecord (ContentType.Alert, new byte[2] { (byte) level, (byte) description });

			if (close) {
				this.context.SentConnectionEnd = true;
			}
		}

		#endregion

		#region Send Record Methods

		public void SendChangeCipherSpec()
		{
			DebugHelper.WriteLine(">>>> Write Change Cipher Spec");

			// Send Change Cipher Spec message with the current cipher
			// or as plain text if this is the initial negotiation
			this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});

			Context ctx = this.context;

			// Reset sequence numbers
			ctx.WriteSequenceNumber = 0;

			// all further data sent will be encrypted with the negotiated
			// security parameters (now the current parameters)
			if (ctx is ClientContext) {
				ctx.StartSwitchingSecurityParameters (true);
			} else {
				ctx.EndSwitchingSecurityParameters (false);
			}
		}

		public IAsyncResult BeginSendRecord(HandshakeType handshakeType, AsyncCallback callback, object state)
		{
			HandshakeMessage msg = this.GetMessage(handshakeType);

			msg.Process();

			DebugHelper.WriteLine(">>>> Write handshake record ({0}|{1})", context.Protocol, msg.ContentType);

			SendRecordAsyncResult internalResult = new SendRecordAsyncResult(callback, state, msg);

			this.BeginSendRecord(msg.ContentType, msg.EncodeMessage(), new AsyncCallback(InternalSendRecordCallback), internalResult);

			return internalResult;
		}

		private void InternalSendRecordCallback(IAsyncResult ar)
		{
			SendRecordAsyncResult internalResult = ar.AsyncState as SendRecordAsyncResult;
			
			try
			{
				this.EndSendRecord(ar);

				// Update session
				internalResult.Message.Update();

				// Reset message contents
				internalResult.Message.Reset();

				internalResult.SetComplete();
			}
			catch (Exception ex)
			{
				internalResult.SetComplete(ex);
			}
		}

		public IAsyncResult BeginSendRecord(ContentType contentType, byte[] recordData, AsyncCallback callback, object state)
		{
			if (this.context.SentConnectionEnd)
			{
				throw new TlsException(
					AlertDescription.InternalError,
					"The session is finished and it's no longer valid.");
			}

			byte[] record = this.EncodeRecord(contentType, recordData);

			return this.innerStream.BeginWrite(record, 0, record.Length, callback, state);
		}

		public void EndSendRecord(IAsyncResult asyncResult)
		{
			if (asyncResult is SendRecordAsyncResult)
			{
				SendRecordAsyncResult internalResult = asyncResult as SendRecordAsyncResult;
				if (!internalResult.IsCompleted)
					internalResult.AsyncWaitHandle.WaitOne();
				if (internalResult.CompletedWithError)
					throw internalResult.AsyncException;
			}
			else
			{
				this.innerStream.EndWrite(asyncResult);
			}
		}

		public void SendRecord(ContentType contentType, byte[] recordData)
		{
			IAsyncResult ar = this.BeginSendRecord(contentType, recordData, null, null);

			this.EndSendRecord(ar);
		}

		public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
		{
			return this.EncodeRecord(
				contentType,
				recordData,
				0,
				recordData.Length);
		}

		public byte[] EncodeRecord(
			ContentType	contentType, 
			byte[]		recordData,
			int			offset,
			int			count)
		{
			if (this.context.SentConnectionEnd)
			{
				throw new TlsException(
					AlertDescription.InternalError,
					"The session is finished and it's no longer valid.");
			}

			TlsStream record = new TlsStream();

			int	position = offset;

			while (position < ( offset + count ))
			{
				short	fragmentLength = 0;
				byte[]	fragment;

				if ((count + offset - position) > Context.MAX_FRAGMENT_SIZE)
				{
					fragmentLength = Context.MAX_FRAGMENT_SIZE;
				}
				else
				{
					fragmentLength = (short)(count + offset - position);
				}

				// Fill the fragment data
				fragment = new byte[fragmentLength];
				Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);

				if ((this.Context.Write != null) && (this.Context.Write.Cipher != null))
				{
					// Encrypt fragment
					fragment = this.encryptRecordFragment (contentType, fragment);
				}

				// Write tls message
				record.Write((byte)contentType);
				record.Write(this.context.Protocol);
				record.Write((short)fragment.Length);
				record.Write(fragment);

				DebugHelper.WriteLine("Record data", fragment);

				// Update buffer position
				position += fragmentLength;
			}

			return record.ToArray();
		}
		
		#endregion

		#region Cryptography Methods

		private byte[] encryptRecordFragment(
			ContentType	contentType, 
			byte[]		fragment)
		{
			byte[] mac	= null;

			// Calculate message MAC
			if (this.Context is ClientContext)
			{
				mac = this.context.Write.Cipher.ComputeClientRecordMAC(contentType, fragment);
			}	
			else
			{
				mac = this.context.Write.Cipher.ComputeServerRecordMAC (contentType, fragment);
			}

			DebugHelper.WriteLine(">>>> Record MAC", mac);

			// Encrypt the message
			byte[] ecr = this.context.Write.Cipher.EncryptRecord (fragment, mac);

			// Update sequence number
			this.context.WriteSequenceNumber++;

			return ecr;
		}

		private byte[] decryptRecordFragment(
			ContentType	contentType, 
			byte[]		fragment)
		{
			byte[]	dcrFragment		= null;
			byte[]	dcrMAC			= null;

			try
			{
				this.context.Read.Cipher.DecryptRecord (fragment, out dcrFragment, out dcrMAC);
			}
			catch
			{
#if !MOONLIGHT
				if (this.context is ServerContext)
				{
					this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
				}
#endif
				throw;
			}
			
			// Generate record MAC
			byte[] mac = null;

			if (this.Context is ClientContext)
			{
				mac = this.context.Read.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
			}
			else
			{
				mac = this.context.Read.Cipher.ComputeClientRecordMAC (contentType, dcrFragment);
			}

			DebugHelper.WriteLine(">>>> Record MAC", mac);

			// Check record MAC
			if (!Compare (mac, dcrMAC))
			{
				throw new TlsException(AlertDescription.BadRecordMAC, "Bad record MAC");
			}

			// Update sequence number
			this.context.ReadSequenceNumber++;

			return dcrFragment;
		}

		private bool Compare (byte[] array1, byte[] array2)
		{
			if (array1 == null)
				return (array2 == null);
			if (array2 == null)
				return false;
			if (array1.Length != array2.Length)
				return false;
			for (int i = 0; i < array1.Length; i++) {
				if (array1[i] != array2[i])
					return false;
			}
			return true;
		}

		#endregion

		#region CipherSpecV2 processing

		private void ProcessCipherSpecV2Buffer (SecurityProtocolType protocol, byte[] buffer)
		{
			TlsStream codes = new TlsStream(buffer);

			string prefix = (protocol == SecurityProtocolType.Ssl3) ? "SSL_" : "TLS_";

			while (codes.Position < codes.Length)
			{
				byte check = codes.ReadByte();

				if (check == 0)
				{
					// SSL/TLS cipher spec
					short code = codes.ReadInt16();	
					int index = this.Context.SupportedCiphers.IndexOf(code);
					if (index != -1)
					{
						this.Context.Negotiating.Cipher = this.Context.SupportedCiphers[index];
						break;
					}
				}
				else
				{
					byte[] tmp = new byte[2];
					codes.Read(tmp, 0, tmp.Length);

					int tmpCode = ((check & 0xff) << 16) | ((tmp[0] & 0xff) << 8) | (tmp[1] & 0xff);
					CipherSuite cipher = this.MapV2CipherCode(prefix, tmpCode);

					if (cipher != null)
					{
						this.Context.Negotiating.Cipher = cipher;
						break;
					}
				}
			}

			if (this.Context.Negotiating == null)
			{
				throw new TlsException(AlertDescription.InsuficientSecurity, "Insuficient Security");
			}
		}

		private CipherSuite MapV2CipherCode(string prefix, int code)
		{
			try
			{
				switch (code)
				{
					case 65664:
						// TLS_RC4_128_WITH_MD5
						return this.Context.SupportedCiphers[prefix + "RSA_WITH_RC4_128_MD5"];
					
					case 131200:
						// TLS_RC4_128_EXPORT40_WITH_MD5
						return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC4_40_MD5"];
					
					case 196736:
						// TLS_RC2_CBC_128_CBC_WITH_MD5
						return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
					
					case 262272:
						// TLS_RC2_CBC_128_CBC_EXPORT40_WITH_MD5
						return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
					
					case 327808:
						// TLS_IDEA_128_CBC_WITH_MD5
						return null;
					
					case 393280:
						// TLS_DES_64_CBC_WITH_MD5
						return null;

					case 458944:
						// TLS_DES_192_EDE3_CBC_WITH_MD5
						return null;

					default:
						return null;
				}
			}
			catch
			{
				return null;
			}
		}

		#endregion
	}
}
