diff --git a/AUTHORS.txt b/AUTHORS.txt index 30777d436..e705ff820 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -36,3 +36,17 @@ CONTRIBUTORS ordered by first contribution. * xubin * intliang * flowerwrong +* YLX<568414379@qq.com> +* J +* Harlan +* hankun +* JonathanBarratt +* KeeganH +* StevenLiu +* liuxc0116 +* ChengdongZhang +* lovacat +* qiang.li +* HungMingWu +* Himer +* xialixin \ No newline at end of file diff --git a/README.md b/README.md index dc95a7f4c..63a9b1fad 100755 --- a/README.md +++ b/README.md @@ -148,6 +148,7 @@ For previous versions, please read: ## V3 changes +* v3.0, 2019-12-24, For [#1508][bug #1508], support chunk length and content in multiple parts. * v3.0, 2019-12-23, Merge SRS2 for running srs-librtmp on Windows. 3.0.80 * v3.0, 2019-12-23, For [#1535][bug #1535], deprecate Adobe FMS/AMS edge token traversing([CN][v3_CN_DRM2], [EN][v3_EN_DRM2]) authentication. 3.0.79 * v3.0, 2019-12-23, For [#1535][bug #1535], deprecate BWT(bandwidth testing)([CN][v1_CN_BandwidthTestTool], [EN][v1_EN_BandwidthTestTool]). 3.0.78 diff --git a/trunk/scripts/new_authors.sh b/trunk/scripts/new_authors.sh new file mode 100755 index 000000000..f0a94123c --- /dev/null +++ b/trunk/scripts/new_authors.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +AFILE=`dirname $0`/../../AUTHORS.txt +if [[ ! -f $AFILE ]]; then echo "No file at $AFILE"; exit -1; fi + +authors=`git log --format='%ae'|grep -v localhost|grep -v demo|grep -v none|sort|uniq` +if [[ $? -ne 0 ]]; then echo "no authors"; exit -1; fi + +for author in $authors; do + echo $author| grep 'winlin' >/dev/null 2>&1 && continue; + echo $author| grep 'winterserver' >/dev/null 2>&1 && continue; + echo $author| grep 'wenjie.zhao' >/dev/null 2>&1 && continue; + echo $author| grep 'zhaowenjie' >/dev/null 2>&1 && continue; + + grep $author $AFILE 1>/dev/null 2>/dev/null && continue; + + git log -1 --author="$author" --format='%an<%ae>'| sed 's/ //g' +done diff --git a/trunk/src/app/srs_app_caster_flv.cpp b/trunk/src/app/srs_app_caster_flv.cpp index 87f7a893e..d3b96de46 100644 --- a/trunk/src/app/srs_app_caster_flv.cpp +++ b/trunk/src/app/srs_app_caster_flv.cpp @@ -295,7 +295,7 @@ srs_error_t SrsHttpFileReader::read(void* buf, size_t count, ssize_t* pnread) int total_read = 0; while (total_read < (int)count) { - int nread = 0; + ssize_t nread = 0; if ((err = http->read((char*)buf + total_read, (int)(count - total_read), &nread)) != srs_success) { return srs_error_wrap(err, "read"); } @@ -306,7 +306,7 @@ srs_error_t SrsHttpFileReader::read(void* buf, size_t count, ssize_t* pnread) } srs_assert(nread); - total_read += nread; + total_read += (int)nread; } if (pnread) { diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index c5fae9974..1a790627a 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -3855,17 +3855,6 @@ srs_error_t SrsConfig::check_normal_config() SRS_CONSTS_RTMP_MIN_CHUNK_SIZE, SRS_CONSTS_RTMP_MAX_CHUNK_SIZE); } } - for (int i = 0; i < (int)vhosts.size(); i++) { - SrsConfDirective* vhost = vhosts[i]; - srs_assert(vhost != NULL); - if (get_dvr_enabled(vhost->arg0())) { - srs_warn("can't enable vhost.dvr of %s", vhost->arg0().c_str()); - } - if (get_hls_enabled(vhost->arg0())) { - srs_warn("can't enable vhost.hls of %s", vhost->arg0().c_str()); - } - // TODO: FIXME: required http server when hls storage is ram or both. - } // asprocess conflict with daemon if (get_asprocess() && get_daemon()) { diff --git a/trunk/src/app/srs_app_http_hooks.cpp b/trunk/src/app/srs_app_http_hooks.cpp index 45a396ae5..241eb8976 100644 --- a/trunk/src/app/srs_app_http_hooks.cpp +++ b/trunk/src/app/srs_app_http_hooks.cpp @@ -398,11 +398,11 @@ srs_error_t SrsHttpHooks::on_hls_notify(int cid, std::string url, SrsRequest* re int nb_read = 0; ISrsHttpResponseReader* br = msg->body_reader(); while (nb_read < nb_notify && !br->eof()) { - int nb_bytes = 0; + ssize_t nb_bytes = 0; if ((err = br->read(buf, nb_buf, &nb_bytes)) != srs_success) { break; } - nb_read += nb_bytes; + nb_read += (int)nb_bytes; } int spenttime = (int)(srsu2ms(srs_update_system_time()) - starttime); diff --git a/trunk/src/kernel/srs_kernel_codec.cpp b/trunk/src/kernel/srs_kernel_codec.cpp index 7351573c0..0bca66537 100644 --- a/trunk/src/kernel/srs_kernel_codec.cpp +++ b/trunk/src/kernel/srs_kernel_codec.cpp @@ -694,6 +694,7 @@ srs_error_t SrsFormat::video_avc_demux(SrsBuffer* stream, int64_t timestamp) nb_raw = stream->size() - stream->pos(); if (avc_packet_type == SrsVideoAvcFrameTraitSequenceHeader) { + // TODO: FIXME: Maybe we should ignore any error for parsing sps/pps. if ((err = avc_demux_sps_pps(stream)) != srs_success) { return srs_error_wrap(err, "demux SPS/PPS"); } @@ -708,6 +709,9 @@ srs_error_t SrsFormat::video_avc_demux(SrsBuffer* stream, int64_t timestamp) return err; } +// For media server, we don't care the codec, so we just try to parse sps-pps, and we could ignore any error if fail. +// LCOV_EXCL_START + srs_error_t SrsFormat::avc_demux_sps_pps(SrsBuffer* stream) { // AVCDecoderConfigurationRecord @@ -1016,6 +1020,8 @@ srs_error_t SrsFormat::avc_demux_sps_rbsp(char* rbsp, int nb_rbsp) return err; } +// LCOV_EXCL_STOP + srs_error_t SrsFormat::video_nalu_demux(SrsBuffer* stream) { srs_error_t err = srs_success; diff --git a/trunk/src/kernel/srs_kernel_mp4.hpp b/trunk/src/kernel/srs_kernel_mp4.hpp index 6a3e58966..b39ca1fff 100644 --- a/trunk/src/kernel/srs_kernel_mp4.hpp +++ b/trunk/src/kernel/srs_kernel_mp4.hpp @@ -866,6 +866,7 @@ public: // 8.6.6 Edit List Box // ISO_IEC_14496-12-base-format-2012.pdf, page 55 +// LCOV_EXCL_START class SrsMp4ElstEntry { public: @@ -890,6 +891,7 @@ public: virtual std::stringstream& dumps(std::stringstream& ss, SrsMp4DumpContext dc); virtual std::stringstream& dumps_detail(std::stringstream& ss, SrsMp4DumpContext dc); }; +// LCOV_EXCL_STOP // 8.6.6 Edit List Box (elst) // ISO_IEC_14496-12-base-format-2012.pdf, page 54 @@ -1564,6 +1566,7 @@ public: // 8.6.1.3 Composition Time to Sample Box (ctts), for Video. // ISO_IEC_14496-12-base-format-2012.pdf, page 49 +// LCOV_EXCL_START class SrsMp4CttsEntry { public: @@ -1580,6 +1583,7 @@ public: public: virtual std::stringstream& dumps_detail(std::stringstream& ss, SrsMp4DumpContext dc); }; +// LCOV_EXCL_STOP // 8.6.1.3 Composition Time to Sample Box (ctts), for Video. // ISO_IEC_14496-12-base-format-2012.pdf, page 49 diff --git a/trunk/src/kernel/srs_kernel_ts.cpp b/trunk/src/kernel/srs_kernel_ts.cpp index 5541923bc..c691e5175 100644 --- a/trunk/src/kernel/srs_kernel_ts.cpp +++ b/trunk/src/kernel/srs_kernel_ts.cpp @@ -810,6 +810,8 @@ SrsTsPacket* SrsTsPacket::create_pes_first(SrsTsContext* context, pkt->payload = pes; if (pcr >= 0) { + // Ignore coverage for PCR, we don't use it in HLS. + // LCOV_EXCL_START SrsTsAdaptationField* af = new SrsTsAdaptationField(pkt); pkt->adaptation_field = af; pkt->adaption_field_control = SrsTsAdaptationFieldTypeBoth; @@ -825,6 +827,7 @@ SrsTsPacket* SrsTsPacket::create_pes_first(SrsTsContext* context, af->adaptation_field_extension_flag = 0; af->program_clock_reference_base = pcr; af->program_clock_reference_extension = 0; + // LCOV_EXCL_STOP } pes->packet_start_code_prefix = 0x01; @@ -972,7 +975,9 @@ srs_error_t SrsTsAdaptationField::decode(SrsBuffer* stream) const1_value0 = (pcrv >> 9) & 0x3F; program_clock_reference_base = (pcrv >> 15) & 0x1ffffffffLL; } - + + // Ignore coverage for bellow, we don't use it in HLS. + // LCOV_EXCL_START if (OPCR_flag) { if (!stream->require(6)) { return srs_error_new(ERROR_STREAM_CASTER_TS_AF, "ts: demux af OPCR_flag"); @@ -1080,6 +1085,7 @@ srs_error_t SrsTsAdaptationField::decode(SrsBuffer* stream) nb_af_ext_reserved = adaptation_field_extension_length - (stream->pos() - pos_af_ext); stream->skip(nb_af_ext_reserved); } + // LCOV_EXCL_STOP nb_af_reserved = adaption_field_length - (stream->pos() - pos_af); stream->skip(nb_af_reserved); @@ -1143,7 +1149,9 @@ srs_error_t SrsTsAdaptationField::encode(SrsBuffer* stream) tmpv |= (splicing_point_flag << 2) & 0x04; tmpv |= (transport_private_data_flag << 1) & 0x02; stream->write_1bytes(tmpv); - + + // Ignore the coverage bellow, for we don't use them in HLS. + // LCOV_EXCL_START if (PCR_flag) { if (!stream->require(6)) { return srs_error_new(ERROR_STREAM_CASTER_TS_AF, "ts: mux af PCR_flag"); @@ -1236,6 +1244,7 @@ srs_error_t SrsTsAdaptationField::encode(SrsBuffer* stream) stream->skip(nb_af_ext_reserved); } } + // LCOV_EXCL_STOP if (nb_af_reserved) { stream->skip(nb_af_reserved); @@ -1460,7 +1469,10 @@ srs_error_t SrsTsPayloadPES::decode(SrsBuffer* stream, SrsTsMessage** ppmsg) msg->dts = dts; msg->pts = pts; } - + + // Ignore coverage bellow, for we don't use them in HLS. + // LCOV_EXCL_START + // 6B if (ESCR_flag) { ESCR_extension = 0; @@ -1588,6 +1600,8 @@ srs_error_t SrsTsPayloadPES::decode(SrsBuffer* stream, SrsTsMessage** ppmsg) } stream->skip(nb_stuffings); } + + // LCOV_EXCL_STOP // PES_packet_data_byte, page58. // the packet size contains the header size. @@ -1610,6 +1624,9 @@ srs_error_t SrsTsPayloadPES::decode(SrsBuffer* stream, SrsTsMessage** ppmsg) if ((err = msg->dump(stream, &nb_bytes)) != srs_success) { return srs_error_wrap(err, "dump pes"); } + + // Ignore coverage bellow, for we don't use them in HLS. + // LCOV_EXCL_START } else if (sid == SrsTsPESStreamIdProgramStreamMap || sid == SrsTsPESStreamIdPrivateStream2 || sid == SrsTsPESStreamIdEcmStream @@ -1633,6 +1650,8 @@ srs_error_t SrsTsPayloadPES::decode(SrsBuffer* stream, SrsTsMessage** ppmsg) nb_paddings = stream->size() - stream->pos(); stream->skip(nb_paddings); srs_info("ts: drop %dB padding bytes", nb_paddings); + + // LCOV_EXCL_STOP } else { int nb_drop = stream->size() - stream->pos(); stream->skip(nb_drop); @@ -1685,13 +1704,16 @@ int SrsTsPayloadPES::size() sz += additional_copy_info_flag? 1:0; sz += PES_CRC_flag? 2:0; sz += PES_extension_flag? 1:0; - + if (PES_extension_flag) { + // Ignore coverage bellow, for we don't use them in HLS. + // LCOV_EXCL_START sz += PES_private_data_flag? 16:0; sz += pack_header_field_flag ? 1 + pack_field.size() : 0; // 1+x bytes. sz += program_packet_sequence_counter_flag? 2:0; sz += P_STD_buffer_flag? 2:0; sz += PES_extension_flag_2 ? 1 + PES_extension_field.size() : 0; // 1+x bytes. + // LCOV_EXCL_STOP } PES_header_data_length = sz - PES_header_data_length; @@ -1802,6 +1824,9 @@ srs_error_t SrsTsPayloadPES::encode(SrsBuffer* stream) srs_warn("ts: sync dts=%" PRId64 ", pts=%" PRId64, dts, pts); } } + + // Ignore coverage bellow, for we don't use them in HLS. + // LCOV_EXCL_START // 6B if (ESCR_flag) { @@ -1861,6 +1886,8 @@ srs_error_t SrsTsPayloadPES::encode(SrsBuffer* stream) stream->skip(nb_stuffings); srs_warn("ts: demux PES, ignore the stuffings."); } + + // LCOV_EXCL_STOP return err; } diff --git a/trunk/src/kernel/srs_kernel_utility.cpp b/trunk/src/kernel/srs_kernel_utility.cpp index 4fab6985a..25a1683b8 100644 --- a/trunk/src/kernel/srs_kernel_utility.cpp +++ b/trunk/src/kernel/srs_kernel_utility.cpp @@ -568,11 +568,11 @@ int srs_do_create_dir_recursively(string dir) // create curren dir. // for srs-librtmp, @see https://github.com/ossrs/srs/issues/213 -#ifndef _WIN32 +#ifdef _WIN32 + if (::_mkdir(dir.c_str()) < 0) { +#else mode_t mode = S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IXOTH; if (::mkdir(dir.c_str(), mode) < 0) { -#else - if (::_mkdir(dir.c_str()) < 0) { #endif if (errno == EEXIST) { return ERROR_SYSTEM_DIR_EXISTS; diff --git a/trunk/src/libs/srs_lib_simple_socket.cpp b/trunk/src/libs/srs_lib_simple_socket.cpp index fb3249cfc..127d99200 100644 --- a/trunk/src/libs/srs_lib_simple_socket.cpp +++ b/trunk/src/libs/srs_lib_simple_socket.cpp @@ -187,7 +187,7 @@ int srs_hijack_io_set_recv_timeout(srs_hijack_io_t ctx, int64_t tm) SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; #ifdef _WIN32 - DWORD tv = (DWORD)(timeout_us/1000); + DWORD tv = (DWORD)(tm); // To convert tv to const char* to make VS2015 happy. if (setsockopt(skt->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) { @@ -229,7 +229,7 @@ int srs_hijack_io_set_send_timeout(srs_hijack_io_t ctx, int64_t tm) SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; #ifdef _WIN32 - DWORD tv = (DWORD)(timeout_us/1000); + DWORD tv = (DWORD)(tm); // To convert tv to const char* to make VS2015 happy. if (setsockopt(skt->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == -1) { diff --git a/trunk/src/protocol/srs_http_stack.hpp b/trunk/src/protocol/srs_http_stack.hpp index 9044f5549..34eb7ae45 100644 --- a/trunk/src/protocol/srs_http_stack.hpp +++ b/trunk/src/protocol/srs_http_stack.hpp @@ -26,6 +26,8 @@ #include +#include + // Default http listen port. #define SRS_DEFAULT_HTTP_PORT 80 @@ -160,7 +162,7 @@ public: // w->header()->set_content_type("text/plain; charset=utf-8"); // w->header()->set_content_length(msg.length()); // w->write_header(SRS_CONSTS_HTTP_OK); -// w->write((char*)msg.data(), (int)msg.length()); +// w->write((char*)msg.data(), (int)msg.length()); // write N times, N>0 // w->final_request(); // optional flush. // Usage 2, response with HTTP code only, zero content length. // ISrsHttpResponseWriter* w; // create or get response. @@ -215,7 +217,7 @@ public: }; // The reader interface for http response. -class ISrsHttpResponseReader +class ISrsHttpResponseReader : public ISrsReader { public: ISrsHttpResponseReader(); @@ -223,18 +225,6 @@ public: public: // Whether response read EOF. virtual bool eof() = 0; - // Read from the response body. - // @param data, the buffer to read data buffer to. - // @param nb_data, the max size of data buffer. - // @param nb_read, the actual read size of bytes. NULL to ignore. - // @remark when eof(), return error. - // @remark for some server, the content-length not specified and not chunked, - // which is actually the infinite chunked encoding, which after http header - // is http response data, it's ok for browser. that is, - // when user call this read, please ensure there is data to read(by content-length - // or by chunked), because the sdk never know whether there is no data or - // infinite chunked. - virtual srs_error_t read(char* data, int nb_data, int* nb_read) = 0; }; // Objects implementing the Handler interface can be diff --git a/trunk/src/service/srs_service_http_conn.cpp b/trunk/src/service/srs_service_http_conn.cpp index 9503c19d0..8a6a1b876 100644 --- a/trunk/src/service/srs_service_http_conn.cpp +++ b/trunk/src/service/srs_service_http_conn.cpp @@ -185,6 +185,10 @@ int SrsHttpParser::on_headers_complete(http_parser* parser) // save the parser when header parse completed. obj->state = SrsHttpParseStateHeaderComplete; + // We must update the body start when header complete, because sometimes we only got header. + // When we got the body start event, we will update it to much precious position. + obj->p_body_start = obj->buffer->bytes() + obj->buffer->size(); + srs_info("***HEADERS COMPLETE***"); // see http_parser.c:1570, return 1 to skip body. @@ -551,7 +555,7 @@ srs_error_t SrsHttpMessage::body_read_all(string& body) // whatever, read util EOF. while (!_body->eof()) { - int nb_read = 0; + ssize_t nb_read = 0; if ((err = _body->read(buf, SRS_HTTP_READ_CACHE_BYTES, &nb_read)) != srs_success) { return srs_error_wrap(err, "read body"); } @@ -699,7 +703,9 @@ srs_error_t SrsHttpResponseWriter::write(char* data, int size) if (hdr->content_type().empty()) { hdr->set_content_type("text/plain; charset=utf-8"); } - hdr->set_content_length(size); + if (hdr->content_length() == -1) { + hdr->set_content_length(size); + } write_header(SRS_CONSTS_HTTP_OK); } @@ -912,7 +918,7 @@ bool SrsHttpResponseReader::eof() return is_eof; } -srs_error_t SrsHttpResponseReader::read(char* data, int nb_data, int* nb_read) +srs_error_t SrsHttpResponseReader::read(void* data, size_t nb_data, ssize_t* nb_read) { srs_error_t err = srs_success; @@ -927,7 +933,7 @@ srs_error_t SrsHttpResponseReader::read(char* data, int nb_data, int* nb_read) // read by specified content-length if (owner->content_length() != -1) { - int max = (int)owner->content_length() - (int)nb_total_read; + size_t max = (size_t)owner->content_length() - (size_t)nb_total_read; if (max <= 0) { is_eof = true; return err; @@ -951,7 +957,7 @@ srs_error_t SrsHttpResponseReader::read(char* data, int nb_data, int* nb_read) return err; } -srs_error_t SrsHttpResponseReader::read_chunked(char* data, int nb_data, int* nb_read) +srs_error_t SrsHttpResponseReader::read_chunked(void* data, size_t nb_data, ssize_t* nb_read) { srs_error_t err = srs_success; @@ -1003,32 +1009,34 @@ srs_error_t SrsHttpResponseReader::read_chunked(char* data, int nb_data, int* nb } // all bytes in chunk is left now. - nb_chunk = nb_left_chunk = ilength; + nb_chunk = nb_left_chunk = (size_t)ilength; } if (nb_chunk <= 0) { // for the last chunk, eof. is_eof = true; + *nb_read = 0; } else { // for not the last chunk, there must always exists bytes. // left bytes in chunk, read some. srs_assert(nb_left_chunk); - int nb_bytes = srs_min(nb_left_chunk, nb_data); - err = read_specified(data, nb_bytes, &nb_bytes); + size_t nb_bytes = srs_min(nb_left_chunk, nb_data); + err = read_specified(data, nb_bytes, (ssize_t*)&nb_bytes); // the nb_bytes used for output already read size of bytes. if (nb_read) { *nb_read = nb_bytes; } nb_left_chunk -= nb_bytes; - - // error or still left bytes in chunk, ignore and read in future. + if (err != srs_success) { return srs_error_wrap(err, "read specified"); } + + // If still left bytes in chunk, ignore and read in future. if (nb_left_chunk > 0) { - return srs_error_new(ERROR_HTTP_INVALID_CHUNK_HEADER, "read specified left=%d", nb_left_chunk); + return err; } } @@ -1041,7 +1049,7 @@ srs_error_t SrsHttpResponseReader::read_chunked(char* data, int nb_data, int* nb return err; } -srs_error_t SrsHttpResponseReader::read_specified(char* data, int nb_data, int* nb_read) +srs_error_t SrsHttpResponseReader::read_specified(void* data, size_t nb_data, ssize_t* nb_read) { srs_error_t err = srs_success; @@ -1052,7 +1060,7 @@ srs_error_t SrsHttpResponseReader::read_specified(char* data, int nb_data, int* } } - int nb_bytes = srs_min(nb_data, buffer->size()); + size_t nb_bytes = srs_min(nb_data, (size_t)buffer->size()); // read data to buffer. srs_assert(nb_bytes); diff --git a/trunk/src/service/srs_service_http_conn.hpp b/trunk/src/service/srs_service_http_conn.hpp index 59fb50fb7..f8eaaa23f 100644 --- a/trunk/src/service/srs_service_http_conn.hpp +++ b/trunk/src/service/srs_service_http_conn.hpp @@ -261,9 +261,9 @@ private: SrsFastStream* buffer; bool is_eof; // The left bytes in chunk. - int nb_left_chunk; + size_t nb_left_chunk; // The number of bytes of current chunk. - int nb_chunk; + size_t nb_chunk; // Already read total bytes. int64_t nb_total_read; public: @@ -274,10 +274,10 @@ public: // Interface ISrsHttpResponseReader public: virtual bool eof(); - virtual srs_error_t read(char* data, int nb_data, int* nb_read); + virtual srs_error_t read(void* buf, size_t size, ssize_t* nread); private: - virtual srs_error_t read_chunked(char* data, int nb_data, int* nb_read); - virtual srs_error_t read_specified(char* data, int nb_data, int* nb_read); + virtual srs_error_t read_chunked(void* buf, size_t size, ssize_t* nread); + virtual srs_error_t read_specified(void* buf, size_t size, ssize_t* nread); }; #endif diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index 778db7e63..4ad2cbe68 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -34,6 +34,60 @@ using namespace std; #include #include +class MockMSegmentsReader : public ISrsReader +{ +public: + vector in_bytes; +public: + MockMSegmentsReader(); + virtual ~MockMSegmentsReader(); +public: + virtual void append(string b) { + in_bytes.push_back(b); + } + virtual srs_error_t read(void* buf, size_t size, ssize_t* nread); +}; + +MockMSegmentsReader::MockMSegmentsReader() +{ +} + +MockMSegmentsReader::~MockMSegmentsReader() +{ +} + +srs_error_t MockMSegmentsReader::read(void* buf, size_t size, ssize_t* nread) +{ + srs_error_t err = srs_success; + + for (;;) { + if (in_bytes.empty() || size <= 0) { + return srs_error_new(-1, "EOF"); + } + + string v = in_bytes[0]; + if (v.empty()) { + in_bytes.erase(in_bytes.begin()); + continue; + } + + int nn = srs_min(size, v.length()); + memcpy(buf, v.data(), nn); + if (nread) { + *nread = nn; + } + + if (nn < (int)v.length()) { + in_bytes[0] = string(v.data() + nn, v.length() - nn); + } else { + in_bytes.erase(in_bytes.begin()); + } + break; + } + + return err; +} + class MockResponseWriter : virtual public ISrsHttpResponseWriter, virtual public ISrsHttpHeaderFilter { public: @@ -192,12 +246,41 @@ VOID TEST(ProtocolHTTPTest, ResponseDetect) VOID TEST(ProtocolHTTPTest, ResponseWriter) { + srs_error_t err; + + // Directly final_request, should work. + if (true) { + MockResponseWriter w; + w.header()->set_content_length(0); + HELPER_ASSERT_SUCCESS(w.final_request()); + } + + // Directly final_request, should work. + if (true) { + MockResponseWriter w; + HELPER_ASSERT_SUCCESS(w.final_request()); + } + + // When content-length is set, we could write multiple parts. + if (true) { + MockResponseWriter w; + + char msg[] = "Hello, world!"; + w.header()->set_content_length(sizeof(msg) - 1); + HELPER_EXPECT_SUCCESS(w.write((char*)msg, 5)); + HELPER_EXPECT_SUCCESS(w.write((char*)(msg+5), 2)); + HELPER_EXPECT_SUCCESS(w.write((char*)(msg+7), 5)); + HELPER_EXPECT_SUCCESS(w.write((char*)(msg+12), 1)); + + __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w); + } + // If directly write string, response with content-length. if (true) { MockResponseWriter w; char msg[] = "Hello, world!"; - w.write((char*)msg, sizeof(msg) - 1); + HELPER_EXPECT_SUCCESS(w.write((char*)msg, sizeof(msg) - 1)); __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w); } @@ -211,8 +294,8 @@ VOID TEST(ProtocolHTTPTest, ResponseWriter) w.header()->set_content_type("text/plain; charset=utf-8"); w.header()->set_content_length(sizeof(msg) - 1); w.write_header(SRS_CONSTS_HTTP_OK); - w.write((char*)msg, sizeof(msg) - 1); - w.final_request(); + HELPER_EXPECT_SUCCESS(w.write((char*)msg, sizeof(msg) - 1)); + HELPER_ASSERT_SUCCESS(w.final_request()); __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w); } @@ -223,7 +306,7 @@ VOID TEST(ProtocolHTTPTest, ResponseWriter) w.header()->set_content_length(0); w.write_header(SRS_CONSTS_HTTP_OK); - w.final_request(); + HELPER_ASSERT_SUCCESS(w.final_request()); __MOCK_HTTP_EXPECT_STREQ(200, "", w); } @@ -234,7 +317,7 @@ VOID TEST(ProtocolHTTPTest, ResponseWriter) w.header()->set_content_length(0); w.write_header(SRS_CONSTS_HTTP_OK); - w.write(NULL, 0); + HELPER_EXPECT_SUCCESS(w.write(NULL, 0)); __MOCK_HTTP_EXPECT_STREQ(200, "", w); } @@ -245,9 +328,9 @@ VOID TEST(ProtocolHTTPTest, ResponseWriter) w.header()->set_content_type("application/octet-stream"); w.write_header(SRS_CONSTS_HTTP_OK); - w.write((char*)"Hello", 5); - w.write((char*)", world!", 8); - w.final_request(); + HELPER_EXPECT_SUCCESS(w.write((char*)"Hello", 5)); + HELPER_EXPECT_SUCCESS(w.write((char*)", world!", 8)); + HELPER_ASSERT_SUCCESS(w.final_request()); __MOCK_HTTP_EXPECT_STREQ2(200, "5\r\nHello\r\n8\r\n, world!\r\n0\r\n\r\n", w); } @@ -256,8 +339,8 @@ VOID TEST(ProtocolHTTPTest, ResponseWriter) w.header()->set_content_type("application/octet-stream"); w.write_header(SRS_CONSTS_HTTP_OK); - w.write((char*)"Hello, world!", 13); - w.final_request(); + HELPER_EXPECT_SUCCESS(w.write((char*)"Hello, world!", 13)); + HELPER_ASSERT_SUCCESS(w.final_request()); __MOCK_HTTP_EXPECT_STREQ2(200, "d\r\nHello, world!\r\n0\r\n\r\n", w); } @@ -266,8 +349,8 @@ VOID TEST(ProtocolHTTPTest, ResponseWriter) w.header()->set_content_type("application/octet-stream"); w.write_header(SRS_CONSTS_HTTP_OK); - w.write((char*)"Hello, world!", 13); - w.final_request(); + HELPER_EXPECT_SUCCESS(w.write((char*)"Hello, world!", 13)); + HELPER_ASSERT_SUCCESS(w.final_request()); __MOCK_HTTP_EXPECT_STREQ2(200, "d\r\nHello, world!\r\n0\r\n\r\n", w); } @@ -275,18 +358,176 @@ VOID TEST(ProtocolHTTPTest, ResponseWriter) // If directly write empty string, sent an empty response with content-length 0 if (true) { MockResponseWriter w; - w.write(NULL, 0); + HELPER_EXPECT_SUCCESS(w.write(NULL, 0)); __MOCK_HTTP_EXPECT_STREQ(200, "", w); } // If directly final request, response with EOF of chunked. if (true) { MockResponseWriter w; - w.final_request(); + HELPER_ASSERT_SUCCESS(w.final_request()); __MOCK_HTTP_EXPECT_STREQ2(200, "0\r\n\r\n", w); } } +VOID TEST(ProtocolHTTPTest, ChunkSmallBuffer) +{ + srs_error_t err; + + // No chunk end flag, error. + if (true) { + MockMSegmentsReader io; + io.append(mock_http_response2(200, "0d\r\n")); + io.append("Hello, world!\r\n"); + + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); + + char buf[32]; ssize_t nread = 0; + ISrsHttpResponseReader* r = msg->body_reader(); + + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(r->read(buf, 32, &nread)); + EXPECT_EQ(13, nread); + EXPECT_STREQ("Hello, world!", buf); + + err = r->read(buf, 32, &nread); + EXPECT_EQ(-1, srs_error_code(err)); + srs_freep(err); + + srs_freep(msg); + } + + // Read util EOF(nread=0) or err(ERROR_HTTP_RESPONSE_EOF). + if (true) { + MockMSegmentsReader io; + io.append(mock_http_response2(200, "0d\r\n")); + io.append("Hello, world!\r\n0\r\n\r\n"); + + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); + + char buf[32]; ssize_t nread = 0; + ISrsHttpResponseReader* r = msg->body_reader(); + + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(r->read(buf, 32, &nread)); + EXPECT_EQ(13, nread); + EXPECT_STREQ("Hello, world!", buf); + + HELPER_ASSERT_SUCCESS(r->read(buf, 32, &nread)); + EXPECT_EQ(0, nread); + + err = r->read(buf, 32, &nread); + EXPECT_EQ(ERROR_HTTP_RESPONSE_EOF, srs_error_code(err)); + srs_freep(err); + + srs_freep(msg); + } + + // In this case, we only got header complete, no body start event. + if (true) { + MockMSegmentsReader io; + io.append(mock_http_response2(200, "0d\r\n")); + io.append("Hello, world!\r\n0\r\n\r\n"); + + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); + + char buf[32]; ssize_t nread = 0; + ISrsHttpResponseReader* r = msg->body_reader(); + + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(r->read(buf, 32, &nread)); + EXPECT_EQ(13, nread); + EXPECT_STREQ("Hello, world!", buf); + + srs_freep(msg); + } +} + +VOID TEST(ProtocolHTTPTest, ClientSmallBuffer) +{ + srs_error_t err; + + // The chunk content is sent in multiple parts. + if (true) { + MockMSegmentsReader io; + io.append(mock_http_response2(200, "0d\r\n")); + io.append("Hello,"); + io.append(" world!"); + io.append("\r\n0\r\n\r\n"); + + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); + + char buf[32]; ssize_t nread = 0; + ISrsHttpResponseReader* r = msg->body_reader(); + + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(r->read(buf, 32, &nread)); + EXPECT_EQ(6, nread); + EXPECT_STREQ("Hello,", buf); + + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(r->read(buf, 32, &nread)); + EXPECT_EQ(7, nread); + EXPECT_STREQ(" world!", buf); + + srs_freep(msg); + } + + // The chunk size is sent separately before chunk content. + if (true) { + MockMSegmentsReader io; + io.append(mock_http_response2(200, "0d\r\n")); + io.append("Hello, world!\r\n0\r\n\r\n"); + + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); + + char buf[32]; ssize_t nread = 0; + ISrsHttpResponseReader* r = msg->body_reader(); + + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(r->read(buf, 32, &nread)); + EXPECT_EQ(13, nread); + EXPECT_STREQ("Hello, world!", buf); + + srs_freep(msg); + } + + // If buffer is smaller than chunk, we could read N times to get the whole chunk. + if (true) { + MockBufferIO io; io.append(mock_http_response2(200, "0d\r\nHello, world!\r\n0\r\n\r\n")); + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); + + char buf[32]; ssize_t nread = 0; + ISrsHttpResponseReader* r = msg->body_reader(); + + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(r->read(buf, 5, &nread)); + EXPECT_EQ(5, nread); + EXPECT_STREQ("Hello", buf); + + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(r->read(buf, 7, &nread)); + EXPECT_EQ(7, nread); + EXPECT_STREQ(", world", buf); + + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(r->read(buf, 7, &nread)); + EXPECT_EQ(1, nread); + EXPECT_STREQ("!", buf); + + HELPER_ASSERT_SUCCESS(r->read(buf, 7, &nread)); + EXPECT_EQ(0, nread); + + srs_freep(msg); + } +} + VOID TEST(ProtocolHTTPTest, ClientRequest) { srs_error_t err; @@ -1157,57 +1398,6 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) } } -class MockMSegmentsReader : public ISrsReader -{ -public: - vector in_bytes; -public: - MockMSegmentsReader(); - virtual ~MockMSegmentsReader(); -public: - virtual srs_error_t read(void* buf, size_t size, ssize_t* nread); -}; - -MockMSegmentsReader::MockMSegmentsReader() -{ -} - -MockMSegmentsReader::~MockMSegmentsReader() -{ -} - -srs_error_t MockMSegmentsReader::read(void* buf, size_t size, ssize_t* nread) -{ - srs_error_t err = srs_success; - - for (;;) { - if (in_bytes.empty() || size <= 0) { - return srs_error_new(-1, "EOF"); - } - - string v = in_bytes[0]; - if (v.empty()) { - in_bytes.erase(in_bytes.begin()); - continue; - } - - int nn = srs_min(size, v.length()); - memcpy(buf, v.data(), nn); - if (nread) { - *nread = nn; - } - - if (nn < (int)v.length()) { - in_bytes[0] = string(v.data() + nn, v.length() - nn); - } else { - in_bytes.erase(in_bytes.begin()); - } - break; - } - - return err; -} - VOID TEST(ProtocolHTTPTest, MSegmentsReader) { srs_error_t err; diff --git a/trunk/src/utest/srs_utest_kernel.cpp b/trunk/src/utest/srs_utest_kernel.cpp index 3a994d064..47908ddae 100644 --- a/trunk/src/utest/srs_utest_kernel.cpp +++ b/trunk/src/utest/srs_utest_kernel.cpp @@ -2721,6 +2721,12 @@ VOID TEST(KernelErrorTest, CoverAll) EXPECT_TRUE(srs_is_client_gracefully_close(err)); srs_freep(err); } + + if (true) { + srs_error_t err = srs_error_new(ERROR_HTTP_STREAM_EOF, "graceful close error"); + EXPECT_TRUE(srs_is_server_gracefully_close(err)); + srs_freep(err); + } if (true) { srs_error_t err = srs_error_wrap(srs_error_new(ERROR_CONTROL_RTMP_CLOSE, "control error"), "wrapped");