root/cygnal/libnet/rtmp.h

/* [<][>][^][v][top][bottom][index][help] */

INCLUDED FROM


// 
//   Copyright (C) 2006, 2007, 2008, 2009, 2010,
//   2011 Free Software Foundation, Inc
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

#ifndef GNASH_LIBNET_RTMP_H
#define GNASH_LIBNET_RTMP_H

#include <deque>
#include <boost/cstdint.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/lexical_cast.hpp>
#include <string>
#include <vector>
#include <time.h>

#include "amf.h"
#include "element.h"
#include "network.h"
#include "buffer.h"
#include "rtmp_msg.h"
#include "cque.h"
#include "dsodefs.h"
#include "utility.h"

namespace gnash
{

/// \page RTMP RTMP Protocol
/// \section rtmp_handshake RTMP Handshake
///     The headers and data for the initial RTMP handshake differ from
///     what is used by RTMP during normal message processing. The layout
///     is this:
/// <pre>
///     [version (1 byte)]
///     [1st timestamp (4 bytes)]
///     [2nd timestamp (4 bytes)]
///     [1528 bytes of random data]
/// </pre>
///
///     The handshake process is client sends a handhsake request to the
///     server. This is 1537 bytes (version + handshake).
///     The server then responds with a 3073 byte packet, which is the
///     version byte plus two 1536 byte packets, the second one being the
///     the same random data as we originally sent, but with the header
///     changed.
///
/// \section rtmp_packet RTMP Packet
///     An RTMP packet has a variablew length header field, followed by data
///     encoded in AMF format. The header can be one of 1, 4, 8, or 12 bytes.
///     


/// \var RTMP_HANDSHAKE_VERSION_SIZE
///     The RTMP version field of the handshake is 1 byte large
const int  RTMP_HANDSHAKE_VERSION_SIZE = 1;
/// \var RTMP_VERSION
///     The RTMP version number for now is always a 3.
const boost::uint8_t RTMP_VERSION = 0x3;
/// \var
///    This is the total size of an RTMP packet, not including the
///    version field.
const int  RTMP_HANDSHAKE_SIZE  = 1536;
/// \var
///    This is the size of the random data section in the RTMP handshake
const int  RTMP_RANDOM_SIZE     = 1528;
/// \var
///    The RTMP handshake header if always 2 32bit words. (8 bytes)
const int  RTMP_HANDSHAKE_HEADER_SIZE = 8;

/// \var
///    This is the maximum number of channels supported in a single
///    RTMP connection.
const int  MAX_AMF_INDEXES      = 64;

/// \par RTMP Header
///
/// \var 
///     This is a mask to get to the upper 2 bits of the header
const int  RTMP_HEADSIZE_MASK   = 0xc0;
/// \var 
///     This is a mask to get to the lower 6 bits of the header
const char RTMP_INDEX_MASK      = 0x3f;
/// \var 
///     All video data packets are 128 bytes
const int  RTMP_VIDEO_PACKET_SIZE = 128;
/// \var 
///     All audio data packets are 64 bytes
const int  RTMP_AUDIO_PACKET_SIZE = 64;
/// \var 
///     While the RTMP header can be one of 4 sizes, this is the
///     maximum size used
const int  RTMP_MAX_HEADER_SIZE = 12;
/// \var 
///     All System Ping messages are 6 bytes
const int  PING_MSG_SIZE        = 6;
/// \var 
///     This is a reserved channel for system messages
const int  RTMP_SYSTEM_CHANNEL = 2;

// For terminating sequences, a byte with value 0x09 is used.
const char TERMINATOR = 0x09;

// Each packet consists of the following:
//
// The first byte of the AMF file/stream is believed to be a version
// indicator. So far the only valid value for this field that has been
// found is 0x00. If it is anything other than 0x00 (zero), your
// system should consider the AMF file/stream to be
// 'malformed'd. This can happen in the IDE if AMF calls are put
// on the stack but never executed and the user exits the movie from the
// IDE; the two top bytes will be random and the number of headers will
// be unreliable.

// The third and fourth bytes form an integer value that specifies the
// number of headers.
typedef struct {
    boost::uint8_t version;
    boost::uint8_t source;
    boost::uint32_t  count;
} amfpacket_t;

typedef enum {
    onStatus,
    onResult,
    onDebugEvents
} amfresponse_e;

class DSOEXPORT RTMP : public Network
{
public:
    typedef std::map<const char*, cygnal::Element> AMFProperties;
    typedef std::deque<CQue *> queues_t;
    typedef enum {
        ENCODE_AMF0=0x0,
        ENCODE_AMF3=0x3
    } encoding_types_e;
    typedef enum {
        RAW     = 0x0001,
        ADPCM   = 0x0002,
        MP3     = 0x0004,
        INTEL   = 0x0005,
        CELT    = 0x0008,               // unique to Gnash
        NELLY8  = 0x0020,
        NELLY   = 0x0040,
        G711A   = 0x0080,
        G711U   = 0x0100,
        NELLY16 = 0x0200,
        AAC     = 0x0400,
        SPEEX   = 0x0800,
        DEFAULT_AUDIO_SET = 0x0267,
        ALLAUDIO = 0x0fff
    } audiocodecs_e;
    typedef enum {
        UNUSED   = 0x0001,
        JPEG     = 0x0002,
        SORENSON = 0x4,
        ADOBE    = 0x0008,
        VP6      = 0x0010,
        VP6ALPHA = 0x0020,
        SCREEN2  = 0x0040,
        H264     = 0x0080,
        DEFAULT_VIDEO_SET = 0x007c,
        ALLVIDEO = 0x00ff
    } videocodecs_e;
    typedef enum {
        SEEK = 0x1,
        AMF0 = 0x0,
        AMF3 = 0x3
    } videofunction_e;
    // The second byte of the AMF file/stream is appears to be 0x00 if the
    // client is the Flash Player and 0x01 if the client is the FlashCom
    // server.
    typedef enum {
        NONE         = 0x0,
        CHUNK_SIZE   = 0x1,
        ABORT        = 0x2,
        BYTES_READ   = 0x3,
        USER         = 0x4,
        WINDOW_SIZE  = 0x5,
        SET_BANDWITH = 0x6,
        ROUTE        = 0x7,
        AUDIO_DATA   = 0x8,
        VIDEO_DATA   = 0x9,
        SHARED_OBJ   = 0xa,
        AMF3_NOTIFY  = 0xf,
        AMF3_SHARED_OBJ = 0x10,
        AMF3_INVOKE  = 0x11,
        NOTIFY       = 0x12,
        INVOKE       = 0x14,
        FLV_DATA     = 0x16
    } content_types_e;
    typedef enum {
        STREAM_START  = 0x0,
        STREAM_EOF    = 0x1,
        STREAM_NODATA = 0x2,
        STREAM_BUFFER = 0x3,
        STREAM_LIVE   = 0x4,
        STREAM_PING   = 0x6,
        STREAM_PONG   = 0x7
    } user_control_e;
    typedef enum {
         CREATE_OBJ     = 0x1,      // Client sends event
         DELETE_OBJ     = 0x2,      // Client sends event
         REQUEST_CHANGE = 0x3,      // Client sends event
         CHANGE         = 0x4,      // Server sends event
         SUCCESS_CLIENT = 0x5,      // Server sends event
         SEND_MESSAGE   = 0x6,      // Client sends event
         STATUS         = 0x7,      // Server sends evetn
         CLEAR          = 0x8,      // Server sends event
         DELETE_SLOT    = 0x9,      // Server sends event
         REQUEST_DELETE_SLOT = 0xa, // Client sends event
         SUCCESS_SERVER = 0xb       // Server sends event
     } sharedobj_types_e;
    typedef enum {
        PING_CLEAR  = 0x0,      // clear the stream
        PING_PLAY   = 0x1,      // clear the playing buffer
        PING_TIME   = 0x3,      // Buffer time in milliseconds
        PING_RESET  = 0x4,      // Reset stream
        PING_CLIENT = 0x6,      // Ping the client from the server
        PONG_CLIENT = 0x7       // pong reply from client to server
    } rtmp_ping_e;
    typedef enum {
        STREAM_PLAY,            // play the existing stream
        STREAM_PAUSE,           // pause the existing stream
        STREAM_PUBLISH,         // publish the existing stream
        STREAM_STOP,            // stop the existing stream
        STREAM_SEEK             // seek in the existing stream
    } rtmp_op_e;
    typedef struct {
        rtmp_ping_e type;       // the type of the ping message
        boost::uint16_t target; // all Ping message data fields
        boost::uint16_t param1; // are 2 bytes long
        boost::uint16_t param2;
        boost::uint16_t param3;
    } rtmp_ping_t;
    typedef struct {
        user_control_e type;
        boost::uint32_t param1;
        boost::uint32_t param2; // only used by 
    } user_event_t;
    typedef enum {
        RTMP_STATE_HANDSHAKE_SEND,
        RTMP_STATE_HANDSHAKE_RECV,
        RTMP_STATE_HANDSHAKE_ACK,
        RTMP_STATE_CONNECT,
        RTMP_STATE_NETCONNECT,
        RTMP_STATE_NETSTREAM,
        RTMP_STATE_HEADER,
        RTMP_STATE_DONE
    } rtmp_state_t;
//     typedef struct {
//      rtmp_status_e status;
//      std::string   method;
//      double        streamid;
//      std::vector<boost::shared_ptr<cygnal::Element> > objs;
//     } rtmp_msg_t;
    typedef enum {
        RTMP_ERR_UNDEF,
        RTMP_ERR_NOTFOUND,
        RTMP_ERR_PERM,
        RTMP_ERR_DISKFULL,
        RTMP_ERR_ILLEGAL,
        RTMP_ERR_UNKNOWNID,
        RTMP_ERR_EXISTS,
        RTMP_ERR_NOSUCHUSER,
        RTMP_ERR_TIMEOUT,
        RTMP_ERR_NORESPONSE
    } rtmp_error_t;

// Each header consists of the following:
// a single byte that is the index of the RTMP channel,
// then two bits that's a flag to note the size of the header,
// which can be 1, 4, 8, or 12 bytes long.
    
// More info at http://wiki.gnashdev.org/RTMP
    typedef struct {
        int             channel;
        int             head_size;
        int             bodysize;
        RTMPMsg::rtmp_source_e   src_dest;
        content_types_e type;
    } rtmp_head_t;
    typedef struct {
        boost::uint32_t uptime;
        boost::uint8_t version[4];
    } rtmp_handshake_head_t;
    typedef enum {
        HEADER_12 = 0x0,
        HEADER_8  = 0x40,
        HEADER_4  = 0x80,
        HEADER_1  = 0xc0
    } rtmp_headersize_e;    
    
// Each body consists of the following:
//
// * UTF String - Target
// * UTF String - Response
// * Long - Body length in bytes
// * Variable - Actual data (including a type code)
//     typedef struct {
//         amf::amfutf8_t target;
//         amf::amfutf8_t response;
//      boost::uint32_t length;
//         void *data;
//     } rtmp_body_t;
    
