libavformat/rtmpproto.c
9fd6b843
 /*
  * RTMP network protocol
de421b20
  * Copyright (c) 2009 Konstantin Shishkov
9fd6b843
  *
  * This file is part of FFmpeg.
  *
  * FFmpeg 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.
  *
  * FFmpeg 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 FFmpeg; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
 /**
ba87f080
  * @file
9fd6b843
  * RTMP protocol
  */
 
 #include "libavcodec/bytestream.h"
 #include "libavutil/avstring.h"
08225d01
 #include "libavutil/base64.h"
3383a53e
 #include "libavutil/intfloat.h"
9fd6b843
 #include "libavutil/lfg.h"
08225d01
 #include "libavutil/md5.h"
6465562e
 #include "libavutil/opt.h"
e5f2731c
 #include "libavutil/random_seed.h"
9fd6b843
 #include "libavutil/sha.h"
 #include "avformat.h"
e4a9e3cc
 #include "internal.h"
9fd6b843
 
 #include "network.h"
 
 #include "flv.h"
 #include "rtmp.h"
acd554c1
 #include "rtmpcrypt.h"
9fd6b843
 #include "rtmppkt.h"
0589da0a
 #include "url.h"
9fd6b843
 
93f257db
 #if CONFIG_ZLIB
 #include <zlib.h>
 #endif
 
da8ef5ac
 #define APP_MAX_LENGTH 1024
b3b17512
 #define PLAYPATH_MAX_LENGTH 256
55c9320e
 #define TCURL_MAX_LENGTH 512
e64673e4
 #define FLASHVER_MAX_LENGTH 64
e5f2731c
 #define RTMP_PKTDATA_DEFAULT_SIZE 4096
6465562e
 
9fd6b843
 /** RTMP protocol handler state */
 typedef enum {
     STATE_START,      ///< client has not done anything yet
     STATE_HANDSHAKED, ///< client has performed handshake
6bf22e18
     STATE_FCPUBLISH,  ///< client FCPublishing stream (for output)
9fd6b843
     STATE_PLAYING,    ///< client has started receiving multimedia data from server
6bf22e18
     STATE_PUBLISHING, ///< client has started sending multimedia data to server (for output)
e5f2731c
     STATE_RECEIVING,  ///< received a publish command (for input)
72b870b9
     STATE_STOPPED,    ///< the broadcast has been stopped
9fd6b843
 } ClientState;
 
f89584ca
 typedef struct TrackedMethod {
     char *name;
     int id;
 } TrackedMethod;
 
9fd6b843
 /** protocol handler context */
 typedef struct RTMPContext {
6465562e
     const AVClass *class;
9fd6b843
     URLContext*   stream;                     ///< TCP stream used in interactions with RTMP server
     RTMPPacket    prev_pkt[2][RTMP_CHANNELS]; ///< packet history used when reading and sending packets
f5ce90f2
     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
b316991b
     int           is_input;                   ///< input/output flag
b3b17512
     char          *playpath;                  ///< stream identifier to play (with possible "mp4:" prefix)
b2e495af
     int           live;                       ///< 0: recorded, -1: live, -2: both
6465562e
     char          *app;                       ///< name of application
8ee3e187
     char          *conn;                      ///< append arbitrary AMF data to the Connect message
9fd6b843
     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
46743a85
     int           flv_nb_packets;             ///< number of flv packets published
6bf22e18
     RTMPPacket    out_pkt;                    ///< rtmp packet, created from flv a/v or metadata (for output)
bf7c1719
     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
3ffe32eb
     int           skip_bytes;                 ///< number of bytes to skip from the input FLV stream in the next write call
b14629e5
     uint8_t       flv_header[11];             ///< partial incoming flv packet header
     int           flv_header_bytes;           ///< number of initialized bytes in flv_header
704af3e2
     int           nb_invokes;                 ///< keeps track of invoke messages
55c9320e
     char*         tcurl;                      ///< url of the target stream
e64673e4
     char*         flashver;                   ///< version of the flash plugin
635ac8e1
     char*         swfhash;                    ///< SHA256 hash of the decompressed SWF file (32 bytes)
     int           swfhash_len;                ///< length of the SHA256 hash
     int           swfsize;                    ///< size of the decompressed SWF file
05945db9
     char*         swfurl;                     ///< url of the swf player
93f257db
     char*         swfverify;                  ///< URL to player swf file, compute hash/size automatically
635ac8e1
     char          swfverification[42];        ///< hash of the SWF verification
758377a2
     char*         pageurl;                    ///< url of the web page
00cb52c6
     char*         subscribe;                  ///< name of live stream to subscribe
c2d38bea
     int           server_bw;                  ///< server bandwidth
9477c035
     int           client_buffer_time;         ///< client buffer time in ms
46743a85
     int           flush_interval;             ///< number of packets flushed in the same request (RTMPT only)
acd554c1
     int           encrypted;                  ///< use an encrypted connection (RTMPE only)
f89584ca
     TrackedMethod*tracked_methods;            ///< tracked methods buffer
     int           nb_tracked_methods;         ///< number of tracked methods
     int           tracked_methods_size;       ///< size of the tracked methods buffer
e5f2731c
     int           listen;                     ///< listen mode flag
     int           listen_timeout;             ///< listen timeout to wait for new connections
     int           nb_streamid;                ///< The next stream id to return on createStream calls
08225d01
     char          username[50];
     char          password[50];
     char          auth_params[500];
     int           do_reconnect;
     int           auth_tried;
9fd6b843
 } 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
 };
 
f89584ca
 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--;
 }
 
a8103503
 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;
 }
 
f89584ca
 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);
08225d01
     rt->tracked_methods      = NULL;
     rt->tracked_methods_size = 0;
     rt->nb_tracked_methods   = 0;
f89584ca
 }
 
 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;
     }
 
f5ce90f2
     ret = ff_rtmp_packet_write(rt->stream, pkt, rt->out_chunk_size,
f89584ca
                                rt->prev_pkt[1]);
 fail:
     ff_rtmp_packet_destroy(pkt);
     return ret;
 }
 
8ee3e187
 static int rtmp_write_amf_data(URLContext *s, char *param, uint8_t **p)
 {
05338686
     char *field, *value;
8ee3e187
     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];
05338686
         field = param + 3;
         value = strchr(field, ':');
         if (!value)
             goto fail;
         *value = '\0';
         value++;
8ee3e187
 
         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);
 }
 
9fd6b843
 /**
49bd8e4b
  * Generate 'connect' call and send it to the server.
9fd6b843
  */
f645f1d6
 static int gen_connect(URLContext *s, RTMPContext *rt)
9fd6b843
 {
     RTMPPacket pkt;
e64673e4
     uint8_t *p;
f645f1d6
     int ret;
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
da8ef5ac
                                      0, 4096 + APP_MAX_LENGTH)) < 0)
f645f1d6
         return ret;
9fd6b843
 
     p = pkt.data;
 
     ff_amf_write_string(&p, "connect");
1eef08f9
     ff_amf_write_number(&p, ++rt->nb_invokes);
9fd6b843
     ff_amf_write_object_start(&p);
     ff_amf_write_field_name(&p, "app");
08225d01
     ff_amf_write_string2(&p, rt->app, rt->auth_params);
9fd6b843
 
e64673e4
     if (!rt->is_input) {
6bf22e18
         ff_amf_write_field_name(&p, "type");
         ff_amf_write_string(&p, "nonprivate");
     }
9fd6b843
     ff_amf_write_field_name(&p, "flashVer");
e64673e4
     ff_amf_write_string(&p, rt->flashver);
05945db9
 
     if (rt->swfurl) {
         ff_amf_write_field_name(&p, "swfUrl");
         ff_amf_write_string(&p, rt->swfurl);
     }
 
9fd6b843
     ff_amf_write_field_name(&p, "tcUrl");
08225d01
     ff_amf_write_string2(&p, rt->tcurl, rt->auth_params);
6bf22e18
     if (rt->is_input) {
c7240611
         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);
faba4a9b
 
         /* 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. */
c7240611
         ff_amf_write_field_name(&p, "audioCodecs");
faba4a9b
         ff_amf_write_number(&p, 4071.0);
c7240611
         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);
758377a2
 
         if (rt->pageurl) {
             ff_amf_write_field_name(&p, "pageUrl");
             ff_amf_write_string(&p, rt->pageurl);
         }
6bf22e18
     }
9fd6b843
     ff_amf_write_object_end(&p);
 
8ee3e187
     if (rt->conn) {
05338686
         char *param = rt->conn;
8ee3e187
 
         // Write arbitrary AMF data to the Connect message.
         while (param != NULL) {
05338686
             char *sep;
             param += strspn(param, " ");
             if (!*param)
                 break;
             sep = strchr(param, ' ');
             if (sep)
                 *sep = '\0';
8ee3e187
             if ((ret = rtmp_write_amf_data(s, param, &p)) < 0) {
                 // Invalid AMF parameter.
                 ff_rtmp_packet_destroy(&pkt);
                 return ret;
             }
 
05338686
             if (sep)
                 param = sep + 1;
             else
                 break;
8ee3e187
         }
     }
 
9fd6b843
     pkt.data_size = p - pkt.data;
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 1);
9fd6b843
 }
 
e5f2731c
 static int read_connect(URLContext *s, RTMPContext *rt)
 {
     RTMPPacket pkt = { 0 };
     uint8_t *p;
     const uint8_t *cp;
     int ret;
     char command[64];
     int stringlen;
     double seqnum;
     uint8_t tmpstr[256];
     GetByteContext gbc;
 
     if ((ret = ff_rtmp_packet_read(rt->stream, &pkt, rt->in_chunk_size,
                                    rt->prev_pkt[1])) < 0)
         return ret;
     cp = pkt.data;
     bytestream2_init(&gbc, cp, pkt.data_size);
     if (ff_amf_read_string(&gbc, command, sizeof(command), &stringlen)) {
         av_log(s, AV_LOG_ERROR, "Unable to read command string\n");
         ff_rtmp_packet_destroy(&pkt);
         return AVERROR_INVALIDDATA;
     }
     if (strcmp(command, "connect")) {
         av_log(s, AV_LOG_ERROR, "Expecting connect, got %s\n", command);
         ff_rtmp_packet_destroy(&pkt);
         return AVERROR_INVALIDDATA;
     }
     ret = ff_amf_read_number(&gbc, &seqnum);
     if (ret)
         av_log(s, AV_LOG_WARNING, "SeqNum not found\n");
     /* Here one could parse an AMF Object with data as flashVers and others. */
     ret = ff_amf_get_field_value(gbc.buffer,
                                  gbc.buffer + bytestream2_get_bytes_left(&gbc),
                                  "app", tmpstr, sizeof(tmpstr));
     if (ret)
         av_log(s, AV_LOG_WARNING, "App field not found in connect\n");
     if (!ret && strcmp(tmpstr, rt->app))
         av_log(s, AV_LOG_WARNING, "App field don't match up: %s <-> %s\n",
                tmpstr, rt->app);
     ff_rtmp_packet_destroy(&pkt);
 
     // Send Window Acknowledgement Size (as defined in speficication)
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL,
                                      RTMP_PT_SERVER_BW, 0, 4)) < 0)
         return ret;
     p = pkt.data;
     bytestream_put_be32(&p, rt->server_bw);
     pkt.data_size = p - pkt.data;
     ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
                                rt->prev_pkt[1]);
     ff_rtmp_packet_destroy(&pkt);
     if (ret < 0)
         return ret;
     // Send Peer Bandwidth
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL,
                                      RTMP_PT_CLIENT_BW, 0, 5)) < 0)
         return ret;
     p = pkt.data;
     bytestream_put_be32(&p, rt->server_bw);
     bytestream_put_byte(&p, 2); // dynamic
     pkt.data_size = p - pkt.data;
     ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
                                rt->prev_pkt[1]);
     ff_rtmp_packet_destroy(&pkt);
     if (ret < 0)
         return ret;
 
     // Ping request
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL,
                                      RTMP_PT_PING, 0, 6)) < 0)
         return ret;
 
     p = pkt.data;
     bytestream_put_be16(&p, 0); // 0 -> Stream Begin
     bytestream_put_be32(&p, 0);
     ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
                                rt->prev_pkt[1]);
     ff_rtmp_packet_destroy(&pkt);
     if (ret < 0)
         return ret;
 
     // Chunk size
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL,
                                      RTMP_PT_CHUNK_SIZE, 0, 4)) < 0)
         return ret;
 
     p = pkt.data;
     bytestream_put_be32(&p, rt->out_chunk_size);
     ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
                                rt->prev_pkt[1]);
     ff_rtmp_packet_destroy(&pkt);
     if (ret < 0)
         return ret;
 
     // Send result_ NetConnection.Connect.Success to connect
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL,
                                      RTMP_PT_INVOKE, 0,
                                      RTMP_PKTDATA_DEFAULT_SIZE)) < 0)
         return ret;
 
     p = pkt.data;
     ff_amf_write_string(&p, "_result");
     ff_amf_write_number(&p, seqnum);
 
     ff_amf_write_object_start(&p);
     ff_amf_write_field_name(&p, "fmsVer");
     ff_amf_write_string(&p, "FMS/3,0,1,123");
     ff_amf_write_field_name(&p, "capabilities");
     ff_amf_write_number(&p, 31);
     ff_amf_write_object_end(&p);
 
     ff_amf_write_object_start(&p);
     ff_amf_write_field_name(&p, "level");
     ff_amf_write_string(&p, "status");
     ff_amf_write_field_name(&p, "code");
     ff_amf_write_string(&p, "NetConnection.Connect.Success");
     ff_amf_write_field_name(&p, "description");
     ff_amf_write_string(&p, "Connection succeeded.");
     ff_amf_write_field_name(&p, "objectEncoding");
     ff_amf_write_number(&p, 0);
     ff_amf_write_object_end(&p);
 
     pkt.data_size = p - pkt.data;
     ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
                                rt->prev_pkt[1]);
     ff_rtmp_packet_destroy(&pkt);
     if (ret < 0)
         return ret;
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL,
                                      RTMP_PT_INVOKE, 0, 30)) < 0)
         return ret;
     p = pkt.data;
     ff_amf_write_string(&p, "onBWDone");
     ff_amf_write_number(&p, 0);
     ff_amf_write_null(&p);
     ff_amf_write_number(&p, 8192);
     pkt.data_size = p - pkt.data;
     ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
                                rt->prev_pkt[1]);
     ff_rtmp_packet_destroy(&pkt);
 
     return ret;
 }
 
