class BaseNegotiator(object): """ Base functionality for negotiating an RTMP handshake. Call L{start} to begin negotiations. @ivar observer: An observer for handshake negotiations. @type observer: L{IHandshakeObserver} @ivar started: Whether negotiations have begun. @type started: C{bool} @ivar _buffer: Any data that has been received but not yet been consumed. @type _buffer: L{BufferedByteStream} """ started = False nearRequest = None nearResponse = None farRequest = None farResponse = None protocolVersion = 3 farProtocolVersion = None def __init__(self, observer, output): self.observer = observer self.output = output def start(self, uptime=0, version=0): """ Called to start the handshaking negotiations. """ if self.started: raise AlreadyStarted('Handshake negotiator cannot be restarted') self.started = True self.uptime = uptime self.version = version self._buffer = BufferedByteStream() def readPacket(self): if self._buffer.remaining() < HANDSHAKE_LENGTH: # we're expecting more data return packet = self._buffer.read(HANDSHAKE_LENGTH) self._buffer.consume() return packet def dataReceived(self, data): """ Called when handshake data has been received. """ if not self.started: raise HandshakeError('Data received, but negotiator not started') self._buffer.append(data) if self.farProtocolVersion is None: self.farProtocolVersion = self._buffer.read_uchar() packet = self.readPacket() if not packet: return if not self.farRequest: self.farRequest = self.buildFarRequest() self.farRequest.decode(packet) self.farRequestReceived(self.farRequest) packet = self.readPacket() if not packet: return if not self.farResponse: self.farResponse = self.buildFarResponse() self.farResponse.decode(packet) self.farResponseReceived(self.farResponse) def buildFarRequest(self): """ """ return RequestPacket() def buildFarResponse(self): """ """ return ResponsePacket() def buildNearRequest(self): """ """ p = RequestPacket() p.uptime = self.uptime p.version = self.version return p def buildNearResponse(self): """ """ return ResponsePacket() def farRequestReceived(self, request): """ Called when request packet has been received from the peer. """ raise NotImplementedError def farResponseReceived(self, response): """ Called when response packet has been received from the peer. """ raise NotImplementedError
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 StreamingChannel(object): """ """ def __init__(self, channel, streamId, output): self.type = None self.channel = channel self.streamId = streamId self.output = output self.stream = BufferedByteStream() self._lastHeader = None self._oldStream = channel.stream channel.stream = self.stream h = header.Header(channel.channelId) # encode a continuation header for speed header.encode(self.stream, h, h) self._continuationHeader = self.stream.getvalue() self.stream.consume() def __del__(self): try: self.channel.stream = self._oldStream except: pass def setType(self, type): self.type = type def sendData(self, data, timestamp): c = self.channel if timestamp < c.timestamp: relTimestamp = timestamp else: relTimestamp = timestamp - c.timestamp h = header.Header(c.channelId, relTimestamp, self.type, len(data), self.streamId) if self._lastHeader is None: h.full = True c.setHeader(h) c.append(data) header.encode(self.stream, h, self._lastHeader) self._lastHeader = h c.marshallOneFrame() while not c.complete(): self.stream.write(self._continuationHeader) c.marshallOneFrame() c.reset() self.output.write(self.stream.getvalue()) self.stream.consume()
class BaseNegotiator(object): """ Base functionality for negotiating an RTMP handshake. @ivar observer: An observer for handshake negotiations. @type observer: L{IHandshakeObserver} @ivar buffer: Any data that has not yet been consumed. @type buffer: L{BufferedByteStream} @ivar started: Determines whether negotiations have already begun. @type started: C{bool} @ivar my_syn: The initial handshake packet that will be sent by this negotiator. @type my_syn: L{Packet} @ivar my_ack: The handshake packet that will be sent after the peer has sent its syn. @ivar peer_syn: The initial L{Packet} received from the peer. @ivar peer_ack: The L{Packet} received in acknowledgement of my syn. @ivar peer_version: The handshake version that the peer understands. """ implements(IHandshakeNegotiator) def __init__(self, observer, transport): self.observer = observer self.transport = transport self.started = False def start(self, uptime=None, version=None): """ Called to start the handshaking negotiations. """ if self.started: raise HandshakeError('Handshake negotiator cannot be restarted') self.started = True self.buffer = BufferedByteStream() self.peer_version = None self.my_syn = Packet(uptime, version) self.my_ack = None self.peer_syn = None self.peer_ack = None self.buildSynPayload(self.my_syn) self._writePacket(self.my_syn) def getPeerPacket(self): """ Attempts to decode a L{Packet} from the buffer. If there is not enough data in the buffer then C{None} is returned. """ if self.buffer.remaining() < HANDSHAKE_LENGTH: # we're expecting more data return packet = Packet() packet.decode(self.buffer) return packet def _writePacket(self, packet, stream=None): stream = stream or BufferedByteStream() packet.encode(stream) self.transport.write(stream.getvalue()) def dataReceived(self, data): """ Called when handshake data has been received. If an error occurs whilst negotiating the handshake then C{self.observer.handshakeFailure} will be called, citing the reason. 3 stages of data are received. The handshake version, the syn packet and then the ack packet. """ if not self.started: raise HandshakeError('Data was received, but negotiator was ' 'not started') self.buffer.append(data) self._process() def _process(self): if not self.peer_syn: self.peer_syn = self.getPeerPacket() if not self.peer_syn: return self.buffer.consume() self.synReceived() if not self.peer_ack: self.peer_ack = self.getPeerPacket() if not self.peer_ack: return self.buffer.consume() self.ackReceived() # if we get here then a successful handshake has been negotiated. # inform the observer accordingly self.observer.handshakeSuccess(self.buffer.getvalue()) def writeAck(self): """ Writes L{self.my_ack} to the observer. """ self._writePacket(self.my_ack) def buildSynPayload(self, packet): """ Called to build the syn packet, based on the state of the negotiations. """ raise NotImplementedError def buildAckPayload(self, packet): """ Called to build the ack packet, based on the state of the negotiations. """ raise NotImplementedError def synReceived(self): """ Called when the peers syn packet has been received. Use this function to do any validation/verification. """ def ackReceived(self): """
class StreamingChannel(object): """ """ def __init__(self, encoder, streamId, output): self.encoder = encoder self.channel = self.encoder.acquireChannel() if self.channel is None: # todo: make this better raise RuntimeError('No streaming channel available') self.type = None self.streamId = streamId self.output = output self.stream = BufferedByteStream() self._lastHeader = None self._oldStream = self.channel.stream self.channel.stream = self.stream h = header.Header(self.channel.channelId) # encode a continuation header for speed header.encode(self.stream, h, h) self._continuationHeader = self.stream.getvalue() self.stream.consume() def __del__(self): try: self.channel.stream = self._oldStream except: pass def setType(self, type): self.type = type def sendData(self, data, timestamp): c = self.channel if timestamp < c.timestamp: relTimestamp = timestamp else: relTimestamp = timestamp - c.timestamp h = header.Header(c.channelId, relTimestamp, self.type, len(data), self.streamId) if self._lastHeader is None: h.full = True c.setHeader(h) c.append(data) header.encode(self.stream, h, self._lastHeader) self._lastHeader = h c.marshallOneFrame() while not c.complete(): self.stream.write(self._continuationHeader) c.marshallOneFrame() c.reset() s = self.stream.getvalue() self.output.write(s) self.encoder.bytes += len(s) self.stream.consume()
class Codec(object): """ Generic channels and frame operations. @ivar stream: The underlying buffer containing the raw bytes. @type stream: L{BufferedByteStream} @ivar channels: A L{dict} of L{BaseChannel} objects that are handling data. @ivar frameSize: The maximum size for an individual frame. Read-only, use L{setFrameSize} instead. """ def __init__(self): self.buffer = BufferedByteStream() self.channels = {} self.frameSize = FRAME_SIZE self.bytes = 0 def setFrameSize(self, size): """ Set the size of the next frame to be handled. """ self.frameSize = size for channel in self.channels.values(): channel.setFrameSize(size) def buildChannel(self, channelId): """ Called to build a channel suitable for use with this codec. Must be implemented by subclasses. """ raise NotImplementedError def getChannel(self, channelId): """ Returns a channel based on channelId. If the channel doesn't exist, then one is created. @param channelId: Index for the channel to retrieve. @type channelId: C{int} @rtype: L{Channel} """ channel = self.channels.get(channelId, None) if channel is not None: return channel if channelId > MAX_CHANNELS: raise IndexError('Attempted to get channelId %d which is > %d' % ( channelId, MAX_CHANNELS)) channel = self.buildChannel(channelId) self.channels[channelId] = channel channel.reset() return channel def clear(self): """ Clears the underlying buffer. """ self.buffer.consume() self.buffer.truncate()
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