    RTMP();
    virtual ~RTMP();

    // Decode
    boost::shared_ptr<rtmp_head_t> decodeHeader(boost::uint8_t *header);
    boost::shared_ptr<rtmp_head_t> decodeHeader(cygnal::Buffer &data);
    
    boost::shared_ptr<cygnal::Buffer> encodeHeader(int amf_index,
                                        rtmp_headersize_e head_size,
                                        size_t total_size, content_types_e type,
                                        RTMPMsg::rtmp_source_e routing);
    boost::shared_ptr<cygnal::Buffer> encodeHeader(int amf_index,
                                                rtmp_headersize_e head_size);
    
    void addProperty(cygnal::Element &el);
    void addProperty(char *name, cygnal::Element &el);
    void addProperty(std::string &name, cygnal::Element &el);
    cygnal::Element &getProperty(const std::string &name);
//     void setHandler(Handler *hand) { _handler = hand; };
    int headerSize(boost::uint8_t header);

    rtmp_head_t *getHeader()    { return &_header; };
    int getHeaderSize()         { return _header.head_size; }; 
    int getTotalSize()          { return _header.bodysize; }; 
    RTMPMsg::rtmp_source_e getRouting()  { return _header.src_dest; };
    int getChannel()            { return _header.channel; };
    int getPacketSize()         { return _packet_size; };
    int getMysteryWord()        { return _mystery_word; };