9fd6b843
 /**
49bd8e4b
  * Generate 'releaseStream' call and send it to the server. It should make
6bf22e18
  * the server release some channel for media streams.
  */
f645f1d6
 static int gen_release_stream(URLContext *s, RTMPContext *rt)
6bf22e18
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
6bf22e18
 
f645f1d6
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                      0, 29 + strlen(rt->playpath))) < 0)
         return ret;
6bf22e18
 
7f804085
     av_log(s, AV_LOG_DEBUG, "Releasing stream...\n");
6bf22e18
     p = pkt.data;
     ff_amf_write_string(&p, "releaseStream");
704af3e2
     ff_amf_write_number(&p, ++rt->nb_invokes);
6bf22e18
     ff_amf_write_null(&p);
     ff_amf_write_string(&p, rt->playpath);
 
7011a42b
     return rtmp_send_packet(rt, &pkt, 1);
6bf22e18
 }
 
 /**
49bd8e4b
  * Generate 'FCPublish' call and send it to the server. It should make
6bf22e18
  * the server preapare for receiving media streams.
  */
f645f1d6
 static int gen_fcpublish_stream(URLContext *s, RTMPContext *rt)
6bf22e18
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
6bf22e18
 
f645f1d6
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                      0, 25 + strlen(rt->playpath))) < 0)
         return ret;
6bf22e18
 
7f804085
     av_log(s, AV_LOG_DEBUG, "FCPublish stream...\n");
6bf22e18
     p = pkt.data;
     ff_amf_write_string(&p, "FCPublish");
704af3e2
     ff_amf_write_number(&p, ++rt->nb_invokes);
6bf22e18
     ff_amf_write_null(&p);
     ff_amf_write_string(&p, rt->playpath);
 
7011a42b
     return rtmp_send_packet(rt, &pkt, 1);
6bf22e18
 }
 
 /**
49bd8e4b
  * Generate 'FCUnpublish' call and send it to the server. It should make
6bf22e18
  * the server destroy stream.
  */
f645f1d6
 static int gen_fcunpublish_stream(URLContext *s, RTMPContext *rt)
6bf22e18
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
6bf22e18
 
f645f1d6
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                      0, 27 + strlen(rt->playpath))) < 0)
         return ret;
6bf22e18
 
7f804085
     av_log(s, AV_LOG_DEBUG, "UnPublishing stream...\n");
6bf22e18
     p = pkt.data;
     ff_amf_write_string(&p, "FCUnpublish");
704af3e2
     ff_amf_write_number(&p, ++rt->nb_invokes);
6bf22e18
     ff_amf_write_null(&p);
     ff_amf_write_string(&p, rt->playpath);
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 0);
6bf22e18
 }
 
 /**
49bd8e4b
  * Generate 'createStream' call and send it to the server. It should make
9fd6b843
  * the server allocate some channel for media streams.
  */
f645f1d6
 static int gen_create_stream(URLContext *s, RTMPContext *rt)
9fd6b843
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
9fd6b843
 
7f804085
     av_log(s, AV_LOG_DEBUG, "Creating stream...\n");
f645f1d6
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                      0, 25)) < 0)
         return ret;
9fd6b843
 
     p = pkt.data;
     ff_amf_write_string(&p, "createStream");
704af3e2
     ff_amf_write_number(&p, ++rt->nb_invokes);
6bf22e18
     ff_amf_write_null(&p);
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 1);
6bf22e18
 }
 
 
 /**
49bd8e4b
  * Generate 'deleteStream' call and send it to the server. It should make
6bf22e18
  * the server remove some channel for media streams.
  */
f645f1d6
 static int gen_delete_stream(URLContext *s, RTMPContext *rt)
6bf22e18
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
6bf22e18
 
7f804085
     av_log(s, AV_LOG_DEBUG, "Deleting stream...\n");
f645f1d6
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                      0, 34)) < 0)
         return ret;
6bf22e18
 
     p = pkt.data;
     ff_amf_write_string(&p, "deleteStream");
1eef08f9
     ff_amf_write_number(&p, ++rt->nb_invokes);
9fd6b843
     ff_amf_write_null(&p);
6bf22e18
     ff_amf_write_number(&p, rt->main_channel_id);
9fd6b843
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 0);
9fd6b843
 }
 
 /**
9477c035
  * 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);
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 0);
9477c035
 }
 
 /**
49bd8e4b
  * Generate 'play' call and send it to the server, then ping the server
9fd6b843
  * to start actual playing.
  */
f645f1d6
 static int gen_play(URLContext *s, RTMPContext *rt)
9fd6b843
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
9fd6b843
 
7f804085
     av_log(s, AV_LOG_DEBUG, "Sending play command for '%s'\n", rt->playpath);
f645f1d6
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_VIDEO_CHANNEL, RTMP_PT_INVOKE,
                                      0, 29 + strlen(rt->playpath))) < 0)
         return ret;
 
9fd6b843
     pkt.extra = rt->main_channel_id;
 
     p = pkt.data;
     ff_amf_write_string(&p, "play");
1eef08f9
     ff_amf_write_number(&p, ++rt->nb_invokes);
9fd6b843
     ff_amf_write_null(&p);
     ff_amf_write_string(&p, rt->playpath);
b2e495af
     ff_amf_write_number(&p, rt->live);
9fd6b843
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 1);
9fd6b843
 }
 
 /**
49bd8e4b
  * Generate 'publish' call and send it to the server.
6bf22e18
  */
f645f1d6
 static int gen_publish(URLContext *s, RTMPContext *rt)
6bf22e18
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
6bf22e18
 
7f804085
     av_log(s, AV_LOG_DEBUG, "Sending publish command for '%s'\n", rt->playpath);
f645f1d6
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE,
                                      0, 30 + strlen(rt->playpath))) < 0)
         return ret;
 
6bf22e18
     pkt.extra = rt->main_channel_id;
 
     p = pkt.data;
     ff_amf_write_string(&p, "publish");
1eef08f9
     ff_amf_write_number(&p, ++rt->nb_invokes);
6bf22e18
     ff_amf_write_null(&p);
     ff_amf_write_string(&p, rt->playpath);
     ff_amf_write_string(&p, "live");
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 1);
6bf22e18
 }
 
 /**
49bd8e4b
  * Generate ping reply and send it to the server.
9fd6b843
  */
f645f1d6
 static int gen_pong(URLContext *s, RTMPContext *rt, RTMPPacket *ppkt)
9fd6b843
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
 
8ea1459b
     if (ppkt->data_size < 6) {
         av_log(s, AV_LOG_ERROR, "Too short ping packet (%d)\n",
                ppkt->data_size);
         return AVERROR_INVALIDDATA;
     }
 
f645f1d6
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING,
                                      ppkt->timestamp + 1, 6)) < 0)
         return ret;
9fd6b843
 
     p = pkt.data;
     bytestream_put_be16(&p, 7);
4aaebf78
     bytestream_put_be32(&p, AV_RB32(ppkt->data+2));
f645f1d6
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 0);
9fd6b843
 }
 
bf7c1719
 /**
635ac8e1
  * Generate SWF verification message and send it to the server.
  */
 static int gen_swf_verification(URLContext *s, RTMPContext *rt)
 {
     RTMPPacket pkt;
     uint8_t *p;
     int ret;
 
     av_log(s, AV_LOG_DEBUG, "Sending SWF verification...\n");
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING,
                                      0, 44)) < 0)
         return ret;
 
     p = pkt.data;
     bytestream_put_be16(&p, 27);
     memcpy(p, rt->swfverification, 42);
 
     return rtmp_send_packet(rt, &pkt, 0);
 }
 
 /**
34d908c0
  * Generate server bandwidth message and send it to the server.
  */
f645f1d6
 static int gen_server_bw(URLContext *s, RTMPContext *rt)
34d908c0
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_SERVER_BW,
                                      0, 4)) < 0)
         return ret;
34d908c0
 
     p = pkt.data;
c2d38bea
     bytestream_put_be32(&p, rt->server_bw);
f645f1d6
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 0);
34d908c0
 }
 
 /**
d55961fa
  * Generate check bandwidth message and send it to the server.
  */
f645f1d6
 static int gen_check_bw(URLContext *s, RTMPContext *rt)
d55961fa
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
d55961fa
 
f645f1d6
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                      0, 21)) < 0)
         return ret;
d55961fa
 
     p = pkt.data;
     ff_amf_write_string(&p, "_checkbw");
8b6a5a79
     ff_amf_write_number(&p, ++rt->nb_invokes);
d55961fa
     ff_amf_write_null(&p);
 
fb7e7808
     return rtmp_send_packet(rt, &pkt, 1);
d55961fa
 }
 
 /**
49bd8e4b
  * Generate report on bytes read so far and send it to the server.
bf7c1719
  */
f645f1d6
 static int gen_bytes_read(URLContext *s, RTMPContext *rt, uint32_t ts)
bf7c1719
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_BYTES_READ,
                                      ts, 4)) < 0)
         return ret;
bf7c1719
 
     p = pkt.data;
     bytestream_put_be32(&p, rt->bytes_read);
f645f1d6
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 0);
bf7c1719
 }
 
00cb52c6
 static int gen_fcsubscribe_stream(URLContext *s, RTMPContext *rt,
                                   const char *subscribe)
f9e77c17
 {
     RTMPPacket pkt;
     uint8_t *p;
     int ret;
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
00cb52c6
                                      0, 27 + strlen(subscribe))) < 0)
