Skip to content
Snippets Groups Projects
rtmpproto.c 55.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * RTMP network protocol
     * Copyright (c) 2009 Kostya Shishkov
     *
    
     * This file is part of Libav.
    
     * Libav is free software; you can redistribute it and/or
    
     * modify it under the terms of the GNU Lesser General Public
     * License as published by the Free Software Foundation; either
     * version 2.1 of the License, or (at your option) any later version.
     *
    
     * Libav 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
     * Lesser General Public License for more details.
     *
     * You should have received a copy of the GNU Lesser General Public
    
     * License along with Libav; if not, write to the Free Software
    
     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
     */
    
    /**
    
     * RTMP protocol
     */
    
    #include "libavcodec/bytestream.h"
    #include "libavutil/avstring.h"
    
    #include "libavutil/intfloat.h"
    
    #include "libavutil/lfg.h"
    
    #include "libavutil/sha.h"
    #include "avformat.h"
    
    
    #include "network.h"
    
    #include "flv.h"
    #include "rtmp.h"
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
    #include "rtmpcrypt.h"
    
    #include "rtmppkt.h"
    
    #include "url.h"
    
    /** RTMP protocol handler state */
    typedef enum {
        STATE_START,      ///< client has not done anything yet
        STATE_HANDSHAKED, ///< client has performed handshake
    
        STATE_FCPUBLISH,  ///< client FCPublishing stream (for output)
    
        STATE_PLAYING,    ///< client has started receiving multimedia data from server
    
        STATE_PUBLISHING, ///< client has started sending multimedia data to server (for output)
    
        STATE_STOPPED,    ///< the broadcast has been stopped
    
    } ClientState;
    
    
    typedef struct TrackedMethod {
        char *name;
        int id;
    } TrackedMethod;
    
    
    /** protocol handler context */
    typedef struct RTMPContext {
    
        URLContext*   stream;                     ///< TCP stream used in interactions with RTMP server
        RTMPPacket    prev_pkt[2][RTMP_CHANNELS]; ///< packet history used when reading and sending packets
    
        int           in_chunk_size;              ///< size of the chunks incoming RTMP packets are divided into
        int           out_chunk_size;             ///< size of the chunks outgoing RTMP packets are divided into
    
        int           is_input;                   ///< input/output flag
    
        char          *playpath;                  ///< stream identifier to play (with possible "mp4:" prefix)
    
        int           live;                       ///< 0: recorded, -1: live, -2: both
    
        char          *conn;                      ///< append arbitrary AMF data to the Connect message
    
        ClientState   state;                      ///< current state
        int           main_channel_id;            ///< an additional channel ID which is used for some invocations
        uint8_t*      flv_data;                   ///< buffer with data for demuxer
        int           flv_size;                   ///< current buffer size
        int           flv_off;                    ///< number of bytes read from current buffer
    
        int           flv_nb_packets;             ///< number of flv packets published
    
        RTMPPacket    out_pkt;                    ///< rtmp packet, created from flv a/v or metadata (for output)
    
        uint32_t      client_report_size;         ///< number of bytes after which client should report to server
        uint32_t      bytes_read;                 ///< number of bytes read from server
        uint32_t      last_bytes_read;            ///< number of bytes read last reported to server
    
        int           skip_bytes;                 ///< number of bytes to skip from the input FLV stream in the next write call
    
        uint8_t       flv_header[11];             ///< partial incoming flv packet header
        int           flv_header_bytes;           ///< number of initialized bytes in flv_header
    
        int           nb_invokes;                 ///< keeps track of invoke messages
    
        char*         flashver;                   ///< version of the flash plugin
    
        char*         pageurl;                    ///< url of the web page
    
        char*         subscribe;                  ///< name of live stream to subscribe
    
        int           server_bw;                  ///< server bandwidth
    
        int           client_buffer_time;         ///< client buffer time in ms
    
        int           flush_interval;             ///< number of packets flushed in the same request (RTMPT only)
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
        int           encrypted;                  ///< use an encrypted connection (RTMPE only)
    
        TrackedMethod*tracked_methods;            ///< tracked methods buffer
        int           nb_tracked_methods;         ///< number of tracked methods
        int           tracked_methods_size;       ///< size of the tracked methods buffer
    
    } RTMPContext;
    
    #define PLAYER_KEY_OPEN_PART_LEN 30   ///< length of partial key used for first client digest signing
    /** Client key used for digest signing */
    static const uint8_t rtmp_player_key[] = {
        'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
        'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ', '0', '0', '1',
    
        0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02,
        0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8,
        0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
    };
    
    #define SERVER_KEY_OPEN_PART_LEN 36   ///< length of partial key used for first server digest signing
    /** Key used for RTMP server digest signing */
    static const uint8_t rtmp_server_key[] = {
        'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
        'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ',
        'S', 'e', 'r', 'v', 'e', 'r', ' ', '0', '0', '1',
    
        0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02,
        0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8,
        0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
    };
    
    
    static int add_tracked_method(RTMPContext *rt, const char *name, int id)
    {
        void *ptr;
    
        if (rt->nb_tracked_methods + 1 > rt->tracked_methods_size) {
            rt->tracked_methods_size = (rt->nb_tracked_methods + 1) * 2;
            ptr = av_realloc(rt->tracked_methods,
                             rt->tracked_methods_size * sizeof(*rt->tracked_methods));
            if (!ptr)
                return AVERROR(ENOMEM);
            rt->tracked_methods = ptr;
        }
    
        rt->tracked_methods[rt->nb_tracked_methods].name = av_strdup(name);
        if (!rt->tracked_methods[rt->nb_tracked_methods].name)
            return AVERROR(ENOMEM);
        rt->tracked_methods[rt->nb_tracked_methods].id = id;
        rt->nb_tracked_methods++;
    
        return 0;
    }
    
    static void del_tracked_method(RTMPContext *rt, int index)
    {
        memmove(&rt->tracked_methods[index], &rt->tracked_methods[index + 1],
                sizeof(*rt->tracked_methods) * (rt->nb_tracked_methods - index - 1));
        rt->nb_tracked_methods--;
    }
    
    
    static int find_tracked_method(URLContext *s, RTMPPacket *pkt, int offset,
                                   char **tracked_method)
    {
        RTMPContext *rt = s->priv_data;
        GetByteContext gbc;
        double pkt_id;
        int ret;
        int i;
    
        bytestream2_init(&gbc, pkt->data + offset, pkt->data_size - offset);
        if ((ret = ff_amf_read_number(&gbc, &pkt_id)) < 0)
            return ret;
    
        for (i = 0; i < rt->nb_tracked_methods; i++) {
            if (rt->tracked_methods[i].id != pkt_id)
                continue;
    
            *tracked_method = rt->tracked_methods[i].name;
            del_tracked_method(rt, i);
            break;
        }
    
        return 0;
    }
    
    
    static void free_tracked_methods(RTMPContext *rt)
    {
        int i;
    
        for (i = 0; i < rt->nb_tracked_methods; i ++)
            av_free(rt->tracked_methods[i].name);
        av_free(rt->tracked_methods);
    }
    
    static int rtmp_send_packet(RTMPContext *rt, RTMPPacket *pkt, int track)
    {
        int ret;
    
        if (pkt->type == RTMP_PT_INVOKE && track) {
            GetByteContext gbc;
            char name[128];
            double pkt_id;
            int len;
    
            bytestream2_init(&gbc, pkt->data, pkt->data_size);
            if ((ret = ff_amf_read_string(&gbc, name, sizeof(name), &len)) < 0)
                goto fail;
    
            if ((ret = ff_amf_read_number(&gbc, &pkt_id)) < 0)
                goto fail;
    
            if ((ret = add_tracked_method(rt, name, pkt_id)) < 0)
                goto fail;
        }
    
    
        ret = ff_rtmp_packet_write(rt->stream, pkt, rt->out_chunk_size,
    
                                   rt->prev_pkt[1]);
    fail:
        ff_rtmp_packet_destroy(pkt);
        return ret;
    }
    
    
    static int rtmp_write_amf_data(URLContext *s, char *param, uint8_t **p)
    {
    
        char type;
    
        /* The type must be B for Boolean, N for number, S for string, O for
         * object, or Z for null. For Booleans the data must be either 0 or 1 for
         * FALSE or TRUE, respectively. Likewise for Objects the data must be
         * 0 or 1 to end or begin an object, respectively. Data items in subobjects
         * may be named, by prefixing the type with 'N' and specifying the name
         * before the value (ie. NB:myFlag:1). This option may be used multiple times
         * to construct arbitrary AMF sequences. */
        if (param[0] && param[1] == ':') {
            type = param[0];
            value = param + 2;
        } else if (param[0] == 'N' && param[1] && param[2] == ':') {
            type = param[1];
    
            field = param + 3;
            value = strchr(field, ':');
            if (!value)
                goto fail;
            *value = '\0';
            value++;
    
    
            if (!field || !value)
                goto fail;
    
            ff_amf_write_field_name(p, field);
        } else {
            goto fail;
        }
    
        switch (type) {
        case 'B':
            ff_amf_write_bool(p, value[0] != '0');
            break;
        case 'S':
            ff_amf_write_string(p, value);
            break;
        case 'N':
            ff_amf_write_number(p, strtod(value, NULL));
            break;
        case 'Z':
            ff_amf_write_null(p);
            break;
        case 'O':
            if (value[0] != '0')
                ff_amf_write_object_start(p);
            else
                ff_amf_write_object_end(p);
            break;
        default:
            goto fail;
            break;
        }
    
        return 0;
    
    fail:
        av_log(s, AV_LOG_ERROR, "Invalid AMF parameter: %s\n", param);
        return AVERROR(EINVAL);
    }
    
    
     * Generate 'connect' call and send it to the server.
    
    static int gen_connect(URLContext *s, RTMPContext *rt)
    
    {
        RTMPPacket pkt;
    
        int ret;
    
        if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                         0, 4096)) < 0)
            return ret;
    
    
        p = pkt.data;
    
        ff_amf_write_string(&p, "connect");
    
        ff_amf_write_number(&p, ++rt->nb_invokes);
    
        ff_amf_write_object_start(&p);
        ff_amf_write_field_name(&p, "app");
    
        ff_amf_write_string(&p, rt->app);
    
            ff_amf_write_field_name(&p, "type");
            ff_amf_write_string(&p, "nonprivate");
        }
    
        ff_amf_write_field_name(&p, "flashVer");
    
    
        if (rt->swfurl) {
            ff_amf_write_field_name(&p, "swfUrl");
            ff_amf_write_string(&p, rt->swfurl);
        }
    
    
        ff_amf_write_field_name(&p, "tcUrl");
    
            ff_amf_write_field_name(&p, "fpad");
            ff_amf_write_bool(&p, 0);
            ff_amf_write_field_name(&p, "capabilities");
            ff_amf_write_number(&p, 15.0);
    
    
            /* Tell the server we support all the audio codecs except
             * SUPPORT_SND_INTEL (0x0008) and SUPPORT_SND_UNUSED (0x0010)
             * which are unused in the RTMP protocol implementation. */
    
            ff_amf_write_field_name(&p, "audioCodecs");
    
            ff_amf_write_number(&p, 4071.0);
    
            ff_amf_write_field_name(&p, "videoCodecs");
            ff_amf_write_number(&p, 252.0);
            ff_amf_write_field_name(&p, "videoFunction");
            ff_amf_write_number(&p, 1.0);
    
    
            if (rt->pageurl) {
                ff_amf_write_field_name(&p, "pageUrl");
                ff_amf_write_string(&p, rt->pageurl);
            }
    
        ff_amf_write_object_end(&p);
    
    
        if (rt->conn) {
    
    
            // Write arbitrary AMF data to the Connect message.
            while (param != NULL) {
    
                char *sep;
                param += strspn(param, " ");
                if (!*param)
                    break;
                sep = strchr(param, ' ');
                if (sep)
                    *sep = '\0';
    
                if ((ret = rtmp_write_amf_data(s, param, &p)) < 0) {
                    // Invalid AMF parameter.
                    ff_rtmp_packet_destroy(&pkt);
                    return ret;
                }
    
    
        pkt.data_size = p - pkt.data;
    
    
        return rtmp_send_packet(rt, &pkt, 1);
    
     * Generate 'releaseStream' call and send it to the server. It should make
    
     * the server release some channel for media streams.
     */
    
    static int gen_release_stream(URLContext *s, RTMPContext *rt)
    
        if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                         0, 29 + strlen(rt->playpath))) < 0)
            return ret;
    
        av_log(s, AV_LOG_DEBUG, "Releasing stream...\n");
    
        p = pkt.data;
        ff_amf_write_string(&p, "releaseStream");
    
        ff_amf_write_number(&p, ++rt->nb_invokes);
    
        ff_amf_write_null(&p);
        ff_amf_write_string(&p, rt->playpath);
    
    
        return rtmp_send_packet(rt, &pkt, 0);
    
     * Generate 'FCPublish' call and send it to the server. It should make
    
     * the server preapare for receiving media streams.
     */
    
    static int gen_fcpublish_stream(URLContext *s, RTMPContext *rt)
    
        if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                         0, 25 + strlen(rt->playpath))) < 0)
            return ret;
    
        av_log(s, AV_LOG_DEBUG, "FCPublish stream...\n");
    
        p = pkt.data;
        ff_amf_write_string(&p, "FCPublish");
    
        ff_amf_write_number(&p, ++rt->nb_invokes);
    
        ff_amf_write_null(&p);
        ff_amf_write_string(&p, rt->playpath);
    
    
        return rtmp_send_packet(rt, &pkt, 0);
    
     * Generate 'FCUnpublish' call and send it to the server. It should make
    
     * the server destroy stream.
     */
    
    static int gen_fcunpublish_stream(URLContext *s, RTMPContext *rt)
    
        if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                         0, 27 + strlen(rt->playpath))) < 0)
            return ret;
    
        av_log(s, AV_LOG_DEBUG, "UnPublishing stream...\n");
    
        p = pkt.data;
        ff_amf_write_string(&p, "FCUnpublish");
    
        ff_amf_write_number(&p, ++rt->nb_invokes);
    
        ff_amf_write_null(&p);
        ff_amf_write_string(&p, rt->playpath);
    
    
        return rtmp_send_packet(rt, &pkt, 0);
    
     * Generate 'createStream' call and send it to the server. It should make
    
     * the server allocate some channel for media streams.
     */
    
    static int gen_create_stream(URLContext *s, RTMPContext *rt)
    
    {
        RTMPPacket pkt;
        uint8_t *p;
    
        av_log(s, AV_LOG_DEBUG, "Creating stream...\n");
    
    
        if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                         0, 25)) < 0)
            return ret;
    
    
        p = pkt.data;
        ff_amf_write_string(&p, "createStream");
    
        ff_amf_write_number(&p, ++rt->nb_invokes);
    
        return rtmp_send_packet(rt, &pkt, 1);
    
     * Generate 'deleteStream' call and send it to the server. It should make
    
     * the server remove some channel for media streams.
     */
    
    static int gen_delete_stream(URLContext *s, RTMPContext *rt)
    
        av_log(s, AV_LOG_DEBUG, "Deleting stream...\n");
    
    
        if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                         0, 34)) < 0)
            return ret;
    
    
        p = pkt.data;
        ff_amf_write_string(&p, "deleteStream");
    
        ff_amf_write_number(&p, ++rt->nb_invokes);
    
        ff_amf_write_null(&p);
    
        ff_amf_write_number(&p, rt->main_channel_id);
    
        return rtmp_send_packet(rt, &pkt, 0);
    
    /**
     * Generate client buffer time and send it to the server.
     */
    static int gen_buffer_time(URLContext *s, RTMPContext *rt)
    {
        RTMPPacket pkt;
        uint8_t *p;
        int ret;
    
        if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING,
                                         1, 10)) < 0)
            return ret;
    
        p = pkt.data;
        bytestream_put_be16(&p, 3);
        bytestream_put_be32(&p, rt->main_channel_id);
        bytestream_put_be32(&p, rt->client_buffer_time);
    
    
        return rtmp_send_packet(rt, &pkt, 0);
    
     * Generate 'play' call and send it to the server, then ping the server
    
     * to start actual playing.
     */
    
    static int gen_play(URLContext *s, RTMPContext *rt)
    
    {
        RTMPPacket pkt;
        uint8_t *p;
    
        av_log(s, AV_LOG_DEBUG, "Sending play command for '%s'\n", rt->playpath);
    
    
        if ((ret = ff_rtmp_packet_create(&pkt, RTMP_VIDEO_CHANNEL, RTMP_PT_INVOKE,
                                         0, 29 + strlen(rt->playpath))) < 0)
            return ret;
    
    
        pkt.extra = rt->main_channel_id;
    
        p = pkt.data;
        ff_amf_write_string(&p, "play");
    
        ff_amf_write_number(&p, ++rt->nb_invokes);
    
        ff_amf_write_null(&p);
        ff_amf_write_string(&p, rt->playpath);
    
        return rtmp_send_packet(rt, &pkt, 1);
    
     * Generate 'publish' call and send it to the server.
    
    static int gen_publish(URLContext *s, RTMPContext *rt)
    
        av_log(s, AV_LOG_DEBUG, "Sending publish command for '%s'\n", rt->playpath);
    
    
        if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE,
                                         0, 30 + strlen(rt->playpath))) < 0)
            return ret;
    
    
        pkt.extra = rt->main_channel_id;
    
        p = pkt.data;
        ff_amf_write_string(&p, "publish");
    
        ff_amf_write_number(&p, ++rt->nb_invokes);
    
        ff_amf_write_null(&p);
        ff_amf_write_string(&p, rt->playpath);
        ff_amf_write_string(&p, "live");
    
    
        return rtmp_send_packet(rt, &pkt, 1);
    
     * Generate ping reply and send it to the server.
    
    static int gen_pong(URLContext *s, RTMPContext *rt, RTMPPacket *ppkt)
    
    {
        RTMPPacket pkt;
        uint8_t *p;
    
        if (ppkt->data_size < 6) {
            av_log(s, AV_LOG_ERROR, "Too short ping packet (%d)\n",
                   ppkt->data_size);
            return AVERROR_INVALIDDATA;
        }
    
    
        if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING,
                                         ppkt->timestamp + 1, 6)) < 0)
            return ret;
    
    
        p = pkt.data;
        bytestream_put_be16(&p, 7);
    
        bytestream_put_be32(&p, AV_RB32(ppkt->data+2));
    
        return rtmp_send_packet(rt, &pkt, 0);
    
    /**
     * Generate server bandwidth message and send it to the server.
     */
    
    static int gen_server_bw(URLContext *s, RTMPContext *rt)
    
    {
        RTMPPacket pkt;
        uint8_t *p;
    
        int ret;
    
        if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_SERVER_BW,
                                         0, 4)) < 0)
            return ret;
    
        bytestream_put_be32(&p, rt->server_bw);
    
        return rtmp_send_packet(rt, &pkt, 0);
    
    /**
     * Generate check bandwidth message and send it to the server.
     */
    
    static int gen_check_bw(URLContext *s, RTMPContext *rt)
    
    {
        RTMPPacket pkt;
        uint8_t *p;
    
        if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                         0, 21)) < 0)
            return ret;
    
    
        p = pkt.data;
        ff_amf_write_string(&p, "_checkbw");
    
        ff_amf_write_number(&p, RTMP_NOTIFICATION);
    
        ff_amf_write_null(&p);
    
    
        return rtmp_send_packet(rt, &pkt, 0);
    
     * Generate report on bytes read so far and send it to the server.
    
    static int gen_bytes_read(URLContext *s, RTMPContext *rt, uint32_t ts)
    
    {
        RTMPPacket pkt;
        uint8_t *p;
    
        int ret;
    
        if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_BYTES_READ,
                                         ts, 4)) < 0)
            return ret;
    
    
        p = pkt.data;
        bytestream_put_be32(&p, rt->bytes_read);
    
        return rtmp_send_packet(rt, &pkt, 0);
    
    static int gen_fcsubscribe_stream(URLContext *s, RTMPContext *rt,
                                      const char *subscribe)
    
    {
        RTMPPacket pkt;
        uint8_t *p;
        int ret;
    
        if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
    
                                         0, 27 + strlen(subscribe))) < 0)
    
            return ret;
    
        p = pkt.data;
        ff_amf_write_string(&p, "FCSubscribe");
        ff_amf_write_number(&p, ++rt->nb_invokes);
        ff_amf_write_null(&p);
    
        ff_amf_write_string(&p, subscribe);
    
        return rtmp_send_packet(rt, &pkt, 1);
    
    int ff_rtmp_calc_digest(const uint8_t *src, int len, int gap,
                            const uint8_t *key, int keylen, uint8_t *dst)
    
    {
        struct AVSHA *sha;
        uint8_t hmac_buf[64+32] = {0};
        int i;
    
        sha = av_mallocz(av_sha_size);
    
        if (!sha)
            return AVERROR(ENOMEM);
    
    
        if (keylen < 64) {
            memcpy(hmac_buf, key, keylen);
        } else {
            av_sha_init(sha, 256);
            av_sha_update(sha,key, keylen);
            av_sha_final(sha, hmac_buf);
        }
        for (i = 0; i < 64; i++)
            hmac_buf[i] ^= HMAC_IPAD_VAL;
    
        av_sha_init(sha, 256);
        av_sha_update(sha, hmac_buf, 64);
        if (gap <= 0) {
            av_sha_update(sha, src, len);
        } else { //skip 32 bytes used for storing digest
            av_sha_update(sha, src, gap);
            av_sha_update(sha, src + gap + 32, len - gap - 32);
        }
        av_sha_final(sha, hmac_buf + 64);
    
        for (i = 0; i < 64; i++)
            hmac_buf[i] ^= HMAC_IPAD_VAL ^ HMAC_OPAD_VAL; //reuse XORed key for opad
        av_sha_init(sha, 256);
        av_sha_update(sha, hmac_buf, 64+32);
        av_sha_final(sha, dst);
    
        av_free(sha);
    
    
        return 0;
    
    int ff_rtmp_calc_digest_pos(const uint8_t *buf, int off, int mod_val,
                                int add_val)
    {
        int i, digest_pos = 0;
    
        for (i = 0; i < 4; i++)
            digest_pos += buf[i + off];
        digest_pos = digest_pos % mod_val + add_val;
    
        return digest_pos;
    }
    
    
     * Put HMAC-SHA2 digest of packet data (except for the bytes where this digest
    
     * will be stored) into that packet.
     *
     * @param buf handshake data (1536 bytes)
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
     * @param encrypted use an encrypted connection (RTMPE)
    
     * @return offset to the digest inside input data
     */
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
    static int rtmp_handshake_imprint_with_digest(uint8_t *buf, int encrypted)
    
        int ret, digest_pos;
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
        if (encrypted)
            digest_pos = ff_rtmp_calc_digest_pos(buf, 772, 728, 776);
        else
            digest_pos = ff_rtmp_calc_digest_pos(buf, 8, 728, 12);
    
        ret = ff_rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos,
                                  rtmp_player_key, PLAYER_KEY_OPEN_PART_LEN,
                                  buf + digest_pos);
    
        if (ret < 0)
            return ret;
    
    
        return digest_pos;
    }
    
    /**
    
     * Verify that the received server response has the expected digest value.
    
     *
     * @param buf handshake data received from the server (1536 bytes)
     * @param off position to search digest offset from
     * @return 0 if digest is valid, digest position otherwise
     */
    static int rtmp_validate_digest(uint8_t *buf, int off)
    {
        uint8_t digest[32];
    
        int ret, digest_pos;
    
        digest_pos = ff_rtmp_calc_digest_pos(buf, off, 728, off + 4);
    
        ret = ff_rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos,
                                  rtmp_server_key, SERVER_KEY_OPEN_PART_LEN,
                                  digest);
    
        if (ret < 0)
            return ret;
    
    
        if (!memcmp(digest, buf + digest_pos, 32))
            return digest_pos;
        return 0;
    }
    
    /**
    
     * Perform handshake with the server by means of exchanging pseudorandom data
    
     * signed with HMAC-SHA2 digest.
     *
     * @return 0 if handshake succeeds, negative value otherwise
     */
    static int rtmp_handshake(URLContext *s, RTMPContext *rt)
    {
        AVLFG rnd;
        uint8_t tosend    [RTMP_HANDSHAKE_PACKET_SIZE+1] = {
            3,                // unencrypted data
            0, 0, 0, 0,       // client uptime
            RTMP_CLIENT_VER1,
            RTMP_CLIENT_VER2,
            RTMP_CLIENT_VER3,
            RTMP_CLIENT_VER4,
        };
        uint8_t clientdata[RTMP_HANDSHAKE_PACKET_SIZE];
        uint8_t serverdata[RTMP_HANDSHAKE_PACKET_SIZE+1];
        int i;
        int server_pos, client_pos;
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
        uint8_t digest[32], signature[32];
        int ret, type = 0;
    
        av_log(s, AV_LOG_DEBUG, "Handshaking...\n");
    
    
        av_lfg_init(&rnd, 0xDEADC0DE);
        // generate handshake packet - 1536 bytes of pseudorandom data
        for (i = 9; i <= RTMP_HANDSHAKE_PACKET_SIZE; i++)
            tosend[i] = av_lfg_get(&rnd) >> 24;
    
        if (rt->encrypted && CONFIG_FFRTMPCRYPT_PROTOCOL) {
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
            /* When the client wants to use RTMPE, we have to change the command
             * byte to 0x06 which means to use encrypted data and we have to set
             * the flash version to at least 9.0.115.0. */
            tosend[0] = 6;
            tosend[5] = 128;
            tosend[6] = 0;
            tosend[7] = 3;
            tosend[8] = 2;
    
            /* Initialize the Diffie-Hellmann context and generate the public key
             * to send to the server. */
            if ((ret = ff_rtmpe_gen_pub_key(rt->stream, tosend + 1)) < 0)
                return ret;
        }
    
    
        client_pos = rtmp_handshake_imprint_with_digest(tosend + 1, rt->encrypted);
    
        if (client_pos < 0)
            return client_pos;
    
        if ((ret = ffurl_write(rt->stream, tosend,
                               RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) {
            av_log(s, AV_LOG_ERROR, "Cannot write RTMP handshake request\n");
            return ret;
        }
    
    
        if ((ret = ffurl_read_complete(rt->stream, serverdata,
                                       RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) {
    
            av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n");
    
    
        if ((ret = ffurl_read_complete(rt->stream, clientdata,
                                       RTMP_HANDSHAKE_PACKET_SIZE)) < 0) {
    
            av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n");
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
        av_log(s, AV_LOG_DEBUG, "Type answer %d\n", serverdata[0]);
    
        av_log(s, AV_LOG_DEBUG, "Server version %d.%d.%d.%d\n",
    
               serverdata[5], serverdata[6], serverdata[7], serverdata[8]);
    
    
        if (rt->is_input && serverdata[5] >= 3) {
    
            server_pos = rtmp_validate_digest(serverdata + 1, 772);
    
            if (server_pos < 0)
                return server_pos;
    
    
            if (!server_pos) {
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
                type = 1;
    
                server_pos = rtmp_validate_digest(serverdata + 1, 8);
    
                if (server_pos < 0)
                    return server_pos;
    
    
                if (!server_pos) {
    
                    av_log(s, AV_LOG_ERROR, "Server response validating failed\n");
    
                    return AVERROR(EIO);
    
            ret = ff_rtmp_calc_digest(tosend + 1 + client_pos, 32, 0,
                                      rtmp_server_key, sizeof(rtmp_server_key),
                                      digest);
    
            if (ret < 0)
                return ret;
    
    
            ret = ff_rtmp_calc_digest(clientdata, RTMP_HANDSHAKE_PACKET_SIZE - 32,
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
                                      0, digest, 32, signature);
    
            if (ret < 0)
                return ret;
    
    
            if (rt->encrypted && CONFIG_FFRTMPCRYPT_PROTOCOL) {
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
                /* Compute the shared secret key sent by the server and initialize
                 * the RC4 encryption. */
                if ((ret = ff_rtmpe_compute_secret_key(rt->stream, serverdata + 1,
                                                       tosend + 1, type)) < 0)
                    return ret;
    
                /* Encrypt the signature received by the server. */
                ff_rtmpe_encrypt_sig(rt->stream, signature, digest, serverdata[0]);
            }
    
            if (memcmp(signature, clientdata + RTMP_HANDSHAKE_PACKET_SIZE - 32, 32)) {
    
                av_log(s, AV_LOG_ERROR, "Signature mismatch\n");
    
                return AVERROR(EIO);
    
            for (i = 0; i < RTMP_HANDSHAKE_PACKET_SIZE; i++)
                tosend[i] = av_lfg_get(&rnd) >> 24;
    
            ret = ff_rtmp_calc_digest(serverdata + 1 + server_pos, 32, 0,
                                      rtmp_player_key, sizeof(rtmp_player_key),
                                      digest);
    
            if (ret < 0)
                return ret;
    
    
            ret = ff_rtmp_calc_digest(tosend, RTMP_HANDSHAKE_PACKET_SIZE - 32, 0,
                                      digest, 32,
                                      tosend + RTMP_HANDSHAKE_PACKET_SIZE - 32);
    
            if (ret < 0)
                return ret;
    
            if (rt->encrypted && CONFIG_FFRTMPCRYPT_PROTOCOL) {
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
                /* Encrypt the signature to be send to the server. */
                ff_rtmpe_encrypt_sig(rt->stream, tosend +
                                     RTMP_HANDSHAKE_PACKET_SIZE - 32, digest,
                                     serverdata[0]);
            }
    
    
            // write reply back to the server
    
            if ((ret = ffurl_write(rt->stream, tosend,
                                   RTMP_HANDSHAKE_PACKET_SIZE)) < 0)
                return ret;
    
            if (rt->encrypted && CONFIG_FFRTMPCRYPT_PROTOCOL) {
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
                /* Set RC4 keys for encryption and update the keystreams. */
                if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0)
                    return ret;
            }
    
            if (rt->encrypted && CONFIG_FFRTMPCRYPT_PROTOCOL) {
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
                /* Compute the shared secret key sent by the server and initialize
                 * the RC4 encryption. */
                if ((ret = ff_rtmpe_compute_secret_key(rt->stream, serverdata + 1,
                                tosend + 1, 1)) < 0)
                    return ret;
    
                if (serverdata[0] == 9) {
                    /* Encrypt the signature received by the server. */
                    ff_rtmpe_encrypt_sig(rt->stream, signature, digest,
                                         serverdata[0]);
                }
            }
    
    
            if ((ret = ffurl_write(rt->stream, serverdata + 1,
                                   RTMP_HANDSHAKE_PACKET_SIZE)) < 0)
                return ret;
    
            if (rt->encrypted && CONFIG_FFRTMPCRYPT_PROTOCOL) {
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
                /* Set RC4 keys for encryption and update the keystreams. */
                if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0)
                    return ret;
            }
    
    static int handle_chunk_size(URLContext *s, RTMPPacket *pkt)
    {
        RTMPContext *rt = s->priv_data;
        int ret;
    
    
            av_log(s, AV_LOG_ERROR,
    
            return AVERROR_INVALIDDATA;
    
            /* Send the same chunk size change packet back to the server,
             * setting the outgoing chunk size to the same as the incoming one. */
            if ((ret = ff_rtmp_packet_write(rt->stream, pkt, rt->out_chunk_size,
    
                                            rt->prev_pkt[1])) < 0)
                return ret;
    
            rt->out_chunk_size = AV_RB32(pkt->data);
    
        rt->in_chunk_size = AV_RB32(pkt->data);
        if (rt->in_chunk_size <= 0) {
            av_log(s, AV_LOG_ERROR, "Incorrect chunk size %d\n",
                   rt->in_chunk_size);
    
            return AVERROR_INVALIDDATA;
    
        av_log(s, AV_LOG_DEBUG, "New incoming chunk size = %d\n",
               rt->in_chunk_size);
    
    static int handle_ping(URLContext *s, RTMPPacket *pkt)
    {
        RTMPContext *rt = s->priv_data;
        int t, ret;
    
    
        if (pkt->data_size < 2) {
            av_log(s, AV_LOG_ERROR, "Too short ping packet (%d)\n",
                   pkt->data_size);
            return AVERROR_INVALIDDATA;
        }
    
    
        t = AV_RB16(pkt->data);