#include "flv.h"
#include "rtmp.h"
+#include "rtmpcrypt.h"
#include "rtmppkt.h"
#include "url.h"
char* tcurl; ///< url of the target stream
char* flashver; ///< version of the flash plugin
char* swfurl; ///< url of the swf player
+ char* pageurl; ///< url of the web page
int server_bw; ///< server bandwidth
int client_buffer_time; ///< client buffer time in ms
int flush_interval; ///< number of packets flushed in the same request (RTMPT only)
+ int encrypted; ///< use an encrypted connection (RTMPE only)
} RTMPContext;
#define PLAYER_KEY_OPEN_PART_LEN 30 ///< length of partial key used for first client digest signing
ff_amf_write_number(&p, 252.0);
ff_amf_write_field_name(&p, "videoFunction");
ff_amf_write_number(&p, 1.0);
+
+ if (rt->pageurl) {
+ ff_amf_write_field_name(&p, "pageUrl");
+ ff_amf_write_string(&p, rt->pageurl);
+ }
}
ff_amf_write_object_end(&p);
uint8_t *p;
int ret;
+ if (ppkt->data_size < 6) {
+ av_log(s, AV_LOG_ERROR, "Too short ping packet (%d)\n",
+ ppkt->data_size);
+ return AVERROR_INVALIDDATA;
+ }
+
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING,
ppkt->timestamp + 1, 6)) < 0)
return ret;
return 0;
}
+int ff_rtmp_calc_digest_pos(const uint8_t *buf, int off, int mod_val,
+ int add_val)
+{
+ int i, digest_pos = 0;
+
+ for (i = 0; i < 4; i++)
+ digest_pos += buf[i + off];
+ digest_pos = digest_pos % mod_val + add_val;
+
+ return digest_pos;
+}
+
/**
* Put HMAC-SHA2 digest of packet data (except for the bytes where this digest
* will be stored) into that packet.
*
* @param buf handshake data (1536 bytes)
+ * @param encrypted use an encrypted connection (RTMPE)
* @return offset to the digest inside input data
*/
-static int rtmp_handshake_imprint_with_digest(uint8_t *buf)
+static int rtmp_handshake_imprint_with_digest(uint8_t *buf, int encrypted)
{
- int i, digest_pos = 0;
- int ret;
+ int ret, digest_pos;
- for (i = 8; i < 12; i++)
- digest_pos += buf[i];
- digest_pos = (digest_pos % 728) + 12;
+ 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,
*/
static int rtmp_validate_digest(uint8_t *buf, int off)
{
- int i, digest_pos = 0;
uint8_t digest[32];
- int ret;
+ int ret, digest_pos;
- for (i = 0; i < 4; i++)
- digest_pos += buf[i + off];
- digest_pos = (digest_pos % 728) + off + 4;
+ 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,
uint8_t serverdata[RTMP_HANDSHAKE_PACKET_SIZE+1];
int i;
int server_pos, client_pos;
- uint8_t digest[32];
- int ret;
+ uint8_t digest[32], signature[32];
+ int ret, type = 0;
av_log(s, AV_LOG_DEBUG, "Handshaking...\n");
// generate handshake packet - 1536 bytes of pseudorandom data
for (i = 9; i <= RTMP_HANDSHAKE_PACKET_SIZE; i++)
tosend[i] = av_lfg_get(&rnd) >> 24;
- client_pos = rtmp_handshake_imprint_with_digest(tosend + 1);
+
+ if (rt->encrypted && CONFIG_FFRTMPCRYPT_PROTOCOL) {
+ /* 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;
return ret;
}
+ 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]);
return server_pos;
if (!server_pos) {
+ type = 1;
server_pos = rtmp_validate_digest(serverdata + 1, 8);
if (server_pos < 0)
return server_pos;
return ret;
ret = ff_rtmp_calc_digest(clientdata, RTMP_HANDSHAKE_PACKET_SIZE - 32,
- 0, digest, 32, digest);
+ 0, digest, 32, signature);
if (ret < 0)
return ret;
- if (memcmp(digest, clientdata + RTMP_HANDSHAKE_PACKET_SIZE - 32, 32)) {
+ if (rt->encrypted && CONFIG_FFRTMPCRYPT_PROTOCOL) {
+ /* 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);
}
if (ret < 0)
return ret;
+ if (rt->encrypted && CONFIG_FFRTMPCRYPT_PROTOCOL) {
+ /* Encrypt the signature to be send to the server. */
+ ff_rtmpe_encrypt_sig(rt->stream, tosend +
+ RTMP_HANDSHAKE_PACKET_SIZE - 32, digest,
+ serverdata[0]);
+ }
+
// write reply back to the server
if ((ret = ffurl_write(rt->stream, tosend,
RTMP_HANDSHAKE_PACKET_SIZE)) < 0)
return ret;
+
+ if (rt->encrypted && CONFIG_FFRTMPCRYPT_PROTOCOL) {
+ /* Set RC4 keys for encryption and update the keystreams. */
+ if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0)
+ return ret;
+ }
} else {
+ if (rt->encrypted && CONFIG_FFRTMPCRYPT_PROTOCOL) {
+ /* Compute the shared secret key sent by the server and initialize
+ * the RC4 encryption. */
+ if ((ret = ff_rtmpe_compute_secret_key(rt->stream, serverdata + 1,
+ tosend + 1, 1)) < 0)
+ return ret;
+
+ if (serverdata[0] == 9) {
+ /* Encrypt the signature received by the server. */
+ ff_rtmpe_encrypt_sig(rt->stream, signature, digest,
+ serverdata[0]);
+ }
+ }
+
if ((ret = ffurl_write(rt->stream, serverdata + 1,
RTMP_HANDSHAKE_PACKET_SIZE)) < 0)
return ret;
+
+ if (rt->encrypted && CONFIG_FFRTMPCRYPT_PROTOCOL) {
+ /* Set RC4 keys for encryption and update the keystreams. */
+ if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0)
+ return ret;
+ }
}
return 0;
}
-/**
- * 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)
+static int handle_chunk_size(URLContext *s, RTMPPacket *pkt)
{
+ RTMPContext *rt = s->priv_data;
+ int ret;
+
+ if (pkt->data_size < 4) {
+ av_log(s, AV_LOG_ERROR,
+ "Too short chunk size change packet (%d)\n",
+ pkt->data_size);
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (!rt->is_input) {
+ if ((ret = ff_rtmp_packet_write(rt->stream, pkt, rt->chunk_size,
+ rt->prev_pkt[1])) < 0)
+ return ret;
+ }
+
+ rt->chunk_size = AV_RB32(pkt->data);
+ if (rt->chunk_size <= 0) {
+ av_log(s, AV_LOG_ERROR, "Incorrect chunk size %d\n", rt->chunk_size);
+ return AVERROR_INVALIDDATA;
+ }
+ av_log(s, AV_LOG_DEBUG, "New chunk size = %d\n", rt->chunk_size);
+
+ return 0;
+}
+
+static int handle_ping(URLContext *s, RTMPPacket *pkt)
+{
+ RTMPContext *rt = s->priv_data;
+ int t, ret;
+
+ if (pkt->data_size < 2) {
+ av_log(s, AV_LOG_ERROR, "Too short ping packet (%d)\n",
+ pkt->data_size);
+ return AVERROR_INVALIDDATA;
+ }
+
+ t = AV_RB16(pkt->data);
+ if (t == 6) {
+ if ((ret = gen_pong(s, rt, pkt)) < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int handle_client_bw(URLContext *s, RTMPPacket *pkt)
+{
+ 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);
+ 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;
+
+ return 0;
+}
+
+static int handle_server_bw(URLContext *s, RTMPPacket *pkt)
+{
+ RTMPContext *rt = s->priv_data;
+
+ 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;
+ }
+
+ 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 handle_invoke(URLContext *s, RTMPPacket *pkt)
+{
+ RTMPContext *rt = s->priv_data;
int i, t;
const uint8_t *data_end = pkt->data + pkt->data_size;
int ret;
-#ifdef DEBUG
- ff_rtmp_packet_dump(s, pkt);
-#endif
+ //TODO: check for the messages sent for wrong state?
+ if (!memcmp(pkt->data, "\002\000\006_error", 9)) {
+ uint8_t tmpstr[256];
- switch (pkt->type) {
- case RTMP_PT_CHUNK_SIZE:
- if (pkt->data_size != 4) {
- av_log(s, AV_LOG_ERROR,
- "Chunk size change packet is not 4 bytes long (%d)\n", pkt->data_size);
- return -1;
- }
- if (!rt->is_input)
- if ((ret = ff_rtmp_packet_write(rt->stream, pkt, rt->chunk_size,
- rt->prev_pkt[1])) < 0)
- return ret;
- rt->chunk_size = AV_RB32(pkt->data);
- if (rt->chunk_size <= 0) {
- av_log(s, AV_LOG_ERROR, "Incorrect chunk size %d\n", rt->chunk_size);
- return -1;
- }
- av_log(s, AV_LOG_DEBUG, "New chunk size = %d\n", rt->chunk_size);
- break;
- case RTMP_PT_PING:
- t = AV_RB16(pkt->data);
- if (t == 6)
- if ((ret = gen_pong(s, rt, pkt)) < 0)
- return ret;
- break;
- case RTMP_PT_CLIENT_BW:
- 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);
- return -1;
- }
- av_log(s, AV_LOG_DEBUG, "Client bandwidth = %d\n", AV_RB32(pkt->data));
- rt->client_report_size = AV_RB32(pkt->data) >> 1;
- break;
- case RTMP_PT_SERVER_BW:
- 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(EINVAL);
- }
- av_log(s, AV_LOG_DEBUG, "Server bandwidth = %d\n", rt->server_bw);
- break;
- case RTMP_PT_INVOKE:
- //TODO: check for the messages sent for wrong state?
- if (!memcmp(pkt->data, "\002\000\006_error", 9)) {
- uint8_t tmpstr[256];
-
- if (!ff_amf_get_field_value(pkt->data + 9, data_end,
- "description", tmpstr, sizeof(tmpstr)))
- av_log(s, AV_LOG_ERROR, "Server error: %s\n",tmpstr);
- return -1;
- } else if (!memcmp(pkt->data, "\002\000\007_result", 10)) {
- switch (rt->state) {
+ if (!ff_amf_get_field_value(pkt->data + 9, data_end,
+ "description", tmpstr, sizeof(tmpstr)))
+ av_log(s, AV_LOG_ERROR, "Server error: %s\n",tmpstr);
+ return -1;
+ } else if (!memcmp(pkt->data, "\002\000\007_result", 10)) {
+ switch (rt->state) {
case STATE_HANDSHAKED:
if (!rt->is_input) {
if ((ret = gen_release_stream(s, rt)) < 0)
}
rt->state = STATE_READY;
break;
- }
- } else if (!memcmp(pkt->data, "\002\000\010onStatus", 11)) {
- const uint8_t* ptr = pkt->data + 11;
- uint8_t tmpstr[256];
-
- 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;
- } else if (!memcmp(pkt->data, "\002\000\010onBWDone", 11)) {
- if ((ret = gen_check_bw(s, rt)) < 0)
- return ret;
}
+ } else if (!memcmp(pkt->data, "\002\000\010onStatus", 11)) {
+ const uint8_t* ptr = pkt->data + 11;
+ uint8_t tmpstr[256];
+
+ 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;
+ } else if (!memcmp(pkt->data, "\002\000\010onBWDone", 11)) {
+ if ((ret = gen_check_bw(s, rt)) < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * 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) {
+ 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;
break;
case RTMP_PT_VIDEO:
case RTMP_PT_AUDIO:
if (port < 0)
port = RTMPS_DEFAULT_PORT;
ff_url_join(buf, sizeof(buf), "tls", NULL, hostname, port, NULL);
+ } else if (!strcmp(proto, "rtmpe") || (!strcmp(proto, "rtmpte"))) {
+ if (!strcmp(proto, "rtmpte"))
+ av_dict_set(&opts, "ffrtmpcrypt_tunneling", "1", 1);
+
+ /* open the encrypted connection */
+ ff_url_join(buf, sizeof(buf), "ffrtmpcrypt", NULL, hostname, port, NULL);
+ rt->encrypted = 1;
} else {
/* open the tcp connection */
if (port < 0)
{"any", "both", 0, AV_OPT_TYPE_CONST, {-2}, 0, 0, DEC, "rtmp_live"},
{"live", "live stream", 0, AV_OPT_TYPE_CONST, {-1}, 0, 0, DEC, "rtmp_live"},
{"recorded", "recorded stream", 0, AV_OPT_TYPE_CONST, {0}, 0, 0, DEC, "rtmp_live"},
+ {"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},
{"rtmp_playpath", "Stream identifier to play or to publish", OFFSET(playpath), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
{"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},
- {"rtmp_tcurl", "URL of the target stream. Defaults to rtmp://host[:port]/app.", OFFSET(tcurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
+ {"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},
{ NULL },
};
.priv_data_class= &rtmp_class,
};
+static const AVClass rtmpe_class = {
+ .class_name = "rtmpe",
+ .item_name = av_default_item_name,
+ .option = rtmp_options,
+ .version = LIBAVUTIL_VERSION_INT,
+};
+
+URLProtocol ff_rtmpe_protocol = {
+ .name = "rtmpe",
+ .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 = &rtmpe_class,
+};
+
static const AVClass rtmps_class = {
.class_name = "rtmps",
.item_name = av_default_item_name,
.priv_data_class = &rtmpt_class,
};
+static const AVClass rtmpte_class = {
+ .class_name = "rtmpte",
+ .item_name = av_default_item_name,
+ .option = rtmp_options,
+ .version = LIBAVUTIL_VERSION_INT,
+};
+
+URLProtocol ff_rtmpte_protocol = {
+ .name = "rtmpte",
+ .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 = &rtmpte_class,
+};
+
static const AVClass rtmpts_class = {
.class_name = "rtmpts",
.item_name = av_default_item_name,