f9e77c17
         return ret;
 
     p = pkt.data;
     ff_amf_write_string(&p, "FCSubscribe");
     ff_amf_write_number(&p, ++rt->nb_invokes);
     ff_amf_write_null(&p);
00cb52c6
     ff_amf_write_string(&p, subscribe);
f9e77c17
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 1);
f9e77c17
 }
 
3505d557
 int ff_rtmp_calc_digest(const uint8_t *src, int len, int gap,
                         const uint8_t *key, int keylen, uint8_t *dst)
9fd6b843
 {
     struct AVSHA *sha;
     uint8_t hmac_buf[64+32] = {0};
     int i;
 
e002e329
     sha = av_sha_alloc();
08e93f5b
     if (!sha)
         return AVERROR(ENOMEM);
9fd6b843
 
     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);
08e93f5b
 
     return 0;
9fd6b843
 }
 
0e31088b
 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;
 }
 
9fd6b843
 /**
49bd8e4b
  * Put HMAC-SHA2 digest of packet data (except for the bytes where this digest
9fd6b843
  * will be stored) into that packet.
  *
  * @param buf handshake data (1536 bytes)
acd554c1
  * @param encrypted use an encrypted connection (RTMPE)
9fd6b843
  * @return offset to the digest inside input data
  */
acd554c1
 static int rtmp_handshake_imprint_with_digest(uint8_t *buf, int encrypted)
9fd6b843
 {
0e31088b
     int ret, digest_pos;
9fd6b843
 
acd554c1
     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);
9fd6b843
 
3505d557
     ret = ff_rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos,
                               rtmp_player_key, PLAYER_KEY_OPEN_PART_LEN,
                               buf + digest_pos);
08e93f5b
     if (ret < 0)
         return ret;
 
9fd6b843
     return digest_pos;
 }
 
 /**
49bd8e4b
  * Verify that the received server response has the expected digest value.
9fd6b843
  *
  * @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];
0e31088b
     int ret, digest_pos;
9fd6b843
 
0e31088b
     digest_pos = ff_rtmp_calc_digest_pos(buf, off, 728, off + 4);
9fd6b843
 
3505d557
     ret = ff_rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos,
                               rtmp_server_key, SERVER_KEY_OPEN_PART_LEN,
                               digest);
08e93f5b
     if (ret < 0)
         return ret;
 
9fd6b843
     if (!memcmp(digest, buf + digest_pos, 32))
         return digest_pos;
     return 0;
 }
 
635ac8e1
 static int rtmp_calc_swf_verification(URLContext *s, RTMPContext *rt,
                                       uint8_t *buf)
 {
     uint8_t *p;
     int ret;
 
     if (rt->swfhash_len != 32) {
         av_log(s, AV_LOG_ERROR,
                "Hash of the decompressed SWF file is not 32 bytes long.\n");
         return AVERROR(EINVAL);
     }
 
     p = &rt->swfverification[0];
     bytestream_put_byte(&p, 1);
     bytestream_put_byte(&p, 1);
     bytestream_put_be32(&p, rt->swfsize);
     bytestream_put_be32(&p, rt->swfsize);
 
     if ((ret = ff_rtmp_calc_digest(rt->swfhash, 32, 0, buf, 32, p)) < 0)
         return ret;
 
     return 0;
 }
 
93f257db
 #if CONFIG_ZLIB
 static int rtmp_uncompress_swfplayer(uint8_t *in_data, int64_t in_size,
                                      uint8_t **out_data, int64_t *out_size)
 {
     z_stream zs = { 0 };
     void *ptr;
     int size;
     int ret = 0;
 
     zs.avail_in = in_size;
     zs.next_in  = in_data;
     ret = inflateInit(&zs);
     if (ret != Z_OK)
         return AVERROR_UNKNOWN;
 
     do {
         uint8_t tmp_buf[16384];
 
         zs.avail_out = sizeof(tmp_buf);
         zs.next_out  = tmp_buf;
 
         ret = inflate(&zs, Z_NO_FLUSH);
         if (ret != Z_OK && ret != Z_STREAM_END) {
             ret = AVERROR_UNKNOWN;
             goto fail;
         }
 
         size = sizeof(tmp_buf) - zs.avail_out;
         if (!(ptr = av_realloc(*out_data, *out_size + size))) {
             ret = AVERROR(ENOMEM);
             goto fail;
         }
         *out_data = ptr;
 
         memcpy(*out_data + *out_size, tmp_buf, size);
         *out_size += size;
     } while (zs.avail_out == 0);
 
 fail:
     inflateEnd(&zs);
     return ret;
 }
 #endif
 
 static int rtmp_calc_swfhash(URLContext *s)
 {
     RTMPContext *rt = s->priv_data;
     uint8_t *in_data = NULL, *out_data = NULL, *swfdata;
     int64_t in_size, out_size;
     URLContext *stream;
     char swfhash[32];
     int swfsize;
     int ret = 0;
 
     /* Get the SWF player file. */
     if ((ret = ffurl_open(&stream, rt->swfverify, AVIO_FLAG_READ,
                           &s->interrupt_callback, NULL)) < 0) {
         av_log(s, AV_LOG_ERROR, "Cannot open connection %s.\n", rt->swfverify);
         goto fail;
     }
 
     if ((in_size = ffurl_seek(stream, 0, AVSEEK_SIZE)) < 0) {
         ret = AVERROR(EIO);
         goto fail;
     }
 
     if (!(in_data = av_malloc(in_size))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
 
     if ((ret = ffurl_read_complete(stream, in_data, in_size)) < 0)
         goto fail;
 
     if (in_size < 3) {
         ret = AVERROR_INVALIDDATA;
         goto fail;
     }
 
     if (!memcmp(in_data, "CWS", 3)) {
         /* Decompress the SWF player file using Zlib. */
         if (!(out_data = av_malloc(8))) {
             ret = AVERROR(ENOMEM);
             goto fail;
         }
         *in_data = 'F'; // magic stuff
         memcpy(out_data, in_data, 8);
         out_size = 8;
 
 #if CONFIG_ZLIB
         if ((ret = rtmp_uncompress_swfplayer(in_data + 8, in_size - 8,
                                              &out_data, &out_size)) < 0)
             goto fail;
 #else
         av_log(s, AV_LOG_ERROR,
                "Zlib is required for decompressing the SWF player file.\n");
         ret = AVERROR(EINVAL);
         goto fail;
 #endif
         swfsize = out_size;
         swfdata = out_data;
     } else {
         swfsize = in_size;
         swfdata = in_data;
     }
 
     /* Compute the SHA256 hash of the SWF player file. */
     if ((ret = ff_rtmp_calc_digest(swfdata, swfsize, 0,
                                    "Genuine Adobe Flash Player 001", 30,
                                    swfhash)) < 0)
         goto fail;
 
     /* Set SWFVerification parameters. */
     av_opt_set_bin(rt, "rtmp_swfhash", swfhash, 32, 0);
     rt->swfsize = swfsize;
 
 fail:
     av_freep(&in_data);
     av_freep(&out_data);
     ffurl_close(stream);
     return ret;
 }
 
9fd6b843
 /**
49bd8e4b
  * Perform handshake with the server by means of exchanging pseudorandom data
9fd6b843
  * 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;
acd554c1
     uint8_t digest[32], signature[32];
     int ret, type = 0;
9fd6b843
 
7f804085
     av_log(s, AV_LOG_DEBUG, "Handshaking...\n");
9fd6b843
 
     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;
acd554c1
 
23e9e5c7
     if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
acd554c1
         /* 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;
     }
 
f7bfb126
     client_pos = rtmp_handshake_imprint_with_digest(tosend + 1, rt->encrypted);
08e93f5b
     if (client_pos < 0)
         return client_pos;
9fd6b843
 
bba287fd
     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;
     }
 
177bcc95
     if ((ret = ffurl_read_complete(rt->stream, serverdata,
                                    RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) {
7f804085
         av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n");
177bcc95
         return ret;
9fd6b843
     }
177bcc95
 
     if ((ret = ffurl_read_complete(rt->stream, clientdata,
                                    RTMP_HANDSHAKE_PACKET_SIZE)) < 0) {
7f804085
         av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n");
177bcc95
         return ret;
9fd6b843
     }
 
acd554c1
     av_log(s, AV_LOG_DEBUG, "Type answer %d\n", serverdata[0]);
7f804085
     av_log(s, AV_LOG_DEBUG, "Server version %d.%d.%d.%d\n",
9fd6b843
            serverdata[5], serverdata[6], serverdata[7], serverdata[8]);
 
e2ee11e8
     if (rt->is_input && serverdata[5] >= 3) {
c7240611
         server_pos = rtmp_validate_digest(serverdata + 1, 772);
08e93f5b
         if (server_pos < 0)
             return server_pos;
 
9fd6b843
         if (!server_pos) {
acd554c1
             type = 1;
c7240611
             server_pos = rtmp_validate_digest(serverdata + 1, 8);
08e93f5b
             if (server_pos < 0)
                 return server_pos;
 
c7240611
             if (!server_pos) {
7f804085
                 av_log(s, AV_LOG_ERROR, "Server response validating failed\n");
a4d3f358
                 return AVERROR(EIO);
c7240611
             }
9fd6b843
         }
 
635ac8e1
         /* Generate SWFVerification token (SHA256 HMAC hash of decompressed SWF,
          * key are the last 32 bytes of the server handshake. */
         if (rt->swfsize) {
             if ((ret = rtmp_calc_swf_verification(s, rt, serverdata + 1 +
                                                   RTMP_HANDSHAKE_PACKET_SIZE - 32)) < 0)
                 return ret;
         }
 
3505d557
         ret = ff_rtmp_calc_digest(tosend + 1 + client_pos, 32, 0,
                                   rtmp_server_key, sizeof(rtmp_server_key),
                                   digest);
08e93f5b
         if (ret < 0)
             return ret;
 
3505d557
         ret = ff_rtmp_calc_digest(clientdata, RTMP_HANDSHAKE_PACKET_SIZE - 32,
acd554c1
                                   0, digest, 32, signature);
08e93f5b
         if (ret < 0)
             return ret;
 
23e9e5c7
         if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
acd554c1
             /* 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)) {
7f804085
             av_log(s, AV_LOG_ERROR, "Signature mismatch\n");
a4d3f358
             return AVERROR(EIO);
c7240611
         }
9fd6b843
 
c7240611
         for (i = 0; i < RTMP_HANDSHAKE_PACKET_SIZE; i++)
             tosend[i] = av_lfg_get(&rnd) >> 24;
3505d557
         ret = ff_rtmp_calc_digest(serverdata + 1 + server_pos, 32, 0,
                                   rtmp_player_key, sizeof(rtmp_player_key),
                                   digest);
08e93f5b
         if (ret < 0)
             return ret;
 
3505d557
         ret = ff_rtmp_calc_digest(tosend, RTMP_HANDSHAKE_PACKET_SIZE - 32, 0,
                                   digest, 32,
                                   tosend + RTMP_HANDSHAKE_PACKET_SIZE - 32);
08e93f5b
         if (ret < 0)
             return ret;
c7240611
 
23e9e5c7
         if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
acd554c1
             /* Encrypt the signature to be send to the server. */
             ff_rtmpe_encrypt_sig(rt->stream, tosend +
                                  RTMP_HANDSHAKE_PACKET_SIZE - 32, digest,
                                  serverdata[0]);
         }
 
c7240611
         // write reply back to the server
bba287fd
         if ((ret = ffurl_write(rt->stream, tosend,
                                RTMP_HANDSHAKE_PACKET_SIZE)) < 0)
             return ret;
acd554c1
 
23e9e5c7
         if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
acd554c1
             /* Set RC4 keys for encryption and update the keystreams. */
             if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0)
                 return ret;
         }
