/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* * Copyright 2020-2021 Couchbase, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "duration_parser.hxx" namespace couchbase::core::utils { /** * leading_int consumes the leading [0-9]* from s. */ static bool leading_int(std::string& s, std::int64_t& v) { v = 0; std::size_t i = 0; for (; i < s.size(); ++i) { auto c = s[i]; if (c < '0' || c > '9') { break; } if (v > std::int64_t((1LLU << 63U) - 1LLU) / 10) { return false; } v = v * 10 + std::int64_t(c) - '0'; if (v < 0) { return false; } } s = s.substr(i); return true; } /** * leading_fraction consumes the leading [0-9]* from s. * * It is used only for fractions, so does not return an error on overflow, * it just stops accumulating precision. */ static void leading_fraction(std::string& s, std::int64_t& x, std::uint32_t& scale) { std::size_t i = 0; scale = 1; bool overflow = false; for (; i < s.size(); ++i) { auto c = s[i]; if (c < '0' || c > '9') { break; } if (overflow) { continue; } if (x > std::int64_t((1LLU << 63LLU) - 1LLU) / 10) { // It's possible for overflow to give a positive number, so take care. overflow = true; continue; } auto y = x * 10 + std::int64_t(c) - '0'; if (y < 0) { overflow = true; continue; } x = y; scale *= 10; } s = s.substr(i); } std::chrono::nanoseconds parse_duration(const std::string& text) { // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ std::string s = text; std::chrono::nanoseconds d{ 0 }; bool neg{ false }; // Consume [-+]? if (!s.empty()) { auto c = s[0]; if (c == '-' || c == '+') { neg = c == '-'; s = s.substr(1); } } if (neg) { throw duration_parse_error("negative durations are not supported: " + text); } // Special case: if all that is left is "0", this is zero. if (s == "0") { return std::chrono::nanoseconds::zero(); } if (s.empty()) { throw duration_parse_error("invalid duration: " + text); } while (!s.empty()) { // The next character must be [0-9.] if (!(s[0] == '.' || ('0' <= s[0] && s[0] <= '9'))) { throw duration_parse_error("invalid duration: " + text); } // Consume [0-9]* auto pl = s.size(); std::int64_t v{ 0 }; // integer before decimal point if (!leading_int(s, v)) { throw duration_parse_error("invalid duration (leading_int overflow): " + text); } bool pre = pl != s.size(); // whether we consumed anything before a period std::int64_t f{ 0 }; // integer after decimal point std::uint32_t scale{ 1 }; // value = v + f/scale // Consume (\.[0-9]*)? bool post = false; if (!s.empty() && s[0] == '.') { s = s.substr(1); pl = s.size(); leading_fraction(s, f, scale); post = pl != s.size(); } if (!pre && !post) { // no digits (e.g. ".s" or "-.s") throw duration_parse_error("invalid duration: " + text); } // Consume unit. std::size_t i = 0; for (; i < s.size(); ++i) { auto c = s[i]; if (c == '.' || ('0' <= c && c <= '9')) { break; } } if (i == 0) { throw duration_parse_error("missing unit in duration: " + text); } auto u = s.substr(0, i); s = s.substr(i); if (u == "ns") { d += std::chrono::nanoseconds(v); /* no sub-nanoseconds, ignore 'f' */ } else if (u == "us" || u == "µs" /* U+00B5 = micro symbol */ || u == "μs" /* U+03BC = Greek letter mu */) { d += std::chrono::microseconds(v) + std::chrono::duration_cast(std::chrono::microseconds(f)) / scale; } else if (u == "ms") { d += std::chrono::milliseconds(v) + std::chrono::duration_cast(std::chrono::milliseconds(f)) / scale; } else if (u == "s") { d += std::chrono::seconds(v) + std::chrono::duration_cast(std::chrono::seconds(f)) / scale; } else if (u == "m") { d += std::chrono::minutes(v) + std::chrono::duration_cast(std::chrono::minutes(f)) / scale; } else if (u == "h") { d += std::chrono::hours(v) + std::chrono::duration_cast(std::chrono::hours(f)) / scale; } else { throw duration_parse_error(std::string("unknown unit ").append(u).append(" in duration ").append(text)); } } return d; } } // namespace couchbase::core::utils