// BinaryFormatter.cs
//
// Author:
//	Dick Porter (dick@ximian.com)
//  Lluis Sanchez Gual (lluis@ideary.com)
//
// (C) 2002 Ximian, Inc.  http://www.ximian.com
// Copyright (C) 2004-2005 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.Collections;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Messaging;
using System.Security.Permissions;

namespace System.Runtime.Serialization.Formatters.Binary {

#if NET_2_0
	[ComVisible (true)]
#endif
	public sealed class BinaryFormatter : IRemotingFormatter, IFormatter 
	{
#if NET_2_0
		private FormatterAssemblyStyle assembly_format = FormatterAssemblyStyle.Simple;
#else
		private FormatterAssemblyStyle assembly_format = FormatterAssemblyStyle.Full;
#endif
		private SerializationBinder binder;
		private StreamingContext context;
		private ISurrogateSelector surrogate_selector;
		private FormatterTypeStyle type_format = FormatterTypeStyle.TypesAlways;
		
#if NET_1_1
		private TypeFilterLevel filter_level = TypeFilterLevel.Full;
#endif
		
		public BinaryFormatter()
		{
			surrogate_selector=null;
			context=new StreamingContext(StreamingContextStates.All);
		}
		
		public BinaryFormatter(ISurrogateSelector selector, StreamingContext context)
		{
			surrogate_selector=selector;
			this.context=context;
		}

		public FormatterAssemblyStyle AssemblyFormat
		{
			get {
				return(assembly_format);
			}
			set {
				assembly_format=value;
			}
		}

		public SerializationBinder Binder
		{
			get {
				return(binder);
			}
			set {
				binder=value;
			}
		}

		public StreamingContext Context 
		{
			get {
				return(context);
			}
			set {
				context=value;
			}
		}
		
		public ISurrogateSelector SurrogateSelector 
		{
			get {
				return(surrogate_selector);
			}
			set {
				surrogate_selector=value;
			}
		}
		
		public FormatterTypeStyle TypeFormat 
		{
			get {
				return(type_format);
			}
			set {
				type_format=value;
			}
		}

#if NET_1_1
#if !NET_2_0
		[System.Runtime.InteropServices.ComVisible (false)]
#endif
		public TypeFilterLevel FilterLevel 
		{
			get { return filter_level; }
			set { filter_level = value; }
		}
#endif

		[SecurityPermission (SecurityAction.Demand, SerializationFormatter = true)]
		public object Deserialize (Stream serializationStream)
		{
			return NoCheckDeserialize (serializationStream, null);
		}

		[SecurityPermission (SecurityAction.Demand, SerializationFormatter = true)]
		public object Deserialize (Stream serializationStream, HeaderHandler handler) 
		{
			return NoCheckDeserialize (serializationStream, handler);
		}

		// shared by Deserialize and UnsafeDeserialize which both involve different security checks
		private object NoCheckDeserialize (Stream serializationStream, HeaderHandler handler)
		{
			if(serializationStream==null) 
			{
				throw new ArgumentNullException("serializationStream");
			}
			if(serializationStream.CanSeek &&
				serializationStream.Length==0) 
			{
				throw new SerializationException("serializationStream supports seeking, but its length is 0");
			}

			BinaryReader reader = new BinaryReader (serializationStream);

			bool hasHeader;
			ReadBinaryHeader (reader, out hasHeader);

			// Messages are read using a special static method, which does not use ObjectReader
			// if it is not needed. This saves time and memory.

			BinaryElement elem = (BinaryElement) reader.PeekChar();

			if (elem == BinaryElement.MethodCall) {
				return MessageFormatter.ReadMethodCall (reader, hasHeader, handler, this);
			}
			else if (elem == BinaryElement.MethodResponse) {
				return MessageFormatter.ReadMethodResponse (reader, hasHeader, handler, null, this);
			}
			else {
				ObjectReader serializer = new ObjectReader (this);

				object result;
				Header[] headers;
				serializer.ReadObjectGraph (reader, hasHeader, out result, out headers);
				if (handler != null) handler(headers);
				return result;
			}
		}
		