6bf22e18
     } else {
23e9e5c7
         if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
acd554c1
             /* 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]);
             }
         }
 
bba287fd
         if ((ret = ffurl_write(rt->stream, serverdata + 1,
                                RTMP_HANDSHAKE_PACKET_SIZE)) < 0)
             return ret;
acd554c1
 
23e9e5c7
         if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
acd554c1
             /* Set RC4 keys for encryption and update the keystreams. */
             if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0)
                 return ret;
         }
6bf22e18
     }
 
9fd6b843
     return 0;
 }
 
e5f2731c
 static int rtmp_receive_hs_packet(RTMPContext* rt, uint32_t *first_int,
                                   uint32_t *second_int, char *arraydata,
                                   int size)
 {
cb5ab02a
     int inoutsize;
e5f2731c
 
     inoutsize = ffurl_read_complete(rt->stream, arraydata,
                                     RTMP_HANDSHAKE_PACKET_SIZE);
     if (inoutsize <= 0)
         return AVERROR(EIO);
     if (inoutsize != RTMP_HANDSHAKE_PACKET_SIZE) {
         av_log(rt, AV_LOG_ERROR, "Erroneous Message size %d"
                " not following standard\n", (int)inoutsize);
         return AVERROR(EINVAL);
     }
 
     *first_int  = AV_RB32(arraydata);
     *second_int = AV_RB32(arraydata + 4);
     return 0;
 }
 
 static int rtmp_send_hs_packet(RTMPContext* rt, uint32_t first_int,
                                uint32_t second_int, char *arraydata, int size)
 {
cb5ab02a
     int inoutsize;
e5f2731c
 
     AV_WB32(arraydata, first_int);
     AV_WB32(arraydata + 4, first_int);
     inoutsize = ffurl_write(rt->stream, arraydata,
                             RTMP_HANDSHAKE_PACKET_SIZE);
     if (inoutsize != RTMP_HANDSHAKE_PACKET_SIZE) {
         av_log(rt, AV_LOG_ERROR, "Unable to write answer\n");
         return AVERROR(EIO);
     }
 
     return 0;
 }
 
 /**
  * rtmp handshake server side
  */
 static int rtmp_server_handshake(URLContext *s, RTMPContext *rt)
 {
     uint8_t buffer[RTMP_HANDSHAKE_PACKET_SIZE];
     uint32_t hs_epoch;
     uint32_t hs_my_epoch;
     uint8_t hs_c1[RTMP_HANDSHAKE_PACKET_SIZE];
     uint8_t hs_s1[RTMP_HANDSHAKE_PACKET_SIZE];
     uint32_t zeroes;
     uint32_t temp       = 0;
     int randomidx       = 0;
cb5ab02a
     int inoutsize       = 0;
e5f2731c
     int ret;
 
     inoutsize = ffurl_read_complete(rt->stream, buffer, 1);       // Receive C0
     if (inoutsize <= 0) {
         av_log(s, AV_LOG_ERROR, "Unable to read handshake\n");
         return AVERROR(EIO);
     }
     // Check Version
     if (buffer[0] != 3) {
         av_log(s, AV_LOG_ERROR, "RTMP protocol version mismatch\n");
         return AVERROR(EIO);
     }
     if (ffurl_write(rt->stream, buffer, 1) <= 0) {                 // Send S0
         av_log(s, AV_LOG_ERROR,
                "Unable to write answer - RTMP S0\n");
         return AVERROR(EIO);
     }
     /* Receive C1 */
     ret = rtmp_receive_hs_packet(rt, &hs_epoch, &zeroes, hs_c1,
                                  RTMP_HANDSHAKE_PACKET_SIZE);
     if (ret) {
         av_log(s, AV_LOG_ERROR, "RTMP Handshake C1 Error\n");
         return ret;
     }
     if (zeroes)
         av_log(s, AV_LOG_WARNING, "Erroneous C1 Message zero != 0\n");
     /* Send S1 */
     /* By now same epoch will be sent */
     hs_my_epoch = hs_epoch;
     /* Generate random */
5a75924d
     for (randomidx = 8; randomidx < (RTMP_HANDSHAKE_PACKET_SIZE);
e5f2731c
          randomidx += 4)
5a75924d
         AV_WB32(hs_s1 + randomidx, av_get_random_seed());
e5f2731c
 
     ret = rtmp_send_hs_packet(rt, hs_my_epoch, 0, hs_s1,
                               RTMP_HANDSHAKE_PACKET_SIZE);
     if (ret) {
         av_log(s, AV_LOG_ERROR, "RTMP Handshake S1 Error\n");
         return ret;
     }
     /* Send S2 */
     ret = rtmp_send_hs_packet(rt, hs_epoch, 0, hs_c1,
                               RTMP_HANDSHAKE_PACKET_SIZE);
     if (ret) {
         av_log(s, AV_LOG_ERROR, "RTMP Handshake S2 Error\n");
         return ret;
     }
     /* Receive C2 */
     ret = rtmp_receive_hs_packet(rt, &temp, &zeroes, buffer,
                                  RTMP_HANDSHAKE_PACKET_SIZE);
     if (ret) {
         av_log(s, AV_LOG_ERROR, "RTMP Handshake C2 Error\n");
         return ret;
     }
     if (temp != hs_my_epoch)
         av_log(s, AV_LOG_WARNING,
                "Erroneous C2 Message epoch does not match up with C1 epoch\n");
     if (memcmp(buffer + 8, hs_s1 + 8,
                RTMP_HANDSHAKE_PACKET_SIZE - 8))
         av_log(s, AV_LOG_WARNING,
                "Erroneous C2 Message random does not match up\n");
 
     return 0;
 }
 
7be2a7d8
 static int handle_chunk_size(URLContext *s, RTMPPacket *pkt)
 {
     RTMPContext *rt = s->priv_data;
     int ret;
 
e49e6b64
     if (pkt->data_size < 4) {
7be2a7d8
         av_log(s, AV_LOG_ERROR,
e49e6b64
                "Too short chunk size change packet (%d)\n",
7be2a7d8
                pkt->data_size);
e7ea6883
         return AVERROR_INVALIDDATA;
7be2a7d8
     }
 
     if (!rt->is_input) {
f5ce90f2
         /* 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,
7be2a7d8
                                         rt->prev_pkt[1])) < 0)
             return ret;
f5ce90f2
         rt->out_chunk_size = AV_RB32(pkt->data);
7be2a7d8
     }
 
f5ce90f2
     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);
e7ea6883
         return AVERROR_INVALIDDATA;
7be2a7d8
     }
f5ce90f2
     av_log(s, AV_LOG_DEBUG, "New incoming chunk size = %d\n",
            rt->in_chunk_size);
7be2a7d8
 
     return 0;
 }
 
0ffd5161
 static int handle_ping(URLContext *s, RTMPPacket *pkt)
 {
     RTMPContext *rt = s->priv_data;
     int t, ret;
 
8ea1459b
     if (pkt->data_size < 2) {
         av_log(s, AV_LOG_ERROR, "Too short ping packet (%d)\n",
                pkt->data_size);
         return AVERROR_INVALIDDATA;
     }
 
0ffd5161
     t = AV_RB16(pkt->data);
     if (t == 6) {
         if ((ret = gen_pong(s, rt, pkt)) < 0)
             return ret;
635ac8e1
     } else if (t == 26) {
         if (rt->swfsize) {
             if ((ret = gen_swf_verification(s, rt)) < 0)
                 return ret;
         } else {
             av_log(s, AV_LOG_WARNING, "Ignoring SWFVerification request.\n");
         }
0ffd5161
     }
 
     return 0;
 }
 
912ecc9a
 static int handle_client_bw(URLContext *s, RTMPPacket *pkt)
9fd6b843
 {
912ecc9a
     RTMPContext *rt = s->priv_data;
 
     if (pkt->data_size < 4) {
         av_log(s, AV_LOG_ERROR,
                "Client bandwidth report packet is less than 4 bytes long (%d)\n",
                pkt->data_size);
088a82bb
         return AVERROR_INVALIDDATA;
912ecc9a
     }
abf77a24
 
     rt->client_report_size = AV_RB32(pkt->data);
     if (rt->client_report_size <= 0) {
         av_log(s, AV_LOG_ERROR, "Incorrect client bandwidth %d\n",
                 rt->client_report_size);
         return AVERROR_INVALIDDATA;
 
     }
     av_log(s, AV_LOG_DEBUG, "Client bandwidth = %d\n", rt->client_report_size);
     rt->client_report_size >>= 1;
912ecc9a
 
     return 0;
 }
 
9b498148
 static int handle_server_bw(URLContext *s, RTMPPacket *pkt)
 {
     RTMPContext *rt = s->priv_data;
 
2357f606
     if (pkt->data_size < 4) {
         av_log(s, AV_LOG_ERROR,
                "Too short server bandwidth report packet (%d)\n",
                pkt->data_size);
         return AVERROR_INVALIDDATA;
     }
 
9b498148
     rt->server_bw = AV_RB32(pkt->data);
     if (rt->server_bw <= 0) {
         av_log(s, AV_LOG_ERROR, "Incorrect server bandwidth %d\n",
                rt->server_bw);
be8f9492
         return AVERROR_INVALIDDATA;
9b498148
     }
     av_log(s, AV_LOG_DEBUG, "Server bandwidth = %d\n", rt->server_bw);
 
     return 0;
 }
 
08225d01
 static int do_adobe_auth(RTMPContext *rt, const char *user, const char *salt,
                          const char *opaque, const char *challenge)
 {
     uint8_t hash[16];
     char hashstr[AV_BASE64_SIZE(sizeof(hash))], challenge2[10];
     struct AVMD5 *md5 = av_md5_alloc();
     if (!md5)
         return AVERROR(ENOMEM);
 
     snprintf(challenge2, sizeof(challenge2), "%08x", av_get_random_seed());
 
     av_md5_init(md5);
     av_md5_update(md5, user, strlen(user));
     av_md5_update(md5, salt, strlen(salt));
     av_md5_update(md5, rt->password, strlen(rt->password));
     av_md5_final(md5, hash);
     av_base64_encode(hashstr, sizeof(hashstr), hash,
                      sizeof(hash));
     av_md5_init(md5);
     av_md5_update(md5, hashstr, strlen(hashstr));
     if (opaque)
         av_md5_update(md5, opaque, strlen(opaque));
     else if (challenge)
         av_md5_update(md5, challenge, strlen(challenge));
     av_md5_update(md5, challenge2, strlen(challenge2));
     av_md5_final(md5, hash);
     av_base64_encode(hashstr, sizeof(hashstr), hash,
                      sizeof(hash));
     snprintf(rt->auth_params, sizeof(rt->auth_params),
              "?authmod=%s&user=%s&challenge=%s&response=%s",
              "adobe", user, challenge2, hashstr);
     if (opaque)
         av_strlcatf(rt->auth_params, sizeof(rt->auth_params),
                     "&opaque=%s", opaque);
 
     av_free(md5);
     return 0;
 }
 
c1ea44c5
 static int do_llnw_auth(RTMPContext *rt, const char *user, const char *nonce)
 {
     uint8_t hash[16];
     char hashstr1[33], hashstr2[33];
     const char *realm = "live";
     const char *method = "publish";
     const char *qop = "auth";
     const char *nc = "00000001";
     char cnonce[10];
     struct AVMD5 *md5 = av_md5_alloc();
     if (!md5)
         return AVERROR(ENOMEM);
 
     snprintf(cnonce, sizeof(cnonce), "%08x", av_get_random_seed());
 
     av_md5_init(md5);
     av_md5_update(md5, user, strlen(user));
     av_md5_update(md5, ":", 1);
     av_md5_update(md5, realm, strlen(realm));
     av_md5_update(md5, ":", 1);
     av_md5_update(md5, rt->password, strlen(rt->password));
     av_md5_final(md5, hash);
     ff_data_to_hex(hashstr1, hash, 16, 1);
     hashstr1[32] = '\0';
 
     av_md5_init(md5);
     av_md5_update(md5, method, strlen(method));
     av_md5_update(md5, ":/", 2);
     av_md5_update(md5, rt->app, strlen(rt->app));
     av_md5_final(md5, hash);
     ff_data_to_hex(hashstr2, hash, 16, 1);
     hashstr2[32] = '\0';
 
     av_md5_init(md5);
     av_md5_update(md5, hashstr1, strlen(hashstr1));
     av_md5_update(md5, ":", 1);
     if (nonce)
         av_md5_update(md5, nonce, strlen(nonce));
     av_md5_update(md5, ":", 1);
     av_md5_update(md5, nc, strlen(nc));
     av_md5_update(md5, ":", 1);
     av_md5_update(md5, cnonce, strlen(cnonce));
     av_md5_update(md5, ":", 1);
     av_md5_update(md5, qop, strlen(qop));
     av_md5_update(md5, ":", 1);
     av_md5_update(md5, hashstr2, strlen(hashstr2));
     av_md5_final(md5, hash);
     ff_data_to_hex(hashstr1, hash, 16, 1);
 
     snprintf(rt->auth_params, sizeof(rt->auth_params),
              "?authmod=%s&user=%s&nonce=%s&cnonce=%s&nc=%s&response=%s",
              "llnw", user, nonce, cnonce, nc, hashstr1);
 
     av_free(md5);
     return 0;
 }
 
08225d01
 static int handle_connect_error(URLContext *s, const char *desc)
 {
     RTMPContext *rt = s->priv_data;
c1ea44c5
     char buf[300], *ptr, authmod[15];
08225d01
     int i = 0, ret = 0;
     const char *user = "", *salt = "", *opaque = NULL,
c1ea44c5
                *challenge = NULL, *cptr = NULL, *nonce = NULL;
08225d01
 
c1ea44c5
     if (!(cptr = strstr(desc, "authmod=adobe")) &&
         !(cptr = strstr(desc, "authmod=llnw"))) {
08225d01
         av_log(s, AV_LOG_ERROR,
                "Unknown connect error (unsupported authentication method?)\n");
         return AVERROR_UNKNOWN;
     }
c1ea44c5
     cptr += strlen("authmod=");
     while (*cptr && *cptr != ' ' && i < sizeof(authmod) - 1)
         authmod[i++] = *cptr++;
     authmod[i] = '\0';
08225d01
 
     if (!rt->username[0] || !rt->password[0]) {
         av_log(s, AV_LOG_ERROR, "No credentials set\n");
         return AVERROR_UNKNOWN;
     }
 
     if (strstr(desc, "?reason=authfailed")) {
         av_log(s, AV_LOG_ERROR, "Incorrect username/password\n");
         return AVERROR_UNKNOWN;
     } else if (strstr(desc, "?reason=nosuchuser")) {
         av_log(s, AV_LOG_ERROR, "Incorrect username\n");
         return AVERROR_UNKNOWN;
     }
 
     if (rt->auth_tried) {
         av_log(s, AV_LOG_ERROR, "Authentication failed\n");
         return AVERROR_UNKNOWN;
     }
 
     rt->auth_params[0] = '\0';
 
     if (strstr(desc, "code=403 need auth")) {
         snprintf(rt->auth_params, sizeof(rt->auth_params),
c1ea44c5
                  "?authmod=%s&user=%s", authmod, rt->username);
08225d01
         return 0;
     }
 
     if (!(cptr = strstr(desc, "?reason=needauth"))) {
         av_log(s, AV_LOG_ERROR, "No auth parameters found\n");
         return AVERROR_UNKNOWN;
     }
 
     av_strlcpy(buf, cptr + 1, sizeof(buf));
     ptr = buf;
 
     while (ptr) {
         char *next  = strchr(ptr, '&');
         char *value = strchr(ptr, '=');
         if (next)
             *next++ = '\0';
         if (value)
             *value++ = '\0';
         if (!strcmp(ptr, "user")) {
             user = value;
         } else if (!strcmp(ptr, "salt")) {
             salt = value;
         } else if (!strcmp(ptr, "opaque")) {
             opaque = value;
         } else if (!strcmp(ptr, "challenge")) {
             challenge = value;
c1ea44c5
         } else if (!strcmp(ptr, "nonce")) {
             nonce = value;
08225d01
         }
         ptr = next;
     }
 
c1ea44c5
     if (!strcmp(authmod, "adobe")) {
a5e6080a
         if ((ret = do_adobe_auth(rt, user, salt, opaque, challenge)) < 0)
c1ea44c5
             return ret;
     } else {
         if ((ret = do_llnw_auth(rt, user, nonce)) < 0)
             return ret;
     }
08225d01
 
     rt->auth_tried = 1;
     return 0;
 }
 
3eebc1e1
 static int handle_invoke_error(URLContext *s, RTMPPacket *pkt)
9fd6b843
 {
08225d01
     RTMPContext *rt = s->priv_data;
9fd6b843
     const uint8_t *data_end = pkt->data + pkt->data_size;
fb7e7808
     char *tracked_method = NULL;
     int level = AV_LOG_ERROR;
3eebc1e1
     uint8_t tmpstr[256];
fb7e7808
     int ret;
 
     if ((ret = find_tracked_method(s, pkt, 9, &tracked_method)) < 0)
         return ret;
3eebc1e1
 
     if (!ff_amf_get_field_value(pkt->data + 9, data_end,
                                 "description", tmpstr, sizeof(tmpstr))) {
7011a42b
         if (tracked_method && (!strcmp(tracked_method, "_checkbw")      ||
                                !strcmp(tracked_method, "releaseStream") ||
                                !strcmp(tracked_method, "FCSubscribe")   ||
                                !strcmp(tracked_method, "FCPublish"))) {
             /* Gracefully ignore Adobe-specific historical artifact errors. */
fb7e7808
             level = AV_LOG_WARNING;
             ret = 0;
08225d01
         } else if (tracked_method && !strcmp(tracked_method, "connect")) {
             ret = handle_connect_error(s, tmpstr);
             if (!ret) {
                 rt->do_reconnect = 1;
                 level = AV_LOG_VERBOSE;
             }
fb7e7808
         } else
c76daa89
             ret = AVERROR_UNKNOWN;
fb7e7808
         av_log(s, level, "Server error: %s\n", tmpstr);
3eebc1e1
     }
 
