On 07/28/2012 09:12 PM, Jordi Ortiz wrote: > --- > doc/protocols.texi | 5 + > libavformat/rtmp.h | 1 + > libavformat/rtmpproto.c | 532 > +++++++++++++++++++++++++++++++++++++++++++++-- > 3 files changed, 517 insertions(+), 21 deletions(-) > > diff --git a/doc/protocols.texi b/doc/protocols.texi > index ff872fc..cf15b36 100644 > --- a/doc/protocols.texi > +++ b/doc/protocols.texi > @@ -188,6 +188,11 @@ application specified in @var{app}, may be prefixed by > "mp4:". You > can override the value parsed from the URI through the @code{rtmp_playpath} > option, too. > > +@item listen > +Act as a server, listening for an incoming connection. > + > +@item timeout > +Maximum time to wait for the incoming connection. Implies listen. > @end table > > Additionally, the following parameters can be set via command line options > diff --git a/libavformat/rtmp.h b/libavformat/rtmp.h > index b9c5f1e..638f926 100644 > --- a/libavformat/rtmp.h > +++ b/libavformat/rtmp.h > @@ -28,6 +28,7 @@ > #define RTMPS_DEFAULT_PORT 443 > > #define RTMP_HANDSHAKE_PACKET_SIZE 1536 > +#define RTMP_HANDSHAKE_PACKET_ARRAY_SIZE 1528 > > #define HMAC_IPAD_VAL 0x36 > #define HMAC_OPAD_VAL 0x5C > diff --git a/libavformat/rtmpproto.c b/libavformat/rtmpproto.c > index 501e0ed..8bb7e9c 100644 > --- a/libavformat/rtmpproto.c > +++ b/libavformat/rtmpproto.c > @@ -29,6 +29,7 @@ > #include "libavutil/intfloat.h" > #include "libavutil/lfg.h" > #include "libavutil/opt.h" > +#include "libavutil/random_seed.h" > #include "libavutil/sha.h" > #include "avformat.h" > #include "internal.h" > @@ -47,6 +48,7 @@ > #define PLAYPATH_MAX_LENGTH 256 > #define TCURL_MAX_LENGTH 512 > #define FLASHVER_MAX_LENGTH 64 > +#define RTMP_PKTDATA_DEFAULT_SIZE 4096 > > /** RTMP protocol handler state */ > typedef enum { > @@ -95,6 +97,8 @@ typedef struct RTMPContext { > 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) > + int listen; ///< listen mode flag > + int listen_timeout; ///< listen timeout to wait > for new connections > } RTMPContext; > > #define PLAYER_KEY_OPEN_PART_LEN 30 ///< length of partial key used for > first client digest signing > @@ -275,6 +279,135 @@ static int gen_connect(URLContext *s, RTMPContext *rt) > return ret; > } > > +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; > + GetByteContext gbc; > + > + if ((ret = ff_rtmp_packet_read(rt->stream, &pkt, rt->chunk_size, > + rt->prev_pkt[1])) < 0) > + return ret; > + cp = pkt.data;
Why not using directly 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"); > + return AVERROR_INVALIDDATA; > + } > + if (strcmp(command, "connect")) { > + av_log(s, AV_LOG_ERROR, "Expecting connect\n"); > + 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. > + * Not needed by now */ > + ff_rtmp_packet_destroy(&pkt); > + > + // Send Window Acknowledgement Size (as defined in speficication) > + pkt.data = NULL; ff_rtmp_packet_destroy should reset pkt fields already (if it does not it is a bug) > + 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->chunk_size, > + rt->prev_pkt[1]); > + ff_rtmp_packet_destroy(&pkt); > + > + // 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; it is sort of wrong aligning this line. > + ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, > + rt->prev_pkt[1]); Check the return value. > + ff_rtmp_packet_destroy(&pkt); > + // Ping request > + if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, > + RTMP_PT_PING, 0, 6)) < 0) > + return ret; > + > + p = pkt.data; > + pkt.data_size = 6; This kind of pattern puzzles me. > + bytestream_put_be16(&p, 0); // 0 -> Stream Begin > + bytestream_put_be32(&p, 0); > + ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, > + rt->prev_pkt[1]); Again missing check. > + ff_rtmp_packet_destroy(&pkt); > + > + // Chunk size > + if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, > + RTMP_PT_CHUNK_SIZE, 0, 4)) < 0) > + return ret; > + > + p = pkt.data; > + pkt.data_size = 4; Again strange setting the size to 4 here and to p - pkt.data before. > + bytestream_put_be32(&p, rt->chunk_size); > + ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, > + rt->prev_pkt[1]); again, please check the return value > + ff_rtmp_packet_destroy(&pkt); > + _result is the result from a message received, which message? > + // Send result_ > + 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, 1); > + > + 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->chunk_size, > + rt->prev_pkt[1]); > + ff_rtmp_packet_destroy(&pkt); > + > + if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, > + RTMP_PT_INVOKE, 0, 28)) < 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->chunk_size, > + rt->prev_pkt[1]); > + ff_rtmp_packet_destroy(&pkt); > + > + return ret; > +} > + > /** > * Generate 'releaseStream' call and send it to the server. It should make > * the server release some channel for media streams. > @@ -886,6 +1019,141 @@ static int rtmp_handshake(URLContext *s, RTMPContext > *rt) > return 0; > } > > +static int rtmp_receive_hs_packet(RTMPContext* rt, uint32_t *first_int, > + uint32_t *second_int, char *arraydata, > + int size) > +{ > + ssize_t inoutsize; > + uint8_t buffer[RTMP_HANDSHAKE_PACKET_SIZE]; > + > + inoutsize = ffurl_read_complete(rt->stream, buffer, sizeof(buffer)); > + if (!inoutsize) > + 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_INVALIDDATA; AVERROR(EINVAL) while you are at it. > + } > + > + *first_int = AV_RB32(buffer); > + *second_int = AV_RB32(buffer + 4); > + memcpy(arraydata, buffer + 8, > + RTMP_HANDSHAKE_PACKET_ARRAY_SIZE); > + return 0; > +} > + > +static int rtmp_send_hs_packet(RTMPContext* rt, uint32_t first_int, > + uint32_t second_int, char *arraydata, int > size) > +{ > + ssize_t inoutsize; > + uint8_t buffer[RTMP_HANDSHAKE_PACKET_SIZE]; > + > + AV_WB32(buffer, first_int); > + AV_WB32(buffer + 4, first_int); > + memcpy(buffer + 8, arraydata, RTMP_HANDSHAKE_PACKET_ARRAY_SIZE); > + inoutsize = ffurl_write(rt->stream, buffer, > + 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_shandshake(URLContext *s, RTMPContext *rt) > +{ > + uint8_t buffer[RTMP_HANDSHAKE_PACKET_SIZE]; > + uint32_t epoch; // to context? > + uint32_t my_epoch; // to context? > + uint32_t zeroes; > + uint32_t temp = 0; > + uint8_t peer_random[RTMP_HANDSHAKE_PACKET_ARRAY_SIZE]; // to context? > + uint8_t my_random[RTMP_HANDSHAKE_PACKET_ARRAY_SIZE]; // to context? yes please. > + int randomidx = 0; > + ssize_t 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); > + } > + if (buffer[0] != 3) { /* Check Version */ > + av_log(s, AV_LOG_ERROR, "RTMP protocol version mismatch\n"); > + return AVERROR_PROTOCOL_NOT_FOUND; > + } > + if (!ffurl_write(rt->stream, buffer, 1)) { // 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, &epoch, &zeroes, peer_random, > + sizeof(peer_random)); > + if (ret) { > + av_log(s, AV_LOG_ERROR, "RTMP Handshake C1 Error\n"); > + return ret; > + } > + if (zeroes) { > + av_log(NULL, AV_LOG_WARNING, > + "Erroneous C1 Message zero != 0\n"); > + } > + /* Send S1 */ > + /* By now same epoch will be sent */ > + my_epoch = epoch; > + /* Generate random */ > + for (randomidx = 0; > + randomidx < (RTMP_HANDSHAKE_PACKET_ARRAY_SIZE); > + randomidx += 4) { > + temp = av_get_random_seed(); > + memcpy(my_random + randomidx, &temp, 4); > + } > + ret = rtmp_send_hs_packet(rt, my_epoch, 0, my_random, > + RTMP_HANDSHAKE_PACKET_ARRAY_SIZE); > + if (ret) { > + av_log(s, AV_LOG_ERROR, "RTMP Handshake S1 Error\n"); > + return ret; > + } > + /* Send S2 */ > + ret = rtmp_send_hs_packet(rt, epoch, 0, peer_random, > + RTMP_HANDSHAKE_PACKET_ARRAY_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, peer_random, > + sizeof(peer_random)); > + if (ret) { > + av_log(s, AV_LOG_ERROR, "RTMP Handshake C2 Error\n"); > + return ret; > + } > + > + > + if (temp != my_epoch) { > + av_log(NULL, AV_LOG_ERROR, "Erroneous C2 Message epoch" > + " does not match up with C1 epoch\n"); > + return AVERROR_INVALIDDATA; > + } > + > + if (memcmp(peer_random, my_random, > + RTMP_HANDSHAKE_PACKET_ARRAY_SIZE)) { > + av_log(NULL, AV_LOG_ERROR, "Erroneous C2 Message random" > + " does not match up\n"); > + return AVERROR_INVALIDDATA; > + } > + > + /* Handshake successful */ > + return 0; > + > +} > + The rest seems more or less ok. Samuel will have to overhaul part of the same file so we should push this change before he starts working on that. ^^ lu -- Luca Barbato Gentoo/linux http://dev.gentoo.org/~lu_zero _______________________________________________ libav-devel mailing list libav-devel@libav.org https://lists.libav.org/mailman/listinfo/libav-devel