diff --git a/README.md b/README.md index 3ddee5355..930a8dd1f 100755 --- a/README.md +++ b/README.md @@ -147,6 +147,8 @@ For previous versions, please read: ## V3 changes +* v3.0, 2019-12-20, Fix [#1508][bug #1508], http-client support read chunked response. 3.0.76 +* v3.0, 2019-12-20, For [#1508][bug #1508], refactor srs_is_digital, support all zeros. * v3.0, 2019-12-19, [3.0 alpha5(3.0.75)][r3.0a5] released. 115362 lines. * v3.0, 2019-12-19, Refine the RTMP iovs cache increasing to much faster. * v3.0, 2019-12-19, Fix [#1524][bug #1524], memory leak for amf0 strict array. 3.0.75 @@ -1543,6 +1545,7 @@ Winlin [bug #1506]: https://github.com/ossrs/srs/issues/1506 [bug #1520]: https://github.com/ossrs/srs/issues/1520 [bug #1223]: https://github.com/ossrs/srs/issues/1223 +[bug #1508]: https://github.com/ossrs/srs/issues/1508 [bug #xxxxxxxxxxxxx]: https://github.com/ossrs/srs/issues/xxxxxxxxxxxxx [bug #1111]: https://github.com/ossrs/srs/issues/1111 diff --git a/trunk/auto/apps.sh b/trunk/auto/apps.sh index 59fc92672..aaccac70d 100755 --- a/trunk/auto/apps.sh +++ b/trunk/auto/apps.sh @@ -23,6 +23,8 @@ echo "# build ${APP_TARGET}" >> ${FILE} # generate the binary depends, for example: # srs: objs/srs echo "${BUILD_KEY}: ${APP_TARGET}" >> ${FILE} +echo "" >> ${FILE} + # the link commands, for example: # objs/srs: objs/src/core/srs_core.o echo -n "${APP_TARGET}: " >> ${FILE} @@ -88,5 +90,6 @@ done # link options. echo -n "${LINK_OPTIONS}" >> ${FILE} echo "" >> ${FILE} +echo "" >> ${FILE} echo -n "Generate app ${APP_NAME} ok"; echo '!'; diff --git a/trunk/auto/libs.sh b/trunk/auto/libs.sh index 89f4e8ccd..af1103414 100755 --- a/trunk/auto/libs.sh +++ b/trunk/auto/libs.sh @@ -19,6 +19,7 @@ echo "Generating lib ${LIB_NAME} depends." echo "" >> ${FILE} echo "# archive library ${LIB_TAGET_STATIC}" >> ${FILE} echo "${BUILD_KEY}: ${LIB_TAGET_STATIC}" >> ${FILE} +echo "" >> ${FILE} # build depends echo -n "${LIB_TAGET_STATIC}: " >> ${FILE} diff --git a/trunk/configure b/trunk/configure index 23119b755..bcffc0dc3 100755 --- a/trunk/configure +++ b/trunk/configure @@ -429,13 +429,13 @@ for SRS_MODULE in ${SRS_MODULES[*]}; do # if export librtmp, donot build the bravo-ingest. if [ $SRS_EXPORT_LIBRTMP_PROJECT != NO ]; then cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} -$SRS_MODULE_NAME: _prepare_dir +$SRS_MODULE_NAME: _prepare_dir server @echo "Ingore the $SRS_MODULE_NAME for srs-librtmp" END else cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} -$SRS_MODULE_NAME: _prepare_dir +$SRS_MODULE_NAME: _prepare_dir server @echo "Build the $SRS_MODULE_NAME over SRS" \$(MAKE) -f ${SRS_OBJS_DIR}/${SRS_MAKEFILE} $SRS_MODULE_NAME diff --git a/trunk/src/app/srs_app_utility.cpp b/trunk/src/app/srs_app_utility.cpp index 6ea2ba0f8..9c6343860 100644 --- a/trunk/src/app/srs_app_utility.cpp +++ b/trunk/src/app/srs_app_utility.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #ifdef SRS_OSX @@ -1158,17 +1157,6 @@ string srs_get_peer_ip(int fd) return std::string(saddr); } -bool srs_is_digit_number(const string& str) -{ - if (str.empty()) { - return false; - } - - int v = ::atoi(str.c_str()); - int powv = (int)pow(10, str.length() - 1); - return v / powv >= 1 && v / powv <= 9; -} - bool srs_is_boolean(const string& str) { return str == "true" || str == "false"; diff --git a/trunk/src/app/srs_app_utility.hpp b/trunk/src/app/srs_app_utility.hpp index e058e0a93..5e4d09afe 100644 --- a/trunk/src/app/srs_app_utility.hpp +++ b/trunk/src/app/srs_app_utility.hpp @@ -640,12 +640,6 @@ extern int srs_get_local_port(int fd); // Where peer ip is the client public ip which connected to server. extern std::string srs_get_peer_ip(int fd); -// Whether string is digit number -// is_digit("1234567890") === true -// is_digit("0123456789") === false -// is_digit("1234567890a") === false -// is_digit("a1234567890") === false -extern bool srs_is_digit_number(const std::string& str); // Whether string is boolean // is_bool("true") == true // is_bool("false") == true diff --git a/trunk/src/core/srs_core.hpp b/trunk/src/core/srs_core.hpp index 182235ff9..ad70e2fa2 100644 --- a/trunk/src/core/srs_core.hpp +++ b/trunk/src/core/srs_core.hpp @@ -27,7 +27,7 @@ // The version config. #define VERSION_MAJOR 3 #define VERSION_MINOR 0 -#define VERSION_REVISION 75 +#define VERSION_REVISION 76 // The macros generated by configure script. #include diff --git a/trunk/src/protocol/srs_protocol_stream.cpp b/trunk/src/protocol/srs_protocol_stream.cpp index 53bee1535..dfc24d7e2 100755 --- a/trunk/src/protocol/srs_protocol_stream.cpp +++ b/trunk/src/protocol/srs_protocol_stream.cpp @@ -52,14 +52,14 @@ IMergeReadHandler::~IMergeReadHandler() } #endif -SrsFastStream::SrsFastStream() +SrsFastStream::SrsFastStream(int size) { #ifdef SRS_PERF_MERGED_READ merged_read = false; _handler = NULL; #endif - nb_buffer = SRS_DEFAULT_RECV_BUFFER_SIZE; + nb_buffer = size? size:SRS_DEFAULT_RECV_BUFFER_SIZE; buffer = (char*)malloc(nb_buffer); p = end = buffer; } @@ -84,8 +84,7 @@ void SrsFastStream::set_buffer(int buffer_size) { // never exceed the max size. if (buffer_size > SRS_MAX_SOCKET_BUFFER) { - srs_warn("limit the user-space buffer from %d to %d", - buffer_size, SRS_MAX_SOCKET_BUFFER); + srs_warn("limit buffer size %d to %d", buffer_size, SRS_MAX_SOCKET_BUFFER); } // the user-space buffer size limit to a max value. @@ -152,7 +151,7 @@ srs_error_t SrsFastStream::grow(ISrsReader* reader, int required_size) srs_assert(nb_exists_bytes >= 0); // resize the space when no left space. - if (nb_free_space < required_size - nb_exists_bytes) { + if (nb_exists_bytes + nb_free_space < required_size) { // reset or move to get more space. if (!nb_exists_bytes) { // reset when buffer is empty. @@ -168,7 +167,7 @@ srs_error_t SrsFastStream::grow(ISrsReader* reader, int required_size) // check whether enough free space in buffer. nb_free_space = (int)(buffer + nb_buffer - end); - if (nb_free_space < required_size - nb_exists_bytes) { + if (nb_exists_bytes + nb_free_space < required_size) { return srs_error_new(ERROR_READER_BUFFER_OVERFLOW, "overflow, required=%d, max=%d, left=%d", required_size, nb_buffer, nb_free_space); } } diff --git a/trunk/src/protocol/srs_protocol_stream.hpp b/trunk/src/protocol/srs_protocol_stream.hpp index 17d935fdb..247d75aef 100644 --- a/trunk/src/protocol/srs_protocol_stream.hpp +++ b/trunk/src/protocol/srs_protocol_stream.hpp @@ -84,7 +84,8 @@ private: // the size of buffer. int nb_buffer; public: - SrsFastStream(); + // If buffer is 0, use default size. + SrsFastStream(int size=0); virtual ~SrsFastStream(); public: /** @@ -128,7 +129,7 @@ public: virtual void skip(int size); public: /** - * grow buffer to the required size, loop to read from skt to fill. + * grow buffer to atleast required size, loop to read from skt to fill. * @param reader, read more bytes from reader to fill the buffer to required size. * @param required_size, loop to fill to ensure buffer size to required. * @return an int error code, error if required_size negative. diff --git a/trunk/src/service/srs_service_http_conn.cpp b/trunk/src/service/srs_service_http_conn.cpp index 3d8d1df06..9503c19d0 100644 --- a/trunk/src/service/srs_service_http_conn.cpp +++ b/trunk/src/service/srs_service_http_conn.cpp @@ -40,6 +40,8 @@ SrsHttpParser::SrsHttpParser() { buffer = new SrsFastStream(); header = NULL; + + p_body_start = p_header_tail = NULL; } SrsHttpParser::~SrsHttpParser() @@ -80,7 +82,7 @@ srs_error_t SrsHttpParser::parse_message(ISrsReader* reader, ISrsHttpMessage** p state = SrsHttpParseStateInit; hp_header = http_parser(); // The body that we have read from cache. - pbody = NULL; + p_body_start = p_header_tail = NULL; // We must reset the field name and value, because we may get a partial value in on_header_value. field_name = field_value = ""; // The header of the request. @@ -115,15 +117,26 @@ srs_error_t SrsHttpParser::parse_message_imp(ISrsReader* reader) while (true) { if (buffer->size() > 0) { - ssize_t nparsed = http_parser_execute(&parser, &settings, buffer->bytes(), buffer->size()); - if (buffer->size() != nparsed) { - return srs_error_new(ERROR_HTTP_PARSE_HEADER, "parse failed, nparsed=%d, size=%d", nparsed, buffer->size()); + ssize_t consumed = http_parser_execute(&parser, &settings, buffer->bytes(), buffer->size()); + + // The error is set in http_errno. + enum http_errno code; + if ((code = HTTP_PARSER_ERRNO(&parser)) != HPE_OK) { + return srs_error_new(ERROR_HTTP_PARSE_HEADER, "parse %dB, nparsed=%d, err=%d/%s %s", + buffer->size(), consumed, http_errno_name(code), http_errno_description(code)); } - // The consumed size, does not include the body. - ssize_t consumed = nparsed; - if (pbody && buffer->bytes() < pbody) { - consumed = pbody - buffer->bytes(); + // When buffer consumed these bytes, it's dropped so the new ptr is actually the HTTP body. But http-parser + // doesn't indicate the specific sizeof header, so we must finger it out. + // @remark We shouldn't use on_body, because it only works for normal case, and losts the chunk header and length. + // @see https://github.com/ossrs/srs/issues/1508 + if (p_header_tail && buffer->bytes() < p_body_start) { + for (const char* p = p_header_tail; p <= p_body_start - 4; p++) { + if (p[0] == SRS_CONSTS_CR && p[1] == SRS_CONSTS_LF && p[2] == SRS_CONSTS_CR && p[3] == SRS_CONSTS_LF) { + consumed = p + 4 - buffer->bytes(); + break; + } + } } srs_info("size=%d, nparsed=%d, consumed=%d", buffer->size(), (int)nparsed, consumed); @@ -199,6 +212,11 @@ int SrsHttpParser::on_url(http_parser* parser, const char* at, size_t length) if (length > 0) { obj->url = string(at, (int)length); } + + // When header parsed, we must save the position of start for body, + // because we have to consume the header in buffer. + // @see https://github.com/ossrs/srs/issues/1508 + obj->p_header_tail = at; srs_info("Method: %d, Url: %.*s", parser->method, (int)length, at); @@ -218,6 +236,11 @@ int SrsHttpParser::on_header_field(http_parser* parser, const char* at, size_t l if (length > 0) { obj->field_name.append(at, (int)length); } + + // When header parsed, we must save the position of start for body, + // because we have to consume the header in buffer. + // @see https://github.com/ossrs/srs/issues/1508 + obj->p_header_tail = at; srs_info("Header field(%d bytes): %.*s", (int)length, (int)length, at); return 0; @@ -231,6 +254,11 @@ int SrsHttpParser::on_header_value(http_parser* parser, const char* at, size_t l if (length > 0) { obj->field_value.append(at, (int)length); } + + // When header parsed, we must save the position of start for body, + // because we have to consume the header in buffer. + // @see https://github.com/ossrs/srs/issues/1508 + obj->p_header_tail = at; srs_info("Header value(%d bytes): %.*s", (int)length, (int)length, at); return 0; @@ -244,9 +272,10 @@ int SrsHttpParser::on_body(http_parser* parser, const char* at, size_t length) // save the parser when body parsed. obj->state = SrsHttpParseStateBody; - // Save the body position. - obj->pbody = at; - + // Used to discover the header length. + // @see https://github.com/ossrs/srs/issues/1508 + obj->p_body_start = at; + srs_info("Body: %.*s", (int)length, at); return 0; @@ -962,11 +991,15 @@ srs_error_t SrsHttpResponseReader::read_chunked(char* data, int nb_data, int* nb // it's ok to set the pos and pos+1 to NULL. at[length - 1] = 0; at[length - 2] = 0; - + // size is the bytes size, excludes the chunk header and end CRLF. - int ilength = (int)::strtol(at, NULL, 16); - if (ilength < 0) { - return srs_error_new(ERROR_HTTP_INVALID_CHUNK_HEADER, "invalid length=%d", ilength); + // @remark It must be hex format, please read https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding#Directives + // @remark For strtol, note that: If no conversion could be performed, 0 is returned and the global variable errno is set to EINVAL. + char* at_parsed = at; errno = 0; + int ilength = (int)::strtol(at, &at_parsed, 16); + if (ilength < 0 || errno != 0 || at_parsed - at != length - 2) { + return srs_error_new(ERROR_HTTP_INVALID_CHUNK_HEADER, "invalid length %s as %d, parsed=%.*s, errno=%d", + at, ilength, (int)(at_parsed-at), at, errno); } // all bytes in chunk is left now. diff --git a/trunk/src/service/srs_service_http_conn.hpp b/trunk/src/service/srs_service_http_conn.hpp index 997e26b66..59fb50fb7 100644 --- a/trunk/src/service/srs_service_http_conn.hpp +++ b/trunk/src/service/srs_service_http_conn.hpp @@ -55,7 +55,11 @@ private: http_parser hp_header; std::string url; SrsHttpHeader* header; - const char* pbody; +private: + // Point to the start of body. + const char* p_body_start; + // To discover the length of header, point to the last few bytes in header. + const char* p_header_tail; public: SrsHttpParser(); virtual ~SrsHttpParser(); diff --git a/trunk/src/service/srs_service_utility.cpp b/trunk/src/service/srs_service_utility.cpp index 434cd9d4d..e6312d272 100644 --- a/trunk/src/service/srs_service_utility.cpp +++ b/trunk/src/service/srs_service_utility.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include #include using namespace std; @@ -48,6 +50,28 @@ bool srs_string_is_rtmp(string url) return srs_string_starts_with(url, "rtmp://"); } +bool srs_is_digit_number(const string& str) +{ + if (str.empty()) { + return false; + } + + const char* p = str.c_str(); + const char* p_end = str.data() + str.length(); + for (; p < p_end; p++) { + if (*p != '0') { + break; + } + } + if (p == p_end) { + return true; + } + + int64_t v = ::atoll(p); + int64_t powv = (int64_t)pow(10, p_end - p - 1); + return v / powv >= 1 && v / powv <= 9; +} + // we detect all network device as internet or intranet device, by its ip address. // key is device name, for instance, eth0 // value is whether internet, for instance, true. diff --git a/trunk/src/service/srs_service_utility.hpp b/trunk/src/service/srs_service_utility.hpp index 86a8e85e8..d9896c13d 100644 --- a/trunk/src/service/srs_service_utility.hpp +++ b/trunk/src/service/srs_service_utility.hpp @@ -36,6 +36,18 @@ extern bool srs_string_is_http(std::string url); extern bool srs_string_is_rtmp(std::string url); +// Whether string is digit number +// is_digit("0") === true +// is_digit("0000000000") === true +// is_digit("1234567890") === true +// is_digit("0123456789") === true +// is_digit("1234567890a") === false +// is_digit("a1234567890") === false +// is_digit("10e3") === false +// is_digit("!1234567890") === false +// is_digit("") === false +extern bool srs_is_digit_number(const std::string& str); + // Get local ip, fill to @param ips extern std::vector& srs_get_local_ips(); diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index c1d49fcc4..778db7e63 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -251,6 +251,26 @@ VOID TEST(ProtocolHTTPTest, ResponseWriter) __MOCK_HTTP_EXPECT_STREQ2(200, "5\r\nHello\r\n8\r\n, world!\r\n0\r\n\r\n", w); } + if (true) { + MockResponseWriter w; + + w.header()->set_content_type("application/octet-stream"); + w.write_header(SRS_CONSTS_HTTP_OK); + w.write((char*)"Hello, world!", 13); + w.final_request(); + + __MOCK_HTTP_EXPECT_STREQ2(200, "d\r\nHello, world!\r\n0\r\n\r\n", w); + } + if (true) { + MockResponseWriter w; + + w.header()->set_content_type("application/octet-stream"); + w.write_header(SRS_CONSTS_HTTP_OK); + w.write((char*)"Hello, world!", 13); + w.final_request(); + + __MOCK_HTTP_EXPECT_STREQ2(200, "d\r\nHello, world!\r\n0\r\n\r\n", w); + } // If directly write empty string, sent an empty response with content-length 0 if (true) { @@ -267,6 +287,42 @@ VOID TEST(ProtocolHTTPTest, ResponseWriter) } } +VOID TEST(ProtocolHTTPTest, ClientRequest) +{ + srs_error_t err; + + // Normal case, with chunked encoding. + 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)); + string res; HELPER_ASSERT_SUCCESS(msg->body_read_all(res)); + EXPECT_EQ(200, msg->status_code()); + EXPECT_STREQ("Hello, world!", res.c_str()); + srs_freep(msg); + } + if (true) { + MockBufferIO io; io.append(mock_http_response2(200, "6\r\nHello!\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)); + string res; HELPER_ASSERT_SUCCESS(msg->body_read_all(res)); + EXPECT_EQ(200, msg->status_code()); + EXPECT_STREQ("Hello!", res.c_str()); + srs_freep(msg); + } + + // Normal case, with specified content-length. + if (true) { + MockBufferIO io; io.append(mock_http_response(200, "Hello, world!")); + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); + string res; HELPER_ASSERT_SUCCESS(msg->body_read_all(res)); + EXPECT_EQ(200, msg->status_code()); + EXPECT_STREQ("Hello, world!", res.c_str()); + srs_freep(msg); + } +} + VOID TEST(ProtocolHTTPTest, ResponseHTTPError) { srs_error_t err; diff --git a/trunk/src/utest/srs_utest_kernel.cpp b/trunk/src/utest/srs_utest_kernel.cpp index fcd39dd6f..3a994d064 100644 --- a/trunk/src/utest/srs_utest_kernel.cpp +++ b/trunk/src/utest/srs_utest_kernel.cpp @@ -271,8 +271,12 @@ MockBufferReader::~MockBufferReader() srs_error_t MockBufferReader::read(void* buf, size_t size, ssize_t* nread) { int len = srs_min(str.length(), size); + if (len == 0) { + return srs_error_new(-1, "no data"); + } memcpy(buf, str.data(), len); + str = str.substr(len); if (nread) { *nread = len; @@ -388,19 +392,73 @@ VOID TEST(KernelBufferTest, EraseBytes) VOID TEST(KernelFastBufferTest, Grow) { - SrsFastStream b; - MockBufferReader r("winlin"); - - b.grow(&r, 1); - EXPECT_EQ('w', b.read_1byte()); + srs_error_t err; - b.grow(&r, 3); - b.skip(1); - EXPECT_EQ('n', b.read_1byte()); - - b.grow(&r, 100); - b.skip(99); - EXPECT_EQ('w', b.read_1byte()); + if(true) { + SrsFastStream b(5); + MockBufferReader r("Hello, world!"); + + HELPER_ASSERT_SUCCESS(b.grow(&r, 5)); + b.skip(2); + + HELPER_ASSERT_FAILED(b.grow(&r, 6)); + } + + if(true) { + SrsFastStream b(5); + MockBufferReader r("Hello, world!"); + + HELPER_ASSERT_SUCCESS(b.grow(&r, 5)); + b.skip(5); + + HELPER_ASSERT_FAILED(b.grow(&r, 6)); + } + + if(true) { + SrsFastStream b(6); + MockBufferReader r("Hello, world!"); + + HELPER_ASSERT_SUCCESS(b.grow(&r, 5)); + EXPECT_EQ('H', b.read_1byte()); EXPECT_EQ('e', b.read_1byte()); EXPECT_EQ('l', b.read_1byte()); + b.skip(2); + + HELPER_ASSERT_SUCCESS(b.grow(&r, 2)); + b.skip(2); + + HELPER_ASSERT_SUCCESS(b.grow(&r, 5)); + EXPECT_EQ('w', b.read_1byte()); EXPECT_EQ('o', b.read_1byte()); EXPECT_EQ('r', b.read_1byte()); + b.skip(2); + } + + if(true) { + SrsFastStream b(5); + MockBufferReader r("Hello, world!"); + + HELPER_ASSERT_SUCCESS(b.grow(&r, 5)); + EXPECT_EQ('H', b.read_1byte()); EXPECT_EQ('e', b.read_1byte()); EXPECT_EQ('l', b.read_1byte()); + b.skip(2); + + HELPER_ASSERT_SUCCESS(b.grow(&r, 2)); + b.skip(2); + + HELPER_ASSERT_SUCCESS(b.grow(&r, 5)); + EXPECT_EQ('w', b.read_1byte()); EXPECT_EQ('o', b.read_1byte()); EXPECT_EQ('r', b.read_1byte()); + b.skip(2); + } + + if (true) { + SrsFastStream b; + MockBufferReader r("winlin"); + + HELPER_ASSERT_SUCCESS(b.grow(&r, 1)); + EXPECT_EQ('w', b.read_1byte()); + + HELPER_ASSERT_SUCCESS(b.grow(&r, 3)); + b.skip(1); + EXPECT_EQ('n', b.read_1byte()); + + HELPER_ASSERT_FAILED(b.grow(&r, 100)); + } } /** diff --git a/trunk/src/utest/srs_utest_service.cpp b/trunk/src/utest/srs_utest_service.cpp index ef47c5942..5d1acb102 100644 --- a/trunk/src/utest/srs_utest_service.cpp +++ b/trunk/src/utest/srs_utest_service.cpp @@ -27,6 +27,7 @@ using namespace std; #include #include #include +#include // Disable coroutine test for OSX. #if !defined(SRS_OSX) @@ -225,6 +226,153 @@ VOID TEST(TCPServerTest, PingPongWithTimeout) } } +VOID TEST(TCPServerTest, StringIsDigital) +{ + EXPECT_EQ(0, ::atoi("0")); + EXPECT_EQ(0, ::atoi("0000000000")); + EXPECT_EQ(1, ::atoi("01")); + EXPECT_EQ(12, ::atoi("012")); + EXPECT_EQ(1234567890L, ::atol("1234567890")); + EXPECT_EQ(123456789L, ::atol("0123456789")); + EXPECT_EQ(1234567890, ::atoi("1234567890a")); + EXPECT_EQ(10, ::atoi("10e3")); + EXPECT_EQ(0, ::atoi("!1234567890")); + EXPECT_EQ(0, ::atoi("")); + + EXPECT_TRUE(srs_is_digit_number("0")); + EXPECT_TRUE(srs_is_digit_number("0000000000")); + EXPECT_TRUE(srs_is_digit_number("1234567890")); + EXPECT_TRUE(srs_is_digit_number("0123456789")); + EXPECT_FALSE(srs_is_digit_number("1234567890a")); + EXPECT_FALSE(srs_is_digit_number("a1234567890")); + EXPECT_FALSE(srs_is_digit_number("10e3")); + EXPECT_FALSE(srs_is_digit_number("!1234567890")); + EXPECT_FALSE(srs_is_digit_number("")); +} + +VOID TEST(TCPServerTest, StringIsHex) +{ + if (true) { + char* str = (char*)"0"; + char* parsed = str; errno = 0; + EXPECT_EQ(0x0, ::strtol(str, &parsed, 16)); + EXPECT_EQ(0, errno); + EXPECT_EQ(str + 1, parsed); + } + + if (true) { + char* str = (char*)"0"; + char* parsed = str; errno = 0; + EXPECT_EQ(0x0, ::strtol(str, &parsed, 16)); + EXPECT_EQ(0, errno); + EXPECT_EQ(str + 1, parsed); + } + + if (true) { + char* str = (char*)"0000000000"; + char* parsed = str; errno = 0; + EXPECT_EQ(0x0, ::strtol(str, &parsed, 16)); + EXPECT_EQ(0, errno); + EXPECT_EQ(str + 10, parsed); + } + + if (true) { + char* str = (char*)"01"; + char* parsed = str; errno = 0; + EXPECT_EQ(0x1, ::strtol(str, &parsed, 16)); + EXPECT_EQ(0, errno); + EXPECT_EQ(str + 2, parsed); + } + + if (true) { + char* str = (char*)"012"; + char* parsed = str; errno = 0; + EXPECT_EQ(0x12, ::strtol(str, &parsed, 16)); + EXPECT_EQ(0, errno); + EXPECT_EQ(str + 3, parsed); + } + + if (true) { + char* str = (char*)"1234567890"; + char* parsed = str; errno = 0; + EXPECT_EQ(0x1234567890L, ::strtol(str, &parsed, 16)); + EXPECT_EQ(0, errno); + EXPECT_EQ(str + 10, parsed); + } + + if (true) { + char* str = (char*)"0123456789"; + char* parsed = str; errno = 0; + EXPECT_EQ(0x123456789L, ::strtol(str, &parsed, 16)); + EXPECT_EQ(0, errno); + EXPECT_EQ(str + 10, parsed); + } + + if (true) { + char* str = (char*)"1234567890a"; + char* parsed = str; errno = 0; + EXPECT_EQ(0x1234567890a, ::strtol(str, &parsed, 16)); + EXPECT_EQ(0, errno); + EXPECT_EQ(str + 11, parsed); + } + + if (true) { + char* str = (char*)"0x1234567890a"; + char* parsed = str; errno = 0; + EXPECT_EQ(0x1234567890a, ::strtol(str, &parsed, 16)); + EXPECT_EQ(0, errno); + EXPECT_EQ(str + 13, parsed); + } + + if (true) { + char* str = (char*)"1234567890f"; + char* parsed = str; errno = 0; + EXPECT_EQ(0x1234567890f, ::strtol(str, &parsed, 16)); + EXPECT_EQ(0, errno); + EXPECT_EQ(str + 11, parsed); + } + + if (true) { + char* str = (char*)"10e3"; + char* parsed = str; errno = 0; + EXPECT_EQ(0x10e3, ::strtol(str, &parsed, 16)); + EXPECT_EQ(0, errno); + EXPECT_EQ(str + 4, parsed); + } + + if (true) { + char* str = (char*)"!1234567890"; + char* parsed = str; errno = 0; + EXPECT_EQ(0x0, ::strtol(str, &parsed, 16)); + EXPECT_EQ(0, errno); + EXPECT_EQ(str, parsed); + } + + if (true) { + char* str = (char*)"1234567890g"; + char* parsed = str; errno = 0; + EXPECT_EQ(0x1234567890, ::strtol(str, &parsed, 16)); + EXPECT_EQ(0, errno); + EXPECT_EQ(str + 10, parsed); + } + + if (true) { + char* str = (char*)""; + char* parsed = str; errno = 0; + EXPECT_EQ(0x0, ::strtol(str, &parsed, 16)); + EXPECT_EQ(0, errno); + EXPECT_EQ(str, parsed); + } + + if (true) { + char* str = (char*)"1fffffffffffffffffffffffffffff"; + char* parsed = str; errno = 0; + EXPECT_EQ(0x7fffffffffffffff, ::strtol(str, &parsed, 16)); + EXPECT_NE(0, errno); + EXPECT_EQ(str+30, parsed); + } +} + VOID TEST(TCPServerTest, WritevIOVC) { srs_error_t err;