fb7e7808
     av_free(tracked_method);
     return ret;
3eebc1e1
 }
 
e5f2731c
 static int send_invoke_response(URLContext *s, RTMPPacket *pkt)
 {
     RTMPContext *rt = s->priv_data;
     double seqnum;
     char filename[64];
     char command[64];
     char statusmsg[128];
     int stringlen;
     char *pchar;
     const uint8_t *p = pkt->data;
     uint8_t *pp      = NULL;
     RTMPPacket spkt  = { 0 };
     GetByteContext gbc;
     int ret;
 
     bytestream2_init(&gbc, p, pkt->data_size);
     if (ff_amf_read_string(&gbc, command, sizeof(command),
                            &stringlen)) {
         av_log(s, AV_LOG_ERROR, "Error in PT_INVOKE\n");
         return AVERROR_INVALIDDATA;
     }
 
     ret = ff_amf_read_number(&gbc, &seqnum);
     if (ret)
         return ret;
     ret = ff_amf_read_null(&gbc);
     if (ret)
         return ret;
     if (!strcmp(command, "FCPublish") ||
         !strcmp(command, "publish")) {
         ret = ff_amf_read_string(&gbc, filename,
                                  sizeof(filename), &stringlen);
         // check with url
         if (s->filename) {
             pchar = strrchr(s->filename, '/');
             if (!pchar) {
                 av_log(s, AV_LOG_WARNING,
                        "Unable to find / in url %s, bad format\n",
                        s->filename);
                 pchar = s->filename;
             }
             pchar++;
             if (strcmp(pchar, filename))
                 av_log(s, AV_LOG_WARNING, "Unexpected stream %s, expecting"
                        " %s\n", filename, pchar);
         }
         rt->state = STATE_RECEIVING;
     }
 
     if (!strcmp(command, "FCPublish")) {
         if ((ret = ff_rtmp_packet_create(&spkt, RTMP_SYSTEM_CHANNEL,
                                          RTMP_PT_INVOKE, 0,
                                          RTMP_PKTDATA_DEFAULT_SIZE)) < 0) {
             av_log(s, AV_LOG_ERROR, "Unable to create response packet\n");
             return ret;
         }
         pp = spkt.data;
         ff_amf_write_string(&pp, "onFCPublish");
     } else if (!strcmp(command, "publish")) {
         PutByteContext pbc;
         // Send Stream Begin 1
         if ((ret = ff_rtmp_packet_create(&spkt, RTMP_NETWORK_CHANNEL,
                                          RTMP_PT_PING, 0, 6)) < 0) {
             av_log(s, AV_LOG_ERROR, "Unable to create response packet\n");
             return ret;
         }
         pp = spkt.data;
         bytestream2_init_writer(&pbc, pp, spkt.data_size);
         bytestream2_put_be16(&pbc, 0);          // 0 -> Stream Begin
         bytestream2_put_be32(&pbc, rt->nb_streamid);
         ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size,
                                    rt->prev_pkt[1]);
         ff_rtmp_packet_destroy(&spkt);
         if (ret < 0)
             return ret;
 
         // Send onStatus(NetStream.Publish.Start)
         if ((ret = ff_rtmp_packet_create(&spkt, RTMP_SYSTEM_CHANNEL,
                                          RTMP_PT_INVOKE, 0,
                                          RTMP_PKTDATA_DEFAULT_SIZE)) < 0) {
             av_log(s, AV_LOG_ERROR, "Unable to create response packet\n");
             return ret;
         }
         spkt.extra = pkt->extra;
         pp = spkt.data;
         ff_amf_write_string(&pp, "onStatus");
         ff_amf_write_number(&pp, 0);
         ff_amf_write_null(&pp);
 
         ff_amf_write_object_start(&pp);
         ff_amf_write_field_name(&pp, "level");
         ff_amf_write_string(&pp, "status");
         ff_amf_write_field_name(&pp, "code");
         ff_amf_write_string(&pp, "NetStream.Publish.Start");
         ff_amf_write_field_name(&pp, "description");
         snprintf(statusmsg, sizeof(statusmsg),
                  "%s is now published", filename);
         ff_amf_write_string(&pp, statusmsg);
         ff_amf_write_field_name(&pp, "details");
         ff_amf_write_string(&pp, filename);
         ff_amf_write_field_name(&pp, "clientid");
         snprintf(statusmsg, sizeof(statusmsg), "%s", LIBAVFORMAT_IDENT);
         ff_amf_write_string(&pp, statusmsg);
         ff_amf_write_object_end(&pp);
 
     } else {
         if ((ret = ff_rtmp_packet_create(&spkt, RTMP_SYSTEM_CHANNEL,
                                          RTMP_PT_INVOKE, 0,
                                          RTMP_PKTDATA_DEFAULT_SIZE)) < 0) {
             av_log(s, AV_LOG_ERROR, "Unable to create response packet\n");
             return ret;
         }
         pp = spkt.data;
         ff_amf_write_string(&pp, "_result");
         ff_amf_write_number(&pp, seqnum);
         ff_amf_write_null(&pp);
         if (!strcmp(command, "createStream")) {
             rt->nb_streamid++;
             if (rt->nb_streamid == 0 || rt->nb_streamid == 2)
                 rt->nb_streamid++; /* Values 0 and 2 are reserved */
             ff_amf_write_number(&pp, rt->nb_streamid);
             /* By now we don't control which streams are removed in
              * deleteStream. There is no stream creation control
              * if a client creates more than 2^32 - 2 streams. */
         }
     }
     spkt.data_size = pp - spkt.data;
     ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size,
                                rt->prev_pkt[1]);
     ff_rtmp_packet_destroy(&spkt);
     return ret;
 }
 
