def write(self, previous=None): """ Write (encoder) header to byte string. @param previous: previous header (used to compress header) @type previous: L{RTMPHeader} @return: encoded header @rtype: C{str} """ if previous is None: diff = 3 else: diff = self.diff(previous) first = self.object_id & 0x3f | ((diff ^ 3) << 6) if diff == 0: return chr(first) buf = BufferedByteStream() buf.write_uchar(first) buf.write_24bit_uint(self.timestamp) if diff > 1: buf.write_24bit_uint(self.length) buf.write_uchar(self.type) if diff > 2: buf.write_ulong(self.stream_id) buf.seek(0, 0) return buf.read()
def write(self, previous=None): """ Write (encoder) header to byte string. @param previous: previous header (used to compress header) @type previous: L{RTMPHeader} @return: encoded header @rtype: C{str} """ if previous is None: diff = 3 else: diff = self.diff(previous) first = self.object_id & 0x3F | ((diff ^ 3) << 6) if diff == 0: return chr(first) buf = BufferedByteStream() buf.write_uchar(first) buf.write_24bit_uint(self.timestamp) if diff > 1: buf.write_24bit_uint(self.length) buf.write_uchar(self.type) if diff > 2: buf.write_ulong(self.stream_id) buf.seek(0, 0) return buf.read()
def test_assemble(self): for fixture in self.data: buf = BufferedByteStream() a = RTMPAssembler(128, buf) for packet in fixture['packets']: a.push_packet(packet) buf.seek(0, 0) self.failUnlessEqual(struct.pack("B" * len(fixture['data']), *fixture['data']), buf.read())
def write(self): """ Encode packet into bytes. @return: representation of packet @rtype: C{str} """ buf = BufferedByteStream() buf.write_ulong(self.bytes) self.header.length = len(buf) buf.seek(0, 0) return buf.read()
def test_assemble_chunks(self): chunkSizes = (32, 64, 128, 256) header = RTMPHeader(object_id=2, timestamp=9504486, length=10, type=0x04, stream_id=0) for chunkSize in chunkSizes: for l in xrange(1, 258): data = ''.join([chr(random.randint(0, 255)) for x in xrange(l)]) buf = BufferedByteStream() a = RTMPAssembler(chunkSize, buf) a.push_packet(DataPacket(header=header, data=data)) buf.seek(0, 0) self.failUnlessEqual(''.join(self.gen_packet("\x02\x91\x06\xe6\x00\x00\x01\x04\x00\x00\x00\x00", ["\xc2"], data, l, chunkSize)), buf.read())
class ProducingChannel(BaseChannel): """ Writes RTMP frames. @ivar buffer: Any data waiting to be written to the underlying stream. @type buffer: L{BufferedByteStream} @ivar acquired: Whether this channel is acquired. See L{ChannelMuxer. acquireChannel} """ def __init__(self, channelId, stream, frameSize): BaseChannel.__init__(self, channelId, stream, frameSize) self.buffer = BufferedByteStream() self.acquired = False self.callback = None def setCallback(self, cb): """ Sets the callback that will be fired once this channel has been completely encoded. """ self.callback = cb def reset(self): """ Called when the channel has completed writing the buffer. """ BaseChannel.reset(self) self.buffer.seek(0) self.buffer.truncate() self.header = None def append(self, data): """ Appends data to the buffer in preparation of encoding in RTMP. """ self.buffer.append(data) def marshallFrame(self, size): """ Writes a section of the buffer as part of the RTMP frame. """ self.stream.write(self.buffer.read(size))
class Packet(object): """ """ format = None challengeKey = None def __init__(self): self.buffer = BufferedByteStream() def computeOffset(self, start, modulus, increment): """ An offset is 4 consecutive bytes encoded at C{start}. s = sum of bytes offset = (s % modulus) + increment """ self.buffer.seek(start) offset = ( self.buffer.read_uchar() + self.buffer.read_uchar() + self.buffer.read_uchar() + self.buffer.read_uchar()) offset %= modulus offset += increment return offset def getDigestAndPayload(self, offset, length): """ Returns the C{digest} and C{payload} components. """ self.buffer.seek(0) payload = self.buffer.read(offset) digest = self.buffer.read(length) payload += self.buffer.read() return digest, payload def setFormat(self, format): self.format = format def setChallengeKey(self, key): self.challengeKey = key def setChallengeDigest(self, digest): self.challengeDigest = digest def decode(self, data): """ Decodes the data bytes into this packet. """ raise NotImplementedError
class RTMPBaseProtocol(protocol.Protocol): """ Basis RTMP protocol implementation. @ivar state: internal state of protocol @ivar input: input packet disassebmbler @type input: L{RTMPDisassembler} @ivar output: output packet assembler @type output: L{RTMPAssembler} @ivar handshakeBuf: buffer, holding input data during handshake @type handshakeBuf: C{BufferedByteStream} """ class State: CONNECTING = 'connecting' """ Connection in progress """ HANDSHAKE_SEND = 'handshake-send' """ Handshake, 1st phase. """ HANDSHAKE_VERIFY = 'handshake-verify' """ Handshake, 2nd phase. """ RUNNING = 'running' """ Usual state of protocol: receiving-sending RTMP packets. """ def __init__(self): """ Constructor. """ self.state = self.State.CONNECTING self.handshakeTimeout = None def connectionMade(self): """ Successfully connected to peer. """ self.input = RTMPDisassembler(constants.DEFAULT_CHUNK_SIZE) self.output = RTMPAssembler(constants.DEFAULT_CHUNK_SIZE, self.transport) self.state = self.State.HANDSHAKE_SEND self.handshakeTimeout = reactor.callLater( config.getint('RTMP', 'handshakeTimeout'), self._handshakeTimedout) self.handshakeBuf = BufferedByteStream() self._beginHandshake() def _beginHandshake(self): """ Begin handshake procedures. Implemented in descendants. """ raise NotImplementedError def _handshakeSendReceived(self): """ Data received in HANDSHAKE_SEND state. Implemented in descendants. """ raise NotImplementedError def _handshakeVerifyReceived(self): """ Data received in HANDSHAKE_VERIFY state. Implemented in descendants. """ raise NotImplementedError def _handshakeComplete(self): """ Handshake complete, clear timeouts. """ if self.handshakeTimeout is not None: self.handshakeTimeout.cancel() self.handshakeTimeout = None self.state = self.State.RUNNING self._regularInput(self.handshakeBuf.read()) del self.handshakeBuf def _handshakeTimedout(self): """ Handshake not completed in timeout. """ self.handshakeTimeout = None self.transport.loseConnection() def connectionLost(self, reason): """ Connection with peer was lost for some reason. """ if self.handshakeTimeout is not None: self.handshakeTimeout.cancel() self.handshakeTimeout = None def _regularInput(self, data): """ Regular RTMP dataflow: stream of RTMP packets. Some bytes (L{data}) was received. @param data: bytes received @type data: C{str} """ self.input.push_data(data) while True: packet = self.input.disassemble() if packet is None: return self._handlePacket(packet) def dataReceived(self, data): """ Some data was received from peer. @param data: bytes received @type data: C{str} """ if self.state == self.State.RUNNING: self._regularInput(data) else: # handshake self.handshakeBuf.seek(0, 2) self.handshakeBuf.write(data) if self.state == self.State.HANDSHAKE_SEND: self._handshakeSendReceived() elif self.state == self.State.HANDSHAKE_VERIFY: self._handshakeVerifyReceived() def _handlePacket(self, packet): """ Dispatch received packet to some handler. @param packet: packet @type packet: L{Packet} """ log.msg("<- %r" % packet) handler = 'handle' + packet.__class__.__name__ try: getattr(self, handler)(packet) except AttributeError: log.msg("Unhandled packet: %r" % packet) def pushPacket(self, packet): """ Push outgoing RTMP packet. @param packet: outgoing packet @type packet: L{Packet}. """ log.msg("-> %r" % packet) self.output.push_packet(packet)
class RTMPBaseProtocol(protocol.Protocol): """ Basis RTMP protocol implementation. @ivar state: internal state of protocol @ivar input: input packet disassebmbler @type input: L{RTMPDisassembler} @ivar output: output packet assembler @type output: L{RTMPAssembler} @ivar handshakeBuf: buffer, holding input data during handshake @type handshakeBuf: C{BufferedByteStream} """ class State: CONNECTING = 'connecting' """ Connection in progress """ HANDSHAKE_SEND = 'handshake-send' """ Handshake, 1st phase. """ HANDSHAKE_VERIFY = 'handshake-verify' """ Handshake, 2nd phase. """ RUNNING = 'running' """ Usual state of protocol: receiving-sending RTMP packets. """ def __init__(self): """ Constructor. """ self.state = self.State.CONNECTING self.handshakeTimeout = None def connectionMade(self): """ Successfully connected to peer. """ self.input = RTMPDisassembler(constants.DEFAULT_CHUNK_SIZE) self.output = RTMPAssembler(constants.DEFAULT_CHUNK_SIZE, self.transport) self.state = self.State.HANDSHAKE_SEND self.handshakeTimeout = reactor.callLater(config.getint('RTMP', 'handshakeTimeout'), self._handshakeTimedout) self.handshakeBuf = BufferedByteStream() self._beginHandshake() def _beginHandshake(self): """ Begin handshake procedures. Implemented in descendants. """ raise NotImplementedError def _handshakeSendReceived(self): """ Data received in HANDSHAKE_SEND state. Implemented in descendants. """ raise NotImplementedError def _handshakeVerifyReceived(self): """ Data received in HANDSHAKE_VERIFY state. Implemented in descendants. """ raise NotImplementedError def _handshakeComplete(self): """ Handshake complete, clear timeouts. """ if self.handshakeTimeout is not None: self.handshakeTimeout.cancel() self.handshakeTimeout = None self.state = self.State.RUNNING self._regularInput(self.handshakeBuf.read()) del self.handshakeBuf def _handshakeTimedout(self): """ Handshake not completed in timeout. """ self.handshakeTimeout = None self.transport.loseConnection() def connectionLost(self, reason): """ Connection with peer was lost for some reason. """ if self.handshakeTimeout is not None: self.handshakeTimeout.cancel() self.handshakeTimeout = None def _regularInput(self, data): """ Regular RTMP dataflow: stream of RTMP packets. Some bytes (L{data}) was received. @param data: bytes received @type data: C{str} """ self.input.push_data(data) while True: packet = self.input.disassemble() if packet is None: return self._handlePacket(packet) def dataReceived(self, data): """ Some data was received from peer. @param data: bytes received @type data: C{str} """ if self.state == self.State.RUNNING: self._regularInput(data) else: # handshake self.handshakeBuf.seek(0, 2) self.handshakeBuf.write(data) if self.state == self.State.HANDSHAKE_SEND: self._handshakeSendReceived() elif self.state == self.State.HANDSHAKE_VERIFY: self._handshakeVerifyReceived() def _handlePacket(self, packet): """ Dispatch received packet to some handler. @param packet: packet @type packet: L{Packet} """ log.msg("<- %r" % packet) handler = 'handle' + packet.__class__.__name__ try: getattr(self, handler)(packet) except AttributeError: log.msg("Unhandled packet: %r" % packet) def pushPacket(self, packet): """ Push outgoing RTMP packet. @param packet: outgoing packet @type packet: L{Packet}. """ log.msg("-> %r" % packet) self.output.push_packet(packet)
class RTMPDisassembler(object): """ Disassembling bytestream into RTMP packets. RTMP stream slices packets into chunks of L{chunkSize}. This class processes incoming stream of RTMP protocol data (after initial handshake) and decodes RTMP packets. Communication goes independently for each object_id. Last received headers are stored for each object_id in L{lastHeaders}. L{pool} holds incomplete packet contents also for each object_id. @ivar lastHeaders: last received header for object_id @type lastHeaders: C{dict}, object_id -> L{RTMPHeader} @ivar pool: incomplete packet data for object_id @type pool: C{dict}, object_id -> L{BufferedByteStream} @ivar chunkSize: size of chunk for this stream @type chunkSize: C{int} @ivar buffer: incoming buffer with data received from protocol @type buffer: L{BufferedByteStream} """ def __init__(self, chunkSize): """ Constructor. @param chunkSize: initial size of chunk @type chunkSize: C{int} """ self.lastHeaders = {} self.pool = {} self.chunkSize = chunkSize self.buffer = BufferedByteStream() def push_data(self, data): """ Push more incoming data. @param data: data received @type data: C{str} """ self.buffer.seek(0, 2) self.buffer.write(data) return self def disassemble(self): """ Disassemble L{buffer} into packets. Returns first decoded packet or None, if no packet could be decoded at the moment. @return: decoded packet @rtype: L{Packet} """ self.buffer.seek(0) while self.buffer.remaining() > 0: try: # try to parse header from stream header = RTMPHeader.read(self.buffer) except NeedBytes, (bytes,): # not enough bytes, return what we've already parsed return None # fill header with extra data from previous headers received # with same object_id header.fill(self.lastHeaders.get(header.object_id, RTMPHeader())) # get buffer for data of this packet buf = self.pool.get(header.object_id, BufferedByteStream()) # this chunk size is minimum of regular chunk size in this # disassembler and what we have left here thisChunk = min(header.length - len(buf), self.chunkSize) if self.buffer.remaining() < thisChunk: # we have not enough bytes to read this chunk of data return None # we got complete chunk buf.write(self.buffer.read(thisChunk)) # store packet header for this object_id self.lastHeaders[header.object_id] = header # skip data left in input buffer self.buffer.consume() # this chunk completes full packet? if len(buf) < header.length: # no, store buffer for further chunks self.pool[header.object_id] = buf else: # parse packet from header and data buf.seek(0, 0) # delete stored data for this packet if header.object_id in self.pool: del self.pool[header.object_id] return self._decode_packet(header, buf) return None
class RTMPDisassembler(object): """ Disassembling bytestream into RTMP packets. RTMP stream slices packets into chunks of L{chunkSize}. This class processes incoming stream of RTMP protocol data (after initial handshake) and decodes RTMP packets. Communication goes independently for each object_id. Last received headers are stored for each object_id in L{lastHeaders}. L{pool} holds incomplete packet contents also for each object_id. @ivar lastHeaders: last received header for object_id @type lastHeaders: C{dict}, object_id -> L{RTMPHeader} @ivar pool: incomplete packet data for object_id @type pool: C{dict}, object_id -> L{BufferedByteStream} @ivar chunkSize: size of chunk for this stream @type chunkSize: C{int} @ivar buffer: incoming buffer with data received from protocol @type buffer: L{BufferedByteStream} """ def __init__(self, chunkSize): """ Constructor. @param chunkSize: initial size of chunk @type chunkSize: C{int} """ self.lastHeaders = {} self.pool = {} self.chunkSize = chunkSize self.buffer = BufferedByteStream() def push_data(self, data): """ Push more incoming data. @param data: data received @type data: C{str} """ self.buffer.seek(0, 2) self.buffer.write(data) return self def disassemble(self): """ Disassemble L{buffer} into packets. Returns first decoded packet or None, if no packet could be decoded at the moment. @return: decoded packet @rtype: L{Packet} """ self.buffer.seek(0) while self.buffer.remaining() > 0: try: # try to parse header from stream header = RTMPHeader.read(self.buffer) except NeedBytes, (bytes, ): # not enough bytes, return what we've already parsed return None # fill header with extra data from previous headers received # with same object_id header.fill(self.lastHeaders.get(header.object_id, RTMPHeader())) # get buffer for data of this packet buf = self.pool.get(header.object_id, BufferedByteStream()) # this chunk size is minimum of regular chunk size in this # disassembler and what we have left here thisChunk = min(header.length - len(buf), self.chunkSize) if self.buffer.remaining() < thisChunk: # we have not enough bytes to read this chunk of data return None # we got complete chunk buf.write(self.buffer.read(thisChunk)) # store packet header for this object_id self.lastHeaders[header.object_id] = header # skip data left in input buffer self.buffer.consume() # this chunk completes full packet? if len(buf) < header.length: # no, store buffer for further chunks self.pool[header.object_id] = buf else: # parse packet from header and data buf.seek(0, 0) # delete stored data for this packet if header.object_id in self.pool: del self.pool[header.object_id] return self._decode_packet(header, buf) return None