Skip to content
Snippets Groups Projects
rtmpproto.c 103 KiB
Newer Older
  • Learn to ignore specific revisions
  •     int ret, digest_pos;
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
        if (encrypted)
            digest_pos = ff_rtmp_calc_digest_pos(buf, 772, 728, 776);
        else
            digest_pos = ff_rtmp_calc_digest_pos(buf, 8, 728, 12);
    
        ret = ff_rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos,
                                  rtmp_player_key, PLAYER_KEY_OPEN_PART_LEN,
                                  buf + digest_pos);
    
        if (ret < 0)
            return ret;
    
    
     * Verify that the received server response has the expected digest value.
    
     *
     * @param buf handshake data received from the server (1536 bytes)
     * @param off position to search digest offset from
     * @return 0 if digest is valid, digest position otherwise
     */
    static int rtmp_validate_digest(uint8_t *buf, int off)
    {
        uint8_t digest[32];
    
        int ret, digest_pos;
    
        digest_pos = ff_rtmp_calc_digest_pos(buf, off, 728, off + 4);
    
        ret = ff_rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos,
                                  rtmp_server_key, SERVER_KEY_OPEN_PART_LEN,
                                  digest);
    
        if (ret < 0)
            return ret;
    
    
        if (!memcmp(digest, buf + digest_pos, 32))
            return digest_pos;
        return 0;
    }
    
    
    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;
    }
    
    
    #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, s->protocols, s)) < 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;
    }
    
    
     * Perform handshake with the server by means of exchanging pseudorandom data
    
     * signed with HMAC-SHA2 digest.
     *
     * @return 0 if handshake succeeds, negative value otherwise
     */
    static int rtmp_handshake(URLContext *s, RTMPContext *rt)
    {
        AVLFG rnd;
        uint8_t tosend    [RTMP_HANDSHAKE_PACKET_SIZE+1] = {
            3,                // unencrypted data
            0, 0, 0, 0,       // client uptime
            RTMP_CLIENT_VER1,
            RTMP_CLIENT_VER2,
            RTMP_CLIENT_VER3,
            RTMP_CLIENT_VER4,
        };
        uint8_t clientdata[RTMP_HANDSHAKE_PACKET_SIZE];
        uint8_t serverdata[RTMP_HANDSHAKE_PACKET_SIZE+1];
        int i;
        int server_pos, client_pos;
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
        uint8_t digest[32], signature[32];
        int ret, type = 0;
    
        av_log(s, AV_LOG_DEBUG, "Handshaking...\n");
    
    
        av_lfg_init(&rnd, 0xDEADC0DE);
        // generate handshake packet - 1536 bytes of pseudorandom data
        for (i = 9; i <= RTMP_HANDSHAKE_PACKET_SIZE; i++)
            tosend[i] = av_lfg_get(&rnd) >> 24;
    
        if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
            /* When the client wants to use RTMPE, we have to change the command
             * byte to 0x06 which means to use encrypted data and we have to set
             * the flash version to at least 9.0.115.0. */
            tosend[0] = 6;
            tosend[5] = 128;
            tosend[6] = 0;
            tosend[7] = 3;
            tosend[8] = 2;
    
            /* Initialize the Diffie-Hellmann context and generate the public key
             * to send to the server. */
            if ((ret = ff_rtmpe_gen_pub_key(rt->stream, tosend + 1)) < 0)
                return ret;
        }
    
    
        client_pos = rtmp_handshake_imprint_with_digest(tosend + 1, rt->encrypted);
    
        if (client_pos < 0)
            return client_pos;
    
        if ((ret = ffurl_write(rt->stream, tosend,
                               RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) {
            av_log(s, AV_LOG_ERROR, "Cannot write RTMP handshake request\n");
            return ret;
        }
    
    
        if ((ret = ffurl_read_complete(rt->stream, serverdata,
                                       RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) {
    
            av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n");
    
    
        if ((ret = ffurl_read_complete(rt->stream, clientdata,
                                       RTMP_HANDSHAKE_PACKET_SIZE)) < 0) {
    
            av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n");
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
        av_log(s, AV_LOG_DEBUG, "Type answer %d\n", serverdata[0]);
    
        av_log(s, AV_LOG_DEBUG, "Server version %d.%d.%d.%d\n",
    
               serverdata[5], serverdata[6], serverdata[7], serverdata[8]);
    
    
        if (rt->is_input && serverdata[5] >= 3) {
    
            server_pos = rtmp_validate_digest(serverdata + 1, 772);
    
            if (server_pos < 0)
                return server_pos;
    
    
            if (!server_pos) {
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
                type = 1;
    
                server_pos = rtmp_validate_digest(serverdata + 1, 8);
    
                if (server_pos < 0)
                    return server_pos;
    
    
                if (!server_pos) {
    
                    av_log(s, AV_LOG_ERROR, "Server response validating failed\n");
    
                    return AVERROR(EIO);
    
            /* 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;
            }
    
    
            ret = ff_rtmp_calc_digest(tosend + 1 + client_pos, 32, 0,
                                      rtmp_server_key, sizeof(rtmp_server_key),
                                      digest);
    
            if (ret < 0)
                return ret;
    
    
            ret = ff_rtmp_calc_digest(clientdata, RTMP_HANDSHAKE_PACKET_SIZE - 32,
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
                                      0, digest, 32, signature);
    
            if (ret < 0)
                return ret;
    
    
            if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
                /* Compute the shared secret key sent by the server and initialize
                 * the RC4 encryption. */
                if ((ret = ff_rtmpe_compute_secret_key(rt->stream, serverdata + 1,
                                                       tosend + 1, type)) < 0)
                    return ret;
    
                /* Encrypt the signature received by the server. */
                ff_rtmpe_encrypt_sig(rt->stream, signature, digest, serverdata[0]);
            }
    
            if (memcmp(signature, clientdata + RTMP_HANDSHAKE_PACKET_SIZE - 32, 32)) {
    
                av_log(s, AV_LOG_ERROR, "Signature mismatch\n");
    
                return AVERROR(EIO);
    
            for (i = 0; i < RTMP_HANDSHAKE_PACKET_SIZE; i++)
                tosend[i] = av_lfg_get(&rnd) >> 24;
    
            ret = ff_rtmp_calc_digest(serverdata + 1 + server_pos, 32, 0,
                                      rtmp_player_key, sizeof(rtmp_player_key),
                                      digest);
    
            if (ret < 0)
                return ret;
    
    
            ret = ff_rtmp_calc_digest(tosend, RTMP_HANDSHAKE_PACKET_SIZE - 32, 0,
                                      digest, 32,
                                      tosend + RTMP_HANDSHAKE_PACKET_SIZE - 32);
    
            if (ret < 0)
                return ret;
    
            if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
                /* Encrypt the signature to be send to the server. */
                ff_rtmpe_encrypt_sig(rt->stream, tosend +
                                     RTMP_HANDSHAKE_PACKET_SIZE - 32, digest,
                                     serverdata[0]);
            }
    
    
            // write reply back to the server
    
            if ((ret = ffurl_write(rt->stream, tosend,
                                   RTMP_HANDSHAKE_PACKET_SIZE)) < 0)
                return ret;
    
            if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
                /* Set RC4 keys for encryption and update the keystreams. */
                if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0)
                    return ret;
            }
    
            if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
                /* Compute the shared secret key sent by the server and initialize
                 * the RC4 encryption. */
                if ((ret = ff_rtmpe_compute_secret_key(rt->stream, serverdata + 1,
                                tosend + 1, 1)) < 0)
                    return ret;
    
                if (serverdata[0] == 9) {
                    /* Encrypt the signature received by the server. */
                    ff_rtmpe_encrypt_sig(rt->stream, signature, digest,
                                         serverdata[0]);
                }
            }
    
    
            if ((ret = ffurl_write(rt->stream, serverdata + 1,
                                   RTMP_HANDSHAKE_PACKET_SIZE)) < 0)
                return ret;
    
            if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
                /* Set RC4 keys for encryption and update the keystreams. */
                if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0)
                    return ret;
            }
    
    static int rtmp_receive_hs_packet(RTMPContext* rt, uint32_t *first_int,
                                      uint32_t *second_int, char *arraydata,
                                      int size)
    {
    
        int inoutsize;
    
    
        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)
    {
    
        int inoutsize;
    
    
        AV_WB32(arraydata, first_int);
    
        AV_WB32(arraydata + 4, second_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;
    
        int inoutsize       = 0;
    
        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;
        }
        /* Send S1 */
        /* By now same epoch will be sent */
        hs_my_epoch = hs_epoch;
        /* Generate random */
    
        for (randomidx = 8; randomidx < (RTMP_HANDSHAKE_PACKET_SIZE);
    
            AV_WB32(hs_s1 + randomidx, av_get_random_seed());
    
    
        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;
    }
    
    
    static int handle_chunk_size(URLContext *s, RTMPPacket *pkt)
    {
        RTMPContext *rt = s->priv_data;
        int ret;
    
    
        if (pkt->size < 4) {
    
            av_log(s, AV_LOG_ERROR,
    
                   pkt->size);
    
            return AVERROR_INVALIDDATA;
    
            /* Send the same chunk size change packet back to the server,
             * setting the outgoing chunk size to the same as the incoming one. */
            if ((ret = ff_rtmp_packet_write(rt->stream, pkt, rt->out_chunk_size,
    
                                            &rt->prev_pkt[1], &rt->nb_prev_pkt[1])) < 0)
    
            rt->out_chunk_size = AV_RB32(pkt->data);
    
        rt->in_chunk_size = AV_RB32(pkt->data);
        if (rt->in_chunk_size <= 0) {
            av_log(s, AV_LOG_ERROR, "Incorrect chunk size %d\n",
                   rt->in_chunk_size);
    
            return AVERROR_INVALIDDATA;
    
        av_log(s, AV_LOG_DEBUG, "New incoming chunk size = %d\n",
               rt->in_chunk_size);
    
    static int handle_ping(URLContext *s, RTMPPacket *pkt)
    {
        RTMPContext *rt = s->priv_data;
        int t, ret;
    
    
        if (pkt->size < 2) {
    
            av_log(s, AV_LOG_ERROR, "Too short ping packet (%d)\n",
    
                   pkt->size);
    
            return AVERROR_INVALIDDATA;
        }
    
    
        t = AV_RB16(pkt->data);
        if (t == 6) {
            if ((ret = gen_pong(s, rt, pkt)) < 0)
                return ret;
    
        } 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");
            }
    
    static int handle_client_bw(URLContext *s, RTMPPacket *pkt)
    {
        RTMPContext *rt = s->priv_data;
    
    
        if (pkt->size < 4) {
    
            av_log(s, AV_LOG_ERROR,
                   "Client bandwidth report packet is less than 4 bytes long (%d)\n",
    
                   pkt->size);
    
            return AVERROR_INVALIDDATA;
    
    
        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;
    
    static int handle_server_bw(URLContext *s, RTMPPacket *pkt)
    {
        RTMPContext *rt = s->priv_data;
    
    
        if (pkt->size < 4) {
    
            av_log(s, AV_LOG_ERROR,
                   "Too short server bandwidth report packet (%d)\n",
    
                   pkt->size);
    
        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);
    
            return AVERROR_INVALIDDATA;
    
        }
        av_log(s, AV_LOG_DEBUG, "Server bandwidth = %d\n", rt->server_bw);
    
        return 0;
    }
    
    
    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;
    }
    
    
    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));
    
        if (!strchr(rt->app, '/'))
            av_md5_update(md5, "/_definst_", strlen("/_definst_"));
    
        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;
    }
    
    
    static int handle_connect_error(URLContext *s, const char *desc)
    {
        RTMPContext *rt = s->priv_data;
    
        char buf[300], *ptr, authmod[15];
    
        int i = 0, ret = 0;
        const char *user = "", *salt = "", *opaque = NULL,
    
                   *challenge = NULL, *cptr = NULL, *nonce = NULL;
    
        if (!(cptr = strstr(desc, "authmod=adobe")) &&
            !(cptr = strstr(desc, "authmod=llnw"))) {
    
            av_log(s, AV_LOG_ERROR,
                   "Unknown connect error (unsupported authentication method?)\n");
            return AVERROR_UNKNOWN;
        }
    
        cptr += strlen("authmod=");
        while (*cptr && *cptr != ' ' && i < sizeof(authmod) - 1)
            authmod[i++] = *cptr++;
        authmod[i] = '\0';
    
    
        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),
    
                     "?authmod=%s&user=%s", authmod, rt->username);
    
            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;
    
            } else if (!strcmp(ptr, "nonce")) {
                nonce = value;
    
        if (!strcmp(authmod, "adobe")) {
    
            if ((ret = do_adobe_auth(rt, user, salt, opaque, challenge)) < 0)
    
                return ret;
        } else {
            if ((ret = do_llnw_auth(rt, user, nonce)) < 0)
                return ret;
        }
    
    static int handle_invoke_error(URLContext *s, RTMPPacket *pkt)
    {
    
        RTMPContext *rt = s->priv_data;
    
        const uint8_t *data_end = pkt->data + pkt->size;
    
        char *tracked_method = NULL;
        int level = AV_LOG_ERROR;
    
        int ret;
    
        if ((ret = find_tracked_method(s, pkt, 9, &tracked_method)) < 0)
            return ret;
    
    
        if (!ff_amf_get_field_value(pkt->data + 9, data_end,
                                    "description", tmpstr, sizeof(tmpstr))) {
    
            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. */
    
                level = AV_LOG_WARNING;
                ret = 0;
    
            } else if (tracked_method && !strcmp(tracked_method, "getStreamLength")) {
                level = rt->live ? AV_LOG_DEBUG : AV_LOG_WARNING;
                ret = 0;
    
            } else if (tracked_method && !strcmp(tracked_method, "connect")) {
                ret = handle_connect_error(s, tmpstr);
                if (!ret) {
                    rt->do_reconnect = 1;
                    level = AV_LOG_VERBOSE;
                }
    
            av_log(s, level, "Server error: %s\n", tmpstr);
    
        av_free(tracked_method);
        return ret;
    
    static int write_begin(URLContext *s)
    {
        RTMPContext *rt = s->priv_data;
        PutByteContext pbc;
        RTMPPacket spkt = { 0 };
        int ret;
    
        // 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;
        }
    
        bytestream2_init_writer(&pbc, spkt.data, spkt.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], &rt->nb_prev_pkt[1]);
    
    
        ff_rtmp_packet_destroy(&spkt);
    
        return ret;
    }
    
    static int write_status(URLContext *s, RTMPPacket *pkt,
                            const char *status, const char *filename)
    {
        RTMPContext *rt = s->priv_data;
        RTMPPacket spkt = { 0 };
        char statusmsg[128];
        uint8_t *pp;
        int ret;
    
        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;
        spkt.extra = pkt->extra;
        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, status);
        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);
    
        spkt.size = pp - spkt.data;
        ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size,
    
                                   &rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
    
        ff_rtmp_packet_destroy(&spkt);
    
        return ret;
    }
    
    
    static int send_invoke_response(URLContext *s, RTMPPacket *pkt)
    {
        RTMPContext *rt = s->priv_data;
        double seqnum;
    
        char command[64];
        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->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);
    
            if (ret) {
                if (ret == AVERROR(EINVAL))
                    av_log(s, AV_LOG_ERROR, "Unable to parse stream name - name too long?\n");
                else
                    av_log(s, AV_LOG_ERROR, "Unable to parse stream name\n");
                return ret;
            }
    
            // 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")) {
    
            ret = write_begin(s);
    
            if (ret < 0)
                return ret;
    
            // Send onStatus(NetStream.Publish.Start)
    
            return write_status(s, pkt, "NetStream.Publish.Start",
                               filename);
    
        } else if (!strcmp(command, "play")) {
            ret = write_begin(s);
            if (ret < 0)
                return ret;
            rt->state = STATE_SENDING;
            return write_status(s, pkt, "NetStream.Play.Start",
                                filename);
    
        } 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.size = pp - spkt.data;
    
        ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size,
    
                                   &rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
    
        ff_rtmp_packet_destroy(&spkt);
        return ret;
    }
    
    
    /**
     * Read the AMF_NUMBER response ("_result") to a function call
     * (e.g. createStream()). This response should be made up of the AMF_STRING
     * "result", a NULL object and then the response encoded as AMF_NUMBER. On a