5e6001db
 static int handle_invoke_result(URLContext *s, RTMPPacket *pkt)
 {
     RTMPContext *rt = s->priv_data;
f89584ca
     char *tracked_method = NULL;
     int ret = 0;
9fd6b843
 
a8103503
     if ((ret = find_tracked_method(s, pkt, 10, &tracked_method)) < 0)
5e6001db
         return ret;
9fd6b843
 
5e6001db
     if (!tracked_method) {
         /* Ignore this reply when the current method is not tracked. */
         return ret;
     }
f89584ca
 
5e6001db
     if (!memcmp(tracked_method, "connect", 7)) {
         if (!rt->is_input) {
             if ((ret = gen_release_stream(s, rt)) < 0)
                 goto fail;
f89584ca
 
5e6001db
             if ((ret = gen_fcpublish_stream(s, rt)) < 0)
                 goto fail;
         } else {
             if ((ret = gen_server_bw(s, rt)) < 0)
                 goto fail;
f89584ca
         }
 
5e6001db
         if ((ret = gen_create_stream(s, rt)) < 0)
             goto fail;
f89584ca
 
5e6001db
         if (rt->is_input) {
             /* Send the FCSubscribe command when the name of live
              * stream is defined by the user or if it's a live stream. */
             if (rt->subscribe) {
                 if ((ret = gen_fcsubscribe_stream(s, rt, rt->subscribe)) < 0)
                     goto fail;
             } else if (rt->live == -1) {
                 if ((ret = gen_fcsubscribe_stream(s, rt, rt->playpath)) < 0)
                     goto fail;
f89584ca
             }
9fd6b843
         }
5e6001db
     } else if (!memcmp(tracked_method, "createStream", 12)) {
         //extract a number from the result
         if (pkt->data[10] || pkt->data[19] != 5 || pkt->data[20]) {
             av_log(s, AV_LOG_WARNING, "Unexpected reply on connect()\n");
         } else {
             rt->main_channel_id = av_int2double(AV_RB64(pkt->data + 21));
6d1c9945
         }
5e6001db
 
         if (!rt->is_input) {
             if ((ret = gen_publish(s, rt)) < 0)
                 goto fail;
         } else {
             if ((ret = gen_play(s, rt)) < 0)
                 goto fail;
             if ((ret = gen_buffer_time(s, rt)) < 0)
                 goto fail;
6d1c9945
         }
5e6001db
     }
 
 fail:
     av_free(tracked_method);
     return ret;
 }
 
71036a3a
 static int handle_invoke_status(URLContext *s, RTMPPacket *pkt)
9fd6b843
 {
6d1c9945
     RTMPContext *rt = s->priv_data;
71036a3a
     const uint8_t *data_end = pkt->data + pkt->data_size;
     const uint8_t *ptr = pkt->data + 11;
     uint8_t tmpstr[256];
9fd6b843
     int i, t;
71036a3a
 
     for (i = 0; i < 2; i++) {
         t = ff_amf_tag_size(ptr, data_end);
         if (t < 0)
             return 1;
         ptr += t;
     }
 
     t = ff_amf_get_field_value(ptr, data_end, "level", tmpstr, sizeof(tmpstr));
     if (!t && !strcmp(tmpstr, "error")) {
         if (!ff_amf_get_field_value(ptr, data_end,
                                     "description", tmpstr, sizeof(tmpstr)))
             av_log(s, AV_LOG_ERROR, "Server error: %s\n", tmpstr);
         return -1;
     }
 
     t = ff_amf_get_field_value(ptr, data_end, "code", tmpstr, sizeof(tmpstr));
     if (!t && !strcmp(tmpstr, "NetStream.Play.Start")) rt->state = STATE_PLAYING;
     if (!t && !strcmp(tmpstr, "NetStream.Play.Stop")) rt->state = STATE_STOPPED;
     if (!t && !strcmp(tmpstr, "NetStream.Play.UnpublishNotify")) rt->state = STATE_STOPPED;
     if (!t && !strcmp(tmpstr, "NetStream.Publish.Start")) rt->state = STATE_PUBLISHING;
 
     return 0;
 }
 
 static int handle_invoke(URLContext *s, RTMPPacket *pkt)
 {
     RTMPContext *rt = s->priv_data;
f89584ca
     int ret = 0;
9fd6b843
 
6d1c9945
     //TODO: check for the messages sent for wrong state?
     if (!memcmp(pkt->data, "\002\000\006_error", 9)) {
3eebc1e1
         if ((ret = handle_invoke_error(s, pkt)) < 0)
             return ret;
6d1c9945
     } else if (!memcmp(pkt->data, "\002\000\007_result", 10)) {
5e6001db
         if ((ret = handle_invoke_result(s, pkt)) < 0)
f89584ca
             return ret;
6d1c9945
     } else if (!memcmp(pkt->data, "\002\000\010onStatus", 11)) {
71036a3a
         if ((ret = handle_invoke_status(s, pkt)) < 0)
             return ret;
6d1c9945
     } else if (!memcmp(pkt->data, "\002\000\010onBWDone", 11)) {
         if ((ret = gen_check_bw(s, rt)) < 0)
             return ret;
e5f2731c
     } else if (!memcmp(pkt->data, "\002\000\015releaseStream", 16) ||
                !memcmp(pkt->data, "\002\000\011FCPublish", 12)     ||
                !memcmp(pkt->data, "\002\000\007publish", 10)       ||
                !memcmp(pkt->data, "\002\000\010_checkbw", 11)      ||
                !memcmp(pkt->data, "\002\000\014createStream", 15)) {
a601eb95
         if ((ret = send_invoke_response(s, pkt)) < 0)
e5f2731c
             return ret;
6d1c9945
     }
 
f89584ca
     return ret;
6d1c9945
 }
 
e5f2731c
 static int handle_notify(URLContext *s, RTMPPacket *pkt) {
     RTMPContext *rt  = s->priv_data;
     const uint8_t *p = NULL;
     uint8_t *cp      = NULL;
     uint8_t commandbuffer[64];
     char statusmsg[128];
     int stringlen;
     GetByteContext gbc;
     PutByteContext pbc;
     uint32_t ts;
     int old_flv_size;
     const uint8_t *datatowrite;
     unsigned datatowritelength;
 
     p = pkt->data;
     bytestream2_init(&gbc, p, pkt->data_size);
     if (ff_amf_read_string(&gbc, commandbuffer, sizeof(commandbuffer),
                            &stringlen))
         return AVERROR_INVALIDDATA;
     if (!strcmp(commandbuffer, "@setDataFrame")) {
         datatowrite       = gbc.buffer;
         datatowritelength = bytestream2_get_bytes_left(&gbc);
         if (ff_amf_read_string(&gbc, statusmsg,
                                sizeof(statusmsg), &stringlen))
             return AVERROR_INVALIDDATA;
         if (strcmp(statusmsg, "onMetaData")) {
             av_log(s, AV_LOG_INFO, "Expecting onMetadata but got %s\n",
                    statusmsg);
             return 0;
         }
 
         /* Provide ECMAArray to flv */
         ts = pkt->timestamp;
 
         // generate packet header and put data into buffer for FLV demuxer
         if (rt->flv_off < rt->flv_size) {
             old_flv_size  = rt->flv_size;
             rt->flv_size += datatowritelength + 15;
         } else {
             old_flv_size = 0;
             rt->flv_size = datatowritelength + 15;
             rt->flv_off  = 0;
         }
 
         cp = av_realloc(rt->flv_data, rt->flv_size);
         if (!cp)
             return AVERROR(ENOMEM);
         rt->flv_data = cp;
         bytestream2_init_writer(&pbc, cp, rt->flv_size);
         bytestream2_skip_p(&pbc, old_flv_size);
         bytestream2_put_byte(&pbc, pkt->type);
         bytestream2_put_be24(&pbc, datatowritelength);
         bytestream2_put_be24(&pbc, ts);
         bytestream2_put_byte(&pbc, ts >> 24);
         bytestream2_put_be24(&pbc, 0);
         bytestream2_put_buffer(&pbc, datatowrite, datatowritelength);
         bytestream2_put_be32(&pbc, 0);
     }
     return 0;
 }
 
