#!/usr/bin/env python
#############################################################################
# Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1997, 1998, 1999
# All Rights Reserved.
#
# The software contained on this media is the property of the DSTC Pty
# Ltd.  Use of this software is strictly in accordance with the
# license agreement in the accompanying LICENSE.HTML file.  If your
# distribution of this software does not contain a LICENSE.HTML file
# then you have no rights to use this software in any manner and
# should contact DSTC at the address below to determine an appropriate
# licensing arrangement.
# 
#      DSTC Pty Ltd
#      Level 7, GP South
#      Staff House Road
#      University of Queensland
#      St Lucia, 4072
#      Australia
#      Tel: +61 7 3365 4310
#      Fax: +61 7 3365 4311
#      Email: enquiries@dstc.edu.au
# 
# This software is being provided "AS IS" without warranty of any
# kind.  In no event shall DSTC Pty Ltd be liable for damage of any
# kind arising out of or in connection with the use or performance of
# this software.
#
# Project:      Fnorb
# File:         $Source: /projects/fnorb/Fnorb-1.1/orb/RCS/OctetStream.py,v $
# Version:      @(#)$RCSfile: OctetStream.py,v $ $Revision: 1.16 $
#
#############################################################################
""" Classes for CORBA octet streams, encapsulations and GIOP messages. """


# Standard/built-in modules.
import new

# Fnorb modules.
import CORBA, GIOP, Util

# Fnorb extension modules.
import cdr

# Fnorb parser modules.
from Fnorb.parser import Stack


class OctetStream:
    """ CORBA octet streams. """

    # The initial size of a new stream.
    INITIAL_SIZE = 512

    def __init__(self, data=None):
	""" Constructor.

	'data' is the contents of the octet stream.

	"""
	# If no data is specified then we initially allocate a fixed size
	# string and then grow it (ie. get a bigger, better, brighter, wider,
	# faster string) as required.
	if data is None:
	    self._data = '\0' * OctetStream.INITIAL_SIZE
	    self._len = 0

	# If data *is* specified then we use that!
	else:
	    self._data = data
	    self._len = len(data)

	return

    def __del__(self):
	
	del self._data
	return

    def __len__(self):
	""" Return the number of octets in the stream.

	This is the number of octets actually marshalled in the stream - not
	the internal buffer size!

	"""
	return self._len

    def marshal(self, format, value, offset, byte_order):
	""" Marshal a value onto the octet stream.

	'format'     identifies the type of the data to be marshalled.
	'value'      is the data to be marshalled!
	'offset'     is the offset in the stream to marshal the data into.
	'byte_order' is the byte ordering to use when marshalling the data.

	The return value is the offset in the stream after marshalling the
	data.

	"""
	# Calculate the space required to marshal the data onto the octet
	# stream.  If the value won't fit into the string that is currently
	# allocated then we create a new, larger one.
	new_offset = cdr.count(format,
			       self._data, # Not modified by this call.
			       offset,
			       byte_order,
			       value)

	# If a new string is required, then make it plenty big enough!
	if new_offset > len(self._data):
	    # For now we'll make the new string twice as big as required.
	    new_len = new_offset * 2
	    self._data = self._data + ('\0' * (new_len - len(self._data)))

	# Now marshal the data onto the octet stream.
	new_offset = cdr.marshal(format,
				 self._data,
				 offset,
				 byte_order,
				 value)

	# Have we increased the *actual* length of the data in the octet
	# stream?
	if new_offset > self._len:
	    self._len = new_offset

	return new_offset

    def unmarshal(self, format, offset, byte_order):
	""" Unmarshal a value from the octet stream.

	'format'     identifies the type of the data to be unmarshalled.
	'offset'     is the offset in the stream to unmarshal the data from.
	'byte_order' is the byte ordering to use when unmarshalling the data.
	
	The return value is a tuple (NewOffset, Value) where 'NewOffset' is the
	offset after unmarshalling the value, and 'Value' (surprise, surprise)
	is the value that was unmarshalled.

	"""
	return cdr.unmarshal(format, self._data, offset, byte_order)

    def data(self):
	""" Return the contents of the octet stream. """

	return self._data[:self._len]

    def cursor(self, offset=0, byte_order=Util.HOST_BYTE_ORDER):
	""" Return a cursor suitable for iterating over me! """

	return OctetStreamCursor(self, offset, byte_order)


