diff options
Diffstat (limited to 'src/util.cc')
-rw-r--r-- | src/util.cc | 1760 |
1 files changed, 1760 insertions, 0 deletions
diff --git a/src/util.cc b/src/util.cc new file mode 100644 index 0000000..06b6e29 --- /dev/null +++ b/src/util.cc @@ -0,0 +1,1760 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "util.h" + +#ifdef HAVE_TIME_H +# include <time.h> +#endif // HAVE_TIME_H +#include <sys/types.h> +#ifdef HAVE_SYS_SOCKET_H +# include <sys/socket.h> +#endif // HAVE_SYS_SOCKET_H +#ifdef HAVE_NETDB_H +# include <netdb.h> +#endif // HAVE_NETDB_H +#include <sys/stat.h> +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#endif // HAVE_FCNTL_H +#ifdef HAVE_NETINET_IN_H +# include <netinet/in.h> +#endif // HAVE_NETINET_IN_H +#ifdef _WIN32 +# include <ws2tcpip.h> +#else // !_WIN32 +# include <netinet/tcp.h> +#endif // !_WIN32 +#ifdef HAVE_ARPA_INET_H +# include <arpa/inet.h> +#endif // HAVE_ARPA_INET_H + +#include <cmath> +#include <cerrno> +#include <cassert> +#include <cstdio> +#include <cstring> +#include <iostream> +#include <fstream> +#include <iomanip> + +#include <openssl/evp.h> + +#include <nghttp2/nghttp2.h> + +#include "ssl_compat.h" +#include "timegm.h" + +namespace nghttp2 { + +namespace util { + +#ifndef _WIN32 +namespace { +int nghttp2_inet_pton(int af, const char *src, void *dst) { + return inet_pton(af, src, dst); +} +} // namespace +#else // _WIN32 +namespace { +// inet_pton-wrapper for Windows +int nghttp2_inet_pton(int af, const char *src, void *dst) { +# if _WIN32_WINNT >= 0x0600 + return InetPtonA(af, src, dst); +# else + // the function takes a 'char*', so we need to make a copy + char addr[INET6_ADDRSTRLEN + 1]; + strncpy(addr, src, sizeof(addr)); + addr[sizeof(addr) - 1] = 0; + + int size = sizeof(struct in6_addr); + + if (WSAStringToAddress(addr, af, nullptr, (LPSOCKADDR)dst, &size) == 0) + return 1; + return 0; +# endif +} +} // namespace +#endif // _WIN32 + +const char UPPER_XDIGITS[] = "0123456789ABCDEF"; + +bool in_rfc3986_unreserved_chars(const char c) { + static constexpr char unreserved[] = {'-', '.', '_', '~'}; + return is_alpha(c) || is_digit(c) || + std::find(std::begin(unreserved), std::end(unreserved), c) != + std::end(unreserved); +} + +bool in_rfc3986_sub_delims(const char c) { + static constexpr char sub_delims[] = {'!', '$', '&', '\'', '(', ')', + '*', '+', ',', ';', '='}; + return std::find(std::begin(sub_delims), std::end(sub_delims), c) != + std::end(sub_delims); +} + +std::string percent_encode(const unsigned char *target, size_t len) { + std::string dest; + for (size_t i = 0; i < len; ++i) { + unsigned char c = target[i]; + + if (in_rfc3986_unreserved_chars(c)) { + dest += c; + } else { + dest += '%'; + dest += UPPER_XDIGITS[c >> 4]; + dest += UPPER_XDIGITS[(c & 0x0f)]; + } + } + return dest; +} + +std::string percent_encode(const std::string &target) { + return percent_encode(reinterpret_cast<const unsigned char *>(target.c_str()), + target.size()); +} + +bool in_token(char c) { + static constexpr char extra[] = {'!', '#', '$', '%', '&', '\'', '*', '+', + '-', '.', '^', '_', '`', '|', '~'}; + return is_alpha(c) || is_digit(c) || + std::find(std::begin(extra), std::end(extra), c) != std::end(extra); +} + +bool in_attr_char(char c) { + static constexpr char bad[] = {'*', '\'', '%'}; + return util::in_token(c) && + std::find(std::begin(bad), std::end(bad), c) == std::end(bad); +} + +StringRef percent_encode_token(BlockAllocator &balloc, + const StringRef &target) { + auto iov = make_byte_ref(balloc, target.size() * 3 + 1); + auto p = percent_encode_token(iov.base, target); + + *p = '\0'; + + return StringRef{iov.base, p}; +} + +size_t percent_encode_tokenlen(const StringRef &target) { + size_t n = 0; + + for (auto first = std::begin(target); first != std::end(target); ++first) { + uint8_t c = *first; + + if (c != '%' && in_token(c)) { + ++n; + continue; + } + + // percent-encoded character '%ff' + n += 3; + } + + return n; +} + +uint32_t hex_to_uint(char c) { + if (c <= '9') { + return c - '0'; + } + if (c <= 'Z') { + return c - 'A' + 10; + } + if (c <= 'z') { + return c - 'a' + 10; + } + return 256; +} + +StringRef quote_string(BlockAllocator &balloc, const StringRef &target) { + auto cnt = std::count(std::begin(target), std::end(target), '"'); + + if (cnt == 0) { + return make_string_ref(balloc, target); + } + + auto iov = make_byte_ref(balloc, target.size() + cnt + 1); + auto p = quote_string(iov.base, target); + + *p = '\0'; + + return StringRef{iov.base, p}; +} + +size_t quote_stringlen(const StringRef &target) { + size_t n = 0; + + for (auto c : target) { + if (c == '"') { + n += 2; + } else { + ++n; + } + } + + return n; +} + +namespace { +template <typename Iterator> +Iterator cpydig(Iterator d, uint32_t n, size_t len) { + auto p = d + len - 1; + + do { + *p-- = (n % 10) + '0'; + n /= 10; + } while (p >= d); + + return d + len; +} +} // namespace + +namespace { +constexpr const char *MONTH[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; +constexpr const char *DAY_OF_WEEK[] = {"Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat"}; +} // namespace + +std::string http_date(time_t t) { + /* Sat, 27 Sep 2014 06:31:15 GMT */ + std::string res(29, 0); + http_date(&res[0], t); + return res; +} + +char *http_date(char *res, time_t t) { + struct tm tms; + + if (gmtime_r(&t, &tms) == nullptr) { + return res; + } + + auto p = res; + + auto s = DAY_OF_WEEK[tms.tm_wday]; + p = std::copy_n(s, 3, p); + *p++ = ','; + *p++ = ' '; + p = cpydig(p, tms.tm_mday, 2); + *p++ = ' '; + s = MONTH[tms.tm_mon]; + p = std::copy_n(s, 3, p); + *p++ = ' '; + p = cpydig(p, tms.tm_year + 1900, 4); + *p++ = ' '; + p = cpydig(p, tms.tm_hour, 2); + *p++ = ':'; + p = cpydig(p, tms.tm_min, 2); + *p++ = ':'; + p = cpydig(p, tms.tm_sec, 2); + s = " GMT"; + p = std::copy_n(s, 4, p); + + return p; +} + +std::string common_log_date(time_t t) { + // 03/Jul/2014:00:19:38 +0900 + std::string res(26, 0); + common_log_date(&res[0], t); + return res; +} + +char *common_log_date(char *res, time_t t) { + struct tm tms; + + if (localtime_r(&t, &tms) == nullptr) { + return res; + } + + auto p = res; + + p = cpydig(p, tms.tm_mday, 2); + *p++ = '/'; + auto s = MONTH[tms.tm_mon]; + p = std::copy_n(s, 3, p); + *p++ = '/'; + p = cpydig(p, tms.tm_year + 1900, 4); + *p++ = ':'; + p = cpydig(p, tms.tm_hour, 2); + *p++ = ':'; + p = cpydig(p, tms.tm_min, 2); + *p++ = ':'; + p = cpydig(p, tms.tm_sec, 2); + *p++ = ' '; + +#ifdef HAVE_STRUCT_TM_TM_GMTOFF + auto gmtoff = tms.tm_gmtoff; +#else // !HAVE_STRUCT_TM_TM_GMTOFF + auto gmtoff = nghttp2_timegm(&tms) - t; +#endif // !HAVE_STRUCT_TM_TM_GMTOFF + if (gmtoff >= 0) { + *p++ = '+'; + } else { + *p++ = '-'; + gmtoff = -gmtoff; + } + + p = cpydig(p, gmtoff / 3600, 2); + p = cpydig(p, (gmtoff % 3600) / 60, 2); + + return p; +} + +std::string iso8601_date(int64_t ms) { + // 2014-11-15T12:58:24.741Z + // 2014-11-15T12:58:24.741+09:00 + std::string res(29, 0); + auto p = iso8601_date(&res[0], ms); + res.resize(p - &res[0]); + return res; +} + +char *iso8601_date(char *res, int64_t ms) { + time_t sec = ms / 1000; + + tm tms; + if (localtime_r(&sec, &tms) == nullptr) { + return res; + } + + auto p = res; + + p = cpydig(p, tms.tm_year + 1900, 4); + *p++ = '-'; + p = cpydig(p, tms.tm_mon + 1, 2); + *p++ = '-'; + p = cpydig(p, tms.tm_mday, 2); + *p++ = 'T'; + p = cpydig(p, tms.tm_hour, 2); + *p++ = ':'; + p = cpydig(p, tms.tm_min, 2); + *p++ = ':'; + p = cpydig(p, tms.tm_sec, 2); + *p++ = '.'; + p = cpydig(p, ms % 1000, 3); + +#ifdef HAVE_STRUCT_TM_TM_GMTOFF + auto gmtoff = tms.tm_gmtoff; +#else // !HAVE_STRUCT_TM_TM_GMTOFF + auto gmtoff = nghttp2_timegm(&tms) - sec; +#endif // !HAVE_STRUCT_TM_TM_GMTOFF + if (gmtoff == 0) { + *p++ = 'Z'; + } else { + if (gmtoff > 0) { + *p++ = '+'; + } else { + *p++ = '-'; + gmtoff = -gmtoff; + } + p = cpydig(p, gmtoff / 3600, 2); + *p++ = ':'; + p = cpydig(p, (gmtoff % 3600) / 60, 2); + } + + return p; +} + +char *iso8601_basic_date(char *res, int64_t ms) { + time_t sec = ms / 1000; + + tm tms; + if (localtime_r(&sec, &tms) == nullptr) { + return res; + } + + auto p = res; + + p = cpydig(p, tms.tm_year + 1900, 4); + p = cpydig(p, tms.tm_mon + 1, 2); + p = cpydig(p, tms.tm_mday, 2); + *p++ = 'T'; + p = cpydig(p, tms.tm_hour, 2); + p = cpydig(p, tms.tm_min, 2); + p = cpydig(p, tms.tm_sec, 2); + *p++ = '.'; + p = cpydig(p, ms % 1000, 3); + +#ifdef HAVE_STRUCT_TM_TM_GMTOFF + auto gmtoff = tms.tm_gmtoff; +#else // !HAVE_STRUCT_TM_TM_GMTOFF + auto gmtoff = nghttp2_timegm(&tms) - sec; +#endif // !HAVE_STRUCT_TM_TM_GMTOFF + if (gmtoff == 0) { + *p++ = 'Z'; + } else { + if (gmtoff > 0) { + *p++ = '+'; + } else { + *p++ = '-'; + gmtoff = -gmtoff; + } + p = cpydig(p, gmtoff / 3600, 2); + p = cpydig(p, (gmtoff % 3600) / 60, 2); + } + + return p; +} + +time_t parse_http_date(const StringRef &s) { + tm tm{}; +#ifdef _WIN32 + // there is no strptime - use std::get_time + std::stringstream sstr(s.str()); + sstr >> std::get_time(&tm, "%a, %d %b %Y %H:%M:%S GMT"); + if (sstr.fail()) { + return 0; + } +#else // !_WIN32 + char *r = strptime(s.c_str(), "%a, %d %b %Y %H:%M:%S GMT", &tm); + if (r == 0) { + return 0; + } +#endif // !_WIN32 + return nghttp2_timegm_without_yday(&tm); +} + +time_t parse_openssl_asn1_time_print(const StringRef &s) { + tm tm{}; + auto r = strptime(s.c_str(), "%b %d %H:%M:%S %Y GMT", &tm); + if (r == nullptr) { + return 0; + } + return nghttp2_timegm_without_yday(&tm); +} + +char upcase(char c) { + if ('a' <= c && c <= 'z') { + return c - 'a' + 'A'; + } else { + return c; + } +} + +std::string format_hex(const unsigned char *s, size_t len) { + std::string res; + res.resize(len * 2); + + for (size_t i = 0; i < len; ++i) { + unsigned char c = s[i]; + + res[i * 2] = LOWER_XDIGITS[c >> 4]; + res[i * 2 + 1] = LOWER_XDIGITS[c & 0x0f]; + } + return res; +} + +StringRef format_hex(BlockAllocator &balloc, const StringRef &s) { + auto iov = make_byte_ref(balloc, s.size() * 2 + 1); + auto p = iov.base; + + for (auto cc : s) { + uint8_t c = cc; + *p++ = LOWER_XDIGITS[c >> 4]; + *p++ = LOWER_XDIGITS[c & 0xf]; + } + + *p = '\0'; + + return StringRef{iov.base, p}; +} + +void to_token68(std::string &base64str) { + std::transform(std::begin(base64str), std::end(base64str), + std::begin(base64str), [](char c) { + switch (c) { + case '+': + return '-'; + case '/': + return '_'; + default: + return c; + } + }); + base64str.erase(std::find(std::begin(base64str), std::end(base64str), '='), + std::end(base64str)); +} + +StringRef to_base64(BlockAllocator &balloc, const StringRef &token68str) { + // At most 3 padding '=' + auto len = token68str.size() + 3; + auto iov = make_byte_ref(balloc, len + 1); + auto p = iov.base; + + p = std::transform(std::begin(token68str), std::end(token68str), p, + [](char c) { + switch (c) { + case '-': + return '+'; + case '_': + return '/'; + default: + return c; + } + }); + + auto rem = token68str.size() & 0x3; + if (rem) { + p = std::fill_n(p, 4 - rem, '='); + } + + *p = '\0'; + + return StringRef{iov.base, p}; +} + +namespace { +// Calculates Damerau–Levenshtein distance between c-string a and b +// with given costs. swapcost, subcost, addcost and delcost are cost +// to swap 2 adjacent characters, substitute characters, add character +// and delete character respectively. +int levenshtein(const char *a, int alen, const char *b, int blen, int swapcost, + int subcost, int addcost, int delcost) { + auto dp = std::vector<std::vector<int>>(3, std::vector<int>(blen + 1)); + for (int i = 0; i <= blen; ++i) { + dp[1][i] = i; + } + for (int i = 1; i <= alen; ++i) { + dp[0][0] = i; + for (int j = 1; j <= blen; ++j) { + dp[0][j] = dp[1][j - 1] + (a[i - 1] == b[j - 1] ? 0 : subcost); + if (i >= 2 && j >= 2 && a[i - 1] != b[j - 1] && a[i - 2] == b[j - 1] && + a[i - 1] == b[j - 2]) { + dp[0][j] = std::min(dp[0][j], dp[2][j - 2] + swapcost); + } + dp[0][j] = std::min(dp[0][j], + std::min(dp[1][j] + delcost, dp[0][j - 1] + addcost)); + } + std::rotate(std::begin(dp), std::begin(dp) + 2, std::end(dp)); + } + return dp[1][blen]; +} +} // namespace + +void show_candidates(const char *unkopt, const option *options) { + for (; *unkopt == '-'; ++unkopt) + ; + if (*unkopt == '\0') { + return; + } + auto unkoptend = unkopt; + for (; *unkoptend && *unkoptend != '='; ++unkoptend) + ; + auto unkoptlen = unkoptend - unkopt; + if (unkoptlen == 0) { + return; + } + int prefix_match = 0; + auto cands = std::vector<std::pair<int, const char *>>(); + for (size_t i = 0; options[i].name != nullptr; ++i) { + auto optnamelen = strlen(options[i].name); + // Use cost 0 for prefix match + if (istarts_with(options[i].name, options[i].name + optnamelen, unkopt, + unkopt + unkoptlen)) { + if (optnamelen == static_cast<size_t>(unkoptlen)) { + // Exact match, then we don't show any condidates. + return; + } + ++prefix_match; + cands.emplace_back(0, options[i].name); + continue; + } + // Use cost 0 for suffix match, but match at least 3 characters + if (unkoptlen >= 3 && + iends_with(options[i].name, options[i].name + optnamelen, unkopt, + unkopt + unkoptlen)) { + cands.emplace_back(0, options[i].name); + continue; + } + // cost values are borrowed from git, help.c. + int sim = + levenshtein(unkopt, unkoptlen, options[i].name, optnamelen, 0, 2, 1, 3); + cands.emplace_back(sim, options[i].name); + } + if (prefix_match == 1 || cands.empty()) { + return; + } + std::sort(std::begin(cands), std::end(cands)); + int threshold = cands[0].first; + // threshold value is a magic value. + if (threshold > 6) { + return; + } + std::cerr << "\nDid you mean:\n"; + for (auto &item : cands) { + if (item.first > threshold) { + break; + } + std::cerr << "\t--" << item.second << "\n"; + } +} + +bool has_uri_field(const http_parser_url &u, http_parser_url_fields field) { + return u.field_set & (1 << field); +} + +bool fieldeq(const char *uri1, const http_parser_url &u1, const char *uri2, + const http_parser_url &u2, http_parser_url_fields field) { + if (!has_uri_field(u1, field)) { + if (!has_uri_field(u2, field)) { + return true; + } else { + return false; + } + } else if (!has_uri_field(u2, field)) { + return false; + } + if (u1.field_data[field].len != u2.field_data[field].len) { + return false; + } + return memcmp(uri1 + u1.field_data[field].off, + uri2 + u2.field_data[field].off, u1.field_data[field].len) == 0; +} + +bool fieldeq(const char *uri, const http_parser_url &u, + http_parser_url_fields field, const char *t) { + return fieldeq(uri, u, field, StringRef{t}); +} + +bool fieldeq(const char *uri, const http_parser_url &u, + http_parser_url_fields field, const StringRef &t) { + if (!has_uri_field(u, field)) { + return t.empty(); + } + auto &f = u.field_data[field]; + return StringRef{uri + f.off, f.len} == t; +} + +StringRef get_uri_field(const char *uri, const http_parser_url &u, + http_parser_url_fields field) { + if (!util::has_uri_field(u, field)) { + return StringRef{}; + } + + return StringRef{uri + u.field_data[field].off, u.field_data[field].len}; +} + +uint16_t get_default_port(const char *uri, const http_parser_url &u) { + if (util::fieldeq(uri, u, UF_SCHEMA, "https")) { + return 443; + } else if (util::fieldeq(uri, u, UF_SCHEMA, "http")) { + return 80; + } else { + return 443; + } +} + +bool porteq(const char *uri1, const http_parser_url &u1, const char *uri2, + const http_parser_url &u2) { + uint16_t port1, port2; + port1 = + util::has_uri_field(u1, UF_PORT) ? u1.port : get_default_port(uri1, u1); + port2 = + util::has_uri_field(u2, UF_PORT) ? u2.port : get_default_port(uri2, u2); + return port1 == port2; +} + +void write_uri_field(std::ostream &o, const char *uri, const http_parser_url &u, + http_parser_url_fields field) { + if (util::has_uri_field(u, field)) { + o.write(uri + u.field_data[field].off, u.field_data[field].len); + } +} + +bool numeric_host(const char *hostname) { + return numeric_host(hostname, AF_INET) || numeric_host(hostname, AF_INET6); +} + +bool numeric_host(const char *hostname, int family) { + int rv; + std::array<uint8_t, sizeof(struct in6_addr)> dst; + + rv = nghttp2_inet_pton(family, hostname, dst.data()); + + return rv == 1; +} + +std::string numeric_name(const struct sockaddr *sa, socklen_t salen) { + std::array<char, NI_MAXHOST> host; + auto rv = getnameinfo(sa, salen, host.data(), host.size(), nullptr, 0, + NI_NUMERICHOST); + if (rv != 0) { + return "unknown"; + } + return host.data(); +} + +std::string to_numeric_addr(const Address *addr) { + return to_numeric_addr(&addr->su.sa, addr->len); +} + +std::string to_numeric_addr(const struct sockaddr *sa, socklen_t salen) { + auto family = sa->sa_family; +#ifndef _WIN32 + if (family == AF_UNIX) { + return reinterpret_cast<const sockaddr_un *>(sa)->sun_path; + } +#endif // !_WIN32 + + std::array<char, NI_MAXHOST> host; + std::array<char, NI_MAXSERV> serv; + auto rv = getnameinfo(sa, salen, host.data(), host.size(), serv.data(), + serv.size(), NI_NUMERICHOST | NI_NUMERICSERV); + if (rv != 0) { + return "unknown"; + } + + auto hostlen = strlen(host.data()); + auto servlen = strlen(serv.data()); + + std::string s; + char *p; + if (family == AF_INET6) { + s.resize(hostlen + servlen + 2 + 1); + p = &s[0]; + *p++ = '['; + p = std::copy_n(host.data(), hostlen, p); + *p++ = ']'; + } else { + s.resize(hostlen + servlen + 1); + p = &s[0]; + p = std::copy_n(host.data(), hostlen, p); + } + *p++ = ':'; + std::copy_n(serv.data(), servlen, p); + + return s; +} + +void set_port(Address &addr, uint16_t port) { + switch (addr.su.storage.ss_family) { + case AF_INET: + addr.su.in.sin_port = htons(port); + break; + case AF_INET6: + addr.su.in6.sin6_port = htons(port); + break; + } +} + +std::string ascii_dump(const uint8_t *data, size_t len) { + std::string res; + + for (size_t i = 0; i < len; ++i) { + auto c = data[i]; + + if (c >= 0x20 && c < 0x7f) { + res += c; + } else { + res += '.'; + } + } + + return res; +} + +char *get_exec_path(int argc, char **const argv, const char *cwd) { + if (argc == 0 || cwd == nullptr) { + return nullptr; + } + + auto argv0 = argv[0]; + auto len = strlen(argv0); + + char *path; + + if (argv0[0] == '/') { + path = static_cast<char *>(malloc(len + 1)); + if (path == nullptr) { + return nullptr; + } + memcpy(path, argv0, len + 1); + } else { + auto cwdlen = strlen(cwd); + path = static_cast<char *>(malloc(len + 1 + cwdlen + 1)); + if (path == nullptr) { + return nullptr; + } + memcpy(path, cwd, cwdlen); + path[cwdlen] = '/'; + memcpy(path + cwdlen + 1, argv0, len + 1); + } + + return path; +} + +bool check_path(const std::string &path) { + // We don't like '\' in path. + return !path.empty() && path[0] == '/' && + path.find('\\') == std::string::npos && + path.find("/../") == std::string::npos && + path.find("/./") == std::string::npos && + !util::ends_with_l(path, "/..") && !util::ends_with_l(path, "/."); +} + +int64_t to_time64(const timeval &tv) { + return tv.tv_sec * 1000000 + tv.tv_usec; +} + +bool check_h2_is_selected(const StringRef &proto) { + return streq(NGHTTP2_H2, proto) || streq(NGHTTP2_H2_16, proto) || + streq(NGHTTP2_H2_14, proto); +} + +namespace { +bool select_proto(const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + const StringRef &key) { + for (auto p = in, end = in + inlen; p + key.size() <= end; p += *p + 1) { + if (std::equal(std::begin(key), std::end(key), p)) { + *out = p + 1; + *outlen = *p; + return true; + } + } + return false; +} +} // namespace + +bool select_h2(const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen) { + return select_proto(out, outlen, in, inlen, NGHTTP2_H2_ALPN) || + select_proto(out, outlen, in, inlen, NGHTTP2_H2_16_ALPN) || + select_proto(out, outlen, in, inlen, NGHTTP2_H2_14_ALPN); +} + +bool select_protocol(const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + std::vector<std::string> proto_list) { + for (const auto &proto : proto_list) { + if (select_proto(out, outlen, in, inlen, StringRef{proto})) { + return true; + } + } + + return false; +} + +std::vector<unsigned char> get_default_alpn() { + auto res = std::vector<unsigned char>(NGHTTP2_H2_ALPN.size() + + NGHTTP2_H2_16_ALPN.size() + + NGHTTP2_H2_14_ALPN.size()); + auto p = std::begin(res); + + p = std::copy_n(std::begin(NGHTTP2_H2_ALPN), NGHTTP2_H2_ALPN.size(), p); + p = std::copy_n(std::begin(NGHTTP2_H2_16_ALPN), NGHTTP2_H2_16_ALPN.size(), p); + p = std::copy_n(std::begin(NGHTTP2_H2_14_ALPN), NGHTTP2_H2_14_ALPN.size(), p); + + return res; +} + +std::vector<StringRef> split_str(const StringRef &s, char delim) { + size_t len = 1; + auto last = std::end(s); + StringRef::const_iterator d; + for (auto first = std::begin(s); (d = std::find(first, last, delim)) != last; + ++len, first = d + 1) + ; + + auto list = std::vector<StringRef>(len); + + len = 0; + for (auto first = std::begin(s);; ++len) { + auto stop = std::find(first, last, delim); + list[len] = StringRef{first, stop}; + if (stop == last) { + break; + } + first = stop + 1; + } + return list; +} + +std::vector<StringRef> split_str(const StringRef &s, char delim, size_t n) { + if (n == 0) { + return split_str(s, delim); + } + + if (n == 1) { + return {s}; + } + + size_t len = 1; + auto last = std::end(s); + StringRef::const_iterator d; + for (auto first = std::begin(s); + len < n && (d = std::find(first, last, delim)) != last; + ++len, first = d + 1) + ; + + auto list = std::vector<StringRef>(len); + + len = 0; + for (auto first = std::begin(s);; ++len) { + if (len == n - 1) { + list[len] = StringRef{first, last}; + break; + } + + auto stop = std::find(first, last, delim); + list[len] = StringRef{first, stop}; + if (stop == last) { + break; + } + first = stop + 1; + } + return list; +} + +std::vector<std::string> parse_config_str_list(const StringRef &s, char delim) { + auto sublist = split_str(s, delim); + auto res = std::vector<std::string>(); + res.reserve(sublist.size()); + for (const auto &s : sublist) { + res.emplace_back(std::begin(s), std::end(s)); + } + return res; +} + +int make_socket_closeonexec(int fd) { +#ifdef _WIN32 + (void)fd; + return 0; +#else // !_WIN32 + int flags; + int rv; + while ((flags = fcntl(fd, F_GETFD)) == -1 && errno == EINTR) + ; + while ((rv = fcntl(fd, F_SETFD, flags | FD_CLOEXEC)) == -1 && errno == EINTR) + ; + return rv; +#endif // !_WIN32 +} + +int make_socket_nonblocking(int fd) { + int rv; + +#ifdef _WIN32 + u_long mode = 1; + + rv = ioctlsocket(fd, FIONBIO, &mode); +#else // !_WIN32 + int flags; + while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR) + ; + while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR) + ; +#endif // !_WIN32 + + return rv; +} + +int make_socket_nodelay(int fd) { + int val = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&val), + sizeof(val)) == -1) { + return -1; + } + return 0; +} + +int create_nonblock_socket(int family) { +#ifdef SOCK_NONBLOCK + auto fd = socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + + if (fd == -1) { + return -1; + } +#else // !SOCK_NONBLOCK + auto fd = socket(family, SOCK_STREAM, 0); + + if (fd == -1) { + return -1; + } + + make_socket_nonblocking(fd); + make_socket_closeonexec(fd); +#endif // !SOCK_NONBLOCK + + if (family == AF_INET || family == AF_INET6) { + make_socket_nodelay(fd); + } + + return fd; +} + +int create_nonblock_udp_socket(int family) { +#ifdef SOCK_NONBLOCK + auto fd = socket(family, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + + if (fd == -1) { + return -1; + } +#else // !SOCK_NONBLOCK + auto fd = socket(family, SOCK_DGRAM, 0); + + if (fd == -1) { + return -1; + } + + make_socket_nonblocking(fd); + make_socket_closeonexec(fd); +#endif // !SOCK_NONBLOCK + + return fd; +} + +int bind_any_addr_udp(int fd, int family) { + addrinfo hints{}; + addrinfo *res, *rp; + int rv; + + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; + + rv = getaddrinfo(nullptr, "0", &hints, &res); + if (rv != 0) { + return -1; + } + + for (rp = res; rp; rp = rp->ai_next) { + if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) { + break; + } + } + + freeaddrinfo(res); + + if (!rp) { + return -1; + } + + return 0; +} + +bool check_socket_connected(int fd) { + int error; + socklen_t len = sizeof(error); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&error, &len) != 0) { + return false; + } + + return error == 0; +} + +int get_socket_error(int fd) { + int error; + socklen_t len = sizeof(error); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&error, &len) != 0) { + return -1; + } + + return error; +} + +bool ipv6_numeric_addr(const char *host) { + uint8_t dst[16]; + return nghttp2_inet_pton(AF_INET6, host, dst) == 1; +} + +namespace { +std::pair<int64_t, size_t> parse_uint_digits(const void *ss, size_t len) { + const uint8_t *s = static_cast<const uint8_t *>(ss); + int64_t n = 0; + size_t i; + if (len == 0) { + return {-1, 0}; + } + constexpr int64_t max = std::numeric_limits<int64_t>::max(); + for (i = 0; i < len; ++i) { + if ('0' <= s[i] && s[i] <= '9') { + if (n > max / 10) { + return {-1, 0}; + } + n *= 10; + if (n > max - (s[i] - '0')) { + return {-1, 0}; + } + n += s[i] - '0'; + continue; + } + break; + } + if (i == 0) { + return {-1, 0}; + } + return {n, i}; +} +} // namespace + +int64_t parse_uint_with_unit(const char *s) { + return parse_uint_with_unit(reinterpret_cast<const uint8_t *>(s), strlen(s)); +} + +int64_t parse_uint_with_unit(const StringRef &s) { + return parse_uint_with_unit(s.byte(), s.size()); +} + +int64_t parse_uint_with_unit(const uint8_t *s, size_t len) { + int64_t n; + size_t i; + std::tie(n, i) = parse_uint_digits(s, len); + if (n == -1) { + return -1; + } + if (i == len) { + return n; + } + if (i + 1 != len) { + return -1; + } + int mul = 1; + switch (s[i]) { + case 'K': + case 'k': + mul = 1 << 10; + break; + case 'M': + case 'm': + mul = 1 << 20; + break; + case 'G': + case 'g': + mul = 1 << 30; + break; + default: + return -1; + } + constexpr int64_t max = std::numeric_limits<int64_t>::max(); + if (n > max / mul) { + return -1; + } + return n * mul; +} + +int64_t parse_uint(const char *s) { + return parse_uint(reinterpret_cast<const uint8_t *>(s), strlen(s)); +} + +int64_t parse_uint(const std::string &s) { + return parse_uint(reinterpret_cast<const uint8_t *>(s.c_str()), s.size()); +} + +int64_t parse_uint(const StringRef &s) { + return parse_uint(s.byte(), s.size()); +} + +int64_t parse_uint(const uint8_t *s, size_t len) { + int64_t n; + size_t i; + std::tie(n, i) = parse_uint_digits(s, len); + if (n == -1 || i != len) { + return -1; + } + return n; +} + +double parse_duration_with_unit(const char *s) { + return parse_duration_with_unit(reinterpret_cast<const uint8_t *>(s), + strlen(s)); +} + +double parse_duration_with_unit(const StringRef &s) { + return parse_duration_with_unit(s.byte(), s.size()); +} + +double parse_duration_with_unit(const uint8_t *s, size_t len) { + constexpr auto max = std::numeric_limits<int64_t>::max(); + int64_t n; + size_t i; + + std::tie(n, i) = parse_uint_digits(s, len); + if (n == -1) { + goto fail; + } + if (i == len) { + return static_cast<double>(n); + } + switch (s[i]) { + case 'S': + case 's': + // seconds + if (i + 1 != len) { + goto fail; + } + return static_cast<double>(n); + case 'M': + case 'm': + if (i + 1 == len) { + // minutes + if (n > max / 60) { + goto fail; + } + return static_cast<double>(n) * 60; + } + + if (i + 2 != len || (s[i + 1] != 's' && s[i + 1] != 'S')) { + goto fail; + } + // milliseconds + return static_cast<double>(n) / 1000.; + case 'H': + case 'h': + // hours + if (i + 1 != len) { + goto fail; + } + if (n > max / 3600) { + goto fail; + } + return static_cast<double>(n) * 3600; + } +fail: + return std::numeric_limits<double>::infinity(); +} + +std::string duration_str(double t) { + if (t == 0.) { + return "0"; + } + auto frac = static_cast<int64_t>(t * 1000) % 1000; + if (frac > 0) { + return utos(static_cast<int64_t>(t * 1000)) + "ms"; + } + auto v = static_cast<int64_t>(t); + if (v % 60) { + return utos(v) + "s"; + } + v /= 60; + if (v % 60) { + return utos(v) + "m"; + } + v /= 60; + return utos(v) + "h"; +} + +std::string format_duration(const std::chrono::microseconds &u) { + const char *unit = "us"; + int d = 0; + auto t = u.count(); + if (t >= 1000000) { + d = 1000000; + unit = "s"; + } else if (t >= 1000) { + d = 1000; + unit = "ms"; + } else { + return utos(t) + unit; + } + return dtos(static_cast<double>(t) / d) + unit; +} + +std::string format_duration(double t) { + const char *unit = "us"; + if (t >= 1.) { + unit = "s"; + } else if (t >= 0.001) { + t *= 1000.; + unit = "ms"; + } else { + t *= 1000000.; + return utos(static_cast<int64_t>(t)) + unit; + } + return dtos(t) + unit; +} + +std::string dtos(double n) { + auto m = llround(100. * n); + auto f = utos(m % 100); + return utos(m / 100) + "." + (f.size() == 1 ? "0" : "") + f; +} + +StringRef make_http_hostport(BlockAllocator &balloc, const StringRef &host, + uint16_t port) { + auto iov = make_byte_ref(balloc, host.size() + 2 + 1 + 5 + 1); + return make_http_hostport(iov.base, host, port); +} + +StringRef make_hostport(BlockAllocator &balloc, const StringRef &host, + uint16_t port) { + auto iov = make_byte_ref(balloc, host.size() + 2 + 1 + 5 + 1); + return make_hostport(iov.base, host, port); +} + +namespace { +void hexdump8(FILE *out, const uint8_t *first, const uint8_t *last) { + auto stop = std::min(first + 8, last); + for (auto k = first; k != stop; ++k) { + fprintf(out, "%02x ", *k); + } + // each byte needs 3 spaces (2 hex value and space) + for (; stop != first + 8; ++stop) { + fputs(" ", out); + } + // we have extra space after 8 bytes + fputc(' ', out); +} +} // namespace + +void hexdump(FILE *out, const uint8_t *src, size_t len) { + if (len == 0) { + return; + } + size_t buflen = 0; + auto repeated = false; + std::array<uint8_t, 16> buf{}; + auto end = src + len; + auto i = src; + for (;;) { + auto nextlen = + std::min(static_cast<size_t>(16), static_cast<size_t>(end - i)); + if (nextlen == buflen && + std::equal(std::begin(buf), std::begin(buf) + buflen, i)) { + // as long as adjacent 16 bytes block are the same, we just + // print single '*'. + if (!repeated) { + repeated = true; + fputs("*\n", out); + } + i += nextlen; + continue; + } + repeated = false; + fprintf(out, "%08lx", static_cast<unsigned long>(i - src)); + if (i == end) { + fputc('\n', out); + break; + } + fputs(" ", out); + hexdump8(out, i, end); + hexdump8(out, i + 8, std::max(i + 8, end)); + fputc('|', out); + auto stop = std::min(i + 16, end); + buflen = stop - i; + auto p = buf.data(); + for (; i != stop; ++i) { + *p++ = *i; + if (0x20 <= *i && *i <= 0x7e) { + fputc(*i, out); + } else { + fputc('.', out); + } + } + fputs("|\n", out); + } +} + +void put_uint16be(uint8_t *buf, uint16_t n) { + uint16_t x = htons(n); + memcpy(buf, &x, sizeof(uint16_t)); +} + +void put_uint32be(uint8_t *buf, uint32_t n) { + uint32_t x = htonl(n); + memcpy(buf, &x, sizeof(uint32_t)); +} + +uint16_t get_uint16(const uint8_t *data) { + uint16_t n; + memcpy(&n, data, sizeof(uint16_t)); + return ntohs(n); +} + +uint32_t get_uint32(const uint8_t *data) { + uint32_t n; + memcpy(&n, data, sizeof(uint32_t)); + return ntohl(n); +} + +uint64_t get_uint64(const uint8_t *data) { + uint64_t n = 0; + n += static_cast<uint64_t>(data[0]) << 56; + n += static_cast<uint64_t>(data[1]) << 48; + n += static_cast<uint64_t>(data[2]) << 40; + n += static_cast<uint64_t>(data[3]) << 32; + n += static_cast<uint64_t>(data[4]) << 24; + n += data[5] << 16; + n += data[6] << 8; + n += data[7]; + return n; +} + +int read_mime_types(std::map<std::string, std::string> &res, + const char *filename) { + std::ifstream infile(filename); + if (!infile) { + return -1; + } + + auto delim_pred = [](char c) { return c == ' ' || c == '\t'; }; + + std::string line; + while (std::getline(infile, line)) { + if (line.empty() || line[0] == '#') { + continue; + } + + auto type_end = std::find_if(std::begin(line), std::end(line), delim_pred); + if (type_end == std::begin(line)) { + continue; + } + + auto ext_end = type_end; + for (;;) { + auto ext_start = std::find_if_not(ext_end, std::end(line), delim_pred); + if (ext_start == std::end(line)) { + break; + } + ext_end = std::find_if(ext_start, std::end(line), delim_pred); +#ifdef HAVE_STD_MAP_EMPLACE + res.emplace(std::string(ext_start, ext_end), + std::string(std::begin(line), type_end)); +#else // !HAVE_STD_MAP_EMPLACE + res.insert(std::make_pair(std::string(ext_start, ext_end), + std::string(std::begin(line), type_end))); +#endif // !HAVE_STD_MAP_EMPLACE + } + } + + return 0; +} + +StringRef percent_decode(BlockAllocator &balloc, const StringRef &src) { + auto iov = make_byte_ref(balloc, src.size() * 3 + 1); + auto p = iov.base; + for (auto first = std::begin(src); first != std::end(src); ++first) { + if (*first != '%') { + *p++ = *first; + continue; + } + + if (first + 1 != std::end(src) && first + 2 != std::end(src) && + is_hex_digit(*(first + 1)) && is_hex_digit(*(first + 2))) { + *p++ = (hex_to_uint(*(first + 1)) << 4) + hex_to_uint(*(first + 2)); + first += 2; + continue; + } + + *p++ = *first; + } + *p = '\0'; + return StringRef{iov.base, p}; +} + +// Returns x**y +double int_pow(double x, size_t y) { + auto res = 1.; + for (; y; --y) { + res *= x; + } + return res; +} + +uint32_t hash32(const StringRef &s) { + /* 32 bit FNV-1a: http://isthe.com/chongo/tech/comp/fnv/ */ + uint32_t h = 2166136261u; + size_t i; + + for (i = 0; i < s.size(); ++i) { + h ^= s[i]; + h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24); + } + + return h; +} + +#if !OPENSSL_1_1_API +namespace { +EVP_MD_CTX *EVP_MD_CTX_new(void) { return EVP_MD_CTX_create(); } +} // namespace + +namespace { +void EVP_MD_CTX_free(EVP_MD_CTX *ctx) { EVP_MD_CTX_destroy(ctx); } +} // namespace +#endif // !OPENSSL_1_1_API + +namespace { +int message_digest(uint8_t *res, const EVP_MD *meth, const StringRef &s) { + int rv; + + auto ctx = EVP_MD_CTX_new(); + if (ctx == nullptr) { + return -1; + } + + auto ctx_deleter = defer(EVP_MD_CTX_free, ctx); + + rv = EVP_DigestInit_ex(ctx, meth, nullptr); + if (rv != 1) { + return -1; + } + + rv = EVP_DigestUpdate(ctx, s.c_str(), s.size()); + if (rv != 1) { + return -1; + } + + unsigned int mdlen = EVP_MD_size(meth); + + rv = EVP_DigestFinal_ex(ctx, res, &mdlen); + if (rv != 1) { + return -1; + } + + return 0; +} +} // namespace + +int sha256(uint8_t *res, const StringRef &s) { + return message_digest(res, EVP_sha256(), s); +} + +int sha1(uint8_t *res, const StringRef &s) { + return message_digest(res, EVP_sha1(), s); +} + +bool is_hex_string(const StringRef &s) { + if (s.size() % 2) { + return false; + } + + for (auto c : s) { + if (!is_hex_digit(c)) { + return false; + } + } + + return true; +} + +StringRef decode_hex(BlockAllocator &balloc, const StringRef &s) { + auto iov = make_byte_ref(balloc, s.size() + 1); + auto p = decode_hex(iov.base, s); + *p = '\0'; + return StringRef{iov.base, p}; +} + +StringRef extract_host(const StringRef &hostport) { + if (hostport[0] == '[') { + // assume this is IPv6 numeric address + auto p = std::find(std::begin(hostport), std::end(hostport), ']'); + if (p == std::end(hostport)) { + return StringRef{}; + } + if (p + 1 < std::end(hostport) && *(p + 1) != ':') { + return StringRef{}; + } + return StringRef{std::begin(hostport), p + 1}; + } + + auto p = std::find(std::begin(hostport), std::end(hostport), ':'); + if (p == std::begin(hostport)) { + return StringRef{}; + } + return StringRef{std::begin(hostport), p}; +} + +std::pair<StringRef, StringRef> split_hostport(const StringRef &hostport) { + if (hostport.empty()) { + return {}; + } + if (hostport[0] == '[') { + // assume this is IPv6 numeric address + auto p = std::find(std::begin(hostport), std::end(hostport), ']'); + if (p == std::end(hostport)) { + return {}; + } + if (p + 1 == std::end(hostport)) { + return {StringRef{std::begin(hostport) + 1, p}, {}}; + } + if (*(p + 1) != ':' || p + 2 == std::end(hostport)) { + return {}; + } + return {StringRef{std::begin(hostport) + 1, p}, + StringRef{p + 2, std::end(hostport)}}; + } + + auto p = std::find(std::begin(hostport), std::end(hostport), ':'); + if (p == std::begin(hostport)) { + return {}; + } + if (p == std::end(hostport)) { + return {StringRef{std::begin(hostport), p}, {}}; + } + if (p + 1 == std::end(hostport)) { + return {}; + } + + return {StringRef{std::begin(hostport), p}, + StringRef{p + 1, std::end(hostport)}}; +} + +std::mt19937 make_mt19937() { + std::random_device rd; + return std::mt19937(rd()); +} + +int daemonize(int nochdir, int noclose) { +#ifdef __APPLE__ + pid_t pid; + pid = fork(); + if (pid == -1) { + return -1; + } else if (pid > 0) { + _exit(EXIT_SUCCESS); + } + if (setsid() == -1) { + return -1; + } + pid = fork(); + if (pid == -1) { + return -1; + } else if (pid > 0) { + _exit(EXIT_SUCCESS); + } + if (nochdir == 0) { + if (chdir("/") == -1) { + return -1; + } + } + if (noclose == 0) { + if (freopen("/dev/null", "r", stdin) == nullptr) { + return -1; + } + if (freopen("/dev/null", "w", stdout) == nullptr) { + return -1; + } + if (freopen("/dev/null", "w", stderr) == nullptr) { + return -1; + } + } + return 0; +#else // !__APPLE__ + return daemon(nochdir, noclose); +#endif // !__APPLE__ +} + +StringRef rstrip(BlockAllocator &balloc, const StringRef &s) { + auto it = std::rbegin(s); + for (; it != std::rend(s) && (*it == ' ' || *it == '\t'); ++it) + ; + + auto len = it - std::rbegin(s); + if (len == 0) { + return s; + } + + return make_string_ref(balloc, StringRef{s.c_str(), s.size() - len}); +} + +#ifdef ENABLE_HTTP3 +int msghdr_get_local_addr(Address &dest, msghdr *msg, int family) { + switch (family) { + case AF_INET: + for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) { + auto pktinfo = reinterpret_cast<in_pktinfo *>(CMSG_DATA(cmsg)); + dest.len = sizeof(dest.su.in); + auto &sa = dest.su.in; + sa.sin_family = AF_INET; + sa.sin_addr = pktinfo->ipi_addr; + + return 0; + } + } + + return -1; + case AF_INET6: + for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { + auto pktinfo = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cmsg)); + dest.len = sizeof(dest.su.in6); + auto &sa = dest.su.in6; + sa.sin6_family = AF_INET6; + sa.sin6_addr = pktinfo->ipi6_addr; + return 0; + } + } + + return -1; + } + + return -1; +} + +unsigned int msghdr_get_ecn(msghdr *msg, int family) { + switch (family) { + case AF_INET: + for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_TOS && + cmsg->cmsg_len) { + return *reinterpret_cast<uint8_t *>(CMSG_DATA(cmsg)); + } + } + + return 0; + case AF_INET6: + for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_TCLASS && + cmsg->cmsg_len) { + return *reinterpret_cast<uint8_t *>(CMSG_DATA(cmsg)); + } + } + + return 0; + } + + return 0; +} + +int fd_set_send_ecn(int fd, int family, unsigned int ecn) { + switch (family) { + case AF_INET: + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &ecn, + static_cast<socklen_t>(sizeof(ecn))) == -1) { + return -1; + } + + return 0; + case AF_INET6: + if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &ecn, + static_cast<socklen_t>(sizeof(ecn))) == -1) { + return -1; + } + + return 0; + } + + return -1; +} +#endif // ENABLE_HTTP3 + +} // namespace util + +} // namespace nghttp2 |