6d1c9945
 /**
  * Parse received packet and possibly perform some action depending on
  * the packet contents.
  * @return 0 for no errors, negative values for serious errors which prevent
  *         further communications, positive values for uncritical errors
  */
 static int rtmp_parse_result(URLContext *s, RTMPContext *rt, RTMPPacket *pkt)
 {
     int ret;
 
 #ifdef DEBUG
     ff_rtmp_packet_dump(s, pkt);
 #endif
 
     switch (pkt->type) {
fb96c1c5
     case RTMP_PT_BYTES_READ:
         av_dlog(s, "received bytes read report\n");
         break;
6d1c9945
     case RTMP_PT_CHUNK_SIZE:
         if ((ret = handle_chunk_size(s, pkt)) < 0)
             return ret;
         break;
     case RTMP_PT_PING:
         if ((ret = handle_ping(s, pkt)) < 0)
             return ret;
         break;
     case RTMP_PT_CLIENT_BW:
         if ((ret = handle_client_bw(s, pkt)) < 0)
             return ret;
         break;
     case RTMP_PT_SERVER_BW:
         if ((ret = handle_server_bw(s, pkt)) < 0)
             return ret;
         break;
     case RTMP_PT_INVOKE:
         if ((ret = handle_invoke(s, pkt)) < 0)
             return ret;
9fd6b843
         break;
08e087cc
     case RTMP_PT_VIDEO:
     case RTMP_PT_AUDIO:
9c9c21ea
     case RTMP_PT_METADATA:
e5f2731c
     case RTMP_PT_NOTIFY:
9c9c21ea
         /* Audio, Video and Metadata packets are parsed in get_packet() */
08e087cc
         break;
9ff930aa
     default:
         av_log(s, AV_LOG_VERBOSE, "Unknown packet type received 0x%02X\n", pkt->type);
         break;
9fd6b843
     }
     return 0;
 }
 
 /**
49bd8e4b
  * Interact with the server by receiving and sending RTMP packets until
9fd6b843
  * there is some significant data (media data or expected status notification).
  *
  * @param s          reading context
1d8041b3
  * @param for_header non-zero value tells function to work until it
  * gets notification from the server that playing has been started,
  * otherwise function will work until some media data is received (or
  * an error happens)
9fd6b843
  * @return 0 for successful operation, negative value in case of error
  */
 static int get_packet(URLContext *s, int for_header)
 {
     RTMPContext *rt = s->priv_data;
     int ret;
56e29bf2
     uint8_t *p;
     const uint8_t *next;
     uint32_t data_size;
     uint32_t ts, cts, pts=0;
9fd6b843
 
72b870b9
     if (rt->state == STATE_STOPPED)
         return AVERROR_EOF;
 
e07c92e4
     for (;;) {
271c869c
         RTMPPacket rpkt = { 0 };
9fd6b843
         if ((ret = ff_rtmp_packet_read(rt->stream, &rpkt,
f5ce90f2
                                        rt->in_chunk_size, rt->prev_pkt[0])) <= 0) {
b381a823
             if (ret == 0) {
9fd6b843
                 return AVERROR(EAGAIN);
             } else {
                 return AVERROR(EIO);
             }
         }
bf7c1719
         rt->bytes_read += ret;
0849a0eb
         if (rt->bytes_read - rt->last_bytes_read > rt->client_report_size) {
7f804085
             av_log(s, AV_LOG_DEBUG, "Sending bytes read report\n");
f645f1d6
             if ((ret = gen_bytes_read(s, rt, rpkt.timestamp + 1)) < 0)
                 return ret;
bf7c1719
             rt->last_bytes_read = rt->bytes_read;
         }
9fd6b843
 
         ret = rtmp_parse_result(s, rt, &rpkt);
         if (ret < 0) {//serious error in current packet
             ff_rtmp_packet_destroy(&rpkt);
f645f1d6
             return ret;
9fd6b843
         }
08225d01
         if (rt->do_reconnect && for_header) {
             ff_rtmp_packet_destroy(&rpkt);
             return 0;
         }
72b870b9
         if (rt->state == STATE_STOPPED) {
             ff_rtmp_packet_destroy(&rpkt);
             return AVERROR_EOF;
         }
e5f2731c
         if (for_header && (rt->state == STATE_PLAYING    ||
                            rt->state == STATE_PUBLISHING ||
                            rt->state == STATE_RECEIVING)) {
9fd6b843
             ff_rtmp_packet_destroy(&rpkt);
             return 0;
         }
6bf22e18
         if (!rpkt.data_size || !rt->is_input) {
9fd6b843
             ff_rtmp_packet_destroy(&rpkt);
             continue;
         }
         if (rpkt.type == RTMP_PT_VIDEO || rpkt.type == RTMP_PT_AUDIO ||
afbacb93
            (rpkt.type == RTMP_PT_NOTIFY && !memcmp("\002\000\012onMetaData", rpkt.data, 13))) {
56e29bf2
             ts = rpkt.timestamp;
9fd6b843
 
             // generate packet header and put data into buffer for FLV demuxer
             rt->flv_off  = 0;
             rt->flv_size = rpkt.data_size + 15;
             rt->flv_data = p = av_realloc(rt->flv_data, rt->flv_size);
             bytestream_put_byte(&p, rpkt.type);
             bytestream_put_be24(&p, rpkt.data_size);
             bytestream_put_be24(&p, ts);
             bytestream_put_byte(&p, ts >> 24);
             bytestream_put_be24(&p, 0);
             bytestream_put_buffer(&p, rpkt.data, rpkt.data_size);
             bytestream_put_be32(&p, 0);
             ff_rtmp_packet_destroy(&rpkt);
             return 0;
e5f2731c
         } else if (rpkt.type == RTMP_PT_NOTIFY) {
             ret = handle_notify(s, &rpkt);
             ff_rtmp_packet_destroy(&rpkt);
             if (ret) {
                 av_log(s, AV_LOG_ERROR, "Handle notify error\n");
                 return ret;
             }
             return 0;
9fd6b843
         } else if (rpkt.type == RTMP_PT_METADATA) {
             // we got raw FLV data, make it available for FLV demuxer
             rt->flv_off  = 0;
             rt->flv_size = rpkt.data_size;
             rt->flv_data = av_realloc(rt->flv_data, rt->flv_size);
56e29bf2
             /* rewrite timestamps */
             next = rpkt.data;
             ts = rpkt.timestamp;
             while (next - rpkt.data < rpkt.data_size - 11) {
                 next++;
                 data_size = bytestream_get_be24(&next);
                 p=next;
                 cts = bytestream_get_be24(&next);
aae9a093
                 cts |= bytestream_get_byte(&next) << 24;
56e29bf2
                 if (pts==0)
                     pts=cts;
                 ts += cts - pts;
                 pts = cts;
                 bytestream_put_be24(&p, ts);
                 bytestream_put_byte(&p, ts >> 24);
                 next += data_size + 3 + 4;
             }
9fd6b843
             memcpy(rt->flv_data, rpkt.data, rpkt.data_size);
             ff_rtmp_packet_destroy(&rpkt);
             return 0;
         }
         ff_rtmp_packet_destroy(&rpkt);
     }
 }
 
 static int rtmp_close(URLContext *h)
 {
     RTMPContext *rt = h->priv_data;
f645f1d6
     int ret = 0;
9fd6b843
 
e07c92e4
     if (!rt->is_input) {
6bf22e18
         rt->flv_data = NULL;
         if (rt->out_pkt.data_size)
             ff_rtmp_packet_destroy(&rt->out_pkt);
615c2879
         if (rt->state > STATE_FCPUBLISH)
f645f1d6
             ret = gen_fcunpublish_stream(h, rt);
6bf22e18
     }
615c2879
     if (rt->state > STATE_HANDSHAKED)
f645f1d6
         ret = gen_delete_stream(h, rt);
6bf22e18
 
f89584ca
     free_tracked_methods(rt);
9fd6b843
     av_freep(&rt->flv_data);
e52a9145
     ffurl_close(rt->stream);
f645f1d6
     return ret;
9fd6b843
 }
 
 /**
49bd8e4b
  * Open RTMP connection and verify that the stream can be played.
9fd6b843
  *
  * URL syntax: rtmp://server[:port][/app][/playpath]
  *             where 'app' is first one or two directories in the path
  *             (e.g. /ondemand/, /flash/live/, etc.)
  *             and 'playpath' is a file name (the rest of the path,
  *             may be prefixed with "mp4:")
  */
 static int rtmp_open(URLContext *s, const char *uri, int flags)
 {
7e580505
     RTMPContext *rt = s->priv_data;
08225d01
     char proto[8], hostname[256], path[1024], auth[100], *fname;
6465562e
     char *old_app;
9fd6b843
     uint8_t buf[2048];
b316991b
     int port;
86991ce2
     AVDictionary *opts = NULL;
9fd6b843
     int ret;
 
e5f2731c
     if (rt->listen_timeout > 0)
         rt->listen = 1;
 
59d96941
     rt->is_input = !(flags & AVIO_FLAG_WRITE);
9fd6b843
 
08225d01
     av_url_split(proto, sizeof(proto), auth, sizeof(auth),
                  hostname, sizeof(hostname), &port,
f984dcf6
                  path, sizeof(path), s->filename);
9fd6b843
 
08225d01
     if (auth[0]) {
         char *ptr = strchr(auth, ':');
         if (ptr) {
             *ptr = '\0';
             av_strlcpy(rt->username, auth, sizeof(rt->username));
             av_strlcpy(rt->password, ptr + 1, sizeof(rt->password));
         }
     }
 
e5f2731c
     if (rt->listen && strcmp(proto, "rtmp")) {
         av_log(s, AV_LOG_ERROR, "rtmp_listen not available for %s\n",
                proto);
         return AVERROR(EINVAL);
     }
86991ce2
     if (!strcmp(proto, "rtmpt") || !strcmp(proto, "rtmpts")) {
         if (!strcmp(proto, "rtmpts"))
             av_dict_set(&opts, "ffrtmphttp_tls", "1", 1);
 
8e50c57d
         /* open the http tunneling connection */
775c4d36
         ff_url_join(buf, sizeof(buf), "ffrtmphttp", NULL, hostname, port, NULL);
6aedabc9
     } else if (!strcmp(proto, "rtmps")) {
         /* open the tls connection */
         if (port < 0)
             port = RTMPS_DEFAULT_PORT;
         ff_url_join(buf, sizeof(buf), "tls", NULL, hostname, port, NULL);
08cd95e8
     } else if (!strcmp(proto, "rtmpe") || (!strcmp(proto, "rtmpte"))) {
         if (!strcmp(proto, "rtmpte"))
             av_dict_set(&opts, "ffrtmpcrypt_tunneling", "1", 1);
 
acd554c1
         /* open the encrypted connection */
         ff_url_join(buf, sizeof(buf), "ffrtmpcrypt", NULL, hostname, port, NULL);
         rt->encrypted = 1;
8e50c57d
     } else {
         /* open the tcp connection */
         if (port < 0)
             port = RTMP_DEFAULT_PORT;
e5f2731c
         if (rt->listen)
             ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port,
                         "?listen&listen_timeout=%d",
                         rt->listen_timeout * 1000);
         else
             ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL);
8e50c57d
     }
9fd6b843
 
08225d01
 reconnect:
f645f1d6
     if ((ret = ffurl_open(&rt->stream, buf, AVIO_FLAG_READ_WRITE,
86991ce2
                           &s->interrupt_callback, &opts)) < 0) {
59d96941
         av_log(s , AV_LOG_ERROR, "Cannot open connection %s\n", buf);
9fd6b843
         goto fail;
fe523958
     }
9fd6b843
 
93f257db
     if (rt->swfverify) {
         if ((ret = rtmp_calc_swfhash(s)) < 0)
             goto fail;
     }
 
c7240611
     rt->state = STATE_START;
e5f2731c
     if (!rt->listen && (ret = rtmp_handshake(s, rt)) < 0)
         goto fail;
     if (rt->listen && (ret = rtmp_server_handshake(s, rt)) < 0)
02490bf3
         goto fail;
9fd6b843
 
f5ce90f2
     rt->out_chunk_size = 128;
     rt->in_chunk_size  = 128; // Probably overwritten later
c7240611
     rt->state = STATE_HANDSHAKED;
6465562e
 
     // Keep the application name when it has been defined by the user.
     old_app = rt->app;
 
     rt->app = av_malloc(APP_MAX_LENGTH);
     if (!rt->app) {
f645f1d6
         ret = AVERROR(ENOMEM);
         goto fail;
6465562e
     }
 
c7240611
     //extract "app" part from path
     if (!strncmp(path, "/ondemand/", 10)) {
         fname = path + 10;
         memcpy(rt->app, "ondemand", 9);
     } else {
4b7304e8
         char *next = *path ? path + 1 : path;
         char *p = strchr(next, '/');
c7240611
         if (!p) {
4b7304e8
             fname = next;
c7240611
             rt->app[0] = '\0';
9fd6b843
         } else {
c6eeb9b7
             // make sure we do not mismatch a playpath for an application instance
c7240611
             char *c = strchr(p + 1, ':');
             fname = strchr(p + 1, '/');
c6eeb9b7
             if (!fname || (c && c < fname)) {
c7240611
                 fname = p + 1;
02ac3398
                 av_strlcpy(rt->app, path + 1, FFMIN(p - path, APP_MAX_LENGTH));
9fd6b843
             } else {
c7240611
                 fname++;
02ac3398
                 av_strlcpy(rt->app, path + 1, FFMIN(fname - path - 1, APP_MAX_LENGTH));
9fd6b843
             }
         }
c7240611
     }
6465562e
 
     if (old_app) {
         // The name of application has been defined by the user, override it.
02ac3398
         if (strlen(old_app) >= APP_MAX_LENGTH) {
             ret = AVERROR(EINVAL);
             goto fail;
         }
6465562e
         av_free(rt->app);
         rt->app = old_app;
     }
 
b3b17512
     if (!rt->playpath) {
f862537d
         int len = strlen(fname);
 
b3b17512
         rt->playpath = av_malloc(PLAYPATH_MAX_LENGTH);
         if (!rt->playpath) {
f645f1d6
             ret = AVERROR(ENOMEM);
             goto fail;
b3b17512
         }
 
0a9a2257
         if (!strchr(fname, ':') && len >= 4 &&
f862537d
             (!strcmp(fname + len - 4, ".f4v") ||
              !strcmp(fname + len - 4, ".mp4"))) {
b3b17512
             memcpy(rt->playpath, "mp4:", 5);
0a9a2257
         } else if (len >= 4 && !strcmp(fname + len - 4, ".flv")) {
f862537d
             fname[len - 4] = '\0';
b3b17512
         } else {
             rt->playpath[0] = 0;
         }
f0615557
         av_strlcat(rt->playpath, fname, PLAYPATH_MAX_LENGTH);
c7240611
     }
9fd6b843
 
55c9320e
     if (!rt->tcurl) {
         rt->tcurl = av_malloc(TCURL_MAX_LENGTH);
08e93f5b
         if (!rt->tcurl) {
             ret = AVERROR(ENOMEM);
             goto fail;
         }
55c9320e
         ff_url_join(rt->tcurl, TCURL_MAX_LENGTH, proto, NULL, hostname,
                     port, "/%s", rt->app);
     }
 
e64673e4
     if (!rt->flashver) {
         rt->flashver = av_malloc(FLASHVER_MAX_LENGTH);
08e93f5b
         if (!rt->flashver) {
             ret = AVERROR(ENOMEM);
             goto fail;
         }
e64673e4
         if (rt->is_input) {
             snprintf(rt->flashver, FLASHVER_MAX_LENGTH, "%s %d,%d,%d,%d",
                     RTMP_CLIENT_PLATFORM, RTMP_CLIENT_VER1, RTMP_CLIENT_VER2,
                     RTMP_CLIENT_VER3, RTMP_CLIENT_VER4);
         } else {
             snprintf(rt->flashver, FLASHVER_MAX_LENGTH,
                     "FMLE/3.0 (compatible; %s)", LIBAVFORMAT_IDENT);
         }
     }
 
