#!/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: /cvsroot/fnorb/fnorb/orb/OctetStream.py,v $ # Version: @(#)$RCSfile: OctetStream.py,v $ $Revision: 1.20 $ # ############################################################################# """ Classes for CORBA octet streams, encapsulations and GIOP messages. """ # Standard/built-in modules. import new, struct from cStringIO import StringIO # Fnorb modules. import CORBA, GIOP, Util, cdrpy # 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._buffer = StringIO() self._len = 0 # If data *is* specified then we use that! else: self._buffer = StringIO(data) self._len = len(data) return def __del__(self): 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 read(self, max = None): """Read at most max bytes from the stream.""" if max is None: return self._buffer.read() else: return self._buffer.read(max) 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. """ # Marshal the data onto the octet stream. self._byte_order = byte_order # If we get an offset past the end of the buffer, then fill it # with the correct number of bytes if offset > self._len: self._buffer.write("x" * (offset - self._len)) self._len = offset else: self._buffer.seek(offset) new_offset = cdrpy.marshal(format, self._buffer, 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. """ self._buffer.seek(offset) return cdrpy.unmarshal(format, self._buffer, offset, byte_order) def data(self): """ Return the contents of the octet stream. """ data = self._buffer.getvalue() return data def cursor(self, offset=0, byte_order=Util.BIG_ENDIAN): """ 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): 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 read(self, max=None): """ Read atmost max bytes from the stream.""" result = self._stream.read(max) self._offset += len(result) return result 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 def giop_version(self): """Return the GIOP version of the underlying stream.""" return self._stream.giop_version() def get_tcs_w(self): """Return the negotiated TCS-W of the underlying stream.""" return self._stream.get_tcs_w() class Encapsulation(OctetStream): """ CORBA encapsulations. """ def __init__(self, data=None, byte_order=Util.BIG_ENDIAN): """ 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._buffer = StringIO() 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._buffer = StringIO(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) def giop_version(self): """ Return the GIOP version of the encapsulation. 15.3.1.6 specifies that in encapsulations, wchar values should always assume GIOP version 1.2.""" return GIOP.Version(1, 2) class GIOPMessage(OctetStream): """ GIOP messages. """ def __init__(self, giop_version, data=None, byte_order=Util.BIG_ENDIAN, 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 ;^) """ # Store the GIOP version self._giop_version = giop_version # The byte order of the octet stream. self._byte_order = byte_order # No negotiated TCS-W self._tcs_w = None # 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._buffer = StringIO() 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._buffer = StringIO(data) self._len = len(data) # Override GIOP version from the header; held in octets 4 and 5 self._giop_version = GIOP.Version(ord(data[4]), ord(data[5])) # Get the byte order of the message (held in the sixth octet!). self._byte_order = ord(data[6]) self._more_fragments = 0 # For GIOP 1.1 and later, only the LSB indicates the byte order if self._giop_version.minor > 0: self._more_fragments = self._byte_order & 2 self._byte_order = self._byte_order & 1 # 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 more_fragments(self): """Return true if more fragments are expected""" return self._more_fragments def add_fragment(self, data, more_fragments): """Add a fragment, and update the flag whether more fragments are expected.""" b = self._buffer pos = b.tell() self._buffer = StringIO(b.getvalue() + data) b.seek(pos, 1) self._more_fragments = more_fragments self._len += len(data) def data(self): # Fixup the length in the GIOP header. self._buffer.seek(8) length_stream = OctetStream() length_stream.marshal('L', self._len - 12, 0, self._byte_order) self._buffer.write(length_stream.data()) data = self._buffer.getvalue() return data def giop_version(self): """ Return the GIOP version of this message. """ return self._giop_version def get_tcs_w(self): return self._tcs_w def set_tcs_w(self, tcs_w): self._tcs_w = tcs_w 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!). if self._giop_version.minor == 0: header = GIOP.MessageHeader_1_0(Util.MAGIC, self._giop_version, self._byte_order, message_type.value(), 0) # Size tc = CORBA.typecode('IDL:omg.org/GIOP/MessageHeader_1_0:1.0') elif self._giop_version.minor == 1: header = GIOP.MessageHeader_1_1(Util.MAGIC, self._giop_version, self._byte_order, message_type.value(), 0) # Size tc = CORBA.typecode('IDL:omg.org/GIOP/MessageHeader_1_1:1.0') elif self._giop_version.minor == 2: header = GIOP.MessageHeader_1_1(Util.MAGIC, self._giop_version, self._byte_order, message_type.value(), 0) # Size tc = CORBA.typecode('IDL:omg.org/GIOP/MessageHeader_1_2:1.0') else: assert 0, 'Internal error: Unknown GIOP version' 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. if self._giop_version.minor == 0: tc = CORBA.typecode('IDL:omg.org/GIOP/MessageHeader_1_0:1.0') elif self._giop_version.minor == 1: tc = CORBA.typecode('IDL:omg.org/GIOP/MessageHeader_1_1:1.0') elif self._giop_version.minor == 2: tc = CORBA.typecode('IDL:omg.org/GIOP/MessageHeader_1_2: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') #############################################################################