    // Decode an RTMP message
    boost::shared_ptr<RTMPMsg> decodeMsgBody(boost::uint8_t *data, size_t size);
    boost::shared_ptr<RTMPMsg> decodeMsgBody(cygnal::Buffer &buf);
    
    virtual boost::shared_ptr<rtmp_ping_t> decodePing(boost::uint8_t *data);
    boost::shared_ptr<rtmp_ping_t> decodePing(cygnal::Buffer &buf);
    
    virtual boost::shared_ptr<user_event_t> decodeUserControl(boost::uint8_t *data);
    boost::shared_ptr<user_event_t> decodeUserControl(cygnal::Buffer &buf);
    virtual boost::shared_ptr<cygnal::Buffer> encodeUserControl(user_control_e, boost::uint32_t data);
    
    
    // These are handlers for the various types
    virtual boost::shared_ptr<cygnal::Buffer> encodeChunkSize(int size);
    virtual void decodeChunkSize();

    virtual boost::shared_ptr<cygnal::Buffer> encodeBytesRead();
    virtual void decodeBytesRead();
    virtual boost::shared_ptr<cygnal::Buffer> encodeServer();
    virtual void decodeServer();
    
    virtual boost::shared_ptr<cygnal::Buffer> encodeClient();
    virtual void decodeClient();
    
