def sendMessage(self, msg, stream, whenDone=None): """ Sends an RTMP message to the peer. Not part of a public api, use C{stream.sendMessage} instead. @param msg: The message being sent to the peer. @type msg: L{message.IMessage} @param stream: The stream instance that is sending the message. @type stream: L{NetStream} @param whenDone: A callback fired when the message has been written to the RTMP stream. See L{BaseStream.sendMessage} """ buf = BufferedByteStream() e = self.encoder # this will probably need to be rethought as this could block for an # unacceptable amount of time. For most messages however it seems to be # fast enough and the penalty for setting up a new thread is too high. msg.encode(buf) e.send(buf.getvalue(), msg.__data_type__, stream.streamId, stream.timestamp, whenDone) if e.active and not self.encoder_task: self.startEncoding()
def sendMessage(self, msg, stream, whenDone=None): """ Queues L{msg} for encoding into RTMP and being spit out to L{output}. This is a low level api and is not for public consumption. Use L{interfaces.IStream.sendMessage} instead. @param msg: The message being sent to the peer. @type msg: L{message.IMessage} @param stream: The stream instance that is sending the message. @type stream: L{interfaces.IStream} @param whenDone: A callback fired when the message has been written to the RTMP stream. See L{BaseStream.sendMessage} """ buf = BufferedByteStream() e = self.encoder # this will probably need to be rethought as this could block for an # unacceptable amount of time. For most messages however it seems to be # fast enough and the penalty for setting up a new thread is too high. msg.encode(buf) e.send(buf.getvalue(), msg.__data_type__, stream.streamId, stream.timestamp, whenDone) if e.active and not self._encoding_task: self.startEncoding()
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 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 __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 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 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())
def startStreaming(self): """ This must be called before any RTMP data is received. """ self.streamManager = self.buildStreamManager() self.controlStream = self.streamManager.getControlStream() self._decodingBuffer = BufferedByteStream() self._encodingBuffer = BufferedByteStream() self.decoder = codec.Decoder(self.getDispatcher(), self.streamManager, stream=self._decodingBuffer) self.encoder = codec.Encoder(self.getWriter(), stream=self._encodingBuffer) self.decoder_task = None self.encoder_task = None
class _BackRelay(protocol.ProcessProtocol): def __init__(self, deferred): self.deferred = deferred self.s = BufferedByteStream() def errReceived(self, text): self.deferred.errback(failure.Failure(IOError())) self.deferred = None self.transport.loseConnection() def outReceived(self, text): self.s.write(text) def processEnded(self, reason): if self.deferred is not None: result = self.s.getvalue() self.deferred.callback(result)
def test_read_short(self): for fixture in self.data: for l in xrange(len(fixture[0])-1): try: RTMPHeader.read(BufferedByteStream(fixture[0][0:l])) self.fail() except NeedBytes, (bytes,): self.failUnlessEqual(len(fixture[0])-l if l != 0 else 1, bytes)
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)
def write(self): """ Encode packet into bytes. @return: representation of packet @rtype: C{str} """ buf = BufferedByteStream() buf.write_ushort(self.event) for val in self.data: buf.write_ulong(val) self.header.length = len(buf) buf.seek(0, 0) return buf.read()
def setUp(self): self.patch(codec, 'ChannelDemuxer', MockChannelDemuxer) self.dispatcher = DispatchTester(self) self.stream_factory = MockStreamFactory(self) self.decoder = codec.Decoder(self.dispatcher, self.stream_factory, stream=BufferedByteStream()) self.expected_streams = None self.streams = {}
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 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 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 dispatchMessage(self, stream, datatype, timestamp, data): """ Called when the RTMP decoder has read a complete RTMP message. @param stream: The L{Stream} to receive this message. @param datatype: The RTMP datatype for the message. @param timestamp: The absolute timestamp this message was received. @param data: The raw data for the message. """ m = message.classByType(datatype)() m.decode(BufferedByteStream(data)) m.dispatch(stream, timestamp)
def dispatchMessage(self, stream, datatype, timestamp, data): p = Packet(self.type, streamId=stream.streamId, datatype=datatype, timestamp=timestamp) self.observer.messageStart(p) e = message.classByType(datatype)() e.decode(BufferedByteStream(data)) e.dispatch(stream, timestamp) self.observer.messageComplete(p)
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))
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()
class BytesReadTestCase(unittest.TestCase): """ Test case for L{fmspy.rtmp.packets.BytesRead}. """ data = [ ( { 'header': RTMPHeader(object_id=2, timestamp=0, length=4, type=0x03, stream_id=0L), 'buf': BufferedByteStream('\x00\x00\x00\x89'), }, BytesRead(bytes=137, header=RTMPHeader(object_id=2, timestamp=0, length=4, type=0x03, stream_id=0L)), ), ] def test_eq(self): self.failUnlessEqual( BytesRead(bytes=5, header=RTMPHeader(object_id=3)), BytesRead(bytes=5, header=RTMPHeader(object_id=3))) self.failIfEqual(BytesRead(bytes=5, header=RTMPHeader(object_id=4)), BytesRead(bytes=5, header=RTMPHeader(object_id=3))) self.failIfEqual(BytesRead(bytes=6, header=RTMPHeader(object_id=3)), BytesRead(bytes=5, header=RTMPHeader(object_id=3))) def test_read(self): for fixture in self.data: fixture[0]['buf'].seek(0) self.failUnlessEqual(fixture[1], BytesRead.read(**fixture[0])) def test_write(self): for fixture in self.data: fixture[0]['buf'].seek(0) self.failUnlessEqual(fixture[0]['buf'].read(), fixture[1].write())
def pyamfEncode(self, obj, amf3=False, use_proxies=False): if amf3 is True: context = pyamf.get_context(pyamf.AMF3) else: context = pyamf.get_context(pyamf.AMF0) stream = BufferedByteStream() if amf3 is True: pyamf_encoder = pyamf.get_encoder(pyamf.AMF3, stream=stream, context=context) else: pyamf_encoder = pyamf.get_encoder(pyamf.AMF0, stream=stream, context=context) pyamf_encoder.writeElement(obj) return pyamf_encoder.stream.getvalue()
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)
def startVersioning(self): """ Start protocol version negotiations. """ self.buffer = BufferedByteStream()
class BaseStreamer(object): """ Provides all the base functionality for handling an RTMP input/output. @ivar decoder: RTMP Decoder that is fed data via L{dataReceived} """ implements(message.IMessageListener) dispatcher = MessageDispatcher @property def decoding(self): """ Whether this streamer is currently decoding RTMP message/s. If all the input buffer has been consumed, this will be C{False}. """ return getattr(self, 'decoding_task', None) is not None @property def encoding(self): """ Whether this streamer is currently encoding RTMP message/s. """ return getattr(self, 'encoding_task', None) is not None def getWriter(self): """ Returns a file like object that provides a I{write} method. This must be provided by subclasses. For example, for L{protocol.Protocol} instances this should return C{self.transport}. """ raise NotImplementedError def buildStreamManager(self): """ Returns an instance that provides L{interfaces.IStreamManager}. This must be provided by subclasses. """ raise NotImplementedError def getDispatcher(self): """ Returns an instance that will provide L{interfaces.IMessageDispatcher} """ return self.dispatcher(self) def bytesInterval(self, bytes): """ """ self.sendMessage(message.BytesRead(bytes), self.controlStream) def startStreaming(self): """ This must be called before any RTMP data is received. """ self.streamManager = self.buildStreamManager() self.controlStream = self.streamManager.getControlStream() self._decodingBuffer = BufferedByteStream() self._encodingBuffer = BufferedByteStream() self.decoder = codec.Decoder(self.getDispatcher(), self.streamManager, stream=self._decodingBuffer) self.encoder = codec.Encoder(self.getWriter(), stream=self._encodingBuffer) self.decoder_task = None self.encoder_task = None def stopStreaming(self, reason=None): """ """ self.streamManager.closeAllStreams() self._decodingBuffer.truncate() self._encodingBuffer.truncate() del self._decodingBuffer del self._encodingBuffer del self.decoder_task, self.decoder del self.encoder_task, self.encoder def dataReceived(self, data): """ Data has been received by the endpoint. """ self.decoder.send(data) if not self.decoding: self.startDecoding() def startDecoding(self): """ Called to start the decoding process. @return: A C{Deferred} which will kill the task once the decoding is done or on error. """ def cullTask(result): self.decoder_task = None return result self.decoder_task = task.coiterate(self.decoder) self.decoder_task.addBoth(cullTask) return self.decoder_task def startEncoding(self): """ Called to start asynchronously iterate the encoder. @return: A C{Deferred} which will kill the task once the encoder is done or on error will kill the connection. @todo: See _startDecoding todo. The same applies here. """ def cullTask(result): self.encoder_task = None return result self.encoder_task = task.coiterate(self.encoder) self.encoder_task.addBoth(cullTask) return self.encoder_task def sendMessage(self, msg, stream, whenDone=None): """ Sends an RTMP message to the peer. Not part of a public api, use C{stream.sendMessage} instead. @param msg: The message being sent to the peer. @type msg: L{message.IMessage} @param stream: The stream instance that is sending the message. @type stream: L{NetStream} @param whenDone: A callback fired when the message has been written to the RTMP stream. See L{BaseStream.sendMessage} """ buf = BufferedByteStream() e = self.encoder # this will probably need to be rethought as this could block for an # unacceptable amount of time. For most messages however it seems to be # fast enough and the penalty for setting up a new thread is too high. msg.encode(buf) e.send(buf.getvalue(), msg.__data_type__, stream.streamId, stream.timestamp, whenDone) if e.active and not self.encoder_task: self.startEncoding() def setFrameSize(self, size): self.sendMessage(message.FrameSize(size), self.controlStream) self.encoder.setFrameSize(size) def getStreamingChannel(self, stream): """ """ return codec.StreamingChannel(self.encoder, stream.streamId, self.getWriter()) def onFrameSize(self, size, timestamp): """ Called when the peer sets its RTMP frame size. @param size: The new size of any RTMP frames sent from the peer. @param timestamp: Time this message was received. """ self.decoder.setFrameSize(size) def onAbort(self, channelId, timestamp): """ Called to abort a channel currently be decoded. """ self.decoder.abort(channelId) def onDownstreamBandwidth(self, interval, timestamp): """ Called when the peer sends its RTMP bytes interval. @param interval: The number of bytes that must be received from the peer before sending an acknowledgement """ self.decoder.setBytesInterval(interval)
def __init__(self): self.buffer = BufferedByteStream()
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 InvokeTestCase(unittest.TestCase): """ Test case for L{fmspy.rtmp.packets.Invoke}. """ data = [ ({ 'header': RTMPHeader(object_id=3, timestamp=0, length=235, type=0x14, stream_id=0L), 'buf': BufferedByteStream( '\x02\x00\x07connect\x00?\xf0\x00\x00\x00\x00\x00\x00\x03\x00\x03app\x02\x00\x04echo\x00\x08flashVer\x02\x00\rLNX 10,0,20,7\x00\x06swfUrl\x06\x00\x05tcUrl\x02\x00\x15rtmp://localhost/echo\x00\x04fpad\x01\x00\x00\x0ccapabilities\x00@.\x00\x00\x00\x00\x00\x00\x00\x0baudioCodecs\x00@\xa8\xee\x00\x00\x00\x00\x00\x00\x0bvideoCodecs\x00@o\x80\x00\x00\x00\x00\x00\x00\rvideoFunction\x00?\xf0\x00\x00\x00\x00\x00\x00\x00\x07pageUrl\x06\x00\x0eobjectEncoding\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t' ), }, Invoke(name=u'connect', argv=({ 'videoCodecs': 252, 'audioCodecs': 3191, 'flashVer': u'LNX 10,0,20,7', 'app': u'echo', 'tcUrl': u'rtmp://localhost/echo', 'videoFunction': 1, 'capabilities': 15, 'pageUrl': pyamf.Undefined, 'fpad': False, 'swfUrl': pyamf.Undefined, 'objectEncoding': 0 }, ), id=1, header=RTMPHeader(object_id=3, timestamp=0, length=235, type=0x14, stream_id=0L)), False), ({ 'header': RTMPHeader(object_id=3, timestamp=0, length=0, type=0x14, stream_id=0L), 'buf': BufferedByteStream( '\x02\x00\x07destroy\x00@@\x80\x00\x00\x00\x00\x00\x03\x00\x0bvideoCodecs\x00@o\x80\x00\x00\x00\x00\x00\x00\x00\t' ), }, Invoke(name=u'destroy', argv=({ 'videoCodecs': 252 }, ), id=33, header=RTMPHeader(object_id=3, timestamp=0, length=0, type=0x14, stream_id=0L)), True), ] def test_eq(self): self.failUnlessEqual( Invoke(name='a', argv=(), id=35.0, header=RTMPHeader(object_id=3)), Invoke(name='a', argv=(), id=35.0, header=RTMPHeader(object_id=3))) self.failIfEqual( Invoke(name='a', argv=(), id=35.0, header=RTMPHeader(object_id=3)), Invoke(name='b', argv=(), id=35.0, header=RTMPHeader(object_id=3))) self.failIfEqual( Invoke(name='a', argv=(), id=35.0, header=RTMPHeader(object_id=3)), Invoke(name='a', argv=('a'), id=35.0, header=RTMPHeader(object_id=3))) self.failIfEqual( Invoke(name='a', argv=(), id=35.0, header=RTMPHeader(object_id=3)), Invoke(name='a', argv=(), id=36.0, header=RTMPHeader(object_id=3))) self.failIfEqual( Invoke(name='a', argv=(), id=35.0, header=RTMPHeader(object_id=3)), Invoke(name='a', argv=(), id=35.0, header=RTMPHeader(object_id=4))) def test_repr(self): self.failUnlessEqual( "<Invoke(name=u'destroy', argv=({'videoCodecs': 252},), id=33, header=<RTMPHeader(object_id=3, timestamp=0, length=0, type=0x14, stream_id=0L)>)>", repr( Invoke(name=u'destroy', argv=({ 'videoCodecs': 252 }, ), id=33, header=RTMPHeader(object_id=3, timestamp=0, length=0, type=0x14, stream_id=0L)))) def test_read(self): for fixture in self.data: fixture[0]['buf'].seek(0) self.failUnlessEqual(fixture[1], Invoke.read(**fixture[0])) def test_write(self): for fixture in self.data: if not fixture[2]: continue fixture[0]['buf'].seek(0) self.failUnlessEqual(fixture[0]['buf'].read(), fixture[1].write())
class StateEngine(BaseStreamer): """ There are three stages to the protocol negotiations before RTMP message streaming can begin. Stage 1 - Version negotiation:: The first byte in any RTMP connection is the I{protocol version} byte. This allows endpoints to negotiate which version of the protocol to proceed with. Valid values appear to be:: - C{0x03}: plain old RTMP this is the baseline protocol that all implementations should default to. - C{0x06}: Used to signify an RTMPE connection. There is another (C{0x08}) but not documented at this time because how/why it is used is unclear. L{versionSuccess} must be called to move on to stage 2. Stage 2 - Handshake negotiations:: The next 1536 * 2 bytes is handshaking data. This part is delegated to a handshake negotiator, which is based on the protocol version. L{handshakeSuccess} must be called to move on to stage 3. Stage 3 - RTMP Message streaming:: Some docstring here. @ivar state: The state of the protocol. """ STATE_VERSION = 'version' STATE_HANDSHAKE = 'handshake' STATE_STREAM = 'stream' state = None protocolVersion = 3 def connectionMade(self): """ Called when this a connection has been made. """ self.state = self.STATE_VERSION self.startVersioning() def connectionLost(self, reason): """ Called when the connection has been lost. @param reason: The reason for the disconnection """ if self.state == self.STATE_VERSION: self.stopVersioning(reason) elif self.state == self.STATE_HANDSHAKE: self.stopHandshaking(reason) elif self.state == self.STATE_STREAM: self.stopStreaming(reason) def dataReceived(self, data): """ """ if self.state == self.STATE_VERSION: self.version_dataReceived(data) elif self.state == self.STATE_HANDSHAKE: self.handshake_dataReceived(data) elif self.state == self.STATE_STREAM: BaseStreamer.dataReceived(self, data) else: raise RuntimeError('Invalid state!') def startVersioning(self): """ Start protocol version negotiations. """ self.buffer = BufferedByteStream() def stopVersioning(self, reason=None): """ Stop protocol version negotiations. @param reason: A L{failure.Failure} object if protocol version negotiations failed. C{None} means success. """ del self.buffer def version_dataReceived(self, data): """ """ if not data: return self.buffer.append(data) self.peerProtocolVersion = self.buffer.read_uchar() self.versionReceived(self.peerProtocolVersion) def versionReceived(self, version): """ Called when the peers' protocol version has been received. The default behaviour is to accept any known version. It is the responsibility for any overriding subclass to call L{versionSuccess} for negotiations to proceed. """ if version == self.protocolVersion: self.versionSuccess() return raise UnknownProtocolVersion('Unhandled protocol version %d' % (version, )) def versionSuccess(self): """ Protocol version negotiations have been successful, now on to handshaking. """ try: data = self.buffer.read() except IOError: data = None self.stopVersioning() self.state = self.STATE_HANDSHAKE self.startHandshaking() if data: # any data that was left over from version negotiations is # artificially re-inserted back into the protocol because the # `state` has changed. self.dataReceived(data) def buildHandshakeNegotiator(self): """ """ raise NotImplementedError def startHandshaking(self): """ """ self.handshaker = self.buildHandshakeNegotiator() # TODO: apply uptime, version to the handshaker instead of 0, 0 self.handshaker.start(0, 0) def stopHandshaking(self, reason=None): """ """ del self.handshaker def handshake_dataReceived(self, data): """ """ self.handshaker.dataReceived(data) def handshakeSuccess(self, data): """ Handshaking was successful, streaming now commences. """ #data = self.handshaker.getRemainingData() self.stopHandshaking() self.state = self.STATE_STREAM self.startStreaming() if data: self.dataReceived(data) def startStreaming(self): """ Because Python is awesome we can short circuit checking state each time L{dataReceived} is called. """ self.dataReceived = lambda x: BaseStreamer.dataReceived(self, x) return BaseStreamer.startStreaming(self)
def __init__(self, channelId, stream, frameSize): BaseChannel.__init__(self, channelId, stream, frameSize) self.buffer = BufferedByteStream() self.acquired = False
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()
def test_read(self): for fixture in self.data: self.failUnlessEqual(fixture[1], RTMPHeader.read(BufferedByteStream(fixture[0])))
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()
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
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 __init__(self, channelId, stream, frameSize): BaseChannel.__init__(self, channelId, stream, frameSize) self.buffer = BufferedByteStream() self.acquired = False self.callback = None
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)
def __init__(self, deferred): self.deferred = deferred self.s = BufferedByteStream()
def setUp(self): self.buffer = BufferedByteStream() self.listener = MockMessageListener()
def __init__(self): self.buffer = BufferedByteStream() self.channels = {} self.frameSize = FRAME_SIZE self.bytes = 0
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()
def setUp(self): self.output = BufferedByteStream() self.encoder = codec.Encoder(self.output)
def loads(data): """returns a list of messages""" stream = BufferedByteStream(data) result = list(pyamf.decode(stream=stream, encoding=3)) stream.close() return result
class BaseStreamer(object): """ Provides all the base functionality for handling an RTMP input/output. @ivar decoder: RTMP Decoder that is fed data via L{dataReceived} """ implements(message.IMessageListener) dispatcher = MessageDispatcher @property def decoding(self): """ Whether this streamer is currently decoding RTMP message/s. If all the input buffer has been consumed, this will be C{False}. """ return getattr(self, 'decoding_task', None) is not None @property def encoding(self): """ Whether this streamer is currently encoding RTMP message/s. """ return getattr(self, 'encoding_task', None) is not None def getWriter(self): """ Returns a file like object that provides a I{write} method. This must be provided by subclasses. For example, for L{protocol.Protocol} instances this should return C{self.transport}. """ raise NotImplementedError def buildStreamManager(self): """ Returns an instance that provides L{interfaces.IStreamManager}. This must be provided by subclasses. """ raise NotImplementedError def getDispatcher(self): """ Returns an instance that will provide L{interfaces.IMessageDispatcher} """ return self.dispatcher(self) def bytesInterval(self, bytes): """ """ self.sendMessage(message.BytesRead(bytes), self.controlStream) def startStreaming(self): """ This must be called before any RTMP data is received. """ self.streamManager = self.buildStreamManager() self.controlStream = self.streamManager.getControlStream() self._decodingBuffer = BufferedByteStream() self._encodingBuffer = BufferedByteStream() self.decoder = codec.Decoder(self.getDispatcher(), self.streamManager, stream=self._decodingBuffer) self.encoder = codec.Encoder(self.getWriter(), stream=self._encodingBuffer) self.decoder_task = None self.encoder_task = None def stopStreaming(self, reason=None): """ """ self.streamManager.closeAllStreams() self._decodingBuffer.truncate() self._encodingBuffer.truncate() del self._decodingBuffer del self._encodingBuffer del self.decoder_task, self.decoder del self.encoder_task, self.encoder def dataReceived(self, data): """ Data has been received by the endpoint. """ self.decoder.send(data) if not self.decoding: self.startDecoding() def startDecoding(self): """ Called to start the decoding process. @return: A C{Deferred} which will kill the task once the decoding is done or on error. """ def cullTask(result): self.decoder_task = None return result self.decoder_task = task.coiterate(self.decoder) self.decoder_task.addBoth(cullTask) return self.decoder_task def startEncoding(self): """ Called to start asynchronously iterate the encoder. @return: A C{Deferred} which will kill the task once the encoder is done or on error will kill the connection. @todo: See _startDecoding todo. The same applies here. """ def cullTask(result): self.encoder_task = None return result self.encoder_task = task.coiterate(self.encoder) self.encoder_task.addBoth(cullTask) return self.encoder_task def sendMessage(self, msg, stream, whenDone=None): """ Sends an RTMP message to the peer. Not part of a public api, use C{stream.sendMessage} instead. @param msg: The message being sent to the peer. @type msg: L{message.IMessage} @param stream: The stream instance that is sending the message. @type stream: L{NetStream} @param whenDone: A callback fired when the message has been written to the RTMP stream. See L{BaseStream.sendMessage} """ buf = BufferedByteStream() e = self.encoder # this will probably need to be rethought as this could block for an # unacceptable amount of time. For most messages however it seems to be # fast enough and the penalty for setting up a new thread is too high. msg.encode(buf) e.send(buf.getvalue(), msg.__data_type__, stream.streamId, stream.timestamp, whenDone) if e.active and not self.encoder_task: self.startEncoding() def setFrameSize(self, size): self.sendMessage(message.FrameSize(size)) self.encoder.setFrameSize(size) def getStreamingChannel(self, stream): """ """ channel = self.encoder.acquireChannel() if channel is None: # todo: make this better raise RuntimeError('No streaming channel available') return codec.StreamingChannel(channel, stream.streamId, self.getWriter()) def onFrameSize(self, size, timestamp): """ Called when the peer sets its RTMP frame size. @param size: The new size of any RTMP frames sent from the peer. @param timestamp: Time this message was received. """ self.decoder.setFrameSize(size) def onDownstreamBandwidth(self, interval, timestamp): """ Called when the peer sends its RTMP bytes interval. @param interval: The number of bytes that must be received from the peer before sending an acknowledgement """ self.decoder.setBytesInterval(interval)
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()
def __init__(self, stream=None): self.stream = stream or BufferedByteStream() self.channels = {} self.frameSize = FRAME_SIZE self.bytes = 0
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 StateEngine(BaseStreamer): """ There are three stages to the protocol negotiations before RTMP message streaming can begin. Stage 1 - Version negotiation:: The first byte in any RTMP connection is the I{protocol version} byte. This allows endpoints to negotiate which version of the protocol to proceed with. Valid values appear to be:: - C{0x03}: plain old RTMP this is the baseline protocol that all implementations should default to. - C{0x06}: Used to signify an RTMPE connection. There is another (C{0x08}) but not documented at this time because how/why it is used is unclear. L{versionSuccess} must be called to move on to stage 2. Stage 2 - Handshake negotiations:: The next 1536 * 2 bytes is handshaking data. This part is delegated to a handshake negotiator, which is based on the protocol version. L{handshakeSuccess} must be called to move on to stage 3. Stage 3 - RTMP Message streaming:: Some docstring here. @ivar state: The state of the protocol. """ STATE_VERSION = 'version' STATE_HANDSHAKE = 'handshake' STATE_STREAM = 'stream' state = None protocolVersion = 3 def connectionMade(self): """ Called when this a connection has been made. """ self.state = self.STATE_VERSION self.startVersioning() def connectionLost(self, reason): """ Called when the connection has been lost. @param reason: The reason for the disconnection """ if self.state == self.STATE_VERSION: self.stopVersioning(reason) elif self.state == self.STATE_HANDSHAKE: self.stopHandshaking(reason) elif self.state == self.STATE_STREAM: self.stopStreaming(reason) def dataReceived(self, data): """ """ if self.state == self.STATE_VERSION: self.version_dataReceived(data) elif self.state == self.STATE_HANDSHAKE: self.handshake_dataReceived(data) elif self.state == self.STATE_STREAM: BaseStreamer.dataReceived(self, data) else: raise RuntimeError('Invalid state!') def startVersioning(self): """ Start protocol version negotiations. """ self.buffer = BufferedByteStream() def stopVersioning(self, reason=None): """ Stop protocol version negotiations. @param reason: A L{failure.Failure} object if protocol version negotiations failed. C{None} means success. """ del self.buffer def version_dataReceived(self, data): """ """ if not data: return self.buffer.append(data) self.peerProtocolVersion = self.buffer.read_uchar() self.versionReceived(self.peerProtocolVersion) def versionReceived(self, version): """ Called when the peers' protocol version has been received. The default behaviour is to accept any known version. It is the responsibility for any overriding subclass to call L{versionSuccess} for negotiations to proceed. """ if version == self.protocolVersion: self.versionSuccess() return raise UnknownProtocolVersion( 'Unhandled protocol version %d' % (version,)) def versionSuccess(self): """ Protocol version negotiations have been successful, now on to handshaking. """ try: data = self.buffer.read() except IOError: data = None self.stopVersioning() self.state = self.STATE_HANDSHAKE self.startHandshaking() if data: # any data that was left over from version negotiations is # artificially re-inserted back into the protocol because the # `state` has changed. self.dataReceived(data) def buildHandshakeNegotiator(self): """ """ raise NotImplementedError def startHandshaking(self): """ """ self.handshaker = self.buildHandshakeNegotiator() # TODO: apply uptime, version to the handshaker instead of 0, 0 self.handshaker.start(0, 0) def stopHandshaking(self, reason=None): """ """ del self.handshaker def handshake_dataReceived(self, data): """ """ self.handshaker.dataReceived(data) def handshakeSuccess(self, data): """ Handshaking was successful, streaming now commences. """ #data = self.handshaker.getRemainingData() self.stopHandshaking() self.state = self.STATE_STREAM self.startStreaming() if data: self.dataReceived(data) def startStreaming(self): """ Because Python is awesome we can short circuit checking state each time L{dataReceived} is called. """ self.dataReceived = lambda x: BaseStreamer.dataReceived(self, x) return BaseStreamer.startStreaming(self)
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
def setUp(self): self.context = pyamf.get_context(self.amf_version) self.stream = BufferedByteStream()