class OctetStreamCursor:
    """ A class to allow iteration over an OctetStream. """

##    def __init__(self, stream, offset, byte_order=Util.HOST_BYTE_ORDER):
    def __init__(self, stream, offset, byte_order, stack=None):
	""" Constructor.

	'stream'     is the octet stream to iterate over.
	'offset'     is the starting position within the octet stream.
	'byte_order' is the byte ordering to use when marshalling/unmarshalling
	             data.

	"""
	self._stream = stream
	self._offset = offset
	self._byte_order = byte_order

	# Container stack for marshalling/unmarshalling recursive types.
	#
	# In the case of an encapsulation cursor, the stack is inherited from
	# the cursor that created it.
	if stack is not None:
	    self._stack = stack

	else:
	    self._stack = Stack.Stack()

	return

    def stream(self):
	""" Return the octet stream that we are iterating over. """

	return self._stream

    def offset(self):
	""" Return the current offset in the octet stream. """

	return self._offset

    def set_offset(self, offset):
	""" Set the offeset in the octet stream. """

	self._offset = offset
	return

    def byte_order(self):
	""" Return the byte ordering of the cursor. """

	return self._byte_order

    def marshal(self, format, value):
	""" Marshal a value onto the octet stream.

	'format' identifies the type of the data to be marshalled.
	'value'  is the data to be marshalled!

	"""
	self._offset = self._stream.marshal(format,
					    value,
					    self._offset,
					    self._byte_order)
	return

    def unmarshal(self, format):
	""" Unmarshal a value from the octet stream.

	'format' identifies the type of the data to be unmarshalled.

	The return value is the value that was unmarshalled.

	"""
	(self._offset, value) = self._stream.unmarshal(format,
						       self._offset,
						       self._byte_order)
	return value

    # fixme: How does this work with regard to calculating padding?
    #
    # answer: I think it works 'cos no item within an encapsulation requires
    # a byte boundary of anything more than a multiple of four, therefore, by
    # the time we have aligned the length of the encapsulation then we have
    # forced the necessary alignment.
    def encapsulation_cursor(self):
	""" Return a new cursor at the start of an encapsulation. """

	# Skip over the length of the encapsulation.
	(offset, length) = self._stream.unmarshal('L',
						  self._offset,
						  self._byte_order)

	# Get the byte order of the encapsulation.
	(offset, byte_order) = self._stream.unmarshal('b',
						      offset,
						      self._byte_order)

	# Create a new cursor with the specified byte order (the cursor also
	# inherits the stack for marshalling/unmarshalling recursive types).
	return OctetStreamCursor(self._stream, offset, byte_order, self._stack)

    def stack(self):
	""" Return the stack of containers.

	This is used to marshal and unmarshal recursive types.

	"""
	return self._stack


class Encapsulation(OctetStream):
    """ CORBA encapsulations. """

    def __init__(self, data=None, byte_order=Util.HOST_BYTE_ORDER):
	""" Constructor.

	'data'       is the contents of the octet stream.
	'byte_order' is the byte order of the octet stream!

	"""
	# The byte order of the octet stream.
	self._byte_order = byte_order

	# If no data is specified then we initially allocate a fixed size
	# string and then grow it (ie. get a bigger, better, brighter, wider,
	# faster string) as required.
	if data is None:
	    self._data = "\0" * OctetStream.INITIAL_SIZE
	    self._len = 0

	    # Marshal the byte order onto the first octet in the stream.
	    offset = self.marshal('b', self._byte_order, 0, self._byte_order)

	# If data *is* specified then we use that!
	else:
	    self._data = data
	    self._len = len(data)

	    # Get the byte order of the encapsulation (held in the first
	    # octet).
	    (offset, self._byte_order) = self.unmarshal('b',0,self._byte_order)

	return

    def cursor(self, offset=1):
	""" Return a cursor suitable for iterating over me! """

	return OctetStreamCursor(self, offset, self._byte_order)