bf7c1719
     rt->client_report_size = 1048576;
     rt->bytes_read = 0;
     rt->last_bytes_read = 0;
c2d38bea
     rt->server_bw = 2500000;
bf7c1719
 
7f804085
     av_log(s, AV_LOG_DEBUG, "Proto = %s, path = %s, app = %s, fname = %s\n",
c7240611
            proto, path, rt->app, rt->playpath);
e5f2731c
     if (!rt->listen) {
         if ((ret = gen_connect(s, rt)) < 0)
             goto fail;
     } else {
         if (read_connect(s, s->priv_data) < 0)
             goto fail;
         rt->is_input = 1;
     }
9fd6b843
 
c7240611
     do {
         ret = get_packet(s, 1);
     } while (ret == EAGAIN);
     if (ret < 0)
         goto fail;
6bf22e18
 
08225d01
     if (rt->do_reconnect) {
         ffurl_close(rt->stream);
         rt->stream       = NULL;
         rt->do_reconnect = 0;
         rt->nb_invokes   = 0;
         memset(rt->prev_pkt, 0, sizeof(rt->prev_pkt));
         free_tracked_methods(rt);
         goto reconnect;
     }
 
6bf22e18
     if (rt->is_input) {
9fd6b843
         // generate FLV header for demuxer
         rt->flv_size = 13;
         rt->flv_data = av_realloc(rt->flv_data, rt->flv_size);
         rt->flv_off  = 0;
         memcpy(rt->flv_data, "FLV\1\5\0\0\0\011\0\0\0\0", rt->flv_size);
6bf22e18
     } else {
         rt->flv_size = 0;
         rt->flv_data = NULL;
         rt->flv_off  = 0;
b14629e5
         rt->skip_bytes = 13;
9fd6b843
     }
 
5958df34
     s->max_packet_size = rt->stream->max_packet_size;
9fd6b843
     s->is_streamed     = 1;
     return 0;
 
 fail:
86991ce2
     av_dict_free(&opts);
9fd6b843
     rtmp_close(s);
f645f1d6
     return ret;
9fd6b843
 }
 
 static int rtmp_read(URLContext *s, uint8_t *buf, int size)
 {
     RTMPContext *rt = s->priv_data;
     int orig_size = size;
     int ret;
 
     while (size > 0) {
         int data_left = rt->flv_size - rt->flv_off;
 
         if (data_left >= size) {
             memcpy(buf, rt->flv_data + rt->flv_off, size);
             rt->flv_off += size;
             return orig_size;
         }
         if (data_left > 0) {
             memcpy(buf, rt->flv_data + rt->flv_off, data_left);
             buf  += data_left;
             size -= data_left;
             rt->flv_off = rt->flv_size;
e8ccf245
             return data_left;
9fd6b843
         }
         if ((ret = get_packet(s, 0)) < 0)
            return ret;
     }
     return orig_size;
 }
 
ea468763
 static int rtmp_write(URLContext *s, const uint8_t *buf, int size)
9fd6b843
 {
ea468763
     RTMPContext *rt = s->priv_data;
6bf22e18
     int size_temp = size;
     int pktsize, pkttype;
     uint32_t ts;
     const uint8_t *buf_temp = buf;
7dc747f5
     uint8_t c;
f645f1d6
     int ret;
6bf22e18
 
     do {
b14629e5
         if (rt->skip_bytes) {
             int skip = FFMIN(rt->skip_bytes, size_temp);
             buf_temp       += skip;
             size_temp      -= skip;
             rt->skip_bytes -= skip;
             continue;
         }
 
         if (rt->flv_header_bytes < 11) {
             const uint8_t *header = rt->flv_header;
             int copy = FFMIN(11 - rt->flv_header_bytes, size_temp);
             bytestream_get_buffer(&buf_temp, rt->flv_header + rt->flv_header_bytes, copy);
             rt->flv_header_bytes += copy;
             size_temp            -= copy;
             if (rt->flv_header_bytes < 11)
                 break;
6bf22e18
 
b14629e5
             pkttype = bytestream_get_byte(&header);
             pktsize = bytestream_get_be24(&header);
             ts = bytestream_get_be24(&header);
             ts |= bytestream_get_byte(&header) << 24;
             bytestream_get_be24(&header);
6bf22e18
             rt->flv_size = pktsize;
 
             //force 12bytes header
             if (((pkttype == RTMP_PT_VIDEO || pkttype == RTMP_PT_AUDIO) && ts == 0) ||
                 pkttype == RTMP_PT_NOTIFY) {
                 if (pkttype == RTMP_PT_NOTIFY)
                     pktsize += 16;
                 rt->prev_pkt[1][RTMP_SOURCE_CHANNEL].channel_id = 0;
             }
 
             //this can be a big packet, it's better to send it right here
f645f1d6
             if ((ret = ff_rtmp_packet_create(&rt->out_pkt, RTMP_SOURCE_CHANNEL,
                                              pkttype, ts, pktsize)) < 0)
                 return ret;
 
6bf22e18
             rt->out_pkt.extra = rt->main_channel_id;
             rt->flv_data = rt->out_pkt.data;
 
             if (pkttype == RTMP_PT_NOTIFY)
                 ff_amf_write_string(&rt->flv_data, "@setDataFrame");
         }
 
         if (rt->flv_size - rt->flv_off > size_temp) {
             bytestream_get_buffer(&buf_temp, rt->flv_data + rt->flv_off, size_temp);
             rt->flv_off += size_temp;
07631dee
             size_temp = 0;
6bf22e18
         } else {
             bytestream_get_buffer(&buf_temp, rt->flv_data + rt->flv_off, rt->flv_size - rt->flv_off);
a14c7842
             size_temp   -= rt->flv_size - rt->flv_off;
6bf22e18
             rt->flv_off += rt->flv_size - rt->flv_off;
         }
 
         if (rt->flv_off == rt->flv_size) {
b14629e5
             rt->skip_bytes = 4;
 
f89584ca
             if ((ret = rtmp_send_packet(rt, &rt->out_pkt, 0)) < 0)
bba287fd
                 return ret;
6bf22e18
             rt->flv_size = 0;
             rt->flv_off = 0;
b14629e5
             rt->flv_header_bytes = 0;
46743a85
             rt->flv_nb_packets++;
6bf22e18
         }
07631dee
     } while (buf_temp - buf < size);
7dc747f5
 
46743a85
     if (rt->flv_nb_packets < rt->flush_interval)
         return size;
     rt->flv_nb_packets = 0;
 
7dc747f5
     /* set stream into nonblocking mode */
     rt->stream->flags |= AVIO_FLAG_NONBLOCK;
 
     /* try to read one byte from the stream */
     ret = ffurl_read(rt->stream, &c, 1);
 
     /* switch the stream back into blocking mode */
     rt->stream->flags &= ~AVIO_FLAG_NONBLOCK;
 
     if (ret == AVERROR(EAGAIN)) {
         /* no incoming data to handle */
         return size;
     } else if (ret < 0) {
         return ret;
     } else if (ret == 1) {
         RTMPPacket rpkt = { 0 };
 
         if ((ret = ff_rtmp_packet_read_internal(rt->stream, &rpkt,
f5ce90f2
                                                 rt->in_chunk_size,
7dc747f5
                                                 rt->prev_pkt[0], c)) <= 0)
              return ret;
 
         if ((ret = rtmp_parse_result(s, rt, &rpkt)) < 0)
             return ret;
 
         ff_rtmp_packet_destroy(&rpkt);
     }
 
6bf22e18
     return size;
9fd6b843
 }
 
6465562e
 #define OFFSET(x) offsetof(RTMPContext, x)
 #define DEC AV_OPT_FLAG_DECODING_PARAM
 #define ENC AV_OPT_FLAG_ENCODING_PARAM
 
 static const AVOption rtmp_options[] = {
     {"rtmp_app", "Name of application to connect to on the RTMP server", OFFSET(app), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
e6153f17
     {"rtmp_buffer", "Set buffer time in milliseconds. The default is 3000.", OFFSET(client_buffer_time), AV_OPT_TYPE_INT, {.i64 = 3000}, 0, INT_MAX, DEC|ENC},
8ee3e187
     {"rtmp_conn", "Append arbitrary AMF data to the Connect message", OFFSET(conn), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
e64673e4
     {"rtmp_flashver", "Version of the Flash plugin used to run the SWF player.", OFFSET(flashver), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
e6153f17
     {"rtmp_flush_interval", "Number of packets flushed in the same request (RTMPT only).", OFFSET(flush_interval), AV_OPT_TYPE_INT, {.i64 = 10}, 0, INT_MAX, ENC},
     {"rtmp_live", "Specify that the media is a live stream.", OFFSET(live), AV_OPT_TYPE_INT, {.i64 = -2}, INT_MIN, INT_MAX, DEC, "rtmp_live"},
124134e4
     {"any", "both", 0, AV_OPT_TYPE_CONST, {.i64 = -2}, 0, 0, DEC, "rtmp_live"},
     {"live", "live stream", 0, AV_OPT_TYPE_CONST, {.i64 = -1}, 0, 0, DEC, "rtmp_live"},
     {"recorded", "recorded stream", 0, AV_OPT_TYPE_CONST, {.i64 = 0}, 0, 0, DEC, "rtmp_live"},
758377a2
     {"rtmp_pageurl", "URL of the web page in which the media was embedded. By default no value will be sent.", OFFSET(pageurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC},
b3b17512
     {"rtmp_playpath", "Stream identifier to play or to publish", OFFSET(playpath), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
00cb52c6
     {"rtmp_subscribe", "Name of live stream to subscribe to. Defaults to rtmp_playpath.", OFFSET(subscribe), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC},
635ac8e1
     {"rtmp_swfhash", "SHA256 hash of the decompressed SWF file (32 bytes).", OFFSET(swfhash), AV_OPT_TYPE_BINARY, .flags = DEC},
e6153f17
     {"rtmp_swfsize", "Size of the decompressed SWF file, required for SWFVerification.", OFFSET(swfsize), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, DEC},
05945db9
     {"rtmp_swfurl", "URL of the SWF player. By default no value will be sent", OFFSET(swfurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
93f257db
     {"rtmp_swfverify", "URL to player swf file, compute hash/size automatically.", OFFSET(swfverify), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC},
63ffa154
     {"rtmp_tcurl", "URL of the target stream. Defaults to proto://host[:port]/app.", OFFSET(tcurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
e6153f17
     {"rtmp_listen", "Listen for incoming rtmp connections", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC, "rtmp_listen" },
     {"timeout", "Maximum timeout (in seconds) to wait for incoming connections. -1 is infinite. Implies -rtmp_listen 1",  OFFSET(listen_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, DEC, "rtmp_listen" },
6465562e
     { NULL },
 };
 
12127b65
 #define RTMP_PROTOCOL(flavor)                    \
 static const AVClass flavor##_class = {          \
     .class_name = #flavor,                       \
     .item_name  = av_default_item_name,          \
     .option     = rtmp_options,                  \
     .version    = LIBAVUTIL_VERSION_INT,         \
 };                                               \
                                                  \
 URLProtocol ff_##flavor##_protocol = {           \
     .name           = #flavor,                   \
     .url_open       = rtmp_open,                 \
     .url_read       = rtmp_read,                 \
     .url_write      = rtmp_write,                \
     .url_close      = rtmp_close,                \
     .priv_data_size = sizeof(RTMPContext),       \
     .flags          = URL_PROTOCOL_FLAG_NETWORK, \
     .priv_data_class= &flavor##_class,           \
9fd6b843
 };
8e50c57d
 
acd554c1
 
12127b65
 RTMP_PROTOCOL(rtmp)
 RTMP_PROTOCOL(rtmpe)
 RTMP_PROTOCOL(rtmps)
 RTMP_PROTOCOL(rtmpt)
 RTMP_PROTOCOL(rtmpte)
 RTMP_PROTOCOL(rtmpts)