		[SecurityPermission (SecurityAction.Demand, SerializationFormatter = true)]
		public object DeserializeMethodResponse (Stream serializationStream, HeaderHandler handler, IMethodCallMessage methodCallMessage)
		{
			return NoCheckDeserializeMethodResponse (serializationStream, handler, methodCallMessage);
		}

		// shared by DeserializeMethodResponse and UnsafeDeserializeMethodResponse which both involve different security checks
		private object NoCheckDeserializeMethodResponse (Stream serializationStream, HeaderHandler handler, IMethodCallMessage methodCallMessage)
		{
			if(serializationStream==null) {
				throw new ArgumentNullException("serializationStream");
			}
			if(serializationStream.CanSeek &&
			   serializationStream.Length==0) {
				throw new SerializationException("serializationStream supports seeking, but its length is 0");
			}

			BinaryReader reader = new BinaryReader (serializationStream);

			bool hasHeader;
			ReadBinaryHeader (reader, out hasHeader);
			return MessageFormatter.ReadMethodResponse (reader, hasHeader, handler, methodCallMessage, this);
		}

		public void Serialize(Stream serializationStream, object graph)
		{
			Serialize (serializationStream, graph, null);
		}

		[SecurityPermission (SecurityAction.Demand, SerializationFormatter = true)]
		public void Serialize(Stream serializationStream, object graph, Header[] headers)
		{
			if(serializationStream==null) {
				throw new ArgumentNullException ("serializationStream");
			}

			BinaryWriter writer = new BinaryWriter (serializationStream);
			WriteBinaryHeader (writer, headers!=null);

			if (graph is IMethodCallMessage) {
				MessageFormatter.WriteMethodCall (writer, graph, headers, surrogate_selector, context, assembly_format, type_format);
			}
			else if (graph is IMethodReturnMessage)  {
				MessageFormatter.WriteMethodResponse (writer, graph, headers, surrogate_selector, context, assembly_format, type_format);
			}
			else {
				ObjectWriter serializer = new ObjectWriter (surrogate_selector, context, assembly_format, type_format);
				serializer.WriteObjectGraph (writer, graph, headers);
			}
			writer.Flush();
		}

		// faster version (under CAS) as this requires a LinkDemand versus full Demand (i.e. a stack-walk)
		// shouldn't be called unless the code is intended to be executed at full-trust
		[ComVisible (false)]
		[SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
		public object UnsafeDeserialize (Stream serializationStream, HeaderHandler handler) 
		{
			return NoCheckDeserialize (serializationStream, handler);
		}
		
		// faster version (under CAS) as this requires a LinkDemand versus full Demand (i.e. a stack-walk)
		// shouldn't be called unless the code is intended to be executed at full-trust
		[ComVisible (false)]
		[SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
		public object UnsafeDeserializeMethodResponse (Stream serializationStream, HeaderHandler handler, IMethodCallMessage methodCallMessage)
		{
			return NoCheckDeserializeMethodResponse (serializationStream, handler, methodCallMessage);
		}
		
		private void WriteBinaryHeader (BinaryWriter writer, bool hasHeaders)
		{
			writer.Write ((byte)BinaryElement.Header);
			writer.Write ((int)1);
			if (hasHeaders) writer.Write ((int)2);
			else writer.Write ((int)-1);
			writer.Write ((int)1);
			writer.Write ((int)0);
		}

		private void ReadBinaryHeader (BinaryReader reader, out bool hasHeaders)
		{
			reader.ReadByte();
			reader.ReadInt32();
			int val = reader.ReadInt32();
			hasHeaders = (val==2);
			reader.ReadInt32();
			reader.ReadInt32();
		}
	}
}