class GIOPMessage(OctetStream):
    """ GIOP messages. """

    # Current version of GIOP.
    __GIOP_VERSION = GIOP.Version(Util.GIOP_VERSION_MAJOR,
				  Util.GIOP_VERSION_MINOR)

    def __init__(self, data=None, byte_order=Util.HOST_BYTE_ORDER, type=None):
	""" Constructor.

	'data'       is the contents of the octet stream.
	'byte_order' is the byte order of the octet stream!
	'type'       is the type of GIOP message ;^)

	"""
	# The byte order of the octet stream.
	self._byte_order = byte_order

	# If no data is specified then we initially allocate a fixed size
	# string and then grow it (ie. get a bigger, better, brighter, wider,
	# faster string) as required.
	if data is None:
	    self._data = "\0" * OctetStream.INITIAL_SIZE
	    self._len = 0

	    # Marshal a GIOP header onto the stream.
	    self._header = self._marshal_header(type)

	# If data *is* specified then we use that!
	else:
	    self._data = data
	    self._len = len(data)

	    # Get the byte order of the message (held in the sixth octet!).
	    (offset, self._byte_order) = self.unmarshal('b',6,self._byte_order)

	    # Unmarshal the GIOP header from the stream.
	    self._header = self._unmarshal_header()

	return

    def header(self):
	""" Return the GIOP message header. """
	
	return self._header

    def data(self):
	""" Return the contents of the octet stream. """

	# Fixup the length in the GIOP header.
	cdr.marshal('L', self._data, 8, self._byte_order, self._len - 12)

	return OctetStream.data(self)

    def cursor(self, offset=12):
	""" Return a cursor suitable for iterating over me! """

	return OctetStreamCursor(self, offset, self._byte_order)

    #########################################################################
    # Internal methods.
    #########################################################################

    def _marshal_header(self, message_type):
	""" Marshal a GIOP header onto the start of the stream.

	'message_type' is the type of GIOP message ;^)

	"""
	# Create a GIOP header (the size of the message is fixed up when
	# the 'data' method is called!).
	header = GIOP.MessageHeader(Util.MAGIC,
				    GIOPMessage.__GIOP_VERSION,
				    self._byte_order,
				    message_type.value(),
				    0) # Size

	tc = CORBA.typecode('IDL:omg.org/GIOP/MessageHeader:1.0')
	tc._fnorb_marshal_value(self.cursor(0), header)

	return header

    def _unmarshal_header(self):
	""" Unmarshal a GIOP header from the start of the stream. """

	# Unmarshal the header from the start of the stream.
	tc = CORBA.typecode('IDL:omg.org/GIOP/MessageHeader:1.0')
	return tc._fnorb_unmarshal_value(self.cursor(0))

#############################################################################

# Testing.
if __name__ == '__main__':

    for stream in [OctetStream(), Encapsulation()]:
	# Marshal some 'stuff' onto the stream...
	cursor = stream.cursor(12)
	cursor.marshal('b', 123)
	cursor.marshal('c', 'X')
	cursor.marshal('o', 125)
	cursor.marshal('h', 126)
	cursor.marshal('H', 127)
	cursor.marshal('l', 128)
	cursor.marshal('L', 129)
	cursor.marshal('n', -9223372036854775808L)
	cursor.marshal('N', 9223372036854775808L)
	cursor.marshal('f', 130.456)
	cursor.marshal('d', 131.456)
	cursor.marshal('s', 'Hello World!')
	cursor.marshal('O', 'Hello World!')
	# ... and make sure it unmarshals correctly ;^)
	cursor = stream.cursor(12)
	print cursor.unmarshal('b')
	print cursor.unmarshal('c')
	print cursor.unmarshal('o')
	print cursor.unmarshal('h')
	print cursor.unmarshal('H')
	print cursor.unmarshal('l')
	print cursor.unmarshal('L')
	print cursor.unmarshal('n')
	print cursor.unmarshal('N')
	print cursor.unmarshal('f')
	print cursor.unmarshal('d')
	print cursor.unmarshal('s')
	print cursor.unmarshal('O')

#############################################################################