    virtual boost::shared_ptr<cygnal::Buffer> encodeAudioData();
    virtual void decodeAudioData();
    
    virtual boost::shared_ptr<cygnal::Buffer> encodeVideoData();
    virtual void decodeVideoData();
    
    virtual boost::shared_ptr<cygnal::Buffer> encodeNotify();
    virtual void decodeNotify();
    
    virtual boost::shared_ptr<cygnal::Buffer> encodeSharedObj();
    virtual void decodeSharedObj();
    
    virtual boost::shared_ptr<cygnal::Buffer> encodeInvoke();
    virtual void decodeInvoke();

    // Receive a message, which is a series of AMF elements, seperated
    // by a one byte header at regular byte intervals. (128 bytes for
    // video data by default). Each message may contain multiple packets.
    boost::shared_ptr<cygnal::Buffer> recvMsg();
    boost::shared_ptr<cygnal::Buffer> recvMsg(int fd);

    // Send a message, usually a single ActionScript object. This message
    // may be broken down into a series of packets on a regular byte
    // interval. (128 bytes for video data by default). Each message main
    // contain multiple packets.
    bool sendMsg(cygnal::Buffer &data);
    bool sendMsg(int channel, rtmp_headersize_e head_size,
              size_t total_size, content_types_e type,
              RTMPMsg::rtmp_source_e routing, cygnal::Buffer &data);
    bool sendMsg(int fd, int channel, rtmp_headersize_e head_size,
              size_t total_size, content_types_e type,
              RTMPMsg::rtmp_source_e routing, cygnal::Buffer &data);
    bool sendMsg(int channel, rtmp_headersize_e head_size,
                 size_t total_size, content_types_e type,
                 RTMPMsg::rtmp_source_e routing, boost::uint8_t *data, size_t size);
    bool sendMsg(int fd, int channel, rtmp_headersize_e head_size,
                 size_t total_size, content_types_e type,
                 RTMPMsg::rtmp_source_e routing, boost::uint8_t *data, size_t size);
    
#if 0
    // Send a Msg, and expect a response back of some kind.
    RTMPMsg *sendRecvMsg(int amf_index, rtmp_headersize_e head_size,
                              size_t total_size, content_types_e type,
                              RTMPMsg::rtmp_source_e routing, cygnal::Buffer &buf);
#endif
    // Split a large buffer into multiple smaller ones of the default chunksize
    // of 128 bytes. We read network data in big chunks because it's more efficient,
    // but RTMP uses a weird scheme of a standard header, and then every chunksize
    // bytes another 1 byte RTMP header. The header itself is not part of the byte
    // count.
    boost::shared_ptr<queues_t> split(cygnal::Buffer &buf);
    boost::shared_ptr<queues_t> split(boost::uint8_t *data, size_t size);

    CQue &operator[] (size_t x) { return _queues[x]; }

    /// \method getTime
    ///    The time on most systems these days is a 64 bit long, but swf
    ///    is old, so it only uses a 32 bit integer instead. We know casting
    ///    looses precision, but that's just the way it is in RTMP.
    boost::uint32_t getTime() {
        time_t t;
        time(&t);
        return boost::lexical_cast<boost::uint32_t>(t);
    };

    void dump();
  protected:
    AMFProperties _properties;
    cygnal::Buffer      *_handshake;
//     Handler  *_handler;
    rtmp_head_t _header;
    int         _packet_size;
    int         _mystery_word;
    size_t      _chunksize[MAX_AMF_INDEXES];
    size_t      _lastsize[MAX_AMF_INDEXES];
    std::vector<size_t> _bodysize;
    std::vector<content_types_e> _type;
    int         _timeout;
    CQue        _queues[MAX_AMF_INDEXES];
//    queues_t    _channels;
    cygnal::Buffer      _buffer;
    rtmp_handshake_head_t _handshake_header;
};

} // end of gnash namespace
// end of _RTMP_H_
#endif

// local Variables:
// mode: C++
// indent-tabs-mode: t
// End:


/* [<][>][^][v][top][bottom][index][help] */